Skip to content

Refactor GameBoard Rendering to Layered Architecture#140

Merged
candour merged 4 commits into
mainfrom
refactor-gameboard-rendering-14384520409405927263
May 10, 2026
Merged

Refactor GameBoard Rendering to Layered Architecture#140
candour merged 4 commits into
mainfrom
refactor-gameboard-rendering-14384520409405927263

Conversation

@candour
Copy link
Copy Markdown
Owner

@candour candour commented May 9, 2026

Refactored the GameBoard.kt component to utilize a layered rendering approach. This change improves both code maintainability and runtime performance by replacing a monolithic sorting block with specialized layers and a centralized RenderingContext. The World layer (stalls, enemies, pillars) is now sorted strictly by row, ensuring correct depth perception with minimal overhead. Static layers like Background and Decals are rendered without sorting. Foreground elements like UI and projectiles are always rendered on top.


PR created automatically by Jules for task 14384520409405927263 started by @candour

Summary by CodeRabbit

  • Refactor

    • Restructured rendering into four visual layers: Background, Decals, World, Foreground.
    • Simplified depth-sorting to sort only the World layer by isometric row (r) for consistent visuals.
    • Centralized rendering logic and improved touch handling for more accurate tap-to-tile detection.
  • Documentation

    • Updated rendering/depth guidance to reflect the new four-layer order and sorting rules.

Review Change Stack

Review Change Stack

Rewrote the GameBoard's rendering loop to use a structured, layered
architecture (Background, Decal, World, Foreground). This improves
performance by reducing object allocations and simplifying sorting
to only the World layer entities.

- Introduced RenderingContext to centralize shared drawing state.
- Implemented row-based depth sorting for the World layer.
- Optimized paint reuse and coordinate calculations.
- Updated AGENTS.md and fixes.md.

Co-authored-by: candour <4670475+candour@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

Warning

Rate limit exceeded

@candour has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 45 minutes and 10 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 36b1c341-e8a5-4006-8904-2d1d59884cef

📥 Commits

Reviewing files that changed from the base of the PR and between f47b976 and e501509.

📒 Files selected for processing (2)
  • app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt
  • fixes.md
📝 Walkthrough

Walkthrough

Refactors GameBoard rendering into four ordered layers (Background, Decals, World, Foreground), adds a RenderingContext for geometry/drawing, simplifies tap-to-axial mapping, and changes depth sorting so only the World layer is ordered by axial r.

Changes

GameBoard Layered Rendering Refactor

Layer / File(s) Summary
Rendering Architecture Design
AGENTS.md
Documentation updated to specify four-layer rendering order (Background, Decals, World, Foreground) with only World layer depth-sorted by axial r.
RenderingContext Helper
app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt
New RenderingContext encapsulates axial→screen conversion, hex-path creation, and sprite drawing; DrawableEntity simplified to private type with r and draw lambda.
Paint and Dimension Setup
app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt
GameBoard uses remembered upgradePaint and spritePaint; removed unused minQ/maxQ and computes board width from hex dimensions and grid width.
Input Handling
app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt
Canvas tap handler now computes fractional then integer r and q from touch offset using current hex pixel dimensions, rowSpacingFactor, and borderPx, via GridUtils.hexRound.
Rendering Pipeline
app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt
Render pass split into backgroundLayer/decalLayer/worldLayer/foregroundLayer; worldLayer sorted by r only; layers drawn in sequence using RenderingContext helpers.
Axial Rounding Helper
app/src/main/java/com/messark/hawker/utils/GridUtils.kt
Added hexRound(q: Float, r: Float) and imported kotlin.math.roundToInt to round fractional axial coordinates to nearest hex.
Change Tracking
fixes.md
New REF-004 entry documents the layered rendering refactor and rendering-loop simplification.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Canvas
  participant GameBoard
  participant GridUtils
  User->>Canvas: tap at offset
  Canvas->>GameBoard: deliver touch offset
  GameBoard->>GameBoard: compute fractional q,r (inverse projection)
  GameBoard->>GridUtils: hexRound(q,r)
  GridUtils-->>GameBoard: AxialCoordinate
  GameBoard->>GameBoard: if cell exists -> onCellClick
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • candour/towerpower#73: Modifies GameBoard rendering/projection code; related to drawing and axial spacing changes.
  • candour/towerpower#125: Modifies GridUtils utilities; related to axial coordinate helpers and rounding.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Refactor GameBoard Rendering to Layered Architecture' directly and clearly describes the main change—refactoring GameBoard to use a layered rendering approach. It aligns with the primary objective across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-gameboard-rendering-14384520409405927263

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

❤️ Share

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

Build Successful! 🚀

Download APK

Note: This link will be removed when the PR is closed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt (2)

411-411: ⚡ Quick win

Avoid per-frame allocation when sorting the World layer.

sortedBy allocates a new list each frame. In-place sorting keeps the hot render path leaner.

♻️ Proposed change
-            worldLayer.sortedBy { it.r }.forEach { it.draw(this) }
+            worldLayer.sortBy { it.r }
+            worldLayer.forEach { it.draw(this) }

243-244: ⚡ Quick win

Capture frame time once per draw pass.

Time is sampled repeatedly inside render lambdas. Using a single frame timestamp improves consistency and trims repeated system calls.

♻️ Proposed change
             val ctx = RenderingContext(
                 wPx = hexWidth.toPx(), hPx = hexHeight.toPx(),
                 rowSpacingFactor = rowSpacingFactor, borderPx = 20.dp.toPx(),
                 spriteSheet, stallsSheet, enemiesSheet, endTableSheet, upgradePaint, spritePaint
             )
+            val frameTimeMs = System.currentTimeMillis()
@@
-                            val p = (System.currentTimeMillis() % 1000) / 1000f
+                            val p = (frameTimeMs % 1000L) / 1000f
@@
-                    val p = ((System.currentTimeMillis() - effect.startTimeMs).toFloat() / effect.durationMs).coerceIn(0f, 1f)
+                    val p = ((frameTimeMs - effect.startTimeMs).toFloat() / effect.durationMs).coerceIn(0f, 1f)

Also applies to: 366-367


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 304278ae-ca8e-4896-935b-6cd69004a5b2

📥 Commits

Reviewing files that changed from the base of the PR and between 66c7acb and 0b726b1.

📒 Files selected for processing (3)
  • AGENTS.md
  • app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt
  • fixes.md

@candour
Copy link
Copy Markdown
Owner Author

candour commented May 10, 2026

Avoid per-frame allocation when sorting the World layer.

sortedBy allocates a new list each frame. In-place sorting keeps the hot render path leaner.

♻️ Proposed change
243-244: ⚡ Quick win

Capture frame time once per draw pass.

Time is sampled repeatedly inside render lambdas. Using a single frame timestamp improves consistency and trims repeated system calls.

@google-labs-jules
Copy link
Copy Markdown
Contributor

Avoid per-frame allocation when sorting the World layer.

sortedBy allocates a new list each frame. In-place sorting keeps the hot render path leaner.

♻️ Proposed change
243-244: ⚡ Quick win

Capture frame time once per draw pass.

Time is sampled repeatedly inside render lambdas. Using a single frame timestamp improves consistency and trims repeated system calls.

Thanks for the feedback! I've implemented both optimizations. The frame timestamp is now captured once per draw pass via RenderingContext, and the World layer is now sorted in-place using sortBy to avoid per-frame list allocations.

- Capture frame timestamp once per draw pass via RenderingContext.
- Use in-place sortBy for World layer depth sorting to avoid per-frame allocations.
- Replace System.currentTimeMillis() calls with cached timestamp.

Co-authored-by: candour <4670475+candour@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Build Successful! 🚀

Download APK

Note: This link will be removed when the PR is closed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d8444955-dfac-4ae7-9ed7-a81db7d18346

📥 Commits

Reviewing files that changed from the base of the PR and between 0b726b1 and d1dc272.

📒 Files selected for processing (1)
  • app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt

Comment thread app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt Outdated
Comment thread app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt Outdated
Comment thread app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt
Comment thread app/src/main/java/com/messark/hawker/ui/components/GameBoard.kt Outdated
- Switched Background, Decal, and Foreground layers to direct drawing to eliminate per-frame object allocations.
- Implemented proper cube coordinate rounding for tap detection via GridUtils.hexRound.
- Fixed pillar removal pulse animation by applying the scale factor to the hex path.
- Optimized GAS_CLOUD effect drawing by moving it to the direct-drawn Decal layer.

Co-authored-by: candour <4670475+candour@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Build Successful! 🚀

Download APK

Note: This link will be removed when the PR is closed.

@candour candour enabled auto-merge (squash) May 10, 2026 01:15
@candour
Copy link
Copy Markdown
Owner Author

candour commented May 10, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

✅ 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.

- Switched to direct drawing for most layers to eliminate per-frame allocations.
- Implemented a WorldItem pool and reused active items list to avoid GC churn during depth sorting.
- Reused Path objects and cached frame timestamps via RenderingContext.
- Implemented mathematically correct cube rounding in GridUtils.hexRound for precise hexagonal grid interaction.
- Fixed pillar removal animation pulse scale.
- Ignored out-of-grid taps.

Co-authored-by: candour <4670475+candour@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Build Successful! 🚀

Download APK

Note: This link will be removed when the PR is closed.

@candour
Copy link
Copy Markdown
Owner Author

candour commented May 10, 2026

@coderabbitai resolve

@candour candour merged commit 01ebbc9 into main May 10, 2026
3 checks passed
@candour candour deleted the refactor-gameboard-rendering-14384520409405927263 branch May 10, 2026 03:13
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

✅ Actions performed

Comments resolved and changes approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant