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

commonMain.QueueingEventSubject.kt Maven / Gradle / Ivy

package kt.mobius.extras

import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
import kt.mobius.EventSource
import kt.mobius.disposables.Disposable
import kt.mobius.functions.Consumer
import kotlin.js.JsExport

/**
 * An EventSource that can also consume events. If it has a subscriber, events will be immediately
 * forwarded to that subscriber. If it doesn't have a subscriber, it will queue up events (up to the
 * maximum capacity specified in the constructor), and forward all queued events to the next
 * subscriber. Only a single subscription at a time is permitted.
 */
@Suppress("NON_EXPORTABLE_TYPE")
@JsExport
public class QueueingEventSubject(
    private val capacity: Int
) : SynchronizedObject(), EventSource, Consumer {
    private enum class State {
        NO_SUBSCRIBER, SUBSCRIBED
    }

    private val queue = ArrayList(capacity)

    // State and subscriber are accessed only in synchronized sections
    private var state = State.NO_SUBSCRIBER
    private var subscriber: Consumer? = null

    override fun subscribe(eventConsumer: Consumer): Disposable {
        lateinit var queued: List

        // Do not invoke consumer in synchronized section
        synchronized(this) {
            if (state == State.SUBSCRIBED) {
                error("Only a single subscription is supported, previous subscriber is: $subscriber")
            }
            state = State.SUBSCRIBED
            subscriber = eventConsumer
            queued = ArrayList(queue)
            queue.clear()
        }
        queued.forEach(eventConsumer::accept)
        return Unsubscriber()
    }

    override fun accept(value: E) {
        var consumerToInvoke: Consumer? = null

        // Do not invoke consumer in synchronized section
        synchronized(this) {
            when (state) {
                State.NO_SUBSCRIBER -> {
                    check(queue.size < capacity) {
                        "Queue capacity exceeded, cannot queue $value"
                    }
                    queue.add(value)
                }
                State.SUBSCRIBED -> consumerToInvoke = subscriber
            }
        }
        consumerToInvoke?.accept(value)
    }

    private fun unsubscribe() {
        synchronized(this) {
            state = State.NO_SUBSCRIBER
            subscriber = null
        }
    }

    private inner class Unsubscriber : SynchronizedObject(), Disposable {
        private var disposed = false

        override fun dispose() {
            synchronized(this) {
                if (disposed) {
                    return
                }
                disposed = true
                unsubscribe()
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy