aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorthedarkcolour <30441001+thedarkcolour@users.noreply.github.com>2020-04-18 00:14:00 -0700
committerthedarkcolour <30441001+thedarkcolour@users.noreply.github.com>2020-04-18 00:14:00 -0700
commit2a30736db3ac5d065d9dd17f04aa10ca64e3d431 (patch)
tree0ea36808ae5bab95582722ec816b9796a6531ffd /src
parentdf25880dd91665c897d22fa43760d2dd8393b469 (diff)
downloadKotlinForForge-2a30736db3ac5d065d9dd17f04aa10ca64e3d431.tar.gz
KotlinForForge-2a30736db3ac5d065d9dd17f04aa10ca64e3d431.tar.bz2
KotlinForForge-2a30736db3ac5d065d9dd17f04aa10ca64e3d431.zip
Update 1.2.0
Diffstat (limited to 'src')
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt6
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt17
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt4
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt51
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt17
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt2
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt373
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt52
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt33
-rw-r--r--src/main/resources/META-INF/mods.toml2
10 files changed, 504 insertions, 53 deletions
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
index fa43f57..547918f 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
@@ -35,7 +35,7 @@ public object AutoKotlinEventBusSubscriber {
* }
*/
public fun inject(mod: ModContainer, scanData: ModFileScanData, classLoader: ClassLoader) {
- logger.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}")
+ LOGGER.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}")
val data: ArrayList<ModFileScanData.AnnotationData> = scanData.annotations.stream()
.filter { annotationData ->
EVENT_BUS_SUBSCRIBER == annotationData.annotationType
@@ -51,7 +51,7 @@ public object AutoKotlinEventBusSubscriber {
val ktObject = Class.forName(annotationData.classType.className, true, classLoader).kotlin.objectInstance
if (ktObject != null && mod.modId == modid && sides.contains(FMLEnvironment.dist)) {
try {
- logger.debug(Logging.LOADING, "Auto-subscribing kotlin object ${annotationData.classType.className} to $busTarget")
+ LOGGER.debug(Logging.LOADING, "Auto-subscribing kotlin object ${annotationData.classType.className} to $busTarget")
if (busTarget == Mod.EventBusSubscriber.Bus.MOD) {
// Gets the correct mod loading context
MOD_BUS.register(ktObject)
@@ -59,7 +59,7 @@ public object AutoKotlinEventBusSubscriber {
FORGE_BUS.register(ktObject)
}
} catch (e: Throwable) {
- logger.fatal(Logging.LOADING, "Failed to load mod class ${annotationData.classType} for @EventBusSubscriber annotation", e)
+ LOGGER.fatal(Logging.LOADING, "Failed to load mod class ${annotationData.classType} for @EventBusSubscriber annotation", e)
throw RuntimeException(e)
}
}
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
index e5ec289..51840d9 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
@@ -1,9 +1,24 @@
package thedarkcolour.kotlinforforge
+import net.minecraft.block.Block
+import net.minecraft.block.material.Material
+import net.minecraftforge.event.RegistryEvent
import net.minecraftforge.fml.common.Mod
+import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext
+import thedarkcolour.kotlinforforge.forge.MOD_BUS
/**
* Set 'modLoader' in mods.toml to "kotlinforforge" and loaderVersion to "[1,)".
+ *
+ * Make sure to use [KotlinModLoadingContext] instead of [FMLJavaModLoadingContext].
*/
@Mod("kotlinforforge")
-public object KotlinForForge \ No newline at end of file
+object KotlinForForge {
+ init {
+ MOD_BUS.addGenericListener(::registerBlocks)
+ }
+
+ private fun registerBlocks(event: RegistryEvent.Register<Block>) {
+ event.registry.register(Block(Block.Properties.create(Material.ROCK)).setRegistryName("bruh"))
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
index 61705ef..d393cbd 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
@@ -15,7 +15,7 @@ public class KotlinLanguageProvider : FMLJavaModLanguageProvider() {
return Consumer { scanResult ->
val target = scanResult.annotations.stream()
.filter { data -> data.annotationType == MODANNOTATION }
- .peek { data -> logger.debug(Logging.SCAN, "Found @Mod class ${data.classType.className} with id ${data.annotationData["value"]}") }
+ .peek { data -> LOGGER.debug(Logging.SCAN, "Found @Mod class ${data.classType.className} with id ${data.annotationData["value"]}") }
.map { data -> KotlinModTarget(data.classType.className, data.annotationData["value"] as String) }
.collect(Collectors.toMap({ target: KotlinModTarget -> target.modId }, { it }, { a, _ -> a }))
scanResult.addLanguageLoader(target)
@@ -29,7 +29,7 @@ public class KotlinLanguageProvider : FMLJavaModLanguageProvider() {
public class KotlinModTarget constructor(private val className: String, val modId: String) : IModLanguageProvider.IModLanguageLoader {
override fun <T> loadMod(info: IModInfo, modClassLoader: ClassLoader, modFileScanResults: ModFileScanData): T {
val ktContainer = Class.forName("thedarkcolour.kotlinforforge.KotlinModContainer", true, Thread.currentThread().contextClassLoader)
- logger.debug(Logging.LOADING, "Loading KotlinModContainer from classloader ${Thread.currentThread().contextClassLoader} - got ${ktContainer.classLoader}}")
+ LOGGER.debug(Logging.LOADING, "Loading KotlinModContainer from classloader ${Thread.currentThread().contextClassLoader} - got ${ktContainer.classLoader}}")
val constructor = ktContainer.declaredConstructors[0]//(IModInfo::class.java, String::class.java, ClassLoader::class.java, ModFileScanData::class.java)!!
return constructor.newInstance(info, className, modClassLoader, modFileScanResults) as T
}
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
index 0ea150f..9802d90 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
@@ -6,21 +6,22 @@ import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.IEventListener
import net.minecraftforge.fml.*
+import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.forgespi.language.IModInfo
import net.minecraftforge.forgespi.language.ModFileScanData
-import java.util.*
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
import java.util.function.Consumer
import java.util.function.Supplier
/**
* Functions as [net.minecraftforge.fml.javafmlmod.FMLModContainer] for Kotlin
*/
-public class KotlinModContainer(private val info: IModInfo, private val className: String, private val classLoader: ClassLoader, private val scanData: ModFileScanData) : ModContainer(info) {
+class KotlinModContainer(private val info: IModInfo, private val className: String, private val classLoader: ClassLoader, private val scanData: ModFileScanData) : ModContainer(info) {
private lateinit var modInstance: Any
- public val eventBus: IEventBus
+ val eventBus: KotlinEventBus
init {
- logger.debug(Logging.LOADING, "Creating KotlinModContainer instance for {} with classLoader {} & {}", className, classLoader, javaClass.classLoader)
+ LOGGER.debug(Logging.LOADING, "Creating KotlinModContainer instance for {} with classLoader {} & {}", className, classLoader, javaClass.classLoader)
triggerMap[ModLoadingStage.CONSTRUCT] = dummy().andThen(::constructMod).andThen(::afterEvent)
triggerMap[ModLoadingStage.CREATE_REGISTRIES] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.LOAD_REGISTRIES] = dummy().andThen(::fireEvent).andThen(::afterEvent)
@@ -30,8 +31,7 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
triggerMap[ModLoadingStage.PROCESS_IMC] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.COMPLETE] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.GATHERDATA] = dummy().andThen(::fireEvent).andThen(::afterEvent)
- eventBus = BusBuilder.builder().setExceptionHandler(::onEventFailed).setTrackPhases(false).build()
- configHandler = Optional.of(Consumer { event -> eventBus.post(event) })
+ eventBus = KotlinEventBus(BusBuilder.builder().setExceptionHandler(::onEventFailed).setTrackPhases(false))
val ctx = KotlinModLoadingContext(this)
contextExtension = Supplier { ctx }
}
@@ -39,25 +39,25 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
private fun dummy(): Consumer<LifecycleEventProvider.LifecycleEvent> = Consumer {}
private fun onEventFailed(iEventBus: IEventBus, event: Event, iEventListeners: Array<IEventListener>, i: Int, throwable: Throwable) {
- logger.error(EventBusErrorMessage(event, i, iEventListeners, throwable))
+ LOGGER.error(EventBusErrorMessage(event, i, iEventListeners, throwable))
}
private fun fireEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
val event = lifecycleEvent.getOrBuildEvent(this)
- logger.debug(Logging.LOADING, "Firing event for modid $modId : $event")
+ LOGGER.debug(Logging.LOADING, "Firing event for modid $modId : $event")
try {
eventBus.post(event)
- logger.debug(Logging.LOADING, "Fired event for modid $modId : $event")
+ LOGGER.debug(Logging.LOADING, "Fired event for modid $modId : $event")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING,"An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
+ LOGGER.error(Logging.LOADING,"An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.errorduringevent", throwable)
}
}
private fun afterEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
if (currentState == ModLoadingStage.ERROR) {
- logger.error(Logging.LOADING, "An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
+ LOGGER.error(Logging.LOADING, "An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
}
}
@@ -65,41 +65,40 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
val modClass: Class<*>
try {
modClass = Class.forName(className, true, classLoader)
- logger.debug(Logging.LOADING, "Loaded kotlin modclass ${modClass.name} with ${modClass.classLoader}")
+ LOGGER.debug(Logging.LOADING, "Loaded kotlin modclass ${modClass.name} with ${modClass.classLoader}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to load kotlin class $className", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to load kotlin class $className", throwable)
throw ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", throwable)
}
try {
- logger.debug(Logging.LOADING, "Loading mod instance ${getModId()} of type ${modClass.name}")
+ LOGGER.debug(Logging.LOADING, "Loading mod instance ${getModId()} of type ${modClass.name}")
modInstance = modClass.kotlin.objectInstance ?: modClass.newInstance()
- logger.debug(Logging.LOADING, "Loaded mod instance ${getModId()} of type ${modClass.name}")
+ LOGGER.debug(Logging.LOADING, "Loaded mod instance ${getModId()} of type ${modClass.name}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to create mod instance. ModID: ${getModId()}, class ${modClass.name}", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to create mod instance. ModID: ${getModId()}, class ${modClass.name}", throwable)
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.failedtoloadmod", throwable, modClass)
}
try {
- logger.debug(Logging.LOADING, "Injecting Automatic Kotlin event subscribers for ${getModId()}")
+ LOGGER.debug(Logging.LOADING, "Injecting Automatic Kotlin event subscribers for ${getModId()}")
// Inject into object EventBusSubscribers
AutoKotlinEventBusSubscriber.inject(this, scanData, modClass.classLoader)
- logger.debug(Logging.LOADING, "Completed Automatic Kotlin event subscribers for ${getModId()}")
+ LOGGER.debug(Logging.LOADING, "Completed Automatic Kotlin event subscribers for ${getModId()}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to register Automatic Kotlin subscribers. ModID: ${getModId()}, class ${modClass.name}", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to register Automatic Kotlin subscribers. ModID: ${getModId()}, class ${modClass.name}", throwable)
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.failedtoloadmod", throwable, modClass)
}
}
- override fun matches(mod: Any?): Boolean {
- return mod == modInstance
- }
-
- override fun getMod(): Any {
- return modInstance
- }
+ override fun matches(mod: Any?) = mod == modInstance
+ override fun getMod() = modInstance
override fun acceptEvent(e: Event) {
eventBus.post(e)
}
+
+ override fun dispatchConfigEvent(event: ModConfig.ModConfigEvent) {
+ eventBus.post(event)
+ }
} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
index af42d16..44aac2d 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
@@ -1,19 +1,22 @@
package thedarkcolour.kotlinforforge
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.ModLoadingContext
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
+import thedarkcolour.kotlinforforge.forge.LOADING_CONTEXT
/**
* Functions as [net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext] for Kotlin
*/
-public class KotlinModLoadingContext constructor(private val container: KotlinModContainer) {
- public fun getEventBus(): IEventBus {
+class KotlinModLoadingContext constructor(private val container: KotlinModContainer) {
+ /**
+ * @see thedarkcolour.kotlinforforge.forge.MOD_BUS
+ */
+ fun getEventBus(): KotlinEventBus {
return container.eventBus
}
- public companion object {
- public fun get(): KotlinModLoadingContext {
- return ModLoadingContext.get().extension()
+ companion object {
+ fun get(): KotlinModLoadingContext {
+ return LOADING_CONTEXT.extension()
}
}
} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
index 8276903..3724657 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
@@ -8,4 +8,4 @@ import org.apache.logging.log4j.LogManager
* Kept here instead of [KotlinForForge] because logger is used
* before [KotlinModContainer] should initialize.
*/
-internal val logger = LogManager.getLogger() \ No newline at end of file
+internal val LOGGER = LogManager.getLogger() \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
new file mode 100644
index 0000000..7323f23
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
@@ -0,0 +1,373 @@
+package thedarkcolour.kotlinforforge.eventbus
+
+import net.jodah.typetools.TypeResolver
+import net.minecraftforge.eventbus.ASMEventHandler
+import net.minecraftforge.eventbus.EventBus
+import net.minecraftforge.eventbus.ListenerList
+import net.minecraftforge.eventbus.api.*
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.MarkerManager
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.util.*
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.function.Consumer
+
+/** @since 1.2.0
+ * Fixes [IEventBus.addListener] for Kotlin SAM interfaces.
+ */
+open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEventBus, IEventExceptionHandler {
+ @Suppress("LeakingThis")
+ private val exceptionHandler = builder.exceptionHandler ?: this
+ private val trackPhases = builder.trackPhases
+ @Volatile
+ private var shutdown = builder.isStartingShutdown
+ protected open val busID = MAX_ID.getAndIncrement()
+ protected open val listeners = ConcurrentHashMap<Any, MutableList<IEventListener>>()
+
+ init {
+ // see companion object
+ if (!synthetic) {
+ RESIZE_LISTENER_LIST(busID + 1)
+ }
+ }
+
+ override fun register(target: Any) {
+ if (!listeners.containsKey(target)) {
+ if (target.javaClass == Class::class.java) {
+ registerClass(target as Class<*>)
+ } else {
+ registerObject(target)
+ }
+ }
+ }
+
+ protected fun registerClass(clazz: Class<*>) {
+ for (method in clazz.methods) {
+ if (Modifier.isStatic(method.modifiers) && method.isAnnotationPresent(SubscribeEvent::class.java)) {
+ registerListener(clazz, method, method)
+ }
+ }
+ }
+
+ protected fun registerObject(target: Any) {
+ val classes = HashSet<Class<*>>()
+ typesFor(target.javaClass, classes)
+ Arrays.stream(target.javaClass.methods).filter { m ->
+ !Modifier.isStatic(m.modifiers)
+ }.forEach { m ->
+ classes.map { c ->
+ getDeclMethod(c, m)
+ }.firstOrNull { rm ->
+ rm?.isAnnotationPresent(SubscribeEvent::class.java) == true
+ }?.let { rm ->
+ registerListener(target, m, rm)
+ }
+ }
+ }
+
+ private fun typesFor(clz: Class<*>, visited: MutableSet<Class<*>>) {
+ if (clz.superclass == null) return
+ typesFor(clz.superclass, visited)
+ Arrays.stream(clz.interfaces).forEach { typesFor(it, visited) }
+ visited.add(clz)
+ }
+
+ private fun getDeclMethod(clz: Class<*>, m: Method): Method? {
+ return try {
+ clz.getDeclaredMethod(m.name, *m.parameterTypes)
+ } catch (nse: NoSuchMethodException) {
+ null
+ }
+ }
+
+ private fun registerListener(target: Any, f: Method, real: Method) {
+ val params: Array<Class<*>> = f.parameterTypes
+
+ if (params.size != 1) {
+ throw IllegalArgumentException("""
+ Function $f has @SubscribeEvent annotation.
+ It has ${params.size} value parameters,
+ but event handler functions require1 value parameter.
+ """.trimIndent()
+ )
+ }
+
+ val type = params[0]
+
+ if (!Event::class.java.isAssignableFrom(type)) {
+ throw IllegalArgumentException("""
+ Function $f has @SubscribeEvent annotation,
+ but takes an argument that is not an Event subtype : $type
+ """.trimIndent())
+ }
+
+ register(type, target, real)
+ }
+
+ private fun register(type: Class<*>, target: Any, f: Method) {
+ try {
+ val asm = ASMEventHandler(target, f, IGenericEvent::class.java.isAssignableFrom(type))
+
+ addToListeners(target, type, asm.priority, asm)
+ } catch (e: IllegalAccessException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: InstantiationException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: NoSuchMethodException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: InvocationTargetException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ }
+ }
+
+ protected open fun addToListeners(target: Any, eventType: Class<*>, priority: EventPriority, listener: IEventListener) {
+ val listenerList = EventListenerHelper.getListenerList(eventType)
+ listenerList.register(busID, priority, listener)
+ val others = listeners.computeIfAbsent(target) { Collections.synchronizedList(ArrayList()) }
+ others.add(listener)
+ }
+
+ /**
+ * Add a consumer listener with default [EventPriority.NORMAL] and not receiving cancelled events.
+ *
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(consumer: Consumer<T>) {
+ addListener(EventPriority.NORMAL, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and not receiving cancelled events.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, consumer: Consumer<T>) {
+ addListener(priority, false, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, receiveCancelled: Boolean, consumer: Consumer<T>) {
+ addListener(priority, passCancelled(receiveCancelled), consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events.
+ *
+ * Use this method when one of the other methods fails to determine the concrete [Event] subclass that is
+ * intended to be subscribed to.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param eventType The concrete [Event] subclass to subscribe to
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, receiveCancelled: Boolean, eventType: Class<T>, consumer: Consumer<T>) {
+ addListener(priority, passCancelled(receiveCancelled), eventType, consumer)
+ }
+
+ private fun <T : Event> addListener(priority: EventPriority, filter: (T) -> Boolean, eventType: Class<T>, consumer: Consumer<T>) {
+ addToListeners(consumer, eventType, priority) { e ->
+ if (filter(e as T)) {
+ consumer.accept(e)
+ }
+ }
+ }
+
+ private fun passCancelled(receiveCancelled: Boolean): (Event) -> Boolean = { event ->
+ receiveCancelled || !event.isCancelable || !event.isCanceled
+ }
+
+ /**
+ * Add a consumer listener for a [GenericEvent] subclass with generic type [F].
+ *
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ inline fun <T : GenericEvent<out F>, reified F> addGenericListener(consumer: Consumer<T>) {
+ addGenericListener(F::class.java, consumer)
+ }
+
+ /**
+ * Add a consumer listener for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, consumer: Consumer<T>) {
+ addGenericListener(genericClassFilter, EventPriority.NORMAL, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and not receiving cancelled events,
+ * for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param priority [EventPriority] for this listener
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, priority: EventPriority, consumer: Consumer<T>) {
+ addGenericListener(genericClassFilter, priority, false, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events,
+ * for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, priority: EventPriority, receiveCancelled: Boolean, consumer: Consumer<T>) {
+ addListener(priority, passGenericCancelled(genericClassFilter, receiveCancelled), consumer)
+ }
+
+ private fun <T : Event> addListener(priority: EventPriority, filter: (T) -> Boolean, consumer: Consumer<T>) {
+ val eventType = reflectKotlinSAM(consumer) as Class<T>?
+
+ if (eventType == null) {
+ LOGGER.error(EVENT_BUS, "Failed to resolve handler for \"$consumer\"")
+ throw IllegalStateException("Failed to resolve KFunction event type: $consumer")
+ }
+ if (eventType == Event::class.java) {
+ LOGGER.warn(EVENT_BUS, """
+ Attempting to add a Lambda listener with computed generic type of Event.
+ Are you sure this is what you meant? NOTE : there are complex lambda forms where
+ the generic type information is erased and cannot be recovered at runtime.
+ """.trimIndent())
+ }
+
+ addListener(priority, filter, eventType, consumer)
+ }
+
+ /**
+ * Fixes issue that crashes when trying to register Kotlin SAM interface
+ * for a [Consumer] using the Java [IEventBus.addListener] method
+ */
+ private fun reflectKotlinSAM(consumer: Consumer<*>): Class<*>? {
+ val clazz = consumer.javaClass
+
+ if (clazz.simpleName.contains("$\$Lambda$")) {
+ return TypeResolver.resolveRawArgument(Consumer::class.java, consumer.javaClass)
+ } else if (clazz.simpleName.contains("\$sam$")) {
+ try {
+ val functionField = clazz.getDeclaredField("function")
+ functionField.isAccessible = true
+ val function = functionField[consumer]
+
+ // Function should have two type parameters (parameter type and return type)
+ return TypeResolver.resolveRawArguments(kotlin.jvm.functions.Function1::class.java, function.javaClass)[0]
+ } catch (e: NoSuchFieldException) {
+ // Kotlin SAM interfaces compile to classes with a "function" field
+ LOGGER.log(Level.FATAL, "Tried to register invalid Kotlin SAM interface: Missing 'function' field")
+ throw e
+ }
+ } else return null
+ }
+
+ private fun <T : GenericEvent<out F>, F> passGenericCancelled(genericClassFilter: Class<F>, receiveCancelled: Boolean): (T) -> Boolean = { event ->
+ event.genericType == genericClassFilter && (receiveCancelled || !event.isCancelable || !event.isCanceled)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events,
+ * for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * Use this method when one of the other methods fails to determine the concrete [GenericEvent] subclass that is
+ * intended to be subscribed to.
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param eventType The concrete [GenericEvent] subclass to subscribe to
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, priority: EventPriority, receiveCancelled: Boolean, eventType: Class<T>, consumer: Consumer<T>) {
+ addListener(priority, passGenericCancelled(genericClassFilter, receiveCancelled), eventType, consumer)
+ }
+
+ override fun unregister(any: Any?) {
+ val list = listeners.remove(any) ?: return
+
+ for (listener in list) {
+ ListenerList.unregisterAll(busID, listener)
+ }
+ }
+
+ override fun post(event: Event): Boolean {
+ if (shutdown) return false
+
+ val listeners = event.listenerList.getListeners(busID)
+
+ for (index in listeners.indices) {
+ try {
+ if (!trackPhases && listeners[index]::class.java == EventPriority::class.java) {
+ continue
+ } else {
+ listeners[index].invoke(event)
+ }
+ } catch (throwable: Throwable) {
+ exceptionHandler.handleException(this, event, listeners, index, throwable)
+ throw throwable
+ }
+ }
+
+ return event.isCancelable && event.isCanceled
+ }
+
+ override fun handleException(bus: IEventBus, event: Event, listeners: Array<out IEventListener>, index: Int, throwable: Throwable) {
+ LOGGER.error(EVENT_BUS)
+ }
+
+ override fun shutdown() {
+ LOGGER.fatal(EVENT_BUS, "KotlinEventBus $busID shutting down - future events will not be posted.", Exception("stacktrace"))
+ }
+
+ override fun start() {
+ shutdown = false
+ }
+
+ companion object {
+ private val LOGGER = LogManager.getLogger()
+ private val EVENT_BUS = MarkerManager.getMarker("EVENTBUS")
+ private val MAX_ID: AtomicInteger
+ private val RESIZE_LISTENER_LIST: (Int) -> Unit
+
+ init {
+ val maxIDField = EventBus::class.java.getDeclaredField("maxID")
+ maxIDField.isAccessible = true
+ MAX_ID = maxIDField.get(null) as AtomicInteger
+ val resizeMethod = ListenerList::class.java.getDeclaredMethod("resize", Int::class.java)
+ resizeMethod.isAccessible = true
+ RESIZE_LISTENER_LIST = { max -> resizeMethod.invoke(null, max) }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt
new file mode 100644
index 0000000..b3cb9d5
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt
@@ -0,0 +1,52 @@
+package thedarkcolour.kotlinforforge.eventbus
+
+import net.minecraftforge.eventbus.EventBus
+import net.minecraftforge.eventbus.api.BusBuilder
+import net.minecraftforge.eventbus.api.IEventBus
+import net.minecraftforge.eventbus.api.IEventExceptionHandler
+import net.minecraftforge.eventbus.api.IEventListener
+import thedarkcolour.kotlinforforge.forge.FORGE_BUS
+import java.util.concurrent.ConcurrentHashMap
+
+/** @since 1.2.0
+ * Fixes [IEventBus.addListener] for Kotlin SAM interfaces
+ * when using [FORGE_BUS].
+ */
+class KotlinEventBusWrapper(private val parent: EventBus) : KotlinEventBus(BusBuilder()
+ .setExceptionHandler(getExceptionHandler(parent))
+ .setTrackPhases(getTrackPhases(parent))
+ .also { if (getShutdown(parent)) it.startShutdown() }
+) {
+ override val busID = getBusID(parent)
+ override val listeners = getListeners(parent)
+
+ // reflection stuff
+ companion object {
+ private val GET_BUS_ID = EventBus::class.java.getDeclaredField("busID").also { it.isAccessible = true }
+ private val GET_LISTENERS = EventBus::class.java.getDeclaredField("listeners").also { it.isAccessible = true }
+ private val GET_EXCEPTION_HANDLER = EventBus::class.java.getDeclaredField("exceptionHandler").also { it.isAccessible = true }
+ private val GET_TRACK_PHASES = EventBus::class.java.getDeclaredField("trackPhases").also { it.isAccessible = true }
+ private val GET_SHUTDOWN = EventBus::class.java.getDeclaredField("shutdown").also { it.isAccessible = true }
+
+ fun getBusID(eventBus: EventBus): Int {
+ return GET_BUS_ID[eventBus] as Int
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun getListeners(eventBus: EventBus): ConcurrentHashMap<Any, MutableList<IEventListener>> {
+ return GET_LISTENERS[eventBus] as ConcurrentHashMap<Any, MutableList<IEventListener>>
+ }
+
+ fun getExceptionHandler(eventBus: EventBus): IEventExceptionHandler {
+ return GET_EXCEPTION_HANDLER[eventBus] as IEventExceptionHandler
+ }
+
+ fun getTrackPhases(eventBus: EventBus): Boolean {
+ return GET_TRACK_PHASES[eventBus] as Boolean
+ }
+
+ fun getShutdown(eventBus: EventBus): Boolean {
+ return GET_SHUTDOWN[eventBus] as Boolean
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt
index d712c75..e0b9cd8 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt
@@ -3,34 +3,43 @@ package thedarkcolour.kotlinforforge.forge
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.common.ForgeConfigSpec
import net.minecraftforge.common.MinecraftForge
-import net.minecraftforge.eventbus.api.IEventBus
+import net.minecraftforge.eventbus.EventBus
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.fml.loading.FMLEnvironment
import thedarkcolour.kotlinforforge.KotlinModLoadingContext
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBusWrapper
/** @since 1.0.0
- * The forge EventBus.
+ * The forge EventBus wrapped in a [KEventBus].
* Many events that occur during the game are fired on this bus.
*
+ * @since 1.2.0
+ * This event bus supports [EventBus.addListener]
+ * for Kotlin SAM interfaces.
+ *
* Examples:
* @see net.minecraftforge.event.entity.player.PlayerEvent
* @see net.minecraftforge.event.entity.living.LivingEvent
* @see net.minecraftforge.event.world.BlockEvent
*/
-public val FORGE_BUS: IEventBus
- inline get() = MinecraftForge.EVENT_BUS
+val FORGE_BUS = KotlinEventBusWrapper(MinecraftForge.EVENT_BUS as EventBus)
/** @since 1.0.0
* The mod-specific EventBus.
* Setup events are typically fired on this bus.
*
+ * @since 1.2.0
+ * This event bus supports [EventBus.addListener]
+ * for Kotlin SAM interfaces.
+ *
* Examples:
* @see net.minecraftforge.fml.event.lifecycle.InterModProcessEvent
* @see net.minecraftforge.event.AttachCapabilitiesEvent
* @see net.minecraftforge.event.RegistryEvent
*/
-public val MOD_BUS: IEventBus
+val MOD_BUS: KotlinEventBus
inline get() = KotlinModLoadingContext.get().getEventBus()
/** @since 1.0.0
@@ -38,22 +47,22 @@ public val MOD_BUS: IEventBus
*
* Used in place of [net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext]
*/
-public val MOD_CONTEXT: KotlinModLoadingContext
+val MOD_CONTEXT: KotlinModLoadingContext
inline get() = KotlinModLoadingContext.get()
-public val LOADING_CONTEXT: ModLoadingContext
+val LOADING_CONTEXT: ModLoadingContext
inline get() = ModLoadingContext.get()
/** @since 1.0.0
* The current [Dist] of this environment.
*/
-public val DIST: Dist = FMLEnvironment.dist
+val DIST: Dist = FMLEnvironment.dist
/** @since 1.0.0
* An alternative to [net.minecraftforge.fml.DistExecutor.callWhenOn]
* that inlines the callable.
*/
-public inline fun <T> callWhenOn(dist: Dist, toRun: () -> T): T? {
+inline fun <T> callWhenOn(dist: Dist, toRun: () -> T): T? {
return if (DIST == dist) {
try {
toRun()
@@ -69,7 +78,7 @@ public inline fun <T> callWhenOn(dist: Dist, toRun: () -> T): T? {
* An alternative to [net.minecraftforge.fml.DistExecutor.runWhenOn]
* that uses Kotlin functions instead of Java functional interfaces.
*/
-public inline fun runWhenOn(dist: Dist, toRun: () -> Unit) {
+inline fun runWhenOn(dist: Dist, toRun: () -> Unit) {
if (DIST == dist) {
toRun()
}
@@ -79,7 +88,7 @@ public inline fun runWhenOn(dist: Dist, toRun: () -> Unit) {
* An alternative to [net.minecraftforge.fml.DistExecutor.runForDist]
* that inlines the method call.
*/
-public inline fun <T> runForDist(clientTarget: () -> T, serverTarget: () -> T): T {
+inline fun <T> runForDist(clientTarget: () -> T, serverTarget: () -> T): T {
return when (DIST) {
Dist.CLIENT -> clientTarget()
Dist.DEDICATED_SERVER -> serverTarget()
@@ -89,7 +98,7 @@ public inline fun <T> runForDist(clientTarget: () -> T, serverTarget: () -> T):
/** @since 1.0.0
* Registers a config.
*/
-public fun registerConfig(type: ModConfig.Type, spec: ForgeConfigSpec, fileName: String? = null) {
+fun registerConfig(type: ModConfig.Type, spec: ForgeConfigSpec, fileName: String? = null) {
if (fileName == null) {
LOADING_CONTEXT.registerConfig(type, spec)
} else {
diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml
index 7f4e0be..a58fee0 100644
--- a/src/main/resources/META-INF/mods.toml
+++ b/src/main/resources/META-INF/mods.toml
@@ -33,6 +33,6 @@ Kotlin for Forge. Allows mods to use the Kotlin programming language.
[[mods]] #mandatory
displayName="Kotlin for Forge" # Name of mod
modId="kotlinforforge" # Modid
-version="1.0.1" # Version of kotlinforforge
+version="1.2.0" # Version of kotlinforforge
authors="TheDarkColour" # Author
credits="Herobrine knows all." # Credits \ No newline at end of file