diff --git a/.idea/runConfigurations/_allTests.xml b/.idea/runConfigurations/_allTests.xml
new file mode 100644
index 000000000..4a728aa1d
--- /dev/null
+++ b/.idea/runConfigurations/_allTests.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 2dbf45aa0..f4c70c6ec 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -74,6 +74,10 @@ subprojects {
if (hasKtP()) {
// apply(plugin = "io.gitlab.arturbosch.detekt")
applyDetekt()
+ if ("gradle" !in name) {
+ useK2()
+ logger.info("Enable K2 for {}", this)
+ }
}
}
}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 34a75b30f..2dab0ad5b 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -58,3 +58,15 @@ idea {
isDownloadSources = true
}
}
+
+// tasks.withType(KotlinCompile::class.java).configureEach {
+// kotlinOptions.languageVersion = "1.9"
+// }
+
+// val compileKotlin: KotlinCompile by tasks
+// compileKotlin.kotlinOptions.freeCompilerArgs += listOf(
+// "-Xjvm-default=all",
+// // "-opt-in=kotlin.RequiresOptIn",
+// // see https://youtrack.jetbrains.com/issue/KTIJ-21563
+// "-Xskip-prerelease-check",
+// )
diff --git a/buildSrc/src/main/kotlin/K2Config.kt b/buildSrc/src/main/kotlin/K2Config.kt
new file mode 100644
index 000000000..5bb81f565
--- /dev/null
+++ b/buildSrc/src/main/kotlin/K2Config.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2024. ForteScarlet.
+ *
+ * Project https://github.com/simple-robot/simpler-robot
+ * Email ForteScarlet@163.com
+ *
+ * This file is part of the Simple Robot Library.
+ *
+ * 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 .
+ *
+ */
+
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.withType
+
+
+fun Project.useK2(languageVersion: String = "2.0") {
+ tasks.withType {
+ kotlinOptions {
+ // useK2
+ this.languageVersion = languageVersion
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/P.kt b/buildSrc/src/main/kotlin/P.kt
index 57c3b7276..cf032fac7 100644
--- a/buildSrc/src/main/kotlin/P.kt
+++ b/buildSrc/src/main/kotlin/P.kt
@@ -81,7 +81,7 @@ sealed class P(override val group: String) : ProjectDetail() {
val versionWithoutSnapshot: Version
init {
- val mainVersion = version(4, 0, 0) - version("dev14")
+ val mainVersion = version(4, 0, 0) - version("dev15")
fun initVersionWithoutSnapshot(status: Version?): Version = if (status == null) {
mainVersion
diff --git a/buildSrc/src/main/kotlin/simbot.dokka-multi-module.gradle.kts b/buildSrc/src/main/kotlin/simbot.dokka-multi-module.gradle.kts
index 8a826ffca..95f99ec6b 100644
--- a/buildSrc/src/main/kotlin/simbot.dokka-multi-module.gradle.kts
+++ b/buildSrc/src/main/kotlin/simbot.dokka-multi-module.gradle.kts
@@ -28,7 +28,7 @@ import java.time.Year
/*
- 使用在根配置,配置dokka多模块
+使用在根配置,配置dokka多模块
*/
plugins {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5e8bcbf31..7491df2b7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,12 +8,13 @@ spring-boot-v3 = "3.2.1"
openjdk-jmh = "1.36"
ktor = "2.3.7"
slf4j = "2.0.7"
-ksp = "1.9.22-1.0.16"
+#ksp = "1.9.22-1.0.16"
+ksp = "1.9.22-1.0.17"
# https://square.github.io/kotlinpoet/
kotlinPoet = "1.15.3"
reactor = "3.6.2"
#
-suspendTransform = "0.6.0"
+suspendTransform = "0.7.0-beta1"
suspendReversal = "0.2.0"
gradleCommon = "0.2.0"
diff --git a/simbot-api/build.gradle.kts b/simbot-api/build.gradle.kts
index c1f11e7ae..3d62cc1e8 100644
--- a/simbot-api/build.gradle.kts
+++ b/simbot-api/build.gradle.kts
@@ -114,6 +114,7 @@ kotlin {
implementation(libs.ktor.client.core)
implementation(kotlin("test-junit5"))
+ implementation(kotlin("reflect"))
implementation(libs.ktor.client.cio)
}
}
diff --git a/simbot-api/src/jvmMain/kotlin/love/forte/simbot/application/ApplicationFactory.jvm.kt b/simbot-api/src/jvmMain/kotlin/love/forte/simbot/application/ApplicationFactory.jvm.kt
index 94cf40b9d..8da09c531 100644
--- a/simbot-api/src/jvmMain/kotlin/love/forte/simbot/application/ApplicationFactory.jvm.kt
+++ b/simbot-api/src/jvmMain/kotlin/love/forte/simbot/application/ApplicationFactory.jvm.kt
@@ -60,7 +60,9 @@ public fun interface JBlockingApplicationLauncher {
}
private class JBlockingApplicationLauncherImpl(
- private val handler: JBlockingApplicationLauncher,
+ // TODO private:
+ // e: file:///G:/code/simbot/simbot-api/src/jvmMain/kotlin/love/forte/simbot/application/ApplicationFactory.jvm.kt:78:35 Cannot access 'val handler: JBlockingApplicationLauncher': it is private/*private to this*/ in 'love/forte/simbot/application/JBlockingApplicationLauncherImpl'
+ val handler: JBlockingApplicationLauncher,
private val handlerContext: CoroutineContext
) : ApplicationLauncher {
override suspend fun launch(): A {
@@ -73,8 +75,8 @@ private class JBlockingApplicationLauncherImpl(
if (this === other) return true
if (other !is JBlockingApplicationLauncherImpl<*>) return false
- if (handler != other.handler) return false
if (handlerContext != other.handlerContext) return false
+ if (this.handler != other.handler) return false
return true
}
diff --git a/simbot-api/src/jvmTest/kotlin/SuspendTransformTests.kt b/simbot-api/src/jvmTest/kotlin/SuspendTransformTests.kt
new file mode 100644
index 000000000..144596408
--- /dev/null
+++ b/simbot-api/src/jvmTest/kotlin/SuspendTransformTests.kt
@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2024. ForteScarlet.
+ *
+ * Project https://github.com/simple-robot/simpler-robot
+ * Email ForteScarlet@163.com
+ *
+ * This file is part of the Simple Robot Library.
+ *
+ * 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 .
+ *
+ */
+import love.forte.simbot.suspendrunner.ST
+import love.forte.simbot.suspendrunner.STP
+import love.forte.simbot.suspendrunner.reserve.SuspendReserve
+import java.lang.reflect.Modifier
+import java.util.concurrent.CompletableFuture
+import kotlin.reflect.KTypeParameter
+import kotlin.reflect.full.memberProperties
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+
+@ST
+interface STTrans1 {
+ suspend fun run1()
+ suspend fun run1(value: String): String
+}
+
+@STP
+interface STPTrans1 {
+ suspend fun run1(): Int
+ suspend fun run2(): String
+}
+
+interface STTrans2 {
+ @ST(blockingSuffix = "Bk", asyncSuffix = "Ay", reserveSuffix = "Rs")
+ suspend fun run1()
+
+ @ST(blockingSuffix = "Bk", asyncSuffix = "Ay", reserveSuffix = "Rs")
+ suspend fun run1(value: String): String
+}
+
+interface STPTrans2 {
+ @STP(blockingSuffix = "Bk", asyncSuffix = "Ay", reserveSuffix = "Rs")
+ suspend fun run1(): Int
+
+ @STP(blockingSuffix = "Bk", asyncSuffix = "Ay", reserveSuffix = "Rs")
+ suspend fun run2(): String
+}
+
+interface STTrans3 {
+ @ST(
+ blockingBaseName = "apply1",
+ blockingSuffix = "Bk",
+ asyncBaseName = "apply1",
+ asyncSuffix = "Ay",
+ reserveBaseName = "apply1",
+ reserveSuffix = "Rs"
+ )
+ suspend fun run1()
+
+ @ST(
+ blockingBaseName = "apply1",
+ blockingSuffix = "Bk",
+ asyncBaseName = "apply1",
+ asyncSuffix = "Ay",
+ reserveBaseName = "apply1",
+ reserveSuffix = "Rs"
+ )
+ suspend fun run1(value: String): String
+}
+
+interface STPTrans3 {
+ @STP(
+ blockingBaseName = "apply1",
+ blockingSuffix = "Bk",
+ asyncBaseName = "apply1",
+ asyncSuffix = "Ay",
+ reserveBaseName = "apply1",
+ reserveSuffix = "Rs"
+ )
+ suspend fun run1(): Int
+
+ @STP(
+ blockingBaseName = "apply2",
+ blockingSuffix = "Bk",
+ asyncBaseName = "apply2",
+ asyncSuffix = "Ay",
+ reserveBaseName = "apply2",
+ reserveSuffix = "Rs"
+ )
+ suspend fun run2(): String
+}
+
+open class Foo
+open class Bar : Foo()
+
+@STP
+interface ITypedTrans1 {
+ suspend fun value(): T
+}
+
+@STP
+interface TypedTrans1Impl : ITypedTrans1 {
+ override suspend fun value(): T
+}
+
+/**
+ *
+ * @author ForteScarlet
+ */
+class SuspendTransformTests {
+
+ @Test
+ fun `interface suspend trans function test`() {
+ with(STTrans1::class.java) {
+ val blockingMethod = getMethod("run1Blocking")
+ val asyncMethod = getMethod("run1Async")
+ val reserveMethod = getMethod("run1Reserve")
+
+ assertEquals(Void.TYPE, blockingMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reserveMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reserveMethod.modifiers))
+ }
+
+ with(STTrans1::class.java) {
+ val blockingMethod = getMethod("run1Blocking", String::class.java)
+ val asyncMethod = getMethod("run1Async", String::class.java)
+ val reserveMethod = getMethod("run1Reserve", String::class.java)
+
+ assertEquals(String::class.java, blockingMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reserveMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reserveMethod.modifiers))
+ }
+ }
+
+ @Test
+ fun `interface suspend trans property test`() {
+ with(STPTrans1::class.memberProperties) {
+ assertTrue(any { it.name == "run1" && it.returnType.classifier == Int::class })
+ assertTrue(any { it.name == "run2" && it.returnType.classifier == String::class })
+ }
+
+ // run1
+ with(STPTrans1::class.java) {
+ val blockingPropertyMethod = getMethod("getRun1")
+ val asyncPropertyMethod = getMethod("getRun1Async")
+ val reservePropertyMethod = getMethod("getRun1Reserve")
+
+ assertEquals(Int::class.javaPrimitiveType, blockingPropertyMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncPropertyMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reservePropertyMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reservePropertyMethod.modifiers))
+ }
+
+ // run2
+ with(STPTrans1::class.java) {
+ val blockingPropertyMethod = getMethod("getRun2")
+ val asyncPropertyMethod = getMethod("getRun2Async")
+ val reservePropertyMethod = getMethod("getRun2Reserve")
+
+ assertEquals(String::class.java, blockingPropertyMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncPropertyMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reservePropertyMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reservePropertyMethod.modifiers))
+ }
+
+ }
+
+ @Test
+ fun `interface suspend trans function with suffix test`() {
+ with(STTrans2::class.java) {
+ val blockingMethod = getMethod("run1Bk")
+ val asyncMethod = getMethod("run1Ay")
+ val reserveMethod = getMethod("run1Rs")
+
+ assertEquals(Void.TYPE, blockingMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reserveMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reserveMethod.modifiers))
+ }
+
+ with(STTrans2::class.java) {
+ val blockingMethod = getMethod("run1Bk", String::class.java)
+ val asyncMethod = getMethod("run1Ay", String::class.java)
+ val reserveMethod = getMethod("run1Rs", String::class.java)
+
+ assertEquals(String::class.java, blockingMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reserveMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reserveMethod.modifiers))
+ }
+ }
+
+ @Test
+ fun `interface suspend trans property with suffix test`() {
+ with(STPTrans2::class.memberProperties) {
+ assertTrue(any { it.name == "run1Bk" && it.returnType.classifier == Int::class })
+ assertTrue(any { it.name == "run2Bk" && it.returnType.classifier == String::class })
+ }
+
+ // run1
+ with(STPTrans2::class.java) {
+ val blockingPropertyMethod = getMethod("getRun1Bk")
+ val asyncPropertyMethod = getMethod("getRun1Ay")
+ val reservePropertyMethod = getMethod("getRun1Rs")
+
+ assertEquals(Int::class.javaPrimitiveType, blockingPropertyMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncPropertyMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reservePropertyMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reservePropertyMethod.modifiers))
+ }
+
+ // run2
+ with(STPTrans2::class.java) {
+ val blockingPropertyMethod = getMethod("getRun2Bk")
+ val asyncPropertyMethod = getMethod("getRun2Ay")
+ val reservePropertyMethod = getMethod("getRun2Rs")
+
+ assertEquals(String::class.java, blockingPropertyMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncPropertyMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reservePropertyMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reservePropertyMethod.modifiers))
+ }
+
+ }
+
+ @Test
+ fun `interface suspend trans function with baseName and suffix test`() {
+ with(STTrans3::class.java) {
+ val blockingMethod = getMethod("apply1Bk")
+ val asyncMethod = getMethod("apply1Ay")
+ val reserveMethod = getMethod("apply1Rs")
+
+ assertEquals(Void.TYPE, blockingMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reserveMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reserveMethod.modifiers))
+ }
+
+ with(STTrans3::class.java) {
+
+ val blockingMethod = getMethod("apply1Bk", String::class.java)
+ val asyncMethod = getMethod("apply1Ay", String::class.java)
+ val reserveMethod = getMethod("apply1Rs", String::class.java)
+
+ assertEquals(String::class.java, blockingMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reserveMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reserveMethod.modifiers))
+ }
+ }
+
+ @Test
+ fun `interface suspend trans property with baseName and suffix test`() {
+ with(STPTrans3::class.memberProperties) {
+ assertTrue(any { it.name == "apply1Bk" && it.returnType.classifier == Int::class })
+ assertTrue(any { it.name == "apply2Bk" && it.returnType.classifier == String::class })
+ }
+
+ // run1
+ with(STPTrans3::class.java) {
+ val blockingPropertyMethod = getMethod("getApply1Bk")
+ val asyncPropertyMethod = getMethod("getApply1Ay")
+ val reservePropertyMethod = getMethod("getApply1Rs")
+
+ assertEquals(Int::class.javaPrimitiveType, blockingPropertyMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncPropertyMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reservePropertyMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reservePropertyMethod.modifiers))
+ }
+
+ // run2
+ with(STPTrans3::class.java) {
+ val blockingPropertyMethod = getMethod("getApply2Bk")
+ val asyncPropertyMethod = getMethod("getApply2Ay")
+ val reservePropertyMethod = getMethod("getApply2Rs")
+
+ assertEquals(String::class.java, blockingPropertyMethod.returnType)
+ assertEquals(CompletableFuture::class.java, asyncPropertyMethod.returnType)
+ assertEquals(SuspendReserve::class.java, reservePropertyMethod.returnType)
+
+ assertFalse(Modifier.isAbstract(blockingPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(asyncPropertyMethod.modifiers))
+ assertFalse(Modifier.isAbstract(reservePropertyMethod.modifiers))
+ }
+
+ }
+
+ @Test
+ fun `typed interface test`() {
+ with(ITypedTrans1::class) {
+ assertTrue(memberProperties.any {
+ it.name == "value" && with(it.returnType.classifier) {
+ this is KTypeParameter && this.upperBounds.any { b -> b.classifier == Foo::class }
+ }
+ })
+ assertTrue(memberProperties.any {
+ it.name == "valueAsync" && it.returnType.classifier == CompletableFuture::class
+ })
+ assertTrue(memberProperties.any {
+ it.name == "valueReserve" && it.returnType.classifier == SuspendReserve::class
+ })
+ }
+ with(TypedTrans1Impl::class) {
+ assertTrue(memberProperties.any {
+ it.name == "value" && with(it.returnType.classifier) {
+ this is KTypeParameter && this.upperBounds.any { b -> b.classifier == Bar::class }
+ }
+ })
+ assertTrue(memberProperties.any {
+ it.name == "valueAsync" && it.returnType.classifier == CompletableFuture::class
+ })
+ assertTrue(memberProperties.any {
+ it.name == "valueReserve" && it.returnType.classifier == SuspendReserve::class
+ })
+ }
+ }
+
+}
diff --git a/simbot-commons/build.gradle.kts b/simbot-commons/build.gradle.kts
new file mode 100644
index 000000000..36f66408b
--- /dev/null
+++ b/simbot-commons/build.gradle.kts
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2024. ForteScarlet.
+ *
+ * Project https://github.com/simple-robot/simpler-robot
+ * Email ForteScarlet@163.com
+ *
+ * This file is part of the Simple Robot Library.
+ *
+ * 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 .
+ *
+ */
+
+
+subprojects {
+
+}
diff --git a/simbot-gradles/simbot-gradle-suspendtransforms/build.gradle.kts b/simbot-gradles/simbot-gradle-suspendtransforms/build.gradle.kts
index 5c126e6dd..b95258423 100644
--- a/simbot-gradles/simbot-gradle-suspendtransforms/build.gradle.kts
+++ b/simbot-gradles/simbot-gradle-suspendtransforms/build.gradle.kts
@@ -40,8 +40,6 @@ kotlin {
configJavaToolchain(JVMConstants.KT_JVM_TARGET_VALUE)
}
-val suspendTransformVersion = "0.6.0-beta3"
-
dependencies {
- api("love.forte.plugin.suspend-transform:suspend-transform-plugin-gradle:$suspendTransformVersion")
+ api(libs.suspend.transform.gradle)
}
diff --git a/website b/website
index 773d65f4d..9e48b7517 160000
--- a/website
+++ b/website
@@ -1 +1 @@
-Subproject commit 773d65f4d358753c2e1029056003329ea704ba93
+Subproject commit 9e48b75171f728f6bb295a4495e50f767fe35421