Skip to content

Commit

Permalink
API improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
omkar-tenkale committed Apr 29, 2024
1 parent 97048f2 commit c4a33bd
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.lifecycleScope
import dev.omkartenkale.nodal.Node.Companion.createRootNode
import dev.omkartenkale.nodal.compose.UI
import dev.omkartenkale.nodal.misc.RemovalRequest
import dev.omkartenkale.nodal.util.RootNodeUtil
import kotlinx.coroutines.launch
import kotlin.reflect.KClass

public abstract class NodalActivity : AppCompatActivity() {

Expand All @@ -19,6 +20,12 @@ public abstract class NodalActivity : AppCompatActivity() {
protected inline fun <reified T : Node> setContentNode(
container: FrameLayout = findViewById(android.R.id.content),
noinline dependencyDeclaration: DependencyDeclaration = {},
): Unit = setContentNode(T::class, container, dependencyDeclaration)

protected fun setContentNode(
klass: KClass<out Node>,
container: FrameLayout = findViewById(android.R.id.content),
dependencyDeclaration: DependencyDeclaration = {},
) {
// val finalNodeConfigBuilder: NodeConfigBuilder = {
// param<RemovalRequest>(RemovalRequest{ finish() })
Expand All @@ -28,9 +35,11 @@ public abstract class NodalActivity : AppCompatActivity() {
// apply(nodeConfigBuilder)
// }

contentNode = createRootNode<T>{
contentNode = createRootNode(
klass = klass,
nodalConfig = NodalConfig(true),
onRequestRemove = { finish() }) {
// provides<Timber> { Timber() }
// provides<TreeVisualiser> { TreeVisualiser() }
// provides<StringProvider> { StringProvider() }
// provides<AnalyticsLogger> { AnalyticsLogger() }
// provides<KoroutineDispatcher> { KoroutineDispatcher() }
Expand All @@ -52,19 +61,17 @@ public abstract class NodalActivity : AppCompatActivity() {
//
// providesSelf<RemovalRequest>(RemovalRequest{ finish() })

provides<NodalConfig>{ NodalConfig(true) }
provides<OnBackPressedDispatcher>{ onBackPressedDispatcher }
provides<UI>{ UI().also {
ui = it
container.addView(ComposeView(this@NodalActivity).also { setContent { ui.drawLayers() } })
} }
providesSelf<RemovalRequest> {
RemovalRequest {
finish()
provides<OnBackPressedDispatcher> { onBackPressedDispatcher }
provides<UI> {
UI().also {
ui = it
container.addView(ComposeView(this@NodalActivity).also { setContent { ui.drawLayers() } })
}
}
include(dependencyDeclaration)
}.apply { dispatchAdded() }
}.also {
RootNodeUtil.dispatchAdded(it)
}
container.addView(ComposeView(this).also { setContent { ui.drawLayers() } })
}

Expand Down Expand Up @@ -92,7 +99,7 @@ public abstract class NodalActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
if (::contentNode.isInitialized) {
contentNode.dispatchRemoved()
RootNodeUtil.dispatchRemoved(contentNode)
}
}
}
204 changes: 124 additions & 80 deletions nodal/src/commonMain/kotlin/dev.omkartenkale.nodal/Node.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package dev.omkartenkale.nodal

import dev.omkartenkale.nodal.compose.UI
import dev.omkartenkale.nodal.exceptions.DisallowedNodeAdditionException
import dev.omkartenkale.nodal.exceptions.NodeCreationException
import dev.omkartenkale.nodal.lifecycle.ChildChangedEvent
import dev.omkartenkale.nodal.lifecycle.ChildrenUpdatedEvent
import dev.omkartenkale.nodal.misc.RemovalRequest
import dev.omkartenkale.nodal.misc.instantiate
import dev.omkartenkale.nodal.plugin.NodalPlugin
import dev.omkartenkale.nodal.plugin.NodalPlugins
import dev.omkartenkale.nodal.plugin.NodeAddedEvent
import dev.omkartenkale.nodal.plugin.NodeCreatedEvent
import dev.omkartenkale.nodal.plugin.NodeRemovedEvent
import dev.omkartenkale.nodal.plugin.default.NodeTreeVisualiser
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlin.reflect.KClass

Expand All @@ -23,99 +29,78 @@ internal val nodeId = atomic(0)
public open class Node {

private lateinit var _scope: Scope
public val dependencies: Scope
protected val dependencies: Scope
get() = if (::_scope.isInitialized) {
_scope
} else error("Accessing dependencies in init block is not supported")

public companion object {
private fun instantiateNode(
klass: KClass<out Node>,
parentScope: Scope,
dependencyDeclaration: DependencyDeclaration
): Node = try {
val newNodeId = nodeId.getAndIncrement()
val nodeScope = Scope("${klass.simpleName}#$newNodeId", parentScope)
klass.instantiate().also {
it.init(nodeScope)
DependencyDeclarationDSL(nodeScope).apply(dependencyDeclaration)
.apply(it.providesDependencies)
}
} catch (t: Throwable) {
throw NodeCreationException(klass, t)
} else error("Accessing dependencies in init block is not supported, Wrap with doOnInit{ .. }")

private val onInitBlocks = mutableListOf<() -> Unit>()
public fun doOnInit(block: () -> Unit) {
if (::_scope.isInitialized) {
block()
} else {
onInitBlocks += block
}

public inline fun <reified T : Node> createRootNode(
noinline dependencyDeclaration: DependencyDeclaration
): T = createRootNode(T::class, dependencyDeclaration) as T

public fun createRootNode(
klass: KClass<out Node>, dependencyDeclaration: DependencyDeclaration
): Node = instantiateNode(klass, createRootScope(), dependencyDeclaration)
}

private fun init(scope: Scope) {
_scope = scope
}
private val _isAddedEvents: MutableStateFlow<Boolean> = MutableStateFlow(false)
public val isAddedEvents: StateFlow<Boolean> = _isAddedEvents

private val removalRequest: RemovalRequest by dependencies<RemovalRequest>()
public val plugins: NodalPlugins by dependencies<NodalPlugins>()
public var isDead: Boolean = false
private set

public open val providesDependencies: DependencyDeclaration = {}
public val stateChangedEvents: MutableStateFlow<NodeLifecycleState> =
MutableStateFlow(NodeLifecycleState.INITIALIZED)
public val childChangedEvents: MutableSharedFlow<ChildChangedEvent> = MutableSharedFlow()

protected var isAdded: Boolean = false
set(value) {
field = value
stateChangedEvents.value =
if (value) NodeLifecycleState.ADDED else NodeLifecycleState.REMOVED
}
private val _childrenUpdatedEvents: MutableSharedFlow<ChildrenUpdatedEvent> =
MutableSharedFlow()
public val childrenUpdatedEvents: SharedFlow<ChildrenUpdatedEvent> = _childrenUpdatedEvents

protected inline fun <reified T : Any> dependencies(): Lazy<T> = lazy {
dependencies.get<T>()
private fun init(scope: Scope) {
_scope = scope
}
// .also {
// if (dependencies.get<NodalConfig>().createEagerInstances) {
// it.value
// }
// }

private val _children = mutableListOf<Node>()
public val children: List<Node> = _children

protected var coroutineScope: CoroutineScope =
public var coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)

public inline fun <reified T : Node> addChild(noinline dependencyDeclaration: DependencyDeclaration = {}): T =
protected inline fun <reified T : Node> addChild(noinline dependencyDeclaration: DependencyDeclaration = {}): T =
addChild(T::class, dependencyDeclaration) as T

public fun addChild(
protected fun addChild(
klass: KClass<out Node>, dependencyDeclaration: DependencyDeclaration
): Node {
if (stateChangedEvents.value == NodeLifecycleState.REMOVED) {
if (isDead) {
throw DisallowedNodeAdditionException(this::class, klass)
}
val dependencyDeclaration: DependencyDeclaration = {
val mergedDependencyDeclaration: DependencyDeclaration = {
providesSelf<RemovalRequest> {
RemovalRequest {
removeChild(children.first())
removeChild(it)
}
}
include(dependencyDeclaration)
}
val node = instantiateNode(klass, dependencies, dependencyDeclaration)
val node = instantiateNode(klass, dependencies, mergedDependencyDeclaration)
plugins.all.forEach { it.onEvent(NodeCreatedEvent(this, node)) }
_children.add(node)
node.dispatchAdded()
coroutineScope.launch { childChangedEvents.emit(ChildChangedEvent.NodeAdded(node)) }
coroutineScope.launch {
_childrenUpdatedEvents.emit(ChildrenUpdatedEvent.NodeAdded(node))
plugins.all.forEach { it.onEvent(NodeAddedEvent(this@Node, node)) }
}
return node
}

public fun removeChild(node: Node) {
protected fun removeChild(node: Node) {
if (children.contains(node)) {
node.dispatchRemoved()
_children.remove(node)
coroutineScope.launch { childChangedEvents.emit(ChildChangedEvent.NodeRemoved(node)) }
coroutineScope.launch {
_childrenUpdatedEvents.emit(ChildrenUpdatedEvent.NodeRemoved(node))
plugins.all.forEach { it.onEvent(NodeRemovedEvent(this@Node, node)) }
}
} else error("Cant remove node as parent does not contain child. \n\tParent: ${this::class.simpleName} (${this::class.qualifiedName}), \n\tChild: ${node::class.simpleName} (${node::class.qualifiedName})")
}

Expand All @@ -124,31 +109,90 @@ public open class Node {
return true
}

public fun dispatchAdded() {
onAdded()
isAdded = true
public open fun onAdded() {}
internal fun dispatchAdded() {
coroutineScope.launch {
onAdded()
_isAddedEvents.emit(true)
}
}

public open fun onAdded(): Unit = Unit

public open fun onRemoved() {}
internal fun dispatchRemoved() {
children.forEach { it.dispatchRemoved() }
isAdded = false
onRemoved()
coroutineScope.cancel()
dependencies.close()
coroutineScope.launch {
onRemoved()
_isAddedEvents.emit(false)
children.forEach { it.dispatchRemoved() }
coroutineScope.cancel()
dependencies.close()
}
isDead = true
}

public open fun onRemoved(): Unit = Unit
protected fun removeSelf(): Unit = removalRequest.invoke()
public fun removeSelf(): Unit = dependencies.get<RemovalRequest>().invoke(this)

public fun Node.onAdded(block: () -> Unit) {
stateChangedEvents.filter { it == NodeLifecycleState.ADDED }.onEach { block() }
.launchIn(coroutineScope)
}
public companion object {
protected inline fun <reified T : Any> Node.dependencies(): Lazy<T> = lazy {
dependencies.get<T>()
}.also {
doOnInit {
if (dependencies.get<NodalConfig>().createEagerInstances) {
it.value
}
}
}

public fun Node.onRemoved(block: () -> Unit) {
stateChangedEvents.filter { it == NodeLifecycleState.REMOVED }.onEach { block() }
.launchIn(coroutineScope)
public val Node.ui: UI get() = dependencies.get<UI>()
private fun instantiateNode(
klass: KClass<out Node>,
parentScope: Scope,
dependencyDeclaration: DependencyDeclaration,
): Node = try {
val newNodeId = nodeId.getAndIncrement()
val nodeScope = Scope("${klass.simpleName}#$newNodeId", parentScope)
klass.instantiate().also {
it.init(nodeScope)
DependencyDeclarationDSL(nodeScope).apply(dependencyDeclaration)
.apply(it.providesDependencies)
it.onInitBlocks.forEach { it.invoke() }
}
} catch (t: Throwable) {
throw NodeCreationException(klass, t)
}

public inline fun <reified T : Node> createRootNode(
nodalConfig: NodalConfig,
plugins: List<NodalPlugin> = emptyList(),
noinline onRequestRemove: (Node) -> Unit,
noinline dependencyDeclaration: DependencyDeclaration,
): T = createRootNode(
T::class,
nodalConfig,
onRequestRemove,
plugins,
dependencyDeclaration
) as T

public fun createRootNode(
klass: KClass<out Node>,
nodalConfig: NodalConfig,
onRequestRemove: (Node) -> Unit,
plugins: List<NodalPlugin> = emptyList(),
dependencyDeclaration: DependencyDeclaration,
): Node {
val mergedDependencyDeclaration: DependencyDeclaration = {
val defaultPlugins = listOf(NodeTreeVisualiser())
provides { NodalPlugins(defaultPlugins + plugins) }
provides<NodalConfig> { nodalConfig }
providesSelf<RemovalRequest> {
RemovalRequest(onRequestRemove)
}

include(dependencyDeclaration)
}
return instantiateNode(klass, createRootScope(), mergedDependencyDeclaration).also { node ->
node.plugins.all.forEach { it.onEvent(NodeCreatedEvent(null, node)) }
}
}
}
}
17 changes: 13 additions & 4 deletions nodal/src/commonMain/kotlin/dev.omkartenkale.nodal/compose/UI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package dev.omkartenkale.nodal.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import dev.omkartenkale.nodal.Node
import dev.omkartenkale.nodal.Node.Companion.ui
import dev.omkartenkale.nodal.util.doOnRemoved
import kotlinx.coroutines.flow.MutableStateFlow

public class UI {
Expand All @@ -18,7 +21,9 @@ public class UI {
}

public fun draw(content: @Composable () -> Unit): Layer {
return Layer(content).also {
return Layer(content) {
layers.remove(it)
}.also {
layers.add(it)
}
}
Expand All @@ -27,16 +32,20 @@ public class UI {
focusState.emit(isFocused)
}

public class Layer(public val content: @Composable () -> Unit) {
internal lateinit var onDestroy: () -> Unit
public class Layer(public val content: @Composable () -> Unit, internal val onDestroy: (Layer)->Unit) {

@Composable
public fun draw() {
content()
}

public fun destroy() {
onDestroy()
onDestroy(this)
}
}
}

public fun Node.draw(content: @Composable () -> Unit) {
val layer = ui.draw(content)
doOnRemoved { layer.destroy() }
}
Loading

0 comments on commit c4a33bd

Please sign in to comment.