mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-17 23:55:21 +01:00
Fix line clearing performance by processing on background thread
This commit is contained in:
parent
9fbffc00d0
commit
8dc1d433ea
2 changed files with 57 additions and 129 deletions
|
@ -90,16 +90,8 @@ class GameView @JvmOverloads constructor(
|
|||
maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
|
||||
}
|
||||
|
||||
private val lineClearPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
alpha = 255
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
// Animation
|
||||
private var lineClearAnimator: ValueAnimator? = null
|
||||
private var lineClearProgress = 0f
|
||||
private val lineClearDuration = 100L // milliseconds
|
||||
// Pre-allocate paint objects to avoid GC
|
||||
private val tmpPaint = Paint()
|
||||
|
||||
// Calculate block size based on view dimensions and board size
|
||||
private var blockSize = 0f
|
||||
|
@ -147,7 +139,7 @@ class GameView @JvmOverloads constructor(
|
|||
gameBoard.onPieceMove = { onPieceMove?.invoke() }
|
||||
gameBoard.onPieceLock = { onPieceLock?.invoke() }
|
||||
|
||||
// Enable hardware acceleration
|
||||
// Force hardware acceleration - This is critical for performance
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
||||
|
||||
// Set better frame rate using modern APIs
|
||||
|
@ -185,7 +177,6 @@ class GameView @JvmOverloads constructor(
|
|||
fun pause() {
|
||||
isPaused = true
|
||||
handler.removeCallbacks(gameLoopRunnable)
|
||||
lineClearAnimator?.cancel()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
|
@ -198,7 +189,6 @@ class GameView @JvmOverloads constructor(
|
|||
gameBoard.reset()
|
||||
gameBoard.startGame() // Add this line to ensure a new piece is spawned
|
||||
handler.removeCallbacks(gameLoopRunnable)
|
||||
lineClearAnimator?.cancel()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
|
@ -216,55 +206,31 @@ class GameView @JvmOverloads constructor(
|
|||
// Move the current tetromino down automatically
|
||||
gameBoard.moveDown()
|
||||
|
||||
// Check if lines need to be cleared and start animation if needed
|
||||
// Check if lines need to be cleared
|
||||
if (gameBoard.linesToClear.isNotEmpty()) {
|
||||
// Trigger line clear callback for vibration
|
||||
onLineClear?.invoke(gameBoard.linesToClear.size)
|
||||
|
||||
// Start line clearing animation immediately
|
||||
startLineClearAnimation()
|
||||
}
|
||||
// Trigger line clearing on a background thread to prevent UI freezes
|
||||
Thread {
|
||||
// Process the line clearing off the UI thread
|
||||
gameBoard.clearLinesFromGrid()
|
||||
|
||||
// Update UI with current game state
|
||||
onGameStateChanged?.invoke(gameBoard.score, gameBoard.level, gameBoard.lines)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the line clearing animation
|
||||
*/
|
||||
private fun startLineClearAnimation() {
|
||||
// Cancel any existing animation
|
||||
lineClearAnimator?.cancel()
|
||||
|
||||
// Reset progress
|
||||
lineClearProgress = 0f
|
||||
|
||||
// Create and start new animation immediately
|
||||
lineClearAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
duration = lineClearDuration
|
||||
interpolator = LinearInterpolator()
|
||||
|
||||
addUpdateListener { animator ->
|
||||
lineClearProgress = animator.animatedValue as Float
|
||||
invalidate()
|
||||
}
|
||||
|
||||
addListener(object : android.animation.AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: android.animation.Animator) {
|
||||
// When animation completes, actually clear the lines
|
||||
gameBoard.clearLinesFromGrid()
|
||||
// Then update UI on the main thread
|
||||
handler.post {
|
||||
invalidate()
|
||||
}
|
||||
})
|
||||
|
||||
start()
|
||||
}.start()
|
||||
} else {
|
||||
// Update UI with current game state
|
||||
onGameStateChanged?.invoke(gameBoard.score, gameBoard.level, gameBoard.lines)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
|
||||
// Enable hardware acceleration
|
||||
// Force hardware acceleration - Critical for performance
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
||||
|
||||
// Update gesture exclusion rect for edge-to-edge rendering
|
||||
|
@ -295,6 +261,18 @@ class GameView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
// Skip drawing if paused or game over - faster return
|
||||
if (isPaused || gameBoard.isGameOver) {
|
||||
super.onDraw(canvas)
|
||||
return
|
||||
}
|
||||
|
||||
// Set hardware layer type during draw for better performance
|
||||
val wasHardwareAccelerated = isHardwareAccelerated
|
||||
if (!wasHardwareAccelerated) {
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
||||
}
|
||||
|
||||
super.onDraw(canvas)
|
||||
|
||||
// Draw background (already black from theme)
|
||||
|
@ -305,14 +283,8 @@ class GameView @JvmOverloads constructor(
|
|||
// Draw grid (very subtle)
|
||||
drawGrid(canvas)
|
||||
|
||||
// Check if line clear animation is in progress
|
||||
if (gameBoard.isLineClearAnimationInProgress) {
|
||||
// Draw the line clearing animation
|
||||
drawLineClearAnimation(canvas)
|
||||
} else {
|
||||
// Draw locked pieces
|
||||
drawLockedBlocks(canvas)
|
||||
}
|
||||
// Draw locked pieces
|
||||
drawLockedBlocks(canvas)
|
||||
|
||||
if (!gameBoard.isGameOver && isRunning) {
|
||||
// Draw ghost piece (landing preview)
|
||||
|
@ -323,49 +295,6 @@ class GameView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the line clearing animation
|
||||
*/
|
||||
private fun drawLineClearAnimation(canvas: Canvas) {
|
||||
// Draw non-clearing blocks
|
||||
for (y in 0 until gameBoard.height) {
|
||||
if (gameBoard.linesToClear.contains(y)) continue
|
||||
|
||||
for (x in 0 until gameBoard.width) {
|
||||
if (gameBoard.isOccupied(x, y)) {
|
||||
drawBlock(canvas, x, y, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw all clearing lines with a single animation effect
|
||||
for (lineY in gameBoard.linesToClear) {
|
||||
for (x in 0 until gameBoard.width) {
|
||||
// Animation effects for all lines simultaneously
|
||||
val brightness = 255 - (lineClearProgress * 150).toInt() // Reduced from 200 for smoother fade
|
||||
val scale = 1.0f - lineClearProgress * 0.3f // Reduced from 0.5f for subtler scaling
|
||||
|
||||
// Set the paint for the clear animation
|
||||
lineClearPaint.color = Color.WHITE
|
||||
lineClearPaint.alpha = brightness.coerceIn(0, 255)
|
||||
|
||||
// Calculate block position with scaling
|
||||
val left = boardLeft + x * blockSize + (blockSize * (1 - scale) / 2)
|
||||
val top = boardTop + lineY * blockSize + (blockSize * (1 - scale) / 2)
|
||||
val right = left + blockSize * scale
|
||||
val bottom = top + blockSize * scale
|
||||
|
||||
// Draw the shrinking, fading block
|
||||
val rect = RectF(left, top, right, bottom)
|
||||
canvas.drawRect(rect, lineClearPaint)
|
||||
|
||||
// Add a more subtle glow effect
|
||||
lineClearPaint.setShadowLayer(8f * (1f - lineClearProgress), 0f, 0f, Color.WHITE)
|
||||
canvas.drawRect(rect, lineClearPaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw glowing border around the playable area
|
||||
*/
|
||||
|
|
|
@ -281,20 +281,15 @@ class GameBoard(
|
|||
* Find lines that should be cleared and store them
|
||||
*/
|
||||
private fun findLinesToClear() {
|
||||
// Clear existing lines before finding new ones
|
||||
// Clear existing lines
|
||||
linesToClear.clear()
|
||||
|
||||
// Quick scan for completed lines
|
||||
for (y in 0 until height) {
|
||||
// Check if line is full
|
||||
var lineFull = true
|
||||
for (x in 0 until width) {
|
||||
if (!grid[y][x]) {
|
||||
lineFull = false
|
||||
break
|
||||
}
|
||||
}
|
||||
val row = grid[y]
|
||||
|
||||
if (lineFull) {
|
||||
// Check if line is full - use all() for better performance
|
||||
if (row.all { it }) {
|
||||
linesToClear.add(y)
|
||||
}
|
||||
}
|
||||
|
@ -322,30 +317,34 @@ class GameBoard(
|
|||
if (linesToClear.isNotEmpty()) {
|
||||
val clearedLines = linesToClear.size
|
||||
|
||||
// Get the highest line that needs to be cleared
|
||||
val highestLine = linesToClear.minOrNull() ?: return
|
||||
// Much faster approach: shift rows in-place without creating temporary arrays
|
||||
// Pre-compute all row movements to minimize array operations
|
||||
val rowMoves = IntArray(height) { -1 } // Where each row should move to
|
||||
var shiftAmount = 0
|
||||
|
||||
// Create a temporary grid to store the new state
|
||||
val newGrid = Array(height) { BooleanArray(width) { false } }
|
||||
|
||||
// Copy non-cleared lines to their new positions
|
||||
var newY = height - 1
|
||||
// Calculate how much to shift each row
|
||||
for (y in height - 1 downTo 0) {
|
||||
if (y !in linesToClear) {
|
||||
for (x in 0 until width) {
|
||||
newGrid[newY][x] = grid[y][x]
|
||||
}
|
||||
newY--
|
||||
if (y in linesToClear) {
|
||||
shiftAmount++
|
||||
} else if (shiftAmount > 0) {
|
||||
rowMoves[y] = y + shiftAmount
|
||||
}
|
||||
}
|
||||
|
||||
// Update the grid with the new state
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
grid[y][x] = newGrid[y][x]
|
||||
// Apply row shifts in a single pass, bottom to top
|
||||
for (y in height - 1 downTo 0) {
|
||||
val targetY = rowMoves[y]
|
||||
if (targetY != -1 && targetY < height) {
|
||||
// Shift this row down
|
||||
System.arraycopy(grid[y], 0, grid[targetY], 0, width)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear top rows (faster than creating a new array)
|
||||
for (y in 0 until clearedLines) {
|
||||
java.util.Arrays.fill(grid[y], false)
|
||||
}
|
||||
|
||||
// Calculate base score (NES scoring system)
|
||||
val baseScore = when (clearedLines) {
|
||||
1 -> 40
|
||||
|
@ -427,7 +426,7 @@ class GameBoard(
|
|||
// Update game speed based on level (NES formula)
|
||||
dropInterval = (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
|
||||
|
||||
// Reset animation state and clear lines
|
||||
// Reset animation state immediately
|
||||
isLineClearAnimationInProgress = false
|
||||
linesToClear.clear()
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue