tagan.codegen-impl.1.4.1.source-code.AccessStrategyManager.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of codegen-impl Show documentation
Show all versions of codegen-impl Show documentation
Yatagan is a Dependency Injection framework, specializing on runtime performance and build speed. Supports code generation (apt/kapt/ksp) or reflection.
/*
* Copyright 2022 Yandex LLC
*
* 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 com.yandex.yatagan.codegen.impl
import com.yandex.yatagan.AssistedFactory
import com.yandex.yatagan.codegen.poetry.TypeSpecBuilder
import com.yandex.yatagan.core.graph.BindingGraph
import com.yandex.yatagan.core.graph.bindings.Binding
import com.yandex.yatagan.core.model.ConditionScope
import com.yandex.yatagan.core.model.DependencyKind
import com.yandex.yatagan.core.model.ScopeModel
import com.yandex.yatagan.core.model.component1
import com.yandex.yatagan.core.model.component2
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class AccessStrategyManager @Inject constructor(
private val thisGraph: BindingGraph,
private val options: ComponentGenerator.Options,
private val cachingStrategySingleThreadFactory: CachingStrategySingleThreadFactory,
private val cachingStrategyMultiThreadFactory: CachingStrategyMultiThreadFactory,
private val slotProviderStrategyFactory: SlotProviderStrategyFactory,
private val wrappingAccessorStrategyFactory: WrappingAccessorStrategyFactory,
private val onTheFlyScopedProviderCreationStrategyFactory: OnTheFlyScopedProviderCreationStrategyFactory,
private val onTheFlyUnscopedProviderCreationStrategyFactory: OnTheFlyUnscopedProviderCreationStrategyFactory,
private val conditionalAccessStrategyFactory: ConditionalAccessStrategyFactory,
) : ComponentGenerator.Contributor {
@AssistedFactory
interface CachingStrategySingleThreadFactory {
fun create(binding: Binding): CachingStrategySingleThread
}
@AssistedFactory
interface CachingStrategyMultiThreadFactory {
fun create(binding: Binding): CachingStrategyMultiThread
}
@AssistedFactory
interface SlotProviderStrategyFactory {
fun create(binding: Binding): SlotProviderStrategy
}
@AssistedFactory
interface OnTheFlyScopedProviderCreationStrategyFactory {
fun create(binding: Binding): OnTheFlyScopedProviderCreationStrategy
}
@AssistedFactory
interface OnTheFlyUnscopedProviderCreationStrategyFactory {
fun create(binding: Binding): OnTheFlyUnscopedProviderCreationStrategy
}
@AssistedFactory
interface WrappingAccessorStrategyFactory {
fun create(binding: Binding, underlying: AccessStrategy): WrappingAccessorStrategy
}
@AssistedFactory
interface ConditionalAccessStrategyFactory {
fun create(
binding: Binding,
underlying: AccessStrategy,
dependencyKind: DependencyKind,
): ConditionalAccessStrategy
}
/**
* A mapping between a local binding and its single dependent binding, if any.
* Required for inline optimization calculations.
*/
private val singleLocalDependentBindingCache: Map =
buildMap(thisGraph.localBindings.size) {
for (binding in thisGraph.localBindings.keys) {
val dependencies = if (binding.nonStaticConditionProviders.isNotEmpty()) {
binding.dependencies + binding.nonStaticConditionProviders
} else binding.dependencies
for ((node, _) in dependencies) {
val dependencyBinding = thisGraph.resolveBinding(node)
.takeIf { it.owner == thisGraph } ?: continue
if (dependencyBinding in this) {
// Not the first dependent binding, explicitly put `null` there,
// as it is no longer single
put(dependencyBinding, null)
} else {
put(dependencyBinding, binding)
}
}
}
}
private val strategies: Map = thisGraph.localBindings.entries.associateBy(
keySelector = { (binding, _) -> binding },
valueTransform = fun(entry: Map.Entry): AccessStrategy {
val (binding, usage) = entry
if (binding.conditionScope == ConditionScope.Never) {
return EmptyAccessStrategy
}
val strategy = if (binding.scopes.isNotEmpty()) {
when(detectDegenerateUsage(binding)) {
DegenerateBindingUsage.SingleLocalDirect -> {
inlineStrategy(
binding = binding,
usage = usage,
)
}
DegenerateBindingUsage.SingleLocalProvider,
DegenerateBindingUsage.SingleLocalLazy -> {
CompositeStrategy(
directStrategy = inlineStrategy(
binding = binding,
usage = usage,
),
lazyStrategy = onTheFlyScopedProviderCreationStrategyFactory.create(binding),
)
}
null -> {
val useMultiThreadCaching =
thisGraph.requiresSynchronizedAccess && ScopeModel.Reusable !in binding.scopes
CompositeStrategy(
directStrategy = if (useMultiThreadCaching) {
cachingStrategyMultiThreadFactory.create(binding)
} else {
cachingStrategySingleThreadFactory.create(binding)
},
lazyStrategy = if (usage.lazy + usage.provider > 0) {
slotProviderStrategyFactory.create(binding)
} else null
)
}
}
} else {
if (usage.lazy + usage.provider == 0) {
// Inline instance creation does the trick.
inlineStrategy(
binding = binding,
usage = usage,
)
} else {
CompositeStrategy(
directStrategy = inlineStrategy(
binding = binding,
usage = usage,
),
lazyStrategy = if (usage.lazy > 0) {
onTheFlyScopedProviderCreationStrategyFactory.create(binding)
} else null,
providerStrategy = if (usage.provider > 0) {
onTheFlyUnscopedProviderCreationStrategyFactory.create(binding)
} else null,
)
}
}
return if (usage.optional > 0) {
fun conditionalStrategy(kind: DependencyKind): ConditionalAccessStrategy {
return conditionalAccessStrategyFactory.create(
underlying = strategy,
binding = binding,
dependencyKind = kind,
)
}
CompositeStrategy(
directStrategy = strategy,
conditionalStrategy = conditionalStrategy(DependencyKind.Direct),
conditionalLazyStrategy = if (usage.optionalLazy > 0)
conditionalStrategy(DependencyKind.Lazy) else null,
conditionalProviderStrategy = if (usage.optionalProvider > 0)
conditionalStrategy(DependencyKind.Provider) else null,
)
} else strategy
},
)
override fun generate(builder: TypeSpecBuilder) {
strategies.entries.let { strategies ->
if (options.sortMethodsForTesting) strategies.sortedBy { it.key } else strategies
}.forEach { (_, strategy) ->
strategy.generateInComponent(builder)
}
}
fun strategyFor(binding: Binding): AccessStrategy {
assert(binding.owner == thisGraph)
return strategies[binding]!!
}
enum class DegenerateBindingUsage {
SingleLocalDirect,
SingleLocalLazy,
SingleLocalProvider,
}
private fun detectDegenerateUsage(
binding: Binding,
usage: BindingGraph.BindingUsage = thisGraph.localBindings[binding]!!
): DegenerateBindingUsage? {
// There's a possibility to use inline strategy (create on request) for scoped node if it can be proven,
// that the binding has *a single eager (direct/optional) usage as a dependency of a binding of the same
// scope*.
val potentialCase = with(usage) {
val directUsage = direct + optional
val providerUsage = provider + optionalProvider
val lazyUsage = lazy + optionalLazy
when {
directUsage == 1 && lazyUsage + providerUsage == 0 -> DegenerateBindingUsage.SingleLocalDirect
directUsage == 0 -> when {
lazyUsage == 0 && providerUsage == 1 -> DegenerateBindingUsage.SingleLocalProvider
lazyUsage == 1 && providerUsage == 0 -> DegenerateBindingUsage.SingleLocalLazy
else -> return null
}
else -> return null
}
}
val singleDependentBinding = singleLocalDependentBindingCache[binding] ?: return null
// Now we know for sure that the dependent binding is *single*.
// We're going to rustle through localBindings of the binding's owner graph in hope to find this single
// dependant binding. If we find it - cool, can use inline. Otherwise, this single dependant may be
// from the child graphs or entry-point(s)/member-injector(s), etc... - not correct to use inline.
if (singleDependentBinding.scopes.let {
// If it is scoped (cached), can use the optimization
it.isNotEmpty() &&
// If the binding is Reusable and the component requires MT-access,
// then we can't use the optimization as it *might* be created multiple times in contended
// MT environments - can't risk that.
(!singleDependentBinding.owner.requiresSynchronizedAccess || ScopeModel.Reusable !in it)
}) {
return potentialCase
}
// For unscoped dependent try to analyze transitively, if it's also only created once
return when (detectDegenerateUsage(singleDependentBinding)) {
DegenerateBindingUsage.SingleLocalDirect,
DegenerateBindingUsage.SingleLocalLazy
-> {
// The unscoped class itself will be created at most once, so it's ok to use optimization
potentialCase
}
DegenerateBindingUsage.SingleLocalProvider, null -> {
// Single provider for unscoped node can spawn multiple instances, no optimization
null
}
}
}
private fun inlineStrategy(
binding: Binding,
usage: BindingGraph.BindingUsage,
): AccessStrategy {
val inline = InlineCreationStrategy(
binding = binding,
)
if (binding.dependencies.isEmpty())
return inline
if (usage.provider + usage.lazy == 0) {
if (usage.direct == 1) {
return inline
}
} else {
if (usage.direct == 0) {
return inline
}
}
return wrappingAccessorStrategyFactory.create(
underlying = inline,
binding = binding,
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy