Good evening! Did you ever happened to hear anyth...
# multiplatform
c
Good evening! Did you ever happened to hear anything about running an infinite loop inside of compose? I have a sample of what I'm talking about but I'm quite unhappy with what I came up with. This is a multiplatform sample and it's in my common 🧵
Copy code
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color.Companion.Green
import androidx.compose.ui.graphics.Color.Companion.Red
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.platform.LocalFontFamilyResolver
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import kotlinx.coroutines.delay
import kotlin.math.max

/**
 * Constants for time calculations.
 */
private const val oneSecondInNano = 1_000_000_000.0
private const val fps = 60.0
private const val desiredMillisPerFrame = oneSecondInNano / fps

/**
 * Runs the provided block of code at a regular interval determined by the desired FPS.
 */
@Composable
fun Run(deltaBlock: (Double, Int) -> Unit) {
    LaunchedEffect(Unit) {
        var lastLoopTime = time.now()

        while (true) {
            withFrameNanos { it }  // This waits for the next frame.
            val now = time.now()
            val updateLength = now - lastLoopTime

            if (updateLength >= desiredMillisPerFrame) {
                lastLoopTime = now
                val delta = updateLength / desiredMillisPerFrame
                val fps = if (delta > 0) {
                    100.0/delta
                }else{
                    0
                }
                deltaBlock(delta, fps.toInt())

                val waitTime = (lastLoopTime - time.now() + desiredMillisPerFrame) / oneSecondInNano
                delay(max(waitTime.toUInt(), 0u).toLong())
            }
        }
    }
}

/**
 * Renders the provided content on a Canvas, updating it at a regular interval.
 */
@Composable
fun RenderCompose(modifier: Modifier = Modifier, content: DrawScope.(Double) -> Unit) {
    var state by remember { mutableStateOf(0.0) }
    var currentFPS = 0
    val fontFamily = LocalFontFamilyResolver.current

    Run { delta, fps ->
        state = delta
        currentFPS = fps
    }

    Canvas(modifier) {
        if (showFPS) {
            drawFPS(fontFamily, currentFPS)
        }
        content(state)
    }
}

/**
 * Runs the provided simulation at a regular interval.
 */
@Composable
fun SimulateCompose(simulation: (Double) -> Unit) {
    Run { delta, _ ->
        simulation(delta)
    }
}

@OptIn(ExperimentalTextApi::class)
private fun DrawScope.drawFPS(fontFamily: FontFamily.Resolver, currentFPS: Int) {
    this.drawRect(color = Red, size = Size(height = 35f, width = 240f))
    this.drawText(
        style = TextStyle.Default.copy(color = Green),
        textMeasurer = TextMeasurer(
            fallbackFontFamilyResolver = fontFamily,
            fallbackDensity = Density(1f),
            fallbackLayoutDirection = LayoutDirection.Ltr,
        ),
        size = Size(height = 35f, width = 240f),
        text = AnnotatedString("Current FPS: $currentFPS"),
    )
}
Copy code
@Composable
fun MovingBallSample() {
    val bounds = Bounds(maxX = 100.dp.value.toDouble(), maxY = 100.dp.value.toDouble())
    val movingObject = ObjectWithAcceleration(bounds = bounds)
    Column {
        RenderCompose(
            Modifier
                .size(bounds.maxX.dp, bounds.maxY.dp)
                .background(Color.White)
        ) {
            movingObject.render(this)
        }

        SimulateCompose {
            movingObject.simulation(it)
        }

//        Controller(moveUp = { movingObject.move(y = -100.0) },
//            moveDown = { movingObject.move(y = 1.0) },
//            moveLeft = { movingObject.move(x = -1.0) },
//            moveRight = { movingObject.move(x = 1.0) },
//            quitGame = { movingObject.stop() }
//        )
    }
}
Copy code
data class Position(var x: Double, var y: Double, val z: Int = 0)

data class Bounds(
    val x: Double = 0.0,
    val maxX: Double = 0.0,
    val y: Double = 0.0,
    val maxY: Double = 0.0
)

class ObjectWithAcceleration(
    private val bounds: Bounds,
    private val radius: Float = 10F
) {
    var position = Position(10.0 + radius, 10.0 + radius)
    private var vx = 0.1
    private var vy = 0.1

    fun simulation(delta: Double) {
        //bounce on collision
        detectCollision(delta)

        // Update position based on velocity

        simulateMovement(
            position = position,
            vx = vx,
            vy = vy,
            delta = delta,
            bounds = bounds,
            radius = radius
        )
    }

    fun move(x: Double = 0.0, y: Double = 0.0) {
        vx += x
        vy += y
    }

    fun stop() {
        vx = 0.0
        vy = 0.0
    }

    private fun detectCollision(delta: Double) {
        val speed = sqrt(vx * vx + vy * vy)
        val maxSpeed = radius / delta // deltaTime is the time elapsed since the last update

        // If the speed is above the maximum, scale the velocity vector down to the maximum speed
        if (speed > maxSpeed) {
            val scale = maxSpeed / speed
            vx *= scale
            vy *= scale
        }

        // Your original collision detection code
        when {
            position.x + radius > bounds.maxX -> {
                vx *= -1.0
                position.x = bounds.maxX - radius
            }

            position.x - radius < bounds.x -> {
                vx *= -1.0
                position.x = bounds.x + radius
            }

            position.y + radius > bounds.maxY -> {
                vy *= -1.0
                position.y = bounds.maxY - radius
            }

            position.y - radius < bounds.y -> {
                vy *= -1.0
                position.y = bounds.y + radius
            }
        }
    }

    fun render(content: DrawScope) {
        content.drawCircle(
            Color.Black,
            radius = radius,
            center = Offset(position.x.toFloat(), position.y.toFloat()),
            style = Stroke(width = 10F)
        )
    }
}