Bevy Rewrite, attempt 2 #46

Merged
Schmarni-Dev merged 18 commits from bevy_for_real_this_time into dev 2025-06-30 05:04:00 -04:00
Schmarni-Dev commented 2025-06-28 07:00:46 -04:00 (Migrated from github.com)

This will not have a working wayland impl for now, thats the next goal

Summary by CodeRabbit

New Features

  • Migrated core rendering, input, and audio systems from StereoKit to Bevy and OpenXR, introducing new plugins for audio, lines, models, text, spatial nodes, hand tracking, controller input, flatscreen (mouse/keyboard) input, and play space management.
  • Added asynchronous channel integration for internal event handling.
  • Improved model, material, and text handling with Bevy ECS and asset management.

Bug Fixes

  • Enhanced bounding box calculations and spatial synchronization for more accurate object transforms and visibility.

Refactor

  • Replaced immediate-mode rendering and input polling with ECS-driven, event-based systems.
  • Removed legacy StereoKit and related code, streamlining dependencies and error handling.
  • Updated input handling to leverage Bevy’s parallel processing and input event system.

Chores

  • Updated dependencies and feature flags for improved compatibility and performance.
  • Added conditional compilation attributes to suppress warnings for unused code when features are disabled.
This will not have a working wayland impl for now, thats the next goal <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit **New Features** * Migrated core rendering, input, and audio systems from StereoKit to Bevy and OpenXR, introducing new plugins for audio, lines, models, text, spatial nodes, hand tracking, controller input, flatscreen (mouse/keyboard) input, and play space management. * Added asynchronous channel integration for internal event handling. * Improved model, material, and text handling with Bevy ECS and asset management. **Bug Fixes** * Enhanced bounding box calculations and spatial synchronization for more accurate object transforms and visibility. **Refactor** * Replaced immediate-mode rendering and input polling with ECS-driven, event-based systems. * Removed legacy StereoKit and related code, streamlining dependencies and error handling. * Updated input handling to leverage Bevy’s parallel processing and input event system. **Chores** * Updated dependencies and feature flags for improved compatibility and performance. * Added conditional compilation attributes to suppress warnings for unused code when features are disabled. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2025-06-28 07:01:07 -04:00 (Migrated from github.com)

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

This update transitions the codebase from a custom StereoKit-based rendering and input loop to a Bevy ECS-driven architecture with deep OpenXR integration. Major subsystems—including audio, 3D models, lines, and text rendering—are refactored to leverage Bevy's plugins, assets, and ECS. Input handling for VR controllers and hands now uses Bevy and OpenXR, with new plugins and systems for lifecycle and state management. The play space and spatial node management are also reworked for Bevy compatibility. Supporting infrastructure such as async channels, color conversion, and entity lifecycle management are introduced to facilitate this migration. Cargo configuration and dependencies are updated to reflect these architectural changes.

Changes

File(s) / Group Change Summary
Cargo.toml Updated default features, added/removed dependencies, added patch overrides, reworked build profiles.
src/core/bevy_channel.rs Added generic async channel integration for Bevy using Tokio MPSC; introduced BevyChannel and BevyChannelReader.
src/core/client.rs Added debug print statements to save_state; used Rust named format arguments.
src/core/color.rs Added ColorConvert trait and implementation for color conversion to Bevy's Color.
src/core/entity_handle.rs Introduced EntityHandlePlugin, EntityHandle wrapper, and channel-based entity destruction.
src/core/mod.rs Added new public modules: color, entity_handle, bevy_channel.
src/main.rs Replaced stereokit_loop with bevy_loop; refactored main control flow to Bevy ECS and OpenXR integration.
src/nodes/audio.rs Refactored audio subsystem to Bevy ECS and OpenXR; introduced AudioNodePlugin, changed Sound to use entity handles.
src/nodes/drawable/lines.rs Refactored lines rendering to Bevy ECS; added LinesNodePlugin, mesh generation, and entity management.
src/nodes/drawable/model.rs Refactored model/material system to Bevy ECS; added ModelNodePlugin, async loading, new material management.
src/nodes/drawable/text.rs Refactored text rendering to Bevy ECS with bevy_mesh_text_3d; added TextNodePlugin, event-driven spawning.
src/nodes/drawable/mod.rs Removed calls to old draw functions for lines, models, and text; commented out shaders module.
src/nodes/input/mod.rs Parallelized input handler processing with tracing spans; no logic changes.
src/nodes/items/camera.rs Removed Skia texture and material fields and update method from CameraItem.
src/nodes/spatial/mod.rs Integrated Bevy's Aabb and transform types; added SpatialNodePlugin and ECS transform sync system.
src/objects/input/sk_controller.rs Refactored controller input to Bevy/OpenXR; added ControllerPlugin, ECS systems, and new action/state handling.
src/objects/input/sk_hand.rs Refactored hand input to Bevy/OpenXR; added HandPlugin, ECS systems, new OpenXR joint/material handling.
src/objects/mod.rs Removed ServerObjects struct and related input management; removed stereokit imports; added RwLock import.
src/objects/play_space.rs Added PlaySpacePlugin for ECS-managed play space; refactored D-Bus bounds to use RwLock and ECS resources.
src/core/error.rs Removed StereoKitError variant from ServerError enum.
src/nodes/items/mod.rs Added conditional dead code allowance attribute to ItemType enum.
src/nodes/items/panel.rs Added conditional dead code allowance attribute to PanelItem::create method.
src/objects/input/eye_pointer.rs Removed entire update method from EyePointer.
src/objects/input/mouse_pointer.rs Added FlatscreenInputPlugin integrating mouse/keyboard input with Bevy ECS; refactored input update to use Bevy input events; updated key mapping.

Sequence Diagram(s)

Main Application Flow (Old vs. New)

Old: StereoKit Loop

sequenceDiagram
    participant Main
    participant StereoKit
    participant Nodes
    participant Audio
    participant Input

    Main->>StereoKit: initialize()
    loop frame
        StereoKit->>Nodes: draw()
        StereoKit->>Audio: update()
        StereoKit->>Input: process()
    end

New: Bevy/OpenXR Loop

sequenceDiagram
    participant Main
    participant BevyApp
    participant Plugins
    participant OpenXR
    participant Systems

    Main->>BevyApp: initialize()
    BevyApp->>Plugins: register (audio, model, lines, text, input, spatial, play space, etc.)
    BevyApp->>OpenXR: setup session
    loop frame (via Bevy schedule)
        BevyApp->>Systems: run (xr_step, camera update, audio, input, etc.)
        Systems->>OpenXR: poll frame state, wait frame, get input
        Systems->>BevyApp: update ECS world (components/resources)
    end

Audio Playback (New Flow)

sequenceDiagram
    participant AudioNodePlugin
    participant BevyApp
    participant Sound
    participant BevyEntity

    Sound->>AudioNodePlugin: set play/stop flag
    AudioNodePlugin->>BevyApp: system update (update_sound_event)
    BevyApp->>BevyEntity: spawn/modify audio entity, play/stop sound

Model Loading and Material Application (New Flow)

sequenceDiagram
    participant ModelNodePlugin
    participant BevyApp
    participant AssetServer
    participant Model
    participant ModelPart

    Model->>ModelNodePlugin: request load (via channel)
    ModelNodePlugin->>AssetServer: load model asset
    AssetServer->>BevyApp: asset ready event
    BevyApp->>Model: spawn entity, create ModelPart(s)
    ModelPart->>ModelNodePlugin: request material update
    ModelNodePlugin->>BevyApp: system update (apply_materials)
    BevyApp->>ModelPart: update Bevy material component

XR Controller Update (New Flow)

sequenceDiagram
    participant ControllerPlugin
    participant BevyApp
    participant OpenXR
    participant Controller

    BevyApp->>ControllerPlugin: system update (update)
    ControllerPlugin->>OpenXR: poll action states, locate spaces
    ControllerPlugin->>Controller: update transform, input state, enabled/disabled
    Controller->>BevyApp: update ECS components/resources

Text Spawning (New Flow)

sequenceDiagram
    participant Text
    participant BevyChannel
    participant TextNodePlugin
    participant BevyApp

    Text->>BevyChannel: send instance for spawn/update
    TextNodePlugin->>BevyApp: system update (spawn_text)
    BevyApp->>Text: spawn/despawn Bevy mesh text entity

Play Space Update and D-Bus Exposure (New Flow)

sequenceDiagram
    participant PlaySpacePlugin
    participant BevyApp
    participant OpenXR
    participant DbusConnection
    participant PlaySpaceBounds

    PlaySpacePlugin->>BevyApp: system update (update)
    BevyApp->>OpenXR: locate stage space
    BevyApp->>PlaySpace: update tracking state, transform, bounds
    PlaySpace->>PlaySpaceBounds: update bounds data
    PlaySpaceBounds->>DbusConnection: expose bounds property via D-Bus

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9347072249 and aec875a1bf.

Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • Cargo.toml (4 hunks)
  • src/core/client.rs (2 hunks)
  • src/core/error.rs (0 hunks)
  • src/main.rs (4 hunks)
  • src/nodes/drawable/lines.rs (2 hunks)
  • src/nodes/drawable/mod.rs (1 hunks)
  • src/nodes/drawable/model.rs (4 hunks)
  • src/nodes/drawable/text.rs (2 hunks)
  • src/nodes/input/mod.rs (2 hunks)
  • src/nodes/items/camera.rs (2 hunks)
  • src/nodes/items/mod.rs (1 hunks)
  • src/nodes/items/panel.rs (1 hunks)
  • src/nodes/spatial/mod.rs (5 hunks)
  • src/objects/input/eye_pointer.rs (0 hunks)
  • src/objects/input/mouse_pointer.rs (8 hunks)
  • src/objects/input/sk_controller.rs (2 hunks)
  • src/objects/input/sk_hand.rs (2 hunks)
  • src/objects/mod.rs (1 hunks)
  • src/objects/play_space.rs (2 hunks)
Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.
<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- This is an auto-generated comment: failure by coderabbit.ai --> > [!CAUTION] > ## Review failed > > The pull request is closed. <!-- end of auto-generated comment: failure by coderabbit.ai --> <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough This update transitions the codebase from a custom StereoKit-based rendering and input loop to a Bevy ECS-driven architecture with deep OpenXR integration. Major subsystems—including audio, 3D models, lines, and text rendering—are refactored to leverage Bevy's plugins, assets, and ECS. Input handling for VR controllers and hands now uses Bevy and OpenXR, with new plugins and systems for lifecycle and state management. The play space and spatial node management are also reworked for Bevy compatibility. Supporting infrastructure such as async channels, color conversion, and entity lifecycle management are introduced to facilitate this migration. Cargo configuration and dependencies are updated to reflect these architectural changes. ## Changes | File(s) / Group | Change Summary | |---------------------------------------------------------|---------------| | Cargo.toml | Updated default features, added/removed dependencies, added patch overrides, reworked build profiles. | | src/core/bevy_channel.rs | Added generic async channel integration for Bevy using Tokio MPSC; introduced `BevyChannel` and `BevyChannelReader`. | | src/core/client.rs | Added debug print statements to `save_state`; used Rust named format arguments. | | src/core/color.rs | Added `ColorConvert` trait and implementation for color conversion to Bevy's `Color`. | | src/core/entity_handle.rs | Introduced `EntityHandlePlugin`, `EntityHandle` wrapper, and channel-based entity destruction. | | src/core/mod.rs | Added new public modules: `color`, `entity_handle`, `bevy_channel`. | | src/main.rs | Replaced `stereokit_loop` with `bevy_loop`; refactored main control flow to Bevy ECS and OpenXR integration. | | src/nodes/audio.rs | Refactored audio subsystem to Bevy ECS and OpenXR; introduced `AudioNodePlugin`, changed `Sound` to use entity handles. | | src/nodes/drawable/lines.rs | Refactored lines rendering to Bevy ECS; added `LinesNodePlugin`, mesh generation, and entity management. | | src/nodes/drawable/model.rs | Refactored model/material system to Bevy ECS; added `ModelNodePlugin`, async loading, new material management. | | src/nodes/drawable/text.rs | Refactored text rendering to Bevy ECS with `bevy_mesh_text_3d`; added `TextNodePlugin`, event-driven spawning. | | src/nodes/drawable/mod.rs | Removed calls to old draw functions for lines, models, and text; commented out `shaders` module. | | src/nodes/input/mod.rs | Parallelized input handler processing with tracing spans; no logic changes. | | src/nodes/items/camera.rs | Removed Skia texture and material fields and update method from `CameraItem`. | | src/nodes/spatial/mod.rs | Integrated Bevy's `Aabb` and transform types; added `SpatialNodePlugin` and ECS transform sync system. | | src/objects/input/sk_controller.rs | Refactored controller input to Bevy/OpenXR; added `ControllerPlugin`, ECS systems, and new action/state handling. | | src/objects/input/sk_hand.rs | Refactored hand input to Bevy/OpenXR; added `HandPlugin`, ECS systems, new OpenXR joint/material handling. | | src/objects/mod.rs | Removed `ServerObjects` struct and related input management; removed stereokit imports; added `RwLock` import. | | src/objects/play_space.rs | Added `PlaySpacePlugin` for ECS-managed play space; refactored D-Bus bounds to use `RwLock` and ECS resources. | | src/core/error.rs | Removed `StereoKitError` variant from `ServerError` enum. | | src/nodes/items/mod.rs | Added conditional dead code allowance attribute to `ItemType` enum. | | src/nodes/items/panel.rs | Added conditional dead code allowance attribute to `PanelItem::create` method. | | src/objects/input/eye_pointer.rs | Removed entire `update` method from `EyePointer`. | | src/objects/input/mouse_pointer.rs | Added `FlatscreenInputPlugin` integrating mouse/keyboard input with Bevy ECS; refactored input update to use Bevy input events; updated key mapping. | ## Sequence Diagram(s) ### Main Application Flow (Old vs. New) #### Old: StereoKit Loop ```mermaid sequenceDiagram participant Main participant StereoKit participant Nodes participant Audio participant Input Main->>StereoKit: initialize() loop frame StereoKit->>Nodes: draw() StereoKit->>Audio: update() StereoKit->>Input: process() end ``` #### New: Bevy/OpenXR Loop ```mermaid sequenceDiagram participant Main participant BevyApp participant Plugins participant OpenXR participant Systems Main->>BevyApp: initialize() BevyApp->>Plugins: register (audio, model, lines, text, input, spatial, play space, etc.) BevyApp->>OpenXR: setup session loop frame (via Bevy schedule) BevyApp->>Systems: run (xr_step, camera update, audio, input, etc.) Systems->>OpenXR: poll frame state, wait frame, get input Systems->>BevyApp: update ECS world (components/resources) end ``` ### Audio Playback (New Flow) ```mermaid sequenceDiagram participant AudioNodePlugin participant BevyApp participant Sound participant BevyEntity Sound->>AudioNodePlugin: set play/stop flag AudioNodePlugin->>BevyApp: system update (update_sound_event) BevyApp->>BevyEntity: spawn/modify audio entity, play/stop sound ``` ### Model Loading and Material Application (New Flow) ```mermaid sequenceDiagram participant ModelNodePlugin participant BevyApp participant AssetServer participant Model participant ModelPart Model->>ModelNodePlugin: request load (via channel) ModelNodePlugin->>AssetServer: load model asset AssetServer->>BevyApp: asset ready event BevyApp->>Model: spawn entity, create ModelPart(s) ModelPart->>ModelNodePlugin: request material update ModelNodePlugin->>BevyApp: system update (apply_materials) BevyApp->>ModelPart: update Bevy material component ``` ### XR Controller Update (New Flow) ```mermaid sequenceDiagram participant ControllerPlugin participant BevyApp participant OpenXR participant Controller BevyApp->>ControllerPlugin: system update (update) ControllerPlugin->>OpenXR: poll action states, locate spaces ControllerPlugin->>Controller: update transform, input state, enabled/disabled Controller->>BevyApp: update ECS components/resources ``` ### Text Spawning (New Flow) ```mermaid sequenceDiagram participant Text participant BevyChannel participant TextNodePlugin participant BevyApp Text->>BevyChannel: send instance for spawn/update TextNodePlugin->>BevyApp: system update (spawn_text) BevyApp->>Text: spawn/despawn Bevy mesh text entity ``` ### Play Space Update and D-Bus Exposure (New Flow) ```mermaid sequenceDiagram participant PlaySpacePlugin participant BevyApp participant OpenXR participant DbusConnection participant PlaySpaceBounds PlaySpacePlugin->>BevyApp: system update (update) BevyApp->>OpenXR: locate stage space BevyApp->>PlaySpace: update tracking state, transform, bounds PlaySpace->>PlaySpaceBounds: update bounds data PlaySpaceBounds->>DbusConnection: expose bounds property via D-Bus ``` </details> <!-- walkthrough_end --> <!-- internal state start --> <!-- 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 --> <!-- internal state end --> --- <details> <summary>📜 Recent review details</summary> **Configuration used: CodeRabbit UI** **Review profile: CHILL** **Plan: Pro** <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 9347072249f18a9e2f9612626429245b5d1c4ed5 and aec875a1bf288ae8ef30532f7892a9dd09cef7da. </details> <details> <summary>⛔ Files ignored due to path filters (1)</summary> * `Cargo.lock` is excluded by `!**/*.lock` </details> <details> <summary>📒 Files selected for processing (19)</summary> * `Cargo.toml` (4 hunks) * `src/core/client.rs` (2 hunks) * `src/core/error.rs` (0 hunks) * `src/main.rs` (4 hunks) * `src/nodes/drawable/lines.rs` (2 hunks) * `src/nodes/drawable/mod.rs` (1 hunks) * `src/nodes/drawable/model.rs` (4 hunks) * `src/nodes/drawable/text.rs` (2 hunks) * `src/nodes/input/mod.rs` (2 hunks) * `src/nodes/items/camera.rs` (2 hunks) * `src/nodes/items/mod.rs` (1 hunks) * `src/nodes/items/panel.rs` (1 hunks) * `src/nodes/spatial/mod.rs` (5 hunks) * `src/objects/input/eye_pointer.rs` (0 hunks) * `src/objects/input/mouse_pointer.rs` (8 hunks) * `src/objects/input/sk_controller.rs` (2 hunks) * `src/objects/input/sk_hand.rs` (2 hunks) * `src/objects/mod.rs` (1 hunks) * `src/objects/play_space.rs` (2 hunks) </details> </details> <!-- finishing_touch_checkbox_start --> <details open="true"> <summary>✨ Finishing Touches</summary> - [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings </details> <!-- finishing_touch_checkbox_end --> <!-- tips_start --> --- 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. <details> <summary>❤️ Share</summary> - [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code) </details> <details> <summary>🪧 Tips</summary> ### Chat There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=StardustXR/server&utm_content=46): - Review comments: Directly reply to a review comment made by CodeRabbit. Example: - `I pushed a fix in commit <commit_id>, please review it.` - `Explain this complex logic.` - `Open a follow-up GitHub issue for this discussion.` - Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples: - `@coderabbitai explain this code block.` - `@coderabbitai modularize this function.` - PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples: - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.` - `@coderabbitai read src/utils.ts and explain its main purpose.` - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.` - `@coderabbitai help me debug CodeRabbit configuration file.` ### Support Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. ### CodeRabbit Commands (Invoked using PR comments) - `@coderabbitai pause` to pause the reviews on a PR. - `@coderabbitai resume` to resume the paused reviews. - `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository. - `@coderabbitai full review` to do a full review from scratch and review all the files again. - `@coderabbitai summary` to regenerate the summary of the PR. - `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR. - `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR. - `@coderabbitai resolve` resolve all the CodeRabbit review comments. - `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository. - `@coderabbitai help` to get help. ### Other keywords and placeholders - Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed. - Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description. - Add `@coderabbitai` anywhere in the PR title to generate the title automatically. ### CodeRabbit Configuration File (`.coderabbit.yaml`) - You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository. - Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information. - If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json` ### Documentation and Community - Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit. - Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback. - Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements. </details> <!-- tips_end -->
Schmarni-Dev commented 2025-06-28 14:47:29 -04:00 (Migrated from github.com)

@coderabbitai review

@coderabbitai review
coderabbitai[bot] commented 2025-06-28 14:47:34 -04:00 (Migrated from github.com)
Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

<!-- This is an auto-generated reply by CodeRabbit --> <details> <summary>✅ Actions performed</summary> Review triggered. > Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused. </details>
coderabbitai[bot] (Migrated from github.com) reviewed 2025-06-28 14:53:16 -04:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 21

🧹 Nitpick comments (15)
src/objects/mod.rs (3)

36-39: Consider removing commented code instead of keeping it.

Since this is a major architectural rewrite transitioning to Bevy/OpenXR, keeping the old StereoKit-based controller and hand code commented out adds clutter. Version control preserves the history if you need to reference the old implementation.

Consider either:

  1. Removing all the commented code if the new Bevy-based input system is functional
  2. Adding a TODO comment explaining when this code will be removed if it's temporarily needed for reference

Also applies to: 44-47, 98-99, 167-174, 177-180, 192-195


73-74: Consider adding explicit type annotation for clarity.

While type inference works here, adding an explicit type would improve code readability and make the intent clearer.

-let play_space_data = Arc::new(RwLock::default());
+let play_space_data: Arc<RwLock<Vec<(f64, f64)>>> = Arc::new(RwLock::default());

1-1: Remove #![allow(unused)] to catch potential issues.

This attribute suppresses warnings that could help identify dead code during the refactoring. Consider removing it to ensure all imports and code are actually used.

Remove the #![allow(unused)] attribute and address any resulting warnings by removing unused imports or code.

src/core/entity_handle.rs (1)

22-28: Enhance error message with entity information.

Consider including the entity ID in the error message to aid debugging.

-if DESTROY.send(self.0).is_none() {
-    error!("Entity Destroy channel not open");
-}
+if DESTROY.send(self.0).is_none() {
+    error!("Failed to send entity {:?} to destroy channel - channel not open", self.0);
+}
src/core/bevy_channel.rs (1)

6-21: Add documentation for public API.

Consider adding documentation to explain the purpose and usage of these types.

+/// A Bevy-compatible channel reader that holds the receiving end of an unbounded channel.
 #[derive(Resource)]
 pub struct BevyChannelReader<T: Send + Sync + 'static>(mpsc::UnboundedReceiver<T>);
+
+/// A lazily-initialized channel for sending messages between Bevy systems.
+/// Must be initialized with `init()` before use.
 pub struct BevyChannel<T: Send + Sync + 'static>(OnceLock<mpsc::UnboundedSender<T>>);
Cargo.toml (1)

37-43: Remove or uncomment the release profile.

The commented release profile adds clutter without providing value.

Either remove these lines entirely or uncomment them if the profile is needed.

src/objects/play_space.rs (1)

155-158: Consider performance implications of cloning bounds data.

The bounds() property clones the entire vector on each access, which could be inefficient for large bounds or frequent access.

If performance becomes an issue, consider:

  1. Caching the bounds and only updating when changed
  2. Returning a reference or using a more efficient data structure
  3. Using a change notification pattern instead of polling
src/main.rs (1)

66-75: Clean up potentially unused StereoKit imports.

Since the main loop has been migrated to Bevy, these StereoKit imports might be unused and should be removed if not needed.

Run the following script to check for unused imports:

#!/bin/bash
# Description: Check if StereoKit types are still used in main.rs

# Check for usage of each StereoKit type
echo "Checking StereoKit type usage in main.rs..."
for type in "Material" "Shader" "AppMode" "DepthMode" "DisplayBlend" "OriginMode" "QuitReason" "SkSettings" "sk_quit" "Handed" "Input" "LogLevel" "Renderer" "SHCubemap" "Tex" "TexFormat" "TexType" "Ui" "Color128" "SphericalHarmonics" "Time"; do
    echo -n "Checking $type: "
    rg -c "\b$type\b" src/main.rs || echo "Not found"
done
src/objects/input/sk_hand.rs (1)

80-85: Improve error logging for hand joint location.

The error message could be more specific about which hand failed.

 		session
 			.locate_hand_joints(tracker, &ref_space, state.predicted_display_time)
-			.inspect_err(|err| error!("Error while locating hand joints"))
+			.inspect_err(|err| error!("Error locating {} hand joints: {}", 
+				match hand.side { HandSide::Left => "left", HandSide::Right => "right" }, 
+				err))
 			.ok()
src/nodes/drawable/lines.rs (2)

189-216: Consider adding documentation for the index generation logic.

The hardcoded index arrays and the mesh generation logic are complex. Adding comments explaining the vertex layout and how indices form the cylindrical mesh would improve maintainability.

Add documentation like:

// Each line point generates 8 vertices arranged in a circle
// Vertices 0-7 form the current ring, 8-15 form the next ring
// The INDECIES array connects these rings to form a cylinder
const INDECIES: [u32; 48] = [
    // Connect ring segments: vertex pairs form triangles
    0, 8, 1, 8, 9, 1,   // Segment 0-1
    1, 9, 2, 9, 10, 2,  // Segment 1-2
    // ... etc
];

73-85: Reduce code duplication in entity spawning.

The entity spawning logic is duplicated between empty and non-empty line cases.

Extract common spawning logic:

let entity = match lines.entity.get() {
    Some(e) => cmds.entity(**e),
    None => {
        let e = cmds.spawn((
            Name::new("LinesNode"),
            SpatialNode(Arc::downgrade(&lines.spatial)),
        ));
        _ = lines.entity.set(e.id().into());
        e
    }
};

if lines_data.is_empty() {
    entity.remove::<Mesh3d>();
    continue;
}

// ... mesh generation ...

entity
    .insert(Mesh3d(meshes.add(mesh)))
    .insert_if_missing(MeshMaterial3d(materials.add(PbrMaterial {
        color: Color::WHITE,
        roughness: 1.0,
        alpha_mode: AlphaMode::Opaque,
        ..default()
    })));

Also applies to: 167-186

src/objects/input/sk_controller.rs (3)

73-88: Consider extracting binding definitions to constants.

The implementation is correct, but could be more maintainable with binding definitions as constants.

+const OCULUS_BINDINGS: &[(&str, &[&str])] = &[
+    ("trigger", &["/user/hand/left/input/trigger/value", "/user/hand/right/input/trigger/value"]),
+    ("stick_click", &["/user/hand/left/input/thumbstick/click", "/user/hand/right/input/thumbstick/click"]),
+    // ... etc
+];
+
 fn suggest_bindings(
     instance: Res<OxrInstance>,
     actions: Res<Actions>,
     mut suggest: EventWriter<OxrSuggestActionBinding>,
 ) {
     let mut bind_all = |interaction_profile: &'static str,
                         bindings: &[(openxr::sys::Action, &[&'static str])]| {
         // ... existing implementation
     };
-    bind_all(
-        "/interaction_profiles/oculus/touch_controller",
-        &[
-            // ... inline bindings
-        ],
-    );
+    bind_all(
+        "/interaction_profiles/oculus/touch_controller",
+        &OCULUS_BINDINGS.iter().map(|(name, paths)| {
+            (actions.get_action(name).as_raw(), *paths)
+        }).collect::<Vec<_>>(),
+    );

291-291: Document or make configurable the hardcoded "Cursor" model part name.

The code assumes the cursor GLB contains a part named "Cursor". This requirement should be documented or made configurable.

+const CURSOR_PART_NAME: &str = "Cursor"; // Expected model part name in cursor.glb
 let model_part = model.get_model_part("Cursor".to_string()).unwrap();

Consider also handling the error case more gracefully instead of using unwrap().


361-361: Consider making the controller scale factor configurable.

The hardcoded scale of 0.02 might not be appropriate for all use cases.

+const CONTROLLER_SCALE: f32 = 0.02;
 self.input
     .spatial
-    .set_local_transform(world_transform * Mat4::from_scale(Vec3::splat(0.02)));
+    .set_local_transform(world_transform * Mat4::from_scale(Vec3::splat(CONTROLLER_SCALE)));
src/nodes/drawable/model.rs (1)

279-280: Consider excluding spherical harmonics from material hash.

The comment indicates spherical harmonics should be per-instance, but it's currently included in the hash. This might prevent material deduplication when it shouldn't.

-// should always be the same, TODO: make the spherical harmonics buffer a per mesh instance thing
-mat.spherical_harmonics.hash(state);
+// TODO: make the spherical harmonics buffer a per mesh instance thing
+// Don't include in hash for now as it should be instance-specific
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd1c6ed0cf and 9347072249.

Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • Cargo.toml (4 hunks)
  • src/core/bevy_channel.rs (1 hunks)
  • src/core/client.rs (2 hunks)
  • src/core/color.rs (1 hunks)
  • src/core/entity_handle.rs (1 hunks)
  • src/core/mod.rs (1 hunks)
  • src/main.rs (6 hunks)
  • src/nodes/audio.rs (2 hunks)
  • src/nodes/drawable/lines.rs (2 hunks)
  • src/nodes/drawable/mod.rs (0 hunks)
  • src/nodes/drawable/model.rs (4 hunks)
  • src/nodes/drawable/text.rs (2 hunks)
  • src/nodes/input/mod.rs (4 hunks)
  • src/nodes/items/camera.rs (2 hunks)
  • src/nodes/spatial/mod.rs (6 hunks)
  • src/objects/input/sk_controller.rs (2 hunks)
  • src/objects/input/sk_hand.rs (3 hunks)
  • src/objects/mod.rs (6 hunks)
  • src/objects/play_space.rs (2 hunks)
💤 Files with no reviewable changes (1)
  • src/nodes/drawable/mod.rs
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/core/entity_handle.rs (1)
src/core/bevy_channel.rs (1)
  • new (10-12)
src/nodes/drawable/model.rs (12)
src/core/resource.rs (1)
  • get_resource_file (21-49)
src/nodes/alias.rs (2)
  • alias (107-107)
  • add_to (59-68)
src/nodes/items/mod.rs (8)
  • parent (389-389)
  • node (80-80)
  • node (121-121)
  • node (314-314)
  • node (401-401)
  • add_to (74-103)
  • add_to (173-196)
  • add_to (312-324)
src/core/bevy_channel.rs (1)
  • new (10-12)
src/nodes/spatial/mod.rs (9)
  • new (109-119)
  • build (26-31)
  • parent (335-335)
  • parent (347-347)
  • parent (482-482)
  • parent (496-496)
  • node (138-140)
  • node (312-312)
  • add_to (120-136)
src/core/registry.rs (3)
  • new (14-16)
  • new (143-145)
  • default (101-103)
src/nodes/drawable/lines.rs (9)
  • build (31-33)
  • update_visibillity (36-52)
  • cmds (43-44)
  • cmds (47-48)
  • node (229-230)
  • node (234-234)
  • node (241-241)
  • node (254-254)
  • add_to (228-250)
src/nodes/drawable/text.rs (10)
  • build (34-47)
  • app (38-39)
  • app (45-45)
  • update_visibillity (50-66)
  • cmds (57-58)
  • cmds (61-62)
  • node (211-211)
  • node (232-232)
  • node (239-239)
  • add_to (208-224)
src/core/entity_handle.rs (1)
  • build (7-10)
src/nodes/drawable/mod.rs (3)
  • parent (119-119)
  • parent (137-137)
  • parent (155-155)
src/nodes/input/handler.rs (1)
  • add_to (12-24)
src/core/client.rs (1)
  • state (64-67)
🔇 Additional comments (20)
src/core/client.rs (1)

29-29: LGTM!

Adding the warn macro import is a good addition for enhanced logging capabilities.

src/core/mod.rs (1)

10-12: LGTM!

The addition of these three modules (color, entity_handle, bevy_channel) appropriately supports the Bevy integration effort by providing essential infrastructure for color conversion, entity lifecycle management, and async communication.

src/nodes/items/camera.rs (1)

137-145: Complete the material system migration.

The material creation and application code is commented out, indicating incomplete migration from StereoKit to Bevy rendering. This leaves the camera item functionality partially disabled.

Please complete the Bevy material system integration or create a TODO/issue to track this work. The current state may result in non-functional camera preview materials.

src/nodes/input/mod.rs (2)

11-11: LGTM!

Adding the debug_span import enables better observability for input processing.


124-124: Excellent tracing implementation!

The debug spans are well-structured and provide valuable observability into input processing phases. The explicit dropping of the serialization span before starting the client input span demonstrates good understanding of span lifecycle management.

Also applies to: 141-141, 167-167, 169-169

src/core/color.rs (1)

1-12: Well-implemented color conversion trait.

The ColorConvert trait provides a clean abstraction for migrating from StereoKit to Bevy color systems. The detailed comment explaining the sRGBA interpretation workaround is valuable technical documentation that addresses the impedance mismatch between the two systems.

src/core/entity_handle.rs (2)

4-17: Clean plugin implementation for entity lifecycle management.

The channel-based approach for entity destruction is well-designed, running in PreUpdate to ensure entities are despawned before other systems process them.


13-17: Bevy’s despawn is idempotent—no panic or crash

Bevy’s EntityCommands::despawn will only emit a warning if the entity is already gone or invalid; it won’t panic at runtime. You have a couple of options depending on your needs:

  • To suppress warnings when an entity doesn’t exist, replace
    cmds.entity(e).despawn();
    
    with
    cmds.entity(e).try_despawn();
    
  • To also remove any child entities, use
    cmds.entity(e).despawn_recursive();
    

No additional validity check is required unless you specifically need to silence logs or recursively destroy children.

src/objects/play_space.rs (1)

22-54: Well-structured Bevy plugin for play space management.

The plugin properly handles OpenXR session lifecycle and integrates cleanly with the Bevy ECS architecture.

src/nodes/audio.rs (1)

52-91: Well-structured audio system implementation!

The audio update system properly handles entity spawning, spatial audio configuration, and playback control with appropriate synchronization primitives.

src/nodes/drawable/text.rs (2)

82-86: Potential race condition with font database swapping.

The temporary swapping of font databases could cause issues if the system runs in parallel or if an error occurs before swapping back.

Consider using a guard pattern to ensure the database is always restored:

struct FontDbGuard<'a> {
    font_system: &'a mut cosmic_text::FontSystem,
    old_db: Option<&'a mut cosmic_text::fontdb::Database>,
}

impl Drop for FontDbGuard<'_> {
    fn drop(&mut self) {
        if let Some(db) = self.old_db.take() {
            std::mem::swap(self.font_system.db_mut(), db);
        }
    }
}

Also applies to: 137-139


88-92: Verify the inverted text alignment mapping.

The text alignment mapping appears inverted (Left maps to Right, Right maps to Left). This might be intentional due to coordinate system differences, but it's worth confirming.

Is this inversion intentional? If so, consider adding a comment explaining why the alignment is inverted.

src/nodes/spatial/mod.rs (2)

24-32: Plugin integration looks good!

The system ordering is correct - updating spatial nodes before TransformPropagate ensures transforms are properly synchronized before Bevy propagates them through the hierarchy.


62-64: Component design is well-structured.

Good use of the #[require] attribute to ensure Transform components are always present, and weak references prevent circular dependencies.

src/objects/input/sk_controller.rs (2)

166-167: Document the GLTF loading issue mentioned in the comment.

The comment mentions a "stupid bevy gltf loading issue (rotated 180 degrees on the y axis)" but doesn't explain the workaround or link to an issue tracker.

Could you provide more context about this GLTF loading issue? Is there a Bevy issue tracker link or a more detailed explanation of the workaround being applied?


175-207: Space lifecycle management is well-implemented.

Proper creation and cleanup of OpenXR action spaces with appropriate null checks.

src/nodes/drawable/model.rs (4)

27-38: Plugin setup with proper system ordering.

Good use of .chain() to ensure model parts are generated before materials are applied.


43-59: Visibility management implementation is consistent with other drawable nodes.

Good use of insert_recursive to propagate visibility to all children.


81-115: Efficient material application with proper deduplication.

Good implementation of deferred material updates using the material registry to prevent duplicates.


415-457: Clean material abstraction with proper asset loading.

Good separation between the material definition and Bevy's PbrMaterial.

**Actionable comments posted: 21** <details> <summary>🧹 Nitpick comments (15)</summary><blockquote> <details> <summary>src/objects/mod.rs (3)</summary><blockquote> `36-39`: **Consider removing commented code instead of keeping it.** Since this is a major architectural rewrite transitioning to Bevy/OpenXR, keeping the old StereoKit-based controller and hand code commented out adds clutter. Version control preserves the history if you need to reference the old implementation. Consider either: 1. Removing all the commented code if the new Bevy-based input system is functional 2. Adding a TODO comment explaining when this code will be removed if it's temporarily needed for reference Also applies to: 44-47, 98-99, 167-174, 177-180, 192-195 --- `73-74`: **Consider adding explicit type annotation for clarity.** While type inference works here, adding an explicit type would improve code readability and make the intent clearer. ```diff -let play_space_data = Arc::new(RwLock::default()); +let play_space_data: Arc<RwLock<Vec<(f64, f64)>>> = Arc::new(RwLock::default()); ``` --- `1-1`: **Remove `#![allow(unused)]` to catch potential issues.** This attribute suppresses warnings that could help identify dead code during the refactoring. Consider removing it to ensure all imports and code are actually used. Remove the `#![allow(unused)]` attribute and address any resulting warnings by removing unused imports or code. </blockquote></details> <details> <summary>src/core/entity_handle.rs (1)</summary><blockquote> `22-28`: **Enhance error message with entity information.** Consider including the entity ID in the error message to aid debugging. ```diff -if DESTROY.send(self.0).is_none() { - error!("Entity Destroy channel not open"); -} +if DESTROY.send(self.0).is_none() { + error!("Failed to send entity {:?} to destroy channel - channel not open", self.0); +} ``` </blockquote></details> <details> <summary>src/core/bevy_channel.rs (1)</summary><blockquote> `6-21`: **Add documentation for public API.** Consider adding documentation to explain the purpose and usage of these types. ```diff +/// A Bevy-compatible channel reader that holds the receiving end of an unbounded channel. #[derive(Resource)] pub struct BevyChannelReader<T: Send + Sync + 'static>(mpsc::UnboundedReceiver<T>); + +/// A lazily-initialized channel for sending messages between Bevy systems. +/// Must be initialized with `init()` before use. pub struct BevyChannel<T: Send + Sync + 'static>(OnceLock<mpsc::UnboundedSender<T>>); ``` </blockquote></details> <details> <summary>Cargo.toml (1)</summary><blockquote> `37-43`: **Remove or uncomment the release profile.** The commented release profile adds clutter without providing value. Either remove these lines entirely or uncomment them if the profile is needed. </blockquote></details> <details> <summary>src/objects/play_space.rs (1)</summary><blockquote> `155-158`: **Consider performance implications of cloning bounds data.** The `bounds()` property clones the entire vector on each access, which could be inefficient for large bounds or frequent access. If performance becomes an issue, consider: 1. Caching the bounds and only updating when changed 2. Returning a reference or using a more efficient data structure 3. Using a change notification pattern instead of polling </blockquote></details> <details> <summary>src/main.rs (1)</summary><blockquote> `66-75`: **Clean up potentially unused StereoKit imports.** Since the main loop has been migrated to Bevy, these StereoKit imports might be unused and should be removed if not needed. Run the following script to check for unused imports: ```shell #!/bin/bash # Description: Check if StereoKit types are still used in main.rs # Check for usage of each StereoKit type echo "Checking StereoKit type usage in main.rs..." for type in "Material" "Shader" "AppMode" "DepthMode" "DisplayBlend" "OriginMode" "QuitReason" "SkSettings" "sk_quit" "Handed" "Input" "LogLevel" "Renderer" "SHCubemap" "Tex" "TexFormat" "TexType" "Ui" "Color128" "SphericalHarmonics" "Time"; do echo -n "Checking $type: " rg -c "\b$type\b" src/main.rs || echo "Not found" done ``` </blockquote></details> <details> <summary>src/objects/input/sk_hand.rs (1)</summary><blockquote> `80-85`: **Improve error logging for hand joint location.** The error message could be more specific about which hand failed. ```diff session .locate_hand_joints(tracker, &ref_space, state.predicted_display_time) - .inspect_err(|err| error!("Error while locating hand joints")) + .inspect_err(|err| error!("Error locating {} hand joints: {}", + match hand.side { HandSide::Left => "left", HandSide::Right => "right" }, + err)) .ok() ``` </blockquote></details> <details> <summary>src/nodes/drawable/lines.rs (2)</summary><blockquote> `189-216`: **Consider adding documentation for the index generation logic.** The hardcoded index arrays and the mesh generation logic are complex. Adding comments explaining the vertex layout and how indices form the cylindrical mesh would improve maintainability. Add documentation like: ```rust // Each line point generates 8 vertices arranged in a circle // Vertices 0-7 form the current ring, 8-15 form the next ring // The INDECIES array connects these rings to form a cylinder const INDECIES: [u32; 48] = [ // Connect ring segments: vertex pairs form triangles 0, 8, 1, 8, 9, 1, // Segment 0-1 1, 9, 2, 9, 10, 2, // Segment 1-2 // ... etc ]; ``` --- `73-85`: **Reduce code duplication in entity spawning.** The entity spawning logic is duplicated between empty and non-empty line cases. Extract common spawning logic: ```rust let entity = match lines.entity.get() { Some(e) => cmds.entity(**e), None => { let e = cmds.spawn(( Name::new("LinesNode"), SpatialNode(Arc::downgrade(&lines.spatial)), )); _ = lines.entity.set(e.id().into()); e } }; if lines_data.is_empty() { entity.remove::<Mesh3d>(); continue; } // ... mesh generation ... entity .insert(Mesh3d(meshes.add(mesh))) .insert_if_missing(MeshMaterial3d(materials.add(PbrMaterial { color: Color::WHITE, roughness: 1.0, alpha_mode: AlphaMode::Opaque, ..default() }))); ``` Also applies to: 167-186 </blockquote></details> <details> <summary>src/objects/input/sk_controller.rs (3)</summary><blockquote> `73-88`: **Consider extracting binding definitions to constants.** The implementation is correct, but could be more maintainable with binding definitions as constants. ```diff +const OCULUS_BINDINGS: &[(&str, &[&str])] = &[ + ("trigger", &["/user/hand/left/input/trigger/value", "/user/hand/right/input/trigger/value"]), + ("stick_click", &["/user/hand/left/input/thumbstick/click", "/user/hand/right/input/thumbstick/click"]), + // ... etc +]; + fn suggest_bindings( instance: Res<OxrInstance>, actions: Res<Actions>, mut suggest: EventWriter<OxrSuggestActionBinding>, ) { let mut bind_all = |interaction_profile: &'static str, bindings: &[(openxr::sys::Action, &[&'static str])]| { // ... existing implementation }; - bind_all( - "/interaction_profiles/oculus/touch_controller", - &[ - // ... inline bindings - ], - ); + bind_all( + "/interaction_profiles/oculus/touch_controller", + &OCULUS_BINDINGS.iter().map(|(name, paths)| { + (actions.get_action(name).as_raw(), *paths) + }).collect::<Vec<_>>(), + ); ``` --- `291-291`: **Document or make configurable the hardcoded "Cursor" model part name.** The code assumes the cursor GLB contains a part named "Cursor". This requirement should be documented or made configurable. ```diff +const CURSOR_PART_NAME: &str = "Cursor"; // Expected model part name in cursor.glb let model_part = model.get_model_part("Cursor".to_string()).unwrap(); ``` Consider also handling the error case more gracefully instead of using `unwrap()`. --- `361-361`: **Consider making the controller scale factor configurable.** The hardcoded scale of 0.02 might not be appropriate for all use cases. ```diff +const CONTROLLER_SCALE: f32 = 0.02; self.input .spatial - .set_local_transform(world_transform * Mat4::from_scale(Vec3::splat(0.02))); + .set_local_transform(world_transform * Mat4::from_scale(Vec3::splat(CONTROLLER_SCALE))); ``` </blockquote></details> <details> <summary>src/nodes/drawable/model.rs (1)</summary><blockquote> `279-280`: **Consider excluding spherical harmonics from material hash.** The comment indicates spherical harmonics should be per-instance, but it's currently included in the hash. This might prevent material deduplication when it shouldn't. ```diff -// should always be the same, TODO: make the spherical harmonics buffer a per mesh instance thing -mat.spherical_harmonics.hash(state); +// TODO: make the spherical harmonics buffer a per mesh instance thing +// Don't include in hash for now as it should be instance-specific ``` </blockquote></details> </blockquote></details> <details> <summary>📜 Review details</summary> **Configuration used: CodeRabbit UI** **Review profile: CHILL** **Plan: Pro** <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between fd1c6ed0cf33f396c5559ac3b97a524eb0c2992f and 9347072249f18a9e2f9612626429245b5d1c4ed5. </details> <details> <summary>⛔ Files ignored due to path filters (1)</summary> * `Cargo.lock` is excluded by `!**/*.lock` </details> <details> <summary>📒 Files selected for processing (19)</summary> * `Cargo.toml` (4 hunks) * `src/core/bevy_channel.rs` (1 hunks) * `src/core/client.rs` (2 hunks) * `src/core/color.rs` (1 hunks) * `src/core/entity_handle.rs` (1 hunks) * `src/core/mod.rs` (1 hunks) * `src/main.rs` (6 hunks) * `src/nodes/audio.rs` (2 hunks) * `src/nodes/drawable/lines.rs` (2 hunks) * `src/nodes/drawable/mod.rs` (0 hunks) * `src/nodes/drawable/model.rs` (4 hunks) * `src/nodes/drawable/text.rs` (2 hunks) * `src/nodes/input/mod.rs` (4 hunks) * `src/nodes/items/camera.rs` (2 hunks) * `src/nodes/spatial/mod.rs` (6 hunks) * `src/objects/input/sk_controller.rs` (2 hunks) * `src/objects/input/sk_hand.rs` (3 hunks) * `src/objects/mod.rs` (6 hunks) * `src/objects/play_space.rs` (2 hunks) </details> <details> <summary>💤 Files with no reviewable changes (1)</summary> * src/nodes/drawable/mod.rs </details> <details> <summary>🧰 Additional context used</summary> <details> <summary>🧬 Code Graph Analysis (2)</summary> <details> <summary>src/core/entity_handle.rs (1)</summary><blockquote> <details> <summary>src/core/bevy_channel.rs (1)</summary> * `new` (10-12) </details> </blockquote></details> <details> <summary>src/nodes/drawable/model.rs (12)</summary><blockquote> <details> <summary>src/core/resource.rs (1)</summary> * `get_resource_file` (21-49) </details> <details> <summary>src/nodes/alias.rs (2)</summary> * `alias` (107-107) * `add_to` (59-68) </details> <details> <summary>src/nodes/items/mod.rs (8)</summary> * `parent` (389-389) * `node` (80-80) * `node` (121-121) * `node` (314-314) * `node` (401-401) * `add_to` (74-103) * `add_to` (173-196) * `add_to` (312-324) </details> <details> <summary>src/core/bevy_channel.rs (1)</summary> * `new` (10-12) </details> <details> <summary>src/nodes/spatial/mod.rs (9)</summary> * `new` (109-119) * `build` (26-31) * `parent` (335-335) * `parent` (347-347) * `parent` (482-482) * `parent` (496-496) * `node` (138-140) * `node` (312-312) * `add_to` (120-136) </details> <details> <summary>src/core/registry.rs (3)</summary> * `new` (14-16) * `new` (143-145) * `default` (101-103) </details> <details> <summary>src/nodes/drawable/lines.rs (9)</summary> * `build` (31-33) * `update_visibillity` (36-52) * `cmds` (43-44) * `cmds` (47-48) * `node` (229-230) * `node` (234-234) * `node` (241-241) * `node` (254-254) * `add_to` (228-250) </details> <details> <summary>src/nodes/drawable/text.rs (10)</summary> * `build` (34-47) * `app` (38-39) * `app` (45-45) * `update_visibillity` (50-66) * `cmds` (57-58) * `cmds` (61-62) * `node` (211-211) * `node` (232-232) * `node` (239-239) * `add_to` (208-224) </details> <details> <summary>src/core/entity_handle.rs (1)</summary> * `build` (7-10) </details> <details> <summary>src/nodes/drawable/mod.rs (3)</summary> * `parent` (119-119) * `parent` (137-137) * `parent` (155-155) </details> <details> <summary>src/nodes/input/handler.rs (1)</summary> * `add_to` (12-24) </details> <details> <summary>src/core/client.rs (1)</summary> * `state` (64-67) </details> </blockquote></details> </details> </details> <details> <summary>🔇 Additional comments (20)</summary><blockquote> <details> <summary>src/core/client.rs (1)</summary> `29-29`: **LGTM!** Adding the `warn` macro import is a good addition for enhanced logging capabilities. </details> <details> <summary>src/core/mod.rs (1)</summary> `10-12`: **LGTM!** The addition of these three modules (`color`, `entity_handle`, `bevy_channel`) appropriately supports the Bevy integration effort by providing essential infrastructure for color conversion, entity lifecycle management, and async communication. </details> <details> <summary>src/nodes/items/camera.rs (1)</summary> `137-145`: **Complete the material system migration.** The material creation and application code is commented out, indicating incomplete migration from StereoKit to Bevy rendering. This leaves the camera item functionality partially disabled. Please complete the Bevy material system integration or create a TODO/issue to track this work. The current state may result in non-functional camera preview materials. </details> <details> <summary>src/nodes/input/mod.rs (2)</summary> `11-11`: **LGTM!** Adding the `debug_span` import enables better observability for input processing. --- `124-124`: **Excellent tracing implementation!** The debug spans are well-structured and provide valuable observability into input processing phases. The explicit dropping of the serialization span before starting the client input span demonstrates good understanding of span lifecycle management. Also applies to: 141-141, 167-167, 169-169 </details> <details> <summary>src/core/color.rs (1)</summary> `1-12`: **Well-implemented color conversion trait.** The `ColorConvert` trait provides a clean abstraction for migrating from StereoKit to Bevy color systems. The detailed comment explaining the sRGBA interpretation workaround is valuable technical documentation that addresses the impedance mismatch between the two systems. </details> <details> <summary>src/core/entity_handle.rs (2)</summary> `4-17`: **Clean plugin implementation for entity lifecycle management.** The channel-based approach for entity destruction is well-designed, running in `PreUpdate` to ensure entities are despawned before other systems process them. --- `13-17`: **Bevy’s `despawn` is idempotent—no panic or crash** Bevy’s `EntityCommands::despawn` will only emit a warning if the entity is already gone or invalid; it won’t panic at runtime. You have a couple of options depending on your needs: - To suppress warnings when an entity doesn’t exist, replace ```rust cmds.entity(e).despawn(); ``` with ```rust cmds.entity(e).try_despawn(); ``` - To also remove any child entities, use ```rust cmds.entity(e).despawn_recursive(); ``` No additional validity check is required unless you specifically need to silence logs or recursively destroy children. </details> <details> <summary>src/objects/play_space.rs (1)</summary> `22-54`: **Well-structured Bevy plugin for play space management.** The plugin properly handles OpenXR session lifecycle and integrates cleanly with the Bevy ECS architecture. </details> <details> <summary>src/nodes/audio.rs (1)</summary> `52-91`: **Well-structured audio system implementation!** The audio update system properly handles entity spawning, spatial audio configuration, and playback control with appropriate synchronization primitives. </details> <details> <summary>src/nodes/drawable/text.rs (2)</summary> `82-86`: **Potential race condition with font database swapping.** The temporary swapping of font databases could cause issues if the system runs in parallel or if an error occurs before swapping back. Consider using a guard pattern to ensure the database is always restored: ```rust struct FontDbGuard<'a> { font_system: &'a mut cosmic_text::FontSystem, old_db: Option<&'a mut cosmic_text::fontdb::Database>, } impl Drop for FontDbGuard<'_> { fn drop(&mut self) { if let Some(db) = self.old_db.take() { std::mem::swap(self.font_system.db_mut(), db); } } } ``` Also applies to: 137-139 --- `88-92`: **Verify the inverted text alignment mapping.** The text alignment mapping appears inverted (Left maps to Right, Right maps to Left). This might be intentional due to coordinate system differences, but it's worth confirming. Is this inversion intentional? If so, consider adding a comment explaining why the alignment is inverted. </details> <details> <summary>src/nodes/spatial/mod.rs (2)</summary> `24-32`: **Plugin integration looks good!** The system ordering is correct - updating spatial nodes before `TransformPropagate` ensures transforms are properly synchronized before Bevy propagates them through the hierarchy. --- `62-64`: **Component design is well-structured.** Good use of the `#[require]` attribute to ensure Transform components are always present, and weak references prevent circular dependencies. </details> <details> <summary>src/objects/input/sk_controller.rs (2)</summary> `166-167`: **Document the GLTF loading issue mentioned in the comment.** The comment mentions a "stupid bevy gltf loading issue (rotated 180 degrees on the y axis)" but doesn't explain the workaround or link to an issue tracker. Could you provide more context about this GLTF loading issue? Is there a Bevy issue tracker link or a more detailed explanation of the workaround being applied? --- `175-207`: **Space lifecycle management is well-implemented.** Proper creation and cleanup of OpenXR action spaces with appropriate null checks. </details> <details> <summary>src/nodes/drawable/model.rs (4)</summary> `27-38`: **Plugin setup with proper system ordering.** Good use of `.chain()` to ensure model parts are generated before materials are applied. --- `43-59`: **Visibility management implementation is consistent with other drawable nodes.** Good use of `insert_recursive` to propagate visibility to all children. --- `81-115`: **Efficient material application with proper deduplication.** Good implementation of deferred material updates using the material registry to prevent duplicates. --- `415-457`: **Clean material abstraction with proper asset loading.** Good separation between the material definition and Bevy's PbrMaterial. </details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
@@ -23,3 +23,3 @@
[features]
default = ["wayland"]
default = []
wayland = ["dep:smithay", "dep:wayland-scanner", "dep:wayland-backend"]
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

⚠️ Potential issue

Breaking change: Wayland no longer in default features.

Removing wayland from default features might break existing users. Consider documenting this change prominently or keeping it as default if it's commonly used.

🤖 Prompt for AI Agents
In Cargo.toml at line 24, the default features list has been changed to exclude
"wayland," which is a breaking change. To fix this, either add "wayland" back to
the default features array if it is commonly used or update the documentation
clearly highlighting this removal so users are aware of the change and can
adjust their dependencies accordingly.
_⚠️ Potential issue_ **Breaking change: Wayland no longer in default features.** Removing `wayland` from default features might break existing users. Consider documenting this change prominently or keeping it as default if it's commonly used. <details> <summary>🤖 Prompt for AI Agents</summary> ``` In Cargo.toml at line 24, the default features list has been changed to exclude "wayland," which is a breaking change. To fix this, either add "wayland" back to the default features array if it is commonly used or update the documentation clearly highlighting this removal so users are aware of the change and can adjust their dependencies accordingly. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -73,3 +67,4 @@
tracing = { version = "0.1.40", features = ["release_max_level_warn"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tracy = { version = "0.11.1", optional = true }
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

🛠️ Refactor suggestion

Pin git dependencies to specific commits for reproducible builds.

Using branch names without commit hashes can lead to non-reproducible builds as branches can be updated.

Add specific commit hashes or tags to ensure reproducible builds:

-bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "non_default_wait_frame_system" }
+bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", rev = "COMMIT_HASH" }
🤖 Prompt for AI Agents
In Cargo.toml around lines 45 to 48, the git dependencies are specified using
branch names, which can cause non-reproducible builds because branches may
change over time. To fix this, replace the branch specifications with exact
commit hashes or tags for each dependency to ensure builds are reproducible and
consistent.
_🛠️ Refactor suggestion_ **Pin git dependencies to specific commits for reproducible builds.** Using branch names without commit hashes can lead to non-reproducible builds as branches can be updated. Add specific commit hashes or tags to ensure reproducible builds: ```diff -bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "non_default_wait_frame_system" } +bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", rev = "COMMIT_HASH" } ``` <details> <summary>🤖 Prompt for AI Agents</summary> ``` In Cargo.toml around lines 45 to 48, the git dependencies are specified using branch names, which can cause non-reproducible builds because branches may change over time. To fix this, replace the branch specifications with exact commit hashes or tags for each dependency to ensure builds are reproducible and consistent. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -83,6 +78,17 @@ glam = { version = "0.29.0", features = ["mint", "serde"] }
mint = "0.5.9"
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

⚠️ Potential issue

Local path dependency will cause issues for other developers.

The local path dependency bevy_sk.path = "../bevy_sk" won't work for other developers or in CI/CD.

Consider using:

  • A git repository with a specific commit
  • Publishing to crates.io
  • A workspace member if it's part of this project
🤖 Prompt for AI Agents
In Cargo.toml at line 89, the local path dependency bevy_sk.path = "../bevy_sk"
should be replaced because it causes issues for other developers and CI/CD.
Instead, specify bevy_sk as a git dependency with a repository URL and a
specific commit hash, or publish the crate to crates.io and reference it by
version, or if bevy_sk is part of this project, include it as a workspace member
in Cargo.toml.
_⚠️ Potential issue_ **Local path dependency will cause issues for other developers.** The local path dependency `bevy_sk.path = "../bevy_sk"` won't work for other developers or in CI/CD. Consider using: - A git repository with a specific commit - Publishing to crates.io - A workspace member if it's part of this project <details> <summary>🤖 Prompt for AI Agents</summary> ``` In Cargo.toml at line 89, the local path dependency bevy_sk.path = "../bevy_sk" should be replaced because it causes issues for other developers and CI/CD. Instead, specify bevy_sk as a git dependency with a repository URL and a specific commit hash, or publish the crate to crates.io and reference it by version, or if bevy_sk is part of this project, include it as a workspace member in Cargo.toml. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -0,0 +14,4 @@
let (tx, rx) = mpsc::unbounded_channel();
self.0.set(tx).unwrap();
app.insert_resource(BevyChannelReader(rx));
}
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:13 -04:00

🛠️ Refactor suggestion

Handle multiple initialization attempts gracefully.

The unwrap() will panic if init is called multiple times. Consider handling this case explicitly.

-pub fn init(&self, app: &mut App) {
-    let (tx, rx) = mpsc::unbounded_channel();
-    self.0.set(tx).unwrap();
-    app.insert_resource(BevyChannelReader(rx));
-}
+pub fn init(&self, app: &mut App) {
+    let (tx, rx) = mpsc::unbounded_channel();
+    if self.0.set(tx).is_err() {
+        warn!("BevyChannel already initialized");
+        return;
+    }
+    app.insert_resource(BevyChannelReader(rx));
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

	pub fn init(&self, app: &mut App) {
		let (tx, rx) = mpsc::unbounded_channel();
		if self.0.set(tx).is_err() {
			warn!("BevyChannel already initialized");
			return;
		}
		app.insert_resource(BevyChannelReader(rx));
	}
🤖 Prompt for AI Agents
In src/core/bevy_channel.rs around lines 13 to 17, the init function uses
unwrap() on self.0.set(tx), which will panic if init is called multiple times.
Modify the code to handle multiple initialization attempts gracefully by
checking if the channel is already set before calling set, or by using a method
that returns a Result and handling the error explicitly without panicking.
_🛠️ Refactor suggestion_ **Handle multiple initialization attempts gracefully.** The `unwrap()` will panic if `init` is called multiple times. Consider handling this case explicitly. ```diff -pub fn init(&self, app: &mut App) { - let (tx, rx) = mpsc::unbounded_channel(); - self.0.set(tx).unwrap(); - app.insert_resource(BevyChannelReader(rx)); -} +pub fn init(&self, app: &mut App) { + let (tx, rx) = mpsc::unbounded_channel(); + if self.0.set(tx).is_err() { + warn!("BevyChannel already initialized"); + return; + } + app.insert_resource(BevyChannelReader(rx)); +} ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion pub fn init(&self, app: &mut App) { let (tx, rx) = mpsc::unbounded_channel(); if self.0.set(tx).is_err() { warn!("BevyChannel already initialized"); return; } app.insert_resource(BevyChannelReader(rx)); } ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/core/bevy_channel.rs around lines 13 to 17, the init function uses unwrap() on self.0.set(tx), which will panic if init is called multiple times. Modify the code to handle multiple initialization attempts gracefully by checking if the channel is already set before calling set, or by using a method that returns a Result and handling the error explicitly without panicking. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -0,0 +27,4 @@
Err(TryRecvError::Disconnected) => panic!("bevy channel should never disconnect"),
Err(TryRecvError::Empty) => None,
}
}
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:13 -04:00

🛠️ Refactor suggestion

Consider graceful handling of disconnected channels.

Panicking on channel disconnection might be too aggressive, especially during shutdown scenarios.

-match self.0.try_recv() {
-    Ok(v) => Some(v),
-    Err(TryRecvError::Disconnected) => panic!("bevy channel should never disconnect"),
-    Err(TryRecvError::Empty) => None,
-}
+match self.0.try_recv() {
+    Ok(v) => Some(v),
+    Err(TryRecvError::Disconnected) => {
+        error!("BevyChannel disconnected unexpectedly");
+        None
+    }
+    Err(TryRecvError::Empty) => None,
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

	pub fn read(&mut self) -> Option<T> {
		match self.0.try_recv() {
			Ok(v) => Some(v),
			Err(TryRecvError::Disconnected) => {
				error!("BevyChannel disconnected unexpectedly");
				None
			}
			Err(TryRecvError::Empty) => None,
		}
	}
🤖 Prompt for AI Agents
In src/core/bevy_channel.rs around lines 24 to 30, the read method panics when
the channel is disconnected, which can be too aggressive during shutdown. Modify
the code to handle the Disconnected error gracefully by returning None or an
appropriate result instead of panicking, ensuring the program can shut down
cleanly without abrupt termination.
_🛠️ Refactor suggestion_ **Consider graceful handling of disconnected channels.** Panicking on channel disconnection might be too aggressive, especially during shutdown scenarios. ```diff -match self.0.try_recv() { - Ok(v) => Some(v), - Err(TryRecvError::Disconnected) => panic!("bevy channel should never disconnect"), - Err(TryRecvError::Empty) => None, -} +match self.0.try_recv() { + Ok(v) => Some(v), + Err(TryRecvError::Disconnected) => { + error!("BevyChannel disconnected unexpectedly"); + None + } + Err(TryRecvError::Empty) => None, +} ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion pub fn read(&mut self) -> Option<T> { match self.0.try_recv() { Ok(v) => Some(v), Err(TryRecvError::Disconnected) => { error!("BevyChannel disconnected unexpectedly"); None } Err(TryRecvError::Empty) => None, } } ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/core/bevy_channel.rs around lines 24 to 30, the read method panics when the channel is disconnected, which can be too aggressive during shutdown. Modify the code to handle the Disconnected error gracefully by returning None or an appropriate result instead of panicking, ensuring the program can shut down cleanly without abrupt termination. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:13 -04:00

🛠️ Refactor suggestion

Replace println! with structured logging.

Use proper tracing macros instead of println! for consistency with the rest of the codebase's logging approach.

Apply this diff to use proper structured logging:

-		println!("start save state");
+		info!("Starting state save operation");
 		let internal = self.root.get()?.save_state().await.ok()?;
-		println!("finished save state");
+		info!("Completed state save operation");
🤖 Prompt for AI Agents
In src/core/client.rs around lines 204 to 206, replace the println! statements
with structured logging using the tracing crate macros, such as tracing::info!
or tracing::debug!, to maintain consistency with the codebase's logging
approach. Remove the println! calls and insert appropriate tracing macros with
the same messages to ensure proper structured logging.
_🛠️ Refactor suggestion_ **Replace `println!` with structured logging.** Use proper tracing macros instead of `println!` for consistency with the rest of the codebase's logging approach. Apply this diff to use proper structured logging: ```diff - println!("start save state"); + info!("Starting state save operation"); let internal = self.root.get()?.save_state().await.ok()?; - println!("finished save state"); + info!("Completed state save operation"); ``` <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/core/client.rs around lines 204 to 206, replace the println! statements with structured logging using the tracing crate macros, such as tracing::info! or tracing::debug!, to maintain consistency with the codebase's logging approach. Remove the println! calls and insert appropriate tracing macros with the same messages to ensure proper structured logging. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:poseidon:panther -->
@@ -226,0 +257,4 @@
let mut app = App::new();
app.insert_resource(DbusConnection(dbus_connection));
app.add_plugins(AssetPlugin {
meta_check: AssetMetaCheck::Never,
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

⚠️ Potential issue

Fix undefined wayland variable in xr_step function.

The wayland variable is referenced but not defined in this function scope. This will cause compilation errors when the wayland feature is enabled.

The wayland instance needs to be made accessible to the xr_step function. Consider these options:

  1. Store it as a Bevy resource:
#[cfg(feature = "wayland")]
#[derive(Resource)]
struct WaylandResource(wayland::Wayland);
  1. Or remove these calls if wayland support is not yet implemented (as mentioned in the PR description).
🤖 Prompt for AI Agents
In src/main.rs between lines 433 and 473, the xr_step function references the
wayland variable without defining or accessing it, causing compilation errors
when the wayland feature is enabled. To fix this, make the wayland instance
accessible inside xr_step by storing it as a Bevy resource (e.g., define a
WaylandResource struct wrapping wayland::Wayland and insert it into the world
resources), then retrieve and use it within xr_step. Alternatively, if wayland
support is not implemented yet, remove or comment out the wayland-related calls
to avoid undefined variable errors.

Addressed in commits d2afa00 to aec875a

_⚠️ Potential issue_ **Fix undefined `wayland` variable in xr_step function.** The `wayland` variable is referenced but not defined in this function scope. This will cause compilation errors when the wayland feature is enabled. The wayland instance needs to be made accessible to the xr_step function. Consider these options: 1. Store it as a Bevy resource: ```rust #[cfg(feature = "wayland")] #[derive(Resource)] struct WaylandResource(wayland::Wayland); ``` 2. Or remove these calls if wayland support is not yet implemented (as mentioned in the PR description). <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/main.rs between lines 433 and 473, the xr_step function references the wayland variable without defining or accessing it, causing compilation errors when the wayland feature is enabled. To fix this, make the wayland instance accessible inside xr_step by storing it as a Bevy resource (e.g., define a WaylandResource struct wrapping wayland::Wayland and insert it into the world resources), then retrieve and use it within xr_step. Alternatively, if wayland support is not implemented yet, remove or comment out the wayland-related calls to avoid undefined variable errors. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion --> ✅ Addressed in commits d2afa00 to aec875a
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

💡 Verification agent

🧩 Analysis chain

Verify the extremely small near plane value.

The near plane is set to 0.003 meters (3mm), which is unusually small and may cause z-fighting or depth precision issues.

Consider using a more typical value like 0.01 or 0.05 meters for VR applications. The small value might be intentional for specific use cases, but it's worth verifying.


🌐 Web query:

What is the recommended near plane distance for VR/XR applications to avoid z-fighting and maintain depth precision?

💡 Result:

In VR/XR applications, the recommended near plane distance is typically set to a value that balances depth precision and minimizes z-fighting.

A common guideline is to set the near plane distance to around 0.1 meters (10 centimeters) or slightly above. This is because:

  • Setting the near plane too close (e.g., less than 0.01 meters) severely reduces depth buffer precision, causing z-fighting at farther distances.
  • Setting it too far can clip nearby objects that are important to the VR experience.

For VR, which often requires accurate depth perception at close range, 0.1 meters is a good starting point. Some developers go as close as 0.05 meters if their engine and hardware support higher precision depth buffers (like using 24 or 32-bit depth buffers), but going below that usually causes depth fighting.

To sum up:

  • Recommended near plane distance: ~0.1 meters (10 cm)
  • Ensure a far plane distance is not excessively large to maximize depth buffer precision.
  • Use a 24-bit or 32-bit depth buffer to improve precision.
  • Adjust based on your scene scale and hardware capabilities.

If you want, I can also look up the latest best practices or guidelines from specific VR platforms or engines. Would you like me to do that?


Re-evaluate the near‐plane setting (0.003 m)

A 3 mm near plane is well below common VR/XR guidelines (typically ≥ 0.05–0.1 m) and can severely degrade depth‐buffer precision, leading to z-fighting. Please confirm whether such an ultra-close near plane is required for your use case, or consider increasing it to a more typical value (e.g., 0.05–0.1 m).

• Location: src/main.rs, lines 415–431
• All three branches set near = 0.003 (Perspective, Orthographic, and XrProjection)

Suggested change example:

--- a/src/main.rs
+++ b/src/main.rs
@@ fn update_cameras(mut camera: Query<&mut Projection, (With<Camera3d>,)>) {
-           Projection::Perspective(p) => p.near = 0.003,
+           Projection::Perspective(p) => p.near = 0.1,     // consider 0.05–0.1 m for VR/XR
@@
-           Projection::Orthographic(o) => o.near = 0.003,
+           Projection::Orthographic(o) => o.near = 0.1,
@@
-                   xr.near = 0.003
+                   xr.near = 0.1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

fn update_cameras(mut camera: Query<&mut Projection, (With<Camera3d>,)>) {
    for mut projection in &mut camera {
        match projection.deref_mut() {
            // consider 0.05–0.1 m for VR/XR instead of 0.003 m
            Projection::Perspective(perspective_projection) => perspective_projection.near = 0.1,
            Projection::Orthographic(orthographic_projection) => {
                orthographic_projection.near = 0.1
            }
            Projection::Custom(custom_projection) => {
                if let Some(xr) = custom_projection.get_mut::<XrProjection>() {
                    xr.near = 0.1
                } else {
                    error_once!("unknown custom camera projection");
                }
            }
        }
    }
}
🤖 Prompt for AI Agents
In src/main.rs around lines 415 to 431, the near plane for all camera
projections is set to 0.003, which is unusually close and can cause depth
precision issues. Review your application's requirements to determine if such a
small near plane is necessary; if not, increase the near plane distance to a
more standard value between 0.05 and 0.1 meters by updating the assignments in
all branches of the match statement accordingly.

Addressed in commits d2afa00 to aec875a

_💡 Verification agent_ <details> <summary>🧩 Analysis chain</summary> **Verify the extremely small near plane value.** The near plane is set to 0.003 meters (3mm), which is unusually small and may cause z-fighting or depth precision issues. Consider using a more typical value like 0.01 or 0.05 meters for VR applications. The small value might be intentional for specific use cases, but it's worth verifying. --- 🌐 Web query: ```web What is the recommended near plane distance for VR/XR applications to avoid z-fighting and maintain depth precision? ``` 💡 Result: In VR/XR applications, the recommended near plane distance is typically set to a value that balances depth precision and minimizes z-fighting. A common guideline is to set the near plane distance to around **0.1 meters (10 centimeters)** or slightly above. This is because: - Setting the near plane too close (e.g., less than 0.01 meters) severely reduces depth buffer precision, causing z-fighting at farther distances. - Setting it too far can clip nearby objects that are important to the VR experience. For VR, which often requires accurate depth perception at close range, 0.1 meters is a good starting point. Some developers go as close as 0.05 meters if their engine and hardware support higher precision depth buffers (like using 24 or 32-bit depth buffers), but going below that usually causes depth fighting. To sum up: - **Recommended near plane distance:** ~0.1 meters (10 cm) - Ensure a far plane distance is not excessively large to maximize depth buffer precision. - Use a 24-bit or 32-bit depth buffer to improve precision. - Adjust based on your scene scale and hardware capabilities. If you want, I can also look up the latest best practices or guidelines from specific VR platforms or engines. Would you like me to do that? --- </details> **Re-evaluate the near‐plane setting (0.003 m)** A 3 mm near plane is well below common VR/XR guidelines (typically ≥ 0.05–0.1 m) and can severely degrade depth‐buffer precision, leading to z-fighting. Please confirm whether such an ultra-close near plane is required for your use case, or consider increasing it to a more typical value (e.g., 0.05–0.1 m). • Location: `src/main.rs`, lines 415–431 • All three branches set `near = 0.003` (Perspective, Orthographic, and `XrProjection`) Suggested change example: ```diff --- a/src/main.rs +++ b/src/main.rs @@ fn update_cameras(mut camera: Query<&mut Projection, (With<Camera3d>,)>) { - Projection::Perspective(p) => p.near = 0.003, + Projection::Perspective(p) => p.near = 0.1, // consider 0.05–0.1 m for VR/XR @@ - Projection::Orthographic(o) => o.near = 0.003, + Projection::Orthographic(o) => o.near = 0.1, @@ - xr.near = 0.003 + xr.near = 0.1 ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion fn update_cameras(mut camera: Query<&mut Projection, (With<Camera3d>,)>) { for mut projection in &mut camera { match projection.deref_mut() { // consider 0.05–0.1 m for VR/XR instead of 0.003 m Projection::Perspective(perspective_projection) => perspective_projection.near = 0.1, Projection::Orthographic(orthographic_projection) => { orthographic_projection.near = 0.1 } Projection::Custom(custom_projection) => { if let Some(xr) = custom_projection.get_mut::<XrProjection>() { xr.near = 0.1 } else { error_once!("unknown custom camera projection"); } } } } } ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/main.rs around lines 415 to 431, the near plane for all camera projections is set to 0.003, which is unusually close and can cause depth precision issues. Review your application's requirements to determine if such a small near plane is necessary; if not, increase the near plane distance to a more standard value between 0.05 and 0.1 meters by updating the assignments in all branches of the match statement accordingly. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion --> ✅ Addressed in commits d2afa00 to aec875a
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

⚠️ Potential issue

Address the memory leak in tokio runtime context handling.

The current implementation leaks memory by forgetting the enter guard. Consider storing the guard and properly dropping it on application shutdown.

Consider this approach to fix the memory leak:

+#[derive(Resource)]
+struct TokioEnterGuard(Option<tokio::runtime::EnterGuard<'static>>);
+
 fn bevy_loop(
 	ready_notifier: Arc<Notify>,
 	project_dirs: Option<ProjectDirs>,
 	args: CliArgs,
 	dbus_connection: Connection,
 	object_registry: ObjectRegistry,
 ) {
 	let mut app = App::new();
 	// ... existing code ...
 	let handle = tokio::runtime::Handle::current();
-	let enter_runtime_context = Arc::new(move || {
-		// TODO: this might be a memory leak
-		std::mem::forget(handle.enter());
-	});
+	let enter_guard = handle.enter();
+	app.insert_resource(TokioEnterGuard(Some(enter_guard)));
+	let enter_runtime_context = Arc::new(|| {});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

#[derive(Resource)]
struct TokioEnterGuard(Option<tokio::runtime::EnterGuard<'static>>);

fn bevy_loop(
    ready_notifier: Arc<Notify>,
    project_dirs: Option<ProjectDirs>,
    args: CliArgs,
    dbus_connection: Connection,
    object_registry: ObjectRegistry,
) {
    let mut app = App::new();
    // ... existing code ...
    let handle = tokio::runtime::Handle::current();
    let enter_guard = handle.enter();
    app.insert_resource(TokioEnterGuard(Some(enter_guard)));
    let enter_runtime_context = Arc::new(|| {});
    // ... rest of bevy_loop ...
}
🤖 Prompt for AI Agents
In src/main.rs around lines 313 to 317, the current code leaks memory by calling
std::mem::forget on the tokio runtime enter guard, preventing it from being
dropped. To fix this, store the enter guard in a struct or variable that lives
for the required scope and ensure it is properly dropped when the application
shuts down, instead of forgetting it. This will prevent the memory leak by
allowing the guard's Drop implementation to run.
_⚠️ Potential issue_ **Address the memory leak in tokio runtime context handling.** The current implementation leaks memory by forgetting the enter guard. Consider storing the guard and properly dropping it on application shutdown. Consider this approach to fix the memory leak: ```diff +#[derive(Resource)] +struct TokioEnterGuard(Option<tokio::runtime::EnterGuard<'static>>); + fn bevy_loop( ready_notifier: Arc<Notify>, project_dirs: Option<ProjectDirs>, args: CliArgs, dbus_connection: Connection, object_registry: ObjectRegistry, ) { let mut app = App::new(); // ... existing code ... let handle = tokio::runtime::Handle::current(); - let enter_runtime_context = Arc::new(move || { - // TODO: this might be a memory leak - std::mem::forget(handle.enter()); - }); + let enter_guard = handle.enter(); + app.insert_resource(TokioEnterGuard(Some(enter_guard))); + let enter_runtime_context = Arc::new(|| {}); ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion #[derive(Resource)] struct TokioEnterGuard(Option<tokio::runtime::EnterGuard<'static>>); fn bevy_loop( ready_notifier: Arc<Notify>, project_dirs: Option<ProjectDirs>, args: CliArgs, dbus_connection: Connection, object_registry: ObjectRegistry, ) { let mut app = App::new(); // ... existing code ... let handle = tokio::runtime::Handle::current(); let enter_guard = handle.enter(); app.insert_resource(TokioEnterGuard(Some(enter_guard))); let enter_runtime_context = Arc::new(|| {}); // ... rest of bevy_loop ... } ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/main.rs around lines 313 to 317, the current code leaks memory by calling std::mem::forget on the tokio runtime enter guard, preventing it from being dropped. To fix this, store the enter guard in a struct or variable that lives for the required scope and ensure it is properly dropped when the application shuts down, instead of forgetting it. This will prevent the memory leak by allowing the guard's Drop implementation to run. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -17,0 +36,4 @@
fn spawn_hmd_audio_listener(mut cmds: Commands, session: Res<OxrSession>) {
let space = session
.create_reference_space(openxr::ReferenceSpaceType::VIEW, BevyTransform::IDENTITY)
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

⚠️ Potential issue

Handle the error from destroy_space instead of ignoring it.

The error from session.destroy_space() is being ignored, which could hide important cleanup failures.

-	_ = session.destroy_space(res.1);
+	if let Err(e) = session.destroy_space(res.1) {
+		error!("Failed to destroy HMD listener space: {}", e);
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

fn despawn_hmd_audio_listener(mut cmds: Commands, session: Res<OxrSession>, res: Res<HmdListener>) {
	cmds.remove_resource::<HmdListener>();
	cmds.entity(res.0).despawn();
	if let Err(e) = session.destroy_space(res.1) {
		error!("Failed to destroy HMD listener space: {}", e);
	}
}
🤖 Prompt for AI Agents
In src/nodes/audio.rs around lines 31 to 35, the error returned by
session.destroy_space() is currently ignored, which can hide cleanup failures.
Modify the code to handle the error by capturing the result and logging or
properly managing any error returned from destroy_space instead of discarding
it.
_⚠️ Potential issue_ **Handle the error from destroy_space instead of ignoring it.** The error from `session.destroy_space()` is being ignored, which could hide important cleanup failures. ```diff - _ = session.destroy_space(res.1); + if let Err(e) = session.destroy_space(res.1) { + error!("Failed to destroy HMD listener space: {}", e); + } ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion fn despawn_hmd_audio_listener(mut cmds: Commands, session: Res<OxrSession>, res: Res<HmdListener>) { cmds.remove_resource::<HmdListener>(); cmds.entity(res.0).despawn(); if let Err(e) = session.destroy_space(res.1) { error!("Failed to destroy HMD listener space: {}", e); } } ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/audio.rs around lines 31 to 35, the error returned by session.destroy_space() is currently ignored, which can hide cleanup failures. Modify the code to handle the error by capturing the result and logging or properly managing any error returned from destroy_space instead of discarding it. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -1,314 +1,498 @@
use super::{MODEL_PART_ASPECT_ALIAS_INFO, MaterialParameter, ModelAspect, ModelPartAspect};
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:15 -04:00

⚠️ Potential issue

Fix early return in iterator that prevents proper cleanup.

Using return inside an iterator skips remaining entities. Should use continue instead.

 let Some(model) = model_node.0.upgrade() else {
     cmds.entity(entity).despawn();
-    return;
+    continue;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

		let Some(model) = model_node.0.upgrade() else {
			cmds.entity(entity).despawn();
			continue;
		};
🤖 Prompt for AI Agents
In src/nodes/drawable/model.rs around lines 127 to 130, the use of `return`
inside the iterator prematurely exits the entire iteration, preventing cleanup
of remaining entities. Replace the `return` statement with `continue` to skip
the current iteration and allow the loop to proceed with other entities,
ensuring proper cleanup.
_⚠️ Potential issue_ **Fix early return in iterator that prevents proper cleanup.** Using `return` inside an iterator skips remaining entities. Should use `continue` instead. ```diff let Some(model) = model_node.0.upgrade() else { cmds.entity(entity).despawn(); - return; + continue; }; ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion let Some(model) = model_node.0.upgrade() else { cmds.entity(entity).despawn(); continue; }; ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/drawable/model.rs around lines 127 to 130, the use of `return` inside the iterator prematurely exits the entire iteration, preventing cleanup of remaining entities. Replace the `return` statement with `continue` to skip the current iteration and allow the loop to proceed with other entities, ensuring proper cleanup. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -76,1 +91,4 @@
{
let Ok(mut mesh_mat) = query.get_mut(**model_part.mesh_entity.get().unwrap()) else {
continue;
};
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:15 -04:00

🛠️ Refactor suggestion

Handle GLTF files with multiple scenes.

The code assumes scene index 0 exists, which may not be true for all GLTF files. Consider making this configurable or loading the default scene.

-let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path));
+// Use the default scene if available, otherwise scene 0
+let handle = asset_server.load::<Scene>(path.clone());

Alternatively, load the GLTF asset first to inspect available scenes:

let gltf_handle = asset_server.load::<Gltf>(path);
// Then in another system, check gltf.default_scene or iterate gltf.scenes
🤖 Prompt for AI Agents
In src/nodes/drawable/model.rs at line 68, the code currently loads a GLTF asset
assuming scene index 0 exists, which may not be valid for all GLTF files. To fix
this, first load the GLTF asset without specifying a scene, then in a separate
system or step, inspect the loaded GLTF to determine the default scene or
available scenes. Adjust the scene index dynamically based on this inspection
rather than hardcoding 0.
_🛠️ Refactor suggestion_ **Handle GLTF files with multiple scenes.** The code assumes scene index 0 exists, which may not be true for all GLTF files. Consider making this configurable or loading the default scene. ```diff -let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path)); +// Use the default scene if available, otherwise scene 0 +let handle = asset_server.load::<Scene>(path.clone()); ``` Alternatively, load the GLTF asset first to inspect available scenes: ```rust let gltf_handle = asset_server.load::<Gltf>(path); // Then in another system, check gltf.default_scene or iterate gltf.scenes ``` <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/drawable/model.rs at line 68, the code currently loads a GLTF asset assuming scene index 0 exists, which may not be valid for all GLTF files. To fix this, first load the GLTF asset without specifying a scene, then in a separate system or step, inspect the loaded GLTF to determine the default scene or available scenes. Adjust the scene index dynamically based on this inspection rather than hardcoding 0. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:15 -04:00

🛠️ Refactor suggestion

Address the potential DoS vector in model part creation.

The comment mentions this could be a DoS vector. Consider adding limits or validation.

 None => {
-    // TODO: this could be a denail of service vector
+    // Limit pre-bound parts to prevent DoS
+    const MAX_PREBOUND_PARTS: usize = 100;
+    if self.pre_bound_parts.lock().len() >= MAX_PREBOUND_PARTS {
+        bail!("Maximum number of pre-bound parts ({MAX_PREBOUND_PARTS}) exceeded");
+    }
     let client = self.spatial.node().unwrap().get_client().unwrap();
🤖 Prompt for AI Agents
In src/nodes/drawable/model.rs at line 589, the comment notes a potential denial
of service (DoS) vector in the model part creation. To fix this, add validation
checks or limits on the input size or complexity during model part creation to
prevent excessive resource consumption. Implement safeguards such as maximum
allowed parts or input size thresholds to mitigate the DoS risk.
_🛠️ Refactor suggestion_ **Address the potential DoS vector in model part creation.** The comment mentions this could be a DoS vector. Consider adding limits or validation. ```diff None => { - // TODO: this could be a denail of service vector + // Limit pre-bound parts to prevent DoS + const MAX_PREBOUND_PARTS: usize = 100; + if self.pre_bound_parts.lock().len() >= MAX_PREBOUND_PARTS { + bail!("Maximum number of pre-bound parts ({MAX_PREBOUND_PARTS}) exceeded"); + } let client = self.spatial.node().unwrap().get_client().unwrap(); ``` <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/drawable/model.rs at line 589, the comment notes a potential denial of service (DoS) vector in the model part creation. To fix this, add validation checks or limits on the input size or complexity during model part creation to prevent excessive resource consumption. Implement safeguards such as maximum allowed parts or input size thresholds to mitigate the DoS risk. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -45,0 +111,4 @@
font_size: style.character_height,
line_height: style.character_height,
alignment,
max_width,
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:15 -04:00

⚠️ Potential issue

Fix incorrect height bounds calculation.

The max_height is incorrectly using bounds.x instead of bounds.y.

 let max_width = style.bounds.as_ref().map(|v| v.bounds.x);
-let max_height = style.bounds.as_ref().map(|v| v.bounds.x);
+let max_height = style.bounds.as_ref().map(|v| v.bounds.y);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        let max_width = style.bounds.as_ref().map(|v| v.bounds.x);
        let max_height = style.bounds.as_ref().map(|v| v.bounds.y);
🤖 Prompt for AI Agents
In src/nodes/drawable/text.rs around lines 104 to 105, the max_height variable
is incorrectly assigned using bounds.x instead of bounds.y. Change the
max_height assignment to use bounds.y to correctly reflect the height bounds
from the style.
_⚠️ Potential issue_ **Fix incorrect height bounds calculation.** The max_height is incorrectly using bounds.x instead of bounds.y. ```diff let max_width = style.bounds.as_ref().map(|v| v.bounds.x); -let max_height = style.bounds.as_ref().map(|v| v.bounds.x); +let max_height = style.bounds.as_ref().map(|v| v.bounds.y); ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion let max_width = style.bounds.as_ref().map(|v| v.bounds.x); let max_height = style.bounds.as_ref().map(|v| v.bounds.y); ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/drawable/text.rs around lines 104 to 105, the max_height variable is incorrectly assigned using bounds.x instead of bounds.y. Change the max_height assignment to use bounds.y to correctly reflect the height bounds from the style. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -14,4 +17,4 @@
use mint::Vector3;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::fmt::Debug;
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:15 -04:00

🛠️ Refactor suggestion

Consider implementing entity cleanup for dead spatial references.

The comment raises a valid concern. When the spatial weak reference can't be upgraded, the entity is orphaned and should be despawned to prevent resource leaks.

 let Some(spatial) = spatial_node.0.upgrade() else {
-    // should we despawn the entity?
+    cmds.entity(entity).despawn_recursive();
     return;
 };

Note: This would require adding Commands to the system parameters.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/nodes/spatial/mod.rs around lines 41 to 44, the code currently returns
early when the spatial weak reference cannot be upgraded, potentially leaving
orphaned entities. To fix this, modify the system to accept Commands as a
parameter and use it to despawn the entity when the spatial reference is dead,
ensuring proper cleanup and preventing resource leaks.
_🛠️ Refactor suggestion_ **Consider implementing entity cleanup for dead spatial references.** The comment raises a valid concern. When the spatial weak reference can't be upgraded, the entity is orphaned and should be despawned to prevent resource leaks. ```diff let Some(spatial) = spatial_node.0.upgrade() else { - // should we despawn the entity? + cmds.entity(entity).despawn_recursive(); return; }; ``` Note: This would require adding `Commands` to the system parameters. > Committable suggestion skipped: line range outside the PR's diff. <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/spatial/mod.rs around lines 41 to 44, the code currently returns early when the spatial weak reference cannot be upgraded, potentially leaving orphaned entities. To fix this, modify the system to accept Commands as a parameter and use it to despawn the entity when the spatial reference is dead, ensuring proper cleanup and preventing resource leaks. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:15 -04:00

⚠️ Potential issue

Fix incorrect AABB transformation for rotated children.

Transforming only the min and max corners of an AABB produces incorrect results when the child is rotated. You need to transform all 8 corners to get the correct enclosing AABB.

-bounds = Aabb::enclosing([
-    bounds.min().into(),
-    bounds.max().into(),
-    mat.transform_point3(child_aabb.min().into()),
-    mat.transform_point3(child_aabb.max().into()),
-])
-.unwrap();
+let corners = [
+    child_aabb.min(),
+    Vec3::new(child_aabb.min().x, child_aabb.min().y, child_aabb.max().z),
+    Vec3::new(child_aabb.min().x, child_aabb.max().y, child_aabb.min().z),
+    Vec3::new(child_aabb.min().x, child_aabb.max().y, child_aabb.max().z),
+    Vec3::new(child_aabb.max().x, child_aabb.min().y, child_aabb.min().z),
+    Vec3::new(child_aabb.max().x, child_aabb.min().y, child_aabb.max().z),
+    Vec3::new(child_aabb.max().x, child_aabb.max().y, child_aabb.min().z),
+    child_aabb.max(),
+];
+let transformed_corners = corners.map(|c| mat.transform_point3(c));
+bounds = Aabb::enclosing(
+    [bounds.min().into(), bounds.max().into()]
+        .into_iter()
+        .chain(transformed_corners)
+)
+.unwrap();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

			let mat = child.local_transform();
			let child_aabb = child.get_bounding_box();

			// Collect all 8 corners of the child AABB
			let corners = [
				child_aabb.min(),
				Vec3::new(child_aabb.min().x, child_aabb.min().y, child_aabb.max().z),
				Vec3::new(child_aabb.min().x, child_aabb.max().y, child_aabb.min().z),
				Vec3::new(child_aabb.min().x, child_aabb.max().y, child_aabb.max().z),
				Vec3::new(child_aabb.max().x, child_aabb.min().y, child_aabb.min().z),
				Vec3::new(child_aabb.max().x, child_aabb.min().y, child_aabb.max().z),
				Vec3::new(child_aabb.max().x, child_aabb.max().y, child_aabb.min().z),
				child_aabb.max(),
			];

			// Transform each corner into parent space
			let transformed_corners = corners.map(|c| mat.transform_point3(c));

			// Enclose the original bounds and the newly transformed corners
			bounds = Aabb::enclosing(
				[bounds.min().into(), bounds.max().into()]
					.into_iter()
					.chain(transformed_corners)
			)
			.unwrap();
		}
🤖 Prompt for AI Agents
In src/nodes/spatial/mod.rs around lines 159 to 168, the current code transforms
only the min and max corners of the child's AABB, which is incorrect for rotated
children. To fix this, compute all 8 corners of the child's AABB, transform each
corner using the child's local transform matrix, and then create the enclosing
AABB from these transformed points to ensure the bounding box correctly encloses
the rotated child.
_⚠️ Potential issue_ **Fix incorrect AABB transformation for rotated children.** Transforming only the min and max corners of an AABB produces incorrect results when the child is rotated. You need to transform all 8 corners to get the correct enclosing AABB. ```diff -bounds = Aabb::enclosing([ - bounds.min().into(), - bounds.max().into(), - mat.transform_point3(child_aabb.min().into()), - mat.transform_point3(child_aabb.max().into()), -]) -.unwrap(); +let corners = [ + child_aabb.min(), + Vec3::new(child_aabb.min().x, child_aabb.min().y, child_aabb.max().z), + Vec3::new(child_aabb.min().x, child_aabb.max().y, child_aabb.min().z), + Vec3::new(child_aabb.min().x, child_aabb.max().y, child_aabb.max().z), + Vec3::new(child_aabb.max().x, child_aabb.min().y, child_aabb.min().z), + Vec3::new(child_aabb.max().x, child_aabb.min().y, child_aabb.max().z), + Vec3::new(child_aabb.max().x, child_aabb.max().y, child_aabb.min().z), + child_aabb.max(), +]; +let transformed_corners = corners.map(|c| mat.transform_point3(c)); +bounds = Aabb::enclosing( + [bounds.min().into(), bounds.max().into()] + .into_iter() + .chain(transformed_corners) +) +.unwrap(); ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion let mat = child.local_transform(); let child_aabb = child.get_bounding_box(); // Collect all 8 corners of the child AABB let corners = [ child_aabb.min(), Vec3::new(child_aabb.min().x, child_aabb.min().y, child_aabb.max().z), Vec3::new(child_aabb.min().x, child_aabb.max().y, child_aabb.min().z), Vec3::new(child_aabb.min().x, child_aabb.max().y, child_aabb.max().z), Vec3::new(child_aabb.max().x, child_aabb.min().y, child_aabb.min().z), Vec3::new(child_aabb.max().x, child_aabb.min().y, child_aabb.max().z), Vec3::new(child_aabb.max().x, child_aabb.max().y, child_aabb.min().z), child_aabb.max(), ]; // Transform each corner into parent space let transformed_corners = corners.map(|c| mat.transform_point3(c)); // Enclose the original bounds and the newly transformed corners bounds = Aabb::enclosing( [bounds.min().into(), bounds.max().into()] .into_iter() .chain(transformed_corners) ) .unwrap(); } ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/spatial/mod.rs around lines 159 to 168, the current code transforms only the min and max corners of the child's AABB, which is incorrect for rotated children. To fix this, compute all 8 corners of the child's AABB, transform each corner using the child's local transform matrix, and then create the enclosing AABB from these transformed points to ensure the bounding box correctly encloses the rotated child. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:15 -04:00

⚠️ Potential issue

Fix incorrect AABB transformation in relative bounding box calculation.

This has the same issue as the previous AABB transformation - only min/max corners are transformed, which produces incorrect results for rotated transforms.

-let bounds = Aabb::enclosing([
-    mat.transform_point3(bb.min().into()),
-    mat.transform_point3(bb.max().into()),
-])
-.unwrap();
+let corners = [
+    bb.min(),
+    Vec3::new(bb.min().x, bb.min().y, bb.max().z),
+    Vec3::new(bb.min().x, bb.max().y, bb.min().z),
+    Vec3::new(bb.min().x, bb.max().y, bb.max().z),
+    Vec3::new(bb.max().x, bb.min().y, bb.min().z),
+    Vec3::new(bb.max().x, bb.min().y, bb.max().z),
+    Vec3::new(bb.max().x, bb.max().y, bb.min().z),
+    bb.max(),
+];
+let bounds = Aabb::enclosing(corners.map(|c| mat.transform_point3(c))).unwrap();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        let corners = [
            bb.min(),
            Vec3::new(bb.min().x, bb.min().y, bb.max().z),
            Vec3::new(bb.min().x, bb.max().y, bb.min().z),
            Vec3::new(bb.min().x, bb.max().y, bb.max().z),
            Vec3::new(bb.max().x, bb.min().y, bb.min().z),
            Vec3::new(bb.max().x, bb.min().y, bb.max().z),
            Vec3::new(bb.max().x, bb.max().y, bb.min().z),
            bb.max(),
        ];
        let bounds = Aabb::enclosing(corners.map(|c| mat.transform_point3(c))).unwrap();
🤖 Prompt for AI Agents
In src/nodes/spatial/mod.rs around lines 422 to 427, the current code transforms
only the min and max corners of the bounding box, which leads to incorrect
results for rotated transforms. To fix this, transform all eight corners of the
original bounding box using the matrix, then compute the enclosing AABB from
these transformed points to correctly handle rotations.
_⚠️ Potential issue_ **Fix incorrect AABB transformation in relative bounding box calculation.** This has the same issue as the previous AABB transformation - only min/max corners are transformed, which produces incorrect results for rotated transforms. ```diff -let bounds = Aabb::enclosing([ - mat.transform_point3(bb.min().into()), - mat.transform_point3(bb.max().into()), -]) -.unwrap(); +let corners = [ + bb.min(), + Vec3::new(bb.min().x, bb.min().y, bb.max().z), + Vec3::new(bb.min().x, bb.max().y, bb.min().z), + Vec3::new(bb.min().x, bb.max().y, bb.max().z), + Vec3::new(bb.max().x, bb.min().y, bb.min().z), + Vec3::new(bb.max().x, bb.min().y, bb.max().z), + Vec3::new(bb.max().x, bb.max().y, bb.min().z), + bb.max(), +]; +let bounds = Aabb::enclosing(corners.map(|c| mat.transform_point3(c))).unwrap(); ``` <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion let corners = [ bb.min(), Vec3::new(bb.min().x, bb.min().y, bb.max().z), Vec3::new(bb.min().x, bb.max().y, bb.min().z), Vec3::new(bb.min().x, bb.max().y, bb.max().z), Vec3::new(bb.max().x, bb.min().y, bb.min().z), Vec3::new(bb.max().x, bb.min().y, bb.max().z), Vec3::new(bb.max().x, bb.max().y, bb.min().z), bb.max(), ]; let bounds = Aabb::enclosing(corners.map(|c| mat.transform_point3(c))).unwrap(); ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/spatial/mod.rs around lines 422 to 427, the current code transforms only the min and max corners of the bounding box, which leads to incorrect results for rotated transforms. To fix this, transform all eight corners of the original bounding box using the matrix, then compute the enclosing AABB from these transformed points to correctly handle rotations. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -25,0 +62,4 @@
app.add_systems(Startup, setup.run_if(resource_exists::<OxrInstance>));
app.add_systems(PreFrameWait, update);
}
}
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:15 -04:00

🛠️ Refactor suggestion

Use platform-appropriate paths and avoid redundant file writes.

The current implementation has portability issues and inefficiently writes the file on every startup.

-const CURSOR_MODEL_PATH: &str = "/tmp/stardust_server/models/cursor.glb";
+use std::sync::OnceLock;
+
+static CURSOR_MODEL_PATH: OnceLock<PathBuf> = OnceLock::new();
+
+fn get_cursor_model_path() -> &'static Path {
+    CURSOR_MODEL_PATH.get_or_init(|| {
+        let path = std::env::temp_dir()
+            .join("stardust_server")
+            .join("models")
+            .join("cursor.glb");
+        if !path.exists() {
+            let cursor = include_bytes!("cursor.glb");
+            fs::create_dir_all(path.parent().unwrap()).expect("Failed to create model directory");
+            fs::write(&path, cursor).expect("Failed to write cursor model");
+        }
+        path
+    })
+}

Then use get_cursor_model_path() instead of CURSOR_MODEL_PATH.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/objects/input/sk_controller.rs around lines 50 to 62, the code uses a
hardcoded path for CURSOR_MODEL_PATH which is not platform-appropriate and
writes the cursor model file on every startup unnecessarily. To fix this,
replace CURSOR_MODEL_PATH with a function get_cursor_model_path() that returns a
platform-appropriate path (e.g., using std::env::temp_dir()), and modify the
code to check if the file already exists before writing it to avoid redundant
writes.
_🛠️ Refactor suggestion_ **Use platform-appropriate paths and avoid redundant file writes.** The current implementation has portability issues and inefficiently writes the file on every startup. ```diff -const CURSOR_MODEL_PATH: &str = "/tmp/stardust_server/models/cursor.glb"; +use std::sync::OnceLock; + +static CURSOR_MODEL_PATH: OnceLock<PathBuf> = OnceLock::new(); + +fn get_cursor_model_path() -> &'static Path { + CURSOR_MODEL_PATH.get_or_init(|| { + let path = std::env::temp_dir() + .join("stardust_server") + .join("models") + .join("cursor.glb"); + if !path.exists() { + let cursor = include_bytes!("cursor.glb"); + fs::create_dir_all(path.parent().unwrap()).expect("Failed to create model directory"); + fs::write(&path, cursor).expect("Failed to write cursor model"); + } + path + }) +} ``` Then use `get_cursor_model_path()` instead of `CURSOR_MODEL_PATH`. > Committable suggestion skipped: line range outside the PR's diff. <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/objects/input/sk_controller.rs around lines 50 to 62, the code uses a hardcoded path for CURSOR_MODEL_PATH which is not platform-appropriate and writes the cursor model file on every startup unnecessarily. To fix this, replace CURSOR_MODEL_PATH with a function get_cursor_model_path() that returns a platform-appropriate path (e.g., using std::env::temp_dir()), and modify the code to check if the file already exists before writing it to avoid redundant writes. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -25,0 +130,4 @@
cmds.entity(entity)
.insert(MeshMaterial3d(handle))
.insert(CorrectHandMaterial);
}
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

⚠️ Potential issue

Fix typo in constant name.

The constant name has a typo.

-	const PINCH_ACTIVACTION_DISTANCE: f32 = 0.01;
+	const PINCH_ACTIVATION_DISTANCE: f32 = 0.01;
 	let combined_radius = joint_1.radius + joint_2.radius;
 	let pinch_dist =
 		Vec3::from(joint_1.position).distance(Vec3::from(joint_2.position)) - combined_radius;
-	(1.0 - ((pinch_dist - PINCH_ACTIVACTION_DISTANCE) / (PINCH_MAX - PINCH_ACTIVACTION_DISTANCE)))
+	(1.0 - ((pinch_dist - PINCH_ACTIVATION_DISTANCE) / (PINCH_MAX - PINCH_ACTIVATION_DISTANCE)))
 		.clamp(0.0, 1.0)
🤖 Prompt for AI Agents
In src/objects/input/sk_hand.rs around lines 92 to 100, the constant name
PINCH_ACTIVACTION_DISTANCE contains a typo. Rename it to
PINCH_ACTIVATION_DISTANCE to correct the spelling. Update all references to this
constant within the function accordingly.
_⚠️ Potential issue_ **Fix typo in constant name.** The constant name has a typo. ```diff - const PINCH_ACTIVACTION_DISTANCE: f32 = 0.01; + const PINCH_ACTIVATION_DISTANCE: f32 = 0.01; let combined_radius = joint_1.radius + joint_2.radius; let pinch_dist = Vec3::from(joint_1.position).distance(Vec3::from(joint_2.position)) - combined_radius; - (1.0 - ((pinch_dist - PINCH_ACTIVACTION_DISTANCE) / (PINCH_MAX - PINCH_ACTIVACTION_DISTANCE))) + (1.0 - ((pinch_dist - PINCH_ACTIVATION_DISTANCE) / (PINCH_MAX - PINCH_ACTIVATION_DISTANCE))) .clamp(0.0, 1.0) ``` <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/objects/input/sk_hand.rs around lines 92 to 100, the constant name PINCH_ACTIVACTION_DISTANCE contains a typo. Rename it to PINCH_ACTIVATION_DISTANCE to correct the spelling. Update all references to this constant within the function accordingly. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -5,0 +64,4 @@
}
}
fn destroy_stage_space(session: Res<OxrSession>, mut cmds: Commands, stage: Res<StageSpace>) {
session.destroy_space(stage.0);
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

🛠️ Refactor suggestion

Consider handling stage space creation failure more robustly.

When stage space creation fails, the system continues without it, which might cause issues in the update system.

Consider either:

  1. Logging a warning and using a fallback space
  2. Marking the play space as unavailable
  3. Retrying creation later
🤖 Prompt for AI Agents
In src/objects/play_space.rs around lines 58 to 66, the function
create_stage_space silently ignores failure to create the stage space, which may
cause issues downstream. Modify the code to handle failure more robustly by
either logging a warning and assigning a fallback space, marking the play space
as unavailable with a clear state or flag, or implementing a retry mechanism to
attempt creation again later. This ensures the system can respond appropriately
when stage space is not available.
_🛠️ Refactor suggestion_ **Consider handling stage space creation failure more robustly.** When stage space creation fails, the system continues without it, which might cause issues in the update system. Consider either: 1. Logging a warning and using a fallback space 2. Marking the play space as unavailable 3. Retrying creation later <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/objects/play_space.rs around lines 58 to 66, the function create_stage_space silently ignores failure to create the stage space, which may cause issues downstream. Modify the code to handle failure more robustly by either logging a warning and assigning a fallback space, marking the play space as unavailable with a clear state or flag, or implementing a retry mechanism to attempt creation again later. This ensures the system can respond appropriately when stage space is not available. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
@@ -5,0 +70,4 @@
/// TODO: impl this
fn update(
session: Option<Res<OxrSession>>,
coderabbitai[bot] (Migrated from github.com) commented 2025-06-28 14:53:14 -04:00

⚠️ Potential issue

Clarify or remove misleading TODO comment.

The function is implemented despite the TODO comment. The commented code suggests bounds calculation is not yet implemented.

Either:

  1. Remove the TODO if the current implementation is complete
  2. Update it to specify what's missing (e.g., "TODO: Implement bounds calculation")
  3. Implement the bounds calculation if that's what's missing

Would you like me to help implement the bounds calculation based on the OpenXR stage reference space?

Also applies to: 120-133

🤖 Prompt for AI Agents
In src/objects/play_space.rs at lines 72 and 120-133, the TODO comments are
misleading because the functions are implemented but may lack bounds
calculation. Review the current implementation to determine if bounds
calculation is missing; if it is, either implement the bounds calculation as per
the OpenXR stage reference space or update the TODO comments to clearly specify
that bounds calculation is pending. If the implementation is complete, remove
the TODO comments entirely to avoid confusion.
_⚠️ Potential issue_ **Clarify or remove misleading TODO comment.** The function is implemented despite the TODO comment. The commented code suggests bounds calculation is not yet implemented. Either: 1. Remove the TODO if the current implementation is complete 2. Update it to specify what's missing (e.g., "TODO: Implement bounds calculation") 3. Implement the bounds calculation if that's what's missing Would you like me to help implement the bounds calculation based on the OpenXR stage reference space? Also applies to: 120-133 <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/objects/play_space.rs at lines 72 and 120-133, the TODO comments are misleading because the functions are implemented but may lack bounds calculation. Review the current implementation to determine if bounds calculation is missing; if it is, either implement the bounds calculation as per the OpenXR stage reference space or update the TODO comments to clearly specify that bounds calculation is pending. If the implementation is complete, remove the TODO comments entirely to avoid confusion. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
Schmarni-Dev commented 2025-06-30 05:00:03 -04:00 (Migrated from github.com)

text align is still slightly broken/unimplemented but its def good enough

text align is still slightly broken/unimplemented but its def good enough
technobaboo commented 2025-06-30 05:01:28 -04:00 (Migrated from github.com)

merged, thank you for your patronage :)

merged, thank you for your patronage :)
technobaboo commented 2025-06-30 05:06:23 -04:00 (Migrated from github.com)

was my bad btw, forgot that merging a PR here in github is different than applying commits other ways

was my bad btw, forgot that merging a PR here in github is different than applying commits other ways
Sign in to join this conversation.