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