diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 19707ba29..e82df754f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -18,7 +18,7 @@ import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole.INSTANCE import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start -import net.mamoe.mirai.console.extension.foldExtensions +import net.mamoe.mirai.console.extension.GlobalComponentStorage import net.mamoe.mirai.console.extensions.BotConfigurationAlterer import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.plugin.PluginLoader @@ -133,8 +133,10 @@ public interface MiraiConsole : CoroutineScope { configuration() } - config = BotConfigurationAlterer.foldExtensions(config) { acc, extension -> - extension.alterConfiguration(id, acc) + config = GlobalComponentStorage.run { + BotConfigurationAlterer.foldExtensions(config) { acc, extension -> + extension.alterConfiguration(id, acc) + } } return when (password) { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ComponentStorage.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ComponentStorage.kt new file mode 100644 index 000000000..e7ffcc587 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ComponentStorage.kt @@ -0,0 +1,218 @@ +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + +package net.mamoe.mirai.console.extension + +import net.mamoe.mirai.console.extensions.LazyPermissionServiceProviderImpl +import net.mamoe.mirai.console.extensions.PermissionServiceProvider +import net.mamoe.mirai.console.extensions.SingletonExtensionSelector +import net.mamoe.mirai.console.extensions.SingletonExtensionSelector.ExtensionPoint.selectSingleton +import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip +import net.mamoe.mirai.console.permission.ExperimentalPermission +import net.mamoe.mirai.console.permission.PermissionService +import net.mamoe.mirai.console.plugin.Plugin +import net.mamoe.mirai.console.plugin.name +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArraySet +import kotlin.contracts.contract +import kotlin.internal.LowPriorityInOverloadResolution +import kotlin.reflect.KClass +import kotlin.reflect.full.companionObjectInstance + +/** + * 组件容器, 容纳 [Plugin] 注册的 [Extension]. + */ +public interface ComponentStorage { + public fun contribute( + extensionPoint: ExtensionPoint, + plugin: Plugin, + extensionInstance: T, + ) + + public fun contribute( + extensionPoint: ExtensionPoint, + plugin: Plugin, + lazyInstance: () -> T, + ) +} + +@Suppress("EXPOSED_SUPER_CLASS") +public class ScopedComponentStorage( + @JvmField + internal val plugin: Plugin, +) : AbstractConcurrentComponentStorage() { + /** + * 注册一个扩展 + */ + public fun contribute( + extensionPoint: ExtensionPoint, + lazyInstance: () -> E, + ) { + contribute(extensionPoint, plugin, lazyInstance) + } + + /** + * 注册一个扩展 + */ + public inline fun contribute( + noinline lazyInstance: () -> E, + ) { + @Suppress("UNCHECKED_CAST") + (contribute( + (E::class.companionObjectInstance as? ExtensionPoint + ?: error("Companion object of ${E::class.qualifiedName} is not an ExtensionPoint")), + lazyInstance + )) + } + + /** + * 注册一个扩展 + */ + @ExperimentalPermission + public fun contributePermissionService( + lazyInstance: () -> PermissionService<*>, + ) { + contribute(PermissionServiceProvider, plugin, LazyPermissionServiceProviderImpl(lazyInstance)) + } +} + +internal object GlobalComponentStorage : AbstractConcurrentComponentStorage() + +internal interface ExtensionRegistry { + val plugin: Plugin + val extension: E + + operator fun component1(): Plugin { + return this.plugin + } + + operator fun component2(): E { + return this.extension + } +} + +internal class LazyExtensionRegistry( + override val plugin: Plugin, + initializer: () -> E, +) : ExtensionRegistry { + override val extension: E by lazy { initializer() } +} + +internal data class DataExtensionRegistry( + override val plugin: Plugin, + override val extension: E, +) : ExtensionRegistry + +internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { + @Suppress("UNCHECKED_CAST") + internal fun ExtensionPoint.getExtensions(): Set> { + return instances.getOrPut(this, ::CopyOnWriteArraySet) as Set> + } + + internal fun mergeWith(another: AbstractConcurrentComponentStorage) { + for ((ep, list) in another.instances) { + for (extensionRegistry in list) { + @Suppress("UNCHECKED_CAST") + ep as ExtensionPoint + this.contribute(ep, extensionRegistry.plugin, lazyInstance = { extensionRegistry.extension }) + } + } + } + + internal inline fun ExtensionPoint.withExtensions(block: T.() -> Unit) { + return withExtensions { _ -> block() } + } + + @LowPriorityInOverloadResolution + internal inline fun ExtensionPoint.withExtensions(block: T.(plugin: Plugin) -> Unit) { + contract { + callsInPlace(block) + } + for ((plugin, extension) in this.getExtensions()) { + kotlin.runCatching { + block.invoke(extension, plugin) + }.getOrElse { throwable -> + throwExtensionException(extension, plugin, throwable) + } + } + } + + internal inline fun > ExtensionPoint.findSingleton(builtin: E): E = + findSingleton(E::class, builtin) + + internal fun > ExtensionPoint.findSingleton(type: KClass, builtin: E): E { + val candidates = this.getExtensions() + return when (candidates.size) { + 0 -> builtin + 1 -> candidates.single().extension + else -> SingletonExtensionSelector.instance.selectSingleton(type, candidates) ?: builtin + } + } + + internal inline fun , T> ExtensionPoint.findSingletonInstance(builtin: T): T = + findSingletonInstance(E::class, builtin) + + internal fun , T> ExtensionPoint.findSingletonInstance( + type: KClass, + builtin: T, + ): T { + val candidates = this.getExtensions() + return when (candidates.size) { + 0 -> builtin + 1 -> candidates.single().extension.instance + else -> SingletonExtensionSelector.instance.selectSingleton(type, candidates)?.instance ?: builtin + } + } + + internal inline fun ExtensionPoint.foldExtensions( + initial: E, + block: (acc: E, extension: T) -> E, + ): E { + contract { + callsInPlace(block) + } + var e: E = initial + for ((plugin, extension) in this.getExtensions()) { + kotlin.runCatching { + e = block.invoke(e, extension) + }.getOrElse { throwable -> + throwExtensionException(extension, plugin, throwable) + } + } + return e + } + + internal fun ExtensionPoint.throwExtensionException( + extension: T, + plugin: Plugin, + throwable: Throwable, + ) { + throw ExtensionException( + "Exception while executing extension ${extension.kClassQualifiedNameOrTip} provided by plugin '${plugin.name}', registered for ${this.type.qualifiedName}", + throwable + ) + } + + internal inline fun ExtensionPoint.useExtensions(block: (extension: T) -> Unit): Unit = + withExtensions(block) + + @LowPriorityInOverloadResolution + internal inline fun ExtensionPoint.useExtensions(block: (extension: T, plugin: Plugin) -> Unit): Unit = + withExtensions(block) + + val instances: MutableMap, MutableSet>> = ConcurrentHashMap() + override fun contribute( + extensionPoint: ExtensionPoint, + plugin: Plugin, + extensionInstance: T, + ) { + instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(DataExtensionRegistry(plugin, extensionInstance)) + } + + override fun contribute( + extensionPoint: ExtensionPoint, + plugin: Plugin, + lazyInstance: () -> T, + ) { + instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(LazyExtensionRegistry(plugin, lazyInstance)) + } +} diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/Extension.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/Extension.kt index 0f1963954..62705a203 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/Extension.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/Extension.kt @@ -12,6 +12,7 @@ package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.extensions.PluginLoaderProvider import net.mamoe.mirai.console.extensions.SingletonExtensionSelector +import net.mamoe.mirai.console.extensions.SingletonExtensionSelector.ExtensionPoint.selectSingleton import net.mamoe.mirai.console.util.ConsoleExperimentalAPI /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt index ebb30b3ec..8a35055bb 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionPoint.kt @@ -11,16 +11,8 @@ package net.mamoe.mirai.console.extension -import net.mamoe.mirai.console.extensions.SingletonExtensionSelector -import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip -import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginLoader -import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.ConsoleExperimentalAPI -import java.util.* -import java.util.concurrent.CopyOnWriteArraySet -import kotlin.contracts.contract -import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf @@ -28,9 +20,6 @@ import kotlin.reflect.full.isSubclassOf public interface ExtensionPoint { public val type: KClass - public fun registerExtension(plugin: Plugin, extension: T) - public fun getExtensions(): Set> - public companion object { @JvmStatic @JvmSynthetic @@ -44,15 +33,7 @@ public interface ExtensionPoint { } @ConsoleExperimentalAPI -public interface SingletonExtensionPoint> : ExtensionPoint { - public companion object { - @JvmStatic - @ConsoleExperimentalAPI - public fun > SingletonExtensionPoint.findSingleton(): T? { - return SingletonExtensionSelector.selectSingleton(type, this.getExtensions()) - } - } -} +public interface SingletonExtensionPoint> : ExtensionPoint /** * 表示一个扩展点 @@ -60,29 +41,8 @@ public interface SingletonExtensionPoint> : ExtensionP @ConsoleExperimentalAPI public open class AbstractExtensionPoint( @ConsoleExperimentalAPI - public override val type: KClass -) : ExtensionPoint { - init { - @Suppress("LeakingThis") - allExtensionPoints.add(this) - } - - private val instances: MutableSet> = CopyOnWriteArraySet() - - @ConsoleExperimentalAPI - public override fun registerExtension(plugin: Plugin, extension: T) { - // require(plugin.isEnabled) { "Plugin $plugin must be enabled before registering an extension." } - requireNotNull(extension::class.qualifiedName) { "Extension must not be an anonymous object" } - instances.add(ExtensionRegistry(plugin, extension)) - } - - public override fun getExtensions(): Set> = Collections.unmodifiableSet(instances) - - internal companion object { - @ConsoleExperimentalAPI - internal val allExtensionPoints: MutableList> = mutableListOf() - } -} + public override val type: KClass, +) : ExtensionPoint /** @@ -99,58 +59,4 @@ public open class ExtensionException : RuntimeException { public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) -} - -internal inline fun AbstractExtensionPoint.withExtensions(block: T.() -> Unit) { - return withExtensions { _ -> block() } -} - -@LowPriorityInOverloadResolution -internal inline fun AbstractExtensionPoint.withExtensions(block: T.(plugin: Plugin) -> Unit) { - contract { - callsInPlace(block) - } - for ((plugin, extension) in this.getExtensions()) { - kotlin.runCatching { - block.invoke(extension, plugin) - }.getOrElse { throwable -> - throwExtensionException(extension, plugin, throwable) - } - } -} - -internal inline fun AbstractExtensionPoint.foldExtensions( - initial: E, - block: (acc: E, extension: T) -> E -): E { - contract { - callsInPlace(block) - } - var e: E = initial - for ((plugin, extension) in this.getExtensions()) { - kotlin.runCatching { - e = block.invoke(e, extension) - }.getOrElse { throwable -> - throwExtensionException(extension, plugin, throwable) - } - } - return e -} - -internal fun AbstractExtensionPoint.throwExtensionException( - extension: T, - plugin: Plugin, - throwable: Throwable -) { - throw ExtensionException( - "Exception while executing extension ${extension.kClassQualifiedNameOrTip} provided by plugin '${plugin.name}', registered for ${this.type.qualifiedName}", - throwable - ) -} - -internal inline fun AbstractExtensionPoint.useExtensions(block: (extension: T) -> Unit): Unit = - withExtensions(block) - -@LowPriorityInOverloadResolution -internal inline fun AbstractExtensionPoint.useExtensions(block: (extension: T, plugin: Plugin) -> Unit): Unit = - withExtensions(block) \ No newline at end of file +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionRegistry.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionRegistry.kt deleted file mode 100644 index d79b1cdf3..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extension/ExtensionRegistry.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.console.extension - -import net.mamoe.mirai.console.plugin.Plugin -import net.mamoe.mirai.console.util.ConsoleExperimentalAPI - -@ConsoleExperimentalAPI -public data class ExtensionRegistry( - public val plugin: Plugin, - public val extension: T -) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PermissionServiceProvider.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PermissionServiceProvider.kt index 8a7a415e3..f0a8308c2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PermissionServiceProvider.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PermissionServiceProvider.kt @@ -5,18 +5,23 @@ import net.mamoe.mirai.console.extension.SingletonExtension import net.mamoe.mirai.console.extension.SingletonExtensionPoint import net.mamoe.mirai.console.permission.ExperimentalPermission import net.mamoe.mirai.console.permission.PermissionService -import net.mamoe.mirai.console.plugin.description.PluginLoadPriority /** * [权限服务][PermissionService] 提供器. * * 当插件注册 [PermissionService] 后, 默认会使用插件的 [PermissionService]. - * - * 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 和 [PluginLoadPriority.ON_EXTENSIONS] 插件提供 */ @ExperimentalPermission public interface PermissionServiceProvider : SingletonExtension> { public companion object ExtensionPoint : AbstractExtensionPoint(PermissionServiceProvider::class), SingletonExtensionPoint +} + +@ExperimentalPermission +public class PermissionServiceProviderImpl(override val instance: PermissionService<*>) : PermissionServiceProvider + +@ExperimentalPermission +public class LazyPermissionServiceProviderImpl(initializer: () -> PermissionService<*>) : PermissionServiceProvider { + override val instance: PermissionService<*> by lazy(initializer) } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt index 0905229a5..cf2271981 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt @@ -3,12 +3,9 @@ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.plugin.PluginLoader -import net.mamoe.mirai.console.plugin.description.PluginLoadPriority /** * 提供扩展 [PluginLoader] - * - * 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 插件提供 */ public interface PluginLoaderProvider : InstanceExtension> { public companion object ExtensionPoint : AbstractExtensionPoint(PluginLoaderProvider::class) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/SingletonExtensionSelector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/SingletonExtensionSelector.kt index fdaf4a2c5..49adf4239 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/SingletonExtensionSelector.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/SingletonExtensionSelector.kt @@ -12,8 +12,9 @@ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.extension.* import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector -import net.mamoe.mirai.console.plugin.description.PluginLoadPriority +import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.name +import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.utils.info import kotlin.reflect.KClass @@ -21,21 +22,33 @@ import kotlin.reflect.KClass * 用于同时拥有多个 [SingletonExtension] 时选择一个实例. * * 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器. - * - * 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 插件提供 */ +@ConsoleExperimentalAPI public interface SingletonExtensionSelector : FunctionExtension { + public data class Registry( + val plugin: Plugin, + val extension: T, + ) + /** + * @return null 表示使用 builtin + */ public fun selectSingleton( extensionType: KClass, - candidates: Collection> + candidates: Collection>, ): T? public companion object ExtensionPoint : AbstractExtensionPoint(SingletonExtensionSelector::class) { - internal val instance: SingletonExtensionSelector by lazy { - val instances = SingletonExtensionSelector.getExtensions() - when { + + private var instanceField: SingletonExtensionSelector? = null + + internal val instance: SingletonExtensionSelector get() = instanceField ?: error("") + + internal fun init() { + check(instanceField == null) { "Internal error: reinitialize SingletonExtensionSelector" } + val instances = GlobalComponentStorage.run { SingletonExtensionSelector.getExtensions() } + instanceField = when { instances.isEmpty() -> BuiltInSingletonExtensionSelector instances.size == 1 -> { instances.single().also { (plugin, ext) -> @@ -50,8 +63,14 @@ public interface SingletonExtensionSelector : FunctionExtension { internal fun selectSingleton( extensionType: KClass, - candidates: Collection> + candidates: Collection>, ): T? = - instance.selectSingleton(extensionType, candidates) + instance.selectSingleton(extensionType, candidates.map { Registry(it.plugin, it.extension) }) + + + internal fun SingletonExtensionSelector.selectSingleton( + extensionType: KClass, + candidates: Collection>, + ): T? = selectSingleton(extensionType, candidates.map { Registry(it.plugin, it.extension) }) } } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index 8f7c529c9..a15c3955c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -27,12 +27,14 @@ import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.data.PluginDataStorage -import net.mamoe.mirai.console.extension.useExtensions +import net.mamoe.mirai.console.extension.GlobalComponentStorage +import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.extensions.PostStartupExtension import net.mamoe.mirai.console.extensions.SingletonExtensionSelector import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope +import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.util.autoHexToBytes @@ -44,6 +46,7 @@ import net.mamoe.mirai.console.permission.RootPermission import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter +import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.utils.* @@ -124,35 +127,48 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI ConsoleDataScope.reloadAll() } - val pluginLoadSession: PluginManagerImpl.PluginLoadSession - - phase `load BEFORE_EXTENSIONS plugins`@{ + phase `initialize all plugins`@{ PluginManager // init - mainLogger.verbose { "Loading PluginLoader provider plugins..." } - PluginManagerImpl.loadEnablePluginProviderPlugins() - mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." } + mainLogger.verbose { "Loading JVM plugins..." } + PluginManagerImpl.loadAllPluginsUsingBuiltInLoaders() + PluginManagerImpl.initExternalPluginLoaders().let { count -> + mainLogger.verbose { "$count external PluginLoader(s) found. " } + if (count != 0) { + mainLogger.verbose { "Loading external plugins..." } + } + } + } + + phase `load all plugins`@{ + PluginManagerImpl.loadPlugins(PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider()) + + mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." } + } + + phase `collect extensions`@{ + for (resolvedPlugin in PluginManagerImpl.resolvedPlugins) { + resolvedPlugin.castOrNull()?.let { + GlobalComponentStorage.mergeWith(it.componentStorage) + } + } } phase `load SingletonExtensionSelector`@{ + SingletonExtensionSelector.init() val instance = SingletonExtensionSelector.instance if (instance is BuiltInSingletonExtensionSelector) { ConsoleDataScope.addAndReloadConfig(instance.config) } } - phase `load ON_EXTENSIONS plugins`@{ - mainLogger.verbose { "Scanning high-priority extension and normal plugins..." } - pluginLoadSession = PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider() - mainLogger.verbose { "${pluginLoadSession.allKindsOfPlugins.size} plugin(s) found." } - - mainLogger.verbose { "Loading Extension provider plugins..." } - PluginManagerImpl.loadEnableHighPriorityExtensionPlugins(pluginLoadSession) - mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." } - } - phase `load PermissionService`@{ mainLogger.verbose { "Loading PermissionService..." } + + PermissionService.instanceField = GlobalComponentStorage.run { + PermissionServiceProvider.findSingletonInstance(BuiltInPermissionService) + } + PermissionService.INSTANCE.let { ps -> if (ps is BuiltInPermissionService) { ConsoleDataScope.addAndReloadConfig(ps.config) @@ -171,13 +187,13 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI CommandManagerImpl.commandListener // start } - phase `load AFTER_EXTENSION plugins`@{ - mainLogger.verbose { "Loading normal plugins..." } - val count = PluginManagerImpl.loadEnableNormalPlugins(pluginLoadSession) - mainLogger.verbose { "$count normal plugin(s) loaded." } - } + phase `enable plugins`@{ + mainLogger.verbose { "Enabling plugins..." } - mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) loaded." } + PluginManagerImpl.enableAllLoadedPlugins() + + mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) enabled." } + } phase `auto-login bots`@{ runBlocking { @@ -198,7 +214,9 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI } } - PostStartupExtension.useExtensions { it() } + GlobalComponentStorage.run { + PostStartupExtension.useExtensions { it() } + } mainLogger.info { "mirai-console started successfully." } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extensions/BuiltInSingletonExtensionSelector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extensions/BuiltInSingletonExtensionSelector.kt index 8621e0c01..5308ba4a3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extensions/BuiltInSingletonExtensionSelector.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/extensions/BuiltInSingletonExtensionSelector.kt @@ -5,7 +5,6 @@ import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.extension.Extension -import net.mamoe.mirai.console.extension.ExtensionRegistry import net.mamoe.mirai.console.extensions.SingletonExtensionSelector import net.mamoe.mirai.console.internal.data.kClassQualifiedName import net.mamoe.mirai.console.plugin.name @@ -25,7 +24,7 @@ internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector { override fun selectSingleton( extensionType: KClass, - candidates: Collection> + candidates: Collection>, ): T? = when { candidates.isEmpty() -> null candidates.size == 1 -> candidates.single().extension @@ -42,14 +41,14 @@ internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector { private fun promptForSelectionAndSave( extensionType: KClass, - candidates: Collection> + candidates: Collection>, ) = promptForManualSelection(extensionType, candidates) .also { config.value[extensionType.qualifiedName!!] = it.extension.kClassQualifiedName!! }.extension - private fun promptForManualSelection( + private fun promptForManualSelection( extensionType: KClass, - candidates: Collection> - ): ExtensionRegistry { + candidates: Collection>, + ): SingletonExtensionSelector.Registry { MiraiConsole.mainLogger.info { "There are multiple ${extensionType.simpleName}s, please select one:" } val candidatesList = candidates.toList() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt index fbeb7d048..c1432d4a4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt @@ -84,10 +84,10 @@ internal object JarPluginLoaderImpl : @Throws(PluginLoadException::class) override fun load(plugin: JvmPlugin) { ensureActive() + runCatching { - if (plugin is JvmPluginInternal) { - plugin.internalOnLoad() - } else plugin.onLoad() + check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin" } + plugin.internalOnLoad(plugin.componentStorage) }.getOrElse { throw PluginLoadException("Exception while loading ${plugin.description.name}", it) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt index 80129ef53..b0afc3348 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt @@ -14,6 +14,7 @@ import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.runCatchingLog +import net.mamoe.mirai.console.extension.ScopedComponentStorage import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.permission.ExperimentalPermission import net.mamoe.mirai.console.permission.Permission @@ -24,7 +25,9 @@ import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer +import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.NamedSupervisorJob import net.mamoe.mirai.utils.MiraiLogger @@ -41,9 +44,12 @@ internal val T.job: Job where T : CoroutineScope, T : Plugin get() = this.co */ @PublishedApi internal abstract class JvmPluginInternal( - parentCoroutineContext: CoroutineContext + parentCoroutineContext: CoroutineContext, ) : JvmPlugin, CoroutineScope { + @Suppress("LeakingThis") + internal val componentStorage: ScopedComponentStorage = ScopedComponentStorage(this) + @OptIn(ExperimentalPermission::class) final override val parentPermission: Permission by lazy { PermissionService.INSTANCE.register( @@ -106,8 +112,8 @@ internal abstract class JvmPluginInternal( } @Throws(Throwable::class) - internal fun internalOnLoad() { // propagate exceptions - onLoad() + internal fun internalOnLoad(componentStorage: ScopedComponentStorage) { + onLoad(componentStorage) } internal fun internalOnEnable(): Boolean { @@ -135,6 +141,7 @@ internal abstract class JvmPluginInternal( // for future use @Suppress("PropertyName") internal val _intrinsicCoroutineContext: CoroutineContext by lazy { + this as AbstractJvmPlugin CoroutineName("Plugin $dataHolderName") } @@ -149,7 +156,7 @@ internal abstract class JvmPluginInternal( .plus(parentCoroutineContext) .plus( NamedSupervisorJob( - "Plugin $dataHolderName", + "Plugin ${(this as AbstractJvmPlugin).dataHolderName}", parentCoroutineContext[Job] ?: JarPluginLoaderImpl.coroutineContext[Job]!! ) ) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt index 160df275a..8694bb37b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt @@ -11,24 +11,22 @@ package net.mamoe.mirai.console.internal.plugin -import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.extension.useExtensions +import net.mamoe.mirai.console.extension.GlobalComponentStorage import net.mamoe.mirai.console.extensions.PluginLoaderProvider import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.plugin.* import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription -import net.mamoe.mirai.console.plugin.description.PluginLoadPriority import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.utils.info import java.io.File import java.nio.file.Path -import java.util.concurrent.locks.ReentrantLock +import java.util.concurrent.CopyOnWriteArrayList internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsole.childScope("PluginManager") { @@ -43,11 +41,11 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol private val _pluginLoaders: MutableList> by lazy { MiraiConsole.builtInPluginLoaders.toMutableList() } - private val loadersLock: ReentrantLock = ReentrantLock() private val logger = MiraiConsole.createLogger("plugin") @JvmField - internal val resolvedPlugins: MutableList = mutableListOf() + internal val resolvedPlugins: MutableList = + CopyOnWriteArrayList() // write operations are mostly performed on init override val plugins: List get() = resolvedPlugins.toList() override val builtInLoaders: List> @@ -64,17 +62,6 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol ?: error("Plugin is unloaded") - override fun PluginLoader<*, *>.register(): Boolean = loadersLock.withLock { - if (_pluginLoaders.any { it::class == this::class }) { - return false - } - _pluginLoaders.add(this) - } - - override fun PluginLoader<*, *>.unregister() = loadersLock.withLock { - _pluginLoaders.remove(this) - } - init { MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion { plugins.forEach { it.disable() } @@ -114,59 +101,65 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol } internal class PluginLoadSession( - val allKindsOfPlugins: List, List>> + val allKindsOfPlugins: List, ) - // Phase #2 - internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession { - return PluginLoadSession(loadersLock.withLock { _pluginLoaders.listAllPlugins() }) + /////////////////////////////////////////////////////////////////////////// + // Phase #0: + // - initialize all plugins using builtin loaders + // - sort by dependencies + /////////////////////////////////////////////////////////////////////////// + + /** + * 使用 [builtInLoaders] 寻找所有插件, 并初始化其主类. + */ + @Suppress("UNCHECKED_CAST") + @Throws(PluginMissingDependencyException::class) + private fun findAndSortAllPluginsUsingBuiltInLoaders(): List { + val allDescriptions = + builtInLoaders.listAndSortAllPlugins() + .asSequence() + .onEach { (_, descriptions) -> + descriptions.let(PluginManagerImpl::checkPluginDescription) + } + + return allDescriptions.toList().sortByDependencies() } - // Phase #0 - internal fun loadEnablePluginProviderPlugins() { - loadAndEnableLoaderProvidersUsingBuiltInLoaders() - } - - // Phase #3 - internal fun loadEnableHighPriorityExtensionPlugins(session: PluginLoadSession): Int { - loadersLock.withLock { - session.allKindsOfPlugins.flatMap { it.second } - .filter { it.loadPriority == PluginLoadPriority.ON_EXTENSIONS } - .sortByDependencies() - .also { it.loadAndEnableAllInOrder() } - .let { return it.size } + internal fun loadAllPluginsUsingBuiltInLoaders() { + for ((l, _, p) in findAndSortAllPluginsUsingBuiltInLoaders()) { + l.load(p) } } - // Phase #4 - internal fun loadEnableNormalPlugins(session: PluginLoadSession): Int { - loadersLock.withLock { - session.allKindsOfPlugins.flatMap { it.second } - .filter { it.loadPriority == PluginLoadPriority.AFTER_EXTENSIONS } - .sortByDependencies() - .also { it.loadAndEnableAllInOrder() } - .let { return it.size } - } - } + /////////////////////////////////////////////////////////////////////////// + // Phase #1: + // - load PluginLoaderProvider + /////////////////////////////////////////////////////////////////////////// - // Phase #1 - internal fun loadPluginLoaderProvidedByPlugins() { - loadersLock.withLock { + internal fun initExternalPluginLoaders(): Int { + var count = 0 + GlobalComponentStorage.run { PluginLoaderProvider.useExtensions { ext, plugin -> logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin.name}" } _pluginLoaders.add(ext.instance) + count++ } } + return count } + // Phase #2 + internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession { + return PluginLoadSession(_pluginLoaders.filterNot { builtInLoaders.contains(it) }.listAndSortAllPlugins()) + } - private fun List.loadAndEnableAllInOrder() { - this.forEach { (loader, _, plugin) -> - loader.loadPluginNoEnable(plugin) - } - this.forEach { (loader, _, plugin) -> - loader.enablePlugin(plugin) - } + internal fun loadPlugins(session: PluginLoadSession) { + session.allKindsOfPlugins.forEach { it.loader.load(it.plugin) } + } + + internal fun enableAllLoadedPlugins() { + resolvedPlugins.forEach { it.enable() } } @kotlin.jvm.Throws(PluginLoadException::class) @@ -178,31 +171,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol } } - /** - * @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable] - */ - @Suppress("UNCHECKED_CAST") - @Throws(PluginMissingDependencyException::class) - private fun loadAndEnableLoaderProvidersUsingBuiltInLoaders(): List { - val allDescriptions = - builtInLoaders.listAllPlugins() - .asSequence() - .onEach { (loader, descriptions) -> - loader as PluginLoader - - descriptions.forEach(PluginManagerImpl::checkPluginDescription) - descriptions.filter { it.loadPriority == PluginLoadPriority.BEFORE_EXTENSIONS }.sortByDependencies() - .loadAndEnableAllInOrder() - } - .flatMap { it.second.asSequence() } - - return allDescriptions.toList() - } - - private fun List>.listAllPlugins(): List, List>> { - return associateWith { loader -> + private fun List>.listAndSortAllPlugins(): List { + return flatMap { loader -> loader.listPlugins().map { plugin -> plugin.description.wrapWith(loader, plugin) } - }.toList() + }.sortByDependencies() } @Throws(PluginMissingDependencyException::class) @@ -220,7 +192,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol fun Collection.filterIsMissing(): List = this.filterNot { it.isOptional || it in resolved } - tailrec fun List.doSort() { + fun List.doSort() { if (this.isEmpty()) return val beforeSize = this.size @@ -239,14 +211,12 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol this.doSort() return resolved } - - // endregion } internal data class PluginDescriptionWithLoader( @JvmField val loader: PluginLoader, // easier type @JvmField val delegate: PluginDescription, - @JvmField val plugin: Plugin + @JvmField val plugin: Plugin, ) : PluginDescription by delegate @Suppress("UNCHECKED_CAST") diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt index 0b935e6f5..cef6b07de 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/permission/PermissionService.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.console.permission -import net.mamoe.mirai.console.extension.SingletonExtensionPoint.Companion.findSingleton import net.mamoe.mirai.console.extensions.PermissionServiceProvider import kotlin.reflect.KClass import kotlin.reflect.full.isSuperclassOf @@ -47,7 +46,7 @@ public interface PermissionService

{ public fun register( id: PermissionId, description: String, - parent: Permission = RootPermission + parent: Permission = RootPermission, ): P /////////////////////////////////////////////////////////////////////////// @@ -56,11 +55,12 @@ public interface PermissionService

{ public fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P) public companion object { + internal var instanceField: PermissionService<*>? = null + @get:JvmName("getInstance") @JvmStatic - public val INSTANCE: PermissionService by lazy { - PermissionServiceProvider.findSingleton()?.instance ?: BuiltInPermissionService - } + public val INSTANCE: PermissionService + get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.") public fun

PermissionService

.getOrFail(id: PermissionId): P = get(id) ?: throw PermissionNotFoundException(id) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt index 17155dcd3..d0f95888c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt @@ -18,7 +18,6 @@ import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription -import net.mamoe.mirai.console.plugin.description.PluginLoadPriority import net.mamoe.mirai.console.plugin.jvm.JvmPlugin /** @@ -64,11 +63,6 @@ public inline val Plugin.name: String get() = this.description.name */ public inline val Plugin.version: Semver get() = this.description.version -/** - * 获取 [PluginDescription.loadPriority] - */ -public inline val Plugin.loadPriority: PluginLoadPriority get() = this.description.loadPriority - /** * 获取 [PluginDescription.info] */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt index f83a5f12e..141cff9ad 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt @@ -13,7 +13,6 @@ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable -import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.register import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader import java.io.File @@ -31,14 +30,11 @@ import java.util.* * * ## 扩展加载器 * 插件被允许扩展一个加载器. - * Console 使用 [ServiceLoader] 加载 [PluginLoader] 的实例. - * 插件也可通过 [PluginManager.register] 手动注册, 然而这是不推荐的. * * ### 实现扩展加载器 * 直接实现接口 [PluginLoader] 或 [FilePluginLoader], 并添加 [ServiceLoader] 相关资源文件即可. * * @see JarPluginLoader Jar 插件加载器 - * @see PluginManager.register 注册一个扩展的插件加载器 */ public interface PluginLoader

{ /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt index 9f7820129..6b8970099 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt @@ -14,10 +14,8 @@ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.plugin.description.PluginDescription -import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import java.io.File import java.nio.file.Path -import java.util.* /** * 插件管理器. @@ -103,22 +101,6 @@ public interface PluginManager { */ public val pluginLoaders: List> - /** - * 手动注册一个扩展的插件加载器. 在启动时会通过 [ServiceLoader] 加载, 但也可以手动注册. - * - * @see PluginLoader 插件加载器 - */ - @ConsoleExperimentalAPI - public fun PluginLoader<*, *>.register(): Boolean - - /** - * 取消注册一个扩展的插件加载器 - * - * @see PluginLoader 插件加载器 - */ - @ConsoleExperimentalAPI - public fun PluginLoader<*, *>.unregister(): Boolean - /** * 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getDescription] */ @@ -158,8 +140,6 @@ public interface PluginManager { public companion object INSTANCE : PluginManager by PluginManagerImpl { // due to Kotlin's bug public override val Plugin.description: PluginDescription get() = PluginManagerImpl.run { description } - public override fun PluginLoader<*, *>.register(): Boolean = PluginManagerImpl.run { register() } - public override fun PluginLoader<*, *>.unregister(): Boolean = PluginManagerImpl.run { unregister() } public override fun Plugin.disable(): Unit = PluginManagerImpl.run { disable() } public override fun Plugin.enable(): Unit = PluginManagerImpl.run { enable() } public override fun Plugin.load(): Unit = PluginManagerImpl.run { load() } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt index c11f4d565..301c04b97 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt @@ -20,13 +20,6 @@ import net.mamoe.mirai.console.plugin.PluginLoadException * @see Plugin */ public interface PluginDescription { - /** - * 插件类型. 将会决定加载顺序 - * - * @see PluginLoadPriority - */ - public val loadPriority: PluginLoadPriority - /** * 插件 ID, 必须全英文, 仅允许英文字母, '-', '_', '.'. * diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginLoadPriority.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginLoadPriority.kt deleted file mode 100644 index 8f49d63e8..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginLoadPriority.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019-2020 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. - * - * https://github.com/mamoe/mirai/blob/master/LICENSE - */ - -package net.mamoe.mirai.console.plugin.description - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.serializer -import net.mamoe.mirai.console.extension.Extension -import net.mamoe.mirai.console.extensions.BotConfigurationAlterer -import net.mamoe.mirai.console.extensions.PermissionServiceProvider -import net.mamoe.mirai.console.extensions.PluginLoaderProvider -import net.mamoe.mirai.console.extensions.SingletonExtensionSelector -import net.mamoe.mirai.console.internal.data.map -import net.mamoe.mirai.console.plugin.description.PluginLoadPriority.* - -/** - * 插件类型. - * - * 插件类型将影响加载顺序: [BEFORE_EXTENSIONS] -> [ON_EXTENSIONS] -> [AFTER_EXTENSIONS]. - * - * 依赖解决过程与插件类型有很大关联. 在一个较早的阶段, 只会解决在此阶段加载的插件. 意味着 [BEFORE_EXTENSIONS] 不允许依赖一个 [AFTER_EXTENSIONS] 类型的插件. - */ -public enum class PluginLoadPriority { - /** - * 表示此插件最早被加载. 在 Console 启动时的第一初始化阶段就会加载这些插件. - * - * 一般只有提供 [PluginLoaderProvider] 或 [SingletonExtensionSelector] 的插件才需要在此阶段加载. - */ - BEFORE_EXTENSIONS, - - /** - * 表示此插件提供一些高优先级的 [Extension], 应在加载其他 [AFTER_EXTENSIONS] 类型插件前加载 - * - * 高优先级的 [Extension] 通常是覆盖 Console 内置的部分服务的扩展. 如 [PermissionServiceProvider]. - * - * 一些普通的 [Extension], 如 [BotConfigurationAlterer], 也可以使用 [AFTER_EXTENSIONS] 类型插件注册. - */ - ON_EXTENSIONS, - - /** - * 表示此插件为一个通常的插件, 在扩展处理完毕后加载. - */ - AFTER_EXTENSIONS; - - public object AsStringSerializer : KSerializer by String.serializer().map( - serializer = { it.name }, - deserializer = { str -> - values().firstOrNull { - it.name.equals(str, ignoreCase = true) - } ?: AFTER_EXTENSIONS - } - ) -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt index 3d3c51659..2fd32ea9f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt @@ -7,10 +7,13 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS") +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE") package net.mamoe.mirai.console.plugin.jvm +import net.mamoe.mirai.console.data.AutoSavePluginDataHolder +import net.mamoe.mirai.console.data.PluginConfig +import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.utils.minutesToMillis @@ -19,18 +22,52 @@ import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** - * [JavaPlugin] 和 [KotlinPlugin] 的父类 + * [JavaPlugin] 和 [KotlinPlugin] 的父类. 所有 [JvmPlugin] 都应该拥有此类作为直接或间接父类. * * @see JavaPlugin * @see KotlinPlugin */ public abstract class AbstractJvmPlugin @JvmOverloads constructor( - parentCoroutineContext: CoroutineContext = EmptyCoroutineContext -) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) { + parentCoroutineContext: CoroutineContext = EmptyCoroutineContext, +) : JvmPlugin, JvmPluginInternal(parentCoroutineContext), AutoSavePluginDataHolder { @ConsoleExperimentalAPI public final override val dataHolderName: String get() = this.description.name + public final override val loader: JarPluginLoader get() = super.loader + + /** + * 重载 [PluginData] + * + * @see reloadPluginData + */ + @JvmName("reloadPluginData") + public fun T.reload(): Unit = loader.dataStorage.load(this@AbstractJvmPlugin, this) + + /** + * 重载 [PluginConfig] + * + * @see reloadPluginConfig + */ + @JvmName("reloadPluginConfig") + public fun T.reload(): Unit = loader.configStorage.load(this@AbstractJvmPlugin, this) + @ConsoleExperimentalAPI public override val autoSaveIntervalMillis: LongRange = 30.secondsToMillis..10.minutesToMillis -} \ No newline at end of file +} + +/** + * 重载一个 [PluginData] + * + * @see AbstractJvmPlugin.reload + */ +@JvmSynthetic +public inline fun AbstractJvmPlugin.reloadPluginData(instance: PluginData): Unit = this.run { instance.reload() } + +/** + * 重载一个 [PluginConfig] + * + * @see AbstractJvmPlugin.reload + */ +@JvmSynthetic +public inline fun AbstractJvmPlugin.reloadPluginConfig(instance: PluginConfig): Unit = this.run { instance.reload() } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt index b6ce680a7..bfb986340 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt @@ -18,23 +18,18 @@ package net.mamoe.mirai.console.plugin.jvm import kotlinx.coroutines.CoroutineScope -import net.mamoe.mirai.console.data.AutoSavePluginDataHolder -import net.mamoe.mirai.console.data.PluginConfig -import net.mamoe.mirai.console.data.PluginData +import net.mamoe.mirai.console.extension.ScopedComponentStorage import net.mamoe.mirai.console.permission.ExperimentalPermission import net.mamoe.mirai.console.permission.PermissionIdNamespace import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginFileExtensions import net.mamoe.mirai.console.plugin.ResourceContainer -import net.mamoe.mirai.console.plugin.getDescription import net.mamoe.mirai.utils.MiraiLogger /** * Java, Kotlin 或其他 JVM 平台插件 * - * 有关 [JvmPlugin] 相关实现方法,请参考 - * * @see AbstractJvmPlugin 默认实现 * * @see JavaPlugin Java 插件 @@ -45,71 +40,39 @@ import net.mamoe.mirai.utils.MiraiLogger */ @OptIn(ExperimentalPermission::class) public interface JvmPlugin : Plugin, CoroutineScope, - PluginFileExtensions, ResourceContainer, AutoSavePluginDataHolder, PermissionIdNamespace { + PluginFileExtensions, ResourceContainer, PermissionIdNamespace { /** 日志 */ public val logger: MiraiLogger /** 插件描述 */ - public val description: JvmPluginDescription get() = loader.getDescription(this) + public val description: JvmPluginDescription /** 所属插件加载器实例 */ - @JvmDefault - public override val loader: JarPluginLoader - get() = JarPluginLoader - - /** - * 重载 [PluginData] - * - * @see reloadPluginData - */ - @JvmDefault - @JvmName("reloadPluginData") - public fun T.reload(): Unit = loader.dataStorage.load(this@JvmPlugin, this) - - /** - * 重载 [PluginConfig] - * - * @see reloadPluginConfig - */ - @JvmDefault - @JvmName("reloadPluginConfig") - public fun T.reload(): Unit = loader.configStorage.load(this@JvmPlugin, this) + // `final` in AbstractJvmPlugin + public override val loader: JarPluginLoader get() = JarPluginLoader /** * 在插件被加载时调用. 只会被调用一次. + * + * 在 [onLoad] 时可注册扩展 [ScopedComponentStorage.contribute] + * + * @receiver 组件容器 */ - @JvmDefault - public fun onLoad() { - } + public fun @ParameterName("storage") ScopedComponentStorage.onLoad() {} /** * 在插件被启用时调用, 可能会被调用多次 */ - @JvmDefault - public fun onEnable() { - } + public fun onEnable() {} /** * 在插件被关闭时调用, 可能会被调用多次 */ - @JvmDefault - public fun onDisable() { + public fun onDisable() {} + + public companion object { + @JvmSynthetic + public inline fun JvmPlugin.onLoad(storage: ScopedComponentStorage): Unit = storage.onLoad() } -} - -/** - * 重载一个 [PluginData] - * - * @see JvmPlugin.reload - */ -@JvmSynthetic -public inline fun JvmPlugin.reloadPluginData(instance: PluginData): Unit = this.run { instance.reload() } - -/** - * 重载一个 [PluginConfig] - * - * @see JvmPlugin.reload - */ -@JvmSynthetic -public inline fun JvmPlugin.reloadPluginConfig(instance: PluginConfig): Unit = this.run { instance.reload() } \ No newline at end of file +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt index 64be7d2a9..30f366424 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt @@ -14,7 +14,6 @@ package net.mamoe.mirai.console.plugin.jvm import com.vdurmont.semver4j.Semver import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription -import net.mamoe.mirai.console.plugin.description.PluginLoadPriority import kotlin.internal.LowPriorityInOverloadResolution /** @@ -22,6 +21,8 @@ import kotlin.internal.LowPriorityInOverloadResolution * * 请不要自行实现 [JvmPluginDescription] 接口. 它不具有继承稳定性. * + * 要查看相关约束, 参考 [PluginDescription] + * * @see SimpleJvmPluginDescription * @see JvmPluginDescriptionBuilder */ @@ -82,7 +83,6 @@ public class JvmPluginDescriptionBuilder( private var author: String = "" private var info: String = "" private var dependencies: MutableSet = mutableSetOf() - private var loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS @ILoveKuriyamaMiraiForever public fun name(value: String): JvmPluginDescriptionBuilder = apply { this.name = value.trim() } @@ -103,21 +103,6 @@ public class JvmPluginDescriptionBuilder( @ILoveKuriyamaMiraiForever public fun info(value: String): JvmPluginDescriptionBuilder = apply { this.info = value.trimIndent() } - @ILoveKuriyamaMiraiForever - public fun kind(value: PluginLoadPriority): JvmPluginDescriptionBuilder = apply { this.loadPriority = value } - - @ILoveKuriyamaMiraiForever - public fun normalPlugin(): JvmPluginDescriptionBuilder = - apply { this.loadPriority = PluginLoadPriority.AFTER_EXTENSIONS } - - @ILoveKuriyamaMiraiForever - public fun loaderProviderPlugin(): JvmPluginDescriptionBuilder = - apply { this.loadPriority = PluginLoadPriority.BEFORE_EXTENSIONS } - - @ILoveKuriyamaMiraiForever - public fun highPriorityExtensionsPlugin(): JvmPluginDescriptionBuilder = - apply { this.loadPriority = PluginLoadPriority.ON_EXTENSIONS } - @ILoveKuriyamaMiraiForever public fun dependsOn( pluginId: String, @@ -154,7 +139,7 @@ public class JvmPluginDescriptionBuilder( @Suppress("DEPRECATION_ERROR") public fun build(): JvmPluginDescription = - SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority) + SimpleJvmPluginDescription(name, version, id, author, info, dependencies) @Retention(AnnotationRetention.SOURCE) @DslMarker @@ -194,7 +179,6 @@ public data class SimpleJvmPluginDescription public override val author: String = "", public override val info: String = "", public override val dependencies: Set = setOf(), - public override val loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS, ) : JvmPluginDescription { @Deprecated( @@ -216,8 +200,7 @@ public data class SimpleJvmPluginDescription author: String = "", info: String = "", dependencies: Set = setOf(), - loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS, - ) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies, loadPriority) + ) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies) init { require(!name.contains(':')) { "':' is forbidden in plugin name" } @@ -242,8 +225,7 @@ public fun JvmPluginDescription( author: String = "", info: String = "", dependencies: Set = setOf(), - loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS -): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority) +): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies) @Deprecated( "JvmPluginDescription 没有构造器. 请使用 SimpleJvmPluginDescription.", @@ -262,5 +244,4 @@ public fun JvmPluginDescription( author: String = "", info: String = "", dependencies: Set = setOf(), - loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS -): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority) +): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies)