diff --git a/common/src/main/kotlin/com/lambda/event/EventFlow.kt b/common/src/main/kotlin/com/lambda/event/EventFlow.kt index b14290673..bda889797 100644 --- a/common/src/main/kotlin/com/lambda/event/EventFlow.kt +++ b/common/src/main/kotlin/com/lambda/event/EventFlow.kt @@ -5,9 +5,20 @@ import com.lambda.event.callback.ICancellable import com.lambda.event.listener.Listener import com.lambda.threading.runConcurrent import com.lambda.threading.runSafe -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout /** @@ -28,12 +39,35 @@ object EventFlow { * useful when you have multiple independent [Job]s running in parallel. */ val lambdaScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) + + /** + * [concurrentFlow] is a [MutableSharedFlow] of [Event]s with a buffer capacity to handle event emissions. + * + * Events emitted to this flow are processed by concurrent listeners, allowing for parallel event handling. + * + * The buffer overflow strategy is set to [BufferOverflow.DROP_OLDEST], meaning that when the buffer is full, + * the oldest event will be dropped to accommodate a new event. + */ val concurrentFlow = MutableSharedFlow( extraBufferCapacity = 1000, onBufferOverflow = BufferOverflow.DROP_OLDEST ) + /** + * [syncListeners] is a [Subscriber] that manages synchronous listeners. + * + * These listeners will be executed immediately when an event is posted, allowing for immediate responses to events. + * The [syncListeners] are stored in a [Subscriber] object, which is a specialized [ConcurrentHashMap] that manages sets of [Listener]s for different [Event] types. + */ val syncListeners = Subscriber() + + /** + * [concurrentListeners] is a [Subscriber] that manages asynchronous listeners. + * + * These listeners will be executed in parallel, each on a dedicated coroutine, + * allowing for concurrent processing of events. + * The [concurrentListeners] are stored in a [Subscriber] object, which is a specialized [ConcurrentHashMap] that manages sets of [Listener]s for different [Event] types. + */ val concurrentListeners = Subscriber() init { @@ -49,7 +83,14 @@ object EventFlow { } } - suspend inline fun awaitEvent( + /** + * Suspends until an event of type [E] is received that satisfies the given [predicate]. + * + * @param E The type of the event to wait for. This should be a subclass of [Event]. + * @param predicate A lambda to test if the event satisfies the condition. + * @return The first event that matches the predicate. + */ + suspend inline fun blockUntilEvent( noinline predicate: SafeContext.(E) -> Boolean = { true }, ) = concurrentFlow.filterIsInstance().first { runSafe { @@ -57,20 +98,45 @@ object EventFlow { } ?: false } - suspend inline fun awaitEventUnsafe( - noinline predicate: (E) -> Boolean = { true }, - ) = concurrentFlow.filterIsInstance().first(predicate) - - suspend inline fun awaitEvent( + /** + * Suspends until an event of type [E] is received that satisfies the given [predicate], + * or until the specified [timeout] occurs. + * + * @param E The type of the event to wait for. This should be a subclass of [Event]. + * @param timeout The maximum time to wait for the event, in milliseconds. + * @param predicate A lambda to test if the event satisfies the condition. + * @return The first event that matches the predicate or throws a timeout exception if not found. + */ + suspend inline fun blockUntilEvent( timeout: Long, noinline predicate: (E) -> Boolean = { true }, ) = runBlocking { - withTimeout(timeout) { - concurrentFlow.filterIsInstance().first(predicate) - } + withTimeout(timeout) { + concurrentFlow.filterIsInstance().first(predicate) } + } - suspend inline fun awaitEvents( + /** + * Suspends until an event of type [E] is received that satisfies the given [predicate]. + * + * This method is "unsafe" in the sense that it does not execute the predicate within a [SafeContext]. + * + * @param E The type of the event to wait for. This should be a subclass of [Event]. + * @param predicate A lambda to test if the event satisfies the condition. + * @return The first event that matches the predicate. + */ + suspend inline fun blockUntilUnsafeEvent( + noinline predicate: (E) -> Boolean = { true }, + ) = concurrentFlow.filterIsInstance().first(predicate) + + /** + * Returns a [Flow] of events of type [E] that satisfy the given [predicate]. + * + * @param E The type of the event to filter. This should be a subclass of [Event]. + * @param predicate A lambda to test if the event satisfies the condition. + * @return A [Flow] emitting events that match the predicate. + */ + suspend inline fun collectEvents( crossinline predicate: (E) -> Boolean = { true }, ): Flow = flow { concurrentFlow @@ -149,23 +215,58 @@ object EventFlow { concurrentListeners.remove(T::class) } - private fun Event.executeListenerSynchronous() { - syncListeners[this::class]?.forEach { listener -> + /** + * Executes the listeners for the current event type synchronously. + * + * This method retrieves the list of synchronous listeners for the event's class + * and invokes their [Listener.execute] method if the listener should be notified. + * + * @receiver The current event for which listeners are to be executed. + * @param T The type of the event being handled. + */ + private fun T.executeListenerSynchronous() { + syncListeners[this::class]?.forEach { + @Suppress("UNCHECKED_CAST") + val listener = it as? Listener ?: return@forEach if (shouldNotNotify(listener, this)) return@forEach listener.execute(this) } } - private fun Event.executeListenerConcurrently() { - concurrentListeners[this::class]?.forEach { listener -> + /** + * Executes the listeners for the current event type concurrently. + * + * This method retrieves the list of concurrent listeners for the event's class + * and invokes their [Listener.execute] method if the listener should be notified. + * Each listener is executed on the same coroutine scope. + * + * @receiver The current event for which listeners are to be executed. + * @param T The type of the event being handled. + */ + private fun T.executeListenerConcurrently() { + concurrentListeners[this::class]?.forEach { + @Suppress("UNCHECKED_CAST") + val listener = it as? Listener ?: return@forEach if (shouldNotNotify(listener, this)) return@forEach listener.execute(this) } } - private fun shouldNotNotify(listener: Listener, event: Event) = + /** + * Determines whether a given [listener] should be notified about an [event]. + * + * A listener should not be notified if: + * - The listener's owner is a [Muteable] and is currently muted, unless the listener is set to [alwaysListen]. + * - The event is cancellable and has been canceled. + * + * @param listener The listener to check. + * @param event The event being processed. + * @param T The type of the event. + * @return `true` if the listener should not be notified, `false` otherwise. + */ + private fun shouldNotNotify(listener: Listener, event: Event) = listener.owner is Muteable && (listener.owner as Muteable).isMuted && !listener.alwaysListen || event is ICancellable && event.isCanceled() -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/event/Subscriber.kt b/common/src/main/kotlin/com/lambda/event/Subscriber.kt index ae4a7b6f5..563b7be35 100644 --- a/common/src/main/kotlin/com/lambda/event/Subscriber.kt +++ b/common/src/main/kotlin/com/lambda/event/Subscriber.kt @@ -14,26 +14,15 @@ import kotlin.reflect.KClass * * @property defaultListenerSet A [ConcurrentSkipListSet] of [Listener]s, sorted in reverse order. */ -class Subscriber : ConcurrentHashMap, ConcurrentSkipListSet>() { - val defaultListenerSet: ConcurrentSkipListSet - get() = ConcurrentSkipListSet(Comparator.reverseOrder()) +class Subscriber : ConcurrentHashMap, ConcurrentSkipListSet>>() { + val defaultListenerSet: ConcurrentSkipListSet> + get() = ConcurrentSkipListSet(Listener.comparator.reversed()) /** Allows a [Listener] to start receiving a specific type of [Event] */ - inline fun subscribe(listener: Listener) = + inline fun subscribe(listener: Listener) = getOrPut(T::class) { defaultListenerSet }.add(listener) - - /** Forgets about every [Listener]s association to [eventType] */ - fun unsubscribe(eventType: KClass<*>) = remove(eventType) - - /** Allows a [Listener] to stop receiving a specific type of [Event] */ - fun unsubscribe(listener: Listener) { - values.forEach { listeners -> - listeners.remove(listener) - } - } - /** Allows a [Subscriber] to start receiving all [Event]s of another [Subscriber]. */ infix fun subscribe(subscriber: Subscriber) { subscriber.forEach { (eventType, listeners) -> @@ -41,11 +30,18 @@ class Subscriber : ConcurrentHashMap, ConcurrentSkipListSet> } } + /** Forgets about every [Listener]'s association to [eventType] */ + fun unsubscribe(eventType: KClass) = + remove(eventType) + + /** Allows a [Listener] to stop receiving a specific type of [Event] */ + inline fun unsubscribe(listener: Listener) = + getOrElse(T::class) { defaultListenerSet }.remove(listener) + /** Allows a [Subscriber] to stop receiving all [Event]s of another [Subscriber] */ infix fun unsubscribe(subscriber: Subscriber) { - entries.removeAll { (eventType, listeners) -> - subscriber[eventType]?.let { listeners.removeAll(it) } - listeners.isEmpty() + subscriber.forEach { (eventType, listeners) -> + getOrElse(eventType) { defaultListenerSet }.removeAll(listeners) } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/event/listener/Listener.kt b/common/src/main/kotlin/com/lambda/event/listener/Listener.kt index ec4868b3e..7733e5000 100644 --- a/common/src/main/kotlin/com/lambda/event/listener/Listener.kt +++ b/common/src/main/kotlin/com/lambda/event/listener/Listener.kt @@ -27,7 +27,7 @@ import com.lambda.module.Module * @property owner The owner of the [Listener]. This is typically the object that created the [Listener]. * @property alwaysListen If true, the [Listener] will always be triggered, even if the [owner] is [Muteable.isMuted]. */ -abstract class Listener : Comparable { +abstract class Listener : Comparable> { abstract val priority: Int abstract val owner: Any abstract val alwaysListen: Boolean @@ -37,21 +37,17 @@ abstract class Listener : Comparable { * * @param event The event that triggered this listener. */ - abstract fun execute(event: Event) + abstract fun execute(event: T) - /** - * Compares this listener with another listener. - * The comparison is based first on the priority, and then on the hash code of the listeners. - * - * @param other The other listener to compare with. - * @return A negative integer, zero, or a positive integer as this listener is less than, equal to, - * or greater than the specified listener. - */ - override fun compareTo(other: Listener) = - compareBy { + override fun compareTo(other: Listener) = + comparator.compare(this, other) + + companion object { + val comparator = compareBy> { it.priority }.thenBy { - // Needed because ConcurrentSkipListSet handles insertion based on compareTo + // Hashcode is needed because ConcurrentSkipListSet handles insertion based on compareTo it.hashCode() - }.compare(this, other) -} \ No newline at end of file + } + } +} diff --git a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt index 4b0c653e6..1546c98b1 100644 --- a/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt +++ b/common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt @@ -7,7 +7,11 @@ import com.lambda.event.Muteable import com.lambda.task.Task import com.lambda.threading.runConcurrent import com.lambda.threading.runSafe +import com.lambda.util.Pointer import com.lambda.util.selfReference +import kotlin.properties.ReadOnlyProperty +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty /** @@ -20,26 +24,45 @@ import com.lambda.util.selfReference * The [SafeListener] class is used to create [Listener]s that execute a given [function] within a [SafeContext]. * This ensures that the [function] is executed in a context where certain safety conditions are met. * + * The [SafeListener] will keep a reference to the last signal processed by the listener. + * Allowing use cases where the last signal is needed. + * ```kotlin + * val lastPacketReceived by listener() + * + * listener { event -> + * println("Last packet received: ${lastPacketReceived?.packet}") + * // prints the last packet received + * // prints null if no packet was received + * } + * ``` + * * @property priority The priority of the listener. Listeners with higher priority are executed first. * @property owner The owner of the listener. This is typically the object that created the listener. * @property alwaysListen If true, the listener will always be triggered, even if the owner is not enabled. * @property function The function to be executed when the event occurs. This function operates within a [SafeContext]. */ -class SafeListener( +class SafeListener( override val priority: Int = 0, override val owner: Any, override val alwaysListen: Boolean = false, - val function: SafeContext.(Event) -> Unit, -) : Listener() { - override fun execute(event: Event) { + val function: SafeContext.(T) -> Unit, +) : Listener(), ReadOnlyProperty { + /** + * The last processed event signal. + */ + private var lastSignal: T? = null + + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = lastSignal + + /** + * Executes the actions defined by this listener when the event occurs. + * + * Note that running this function outside the game thread can + * lead to race conditions when manipulating shared data. + */ + override fun execute(event: T) { runSafe { -// if (!mc.isOnThread) { -// LOG.warn(""" -// Event ${this::class.simpleName} executed outside the game thread. -// This can lead to race conditions when manipulating shared data. -// Consider moving the execution to the game thread using runSafeOnGameThread { ... } or runOnGameThread { ... }. -// """.trimIndent()) -// } + lastSignal = event function(event) } } @@ -77,21 +100,19 @@ class SafeListener( inline fun Any.listener( priority: Int = 0, alwaysListen: Boolean = false, - noinline function: SafeContext.(T) -> Unit, - ): SafeListener { - val listener = SafeListener(priority, this, alwaysListen) { event -> - function(event as T) - } + noinline function: SafeContext.(T) -> Unit = {}, + ): SafeListener { + val listener = SafeListener(priority, this, alwaysListen) { event -> function(event) } - EventFlow.syncListeners.subscribe(listener) + EventFlow.syncListeners.subscribe(listener) return listener } /** * This function registers a new [SafeListener] for a generic [Event] type [T]. - * The [function] is executed on the same thread where the [Event] was dispatched. - * The [function] will only be executed when the context satisfies certain safety conditions. + * The [transform] is executed on the same thread where the [Event] was dispatched. + * The [transform] will only be executed when the context satisfies certain safety conditions. * These conditions are met when none of the following [SafeContext] properties are null: * - [SafeContext.world] * - [SafeContext.player] @@ -100,11 +121,11 @@ class SafeListener( * * This typically occurs when the user is in-game. * - * After the [function] is executed once, the [SafeListener] will be automatically unsubscribed. + * After the [transform] is executed once, the [SafeListener] will be automatically unsubscribed. * * Usage: * ```kotlin - * private val event by listenOnce { event -> + * private val event by listenNext { event -> * player.sendMessage("Event received only once: $event") * // event is stored in the value * // event is unsubscribed after execution @@ -114,29 +135,33 @@ class SafeListener( * @param T The type of the event to listen for. This should be a subclass of Event. * @param priority The priority of the listener. Listeners with higher priority will be executed first. The Default value is 0. * @param alwaysListen If true, the listener will be executed even if it is muted. The Default value is false. - * @param function The function to be executed when the event is posted. This function should take a SafeContext and an event of type T as parameters. + * @param transform The function used to transform the event into a value. * @return The newly created and registered [SafeListener]. */ - inline fun Any.listenOnce( + inline fun Any.listenOnce( priority: Int = 0, alwaysListen: Boolean = false, - noinline function: SafeContext.(T) -> Unit = {}, - ): Lazy { - // This doesn't leak memory because the owner still has a reference to the listener - var value: T? = null + noinline predicate: SafeContext.(T) -> Boolean = { true }, + noinline transform: SafeContext.(T) -> E? = { null }, + ): ReadWriteProperty { + val pointer = Pointer() - val destroyable by selfReference { + val destroyable by selfReference> { SafeListener(priority, this@listenOnce, alwaysListen) { event -> - function(event as T) - value = event + pointer.value = transform(event) - EventFlow.syncListeners.unsubscribe(self) + if (predicate(event) && + pointer.value != null + ) { + val self by this@selfReference + EventFlow.syncListeners.unsubscribe(self) + } } } EventFlow.syncListeners.subscribe(destroyable) - return lazy { value } + return pointer } /** @@ -173,10 +198,10 @@ class SafeListener( inline fun Task<*>.listener( priority: Int = 0, alwaysListen: Boolean = false, - noinline function: SafeContext.(T) -> Unit, - ): SafeListener { - val listener = SafeListener(priority, this, alwaysListen) { event -> - function(event as T) // ToDo: run function always on game thread + noinline function: SafeContext.(T) -> Unit = {}, + ): SafeListener { + val listener = SafeListener(priority, this, alwaysListen) { event -> + function(event) // ToDo: run function always on game thread } syncListeners.subscribe(listener) @@ -212,11 +237,11 @@ class SafeListener( inline fun Any.concurrentListener( priority: Int = 0, alwaysListen: Boolean = false, - noinline function: suspend SafeContext.(T) -> Unit, - ): SafeListener { - val listener = SafeListener(priority, this, alwaysListen) { event -> + noinline function: suspend SafeContext.(T) -> Unit = {}, + ): SafeListener { + val listener = SafeListener(priority, this, alwaysListen) { event -> runConcurrent { - function(event as T) + function(event) } } diff --git a/common/src/main/kotlin/com/lambda/event/listener/UnsafeListener.kt b/common/src/main/kotlin/com/lambda/event/listener/UnsafeListener.kt index be8a7d997..3b19a9866 100644 --- a/common/src/main/kotlin/com/lambda/event/listener/UnsafeListener.kt +++ b/common/src/main/kotlin/com/lambda/event/listener/UnsafeListener.kt @@ -5,9 +5,13 @@ import com.lambda.event.Event import com.lambda.event.EventFlow import com.lambda.event.Muteable import com.lambda.event.listener.SafeListener.Companion.concurrentListener -import com.lambda.event.listener.SafeListener.Companion.listenOnce import com.lambda.event.listener.SafeListener.Companion.listener +import com.lambda.event.listener.SafeListener.Companion.listenOnce +import com.lambda.util.Pointer import com.lambda.util.selfReference +import kotlin.properties.ReadOnlyProperty +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty /** * An [UnsafeListener] is a specialized type of [Listener] that operates without a [SafeContext]. @@ -19,25 +23,41 @@ import com.lambda.util.selfReference * The [UnsafeListener] class is used to create [Listener]s that execute a given [function] without a [SafeContext]. * This means that the [function] is executed in a context where certain safety conditions may not be met. * + * The [SafeListener] will keep a reference to the last signal processed by the listener. + * Allowing use cases where the last signal is needed. + * ```kotlin + * val lastPacketReceived by unsafeListener() + * + * unsafeListener { event -> + * println("Last packet received: ${lastPacketReceived?.packet}") + * // prints the last packet received + * // prints null if no packet was received + * } + * ``` + * * @property priority The priority of the listener. Listeners with higher priority are executed first. * @property owner The owner of the listener. This is typically the object that created the listener. * @property alwaysListen If true, the listener will always be triggered, even if the owner is not enabled. * @property function The function to be executed when the event occurs. This function operates without a [SafeContext]. */ -class UnsafeListener( +class UnsafeListener( override val priority: Int, override val owner: Any, override val alwaysListen: Boolean = false, - val function: (Event) -> Unit, -) : Listener() { - override fun execute(event: Event) { -// if (!mc.isOnThread) { -// LOG.warn(""" -// Event ${this::class.simpleName} executed outside the game thread. -// This can lead to race conditions when manipulating game data. -// Consider moving the execution to the game thread using runSafeOnGameThread { ... } or runOnGameThread { ... }. -// """.trimIndent()) -// } + val function: (T) -> Unit, +) : Listener(), ReadOnlyProperty { + /** + * The last processed event signal. + */ + private var lastSignal: T? = null + + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = lastSignal + + /** + * Executes the actions defined by this listener when the event occurs. + */ + override fun execute(event: T) { + lastSignal = event function(event) } @@ -62,18 +82,18 @@ class UnsafeListener( * ``` * * @param T The type of the event to listen for. This should be a subclass of Event. - * @param priority The priority of the listener. Listeners with higher priority will be executed first. The Default value is 0. - * @param alwaysListen If true, the listener will be executed even if it is muted. The Default value is false. + * @param priority The priority of the listener. Listeners with higher priority will be executed first. + * @param alwaysListen If true, the listener will be executed even if it is muted. * @param function The function to be executed when the event is posted. This function should take an event of type T as a parameter. * @return The newly created and registered [UnsafeListener]. */ inline fun Any.unsafeListener( priority: Int = 0, alwaysListen: Boolean = false, - noinline function: (T) -> Unit, - ): UnsafeListener { - val listener = UnsafeListener(priority, this, alwaysListen) { event -> - function(event as T) + noinline function: (T) -> Unit = {}, + ): UnsafeListener { + val listener = UnsafeListener(priority, this, alwaysListen) { event -> + function(event) } EventFlow.syncListeners.subscribe(listener) @@ -86,8 +106,6 @@ class UnsafeListener( * The [function] is executed only once when the [Event] is dispatched. * This function should only be used when the [function] performs read actions on the game data. * For only in-game related contexts, use the [SafeListener.listenOnce] function instead. - * The listener will be automatically unsubscribed after the first execution. - * This function is useful for one-time event handling. * * Usage: * ```kotlin @@ -102,31 +120,35 @@ class UnsafeListener( * After the [function] is executed once, the [SafeListener] will be automatically unsubscribed. * * @param T The type of the event to listen for. This should be a subclass of Event. - * @param priority The priority of the listener. Listeners with higher priority will be executed first. The Default value is 0. - * @param alwaysListen If true, the listener will be executed even if it is muted. The Default value is false. - * @param function The function to be executed when the event is posted. This function should take an event of type T as a parameter. + * @param priority The priority of the listener. Listeners with higher priority will be executed first. + * @param alwaysListen If true, the listener will be executed even if it is muted. + * @param transform The function used to transform the event into a value. * @return The newly created and registered [UnsafeListener]. */ - inline fun Any.unsafeListenOnce( + inline fun Any.unsafeListenOnce( priority: Int = 0, alwaysListen: Boolean = false, - noinline function: (T) -> Unit, - ): Lazy { - // This doesn't leak memory because the owner still has a reference to the listener - var value: T? = null + noinline transform: (T) -> E? = { null }, + noinline predicate: (T) -> Boolean = { true }, + ): ReadWriteProperty { + val pointer = Pointer() - val destroyable by selfReference { + val destroyable by selfReference> { UnsafeListener(priority, this@unsafeListenOnce, alwaysListen) { event -> - function(event as T) - value = event + pointer.value = transform(event) - EventFlow.syncListeners.unsubscribe(self) + if (predicate(event) && + pointer.value != null) + { + val self by this@selfReference + EventFlow.syncListeners.unsubscribe(self) + } } } EventFlow.syncListeners.subscribe(destroyable) - return lazy { value } + return pointer } /** @@ -158,11 +180,9 @@ class UnsafeListener( inline fun Any.unsafeConcurrentListener( priority: Int = 0, alwaysListen: Boolean = false, - noinline function: (T) -> Unit, - ): UnsafeListener { - val listener = UnsafeListener(priority, this, alwaysListen) { event -> - function(event as T) - } + noinline function: (T) -> Unit = {}, + ): UnsafeListener { + val listener = UnsafeListener(priority, this, alwaysListen) { event -> function(event) } EventFlow.concurrentListeners.subscribe(listener) diff --git a/common/src/main/kotlin/com/lambda/util/Pointer.kt b/common/src/main/kotlin/com/lambda/util/Pointer.kt new file mode 100644 index 000000000..9d01f5ea9 --- /dev/null +++ b/common/src/main/kotlin/com/lambda/util/Pointer.kt @@ -0,0 +1,16 @@ +package com.lambda.util + +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * A class representing a pointer to a value. + * + * It is a high-level abstraction over a mutable variable that allows for easy access to the value. + */ +data class Pointer(var value: T? = null) : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = value + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { + this.value = value + } +} diff --git a/common/src/main/kotlin/com/lambda/util/SelfReference.kt b/common/src/main/kotlin/com/lambda/util/SelfReference.kt index 2392316bd..fd22b86ea 100644 --- a/common/src/main/kotlin/com/lambda/util/SelfReference.kt +++ b/common/src/main/kotlin/com/lambda/util/SelfReference.kt @@ -1,10 +1,9 @@ package com.lambda.util -class SelfReference(initializer: SelfReference.() -> T) { - val self: T by lazy { inner } +import kotlin.properties.ReadOnlyProperty - private val inner = initializer() - operator fun getValue(thisRef: Any?, property: Any?) = self -} +inline fun selfReference(noinline initializer: ReadOnlyProperty.() -> T) = object : ReadOnlyProperty { + val value: T by lazy { initializer() } -fun selfReference(initializer: SelfReference.() -> T): SelfReference = SelfReference(initializer) + override fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>) = value +}