mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
Structured init phases; Load plugins and init services in order
This commit is contained in:
parent
102f359a11
commit
e1f8125163
@ -46,6 +46,8 @@ import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
@ -84,59 +86,109 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity)
|
||||
|
||||
@OptIn(ConsoleExperimentalAPI::class, ExperimentalPermission::class)
|
||||
@Suppress("RemoveRedundantBackticks")
|
||||
internal fun doStart() {
|
||||
val buildDateFormatted =
|
||||
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
mainLogger.info { "Starting mirai-console..." }
|
||||
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
|
||||
mainLogger.info { frontEndDescription.render() }
|
||||
phase `greeting`@{
|
||||
val buildDateFormatted =
|
||||
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
|
||||
if (coroutineContext[Job] == null) {
|
||||
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
|
||||
}
|
||||
if (coroutineContext[CoroutineExceptionHandler] == null) {
|
||||
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.")
|
||||
mainLogger.info { "Starting mirai-console..." }
|
||||
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
|
||||
mainLogger.info { frontEndDescription.render() }
|
||||
}
|
||||
|
||||
MiraiConsole.job.invokeOnCompletion {
|
||||
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
|
||||
}
|
||||
phase `check coroutineContext`@{
|
||||
if (coroutineContext[Job] == null) {
|
||||
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
|
||||
}
|
||||
if (coroutineContext[CoroutineExceptionHandler] == null) {
|
||||
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.")
|
||||
}
|
||||
|
||||
mainLogger.verbose { "Loading configurations..." }
|
||||
ConsoleDataScope.reloadAll()
|
||||
|
||||
mainLogger.verbose { "Loading PermissionService..." }
|
||||
PermissionService.INSTANCE.let { ps ->
|
||||
if (ps is StorablePermissionService<*>) {
|
||||
ConsoleDataScope.addAndReloadConfig(ps.config)
|
||||
mainLogger.verbose { "Reloaded PermissionService settings." }
|
||||
MiraiConsole.job.invokeOnCompletion {
|
||||
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
|
||||
}
|
||||
}
|
||||
|
||||
mainLogger.verbose { "Loading built-in commands..." }
|
||||
BuiltInCommands.registerAll()
|
||||
mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
|
||||
CommandManager
|
||||
CommandManagerImpl.commandListener // start
|
||||
// start
|
||||
|
||||
mainLogger.verbose { "Loading plugins..." }
|
||||
PluginManager
|
||||
PluginManagerImpl.loadEnablePlugins()
|
||||
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." }
|
||||
mainLogger.info { "mirai-console started successfully." }
|
||||
phase `load configurations`@{
|
||||
mainLogger.verbose { "Loading configurations..." }
|
||||
ConsoleDataScope.reloadAll()
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
for ((id, password) in AutoLoginConfig.plainPasswords) {
|
||||
mainLogger.info { "Auto-login $id" }
|
||||
MiraiConsole.addBot(id, password).alsoLogin()
|
||||
val pluginLoadSession: PluginManagerImpl.PluginLoadSession
|
||||
|
||||
phase `load plugins`@{
|
||||
PluginManager // init
|
||||
|
||||
mainLogger.verbose { "Loading PluginLoader provider plugins..." }
|
||||
PluginManagerImpl.loadEnablePluginProviderPlugins()
|
||||
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
|
||||
|
||||
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.INSTANCE.let { ps ->
|
||||
if (ps is StorablePermissionService<*>) {
|
||||
ConsoleDataScope.addAndReloadConfig(ps.config)
|
||||
mainLogger.verbose { "Reloaded PermissionService settings." }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((id, password) in AutoLoginConfig.md5Passwords) {
|
||||
mainLogger.info { "Auto-login $id" }
|
||||
MiraiConsole.addBot(id, password).alsoLogin()
|
||||
phase `prepare commands`@{
|
||||
mainLogger.verbose { "Loading built-in commands..." }
|
||||
BuiltInCommands.registerAll()
|
||||
mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
|
||||
CommandManager
|
||||
CommandManagerImpl.commandListener // start
|
||||
}
|
||||
|
||||
phase `load normal plugins`@{
|
||||
mainLogger.verbose { "Loading normal plugins..." }
|
||||
val count = PluginManagerImpl.loadEnableNormalPlugins(pluginLoadSession)
|
||||
mainLogger.verbose { "$count normal plugin(s) loaded." }
|
||||
}
|
||||
|
||||
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) loaded." }
|
||||
|
||||
phase `auto-login bots`@{
|
||||
runBlocking {
|
||||
for ((id, password) in AutoLoginConfig.plainPasswords) {
|
||||
mainLogger.info { "Auto-login $id" }
|
||||
MiraiConsole.addBot(id, password).alsoLogin()
|
||||
}
|
||||
|
||||
for ((id, password) in AutoLoginConfig.md5Passwords) {
|
||||
mainLogger.info { "Auto-login $id" }
|
||||
MiraiConsole.addBot(id, password).alsoLogin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PostStartupExtension.useExtensions { it() }
|
||||
|
||||
mainLogger.info { "mirai-console started successfully." }
|
||||
}
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@DslMarker
|
||||
private annotation class MiraiIsCool
|
||||
|
||||
@MiraiIsCool
|
||||
private inline fun phase(block: () -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
@ -22,7 +22,6 @@ import net.mamoe.mirai.console.plugin.PluginLoadException
|
||||
import net.mamoe.mirai.console.plugin.jvm.*
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.info
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@ -73,8 +72,8 @@ internal object JarPluginLoaderImpl :
|
||||
URLClassLoader(arrayOf(it.toURI().toURL()), MiraiConsole::class.java.classLoader)
|
||||
}.onEach { (_, classLoader) ->
|
||||
classLoaders.add(classLoader)
|
||||
}.asSequence().findAllInstances().onEach { loaded ->
|
||||
logger.info { "Successfully initialized JvmPlugin ${loaded}." }
|
||||
}.asSequence().findAllInstances().onEach {
|
||||
//logger.verbose { "Successfully initialized JvmPlugin ${loaded}." }
|
||||
}.onEach { (file, plugin) ->
|
||||
pluginFileToInstanceMap[file] = plugin
|
||||
} + pluginFileToInstanceMap.asSequence()
|
||||
@ -97,9 +96,13 @@ internal object JarPluginLoaderImpl :
|
||||
override fun enable(plugin: JvmPlugin) {
|
||||
if (plugin.isEnabled) return
|
||||
ensureActive()
|
||||
if (plugin is JvmPluginInternal) {
|
||||
plugin.internalOnEnable()
|
||||
} else plugin.onEnable()
|
||||
runCatching {
|
||||
if (plugin is JvmPluginInternal) {
|
||||
plugin.internalOnEnable()
|
||||
} else plugin.onEnable()
|
||||
}.getOrElse {
|
||||
throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun disable(plugin: JvmPlugin) {
|
||||
|
@ -113,48 +113,58 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* STEPS:
|
||||
* 1. 遍历插件列表, 使用 [builtInLoaders] 加载 [PluginKind.LOADER] 类型的插件
|
||||
* 2. [启动][PluginLoader.enable] 所有 [PluginKind.LOADER] 的插件
|
||||
* 3. 使用内建和所有插件提供的 [PluginLoader] 加载全部除 [PluginKind.LOADER] 外的插件列表.
|
||||
* 4. 解决依赖并排序
|
||||
* 5. 依次 [PluginLoader.load]
|
||||
* 但不 [PluginLoader.enable]
|
||||
*
|
||||
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Throws(PluginMissingDependencyException::class)
|
||||
internal fun loadEnablePlugins() {
|
||||
loadAndEnableLoaderProviders()
|
||||
loadPluginLoaderProvidedByPlugins()
|
||||
internal class PluginLoadSession(
|
||||
val allKindsOfPlugins: List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>>
|
||||
)
|
||||
|
||||
// Phase #2
|
||||
internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession {
|
||||
return PluginLoadSession(loadersLock.withLock { _pluginLoaders.listAllPlugins() })
|
||||
}
|
||||
|
||||
// Phase #0
|
||||
internal fun loadEnablePluginProviderPlugins() {
|
||||
loadAndEnableLoaderProvidersUsingBuiltInLoaders()
|
||||
}
|
||||
|
||||
// Phase #3
|
||||
internal fun loadEnableHighPriorityExtensionPlugins(session: PluginLoadSession): Int {
|
||||
loadersLock.withLock {
|
||||
_pluginLoaders.listAllPlugins().flatMap { it.second }
|
||||
.also {
|
||||
logger.debug("All plugins: ${it.joinToString { (_, desc, _) -> desc.name }}")
|
||||
}
|
||||
session.allKindsOfPlugins.flatMap { it.second }
|
||||
.filter { it.kind == PluginKind.HIGH_PRIORITY_EXTENSIONS }
|
||||
.sortByDependencies()
|
||||
.also {
|
||||
logger.debug("Sorted plugins: ${it.joinToString { (_, desc, _) -> desc.name }}")
|
||||
}
|
||||
.loadAndEnableAllInOrder()
|
||||
.also { it.loadAndEnableAllInOrder() }
|
||||
.let { return it.size }
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPluginLoaderProvidedByPlugins() {
|
||||
// Phase #4
|
||||
internal fun loadEnableNormalPlugins(session: PluginLoadSession): Int {
|
||||
loadersLock.withLock {
|
||||
PluginLoaderProvider.useExtensions {
|
||||
logger.info { "Loaded PluginLoader ${it.instance} from $" }
|
||||
_pluginLoaders.add(it.instance)
|
||||
session.allKindsOfPlugins.flatMap { it.second }
|
||||
.filter { it.kind == PluginKind.NORMAL }
|
||||
.sortByDependencies()
|
||||
.also { it.loadAndEnableAllInOrder() }
|
||||
.let { return it.size }
|
||||
}
|
||||
}
|
||||
|
||||
// Phase #1
|
||||
internal fun loadPluginLoaderProvidedByPlugins() {
|
||||
loadersLock.withLock {
|
||||
PluginLoaderProvider.useExtensions { ext, plugin ->
|
||||
logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin.name}" }
|
||||
_pluginLoaders.add(ext.instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {
|
||||
return this.forEach { (loader, _, plugin) ->
|
||||
this.forEach { (loader, _, plugin) ->
|
||||
loader.loadPluginNoEnable(plugin)
|
||||
}
|
||||
this.forEach { (loader, _, plugin) ->
|
||||
loader.enablePlugin(plugin)
|
||||
}
|
||||
}
|
||||
@ -171,7 +181,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Throws(PluginMissingDependencyException::class)
|
||||
private fun loadAndEnableLoaderProviders(): List<PluginDescriptionWithLoader> {
|
||||
private fun loadAndEnableLoaderProvidersUsingBuiltInLoaders(): List<PluginDescriptionWithLoader> {
|
||||
val allDescriptions =
|
||||
builtInLoaders.listAllPlugins()
|
||||
.asSequence()
|
||||
|
@ -79,7 +79,10 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
|
||||
override val permissionType: KClass<PermissionImpl>
|
||||
get() = PermissionImpl::class
|
||||
override val permissions: MutableMap<PermissionId, PermissionImpl> get() = config.permissions
|
||||
override val grantedPermissionsMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>> get() = config.grantedPermissionMap
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override val grantedPermissionsMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||
get() = config.grantedPermissionMap as MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||
|
||||
override fun createPermission(id: PermissionId, description: String, base: PermissionId?): PermissionImpl =
|
||||
PermissionImpl(id, description, base)
|
||||
|
@ -36,8 +36,8 @@ public interface StorablePermissionService<P : Permission> : PermissionService<P
|
||||
ConcurrentHashMap()
|
||||
)
|
||||
|
||||
public val grantedPermissionMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
|
||||
by value<MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>>(ConcurrentHashMap())
|
||||
public val grantedPermissionMap: MutableMap<PermissionId, List<PermissibleIdentifier>>
|
||||
by value<MutableMap<PermissionId, List<PermissibleIdentifier>>>(ConcurrentHashMap())
|
||||
.withDefault { CopyOnWriteArrayList() }
|
||||
|
||||
public companion object {
|
||||
|
@ -12,17 +12,34 @@ package net.mamoe.mirai.console.plugin.description
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
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.internal.data.map
|
||||
import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.description.PluginKind.*
|
||||
|
||||
/**
|
||||
* 插件类型
|
||||
* 插件类型.
|
||||
*
|
||||
* 插件类型将影响加载顺序: [LOADER] -> [HIGH_PRIORITY_EXTENSIONS] -> [NORMAL].
|
||||
*
|
||||
* 依赖解决过程与插件类型有很大关联. 在一个较早的阶段, 只会解决在此阶段加载的插件. 意味着 [LOADER] 不允许依赖一个 [NORMAL] 类型的插件.
|
||||
*/
|
||||
@Serializable(with = PluginKind.AsStringSerializer::class)
|
||||
public enum class PluginKind {
|
||||
/** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */
|
||||
/** 表示此插件提供一个 [PluginLoader], 也可以同时提供其他 [Extension] 应最早被加载 */
|
||||
LOADER,
|
||||
|
||||
/**
|
||||
* 表示此插件提供一些高优先级的 [Extension], 应在加载其他 [NORMAL] 类型插件前加载
|
||||
*
|
||||
* 高优先级的 [Extension] 通常是覆盖 Console 内置的部分服务的扩展. 如 [PermissionServiceProvider].
|
||||
*
|
||||
* 一些普通的 [Extension], 如 [BotConfigurationAlterer], 也可以使用 [NORMAL] 类型插件注册.
|
||||
*/
|
||||
HIGH_PRIORITY_EXTENSIONS,
|
||||
|
||||
/** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */
|
||||
NORMAL;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user