Skip to content

Commit b4ff344

Browse files
authored
Listener semmantics and dynamic scheduling (#89)
### Semmantics Changed ```kotlin listener<TickEvent.Pre> { } listenOnce<TickEvent.Pre> {} concurrentListener<TickEvent.Pre> { } unsafeListener<TickEvent.Pre> {} ``` to ```kotlin listen<TickEvent.Pre> { } listenOnce<TickEvent.Pre> {} listenConcurrently<TickEvent.Pre> { } listenUnsafe<TickEvent.Pre> {} ``` as its more naturally readable. ### Scheduling Now you can pass the scheduling dispatcher to the concurrent listener: ```kotlin listenConcurrently<Data.Safe>(Dispatchers.IO) { } ```
1 parent bdb9dd1 commit b4ff344

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+413
-407
lines changed

common/src/main/kotlin/com/lambda/config/Configuration.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import com.lambda.Lambda.LOG
2424
import com.lambda.Lambda.gson
2525
import com.lambda.config.configurations.ModuleConfig
2626
import com.lambda.event.events.ClientEvent
27-
import com.lambda.event.listener.UnsafeListener.Companion.unsafeListener
27+
import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe
2828
import com.lambda.threading.runIO
2929
import com.lambda.util.Communication.info
3030
import com.lambda.util.Communication.logError
@@ -57,9 +57,9 @@ abstract class Configuration : Jsonable {
5757
get() = File("${primary.parent}/${primary.nameWithoutExtension}-backup.${primary.extension}")
5858

5959
init {
60-
unsafeListener<ClientEvent.Startup> { tryLoad() }
60+
listenUnsafe<ClientEvent.Startup> { tryLoad() }
6161

62-
unsafeListener<ClientEvent.Shutdown>(Int.MIN_VALUE) { trySave() }
62+
listenUnsafe<ClientEvent.Shutdown>(Int.MIN_VALUE) { trySave() }
6363

6464
register()
6565
}

common/src/main/kotlin/com/lambda/core/PingManager.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package com.lambda.core
1919

2020
import com.lambda.event.events.PacketEvent
2121
import com.lambda.event.events.TickEvent
22-
import com.lambda.event.listener.SafeListener.Companion.listener
22+
import com.lambda.event.listener.SafeListener.Companion.listen
2323
import com.lambda.util.collections.LimitedOrderedSet
2424
import net.minecraft.network.packet.c2s.query.QueryPingC2SPacket
2525
import net.minecraft.network.packet.s2c.query.PingResultS2CPacket
@@ -33,12 +33,12 @@ object PingManager : Loadable {
3333
get() = pings.lastOrNull() ?: 0
3434

3535
init {
36-
listener<TickEvent.Pre> {
36+
listen<TickEvent.Pre> {
3737
connection.sendPacket(QueryPingC2SPacket(Util.getMeasuringTimeMs()))
3838
}
3939

40-
listener<PacketEvent.Receive.Pre> { event ->
41-
if (event.packet !is PingResultS2CPacket) return@listener
40+
listen<PacketEvent.Receive.Pre> { event ->
41+
if (event.packet !is PingResultS2CPacket) return@listen
4242

4343
pings.add(Util.getMeasuringTimeMs() - event.packet.startTime)
4444
}

common/src/main/kotlin/com/lambda/event/EventFlow.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ object EventFlow {
6666
* the oldest event will be dropped to accommodate a new event.
6767
*/
6868
val concurrentFlow = MutableSharedFlow<Event>(
69-
extraBufferCapacity = 1000,
69+
extraBufferCapacity = 10000,
7070
onBufferOverflow = BufferOverflow.DROP_OLDEST
7171
)
7272

common/src/main/kotlin/com/lambda/event/listener/SafeListener.kt

Lines changed: 59 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import com.lambda.threading.runConcurrent
2626
import com.lambda.threading.runSafe
2727
import com.lambda.util.Pointer
2828
import com.lambda.util.selfReference
29+
import kotlinx.coroutines.CoroutineDispatcher
30+
import kotlinx.coroutines.Dispatchers
2931
import kotlin.properties.ReadOnlyProperty
3032
import kotlin.properties.ReadWriteProperty
3133
import kotlin.reflect.KProperty
@@ -99,11 +101,11 @@ class SafeListener<T : Event>(
99101
*
100102
* Usage:
101103
* ```kotlin
102-
* listener<MyEvent> { event ->
104+
* listen<MyEvent> { event ->
103105
* player.sendMessage("Event received: $event")
104106
* }
105107
*
106-
* listener<MyEvent>(priority = 1) { event ->
108+
* listen<MyEvent>(priority = 1) { event ->
107109
* player.sendMessage("Event received before the previous listener: $event")
108110
* }
109111
* ```
@@ -114,18 +116,65 @@ class SafeListener<T : Event>(
114116
* @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.
115117
* @return The newly created and registered [SafeListener].
116118
*/
117-
inline fun <reified T : Event> Any.listener(
119+
inline fun <reified T : Event> Any.listen(
118120
priority: Int = 0,
119121
alwaysListen: Boolean = false,
120122
noinline function: SafeContext.(T) -> Unit = {},
121123
): SafeListener<T> {
122-
val listener = SafeListener<T>(priority, this, alwaysListen) { event -> function(event) }
124+
val listener = SafeListener<T>(priority, this, alwaysListen) { event ->
125+
function(event)
126+
}
123127

124128
EventFlow.syncListeners.subscribe(listener)
125129

126130
return listener
127131
}
128132

133+
/**
134+
* Registers a new [SafeListener] for a generic [Event] type [T] within the context of a [Task].
135+
* The [function] is executed on the same thread where the [Event] was dispatched.
136+
* The [function] will only be executed when the context satisfies certain safety conditions.
137+
* These conditions are met when none of the following [SafeContext] properties are null:
138+
* - [SafeContext.world]
139+
* - [SafeContext.player]
140+
* - [SafeContext.interaction]
141+
* - [SafeContext.connection]
142+
*
143+
* Usage:
144+
* ```kotlin
145+
* myTask.listen<MyEvent> { event ->
146+
* player.sendMessage("Event received: $event")
147+
* }
148+
*
149+
* myTask.listen<MyEvent>(priority = 1) { event ->
150+
* player.sendMessage("Event received before the previous listener: $event")
151+
* }
152+
* ```
153+
*
154+
* @param T The type of the event to listen for.
155+
* This should be a subclass of Event.
156+
* @param priority The priority of the listener.
157+
* Listeners with higher priority will be executed first.
158+
* The Default value is 0.
159+
* @param alwaysListen If true, the listener will be executed even if it is muted. The Default value is false.
160+
* @param function The function to be executed when the event is posted.
161+
* This function should take a SafeContext and an event of type T as parameters.
162+
* @return The newly created and registered [SafeListener].
163+
*/
164+
inline fun <reified T : Event> Task<*>.listen(
165+
priority: Int = 0,
166+
alwaysListen: Boolean = false,
167+
noinline function: SafeContext.(T) -> Unit = {},
168+
): SafeListener<T> {
169+
val listener = SafeListener<T>(priority, this, alwaysListen) { event ->
170+
function(event) // ToDo: run function always on game thread
171+
}
172+
173+
syncListeners.subscribe<T>(listener)
174+
175+
return listener
176+
}
177+
129178
/**
130179
* This function registers a new [SafeListener] for a generic [Event] type [T].
131180
* The [transform] is executed on the same thread where the [Event] was dispatched.
@@ -142,7 +191,7 @@ class SafeListener<T : Event>(
142191
*
143192
* Usage:
144193
* ```kotlin
145-
* private val event by listenNext<MyEvent> { event ->
194+
* private val event by listenOnce<MyEvent> { event ->
146195
* player.sendMessage("Event received only once: $event")
147196
* // event is stored in the value
148197
* // event is unsubscribed after execution
@@ -181,51 +230,6 @@ class SafeListener<T : Event>(
181230
return pointer
182231
}
183232

184-
/**
185-
* Registers a new [SafeListener] for a generic [Event] type [T] within the context of a [Task].
186-
* The [function] is executed on the same thread where the [Event] was dispatched.
187-
* The [function] will only be executed when the context satisfies certain safety conditions.
188-
* These conditions are met when none of the following [SafeContext] properties are null:
189-
* - [SafeContext.world]
190-
* - [SafeContext.player]
191-
* - [SafeContext.interaction]
192-
* - [SafeContext.connection]
193-
*
194-
* Usage:
195-
* ```kotlin
196-
* myTask.listener<MyEvent> { event ->
197-
* player.sendMessage("Event received: $event")
198-
* }
199-
*
200-
* myTask.listener<MyEvent>(priority = 1) { event ->
201-
* player.sendMessage("Event received before the previous listener: $event")
202-
* }
203-
* ```
204-
*
205-
* @param T The type of the event to listen for.
206-
* This should be a subclass of Event.
207-
* @param priority The priority of the listener.
208-
* Listeners with higher priority will be executed first.
209-
* The Default value is 0.
210-
* @param alwaysListen If true, the listener will be executed even if it is muted. The Default value is false.
211-
* @param function The function to be executed when the event is posted.
212-
* This function should take a SafeContext and an event of type T as parameters.
213-
* @return The newly created and registered [SafeListener].
214-
*/
215-
inline fun <reified T : Event> Task<*>.listener(
216-
priority: Int = 0,
217-
alwaysListen: Boolean = false,
218-
noinline function: SafeContext.(T) -> Unit = {},
219-
): SafeListener<T> {
220-
val listener = SafeListener<T>(priority, this, alwaysListen) { event ->
221-
function(event) // ToDo: run function always on game thread
222-
}
223-
224-
syncListeners.subscribe<T>(listener)
225-
226-
return listener
227-
}
228-
229233
/**
230234
* Registers a new [SafeListener] for a generic [Event] type [T].
231235
* The [function] is executed on a new thread running asynchronously to the game thread.
@@ -236,12 +240,12 @@ class SafeListener<T : Event>(
236240
*
237241
* Usage:
238242
* ```kotlin
239-
* concurrentListener<MyEvent> { event ->
243+
* listenConcurrently<MyEvent> { event ->
240244
* println("Concurrent event received: $event")
241245
* // no safe access to player or world
242246
* }
243247
*
244-
* concurrentListener<MyEvent>(priority = 1) { event ->
248+
* listenConcurrently<MyEvent>(priority = 1) { event ->
245249
* println("Concurrent event received before the previous listener: $event")
246250
* }
247251
* ```
@@ -251,13 +255,14 @@ class SafeListener<T : Event>(
251255
* @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.
252256
* @return The newly created and registered [SafeListener].
253257
*/
254-
inline fun <reified T : Event> Any.concurrentListener(
258+
inline fun <reified T : Event> Any.listenConcurrently(
255259
priority: Int = 0,
256260
alwaysListen: Boolean = false,
261+
scheduler: CoroutineDispatcher = Dispatchers.Default,
257262
noinline function: suspend SafeContext.(T) -> Unit = {},
258263
): SafeListener<T> {
259264
val listener = SafeListener<T>(priority, this, alwaysListen) { event ->
260-
runConcurrent {
265+
runConcurrent(scheduler) {
261266
function(event)
262267
}
263268
}

common/src/main/kotlin/com/lambda/event/listener/UnsafeListener.kt

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,16 @@ class UnsafeListener<T : Event>(
8181
* The [function] is executed on the same thread where the [Event] was dispatched.
8282
* The execution of the [function] is independent of the safety conditions of the context.
8383
* Use this function when you need to listen to an [Event] in a context that is not in-game.
84-
* For only in-game related contexts, use the [SafeListener.listener] function instead.
84+
* For only in-game related contexts, use the [SafeListener.listen] function instead.
8585
*
8686
* Usage:
8787
* ```kotlin
88-
* unsafeListener<MyEvent> { event ->
88+
* listenUnsafe<MyEvent> { event ->
8989
* println("Unsafe event received: $event")
9090
* // no safe access to player or world
9191
* }
9292
*
93-
* unsafeListener<MyEvent>(priority = 1) { event ->
93+
* listenUnsafe<MyEvent>(priority = 1) { event ->
9494
* println("Unsafe event received before the previous listener: $event")
9595
* }
9696
* ```
@@ -101,7 +101,7 @@ class UnsafeListener<T : Event>(
101101
* @param function The function to be executed when the event is posted. This function should take an event of type T as a parameter.
102102
* @return The newly created and registered [UnsafeListener].
103103
*/
104-
inline fun <reified T : Event> Any.unsafeListener(
104+
inline fun <reified T : Event> Any.listenUnsafe(
105105
priority: Int = 0,
106106
alwaysListen: Boolean = false,
107107
noinline function: (T) -> Unit = {},
@@ -123,7 +123,7 @@ class UnsafeListener<T : Event>(
123123
*
124124
* Usage:
125125
* ```kotlin
126-
* private val event by unsafeListenOnce<MyEvent> { event ->
126+
* private val event by listenOnceUnsafe<MyEvent> { event ->
127127
* println("Unsafe event received only once: $event")
128128
* // no safe access to player or world
129129
* // event is stored in the value
@@ -139,7 +139,7 @@ class UnsafeListener<T : Event>(
139139
* @param transform The function used to transform the event into a value.
140140
* @return The newly created and registered [UnsafeListener].
141141
*/
142-
inline fun <reified T : Event, reified E> Any.unsafeListenOnce(
142+
inline fun <reified T : Event, reified E> Any.listenOnceUnsafe(
143143
priority: Int = 0,
144144
alwaysListen: Boolean = false,
145145
noinline transform: (T) -> E? = { null },
@@ -148,7 +148,7 @@ class UnsafeListener<T : Event>(
148148
val pointer = Pointer<E>()
149149

150150
val destroyable by selfReference<UnsafeListener<T>> {
151-
UnsafeListener(priority, this@unsafeListenOnce, alwaysListen) { event ->
151+
UnsafeListener(priority, this@listenOnceUnsafe, alwaysListen) { event ->
152152
pointer.value = transform(event)
153153

154154
if (predicate(event) &&
@@ -169,19 +169,19 @@ class UnsafeListener<T : Event>(
169169
* Registers a new [UnsafeListener] for a generic [Event] type [T].
170170
* The [function] is executed on a new thread running asynchronously to the game thread.
171171
* This function should only be used when the [function] performs read actions on the game data.
172-
* For only in-game related contexts, use the [SafeListener.concurrentListener] function instead.
172+
* For only in-game related contexts, use the [SafeListener.listenConcurrently] function instead.
173173
*
174174
* Caution: Using this function to write to the game data can lead to race conditions. Therefore, it is recommended
175175
* to use this function only for read operations to avoid potential concurrency issues.
176176
*
177177
* Usage:
178178
* ```kotlin
179-
* concurrentListener<MyEvent> { event ->
179+
* listenUnsafeConcurrently<MyEvent> { event ->
180180
* println("Concurrent event received: $event")
181181
* // no safe access to player or world
182182
* }
183183
*
184-
* concurrentListener<MyEvent>(priority = 1) { event ->
184+
* listenUnsafeConcurrently<MyEvent>(priority = 1) { event ->
185185
* println("Concurrent event received before the previous listener: $event")
186186
* }
187187
* ```
@@ -191,12 +191,14 @@ class UnsafeListener<T : Event>(
191191
* @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.
192192
* @return The newly created and registered [UnsafeListener].
193193
*/
194-
inline fun <reified T : Event> Any.unsafeConcurrentListener(
194+
inline fun <reified T : Event> Any.listenUnsafeConcurrently(
195195
priority: Int = 0,
196196
alwaysListen: Boolean = false,
197197
noinline function: (T) -> Unit = {},
198198
): UnsafeListener<T> {
199-
val listener = UnsafeListener<T>(priority, this, alwaysListen) { event -> function(event) }
199+
val listener = UnsafeListener<T>(priority, this, alwaysListen) { event ->
200+
function(event)
201+
}
200202

201203
EventFlow.concurrentListeners.subscribe<T>(listener)
202204

common/src/main/kotlin/com/lambda/graphics/RenderMain.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.lambda.Lambda.mc
2121
import com.lambda.event.EventFlow.post
2222
import com.lambda.event.events.RenderEvent
2323
import com.lambda.event.events.TickEvent
24-
import com.lambda.event.listener.SafeListener.Companion.listener
24+
import com.lambda.event.listener.SafeListener.Companion.listen
2525
import com.lambda.graphics.animation.Animation.Companion.exp
2626
import com.lambda.graphics.animation.AnimationTicker
2727
import com.lambda.graphics.buffer.FrameBuffer
@@ -46,7 +46,7 @@ object RenderMain {
4646
private val showHud get() = mc.currentScreen == null || LambdaHudGui.isOpen
4747

4848
private val hudAnimation0 = with(AnimationTicker()) {
49-
listener<TickEvent.Pre> {
49+
listen<TickEvent.Pre> {
5050
tick()
5151
}
5252

0 commit comments

Comments
 (0)