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

commonMain.org.brightify.hyperdrive.property.impl.FlatMapLatestDeferredObservableProperty.kt Maven / Gradle / Ivy

There is a newer version: 0.1.159
Show newest version
package org.brightify.hyperdrive.property.impl

import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
import org.brightify.hyperdrive.CancellationToken
import org.brightify.hyperdrive.property.DeferredObservableProperty
import org.brightify.hyperdrive.property.ObservableProperty
import org.brightify.hyperdrive.utils.Optional
import org.brightify.hyperdrive.utils.flatMap
import org.brightify.hyperdrive.utils.mapToKotlin
import org.brightify.hyperdrive.utils.someOrDefault
import org.brightify.hyperdrive.utils.toOptional

internal class FlatMapLatestDeferredObservableProperty(
    private val switchMapped: DeferredObservableProperty,
    private val transform: (T) -> DeferredObservableProperty,
    private val equalityPolicy: ObservableProperty.EqualityPolicy>,
): DeferredObservableProperty, DeferredObservableProperty.Listener {
    private var activeBacking: DeferredObservableProperty? = switchMapped.latestValue.mapToKotlin(transform)

    override var latestValue: Optional = activeBacking.toOptional().flatMap { it.latestValue }
        private set

    private val valueFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
    private val listeners = ValueChangeListenerHandler(this)
    private val passthroughListener = PassthroughValueChangeListener()
    @Suppress("JoinDeclarationAndAssignment")
    private val switchMappingSubscriptionCancellation: CancellationToken
    private var activeBackingSubscriptionCancellation: CancellationToken?

    override suspend fun await(): U = coroutineScope {
        latestValue.someOrDefault {
            nextValue()
        }
    }

    override suspend fun nextValue(): U = coroutineScope {
        valueFlow.first()
    }

    init {
        switchMappingSubscriptionCancellation = switchMapped.addListener(this)
        activeBackingSubscriptionCancellation = activeBacking?.addListener(passthroughListener)
    }

    override fun valueDidChange(oldValue: Optional, newValue: T) {
        val oldActiveBacking = activeBacking
        val newActiveBacking = transform(newValue)
        if (oldActiveBacking != null && equalityPolicy.isEqual(oldActiveBacking, newActiveBacking)) { return }

        // Only remove the listener if we intend to replace the active backing.
        activeBackingSubscriptionCancellation?.cancel()

        val newActiveBackingLatestValue = newActiveBacking.latestValue
        if (newActiveBackingLatestValue is Optional.Some) {
            listeners.notifyValueWillChange(latestValue, newActiveBackingLatestValue.value)
        }

        activeBacking = newActiveBacking

        if (newActiveBackingLatestValue is Optional.Some) {
            latestValue = newActiveBackingLatestValue
            valueFlow.tryEmit(newActiveBackingLatestValue.value)
            listeners.notifyValueDidChange(oldActiveBacking?.latestValue ?: Optional.None, newActiveBackingLatestValue.value)
        }

        activeBackingSubscriptionCancellation = newActiveBacking.addListener(passthroughListener)
    }

    override fun addListener(listener: DeferredObservableProperty.Listener): CancellationToken = listeners.addListener(listener)

    override fun removeListener(listener: DeferredObservableProperty.Listener) = listeners.removeListener(listener)

    inner class PassthroughValueChangeListener: DeferredObservableProperty.Listener {
        override fun valueWillChange(oldValue: Optional, newValue: U) {
            if (oldValue is Optional.None) {
                listeners.notifyValueWillChange(latestValue, newValue)
            } else {
                listeners.notifyValueWillChange(oldValue, newValue)
            }
        }

        override fun valueDidChange(oldValue: Optional, newValue: U) {
            val oldLatestValue = latestValue
            latestValue = Optional.Some(newValue)
            valueFlow.tryEmit(newValue)
            listeners.notifyValueDidChange(oldLatestValue, newValue)
        }
    }
}