diff --git a/README.md b/README.md index 23e6536e..872abdf5 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Include the dependency to MCCoroutine com.github.shynixn.mccoroutine mccoroutine-bukkit-api - 0.0.4 + 0.0.5 provided ``` @@ -52,7 +52,7 @@ Include the dependency to MCCoroutine ```xml dependencies { - compileOnly("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:0.0.4") + compileOnly("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:0.0.5") } ``` @@ -198,12 +198,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlin.coroutines.CoroutineContext -fun launch(f: suspend CoroutineScope.() -> Unit) { - JavaPlugin.getPlugin(YourPluginClass::class.java).launch(f) +fun launch(f: suspend CoroutineScope.() -> Unit): Job { + return JavaPlugin.getPlugin(YourPluginClass::class.java).launch(f) } -fun launchAsync(f: suspend CoroutineScope.() -> Unit) { - JavaPlugin.getPlugin(YourPluginClass::class.java).launchAsync(f) +fun launchAsync(f: suspend CoroutineScope.() -> Unit): Job { + return JavaPlugin.getPlugin(YourPluginClass::class.java).launchAsync(f) } val Dispatchers.minecraft: CoroutineContext @@ -486,7 +486,7 @@ class PlaceHolderApiConnector(private val cache : UserDataCache) { com.github.shynixn.mccoroutine mccoroutine-bukkit-core - 0.0.4 + 0.0.5 compile @@ -512,7 +512,7 @@ class PlaceHolderApiConnector(private val cache : UserDataCache) { ```xml dependencies { - implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:0.0.4") + implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:0.0.5") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.x.x") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.x.x") implementation("org.jetbrains.kotlin:kotlin-reflect:1.x.x") diff --git a/build.gradle b/build.gradle index 888d6d25..3eb87421 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ allprojects { subprojects { group 'com.github.shynixn.mccoroutine' - version '0.0.4' + version '0.0.5' sourceCompatibility = 1.8 diff --git a/mccoroutine-bukkit-api/src/main/java/com/github/shynixn/mccoroutine/Extension.kt b/mccoroutine-bukkit-api/src/main/java/com/github/shynixn/mccoroutine/Extension.kt index fd61b0f9..125854da 100644 --- a/mccoroutine-bukkit-api/src/main/java/com/github/shynixn/mccoroutine/Extension.kt +++ b/mccoroutine-bukkit-api/src/main/java/com/github/shynixn/mccoroutine/Extension.kt @@ -2,6 +2,7 @@ package com.github.shynixn.mccoroutine import com.github.shynixn.mccoroutine.contract.MCCoroutine import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import org.bukkit.command.PluginCommand import org.bukkit.event.Listener import org.bukkit.plugin.Plugin @@ -68,9 +69,10 @@ val Plugin.scope: CoroutineScope * for example that event cancelling or modifying return values is still possible. * @param dispatcher Coroutine context. The default context is minecraft dispatcher. * @param f callback function inside a coroutine scope. + * @return Cancelable coroutine job. */ -fun Plugin.launch(dispatcher: CoroutineContext, f: suspend CoroutineScope.() -> Unit) { - mcCoroutine.getCoroutineSession(this).launch(dispatcher, f) +fun Plugin.launch(dispatcher: CoroutineContext, f: suspend CoroutineScope.() -> Unit): Job { + return mcCoroutine.getCoroutineSession(this).launch(dispatcher, f) } /** @@ -79,9 +81,10 @@ fun Plugin.launch(dispatcher: CoroutineContext, f: suspend CoroutineScope.() -> * calling this function Bukkit.isPrimaryThread() is true. This means * for example that event cancelling or modifying return values is still possible. * @param f callback function inside a coroutine scope. + * @return Cancelable coroutine job. */ -fun Plugin.launch(f: suspend CoroutineScope.() -> Unit) { - mcCoroutine.getCoroutineSession(this).launch(minecraftDispatcher, f) +fun Plugin.launch(f: suspend CoroutineScope.() -> Unit): Job { + return mcCoroutine.getCoroutineSession(this).launch(minecraftDispatcher, f) } /** @@ -90,9 +93,10 @@ fun Plugin.launch(f: suspend CoroutineScope.() -> Unit) { * calling this function Bukkit.isPrimaryThread() is false. This means * for example that event cancelling or modifying return values is still possible. * @param f callback function inside a coroutine scope. + * @return Cancelable coroutine job. */ -fun Plugin.launchAsync(f: suspend CoroutineScope.() -> Unit) { - mcCoroutine.getCoroutineSession(this).launch(this.asyncDispatcher, f) +fun Plugin.launchAsync(f: suspend CoroutineScope.() -> Unit): Job { + return mcCoroutine.getCoroutineSession(this).launch(this.asyncDispatcher, f) } /** diff --git a/mccoroutine-bukkit-api/src/main/java/com/github/shynixn/mccoroutine/contract/CoroutineSession.kt b/mccoroutine-bukkit-api/src/main/java/com/github/shynixn/mccoroutine/contract/CoroutineSession.kt index a789d684..f48d8962 100644 --- a/mccoroutine-bukkit-api/src/main/java/com/github/shynixn/mccoroutine/contract/CoroutineSession.kt +++ b/mccoroutine-bukkit-api/src/main/java/com/github/shynixn/mccoroutine/contract/CoroutineSession.kt @@ -1,6 +1,7 @@ package com.github.shynixn.mccoroutine.contract import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlin.coroutines.CoroutineContext interface CoroutineSession { @@ -31,8 +32,9 @@ interface CoroutineSession { /** * Launches the given function on the plugin coroutine scope. + * @return Cancelable coroutine job. */ - fun launch(dispatcher: CoroutineContext, f: suspend CoroutineScope.() -> Unit) + fun launch(dispatcher: CoroutineContext, f: suspend CoroutineScope.() -> Unit) : Job /** * Disposes the session. diff --git a/mccoroutine-bukkit-core/src/main/java/com/github/shynixn/mccoroutine/service/CoroutineSessionImpl.kt b/mccoroutine-bukkit-core/src/main/java/com/github/shynixn/mccoroutine/service/CoroutineSessionImpl.kt index d32d7cf8..8a5e34d8 100644 --- a/mccoroutine-bukkit-core/src/main/java/com/github/shynixn/mccoroutine/service/CoroutineSessionImpl.kt +++ b/mccoroutine-bukkit-core/src/main/java/com/github/shynixn/mccoroutine/service/CoroutineSessionImpl.kt @@ -60,18 +60,17 @@ internal class CoroutineSessionImpl(private val plugin: Plugin) : CoroutineSessi /** * Launches the given function on the plugin coroutine scope. */ - override fun launch(dispatcher: CoroutineContext, f: suspend CoroutineScope.() -> Unit) { + override fun launch(dispatcher: CoroutineContext, f: suspend CoroutineScope.() -> Unit) : Job { if (disposed) { - return + return Job() } if (dispatcher == Dispatchers.Unconfined) { // If the dispatcher is unconfined. Always schedule immediately. - launchInternal(dispatcher, CoroutineStart.UNDISPATCHED, f) - return + return launchInternal(dispatcher, CoroutineStart.UNDISPATCHED, f) } - launchInternal(dispatcher, CoroutineStart.DEFAULT, f) + return launchInternal(dispatcher, CoroutineStart.DEFAULT, f) } /** @@ -81,9 +80,9 @@ internal class CoroutineSessionImpl(private val plugin: Plugin) : CoroutineSessi dispatcher: CoroutineContext, coroutineStart: CoroutineStart, f: suspend CoroutineScope.() -> Unit - ) { + ) : Job { // Launch a new coroutine on the current thread thread on the plugin scope. - scope.launch(dispatcher, coroutineStart) { + return scope.launch(dispatcher, coroutineStart) { try { // The user may or may not launch multiple sub suspension operations. If // one of those fails, only this scope should fail instead of the plugin scope. diff --git a/mccoroutine-bukkit-core/src/main/java/com/github/shynixn/mccoroutine/service/EventServiceImpl.kt b/mccoroutine-bukkit-core/src/main/java/com/github/shynixn/mccoroutine/service/EventServiceImpl.kt index eb81ca9c..5452997c 100644 --- a/mccoroutine-bukkit-core/src/main/java/com/github/shynixn/mccoroutine/service/EventServiceImpl.kt +++ b/mccoroutine-bukkit-core/src/main/java/com/github/shynixn/mccoroutine/service/EventServiceImpl.kt @@ -3,26 +3,17 @@ package com.github.shynixn.mccoroutine.service import com.github.shynixn.mccoroutine.contract.CoroutineSession import com.github.shynixn.mccoroutine.contract.EventService import com.github.shynixn.mccoroutine.extension.invokeSuspend -import com.github.shynixn.mccoroutine.launch -import com.github.shynixn.mccoroutine.minecraftDispatcher import kotlinx.coroutines.Dispatchers import org.bukkit.Warning import org.bukkit.event.* import org.bukkit.plugin.* -import org.bukkit.plugin.java.JavaPluginLoader import java.lang.Deprecated import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method -import java.util.* import java.util.logging.Level -import kotlin.Any -import kotlin.Exception import kotlin.IllegalArgumentException -import kotlin.Int import kotlin.String import kotlin.Throwable -import kotlin.collections.HashMap -import kotlin.collections.HashSet internal class EventServiceImpl(private val plugin: Plugin, private val coroutineSession: CoroutineSession) : EventService { @@ -37,119 +28,96 @@ internal class EventServiceImpl(private val plugin: Plugin, private val coroutin method.isAccessible = true for (entry in registeredListeners.entries) { - val clazz = entry.key as Class<*> + val clazz = entry.key val handlerList = method.invoke(plugin.server.pluginManager, clazz) as HandlerList handlerList.registerAll(entry.value as MutableCollection) } } /** - * Ugly coroutine listener hacking. + * Creates a listener according to the spigot implementation. */ - private fun createCoroutineListener(listener: Listener, plugin: Plugin): HashMap<*, *> { - val ret: HashMap, Set> = HashMap() + private fun createCoroutineListener( + listener: Listener, + plugin: Plugin + ): Map, MutableSet> { + val eventMethods = HashSet() - val methods: HashSet<*> try { - val publicMethods = listener.javaClass.methods - val privateMethods = listener.javaClass.declaredMethods - methods = HashSet(publicMethods.size + privateMethods.size, 1.0f) - var var11 = publicMethods - var var10 = publicMethods.size - var method: Method? - var var9: Int - var9 = 0 - while (var9 < var10) { - method = var11[var9] - methods.add(method) - ++var9 + // Adds public methods of the current class and inherited classes + eventMethods.addAll(listener.javaClass.methods) + // Adds all methods of the current class + eventMethods.addAll(listener.javaClass.declaredMethods) + } catch (e: NoClassDefFoundError) { + plugin.logger.severe("Plugin " + plugin.description.fullName + " has failed to register events for " + listener.javaClass + " because " + e.message + " does not exist.") + return emptyMap() + } + + val result = mutableMapOf, MutableSet>() + + for (method in eventMethods) { + val annotation = method.getAnnotation(EventHandler::class.java) + + if (annotation == null || method.isBridge || method.isSynthetic) { + continue } - var11 = privateMethods - var10 = privateMethods.size - var9 = 0 - while (var9 < var10) { - method = var11[var9] - methods.add(method) - ++var9 + + val eventClass = method.parameterTypes[0].asSubclass(Event::class.java) + method.isAccessible = true + + if (!result.containsKey(eventClass)) { + result[eventClass] = HashSet() } - } catch (var15: NoClassDefFoundError) { - plugin.logger.severe("Plugin " + plugin.description.fullName + " has failed to register events for " + listener.javaClass + " because " + var15.message + " does not exist.") - return ret - } - val var17: Iterator<*> = methods.iterator() - - while (true) { - while (true) { - var method: Method - var eh: EventHandler? - do { - do { - do { - if (!var17.hasNext()) { - return ret - } - method = var17.next() as Method - eh = method.getAnnotation(EventHandler::class.java) - } while (eh == null) - } while (method.isBridge) - } while (method.isSynthetic) - - var checkClass: Class<*> = method.getParameterTypes()[0] - val eventClass = checkClass.asSubclass(Event::class.java) - method.isAccessible = true - - var eventSet: MutableSet? = ret[eventClass] as MutableSet? - - if (eventSet == null) { - eventSet = HashSet() - ret[eventClass] = eventSet - } + var clazz: Class<*> = eventClass - var clazz: Class<*> = eventClass - - while (Event::class.java.isAssignableFrom(clazz)) { - if (clazz.getAnnotation(Deprecated::class.java) != null) { - val warning = clazz.getAnnotation(Warning::class.java) - val warningState: Warning.WarningState = plugin.server.getWarningState() - if (warningState.printFor(warning)) { - plugin.logger.log( - Level.WARNING, - String.format( - "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated. \"%s\"; please notify the authors %s.", - plugin.description.fullName, - clazz.name, - method.toGenericString(), - if (warning != null && warning.reason.length != 0) warning.reason else "Server performance will be affected", - Arrays.toString(plugin.description.authors.toTypedArray()) - ), - if (warningState == Warning.WarningState.ON) AuthorNagException(null as String?) else null - ) - } - break - } + while (Event::class.java.isAssignableFrom(clazz)) { + if (clazz.getAnnotation(Deprecated::class.java) == null) { clazz = clazz.superclass + continue + } + + val warning = clazz.getAnnotation(Warning::class.java) + val warningState = plugin.server.warningState + + if (!warningState.printFor(warning)) { + break } - val executor = createEventExecutor(plugin, eventClass, method) - eventSet!!.add( - RegisteredListener( - listener, - executor, - eh!!.priority, - plugin, - eh!!.ignoreCancelled - ) + plugin.logger.log( + Level.WARNING, + """"%s" has registered a listener for %s on method "%s", but the event is Deprecated. "%s"; please notify the authors %s.""".format( + plugin.description.fullName, + clazz.name, + method.toGenericString(), + if (warning?.reason?.isNotEmpty() == true) warning.reason else "Server performance will be affected", + plugin.description.authors.toTypedArray().contentToString() + ), + if (warningState == Warning.WarningState.ON) { + AuthorNagException(null as String?) + } else null ) } + + val executor = createEventExecutor(eventClass, method) + result[eventClass]!!.add( + RegisteredListener( + listener, + executor, + annotation.priority, + plugin, + annotation.ignoreCancelled + ) + ) } + + return result } /** * Creates a single event executor. */ private fun createEventExecutor( - plugin: Plugin, eventClass: Class<*>, method: Method ): EventExecutor { diff --git a/mccoroutine-bukkit-core/src/test/java/helper/MockedCoroutineSession.kt b/mccoroutine-bukkit-core/src/test/java/helper/MockedCoroutineSession.kt index 14f42515..1350e57f 100644 --- a/mccoroutine-bukkit-core/src/test/java/helper/MockedCoroutineSession.kt +++ b/mccoroutine-bukkit-core/src/test/java/helper/MockedCoroutineSession.kt @@ -3,10 +3,7 @@ package helper import com.github.shynixn.mccoroutine.contract.CommandService import com.github.shynixn.mccoroutine.contract.CoroutineSession import com.github.shynixn.mccoroutine.contract.EventService -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import org.mockito.Mockito import kotlin.coroutines.CoroutineContext @@ -48,8 +45,8 @@ class MockedCoroutineSession : CoroutineSession { /** * Launches the given function on the plugin coroutine scope. */ - override fun launch(dispatcher: CoroutineContext, f: suspend kotlinx.coroutines.CoroutineScope.() -> Unit) { - GlobalScope.launch(dispatcher) { + override fun launch(dispatcher: CoroutineContext, f: suspend kotlinx.coroutines.CoroutineScope.() -> Unit) : Job { + return GlobalScope.launch(dispatcher) { f.invoke(this) } } diff --git a/mccoroutine-bukkit-sample/build.gradle.kts b/mccoroutine-bukkit-sample/build.gradle.kts index 0d080ef7..11c57568 100644 --- a/mccoroutine-bukkit-sample/build.gradle.kts +++ b/mccoroutine-bukkit-sample/build.gradle.kts @@ -15,7 +15,7 @@ tasks.withType { archiveName = "$baseName-$version.$extension" // Change the output folder of the plugin. - destinationDir = File("D:\\Benutzer\\Temp\\plugins") + // destinationDir = File("D:\\Benutzer\\Temp\\plugins") } repositories { diff --git a/mccoroutine-bukkit-sample/src/main/resources/plugin.yml b/mccoroutine-bukkit-sample/src/main/resources/plugin.yml index 7f198c5a..8756359f 100644 --- a/mccoroutine-bukkit-sample/src/main/resources/plugin.yml +++ b/mccoroutine-bukkit-sample/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: MCCoroutine-Sample -version: 0.0.4 +version: 0.0.5 author: Shynixn main: com.github.shynixn.mccoroutine.sample.MCCoroutineSamplePlugin commands: