Replace properties with flows
This commit is contained in:
parent
991f77c45a
commit
25281d0f6d
@ -1,31 +0,0 @@
|
|||||||
package space.kscience.dataforge.properties
|
|
||||||
|
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.*
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.startsWith
|
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
public class MetaProperty<T : Any>(
|
|
||||||
public val meta: ObservableMutableMeta,
|
|
||||||
public val name: Name,
|
|
||||||
public val converter: MetaConverter<T>,
|
|
||||||
) : Property<T?> {
|
|
||||||
|
|
||||||
override var value: T?
|
|
||||||
get() = converter.readNullable(meta[name])
|
|
||||||
set(value) {
|
|
||||||
meta[name] = converter.convertNullable(value) ?: Meta.EMPTY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
|
||||||
meta.onChange(owner) { name ->
|
|
||||||
if (name.startsWith(this@MetaProperty.name)) callback(converter.readNullable(this[name]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeChangeListener(owner: Any?) {
|
|
||||||
meta.removeListener(owner)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package space.kscience.dataforge.properties
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
public interface Property<T> {
|
|
||||||
public var value: T
|
|
||||||
|
|
||||||
public fun onChange(owner: Any? = null, callback: (T) -> Unit)
|
|
||||||
public fun removeChangeListener(owner: Any? = null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
public fun <T> Property<T>.toFlow(): StateFlow<T> = MutableStateFlow(value).also { stateFlow ->
|
|
||||||
onChange {
|
|
||||||
stateFlow.value = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reflect all changes in the [source] property onto this property. Does not reflect changes back.
|
|
||||||
*
|
|
||||||
* @return a mirroring job
|
|
||||||
*/
|
|
||||||
@DFExperimental
|
|
||||||
public fun <T> Property<T>.mirror(source: Property<T>) {
|
|
||||||
source.onChange(this) {
|
|
||||||
this.value = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bi-directional connection between properties
|
|
||||||
*/
|
|
||||||
@DFExperimental
|
|
||||||
public fun <T> Property<T>.bind(other: Property<T>) {
|
|
||||||
onChange(other) {
|
|
||||||
other.value = it
|
|
||||||
}
|
|
||||||
other.onChange {
|
|
||||||
this.value = it
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,51 @@
|
|||||||
|
package space.kscience.dataforge.properties
|
||||||
|
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import space.kscience.dataforge.meta.*
|
||||||
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
public fun <T> ObservableMeta.asFlow(converter: MetaSpec<T>): Flow<T> = callbackFlow {
|
||||||
|
onChange(this){
|
||||||
|
trySend(converter.read(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
awaitClose{
|
||||||
|
removeListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
public fun <T> MutableMeta.listenTo(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
flow: Flow<T>,
|
||||||
|
): Job = flow.onEach {
|
||||||
|
update(converter.convert(it))
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
public fun <T> ObservableMutableMeta.bind(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
converter: MetaConverter<T>,
|
||||||
|
flow: MutableSharedFlow<T>,
|
||||||
|
): Job = scope.launch{
|
||||||
|
listenTo(this, converter,flow)
|
||||||
|
onChange(flow){
|
||||||
|
launch {
|
||||||
|
flow.emit(converter.read(this@onChange))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flow.onCompletion {
|
||||||
|
removeListener(flow)
|
||||||
|
}
|
||||||
|
}.also {
|
||||||
|
it.invokeOnCompletion {
|
||||||
|
removeListener(flow)
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
package space.kscience.dataforge.properties
|
|
||||||
|
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.Scheme
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import space.kscience.dataforge.names.parseAsName
|
|
||||||
import space.kscience.dataforge.names.startsWith
|
|
||||||
import kotlin.reflect.KMutableProperty1
|
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
public fun <S : Scheme, T : Any> S.property(property: KMutableProperty1<S, T?>): Property<T?> =
|
|
||||||
object : Property<T?> {
|
|
||||||
override var value: T?
|
|
||||||
get() = property.get(this@property)
|
|
||||||
set(value) {
|
|
||||||
property.set(this@property, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
|
||||||
this@property.meta.onChange(this) { name ->
|
|
||||||
if (name.startsWith(property.name.parseAsName(true))) {
|
|
||||||
callback(property.get(this@property))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeChangeListener(owner: Any?) {
|
|
||||||
this@property.meta.removeListener(this@property)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package space.kscience.dataforge.properties
|
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.Scheme
|
|
||||||
import space.kscience.dataforge.meta.SchemeSpec
|
|
||||||
import space.kscience.dataforge.meta.int
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
internal class TestScheme : Scheme() {
|
|
||||||
var a by int()
|
|
||||||
var b by int()
|
|
||||||
companion object : SchemeSpec<TestScheme>(::TestScheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
class MetaPropertiesTest {
|
|
||||||
@Test
|
|
||||||
fun testBinding() {
|
|
||||||
val scheme = TestScheme.empty()
|
|
||||||
val a = scheme.property(TestScheme::a)
|
|
||||||
val b = scheme.property(TestScheme::b)
|
|
||||||
a.bind(b)
|
|
||||||
scheme.a = 2
|
|
||||||
assertEquals(2, scheme.b)
|
|
||||||
assertEquals(2, b.value)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package space.kscience.dataforge.properties
|
|
||||||
|
|
||||||
import org.w3c.dom.HTMLInputElement
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
public fun HTMLInputElement.bindValue(property: Property<String>) {
|
|
||||||
if (this.onchange != null) error("Input element already bound")
|
|
||||||
this.onchange = {
|
|
||||||
property.value = this.value
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
property.onChange(this) {
|
|
||||||
if (value != it) {
|
|
||||||
value = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DFExperimental
|
|
||||||
public fun HTMLInputElement.bindChecked(property: Property<Boolean>) {
|
|
||||||
if (this.onchange != null) error("Input element already bound")
|
|
||||||
this.onchange = {
|
|
||||||
property.value = this.checked
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
property.onChange(this) {
|
|
||||||
if (checked != it) {
|
|
||||||
checked = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,12 +17,15 @@ internal data class MetaListener(
|
|||||||
*/
|
*/
|
||||||
public interface ObservableMeta : Meta {
|
public interface ObservableMeta : Meta {
|
||||||
/**
|
/**
|
||||||
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
* Add change listener to this meta. The Owner is declared to be able to remove listeners later.
|
||||||
|
* Listeners without an owner could be only removed all together.
|
||||||
|
*
|
||||||
|
* `this` object in the listener represents the current state of this meta. The name points to a changed node
|
||||||
*/
|
*/
|
||||||
public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit)
|
public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all listeners belonging to given owner
|
* Remove all listeners belonging to the given [owner]. Passing null removes all listeners.
|
||||||
*/
|
*/
|
||||||
public fun removeListener(owner: Any?)
|
public fun removeListener(owner: Any?)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user