Hi! I'm trying to create a Kotlin wrapper for a Ja...
# getting-started
c
Hi! I'm trying to create a Kotlin wrapper for a Java library, and I'm having some trouble. I declare a map of type
Map<String, List<(Event) -> Unit>>
, and then I have this function that adds a lambda into the map:
Copy code
inline fun <reified T : Event> T.subscribe( handler: (T) -> Unit) {
    EventManager.handlers.getOrPut(T::class.simpleName!!, mutableListOf<(T) -> Unit>())
}
The problem is is that I am getting this error:
Copy code
Type mismatch.
Required:
() → TypeVariable(V)
Found:
MutableList<(T) → Unit>
Even though I declare
T
as a subclass of
Event
. Is this intended behavior?
d
The problem is that T is a subclass of Event, not a super. You’d have to declare the list element as (Any)->Unit
Wait scrap that…
You can’t do what you’re trying to do without an unsafe cast somewhere, since the type information is lost when you put the list into the map.
c
Ohhhhhhh. Okay, thank you. I meant subclass anyways.
d
I'm writing up an example that will work, hold on a second.
c
Oh, well thank you!
d
I would go about it this way:
Copy code
import kotlin.reflect.KClass

typealias Handler<T> = (T) -> Unit
typealias HandlerList<T> = MutableList<Handler<T>>

interface Event

class EventManager {
    private val handlers: MutableMap<KClass<*>, HandlerList<*>> = mutableMapOf()

    inline fun <reified T : Event> subscribe(noinline handler: Handler<T>) = subscribe(T::class, handler)

    fun <T : Event> subscribe(kClass: KClass<T>, handler: Handler<T>) {
        handlers.computeIfAbsent(kClass) { mutableListOf() }.add(handler)
    }

    inline fun <reified T : Event> dispatch(event: T) = dispatch(T::class, event)

    fun <T : Event> dispatch(kClass: KClass<T>, event: T) =
        handlerList(kClass).forEach { handler -> handler(event) }

    @Suppress("UNCHECKED_CAST")
    private fun <T : Event> handlerList(kClass: KClass<T>): List<Handler<T>> =
        handlers[kClass] as? HandlerList<T> ?: emptyList()
}
c
I see where I went wrong.
MutableMap#getOrPut
takes
(K, () -> V)
d
Note, this is untested.
It should compile, but whether it actually works or not 🤷
c
I see. thank you
i'll test it
d
I wrote this in hopes that you can dissect it and understand what it is doing (rather than just using it as is). If you have questions about how any piece of it works, please feel free to ask!
c
Yep.
It's such a simple solution..... `
Copy code
inline fun <T : Event> T.subscribe(handler: (T) -> Unit) {
    EventManager.handlers.getOrPut(this::class.simpleName!!) { mutableListOf() }
}
because I wasn't passing a lambda.
I really do need to look at function signatures.
Thanks for the help, though!
d
The terms you should learn about are covariance and contravariance
In particular, the Contravariant method parameter type section and below
c
Ah, thank you 🙂. I just realized this isn't even possible, as I cannot introduce a
companion object
to a java class.
d
You can use
@JvmStatic
to create static methods though.
c
nevermind nly members in named objects and companion objects can be annotated with '@JvmStatic'
Only members in named objects and companion objects can be annotated with '@JvmStatic'
I guess this is a lost cause lol.
o
If anyone seeing this wants to learn about variance, there's a great guide by Dave Leeds https://typealias.com/guides/illustrated-guide-covariance-contravariance/
👍 1
d
I'm a little confused on what you're trying to do then. I thought you were calling Java code from Kotlin, not the other way around.
c
Nope, making a Kotlin wrapper for a Java library.
Currently, you'd have to create a class and then annotate a method with
@EventHandler
, but that's ugly and unintuative. I was going to make an
object
that subscribes to all of the events that are possible to subscribe to, and then iterate through the handlers for that event and call each one. This would allow you to, instead of using an entire class/file, use one line of code to handle an event.
d
Which library are you trying to wrap?
c
Event.subscribe { println("event called" }
instead of
class EventHandler : Listener { @EventHandler fun onEvent(event: Event) { println("event called" } }
I use it all the time, and hate the syntax.
d
So the syntax you'd need is going to be more like
subscribe<Event> { println("event called") }
c
I'd never though of that. Thank you!
d
It looks like maybe the event handling is actually done by Bukkit? I don't actually know a lot about Minecraft modding.
You might be able to call this method directly from your
subscribe
implementation.
Copy code
object DefaultListener : Listener

inline fun <reified E : Event> Plugin.subscribe(
    priority: EventPriority = EventPriority.NORMAL,
    ignoreCancelled: Boolean = false,
    crossinline handler: (E) -> Unit
) {
    pluginManager.registerEvent(
        E::class.java, DefaultListener, priority,
        object : EventExecutor {
            override fun execute(listener: Listener, event: Event) {
                handler(event as E)
            }
        },
        this,
        ignoreCancelled
    )
}
If I'm correct, I think you can use that, and then inside methods in your Plugin class, you can do
Copy code
subscribe<SomeEvent> { println(it) }
Where
it
is the SomeEvent object. This also lets you override the priority and the ignoreCancelled, just like you can with the annotation.
c
The event handling is done by Bukkit, correct.
and, this is now a reality. Thank you 🙂
Copy code
EventManager.subscribe<BlockBreakEvent> { event ->
            
}
I just ended up registering
EventManager
as a listener for every event. Generated ~3k lines of code.
d
That’s one way to do it ;-)
c
I didn't even see your other reply. Looks neat (and useful 😭 )
d
Could slow things down though , if there are a lot of events fired that you don’t care about.
c
Oooh.
Hmmm
d
If you have something working now though, don’t worry about it unless you see slowness.
c
All I do is call
handlers[event.eventName]?.forEach { it(event) }
yeah, that was my thought process.