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

jvmMain.org.openrndr.events.Event.kt Maven / Gradle / Ivy

The newest version!
@file:JvmName("EventJVM")
package org.openrndr.events


import io.github.oshai.kotlinlogging.KotlinLogging
import java.time.Duration
import java.time.Instant
import java.util.concurrent.CopyOnWriteArrayList

private val logger = KotlinLogging.logger {}

private const val ignoreExceptionProperty = "org.openrndr.events.ignoreExceptions"
private val ignoreExceptions by lazy { System.getProperties().containsKey(ignoreExceptionProperty) }

/**
 * an event class
 * @param name a name for the event, this is used for logging and debugging purposes only, default is ""
 * @param postpone should message delivery for this event be postponed, default is false
 */
actual class Event actual constructor(val name: String, var postpone: Boolean) {
    private var lastTriggered = Instant.ofEpochMilli(0L)

    /**
     * number of times this event is triggered
     */
    var triggerCount = 0L
        private set

    private val messages = mutableListOf()
    actual val listeners: MutableList<(T) -> Unit> = CopyOnWriteArrayList()

    private val oneShotListeners = CopyOnWriteArrayList<(T) -> Unit>()


    val timeSinceLastTrigger: Duration
        get() = Duration.between(lastTriggered, Instant.now())

    private fun internalTrigger() {
        triggerCount++
        lastTriggered = Instant.now()
    }


    @Deprecated("use the postpone property directly")
    fun postpone(postpone: Boolean): Event {
        this.postpone = postpone
        return this
    }

    /**
     * trigger the event
     *
     * When [Event.postpone] is false messages will be delivered immediately to the listeners.
     * However, when [Event.postpone] is true messages will not be delivered until [Event.deliver] is invoked.
     *
     * @param message the message to be delivered to the listeners
     */
    actual fun trigger(message: T) {
        internalTrigger()
        if (!postpone) {
            this.listeners.forEach { listener ->
                try {
                    listener(message)
                } catch (e: Throwable) {
                    logger.error { "Exception thrown in listener ('${name}'): ${e.javaClass.simpleName}; '${e.message}'" }
                    if (!ignoreExceptions)
                        throw e
                    else {
                        e.printStackTrace()
                    }
                }
            }
            this.oneShotListeners.forEach { listener ->
                try {
                    listener(message)
                } catch (e: Throwable) {
                    logger.error { "Exception thrown in one-shot listener ('${name}'): ${e.javaClass.simpleName}; '${e.message}'" }
                    if (!ignoreExceptions)
                        throw e
                    else {
                        e.printStackTrace()
                    }
                }
            }
            this.oneShotListeners.clear()
        } else {
            if (this.listeners.size > 0 || this.oneShotListeners.size > 0) {
                synchronized(messages) {
                    messages.add(message)
                }
            }
        }
    }

    /**
     * deliver postponed event messages
     *
     * Invoking this method will only deliver messages when [Event.postpone] is true, otherwise it will silently do
     * nothing.
     */
    actual fun deliver() {
        if (postpone) {
            synchronized(messages) {
                if (messages.size > 0) {
                    val copy = mutableListOf()
                    copy.addAll(messages)
                    messages.clear()

                    copy.forEach { m ->
                        this.listeners.forEach { l ->
                            try {
                                l(m)
                            } catch (e: Exception) {
                                logger.error { "Exception thrown in listener ('${name}'): ${e.javaClass.simpleName}; '${e.message}'" }
                                if (!ignoreExceptions)
                                    throw e
                                else {
                                    e.printStackTrace()
                                }
                            }
                        }
                        this.oneShotListeners.forEach { l ->
                            try {
                                l(m)
                            } catch (e: Exception) {
                                logger.error { "Exception thrown in one-shot listener ('${name}'): ${e.javaClass.simpleName}; '${e.message}'" }
                                if (!ignoreExceptions)
                                    throw e
                                else {
                                    e.printStackTrace()
                                }
                            }
                        }
                        this.oneShotListeners.clear()
                    }
                }
            }
        }
    }
    actual fun listen(listener: (T) -> Unit): (T) -> Unit {
        listeners.add(listener)
        return listener
    }

    actual fun listen(listener: Event): (T) -> Unit {
        val listenFunction = { m: T -> listener.trigger(m) }
        listeners.add(listenFunction)
        return listenFunction
    }
    actual fun cancel(listener: (T) -> Unit) {
        listeners.remove(listener)
    }

    actual fun listenOnce(listener: (T) -> Unit) {
        oneShotListeners.add(listener)
    }

    actual fun listenOnce(listener: Event) {
        oneShotListeners.add { v1 -> listener.trigger(v1) }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy