commonMain.org.brightify.hyperdrive.property.impl.FlatMapLatestDeferredObservableProperty.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of runtime Show documentation
Show all versions of runtime Show documentation
Hyperdrive implementation that's needed for observations and such
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)
}
}
}