Skip to content

Commit

Permalink
feat: added scope
Browse files Browse the repository at this point in the history
  • Loading branch information
y9san9 committed Dec 21, 2023
1 parent 42b0035 commit ee8712d
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 8 deletions.
4 changes: 1 addition & 3 deletions core/src/commonMain/kotlin/app/meetacy/di/DI.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package app.meetacy.di

import app.meetacy.di.annotation.DIDsl
import app.meetacy.di.builder.DIBuilder
import app.meetacy.di.builder.di
import app.meetacy.di.dependency.*
import kotlin.reflect.KType
import kotlin.reflect.typeOf
Expand Down Expand Up @@ -38,7 +36,7 @@ public class DI private constructor(
val provider = provider(key)
val dependency = DependencyPair(key, provider)
val subDI = subDI(dependency.key)
return dependency.provider.createNewInstance(subDI)
return dependency.provider.getInstance(subDI)
}

private fun checkRecursive(dependencyKey: DependencyKey<*>) {
Expand Down
22 changes: 22 additions & 0 deletions core/src/commonMain/kotlin/app/meetacy/di/builder/DIBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,28 @@ public class DIBuilder(dependencies: Dependencies) {
)
}

public inline fun <reified T> scoped(
name: String?,
crossinline builder: ScopedBuilder<T>.() -> Unit
) {
val key = DependencyKey<T>(
type = typeOf<T>(),
name = name
)
val provider = ScopedBuilder<T>().apply(builder).build()
register(key, provider)
}

public inline fun <reified T> scoped(
noinline builder: ScopedBuilder<T>.() -> Unit
): DIBuilderScopedDelegate<T> {
return DIBuilderScopedDelegate(
di = this,
type = typeOf<T>(),
builder = builder
)
}

public fun build(): DI = DI(
dependencies = Dependencies(dependencies.toList())
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,23 @@ public class DIBuilderFactoryDelegate<T>(
return ReadOnlyProperty { _, _ -> }
}
}

public class DIBuilderScopedDelegate<T>(
private val di: DIBuilder,
private val type: KType,
private val builder: ScopedBuilder<T>.() -> Unit
) {
public operator fun provideDelegate(
thisRef: Any?,
property: KProperty<*>
): ReadOnlyProperty<Any?, Unit> {
di.register(
key = DependencyKey(
type = type,
name = property.name
),
provider = ScopedBuilder<T>().apply(builder).build()
)
return ReadOnlyProperty { _, _ -> }
}
}
32 changes: 32 additions & 0 deletions core/src/commonMain/kotlin/app/meetacy/di/builder/ScopedBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package app.meetacy.di.builder

import app.meetacy.di.DI
import app.meetacy.di.dependency.DependencyProvider
import app.meetacy.di.scope.Scope
import app.meetacy.di.scope.scopeOf

public class ScopedBuilder<T> {
private var scope: Scope? = null
private var factory: DependencyProvider<T>? = null

public fun keepWhile(
block: ScopeBuilder.() -> Scope.Dependency
) {
scope = scopeOf { ScopeBuilder(it).run(block) }
}

public fun factory(
block: DI.() -> T
) {
factory = DependencyProvider(block)
}

public fun build(): DependencyProvider<T> = DependencyProvider.Scoped(
scope ?: error("Scope was not initialized, call keepWhile function to setup scope"),
factory ?: error("Factory was not initialized, call factory function to setup factory")
)
}

public class ScopeBuilder(public val di: DI) {
public fun retained(vararg any: Any?): Scope.Dependency = Scope.Dependency(any.toList())
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
package app.meetacy.di.dependency

import app.meetacy.di.DI
import app.meetacy.di.internal.AtomicReference
import app.meetacy.di.internal.updateAndGet
import app.meetacy.di.scope.Scope

public fun interface DependencyProvider<out T> {

public fun createNewInstance(di: DI): T
public fun getInstance(di: DI): T

public class Singleton<out T>(
private val base: DependencyProvider<T>
) : DependencyProvider<T> {
private lateinit var di: DI
private val value by lazy { base.createNewInstance(di) }
private val value by lazy { base.getInstance(di) }

override fun createNewInstance(di: DI): T {
override fun getInstance(di: DI): T {
this.di = di
return value
}
}

public class Scoped<out T>(
private val scope: Scope,
private val base: DependencyProvider<T>
) : DependencyProvider<T> {
private val reference = AtomicReference<Value<T>?>(value = null)

override fun getInstance(di: DI): T = reference.updateAndGet { value ->
val scopeDependency = scope.dependency.getInstance(di)

// if nothing is initialized, we should initialize one
if (value == null) {
val instance = base.getInstance(di)
return@updateAndGet Value(scopeDependency, instance)
}

// if there was already something in there, we should check if scope dependency is the same
if (value.currentScope == scopeDependency) return@updateAndGet value

// dependency changed, so we need to refresh factory
val instance = base.getInstance(di)
Value(scopeDependency, instance)
}.instance

private class Value<out T>(
val currentScope: Scope.Dependency,
val instance: T
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package app.meetacy.di.internal

internal expect class AtomicReference<T>(value: T) {
var value: T
fun compareAndSet(expected: T, new: T): Boolean
}

internal inline fun <T, T2 : T> AtomicReference<T>.updateAndGet(transform: (T) -> T2): T2 {
while (true) {
val new = transform(value)
if (compareAndSet(value, new)) return new
}
}
25 changes: 25 additions & 0 deletions core/src/commonMain/kotlin/app/meetacy/di/scope/Scope.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package app.meetacy.di.scope

import app.meetacy.di.dependency.DependencyKey
import app.meetacy.di.dependency.DependencyProvider

/**
* As long as the dependencies do not change, the scope remains.
* When dependencies change, the new dependency that scoped to this scope
* will be created.
*/
public data class Scope(
public val dependency: DependencyProvider<Dependency>
) {
public data class Dependency(public val underlying: Any?) {
override fun toString(): String = "Scope.Dependency(underlying=$underlying)"
}
}

public fun scopeOf(provider: DependencyProvider<*>): Scope {
return Scope { di -> Scope.Dependency(provider.getInstance(di)) }
}

public fun scopeOf(key: DependencyKey<*>): Scope {
return Scope { di -> Scope.Dependency(di.get(key)) }
}
14 changes: 14 additions & 0 deletions core/src/iosMain/kotlin/app/meetacy/di/internal/AtomicReference.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.meetacy.di.internal

import kotlin.native.concurrent.AtomicReference

internal actual class AtomicReference<T> actual constructor(
value: T
) {
private val delegate = AtomicReference(value)

actual var value: T by delegate::value

actual fun compareAndSet(expected: T, new: T): Boolean =
delegate.compareAndSet(expected, new)
}
14 changes: 14 additions & 0 deletions core/src/jsMain/kotlin/app/meetacy/di/internal/AtomicReference.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.meetacy.di.internal

/**
* For now, JS is single-threaded
*/
internal actual class AtomicReference<T> actual constructor(
actual var value: T
) {
actual fun compareAndSet(expected: T, new: T): Boolean {
if (value !== expected) return false
value = new
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ public class WeakDependencyProvider<out T>(
) : DependencyProvider<T> {
private var reference: WeakReference<T>? = null

override fun createNewInstance(di: DI): T {
override fun getInstance(di: DI): T {
val oldValue = reference?.get()
if (oldValue != null) return oldValue
synchronized(this) {
val value = reference?.get()
if (value != null) return value

val newValue = base.createNewInstance(di)
val newValue = base.getInstance(di)
reference = WeakReference(newValue)
return newValue
}
Expand Down
14 changes: 14 additions & 0 deletions core/src/jvmMain/kotlin/app/meetacy/di/internal/AtomicReference.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.meetacy.di.internal

import java.util.concurrent.atomic.AtomicReference

internal actual class AtomicReference<T> actual constructor(value: T) {
private val delegate = AtomicReference(value)

actual var value: T
get() = delegate.get()
set(value) = delegate.set(value)

actual fun compareAndSet(expected: T, new: T): Boolean =
delegate.compareAndSet(expected, new)
}

0 comments on commit ee8712d

Please sign in to comment.