Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

优化 **持续会话** 模块内部分API、Java友好API和注释,并配置其发布; refactor: 在 common-core 模块中增加与虚拟线程相关的辅助API #798

Merged
merged 1 commit into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/P.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ sealed class P(override val group: String) : ProjectDetail() {
object SimbotLogger : P(GROUP_LOGGER)
object SimbotGradle : P(GROUP_GRADLE)
object SimbotQuantcat : P(GROUP_QUANTCAT)
object SimbotExtension : P(GROUP_QUANTCAT)
object SimbotExtension : P(GROUP_EXTENSION)

final override val version: Version
val versionWithoutSnapshot: Version
Expand Down
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ reactor = "3.6.2"
suspendTransform = "0.7.0-beta1"
suspendReversal = "0.2.0"
gradleCommon = "0.2.0"
# tests
mockk = "1.13.9"


[libraries]
Expand Down Expand Up @@ -120,6 +122,10 @@ gradle-common-core = { group = "love.forte.gradle.common", name = "gradle-common
gradle-common-multiplatform = { group = "love.forte.gradle.common", name = "gradle-common-kotlin-multiplatform", version.ref = "gradleCommon" }
gradle-common-publication = { group = "love.forte.gradle.common", name = "gradle-common-publication", version.ref = "gradleCommon" }

# mockk
## https://mockk.io/
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }

[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,19 @@
*
*/

@file:JvmName("DispatchersUtil")
@file:JvmMultifileClass
package love.forte.simbot.common.coroutines

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName

/**
* 在 JVM 和 native 平台下,得到 `Dispatchers.IO`;
* 在其他没有 `IO` 调度器的平台下得到 [Dispatchers.Default]。
*
*/
public expect val Dispatchers.IOOrDefault: CoroutineDispatcher

Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
@file:JvmName("DispatchersUtil")
@file:JvmMultifileClass

package love.forte.simbot.common.coroutines

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.asCoroutineDispatcher
import love.forte.simbot.annotations.Api4J
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

/**
* 得到 [Dispatchers.IO]。
Expand All @@ -33,3 +43,40 @@ import kotlinx.coroutines.Dispatchers
*/
public actual inline val Dispatchers.IOOrDefault: CoroutineDispatcher
get() = IO

/**
* 在支持虚拟线程调度器时使用虚拟线程调度器 (`Executors.newVirtualThreadPerTaskExecutor`)
* 作为 [CoroutineDispatcher],
* 否则得到 `null`。
*
*/
public val Dispatchers.Virtual: CoroutineDispatcher? by lazy {
runCatching {
val handle = MethodHandles.publicLookup().findStatic(
Executors::class.java,
"newVirtualThreadPerTaskExecutor",
MethodType.methodType(ExecutorService::class.java)
)
(handle.invoke() as Executor).asCoroutineDispatcher()
}.getOrNull()
}

/**
* Friendly API for Java.
*/
@Api4J
public val VirtualDispatcher: CoroutineDispatcher? get() = Dispatchers.Virtual

/**
* 在支持虚拟线程调度器时使用虚拟线程调度器 (`Executors.newVirtualThreadPerTaskExecutor`)
* 作为 [CoroutineDispatcher],
* 否则得到 [Dispatchers.IO]。
*
*/
public val Dispatchers.VirtualOrIO: CoroutineDispatcher by lazy { Dispatchers.Virtual ?: IO }

/**
* Friendly API for Java.
*/
@Api4J
public val VirtualOrIODispatcher: CoroutineDispatcher? get() = Dispatchers.VirtualOrIO
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# 持续会话扩展

> [!warning]
> 尚在试验阶段,随时可能删除或被调整

仍在考虑中。

**持续会话需要解决什么?**
Expand Down Expand Up @@ -36,7 +39,7 @@ suspend fun inSession(event: Event, sessionContext: ContinuousSessionContext): E

val sessionProvider = sessionContext.session(key) { // this: sessionReceiver
// 异步中、可挂起
val e = await() // event
val e = await { it.toResult() } // event
}

// 向会话推送,然后得到一个结果?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ plugins {
setup(P.SimbotExtension)

configJavaCompileWithModule("simbot.extension.continuous.session")
// apply(plugin = "simbot-multiplatform-maven-publish")
apply(plugin = "simbot-multiplatform-maven-publish")

kotlin {
explicitApi()
Expand Down Expand Up @@ -74,13 +74,16 @@ kotlin {
implementation(libs.kotlinx.coroutines.test)
// implementation(libs.kotlinx.coroutines.debug)
implementation(kotlin("test"))
implementation(project(":simbot-cores:simbot-core"))
implementation(project(":simbot-test"))
}
}



jvmMain {
dependencies {
compileOnly(project(":simbot-commons:simbot-common-annotations"))
compileOnly(libs.kotlinx.coroutines.reactive)
compileOnly(libs.kotlinx.coroutines.reactor)
compileOnly(libs.kotlinx.coroutines.rx2)
Expand All @@ -99,6 +102,7 @@ kotlin {
implementation(kotlin("test-junit5"))
implementation(kotlin("reflect"))
implementation(libs.ktor.client.cio)
implementation(libs.mockk)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import kotlin.jvm.JvmName


/**
* 针对 [ContinuousSessionContext] 的基础抽象实现类。
*
* @author ForteScarlet
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,23 @@ package love.forte.simbot.extension.continuous.session

import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic

/**
* 使用于 [ContinuousSessionContext.session] 中的 `receiver` 逻辑函数。
*
* 在 Java 中,可以使用 `InSessions` 中提供的各种静态工厂函数构建它,例如
* `InSessions.async`、`InSessions.mono` 等。
*
* 在 `ContinuousSession` 中使用时,我们强烈建议使用非阻塞的 [InSession] 实现,
* 或者为 `ContinuousSession` 的调度器配置为 **虚拟线程调度器** 。
*
* ```java
* var dispatcher = ExecutorsKt.from(Executors.newVirtualThreadPerTaskExecutor());
* ```
*/
public fun interface InSession<T, R> {
@JvmSynthetic
public suspend fun ContinuousSessionReceiver<T, R>.invoke()
}

Expand Down Expand Up @@ -94,10 +106,7 @@ public fun interface InSession<T, R> {
* |--------------- | --------|
* ↓ |
* return session.push(handleEvent) // 推送 '事件', 得到 '结果'
*
* // 直接返回这个结果
* return result
*
* }
* ```
*
Expand All @@ -115,7 +124,12 @@ public interface ContinuousSessionContext<T, R> {
* @param key session 会话的标识。[key] 的类型应当是一个可以保证能够作为一个 hash key 的类型,
* 例如基础数据类型(例如 [Int]、[String])、数据类类型(data class)、object 类型等。
* @param strategy 当 [key] 出现冲突时的处理策略
* @param inSession 在**异步**中进行
* @param inSession 在**异步**中进行会话逻辑的函数实例。
* 在 Java 中可使用 `InSessions` 中提供的静态工厂函数构建实例,
* 例如 `InSessions.async`、`InSessions.mono` 等。
* 在 `ContinuousSession` 中使用时,我们强烈建议使用非阻塞的 [InSession] 实现,
* 或者为 `ContinuousSession` 的调度器配置为 **虚拟线程调度器** 。
*
* @throws ConflictSessionKeyException 如果 [strategy] 为 [ConflictStrategy.FAILURE] 并且出现了冲突
*/
public fun session(
Expand All @@ -125,21 +139,28 @@ public interface ContinuousSessionContext<T, R> {
): ContinuousSessionProvider<T, R>

/**
* 尝试创建一组 `ContinuousSession`, 并在出现 [key] 冲突时基于 []
* 尝试创建一组 `ContinuousSession`, 并在出现 [key] 冲突时使用 [ConflictStrategy.FAILURE] 作为冲突解决策略。
*/
public fun session(
key: Any,
inSession: InSession<T, R>
): ContinuousSessionProvider<T, R> = session(key, ConflictStrategy.FAILURE, inSession)


/**
* 根据 [key] 获取指定的 [ContinuousSessionProvider] 并在找不到时返回 `null`。
*/
public operator fun get(key: Any): ContinuousSessionProvider<T, R>?

/**
* 判断是否包含某个 [key] 对应的会话。
*/
public operator fun contains(key: Any): Boolean

/**
* 移除某个指定 [key] 的会话。
* [remove] 仅会从记录中移除,不会使用 [ContinuousSessionProvider.cancel],
* 需要由调用者主动使用。
*/
public fun remove(key: Any): ContinuousSessionProvider<T, R>?

/**
Expand All @@ -164,3 +185,4 @@ public interface ContinuousSessionContext<T, R> {
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

package love.forte.simbot.extension.continuous.session

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Job
import love.forte.simbot.application.Application
import love.forte.simbot.application.ApplicationConfiguration
Expand Down Expand Up @@ -77,7 +78,8 @@ public interface EventContinuousSessionContext : ContinuousSessionContext<Event,
): EventContinuousSessionContext {
val config = EventContinuousSessionContextConfiguration()
configurer.invokeWith(config)
val newCoroutineContext = config.coroutineContext.mergeWith(context.applicationConfiguration.coroutineContext)
val newCoroutineContext =
config.coroutineContext.mergeWith(context.applicationConfiguration.coroutineContext)

return EventContinuousSessionContextImpl(newCoroutineContext)
}
Expand All @@ -94,9 +96,28 @@ private class EventContinuousSessionContextImpl(coroutineContext: CoroutineConte
public class EventContinuousSessionContextConfiguration {
/**
* 用于 [EventContinuousSessionContext] 中的协程上下文。
*
* 值来自 [ApplicationConfiguration.coroutineContext],但不包含 Job。
* 如果配置后 [coroutineContext] 存在 [Job], 则会基于此以及 [ApplicationConfiguration.coroutineContext]
* 合成一个新的 [Job]。
*
* 在 Java 中时,如果你打算在逻辑中使用 **阻塞** API,那么建议为其配置虚拟线程调度器;
* 否则,建议在其中使用异步 API,例如 `InSessions.async`。
*/
public var coroutineContext: CoroutineContext = EmptyCoroutineContext

/**
* 为 [coroutineContext] 配置调度器。
* 如果为 `null` 则移除调度器。
*/
@OptIn(ExperimentalStdlibApi::class)
public var coroutineDispatcher: CoroutineDispatcher?
get() = coroutineContext[CoroutineDispatcher]
set(value) {
if (value == null) {
coroutineContext = coroutineContext.minusKey(CoroutineDispatcher)
} else {
coroutineContext += value
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024. ForteScarlet.
*
* Project https://github.com/simple-robot/simpler-robot
* Email [email protected]
*
* This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
*
* You should have received a copy of the Lesser GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

import kotlinx.coroutines.test.runTest
import love.forte.simbot.core.application.launchSimpleApplication
import love.forte.simbot.extension.continuous.session.EventContinuousSessionContext
import love.forte.simbot.plugin.find
import kotlin.test.Test
import kotlin.test.assertNotNull

/*
* Copyright (c) 2024. ForteScarlet.
*
* Project https://github.com/simple-robot/simpler-robot
* Email [email protected]
*
* This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
*
* You should have received a copy of the Lesser GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

/**
*
* @author ForteScarlet
*/
class InAppTests {

@Test
fun installSessionContextTest() = runTest {
val app = launchSimpleApplication {
install(EventContinuousSessionContext)
}

assertNotNull(app.plugins.find<EventContinuousSessionContext>())
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module simbot.extension.continuous.session {
requires kotlin.stdlib;
requires simbot.api;
requires kotlinx.coroutines.core;
requires static simbot.common.annotations;
requires static kotlinx.coroutines.reactor;
requires static reactor.core;

exports love.forte.simbot.extension.continuous.session;
}
Loading
Loading