EntityList.kt

/*
 * Copyright 2022 The Android Open Source Project
 *
 * 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 androidx.compose.ui.node

import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.DrawModifier
import androidx.compose.ui.input.pointer.PointerInputModifier
import androidx.compose.ui.layout.OnPlacedModifier
import androidx.compose.ui.layout.OnRemeasuredModifier
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.semantics.SemanticsEntity
import androidx.compose.ui.semantics.SemanticsModifier

/**
 * A collection of [LayoutNodeEntity] elements. [LayoutNodeEntity] is a node in
 * a linked list and this contains the [head] of the list.
 *
 * This linked list structure makes it easier to execute recursing algorithms
 * using the [LayoutNodeEntity.next] without having to allocate any lambdas.
 */
@kotlin.jvm.JvmInline
internal value class EntityList(
    val entities: Array<LayoutNodeEntity<*, *>?> = arrayOfNulls(TypeCount)
) {
    /**
     * Add [LayoutNodeEntity] values for types that [modifier] supports that should be
     * added before the LayoutModifier.
     */
    fun addBeforeLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
        if (modifier is DrawModifier) {
            add(DrawEntity(layoutNodeWrapper, modifier), DrawEntityType.index)
        }
        if (modifier is PointerInputModifier) {
            add(PointerInputEntity(layoutNodeWrapper, modifier), PointerInputEntityType.index)
        }
        if (modifier is SemanticsModifier) {
            add(SemanticsEntity(layoutNodeWrapper, modifier), SemanticsEntityType.index)
        }
        if (modifier is ParentDataModifier) {
            add(SimpleEntity(layoutNodeWrapper, modifier), ParentDataEntityType.index)
        }
    }

    /**
     * Add [LayoutNodeEntity] values that must be added after the LayoutModifier.
     */
    fun addAfterLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
        if (modifier is OnPlacedModifier) {
            add(SimpleEntity(layoutNodeWrapper, modifier), OnPlacedEntityType.index)
        }
        if (modifier is OnRemeasuredModifier) {
            add(SimpleEntity(layoutNodeWrapper, modifier), RemeasureEntityType.index)
        }
    }

    private fun <T : LayoutNodeEntity<T, *>> add(entity: T, index: Int) {
        @Suppress("UNCHECKED_CAST")
        val head = entities[index] as T?
        entity.next = head
        entities[index] = entity
    }

    /**
     * The head of the linked list of [LayoutNodeEntity] elements of the type [entityType].
     */
    @Suppress("UNCHECKED_CAST")
    fun <T : LayoutNodeEntity<T, M>, M : Modifier> head(entityType: EntityType<T, M>): T? =
        entities[entityType.index] as T?

    /**
     * Returns `true` if there are any elements of the given type.
     */
    fun has(entityType: EntityType<*, *>): Boolean = entities[entityType.index] != null

    /**
     * Remove all entries from the list and call [LayoutNodeEntity.onDetach] on them.
     */
    fun clear() {
        forEach {
            if (it.isAttached) {
                it.onDetach()
            }
        }
        for (index in entities.indices) {
            entities[index] = null
        }
    }

    /**
     * Calls [block] on all entries.
     */
    inline fun forEach(block: (LayoutNodeEntity<*, *>) -> Unit) {
        entities.forEach { head ->
            var node = head
            while (node != null) {
                block(node)
                node = node.next
            }
        }
    }

    /**
     * Executes [block] over all entities with [entityType].
     */
    inline fun <T : LayoutNodeEntity<T, M>, M : Modifier> forEach(
        entityType: EntityType<T, M>,
        block: (T) -> Unit
    ) {
        entities[entityType.index].forEach(block)
    }

    private inline fun <T : LayoutNodeEntity<T, M>, M : Modifier> LayoutNodeEntity<*, *>?.forEach(
        block: (T) -> Unit
    ) {
        var node = this
        while (node != null) {
            @Suppress("UNCHECKED_CAST")
            block(node as T)
            node = node.next
        }
    }

    @kotlin.jvm.JvmInline
    value class EntityType<T : LayoutNodeEntity<T, M>, M : Modifier>(val index: Int)

    companion object {
        val DrawEntityType = EntityType<DrawEntity, DrawModifier>(0)
        val PointerInputEntityType = EntityType<PointerInputEntity, PointerInputModifier>(1)
        val SemanticsEntityType = EntityType<SemanticsEntity, SemanticsModifier>(2)
        val ParentDataEntityType =
            EntityType<SimpleEntity<ParentDataModifier>, ParentDataModifier>(3)
        @OptIn(ExperimentalComposeUiApi::class)
        val OnPlacedEntityType =
            EntityType<SimpleEntity<OnPlacedModifier>, OnPlacedModifier>(4)
        val RemeasureEntityType =
            EntityType<SimpleEntity<OnRemeasuredModifier>, OnRemeasuredModifier>(5)

        private const val TypeCount = 6
    }
}