mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-17 23:45:22 +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)
|
maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.OUTER)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val lineClearPaint = Paint().apply {
|
// Pre-allocate paint objects to avoid GC
|
||||||
color = Color.WHITE
|
private val tmpPaint = Paint()
|
||||||
alpha = 255
|
|
||||||
isAntiAlias = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation
|
|
||||||
private var lineClearAnimator: ValueAnimator? = null
|
|
||||||
private var lineClearProgress = 0f
|
|
||||||
private val lineClearDuration = 100L // milliseconds
|
|
||||||
|
|
||||||
// Calculate block size based on view dimensions and board size
|
// Calculate block size based on view dimensions and board size
|
||||||
private var blockSize = 0f
|
private var blockSize = 0f
|
||||||
|
@ -147,7 +139,7 @@ class GameView @JvmOverloads constructor(
|
||||||
gameBoard.onPieceMove = { onPieceMove?.invoke() }
|
gameBoard.onPieceMove = { onPieceMove?.invoke() }
|
||||||
gameBoard.onPieceLock = { onPieceLock?.invoke() }
|
gameBoard.onPieceLock = { onPieceLock?.invoke() }
|
||||||
|
|
||||||
// Enable hardware acceleration
|
// Force hardware acceleration - This is critical for performance
|
||||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
setLayerType(LAYER_TYPE_HARDWARE, null)
|
||||||
|
|
||||||
// Set better frame rate using modern APIs
|
// Set better frame rate using modern APIs
|
||||||
|
@ -185,7 +177,6 @@ class GameView @JvmOverloads constructor(
|
||||||
fun pause() {
|
fun pause() {
|
||||||
isPaused = true
|
isPaused = true
|
||||||
handler.removeCallbacks(gameLoopRunnable)
|
handler.removeCallbacks(gameLoopRunnable)
|
||||||
lineClearAnimator?.cancel()
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +189,6 @@ class GameView @JvmOverloads constructor(
|
||||||
gameBoard.reset()
|
gameBoard.reset()
|
||||||
gameBoard.startGame() // Add this line to ensure a new piece is spawned
|
gameBoard.startGame() // Add this line to ensure a new piece is spawned
|
||||||
handler.removeCallbacks(gameLoopRunnable)
|
handler.removeCallbacks(gameLoopRunnable)
|
||||||
lineClearAnimator?.cancel()
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,55 +206,31 @@ class GameView @JvmOverloads constructor(
|
||||||
// Move the current tetromino down automatically
|
// Move the current tetromino down automatically
|
||||||
gameBoard.moveDown()
|
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()) {
|
if (gameBoard.linesToClear.isNotEmpty()) {
|
||||||
// Trigger line clear callback for vibration
|
// Trigger line clear callback for vibration
|
||||||
onLineClear?.invoke(gameBoard.linesToClear.size)
|
onLineClear?.invoke(gameBoard.linesToClear.size)
|
||||||
|
|
||||||
// Start line clearing animation immediately
|
// Trigger line clearing on a background thread to prevent UI freezes
|
||||||
startLineClearAnimation()
|
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)
|
// Then update UI on the main thread
|
||||||
}
|
handler.post {
|
||||||
|
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
})
|
}.start()
|
||||||
|
} else {
|
||||||
start()
|
// 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) {
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
super.onSizeChanged(w, h, oldw, oldh)
|
super.onSizeChanged(w, h, oldw, oldh)
|
||||||
|
|
||||||
// Enable hardware acceleration
|
// Force hardware acceleration - Critical for performance
|
||||||
setLayerType(LAYER_TYPE_HARDWARE, null)
|
setLayerType(LAYER_TYPE_HARDWARE, null)
|
||||||
|
|
||||||
// Update gesture exclusion rect for edge-to-edge rendering
|
// Update gesture exclusion rect for edge-to-edge rendering
|
||||||
|
@ -295,6 +261,18 @@ class GameView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
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)
|
super.onDraw(canvas)
|
||||||
|
|
||||||
// Draw background (already black from theme)
|
// Draw background (already black from theme)
|
||||||
|
@ -305,14 +283,8 @@ class GameView @JvmOverloads constructor(
|
||||||
// Draw grid (very subtle)
|
// Draw grid (very subtle)
|
||||||
drawGrid(canvas)
|
drawGrid(canvas)
|
||||||
|
|
||||||
// Check if line clear animation is in progress
|
// Draw locked pieces
|
||||||
if (gameBoard.isLineClearAnimationInProgress) {
|
drawLockedBlocks(canvas)
|
||||||
// Draw the line clearing animation
|
|
||||||
drawLineClearAnimation(canvas)
|
|
||||||
} else {
|
|
||||||
// Draw locked pieces
|
|
||||||
drawLockedBlocks(canvas)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gameBoard.isGameOver && isRunning) {
|
if (!gameBoard.isGameOver && isRunning) {
|
||||||
// Draw ghost piece (landing preview)
|
// 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
|
* Draw glowing border around the playable area
|
||||||
*/
|
*/
|
||||||
|
@ -675,4 +604,4 @@ class GameView @JvmOverloads constructor(
|
||||||
update()
|
update()
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,20 +281,15 @@ class GameBoard(
|
||||||
* Find lines that should be cleared and store them
|
* Find lines that should be cleared and store them
|
||||||
*/
|
*/
|
||||||
private fun findLinesToClear() {
|
private fun findLinesToClear() {
|
||||||
// Clear existing lines before finding new ones
|
// Clear existing lines
|
||||||
linesToClear.clear()
|
linesToClear.clear()
|
||||||
|
|
||||||
|
// Quick scan for completed lines
|
||||||
for (y in 0 until height) {
|
for (y in 0 until height) {
|
||||||
// Check if line is full
|
val row = grid[y]
|
||||||
var lineFull = true
|
|
||||||
for (x in 0 until width) {
|
|
||||||
if (!grid[y][x]) {
|
|
||||||
lineFull = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lineFull) {
|
// Check if line is full - use all() for better performance
|
||||||
|
if (row.all { it }) {
|
||||||
linesToClear.add(y)
|
linesToClear.add(y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -322,30 +317,34 @@ class GameBoard(
|
||||||
if (linesToClear.isNotEmpty()) {
|
if (linesToClear.isNotEmpty()) {
|
||||||
val clearedLines = linesToClear.size
|
val clearedLines = linesToClear.size
|
||||||
|
|
||||||
// Get the highest line that needs to be cleared
|
// Much faster approach: shift rows in-place without creating temporary arrays
|
||||||
val highestLine = linesToClear.minOrNull() ?: return
|
// 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
|
// Calculate how much to shift each row
|
||||||
val newGrid = Array(height) { BooleanArray(width) { false } }
|
|
||||||
|
|
||||||
// Copy non-cleared lines to their new positions
|
|
||||||
var newY = height - 1
|
|
||||||
for (y in height - 1 downTo 0) {
|
for (y in height - 1 downTo 0) {
|
||||||
if (y !in linesToClear) {
|
if (y in linesToClear) {
|
||||||
for (x in 0 until width) {
|
shiftAmount++
|
||||||
newGrid[newY][x] = grid[y][x]
|
} else if (shiftAmount > 0) {
|
||||||
}
|
rowMoves[y] = y + shiftAmount
|
||||||
newY--
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the grid with the new state
|
// Apply row shifts in a single pass, bottom to top
|
||||||
for (y in 0 until height) {
|
for (y in height - 1 downTo 0) {
|
||||||
for (x in 0 until width) {
|
val targetY = rowMoves[y]
|
||||||
grid[y][x] = newGrid[y][x]
|
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)
|
// Calculate base score (NES scoring system)
|
||||||
val baseScore = when (clearedLines) {
|
val baseScore = when (clearedLines) {
|
||||||
1 -> 40
|
1 -> 40
|
||||||
|
@ -427,7 +426,7 @@ class GameBoard(
|
||||||
// Update game speed based on level (NES formula)
|
// Update game speed based on level (NES formula)
|
||||||
dropInterval = (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
|
dropInterval = (1000 * Math.pow(0.8, (level - 1).toDouble())).toLong()
|
||||||
|
|
||||||
// Reset animation state and clear lines
|
// Reset animation state immediately
|
||||||
isLineClearAnimationInProgress = false
|
isLineClearAnimationInProgress = false
|
||||||
linesToClear.clear()
|
linesToClear.clear()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue