Ok so the code below is an event system that does the following:
- Assigns an integer id to a lambda expression
- Puts the lambda's id in an event's mutable set
- Maps the integer ID to the lambda expression
- Returns the id (can be used later to remove events from the lambda)
The code is as follows:
class EventHandler {
companion object {
val handlers = HashMap<KClass<out Event>, MutableSet<Int>>()
val idMap = HashMap<Int, (Event) -> Unit>();
/**
* @param event Class of the event you are registering
* @param handler What to do when the event is called
*/
fun <T : Event> register(event: KClass<T>, handler: (T) -> Unit): Int {
var id: Int = 0;
while(idMap[id] != null) {
id++;
}
var list = handlers.getOrPut(event, {mutableSetOf()});
list.add(id);
idMap[id] = handler;
return id;
}
}
}
The intended use of this method would be something like this:
EventHandler.register(ChatEvent::class) { onChat ->
println(onChat.message)
}
There is an error at the following line: idMap[id] = handler;
The error is because the handler is of type (T) -> Unit
, although it needs to be (Event) -> Unit
in order to add it to the idMap
. Although I said that T
should extend Event
when I created it, so this shouldn't be a problem. Does anyone know why this happens of if there is a solution?
The problem comes from the fact that idMap
takes a function that receives an Event
- any kind of Event
.
Then you try to register a function that takes a specified subclass of Event
, which, when you pull it back out, the compiler won't be able to tell what possible subclass it receives. Yes, you're storing the specific type in your other map, but the compiler can't use that.
I do not believe you can create the mapping system that you want to create. Not without a few more layers of indirection or abstraction...
The reason you get this error is explained well by @jacob-zimmerman in his answer: (T) -> Unit
is not a subtype of (Event) -> Unit
(on the contrary it is a supertype).
You could make an unchecked downcast to required function type:
idMap[id] = handler as (Event) -> Unit
But then before invoking such handler you must check that an event has type that the handler could accept, for example by querying handler from map based on the type of the event:
fun invoke(event: Event) {
val kclass = event.javaClass.kotlin
val eventHandlers = handlers[kclass]?.map { idMap[it]!! } ?: return
eventHandlers.forEach { it.invoke(event) }
}
I figured out an implementation:
class Handlers<T: Event> {
val backingList = ArrayList<(T) -> Unit>()
fun add(handler: (T) -> Unit) {
backingList.add(handler)
}
fun remove(handler: (T) -> Unit) {
backingList.remove(handler)
}
fun handleEvent(event: T) {
backingList.forEach { handle ->
handle(event)
}
}
}
class HandlerMap {
val handlerMap = HashMap<KClass<out Event>, Handlers<out Event>>()
fun <T : Event> register(event: KClass<T>, handler: (T) -> Unit) {
val list: Handlers<T>
if(!handlerMap.containsKey(event)) {
list = Handlers<T>()
handlerMap.put(event, list)
}
else {
list = handlerMap.get(event) as Handlers<T>
}
list.add(handler)
}
fun <T: Event> getHandlers(event: KClass<T>): Handlers<T> {
return handlerMap.get(event) as Handlers<T>
}
}
It has to do some casting due to the Map being kind of open, but the system is closed, so the Handlers
object is guaranteed to be of the exactly right type.
Due to the implementation change, I was pretty sure you didn't care about the index/"id" anymore, but I tried an implementation with it, and it was fine; no real trouble to put it in if you want it.