Skip to content

Commit

Permalink
Use JS-specific collections inside core compose (#2318)
Browse files Browse the repository at this point in the history
These are faster and more memory-efficient than Kotlin's implementation.
  • Loading branch information
JakeWharton authored Sep 25, 2024
1 parent e05aaa2 commit e0989f3
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 6 deletions.
12 changes: 12 additions & 0 deletions redwood-compose/api/redwood-compose.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,20 @@ final object app.cash.redwood.compose/WindowAnimationFrameClock : androidx.compo
final suspend fun <#A1: kotlin/Any?> withFrameNanos(kotlin/Function1<kotlin/Long, #A1>): #A1 // app.cash.redwood.compose/WindowAnimationFrameClock.withFrameNanos|withFrameNanos(kotlin.Function1<kotlin.Long,0:0>){0§<kotlin.Any?>}[0]
}

// Targets: [js]
final val app.cash.redwood.compose/app_cash_redwood_compose_JsArray$stableprop // app.cash.redwood.compose/app_cash_redwood_compose_JsArray$stableprop|#static{}app_cash_redwood_compose_JsArray$stableprop[0]

// Targets: [js]
final val app.cash.redwood.compose/app_cash_redwood_compose_JsSet$stableprop // app.cash.redwood.compose/app_cash_redwood_compose_JsSet$stableprop|#static{}app_cash_redwood_compose_JsSet$stableprop[0]

// Targets: [js]
final val app.cash.redwood.compose/app_cash_redwood_compose_WindowAnimationFrameClock$stableprop // app.cash.redwood.compose/app_cash_redwood_compose_WindowAnimationFrameClock$stableprop|#static{}app_cash_redwood_compose_WindowAnimationFrameClock$stableprop[0]

// Targets: [js]
final fun app.cash.redwood.compose/app_cash_redwood_compose_JsArray$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_JsArray$stableprop_getter|app_cash_redwood_compose_JsArray$stableprop_getter(){}[0]

// Targets: [js]
final fun app.cash.redwood.compose/app_cash_redwood_compose_JsSet$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_JsSet$stableprop_getter|app_cash_redwood_compose_JsSet$stableprop_getter(){}[0]

// Targets: [js]
final fun app.cash.redwood.compose/app_cash_redwood_compose_WindowAnimationFrameClock$stableprop_getter(): kotlin/Int // app.cash.redwood.compose/app_cash_redwood_compose_WindowAnimationFrameClock$stableprop_getter|app_cash_redwood_compose_WindowAnimationFrameClock$stableprop_getter(){}[0]
17 changes: 16 additions & 1 deletion redwood-compose/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@ redwoodBuild {
apply plugin: 'org.jetbrains.kotlin.plugin.compose'

kotlin {
applyDefaultHierarchyTemplate {
it.group("common") {
it.group("nonJs") {
it.group("ios") {}
it.withAndroidTarget()
it.withJvm()
}
}
}

sourceSets {
commonMain {
kotlin.srcDir(redwoodBuild.generateComposeHelpers(tasks, 'app.cash.redwood.compose'))
dependencies {
api libs.kotlinx.coroutines.core
api projects.redwoodRuntime
Expand All @@ -37,6 +46,12 @@ kotlin {
implementation libs.androidx.core
}
}
nonJsMain {
kotlin.srcDir(redwoodBuild.generateComposeHelpers(tasks, 'app.cash.redwood.compose'))
dependencies {
implementation libs.jetbrains.compose.collection
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.redwood.compose

internal expect class PlatformList<T>

internal expect inline fun <T> platformListOf(): PlatformList<T>

internal expect inline val <T> PlatformList<T>.size: Int

internal expect inline operator fun <T> PlatformList<T>.get(index: Int): T

internal expect inline fun <T> PlatformList<T>.add(index: Int, element: T)

internal expect inline fun <T> PlatformList<T>.remove(index: Int, count: Int)

internal expect fun <T> PlatformList<T>.move(from: Int, to: Int, count: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.redwood.compose

internal expect class PlatformSet<T>

internal expect inline fun <T> platformSetOf(): PlatformSet<T>

internal expect inline operator fun <T> PlatformSet<T>.plusAssign(element: T)

internal expect inline fun <T> PlatformSet<T>.forEach(crossinline block: (T) -> Unit)

internal expect inline fun <T> PlatformSet<T>.clear()
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ internal class NodeApplier<W : Any>(
) : AbstractApplier<Node<W>>(ChildrenNode(root)),
RedwoodApplier<W> {
private var closed = false
private val changedWidgets = LinkedHashSet<ChangeListener>()
private val changedWidgets = platformSetOf<ChangeListener>()

override fun recordChanged(widget: Widget<W>) {
if (widget is ChangeListener) {
Expand All @@ -69,10 +69,12 @@ internal class NodeApplier<W : Any>(
override fun onEndChanges() {
check(!closed)

for (changedWidget in changedWidgets) {
changedWidget.onEndChanges()
changedWidgets.let { changedWidgets ->
changedWidgets.forEach { changedWidget ->
changedWidget.onEndChanges()
}
changedWidgets.clear()
}
changedWidgets.clear()

onEndChanges.invoke()
}
Expand Down Expand Up @@ -191,7 +193,7 @@ internal class ChildrenNode<W : Any> private constructor(
constructor(accessor: (Widget<W>) -> Widget.Children<W>) : this(accessor, null, null)
constructor(children: Widget.Children<W>) : this(null, null, children)

private val nodes = mutableListOf<WidgetNode<Widget<W>, W>>()
private val nodes = platformListOf<WidgetNode<Widget<W>, W>>()

fun insert(index: Int, node: WidgetNode<Widget<W>, W>) {
nodes.let { nodes ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE")

package app.cash.redwood.compose

@JsName("Array")
internal external class JsArray<T> {
val length: Int
fun splice(start: Int, deleteCount: Int): JsArray<T>
fun splice(start: Int, deleteCount: Int, element: T)
}

internal actual typealias PlatformList<T> = JsArray<T>

internal actual inline fun <T> platformListOf(): PlatformList<T> {
return js("[]").unsafeCast<PlatformList<T>>()
}

internal actual inline val <T> PlatformList<T>.size: Int get() = length

internal actual inline operator fun <T> PlatformList<T>.get(index: Int): T {
return asDynamic()[index]
}

internal actual inline fun <T> PlatformList<T>.add(index: Int, element: T) {
splice(index, 0, element)
}

internal actual inline fun <T> PlatformList<T>.remove(index: Int, count: Int) {
splice(index, count)
}

internal actual fun <T> PlatformList<T>.move(from: Int, to: Int, count: Int) {
val dest = if (from > to) to else to - count
val removed = splice(from, count)
repeat(count) { offset ->
splice(dest + offset, 0, removed[offset])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE")

package app.cash.redwood.compose

@JsName("Set")
internal external class JsSet<T> {
fun add(element: T)
fun clear()
fun forEach(block: (T) -> Unit)
}

internal actual typealias PlatformSet<T> = JsSet<T>

internal actual inline fun <T> platformSetOf(): PlatformSet<T> {
return JsSet()
}

internal actual inline operator fun <T> PlatformSet<T>.plusAssign(element: T) {
add(element)
}

internal actual inline fun <T> PlatformSet<T>.forEach(crossinline block: (T) -> Unit) {
forEach { block(it) }
}

internal actual inline fun <T> PlatformSet<T>.clear() {
clear()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE")

package app.cash.redwood.compose

@Suppress("ACTUAL_TYPE_ALIAS_NOT_TO_CLASS") // On the JVM it aliases to java.util.ArrayList.
internal actual typealias PlatformList<T> = ArrayList<T>

internal actual inline fun <T> platformListOf(): PlatformList<T> {
return ArrayList()
}

internal actual inline val <T> PlatformList<T>.size: Int get() = size

internal actual inline operator fun <T> PlatformList<T>.get(index: Int): T {
return get(index)
}

internal actual inline fun <T> PlatformList<T>.add(index: Int, element: T) {
add(index, element)
}

internal actual inline fun <T> PlatformList<T>.remove(index: Int, count: Int) {
// Force resolution to the generated extension.
(this as MutableList<T>).remove(index, count)
}

internal actual inline fun <T> PlatformList<T>.move(from: Int, to: Int, count: Int) {
// Force resolution to the generated extension.
(this as MutableList<T>).move(from, to, count)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE")

package app.cash.redwood.compose

import androidx.collection.MutableScatterSet
import androidx.collection.mutableScatterSetOf

internal actual typealias PlatformSet<T> = MutableScatterSet<T>

internal actual inline fun <T> platformSetOf(): PlatformSet<T> {
return mutableScatterSetOf()
}

internal actual inline operator fun <T> PlatformSet<T>.plusAssign(element: T) {
plusAssign(element)
}

internal actual inline fun <T> PlatformSet<T>.forEach(block: (T) -> Unit) {
forEach(block)
}

internal actual inline fun <T> PlatformSet<T>.clear() {
clear()
}

0 comments on commit e0989f3

Please sign in to comment.