diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c23e80d1f..9e38ced79 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -90,7 +90,7 @@ dependencies { implementation(libs.common.utilcode) implementation(libs.common.glide) implementation(libs.common.jsoup) - implementation(libs.common.kotlin.coroutines) + implementation(libs.common.kotlin.coroutines.android) implementation(libs.google.auto.service.annotations) implementation(libs.google.gson) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 233e10e38..96136c188 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ common-javaparser = { module = "com.github.javaparser:javaparser-symbol-solver-c common-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.13.0" } common-io = { module = "commons-io:commons-io", version = "2.13.0" } common-kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } -common-kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlin-coroutines" } +common-kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlin-coroutines" } common-jkotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } common-eventbus_ap = { module = "org.greenrobot:eventbus-annotation-processor", version = "3.3.1" } common-javapoet = { module = "com.squareup:javapoet", version = "1.13.0" } diff --git a/shared/src/main/java/com/itsaky/androidide/utils/StopWatch.kt b/shared/src/main/java/com/itsaky/androidide/utils/StopWatch.kt index a0c0d32a1..3f8c3af4f 100644 --- a/shared/src/main/java/com/itsaky/androidide/utils/StopWatch.kt +++ b/shared/src/main/java/com/itsaky/androidide/utils/StopWatch.kt @@ -48,3 +48,21 @@ constructor( lastLap = System.currentTimeMillis() } } + +/** + * Run the given action with a stopwatch to log the time the action took to execute. + * + * @see StopWatch + */ +inline fun withStopWatch( + label: String, + start: Long = System.currentTimeMillis(), + lastLap: Long = start, + action: (StopWatch) -> R +): R { + return StopWatch(label, start, lastLap).run { + action(this).also { + log() + } + } +} \ No newline at end of file diff --git a/subprojects/projects/build.gradle.kts b/subprojects/projects/build.gradle.kts index f852eb8b4..63c93dec5 100644 --- a/subprojects/projects/build.gradle.kts +++ b/subprojects/projects/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { implementation(projects.subprojects.xmlUtils) implementation(libs.common.io) + implementation(libs.common.kotlin.coroutines.android) implementation(libs.google.auto.service.annotations) implementation(libs.google.guava) diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt index 83190f175..a833d21e9 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt @@ -42,6 +42,15 @@ import com.itsaky.androidide.tooling.api.models.BuildVariantInfo import com.itsaky.androidide.utils.DocumentUtils import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.flashError +import com.itsaky.androidide.utils.withStopWatch +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -88,20 +97,32 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { override fun setupProject(project: IProject) { this.rootProject = ProjectTransformer().transform(CachingProject(project)) - if (this.rootProject == null) { - return - } + val rootProject = this.rootProject ?: return // build variants must be updated before the sources and classpaths are indexed updateBuildVariants { buildVariants -> androidBuildVariants = buildVariants } - this.rootProject!!.subProjects.filterIsInstance(ModuleProject::class.java).forEach { - it.indexSourcesAndClasspaths() - if (it is AndroidModule) { - it.readResources() + withStopWatch("Setup project") { + val indexerScope = CoroutineScope(Dispatchers.Default) + val modulesFlow = flow { + rootProject.subProjects.filterIsInstance(ModuleProject::class.java).forEach { + emit(it) + } } + + val jobs = modulesFlow.map { module -> + indexerScope.async { + module.indexSourcesAndClasspaths() + if (module is AndroidModule) { + module.readResources() + } + } + } + + // wait for the indexing to finish + runBlocking { jobs.toList().awaitAll() } } } diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt index 53e4a8efe..76488784c 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt @@ -37,11 +37,20 @@ import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata import com.itsaky.androidide.tooling.api.models.GradleTask import com.itsaky.androidide.tooling.api.util.findPackageName import com.itsaky.androidide.utils.ILogger +import com.itsaky.androidide.utils.withStopWatch import com.itsaky.androidide.xml.resources.ResourceTableRegistry import com.itsaky.androidide.xml.versions.ApiVersions import com.itsaky.androidide.xml.versions.ApiVersionsRegistry import com.itsaky.androidide.xml.widgets.WidgetTable import com.itsaky.androidide.xml.widgets.WidgetTableRegistry +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList import java.io.File import java.util.concurrent.CompletableFuture @@ -244,25 +253,27 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. * Reads the resource files are creates the [com.android.aaptcompiler.ResourceTable] instances for * the corresponding resource directories. */ - fun readResources() { + suspend fun readResources() { // Read resources in parallel - val threads = mutableListOf() - threads.add( - Thread { - getFrameworkResourceTable() - getResourceTable() - getDependencyResourceTables() + withStopWatch("Read resources for module : $path") { + val resourceReaderScope = CoroutineScope( + Dispatchers.IO + CoroutineName("ResourceReader($path)")) + + val resourceFlow = flow { + emit(getFrameworkResourceTable()) + emit(getResourceTable()) + emit(getDependencyResourceTables()) + emit(getApiVersions()) + emit(getWidgetTable()) } - ) - threads.add(Thread(this::getApiVersions)) - threads.add(Thread(this::getWidgetTable)) - for (thread in threads) { - thread.start() - } + val jobs = resourceFlow.map { result -> + resourceReaderScope.async { + result + } + } - for (thread in threads) { - thread.join() + jobs.toList().awaitAll() } }