All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.peanuuutz.fork.ui.internal.GlobalUIContext.kt Maven / Gradle / Ivy

The newest version!
package net.peanuuutz.fork.ui.internal

import androidx.compose.runtime.BroadcastFrameClock
import androidx.compose.runtime.Recomposer
import androidx.compose.runtime.snapshots.ObserverHandle
import androidx.compose.runtime.snapshots.Snapshot
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import net.minecraft.client.MinecraftClient
import net.peanuuutz.fork.coroutine.core.BroadcastTickClock
import net.peanuuutz.fork.coroutine.core.Client
import net.peanuuutz.fork.coroutine.core.ForkClientMainScope
import net.peanuuutz.fork.coroutine.core.ForkGlobalScope
import net.peanuuutz.fork.coroutine.core.delayTicks
import net.peanuuutz.fork.util.common.nanoNow
import net.peanuuutz.fork.util.fork.ForkLogger

internal object GlobalUIContext : ClientLifecycleEvents.ClientStopping {
    // -------- State --------

    private var isRunning: Boolean = false

    // -------- Frame Clock --------

    private var hasScheduledFrame: Boolean = false

    private val frameClock: BroadcastFrameClock = BroadcastFrameClock { hasScheduledFrame = true }

    private var frameClockJob: Job? = null

    // -------- Tick Clock --------

    private var hasScheduledTick: Boolean = false

    private val tickClock: BroadcastTickClock = BroadcastTickClock { hasScheduledTick = true }

    private var tickClockJob: Job? = null

    // -------- Recomposer --------

    val recomposer: Recomposer = Recomposer(Dispatchers.Client + tickClock)

    private var recomposerJob: Job? = null

    // -------- Snapshot Manager --------

    private val applyFlow: MutableSharedFlow = MutableSharedFlow(
        extraBufferCapacity = 16,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )

    private var applyJob: Job? = null

    private var applyObserver: ObserverHandle? = null

    // -------- Setup --------

    fun ensureStarted() {
        if (isRunning) {
            return
        }
        isRunning = true
        setupSnapshotManager()
        setupRecomposer()
        setupFrameClock()
        setupTickClock()
    }

    private fun setupSnapshotManager() {
        applyJob = ForkClientMainScope.launch(CoroutineName("GlobalWriter")) {
            applyFlow.collect {
                // We apply changes in another scope to avoid blocking
                Snapshot.sendApplyNotifications()
            }
        }
        applyObserver = Snapshot.registerGlobalWriteObserver {
            applyFlow.tryEmit(Unit)
        }
    }

    private fun setupRecomposer() {
        recomposerJob = ForkGlobalScope.launch(frameClock + CoroutineName("Recomposer")) {
            recomposer.runRecomposeAndApplyChanges()
        }
    }

    private fun setupFrameClock() {
        ForkGlobalScope.launch(CoroutineName("Frame")) {
            while (isRunning) {
                if (hasScheduledFrame) {
                    hasScheduledFrame = false
                    // We're on another thread, so switch to main thread to avoid concurrent issues
                    ForkClientMainScope.launch {
                        try {
                            frameClock.sendFrame(nanoNow())
                        } catch (e: Exception) {
                            val message = "Exception occurred while sending frame"
                            ForkLogger.error(message, e)
                        }
                    }
                }
                delay(10L)
            }
        }
    }

    private fun setupTickClock() {
        // Ticking is available on Dispatchers.Client but not Dispatchers.Default
        ForkClientMainScope.launch(CoroutineName("Tick")) {
            var ticks = 0L
            while (isRunning) {
                if (hasScheduledTick) {
                    hasScheduledTick = false
                    // We're already on main thread
                    try {
                        tickClock.sendTick(ticks)
                    } catch (e: Exception) {
                        val message = "Exception occurred while sending tick"
                        ForkLogger.error(message, e)
                    }
                }
                delayTicks(1L)
                ticks++
            }
        }
    }

    // -------- Dispose --------

    override fun onClientStopping(client: MinecraftClient) {
        isRunning = false
        disposeSnapshotManager()
        disposeRecomposer()
        disposeFrameClock()
        disposeTickClock()
    }

    private fun disposeSnapshotManager() {
        applyJob?.cancel()
        applyJob = null
        applyObserver?.dispose()
        applyObserver = null
    }

    private fun disposeRecomposer() {
        recomposer.close()
        recomposerJob?.cancel()
        recomposerJob = null
    }

    private fun disposeFrameClock() {
        frameClock.cancel()
        frameClockJob?.cancel()
        frameClockJob = null
    }

    private fun disposeTickClock() {
        tickClock.cancel()
        tickClockJob?.cancel()
        tickClockJob = null
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy