Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: use immutable event results #29

Draft
wants to merge 1 commit into
base: events
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import net.silkmc.silk.core.event.Events
* Set up a callback which automatically registers this command on server startup.
*/
fun LiteralArgumentBuilder<CommandSourceStack>.setupRegistrationCallback() {
Events.Command.register.listen { event ->
Events.Command.register.listen {
event.dispatcher.register(this)
}
}
Expand All @@ -22,7 +22,7 @@ fun LiteralArgumentBuilder<CommandSourceStack>.setupRegistrationCallback() {
* Set up a callback which automatically registers this command on server startup.
*/
fun RegistrableCommand<CommandSourceStack>.setupRegistrationCallback() {
Events.Command.register.listen { event ->
Events.Command.register.listen {
commandBuilder.toBrigadier(event.context).forEach {
event.dispatcher.root.addChild(it)
}
Expand All @@ -32,7 +32,7 @@ fun RegistrableCommand<CommandSourceStack>.setupRegistrationCallback() {
@Environment(EnvType.CLIENT)
@JvmName("setupRegistrationCallbackClient")
fun LiteralArgumentBuilder<ClientCommandSourceStack>.setupRegistrationCallback() {
Events.Command.registerClient.listen { event ->
Events.Command.registerClient.listen {
event.dispatcher.register(this)
}
}
Expand All @@ -43,7 +43,7 @@ fun LiteralArgumentBuilder<ClientCommandSourceStack>.setupRegistrationCallback()
@Environment(EnvType.CLIENT)
@JvmName("setupRegistrationCallbackClient")
fun RegistrableCommand<ClientCommandSourceStack>.setupRegistrationCallback() {
Events.Command.registerClient.listen { event ->
Events.Command.registerClient.listen {
commandBuilder.toBrigadier(event.context).forEach {
event.dispatcher.root.addChild(it)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.silkmc.silk.core.mixin.server;

import net.minecraft.server.MinecraftServer;
import net.silkmc.silk.core.event.Cancellable.Cancelled;
import net.silkmc.silk.core.event.ServerEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
Expand Down Expand Up @@ -58,8 +59,8 @@ private void onStopped(CallbackInfo ci) {
private void onHalt(CallbackInfo ci) {
if (
ServerEvents.INSTANCE.getPreHalt()
.invoke(new ServerEvents.ServerEvent((MinecraftServer) (Object) this))
.isCancelled().get()
// instanceof would work too, or we could add a utility property `isCancelled`
.invoke(new ServerEvents.ServerEvent((MinecraftServer) (Object) this)) == Cancelled.INSTANCE
) {
ci.cancel();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.silkmc.silk.core.event

import net.silkmc.silk.core.annotations.ExperimentalSilkApi

@ExperimentalSilkApi
sealed interface Cancellable {
@ExperimentalSilkApi
object Active : Cancellable

@ExperimentalSilkApi
object Cancelled : Cancellable
}
96 changes: 48 additions & 48 deletions silk-core/src/main/kotlin/net/silkmc/silk/core/event/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,21 @@ object Events
* [onlySync] and [onlySyncImmutable] functions in the `companion object`.
*/
@ExperimentalSilkApi
open class Event<T, S : EventScope>(val scopeSupplier: () -> S) {
open class Event<E, R>(val resultSupplier: () -> R) {

/**
* The listeners added by [listen], sorted by [EventPriority] via the index
* in the outer list.
*/
@InternalSilkApi
val listenersByPriority: List<MutableList<context(S, MutableEventScope) (T) -> Unit>> = buildList {
repeat(EventPriority.values().size) {
add(ArrayList())
}
}
val listenersByPriority: List<MutableList<Listener<E, R>>> =
List(EventPriority.values().size) { ArrayList() }

/**
* The listeners added by [monitor].
*/
@InternalSilkApi
val monitorListeners: MutableList<context(S) (T) -> Unit> = ArrayList()
val monitorListeners: MutableList<Listener<E, R>> = ArrayList()

/**
* Listens to this event. The [callback] will always be called synchronously.
Expand All @@ -54,17 +51,22 @@ open class Event<T, S : EventScope>(val scopeSupplier: () -> S) {
* invoked synchronously after all mutations have taken place, see [monitor]
* @param register if true (the default), the listener will be register immediately
*
* @return the [ListenerInstance], making it possible to register and unregister
* @return the [Listener], making it possible to register and unregister
* the listener later
*/
fun listen(
priority: EventPriority = EventPriority.NORMAL,
register: Boolean = true,
callback: context(S, MutableEventScope) (T) -> Unit,
): ListenerInstance<*> {
return ListenerInstance(this, callback, listenersByPriority[priority.ordinal])
.also { if (register) it.register() }
}
callback: context(EventScope<E, R>) () -> Unit,
): Listener<E, R> = ViewListener(this, listenersByPriority[priority.ordinal], callback)
.also { if (register) it.register() }

fun listenMut(
priority: EventPriority = EventPriority.NORMAL,
register: Boolean = true,
callback: context(EventScope<E, R>) () -> R,
): Listener<E, R> = MutListener(this, listenersByPriority[priority.ordinal], callback)
.also { if (register) it.register() }

/**
* Monitors this event. This is the same as [listen], but you do not have access
Expand All @@ -73,37 +75,36 @@ open class Event<T, S : EventScope>(val scopeSupplier: () -> S) {
*/
fun monitor(
register: Boolean = true,
callback: context(S) (T) -> Unit,
): ListenerInstance<*> {
return ListenerInstance(this, callback, monitorListeners)
.also { if (register) it.register() }
}
callback: context(EventScope<E, R>) () -> Unit,
): Listener<E, R> = ViewListener(this, monitorListeners, callback)
.also { if (register) it.register() }

/**
* Invokes this event. Calling this function will trigger all
* listeners and collectors.
*/
open fun invoke(instance: T, scope: S) {
open fun invoke(instance: E, initialResult: R) {
synchronized(this) {
var scope = EventScope(instance, initialResult)
for (listeners in listenersByPriority) {
for (listener in listeners) {
listener(scope, MutableEventScope, instance)
scope = listener(scope)
}
}
for (listener in monitorListeners) {
listener(scope, instance)
listener(scope)
}
}
}

/**
* Same as [invoke], but it uses the default scope provided by [scopeSupplier].
* Returns the resulting scope.
* Same as [invoke], but it uses the default initial result provided by [resultSupplier].
* Returns the final result.
*/
fun invoke(instance: T): S {
val scope = scopeSupplier()
invoke(instance, scope)
return scope
fun invoke(instance: E): R {
val result = resultSupplier()
invoke(instance, result)
return result
}

companion object {
Expand All @@ -114,39 +115,38 @@ open class Event<T, S : EventScope>(val scopeSupplier: () -> S) {
* The scope [S] passed to this function determines what kind of actions
* can be performed in response to the event.
*
* @param scopeSupplier creates the default scope for this event
* @param resultSupplier creates the default scope for this event
*/
fun <T, S : EventScope> syncAsync(clientSide: Boolean = false, scopeSupplier: () -> S) =
AsyncEvent<T, S>(clientSide, scopeSupplier)
fun <E, R> syncAsync(clientSide: Boolean = false, resultSupplier: () -> R) =
AsyncEvent<E, R>(clientSide, resultSupplier)

/**
* Creates an [AsyncEvent] with synchronous and asynchronous
* listener invocation, accepting events of the type [T].
* listener invocation, accepting events of the type [E].
* The event scope passed to event handlers will be empty,
* effectively making the event immutable for all handlers.
*/
fun <T> syncAsyncImmutable(clientSide: Boolean = false) =
AsyncEvent<T, EventScope.Empty>(clientSide, scopeSupplier = { EventScope.Empty })
fun <E> syncAsyncImmutable(clientSide: Boolean = false) =
AsyncEvent<E, Unit>(clientSide, resultSupplier = { })

/**
* Creates a classic [Event] without async listener invocation,
* accepting events of the type [T].
* The scope [S] passed to this function determines what kind of actions
* accepting events of the type [E].
* The scope [R] passed to this function determines what kind of actions
* can be performed in response to the event.
*
* @param scopeSupplier creates the default scope for this event
* @param resultSupplier creates the default scope for this event
*/
fun <T, S : EventScope> onlySync(scopeSupplier: () -> S) =
Event<T, S>(scopeSupplier)
fun <E, R> onlySync(resultSupplier: () -> R) =
Event<E, R>(resultSupplier)

/**
* Creates a classic [Event] without async listener invocation,
* accepting events of the type [T].
* accepting events of the type [E].
* The event scope passed to event handlers will be empty,
* effectively making the event immutable for all handlers.
*/
fun <T> onlySyncImmutable() =
Event<T, EventScope.Empty>(scopeSupplier = { EventScope.Empty })
fun <E> onlySyncImmutable() = Event<E, Unit>(resultSupplier = { })
}
}

Expand All @@ -158,15 +158,15 @@ open class Event<T, S : EventScope>(val scopeSupplier: () -> S) {
* [Event.syncAsyncImmutable] functions.
*/
@ExperimentalSilkApi
open class AsyncEvent<T, S : EventScope>(val clientSide: Boolean, scopeSupplier: () -> S) : Event<T, S>(scopeSupplier) {
open class AsyncEvent<E, R>(val clientSide: Boolean, resultSupplier: () -> R) : Event<E, R>(resultSupplier) {

/**
* The internal [flow] to which events are [emitted][MutableSharedFlow.emit].
* It has no buffer, so new calls to [MutableSharedFlow.emit] will suspend if
* the listeners have not finished handling the previous event.
*/
@InternalSilkApi
open val flow = MutableSharedFlow<T>()
open val flow = MutableSharedFlow<E>()

/**
* The scope used for emitting events without blocking the current execution.
Expand All @@ -189,7 +189,7 @@ open class AsyncEvent<T, S : EventScope>(val clientSide: Boolean, scopeSupplier:
* current scope.
*/
context(CoroutineScope)
fun collectInScope(collector: FlowCollector<T>): Job = launch {
fun collectInScope(collector: FlowCollector<E>): Job = launch {
flow.collect(collector)
}

Expand All @@ -201,7 +201,7 @@ open class AsyncEvent<T, S : EventScope>(val clientSide: Boolean, scopeSupplier:
* Minecraft main thread dispatcher ([syncDispatcher]).
*/
context(CoroutineScope)
fun collectInScopeSync(collector: FlowCollector<T>): Job = launch(syncDispatcher) {
fun collectInScopeSync(collector: FlowCollector<E>): Job = launch(syncDispatcher) {
flow.collect(collector)
}

Expand All @@ -214,12 +214,12 @@ open class AsyncEvent<T, S : EventScope>(val clientSide: Boolean, scopeSupplier:
* This function never completes, read [the official collect docs][kotlinx.coroutines.flow.SharedFlow.collect]
* for more info.
*/
suspend fun collect(collector: FlowCollector<T>): Nothing {
suspend fun collect(collector: FlowCollector<E>): Nothing {
flow.collect(collector)
}

override fun invoke(instance: T, scope: S) {
super.invoke(instance, scope)
override fun invoke(instance: E, initialResult: R) {
super.invoke(instance, initialResult)
invokeScope.launch {
flow.emit(instance)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,11 @@ package net.silkmc.silk.core.event

import net.silkmc.silk.core.annotations.ExperimentalSilkApi

/**
* A marker which can be passed to event handlers to make the event
* scope mutable. Functions which mutate the scope will require
* this marker in the context.
*/
@ExperimentalSilkApi
object MutableEventScope

/**
* An event scope can provide additional information and / or
* functions to event handlers without the event itself having
* to implement these features.
* The prime example for this is cancellation, see [Cancellable].
*/
@ExperimentalSilkApi
interface EventScope {

/**
* An empty [EventScope], this is simply a no-op implementation.
*/
object Empty : EventScope

/**
* A simple [EventScope] providing the [isCancelled] property.
*/
open class Cancellable : EventScope {

/**
* If set to `true`, further execution of the action why this event
* was invoked will be cancelled.
*
* The default value of this property is `false`.
*
* See [context-receivers/contextual-delegated-properties KEEP](https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md#contextual-delegated-properties)
* for why this isn't a delegate *yet*.
*/
val isCancelled = EventScopeProperty(false)
}
}

@ExperimentalSilkApi
class EventScopeProperty<V>(private var value: V) {

/**
* Returns the current value of this property.
*/
fun get(): V {
return value
}

/**
* Mutates this property. This functions **must** be called in a
* [MutableEventScope], therefore in a normal synchronous listener.
*
* See [context-receivers/contextual-delegated-properties KEEP](https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md#contextual-delegated-properties)
* for why this isn't a delegate *yet*.
*/
context(MutableEventScope)
fun set(value: V) {
this.value = value
}
}
data class EventScope<E, R>(val event: E, val result: R)
Loading