Skip to content

Refactor Game Engine Efficiency - #149

Merged
candour merged 4 commits into
mainfrom
refactor-game-engine-efficiency-12457731955428841459
May 12, 2026
Merged

Refactor Game Engine Efficiency#149
candour merged 4 commits into
mainfrom
refactor-game-engine-efficiency-12457731955428841459

Conversation

@candour

@candour candour commented May 11, 2026

Copy link
Copy Markdown
Owner

This refactoring improves the performance of the core game loop by reducing redundant state copies and optimizing spatial queries. Specifically:

  1. updateTransientState now handles multiple transient types in a single iteration over the hex map.
  2. handleStallFiring uses a pre-calculated list of obstruction coordinates, avoiding repeated filtering of the entire map during LOS checks.
  3. Geometric calculations are moved to GridUtils.kt for better maintainability and reusability.
    Verified with existing unit tests (Pathfinding, StallStats, UncleDoubleGrab).

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

Summary by CodeRabbit

  • Bug Fixes

    • Stalls now properly respect line-of-sight obstructions when selecting targets.
    • Improved enemy-grab behavior: grabbed enemies move/release correctly and special enemy buffs are cleared on release.
  • Performance

    • Optimized game state updates by consolidating transient updates into a single pass for smoother updates.
  • Documentation

    • Added changelog entry documenting these fixes.

Review Change Stack

- Consolidated transient state updates (puddles, visual effects, held enemies) into a single pass in `MainViewModel.kt` to reduce `GameState` copies.
- Optimized Line-of-Sight (LOS) firing logic by pre-filtering obstructions once per tick.
- Centralized isometric geometry and LOS math into `GridUtils.kt`.
- Updated `fixes.md` with refactoring details (REF-006).

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

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@candour has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 26 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: ec4ae9c0-81b7-4d1f-993a-7efecc3579df

📥 Commits

Reviewing files that changed from the base of the PR and between cb46015 and 4f84bb4.

📒 Files selected for processing (1)
  • app/src/main/java/com/messark/hawker/MainViewModel.kt
📝 Walkthrough

Walkthrough

Consolidates transient/held-enemy state updates into updateTransientState, moves LOS geometry into GridUtils (ISOMETRIC_Y_FACTOR, lineIntersectsCircle, isLineOfSightBlocked), and updates handleStallFiring to precompute obstructions and use GridUtils for LOS checks.

Changes

Line-of-Sight Geometry and Engine Consolidation

Layer / File(s) Summary
Geometry Foundation
app/src/main/java/com/messark/hawker/utils/GridUtils.kt
Adds ISOMETRIC_Y_FACTOR, lineIntersectsCircle() (segment–circle intersection via quadratic discriminant), and isLineOfSightBlocked() (isometric-coordinate obstruction checks).
Transient State Consolidation
app/src/main/java/com/messark/hawker/MainViewModel.kt
Introduces updateTransientState() to expire puddles/visual effects and manage held-enemy lifecycle, including Tiger Mom release/buff cleanup.
Game Loop Integration
app/src/main/java/com/messark/hawker/MainViewModel.kt
updateGame() calls the consolidated updateTransientState() instead of separate transient and held-enemy update functions.
Stall Firing with Line-of-Sight
app/src/main/java/com/messark/hawker/MainViewModel.kt
handleStallFiring() precomputes obstruction coordinates when blockable stalls exist and switches target filtering to GridUtils.isLineOfSightBlocked(), removing old local LOS helpers.
Changelog Entry
fixes.md
REF-006 documents the refactor centralizing geometry and consolidating transient updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • candour/towerpower#133: Restructures MainViewModel stall-firing and projectile pipelines; touches the same handleStallFiring logic.
  • candour/towerpower#134: Modifies Tiger Mom buff cleanup in MainViewModel; related to Tiger Mom release/buff handling changes.
  • candour/towerpower#135: Changes LOS geometry and stall-targeting code; directly related to GridUtils LOS logic.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 Game Engine Efficiency' directly and clearly summarizes the main objective of the pull request, which consolidates transient updates and optimizes spatial queries to improve game loop performance.
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-game-engine-efficiency-12457731955428841459

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

Build Successful! 🚀

Download APK

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/main/java/com/messark/hawker/MainViewModel.kt (1)

446-450: ⚡ Quick win

Avoid repeated linear enemy lookup in the transient loop.

Line 449 does indexOfFirst per held-stall check. In this per-tick path, a one-time id -> index map removes repeated scans and keeps the refactor’s performance gains more consistent.

Suggested refactor
         var updatedHexes = state.hexes.toMutableMap()
         var updatedEnemies = state.enemies.toMutableList()
+        val enemyIndexById = updatedEnemies.withIndex().associate { it.value.id to it.index }
         var changed = false

         state.hexes.forEach { (coord, tile) ->
             val stall = tile.stall
             if (stall?.heldEnemyId != null) {
-                val enemyIndex = updatedEnemies.indexOfFirst { it.id == stall.heldEnemyId }
+                val enemyIndex = enemyIndexById[stall.heldEnemyId] ?: -1
                 if (enemyIndex != -1) {

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c2e31b5-147b-4eee-950e-254274144142

📥 Commits

Reviewing files that changed from the base of the PR and between be3e1e8 and 09fd095.

📒 Files selected for processing (3)
  • app/src/main/java/com/messark/hawker/MainViewModel.kt
  • app/src/main/java/com/messark/hawker/utils/GridUtils.kt
  • fixes.md

Comment thread app/src/main/java/com/messark/hawker/MainViewModel.kt
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@candour

candour commented May 12, 2026

Copy link
Copy Markdown
Owner Author

Avoid repeated linear enemy lookup in the transient loop.

Line 449 does indexOfFirst per held-stall check. In this per-tick path, a one-time id -> index map removes repeated scans and keeps the refactor’s performance gains more consistent.

@github-actions

Copy link
Copy Markdown

Build Successful! 🚀

Download APK

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/main/java/com/messark/hawker/MainViewModel.kt (1)

442-444: ⚡ Quick win

Defer the mutable copies until the first actual mutation.

state.hexes.toMutableMap() and state.enemies.toMutableList() happen on every tick, even when no held enemy changes and no transient expires. In a 32 ms loop, that keeps allocation pressure high and blunts the performance gain from this refactor.

♻️ One way to make this copy-on-write
-        var updatedHexes = state.hexes.toMutableMap()
-        var updatedEnemies = state.enemies.toMutableList()
+        var updatedHexes: MutableMap<AxialCoordinate, HexTile>? = null
+        var updatedEnemies: MutableList<Enemy>? = null
         var changed = false
...
-                val enemyIndex = updatedEnemies.indexOfFirst { it.id == stall.heldEnemyId }
+                val enemies = updatedEnemies ?: state.enemies.toMutableList().also { updatedEnemies = it }
+                val enemyIndex = enemies.indexOfFirst { it.id == stall.heldEnemyId }
...
-                        updatedEnemies[enemyIndex] = releasedEnemy
-                        updatedHexes[coord] = tile.copy(stall = stall.copy(heldEnemyId = null))
+                        enemies[enemyIndex] = releasedEnemy
+                        val hexes = updatedHexes ?: state.hexes.toMutableMap().also { updatedHexes = it }
+                        hexes[coord] = tile.copy(stall = stall.copy(heldEnemyId = null))
...
-                hexes = updatedHexes,
-                enemies = updatedEnemies
+                hexes = updatedHexes ?: state.hexes,
+                enemies = updatedEnemies ?: state.enemies

Also applies to: 495-500


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b9aff82-29d6-4297-8549-15dc192ccae2

📥 Commits

Reviewing files that changed from the base of the PR and between 09fd095 and cb46015.

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

Comment thread app/src/main/java/com/messark/hawker/MainViewModel.kt Outdated
@google-labs-jules

Copy link
Copy Markdown
Contributor

Avoid repeated linear enemy lookup in the transient loop.

Line 449 does indexOfFirst per held-stall check. In this per-tick path, a one-time id -> index map removes repeated scans and keeps the refactor’s performance gains more consistent.

Good catch! I've optimized the transient update loop by pre-calculating an ID-to-index map for enemies. This ensures that lookups are O(1) inside the hex-map iteration, keeping the performance gains consistent even with many held enemies.

- Replaced linear `indexOfFirst` with an ID-to-index map in `updateTransientState`.
- Addressed PR feedback regarding performance consistency.

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 commented May 12, 2026

Copy link
Copy Markdown
Owner Author

Defer the mutable copies until the first actual mutation.

state.hexes.toMutableMap() and state.enemies.toMutableList() happen on every tick, even when no held enemy changes and no transient expires. In a 32 ms loop, that keeps allocation pressure high and blunts the performance gain from this refactor.

@google-labs-jules

Copy link
Copy Markdown
Contributor

Defer the mutable copies until the first actual mutation.

state.hexes.toMutableMap() and state.enemies.toMutableList() happen on every tick, even when no held enemy changes and no transient expires. In a 32 ms loop, that keeps allocation pressure high and blunts the performance gain from this refactor.

Excellent point. I've refactored updateTransientState to defer toMutableMap() and toMutableList() calls until the very moment a mutation is needed. I've also added any checks for puddles and visual effects to avoid unnecessary filtering when nothing has expired. This significantly reduces allocation pressure during the 32ms game tick.

…pressure

- Refactored `updateTransientState` to only perform `toMutableList` and `toMutableMap` when changes are detected.
- Added `any` checks for puddle and effect expiration to avoid redundant filtering.
- Optimized `enemyIndexMap` creation to only occur when held enemies are present.

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 12, 2026 01:19
@candour

candour commented May 12, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resolve

@candour
candour merged commit 787fb64 into main May 12, 2026
3 checks passed
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Comments resolved and changes approved.

@candour
candour deleted the refactor-game-engine-efficiency-12457731955428841459 branch May 12, 2026 01:19
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