From 1f10e521311df20b1b59ad676e28668fc803ccae Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Wed, 23 Dec 2020 23:28:23 +0100 Subject: [PATCH] Use one EventBus for all events instead of one for every player/entity/session/... --- .../src/main/kotlin/space/blokk/Blokk.kt | 3 - .../main/kotlin/space/blokk/entity/Entity.kt | 5 +- .../main/kotlin/space/blokk/event/Event.kt | 7 + .../main/kotlin/space/blokk/event/EventBus.kt | 192 ++++++++-------- .../space/blokk/event/EventBusWrapper.kt | 12 + .../kotlin/space/blokk/event/EventHandler.kt | 18 +- .../kotlin/space/blokk/event/EventTarget.kt | 27 --- .../space/blokk/event/EventTargetGroup.kt | 47 ---- .../main/kotlin/space/blokk/event/Listener.kt | 3 - .../main/kotlin/space/blokk/net/Session.kt | 25 ++- .../net/event/ClientBrandReceivedEvent.kt | 4 +- .../blokk/net/event/PacketReceivedEvent.kt | 2 +- .../space/blokk/net/event/PacketSendEvent.kt | 2 +- .../net/event/PlayerInitializationEvent.kt | 2 +- .../net/event/ServerListInfoRequestEvent.kt | 4 +- .../blokk/net/event/SessionAfterLoginEvent.kt | 2 +- .../space/blokk/net/event/SessionEvent.kt | 3 +- .../main/kotlin/space/blokk/player/Player.kt | 5 +- .../main/kotlin/space/blokk/server/Server.kt | 11 +- .../main/kotlin/space/blokk/world/World.kt | 4 +- .../space/blokk/CreateBlokkProviderMock.kt | 7 - .../kotlin/space/blokk/event/EventBusTest.kt | 211 ------------------ .../main/kotlin/space/blokk/BlokkServer.kt | 6 +- .../kotlin/space/blokk/event/BlokkEventBus.kt | 53 +++++ .../kotlin/space/blokk/net/BlokkSession.kt | 39 ++-- .../space/blokk/net/BlokkSocketServer.kt | 4 +- .../space/blokk/net/LoginAndJoinProcedure.kt | 4 +- .../space/blokk/net/PacketMessageHandler.kt | 3 +- .../play/ClientSettingsPacketHandler.kt | 2 +- .../IncomingPluginMessagePacketHandler.kt | 2 +- .../packet/status/StatusProtocolHandler.kt | 2 +- .../kotlin/space/blokk/player/BlokkPlayer.kt | 4 - .../space/blokk/testplugin/TestPlugin.kt | 21 +- .../blokk/testplugin/anvil/AnvilWorld.kt | 3 +- 34 files changed, 265 insertions(+), 474 deletions(-) create mode 100644 blokk-api/src/main/kotlin/space/blokk/event/EventBusWrapper.kt delete mode 100644 blokk-api/src/main/kotlin/space/blokk/event/EventTarget.kt delete mode 100644 blokk-api/src/main/kotlin/space/blokk/event/EventTargetGroup.kt delete mode 100644 blokk-api/src/main/kotlin/space/blokk/event/Listener.kt delete mode 100644 blokk-api/src/test/kotlin/space/blokk/CreateBlokkProviderMock.kt delete mode 100644 blokk-api/src/test/kotlin/space/blokk/event/EventBusTest.kt create mode 100644 blokk-server/src/main/kotlin/space/blokk/event/BlokkEventBus.kt diff --git a/blokk-api/src/main/kotlin/space/blokk/Blokk.kt b/blokk-api/src/main/kotlin/space/blokk/Blokk.kt index 1e9b90d..0db3600 100644 --- a/blokk-api/src/main/kotlin/space/blokk/Blokk.kt +++ b/blokk-api/src/main/kotlin/space/blokk/Blokk.kt @@ -1,8 +1,5 @@ package space.blokk -import space.blokk.event.EventTargetGroup -import space.blokk.net.Session -import space.blokk.player.Player import space.blokk.server.Server private lateinit var serverInstance: Server diff --git a/blokk-api/src/main/kotlin/space/blokk/entity/Entity.kt b/blokk-api/src/main/kotlin/space/blokk/entity/Entity.kt index 0424c86..125d836 100644 --- a/blokk-api/src/main/kotlin/space/blokk/entity/Entity.kt +++ b/blokk-api/src/main/kotlin/space/blokk/entity/Entity.kt @@ -3,16 +3,13 @@ package space.blokk.entity import space.blokk.NamespacedID import space.blokk.event.Event import space.blokk.event.EventBus -import space.blokk.event.EventTarget import space.blokk.logging.Logger import java.util.* import kotlin.reflect.KClass -abstract class Entity internal constructor() : EventTarget { +abstract class Entity internal constructor() { val uuid: UUID = UUID.randomUUID() - override val eventBus: EventBus = EventBus(Event::class, Logger("Entity/$uuid")) - companion object { internal inline fun type( numericID: Int, diff --git a/blokk-api/src/main/kotlin/space/blokk/event/Event.kt b/blokk-api/src/main/kotlin/space/blokk/event/Event.kt index 3b85b87..8431be5 100644 --- a/blokk-api/src/main/kotlin/space/blokk/event/Event.kt +++ b/blokk-api/src/main/kotlin/space/blokk/event/Event.kt @@ -1,3 +1,10 @@ package space.blokk.event +import space.blokk.Blokk +import kotlin.reflect.KClass + abstract class Event + +abstract class TargetedEvent: Event() { + abstract val target: T +} diff --git a/blokk-api/src/main/kotlin/space/blokk/event/EventBus.kt b/blokk-api/src/main/kotlin/space/blokk/event/EventBus.kt index d43b204..4f55a7d 100644 --- a/blokk-api/src/main/kotlin/space/blokk/event/EventBus.kt +++ b/blokk-api/src/main/kotlin/space/blokk/event/EventBus.kt @@ -1,104 +1,116 @@ package space.blokk.event -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.async -import space.blokk.Blokk -import space.blokk.logging.Logger +import kotlinx.coroutines.suspendCancellableCoroutine import space.blokk.plugin.Plugin -import space.blokk.util.pluralizeWithCount -import kotlin.coroutines.CoroutineContext +import java.util.concurrent.ConcurrentSkipListSet +import kotlin.coroutines.resume import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.full.* -import kotlin.system.measureTimeMillis -// TODO: Only create one event bus for everything and add helper method instead -class EventBus( - private val eventClass: KClass, - private val logger: Logger, - coroutineContext: CoroutineContext = Blokk.coroutineContext -) { - private val scope = CoroutineScope(coroutineContext) +abstract class EventEmitter { + protected val handlers = ConcurrentSkipListSet>() /** - * All event handlers, sorted by their priority and the order in which they were registered. + * Registers a new event handler. */ - private val handlers = mutableListOf>() + open fun on( + eventType: KClass, + priority: EventPriority = EventPriority.NORMAL, + fn: suspend (event: T) -> Unit + ): EventHandler { + val handler = EventHandler(this, eventType, Plugin.getCalling(), priority, fn) + handlers.add(handler) + return handler + } /** - * Invokes all previously registered event handlers sorted by their priority - * and the order in which they were registered. + * Registers a new event handler. + * + * The handler function receives a function to remove the event handler as second parameter. + */ + fun onRemovable( + eventType: KClass, + priority: EventPriority = EventPriority.NORMAL, + fn: suspend (event: T, remove: () -> Unit) -> Unit + ): EventHandler { + lateinit var handler: EventHandler + + handler = on(eventType, priority) { + fn(it, handler::remove) + } + + return handler + } + + /** + * Registers a new event handler which is automatically removed after it was invoked. + */ + fun once( + eventType: KClass, + priority: EventPriority = EventPriority.NORMAL, + fn: suspend (event: T) -> Unit + ): EventHandler = onRemovable(eventType, priority) { event, remove -> remove(); fn(event) } + + /** + * Suspends until an event of type [T] is emitted and returns it. + */ + suspend fun waitFor(eventType: KClass, priority: EventPriority = EventPriority.NORMAL): T = + suspendCancellableCoroutine { c -> + val handler = EventHandler(this, eventType, Plugin.getCalling(), priority) { + c.resume(it) + } + + handlers.add(handler) + + c.invokeOnCancellation { + handlers.remove(handler) + } + } + + /** + * @return Whether the event handler was not already removed. + */ + fun remove(eventHandler: EventHandler<*>): Boolean = handlers.remove(eventHandler) + + /** + * Registers a new event handler. + */ + inline fun on( + priority: EventPriority = EventPriority.NORMAL, + noinline fn: suspend (event: T) -> Unit + ): EventHandler = on(T::class, priority, fn) + + /** + * Registers a new event handler. + * + * The handler function receives a function to remove the event handler as second parameter. + */ + inline fun onRemovable( + priority: EventPriority = EventPriority.NORMAL, + noinline fn: suspend (event: T, remove: () -> Unit) -> Unit + ): EventHandler = onRemovable(T::class, priority, fn) + + /** + * Registers a new event handler which is automatically removed after it was invoked. + */ + inline fun once( + priority: EventPriority = EventPriority.NORMAL, + noinline fn: suspend (event: T) -> Unit + ): EventHandler = once(T::class, priority, fn) + + /** + * Suspends until an event of type [T] is emitted and returns it. + */ + suspend inline fun waitFor(priority: EventPriority = EventPriority.NORMAL): T = + waitFor(T::class, priority) +} + +abstract class EventBus: EventEmitter() { + /** + * Invokes all previously registered event handlers sorted by their priority. + * + * The coroutine context is inherited. * * @return [event] */ - suspend fun emit(event: T): T { - if (Blokk.developmentMode) { - val handlers = handlers.filter { it.eventType.isInstance(event) } - val time = measureTimeMillis { handlers.forEach { it.fn.callSuspend(it.listener, event) } } - - logger trace "Emitted ${event::class.java.simpleName} to " + - "${pluralizeWithCount("handler", handlers.size)}, took ${time}ms" - } else handlers.filter { it.eventType.isInstance(event) }.forEach { it.fn.callSuspend(it.listener, event) } - - return event - } - - fun emitAsync(event: T) = scope.async { emit(event) } - - /** - * Registers all [event handlers][EventHandler] in [listener] to be invoked when their corresponding event is emitted. - * - * @return [listener] - * @throws InvalidEventHandlerException if one of the event handlers does not meet the requirements - */ - fun register(listener: T): T { - val handlersOfListener = listener::class.functions - .mapNotNull { method -> method.findAnnotation()?.let { method to it } } - .toMap() - - for ((method, data) in handlersOfListener) { - if (method.valueParameters.size != 1) - throw InvalidEventHandlerException("${method.name} must have exactly one parameter") - - @Suppress("UNCHECKED_CAST") - val klass = method.parameters[1].type.classifier as KClass - - if (!eventClass.isSuperclassOf(klass)) - throw InvalidEventHandlerException( - "${method.name}'s first parameter type is incompatible with the " + - "one required by the EventBus" - ) - - @Suppress("UNCHECKED_CAST") - val handler = Handler( - klass, - listener, - method as KFunction, - Plugin.getCalling(), - data.priority - ) - - val insertIndex = handlers.indexOfLast { it.priority.ordinal <= handler.priority.ordinal } + 1 - handlers.add(insertIndex, handler) - } - - return listener - } - - /** - * Unregisters all [event handlers][EventHandler] in [listener]. - */ - fun unregister(listener: Listener) { - handlers.removeIf { it.listener === listener } - } - - class InvalidEventHandlerException internal constructor(message: String) : Exception(message) - - private data class Handler( - val eventType: KClass, - val listener: Listener, - val fn: KFunction, - val plugin: Plugin?, - val priority: EventPriority - ) + abstract suspend fun emit(event: T): T } diff --git a/blokk-api/src/main/kotlin/space/blokk/event/EventBusWrapper.kt b/blokk-api/src/main/kotlin/space/blokk/event/EventBusWrapper.kt new file mode 100644 index 0000000..6d606a9 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/event/EventBusWrapper.kt @@ -0,0 +1,12 @@ +package space.blokk.event + +import space.blokk.Blokk +import kotlin.reflect.KClass + +class EventBusWrapper(private val target: Any): EventEmitter>() { + override fun > on( + eventType: KClass, + priority: EventPriority, + fn: suspend (event: T) -> Unit + ): EventHandler = Blokk.eventBus.on(eventType, priority) { event -> if (event.target == target) fn(event) } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/event/EventHandler.kt b/blokk-api/src/main/kotlin/space/blokk/event/EventHandler.kt index 2ac722f..d0cd596 100644 --- a/blokk-api/src/main/kotlin/space/blokk/event/EventHandler.kt +++ b/blokk-api/src/main/kotlin/space/blokk/event/EventHandler.kt @@ -1,3 +1,19 @@ package space.blokk.event -annotation class EventHandler(val priority: EventPriority = EventPriority.NORMAL) +import space.blokk.plugin.Plugin +import kotlin.reflect.KClass + +data class EventHandler internal constructor( + private val eventEmitter: EventEmitter<*>, + val eventType: KClass, + val plugin: Plugin?, + val priority: EventPriority, + val fn: suspend (event: T) -> Unit +): Comparable> { + /** + * Unregisters this event handler. + */ + fun remove(): Boolean = eventEmitter.remove(this) + + override fun compareTo(other: EventHandler<*>): Int = priority.compareTo(other.priority) +} diff --git a/blokk-api/src/main/kotlin/space/blokk/event/EventTarget.kt b/blokk-api/src/main/kotlin/space/blokk/event/EventTarget.kt deleted file mode 100644 index a7a0f84..0000000 --- a/blokk-api/src/main/kotlin/space/blokk/event/EventTarget.kt +++ /dev/null @@ -1,27 +0,0 @@ -package space.blokk.event - -import kotlinx.coroutines.Deferred - -interface EventTarget { - val eventBus: EventBus - - /** - * Shorthand for [`eventBus.emit(event)`][EventBus.emit]. - */ - suspend fun emit(event: E): E = eventBus.emit(event) - - /** - * Shorthand for [`eventBus.emitAsync(event)`][EventBus.emitAsync]. - */ - fun emitAsync(event: E): Deferred = eventBus.emitAsync(event) - - /** - * Shorthand for [`eventBus.register(listener)`][EventBus.register]. - */ - fun registerListener(listener: Listener) = eventBus.register(listener) - - /** - * Shorthand for [`eventBus.unregister(listener)`][EventBus.unregister]. - */ - fun unregisterListener(listener: Listener) = eventBus.unregister(listener) -} diff --git a/blokk-api/src/main/kotlin/space/blokk/event/EventTargetGroup.kt b/blokk-api/src/main/kotlin/space/blokk/event/EventTargetGroup.kt deleted file mode 100644 index 73ea44a..0000000 --- a/blokk-api/src/main/kotlin/space/blokk/event/EventTargetGroup.kt +++ /dev/null @@ -1,47 +0,0 @@ -package space.blokk.event - -import java.util.concurrent.CopyOnWriteArraySet - -abstract class EventTargetGroup>(threadSafe: Boolean = false) : Iterable { - private val targets: MutableSet = if (threadSafe) CopyOnWriteArraySet() else HashSet() - private val listeners: MutableSet = if (threadSafe) CopyOnWriteArraySet() else HashSet() - - /** - * Registers a listener for all elements in this group. - * - * You should never reuse [listener] for other groups or directly on event targets - * because this may lead to strange behaviour. - */ - fun registerListener(listener: T): T { - listeners.add(listener) - targets.forEach { it.eventBus.register(listener) } - return listener - } - - /** - * Unregisters a listener for all elements in this group. - * - * You should only unregister listeners which you previously registered using [registerListener]. - */ - fun unregisterListener(listener: Listener) { - targets.forEach { it.eventBus.unregister(listener) } - listeners.remove(listener) - } - - override fun iterator(): Iterator = targets.iterator() - - protected fun addTarget(target: T) { - targets.add(target) - listeners.forEach { target.eventBus.register(it) } - } - - protected fun removeTarget(target: T) { - targets.remove(target) - listeners.forEach { target.eventBus.unregister(it) } - } - - class Mutable>(threadSafe: Boolean = false) : EventTargetGroup(threadSafe) { - fun add(target: T) = addTarget(target) - fun remove(target: T) = removeTarget(target) - } -} diff --git a/blokk-api/src/main/kotlin/space/blokk/event/Listener.kt b/blokk-api/src/main/kotlin/space/blokk/event/Listener.kt deleted file mode 100644 index 39974c3..0000000 --- a/blokk-api/src/main/kotlin/space/blokk/event/Listener.kt +++ /dev/null @@ -1,3 +0,0 @@ -package space.blokk.event - -interface Listener diff --git a/blokk-api/src/main/kotlin/space/blokk/net/Session.kt b/blokk-api/src/main/kotlin/space/blokk/net/Session.kt index 48873fd..87571aa 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/Session.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/Session.kt @@ -3,7 +3,7 @@ package space.blokk.net import io.netty.buffer.ByteBuf import kotlinx.coroutines.CoroutineScope import space.blokk.chat.TextComponent -import space.blokk.event.EventTarget +import space.blokk.event.EventBusWrapper import space.blokk.net.event.SessionEvent import space.blokk.net.packet.OutgoingPacket import space.blokk.net.packet.Protocol @@ -14,29 +14,32 @@ import java.net.InetAddress import java.util.* import kotlin.coroutines.CoroutineContext -interface Session : EventTarget, CoroutineScope { +abstract class Session { + @Suppress("LeakingThis") + val events = EventBusWrapper(this) + /** * The IP address of this session */ - val address: InetAddress + abstract val address: InetAddress /** * Unconfined [CoroutineContext] which is cancelled when the session is disconnected. */ - override val coroutineContext: CoroutineContext + abstract val coroutineContext: CoroutineContext /** * The brand name the client optionally sent during the login procedure. * [ClientBrandReceivedEvent][space.blokk.net.event.ClientBrandReceivedEvent] is emitted when this value changes. */ - val brand: String? + abstract val brand: String? /** * The round trip time (in milliseconds) of the KeepAlive packet which is sent to the client every few seconds. * * It is -1 until the first IncomingKeepAlive packet is received. */ - val ping: Int + abstract val ping: Int /** * The player corresponding to this session. @@ -46,7 +49,7 @@ interface Session : EventTarget, CoroutineScope { /** * The current state of this session. */ - val state: State + abstract val state: State sealed class State { object WaitingForHandshake : State() @@ -90,7 +93,7 @@ interface Session : EventTarget, CoroutineScope { /** * The protocol this session is currently using. If this is null, the client is not connected anymore. */ - val currentProtocol: Protocol? + abstract val currentProtocol: Protocol? /** * Closes the connection with the client. @@ -99,7 +102,7 @@ interface Session : EventTarget, CoroutineScope { * @param loggableReason A short, loggable representation of [reason]. Not shown to the player, except * in development mode. */ - suspend fun disconnect( + abstract suspend fun disconnect( reason: TextComponent = TextComponent of "Disconnected.", loggableReason: String = reason.text ) @@ -107,10 +110,10 @@ interface Session : EventTarget, CoroutineScope { /** * Sends a packet. */ - suspend fun send(packet: OutgoingPacket) + abstract suspend fun send(packet: OutgoingPacket) /** * Sends a plugin message packet. */ - suspend fun sendPluginMessage(channel: String, data: ByteBuf) + abstract suspend fun sendPluginMessage(channel: String, data: ByteBuf) } diff --git a/blokk-api/src/main/kotlin/space/blokk/net/event/ClientBrandReceivedEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/event/ClientBrandReceivedEvent.kt index d6059d1..78bcc74 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/event/ClientBrandReceivedEvent.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/event/ClientBrandReceivedEvent.kt @@ -7,8 +7,8 @@ import space.blokk.net.Session * Emitted when the client sends his brand during the login process. */ class ClientBrandReceivedEvent( - session: Session, + override val target: Session, var brand: String -) : SessionEvent(session), Cancellable { +) : SessionEvent(), Cancellable { override var cancelled = false } diff --git a/blokk-api/src/main/kotlin/space/blokk/net/event/PacketReceivedEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/event/PacketReceivedEvent.kt index 9565a79..181828d 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/event/PacketReceivedEvent.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/event/PacketReceivedEvent.kt @@ -7,6 +7,6 @@ import space.blokk.net.packet.IncomingPacket /** * Emitted when a packet is received. */ -class PacketReceivedEvent(session: Session, var packet: T) : SessionEvent(session), Cancellable { +class PacketReceivedEvent(override val target: Session, var packet: T) : SessionEvent(), Cancellable { override var cancelled = false } diff --git a/blokk-api/src/main/kotlin/space/blokk/net/event/PacketSendEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/event/PacketSendEvent.kt index 084a18d..8d5214e 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/event/PacketSendEvent.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/event/PacketSendEvent.kt @@ -7,6 +7,6 @@ import space.blokk.net.packet.OutgoingPacket /** * Emitted when a packet is going to be sent. */ -class PacketSendEvent(session: Session, var packet: OutgoingPacket) : SessionEvent(session), Cancellable { +class PacketSendEvent(override val target: Session, var packet: OutgoingPacket) : SessionEvent(), Cancellable { override var cancelled = false } diff --git a/blokk-api/src/main/kotlin/space/blokk/net/event/PlayerInitializationEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/event/PlayerInitializationEvent.kt index 5f51b4e..91bb786 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/event/PlayerInitializationEvent.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/event/PlayerInitializationEvent.kt @@ -10,7 +10,7 @@ import space.blokk.world.VoxelLocation * * If the event is cancelled, the session is disconnected. */ -class PlayerInitializationEvent(session: Session, val settings: Player.Settings) : SessionEvent(session), Cancellable { +class PlayerInitializationEvent(override val target: Session, val settings: Player.Settings) : SessionEvent(), Cancellable { override var cancelled = false var compassTarget: VoxelLocation = VoxelLocation(0, 0, 0) } diff --git a/blokk-api/src/main/kotlin/space/blokk/net/event/ServerListInfoRequestEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/event/ServerListInfoRequestEvent.kt index bfeeb5a..03d26ee 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/event/ServerListInfoRequestEvent.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/event/ServerListInfoRequestEvent.kt @@ -10,8 +10,8 @@ import space.blokk.net.Session * If the event is cancelled or [response] is null after all handlers ran, the session is disconnected. */ class ServerListInfoRequestEvent( - session: Session, + override val target: Session, var response: ServerListInfo? = null -) : SessionEvent(session), Cancellable { +) : SessionEvent(), Cancellable { override var cancelled = false } diff --git a/blokk-api/src/main/kotlin/space/blokk/net/event/SessionAfterLoginEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/event/SessionAfterLoginEvent.kt index d686f7c..26d4f78 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/event/SessionAfterLoginEvent.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/event/SessionAfterLoginEvent.kt @@ -11,7 +11,7 @@ import space.blokk.world.WorldAndLocationWithRotation * * If the event is cancelled, the session is disconnected. */ -class SessionAfterLoginEvent(session: Session) : SessionEvent(session), Cancellable { +class SessionAfterLoginEvent(override val target: Session) : SessionEvent(), Cancellable { override var cancelled = false /** diff --git a/blokk-api/src/main/kotlin/space/blokk/net/event/SessionEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/event/SessionEvent.kt index d39f0c8..df117f2 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/event/SessionEvent.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/event/SessionEvent.kt @@ -1,6 +1,7 @@ package space.blokk.net.event import space.blokk.event.Event +import space.blokk.event.TargetedEvent import space.blokk.net.Session -abstract class SessionEvent(val session: Session) : Event() +abstract class SessionEvent : TargetedEvent() diff --git a/blokk-api/src/main/kotlin/space/blokk/player/Player.kt b/blokk-api/src/main/kotlin/space/blokk/player/Player.kt index cac3e46..fbbc22c 100644 --- a/blokk-api/src/main/kotlin/space/blokk/player/Player.kt +++ b/blokk-api/src/main/kotlin/space/blokk/player/Player.kt @@ -2,7 +2,6 @@ package space.blokk.player import kotlinx.coroutines.CoroutineScope import space.blokk.chat.TextComponent -import space.blokk.event.EventTarget import space.blokk.net.Session import space.blokk.player.event.PlayerEvent import space.blokk.world.Chunk @@ -15,8 +14,8 @@ import kotlin.coroutines.CoroutineContext /** * A **real** player. */ -interface Player : EventTarget, CoroutineScope { - override val coroutineContext: CoroutineContext get() = session.coroutineContext +interface Player { + val coroutineContext: CoroutineContext get() = session.coroutineContext /** * The session of this player. diff --git a/blokk-api/src/main/kotlin/space/blokk/server/Server.kt b/blokk-api/src/main/kotlin/space/blokk/server/Server.kt index 34586f3..45ab625 100644 --- a/blokk-api/src/main/kotlin/space/blokk/server/Server.kt +++ b/blokk-api/src/main/kotlin/space/blokk/server/Server.kt @@ -4,22 +4,19 @@ import space.blokk.Registry import space.blokk.Scheduler import space.blokk.command.Command import space.blokk.event.EventBus -import space.blokk.event.EventTarget -import space.blokk.event.EventTargetGroup import space.blokk.logging.Logger import space.blokk.logging.LoggingOutputProvider import space.blokk.net.Session import space.blokk.player.Player import space.blokk.plugin.PluginManager import space.blokk.recipe.Recipe -import space.blokk.server.event.ServerEvent import space.blokk.world.BiomeRegistry import space.blokk.world.Dimension import java.io.File import kotlin.coroutines.CoroutineContext -interface Server : EventTarget { - override val eventBus: EventBus +interface Server { + val eventBus: EventBus /** * [CoroutineContext] confined to the server thread. @@ -31,12 +28,12 @@ interface Server : EventTarget { /** * All sessions connected to the server. */ - val sessions: EventTargetGroup + val sessions: Collection /** * All players connected to the server. */ - val players: EventTargetGroup + val players: Collection val pluginManager: PluginManager val serverDirectory: File diff --git a/blokk-api/src/main/kotlin/space/blokk/world/World.kt b/blokk-api/src/main/kotlin/space/blokk/world/World.kt index 5a23acc..51d2e43 100644 --- a/blokk-api/src/main/kotlin/space/blokk/world/World.kt +++ b/blokk-api/src/main/kotlin/space/blokk/world/World.kt @@ -1,9 +1,7 @@ package space.blokk.world import kotlinx.coroutines.* -import space.blokk.CoordinatePartOrder import space.blokk.entity.Entity -import space.blokk.event.EventTargetGroup import space.blokk.util.newSingleThreadDispatcher import java.util.* import kotlin.coroutines.CoroutineContext @@ -39,7 +37,7 @@ abstract class World(val uuid: UUID) { /** * All entities in this world. Use [spawnEntity] for spawning new ones. */ - abstract val entities: EventTargetGroup.Mutable + abstract val entities: Collection abstract fun getChunk(key: Chunk.Key): Chunk diff --git a/blokk-api/src/test/kotlin/space/blokk/CreateBlokkProviderMock.kt b/blokk-api/src/test/kotlin/space/blokk/CreateBlokkProviderMock.kt deleted file mode 100644 index 2a0b8c7..0000000 --- a/blokk-api/src/test/kotlin/space/blokk/CreateBlokkProviderMock.kt +++ /dev/null @@ -1,7 +0,0 @@ -package space.blokk - -import space.blokk.server.Server - -fun mockServerInstance() { - // TODO -} diff --git a/blokk-api/src/test/kotlin/space/blokk/event/EventBusTest.kt b/blokk-api/src/test/kotlin/space/blokk/event/EventBusTest.kt deleted file mode 100644 index aa5cee4..0000000 --- a/blokk-api/src/test/kotlin/space/blokk/event/EventBusTest.kt +++ /dev/null @@ -1,211 +0,0 @@ -package space.blokk.event - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Test -import space.blokk.logging.Logger -import space.blokk.mockServerInstance -import strikt.api.expectThat -import strikt.api.expectThrows -import strikt.assertions.* -import kotlin.system.measureTimeMillis - -abstract class TestEvent : Event() -private class FirstEvent : TestEvent() -private class SecondEvent : TestEvent() - -class EventBusTest { - init { - mockServerInstance() - } - - private val eventBus = EventBus(TestEvent::class, Logger("logger"), Dispatchers.Default) - - @Test - fun `calls the handler exactly 1 time when the event is emitted 1 time`() { - var calledCount = 0 - eventBus.register(object : Listener { - @EventHandler - fun onFirstEvent(event: FirstEvent) { - calledCount++ - } - }) - - runBlocking { eventBus.emit(FirstEvent()) } - expectThat(calledCount).isEqualTo(1) - } - - @Test - fun `calls the handler exactly 3 times when the event is emitted 3 times`() { - var calledCount = 0 - eventBus.register(object : Listener { - @EventHandler - fun onFirstEvent(event: FirstEvent) { - calledCount++ - } - }) - - - runBlocking { - eventBus.emit(FirstEvent()) - eventBus.emit(FirstEvent()) - eventBus.emit(FirstEvent()) - } - - expectThat(calledCount).isEqualTo(3) - } - - @Test - fun `calls no handlers if no event is emitted`() { - var onFirstEventCalled = false - var onSecondEventCalled = false - eventBus.register(object : Listener { - @EventHandler - fun onFirstEvent(event: FirstEvent) { - onFirstEventCalled = true - } - - @EventHandler - fun onSecondEvent(event: SecondEvent) { - onSecondEventCalled = true - } - }) - - // No emit - - expectThat(onFirstEventCalled).isFalse() - expectThat(onSecondEventCalled).isFalse() - } - - @Test - fun `calls handlers for supertypes of the emitted event`() { - var onTestEventCalled = false - var onFirstEventCalled = false - eventBus.register(object : Listener { - @EventHandler - fun onTestEvent(event: TestEvent) { - onTestEventCalled = true - } - - @EventHandler - fun onFirstEvent(event: FirstEvent) { - onFirstEventCalled = true - } - }) - - runBlocking { eventBus.emit(FirstEvent()) } - - expectThat(onTestEventCalled).isTrue() - expectThat(onFirstEventCalled).isTrue() - } - - @Test - fun `stops calling handlers after they were unregistered`() { - var called = false - val listener = eventBus.register(object : Listener { - @EventHandler - fun onFirstEvent(event: FirstEvent) { - called = true - } - }) - - eventBus.unregister(listener) - - runBlocking { eventBus.emit(FirstEvent()) } - expectThat(called).isFalse() - } - - @Test - fun `throws an error if an event handler function has an invalid signature`() { - expectThrows { - eventBus.register(object : Listener { - @EventHandler - fun onEvent(coolParam: String) { - } - }) - } - - expectThrows { - eventBus.register(object : Listener { - @EventHandler - fun onFirstEvent(coolParam: String, event: FirstEvent) { - } - }) - } - - expectThrows { - eventBus.register(object : Listener { - @EventHandler - fun onFirstEvent(event: FirstEvent, coolParam: String) { - } - }) - } - } - - @Test - fun `calls handlers in the right order, sorted by priority`() { - val order = mutableListOf() - - eventBus.register(object : Listener { - @EventHandler(EventPriority.HIGHEST) - fun onFirstEventHighest(event: FirstEvent) { - order.add(EventPriority.HIGHEST) - } - - @EventHandler(EventPriority.LOW) - fun onFirstEventLow(event: FirstEvent) { - order.add(EventPriority.LOW) - } - - @EventHandler(EventPriority.HIGHER) - fun onFirstEventHigher(event: FirstEvent) { - order.add(EventPriority.HIGHER) - } - - @EventHandler(EventPriority.LOWEST) - fun onFirstEventLowest(event: FirstEvent) { - order.add(EventPriority.LOWEST) - } - - @EventHandler(EventPriority.MONITOR) - fun onFirstEventMonitor(event: FirstEvent) { - order.add(EventPriority.MONITOR) - } - - @EventHandler(EventPriority.LOWER) - fun onFirstEventLower(event: FirstEvent) { - order.add(EventPriority.LOWER) - } - - @EventHandler(EventPriority.NORMAL) - fun onFirstEventNormal(event: FirstEvent) { - order.add(EventPriority.NORMAL) - } - - @EventHandler(EventPriority.HIGH) - fun onFirstEventHigh(event: FirstEvent) { - order.add(EventPriority.HIGH) - } - }) - - runBlocking { eventBus.emit(FirstEvent()) } - expectThat(order).isSorted(Comparator.comparing { it.ordinal }) - } - - @Test - fun `suspends until all handlers ran`() { - val delay = 2000L - eventBus.register(object : Listener { - @EventHandler - suspend fun onFirstEvent(event: FirstEvent) { - // Simulate long running operation - delay(2000) - } - }) - - val duration = measureTimeMillis { runBlocking { eventBus.emit(FirstEvent()) } } - expectThat(duration).isGreaterThanOrEqualTo(delay) - } -} diff --git a/blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt b/blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt index 787b0b8..70b7fbf 100644 --- a/blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt +++ b/blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt @@ -9,8 +9,8 @@ import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.runBlocking import space.blokk.command.Command import space.blokk.config.BlokkConfig +import space.blokk.event.BlokkEventBus import space.blokk.event.EventBus -import space.blokk.event.EventTargetGroup import space.blokk.logging.BlokkLoggingOutputProvider import space.blokk.logging.Logger import space.blokk.net.BlokkSocketServer @@ -44,10 +44,10 @@ class BlokkServer internal constructor() : Server { override val coroutineContext: CoroutineContext = CoroutineName("Server") + Executors.newSingleThreadExecutor().asCoroutineDispatcher() + SupervisorJob() - override val eventBus = EventBus(ServerEvent::class, logger, coroutineContext) + override val eventBus = BlokkEventBus(this) override val sessions by socketServer::sessions - override val players = EventTargetGroup.Mutable(true) + override val players get() = sessions.mapNotNull { it.player } override val pluginManager = BlokkPluginManager(this) override val serverDirectory: File = diff --git a/blokk-server/src/main/kotlin/space/blokk/event/BlokkEventBus.kt b/blokk-server/src/main/kotlin/space/blokk/event/BlokkEventBus.kt new file mode 100644 index 0000000..964ec94 --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/event/BlokkEventBus.kt @@ -0,0 +1,53 @@ +package space.blokk.event + +import kotlinx.coroutines.suspendCancellableCoroutine +import space.blokk.BlokkServer +import space.blokk.logging.Logger +import space.blokk.plugin.Plugin +import space.blokk.util.pluralizeWithCount +import java.util.concurrent.ConcurrentSkipListSet +import kotlin.coroutines.resume +import kotlin.reflect.KClass +import kotlin.system.measureTimeMillis + +class BlokkEventBus(private val server: BlokkServer) : EventBus() { + private val logger = Logger("EventBus", false) + + /** + * Invokes all previously registered event handlers sorted by their priority. + * + * The coroutine context is inherited. + * + * @return [event] + */ + override suspend fun emit(event: T): T { + if (server.developmentMode) { + var count = 0 + val time = measureTimeMillis { + for (handler in handlers) { + if (handler.eventType.isInstance(event)) { + @Suppress("UNCHECKED_CAST") + handler as EventHandler + + count++ + handler.fn(event) + } + } + } + + logger trace "Emitted ${event::class.java.simpleName} to " + + "${pluralizeWithCount("handler", count)}, took ${time}ms" + } else { + for (handler in handlers) { + if (handler.eventType.isInstance(event)) { + @Suppress("UNCHECKED_CAST") + handler as EventHandler + + handler.fn(event) + } + } + } + + return event + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt index 17d1199..acb7595 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt @@ -10,13 +10,17 @@ import space.blokk.chat.TextComponent import space.blokk.event.* import space.blokk.logging.Logger import space.blokk.net.Session.State +import space.blokk.net.event.ClientBrandReceivedEvent import space.blokk.net.event.PacketReceivedEvent import space.blokk.net.event.PacketSendEvent import space.blokk.net.event.SessionEvent +import space.blokk.net.packet.IncomingPacket import space.blokk.net.packet.OutgoingPacket +import space.blokk.net.packet.Packet import space.blokk.net.packet.Protocol import space.blokk.net.packet.handshaking.HandshakingProtocol import space.blokk.net.packet.login.LoginProtocol +import space.blokk.net.packet.play.IncomingKeepAlivePacket import space.blokk.net.packet.play.OutgoingKeepAlivePacket import space.blokk.net.packet.play.OutgoingPluginMessagePacket import space.blokk.net.packet.play.PlayProtocol @@ -29,7 +33,7 @@ import javax.crypto.SecretKey import kotlin.coroutines.CoroutineContext import kotlin.properties.Delegates -class BlokkSession(private val channel: Channel, val server: BlokkServer) : Session { +class BlokkSession(private val channel: Channel, val server: BlokkServer) : Session() { override val address: InetAddress = (channel.remoteAddress() as InetSocketAddress).address private val identifier = "BlokkSession(${address.hostAddress})" @@ -38,6 +42,8 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess override val coroutineContext: CoroutineContext = CoroutineName(identifier) + Dispatchers.Unconfined + SupervisorJob() + val scope = CoroutineScope(coroutineContext) + override var brand: String? = null override var ping: Int = -1 @@ -61,8 +67,6 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess is State.Disconnected -> null } - override val eventBus = EventBus(SessionEvent::class, logger, coroutineContext) - private var disconnectReason: String? = null override suspend fun disconnect(reason: TextComponent, loggableReason: String) { @@ -95,7 +99,7 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess fun scheduleKeepAlivePacket() { val timeSinceLastPacket = (System.currentTimeMillis() - lastKeepAlivePacketTimestamp).toInt() - launch { + scope.launch { delay(KEEP_ALIVE_PACKET_INTERVAL.toLong() - timeSinceLastPacket) lastKeepAlivePacketTimestamp = System.currentTimeMillis() @@ -114,18 +118,9 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess val joinProcedure = LoginAndJoinProcedure(this) - init { - eventBus.register(object : Listener { - @EventHandler(priority = EventPriority.INTERNAL) - suspend fun onSessionPacketReceived(event: PacketReceivedEvent<*>) { - SessionPacketReceivedEventHandler.handle(event.session as BlokkSession, event.packet) - } - }) - } - - fun onConnect() = launch { + fun onConnect() = scope.launch { logger trace "Connected" - if (server.eventBus.emitAsync(SessionInitializedEvent(this@BlokkSession)).isCancelled) channel.close() + if (server.eventBus.emit(SessionInitializedEvent(this@BlokkSession)).cancelled) channel.close() else server.sessions.add(this@BlokkSession) } @@ -139,12 +134,22 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess else message } - cancel(DisconnectedCancellationException(reason)) + coroutineContext.cancel(DisconnectedCancellationException(reason)) state = State.Disconnected(reason) server.sessions.remove(this) } + fun onPacketReceived(packet: IncomingPacket) { + logger.trace { "Packet received: $packet" } + + scope.launch { + server.eventBus.emit(PacketReceivedEvent(this@BlokkSession, packet)).ifNotCancelled { + SessionPacketReceivedEventHandler.handle(this@BlokkSession, packet) + } + } + } + fun failBecauseOfClient(message: String) { val messageGetter = { "A connection error caused by the client occurred: $message" } if (server.config.silentNonServerErrors) logger debug messageGetter else logger error messageGetter @@ -169,7 +174,7 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess if (state is State.Disconnected) throw IllegalStateException("The session is not active anymore") logger.trace { "Sending packet: $packet" } - eventBus.emit(PacketSendEvent(this@BlokkSession, packet)).ifNotCancelled { + server.eventBus.emit(PacketSendEvent(this@BlokkSession, packet)).ifNotCancelled { try { channel.writeAndFlush(OutgoingPacketMessage(this@BlokkSession, it.packet)).awaitSuspending() } catch (t: Throwable) { diff --git a/blokk-server/src/main/kotlin/space/blokk/net/BlokkSocketServer.kt b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSocketServer.kt index 267fc49..d8d0801 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/BlokkSocketServer.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSocketServer.kt @@ -11,8 +11,8 @@ import io.netty.channel.kqueue.KQueueServerSocketChannel import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.socket.nio.NioServerSocketChannel import space.blokk.BlokkServer -import space.blokk.event.EventTargetGroup import space.blokk.logging.Logger +import java.util.concurrent.CopyOnWriteArraySet class BlokkSocketServer(val server: BlokkServer) { private val logger = Logger("BlokkSocketServer") @@ -32,7 +32,7 @@ class BlokkSocketServer(val server: BlokkServer) { .childOption(ChannelOption.TCP_NODELAY, true) .childHandler(BlokkChannelInitializer(this)) - internal val sessions = EventTargetGroup.Mutable(true) + internal val sessions: MutableSet = CopyOnWriteArraySet() fun bind() { bootstrap.bind(server.config.host, server.config.port) diff --git a/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt b/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt index 5bad5aa..98b0e52 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt @@ -80,7 +80,7 @@ class LoginAndJoinProcedure(val session: BlokkSession) { private suspend fun afterLogin() { val state: Session.State.LoginSucceeded = session.state.getOrFail() - val event = session.emit(SessionAfterLoginEvent(session)) + val event = session.server.eventBus.emit(SessionAfterLoginEvent(session)) val initialWorldAndLocation = event.initialWorldAndLocation when { @@ -145,7 +145,7 @@ class LoginAndJoinProcedure(val session: BlokkSession) { val state: Session.State.WaitingForClientSettings = session.state.getOrFail() val settings = packet.asPlayerSettings() - val event = session.emit(PlayerInitializationEvent(session, settings)) + val event = session.server.eventBus.emit(PlayerInitializationEvent(session, settings)) event.ifCancelled { session.disconnect(loggableReason = "PlayerInitializationEvent was cancelled") diff --git a/blokk-server/src/main/kotlin/space/blokk/net/PacketMessageHandler.kt b/blokk-server/src/main/kotlin/space/blokk/net/PacketMessageHandler.kt index c84ddbc..c971239 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/PacketMessageHandler.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/PacketMessageHandler.kt @@ -8,7 +8,6 @@ import space.blokk.net.event.PacketReceivedEvent class PacketMessageHandler(private val session: BlokkSession) : SimpleChannelInboundHandler>() { override fun channelRead0(ctx: ChannelHandlerContext, msg: IncomingPacketMessage<*>) { - session.logger.trace { "Packet received: ${msg.packet}" } - session.launch { session.eventBus.emit(PacketReceivedEvent(session, msg.packet)) } + session.onPacketReceived(msg.packet) } } diff --git a/blokk-server/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacketHandler.kt b/blokk-server/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacketHandler.kt index ba73646..6c753ab 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacketHandler.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacketHandler.kt @@ -17,7 +17,7 @@ object ClientSettingsPacketHandler : PacketReceivedEventHandler { session, _ -> - val event = session.eventBus.emit(ServerListInfoRequestEvent(session)) + val event = session.server.eventBus.emit(ServerListInfoRequestEvent(session)) val response = event.response when { diff --git a/blokk-server/src/main/kotlin/space/blokk/player/BlokkPlayer.kt b/blokk-server/src/main/kotlin/space/blokk/player/BlokkPlayer.kt index cc9fb9e..4a912be 100644 --- a/blokk-server/src/main/kotlin/space/blokk/player/BlokkPlayer.kt +++ b/blokk-server/src/main/kotlin/space/blokk/player/BlokkPlayer.kt @@ -41,10 +41,6 @@ class BlokkPlayer( this.selectedHotbarSlot = selectedHotbarSlot } - private val identifier = "BlokkPlayer($name)" - private val logger = Logger(identifier) - override val eventBus = EventBus(PlayerEvent::class, logger, coroutineContext) - override var playerListName: TextComponent? = null override var currentlyViewedChunks: List = emptyList() diff --git a/test-plugin/src/main/kotlin/space/blokk/testplugin/TestPlugin.kt b/test-plugin/src/main/kotlin/space/blokk/testplugin/TestPlugin.kt index 72ce7f6..17903c6 100644 --- a/test-plugin/src/main/kotlin/space/blokk/testplugin/TestPlugin.kt +++ b/test-plugin/src/main/kotlin/space/blokk/testplugin/TestPlugin.kt @@ -2,8 +2,6 @@ package space.blokk.testplugin import space.blokk.Blokk import space.blokk.NamespacedID -import space.blokk.event.EventHandler -import space.blokk.event.Listener import space.blokk.net.event.SessionAfterLoginEvent import space.blokk.plugin.Plugin import space.blokk.testplugin.anvil.AnvilWorld @@ -32,16 +30,13 @@ class TestPlugin: Plugin("Test", "1.0.0") { world.getVoxel(VoxelLocation(-1, 2, -1)).block = CraftingTable() - Blokk.sessions.registerListener(object : Listener { - @EventHandler - fun onSessionAfterLogin(event: SessionAfterLoginEvent) { - event.canFly = true - event.flyingSpeed = 2.0f - event.initialWorldAndLocation = WorldAndLocationWithRotation( - world, - VoxelLocation(0, 2, 0).atTopCenter().withRotation(0f, 0f) - ) - } - }) + Blokk.eventBus.on { event -> + event.canFly = true + event.flyingSpeed = 2.0f + event.initialWorldAndLocation = WorldAndLocationWithRotation( + world, + VoxelLocation(0, 2, 0).atTopCenter().withRotation(0f, 0f) + ) + } } } diff --git a/test-plugin/src/main/kotlin/space/blokk/testplugin/anvil/AnvilWorld.kt b/test-plugin/src/main/kotlin/space/blokk/testplugin/anvil/AnvilWorld.kt index 02c3dee..75ee724 100644 --- a/test-plugin/src/main/kotlin/space/blokk/testplugin/anvil/AnvilWorld.kt +++ b/test-plugin/src/main/kotlin/space/blokk/testplugin/anvil/AnvilWorld.kt @@ -4,7 +4,6 @@ import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader import com.google.common.cache.LoadingCache import space.blokk.entity.Entity -import space.blokk.event.EventTargetGroup import space.blokk.world.Chunk import space.blokk.world.Dimension import space.blokk.world.World @@ -15,7 +14,7 @@ class AnvilWorld( override val isFlat: Boolean ) : World(UUID.randomUUID()) { override val loadedChunks: Map get() = chunks.asMap().filter { (_, chunk) -> chunk.loaded } - override val entities: EventTargetGroup.Mutable = EventTargetGroup.Mutable(false) + override val entities = emptyList() private val chunks: LoadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader() {