stop generating expression resolvers outside RuntimeStore

commit_hash:bd6db1f47f1cf7b663f3b6b69526fab5dc32b125
This commit is contained in:
i-ts
2025-04-04 19:27:42 +03:00
parent 3b4192b078
commit ef505e4731
8 changed files with 74 additions and 28 deletions
@@ -2,6 +2,7 @@ package com.yandex.div.core.expression
import com.yandex.div.core.Disposable
import com.yandex.div.core.ObserverList
import com.yandex.div.core.expression.local.RuntimeStore
import com.yandex.div.core.expression.variables.ConstantsProvider
import com.yandex.div.core.expression.variables.VariableAndConstantController
import com.yandex.div.core.expression.variables.VariableController
@@ -26,10 +27,15 @@ import com.yandex.div.json.typeMismatch
import org.json.JSONObject
internal class ExpressionResolverImpl(
/**
* Unique path by which resolver could be found at [RuntimeStore].
*/
internal val path: String,
internal val runtimeStore: RuntimeStore,
private val variableController: VariableController,
private val evaluator: Evaluator,
private val errorCollector: ErrorCollector,
private val onCreateCallback: OnCreateCallback
private val onCreateCallback: OnCreateCallback,
) : ExpressionResolver {
private val evaluationsCache = mutableMapOf<String, Any>()
@@ -230,9 +236,12 @@ internal class ExpressionResolverImpl(
}
}
operator fun plus(constants: ConstantsProvider): ExpressionResolverImpl {
internal fun withConstants(pathSegment: String,
constants: ConstantsProvider): ExpressionResolverImpl {
val variableAndConstantController = VariableAndConstantController(variableController, constants)
return ExpressionResolverImpl(
path = this.path + "/" + pathSegment,
runtimeStore = this.runtimeStore,
variableController = variableAndConstantController,
evaluator = Evaluator(
evaluationContext = EvaluationContext(
@@ -254,6 +263,26 @@ internal class ExpressionResolverImpl(
}
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other !is ExpressionResolverImpl) {
return false
}
if (this.runtimeStore !== other.runtimeStore) {
return false
}
return path == other.path
}
override fun hashCode(): Int {
return 31 * path.hashCode() + runtimeStore.hashCode()
}
/**
* ExpressionResolverImpl may create new instance in 'plus' operator.
* To be able to registrate all created resolvers and their variables controllers
@@ -4,17 +4,14 @@ import com.yandex.div.core.DivViewFacade
import com.yandex.div.core.expression.local.RuntimeStore
import com.yandex.div.core.expression.triggers.TriggersController
import com.yandex.div.core.expression.variables.VariableController
import com.yandex.div.internal.Assert
import com.yandex.div.json.expressions.ExpressionResolver
internal class ExpressionsRuntime(
val expressionResolver: ExpressionResolver,
val expressionResolver: ExpressionResolverImpl,
val variableController: VariableController,
val triggersController: TriggersController? = null,
val functionProvider: FunctionProviderDecorator,
val runtimeStore: RuntimeStore,
) {
private val expressionResolverImpl get() = expressionResolver as? ExpressionResolverImpl
private var unsubscribed = true
fun clearBinding() {
@@ -28,9 +25,7 @@ internal class ExpressionsRuntime(
fun updateSubscriptions() {
if (unsubscribed) {
unsubscribed = false
expressionResolverImpl?.subscribeOnVariables() ?: run {
Assert.fail("ExpressionRuntime must have ExpressionResolverImpl as expressionResolver.")
}
expressionResolver.subscribeOnVariables()
variableController.restoreSubscriptions()
}
}
@@ -147,10 +147,12 @@ internal class ExpressionsRuntimeProvider @Inject constructor(
}
val expressionResolver = ExpressionResolverImpl(
variableController,
evaluator,
errorCollector,
callback,
path = "dataTag: '${tag.id}'",
runtimeStore = runtimeStore,
variableController = variableController,
evaluator = evaluator,
errorCollector = errorCollector,
onCreateCallback = callback,
)
val triggersController = TriggersController(
@@ -98,7 +98,7 @@ internal class RuntimeStore(
internal fun resolveRuntimeWith(
path: String,
div: Div,
div: Div?,
resolver: ExpressionResolver,
parentResolver: ExpressionResolver?,
): ExpressionsRuntime? {
@@ -157,6 +157,8 @@ internal class RuntimeStore(
val evaluator = Evaluator(evaluationContext)
val resolver = ExpressionResolverImpl(
path = path + "/" + (baseRuntime.expressionResolver as? ExpressionResolverImpl)?.path,
runtimeStore = this,
variableController = localVariableController,
evaluator = evaluator,
errorCollector = errorCollector,
@@ -185,7 +187,7 @@ internal class RuntimeStore(
private fun getRuntimeOrCreateChild(
path: String,
div: Div,
div: Div?,
existingRuntime: ExpressionsRuntime? = null,
parentResolver: ExpressionResolver? = null,
parentRuntime: ExpressionsRuntime? = null,
@@ -201,9 +203,9 @@ internal class RuntimeStore(
val parentRuntime = parentRuntime ?: parentResolver?.let { getRuntimeWithOrNull(it) }
val variables = div.value().variables?.toVariables()
val variableTriggers = div.value().variableTriggers
val functions = div.value().functions
val variables = div?.value()?.variables?.toVariables()
val variableTriggers = div?.value()?.variableTriggers
val functions = div?.value()?.functions
if (needLocalRuntime(variables, variableTriggers, functions)) {
return createChildRuntime(runtime, parentRuntime, path, variables, variableTriggers, functions)
@@ -3,6 +3,7 @@ package com.yandex.div.internal.core
import com.yandex.div.core.annotations.InternalApi
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.variables.ConstantsProvider
import com.yandex.div.internal.Assert
import com.yandex.div.internal.util.forEach
import com.yandex.div.internal.util.mapIndexedNotNull
import com.yandex.div.json.expressions.ExpressionResolver
@@ -69,7 +70,20 @@ private fun DivCollectionItemBuilder.getItemResolver(
dataElementName to validElement,
INDEX_VARIABLE_NAME to index.toLong()
))
return resolverImpl + localDataProvider
val itemResolver = resolverImpl.withConstants(
pathSegment = "${dataElement}:$index", localDataProvider)
val runtime = resolverImpl.runtimeStore.resolveRuntimeWith(
itemResolver.path,
div = null,
resolver = itemResolver,
parentResolver = resolverImpl,
)
return runtime?.expressionResolver ?: run {
Assert.fail(
"Failed to acquire ExpressionResolver from store! This may lead to leaks and errors!")
itemResolver
}
}
private fun Div.copy(id: String? = value().id): Div {
@@ -81,6 +81,8 @@ class ExpressionResolverImplTest {
)
private val underTest = ExpressionResolverImpl(
"",
mock(),
externalVariables,
Evaluator(evaluationContext),
mock()
@@ -88,6 +90,8 @@ class ExpressionResolverImplTest {
private val withFuncGetCallback = { callback: () -> Unit ->
ExpressionResolverImpl(
"",
mock(),
VariableControllerImpl(),
Evaluator(
EvaluationContext(
@@ -51,14 +51,14 @@ class RuntimeStoreTest {
underTest.putRuntime(runtimeFromCallback!!)
}
private val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
private val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
private val rootVariableController: VariableController = mock<VariableController>()
private val functionProvider = mock<FunctionProviderDecorator>()
private val divBase = mock<DivBase>()
private val div = mock<Div> {
on { value() } doReturn divBase
}
private val rootResolver = ExpressionResolverImpl(rootVariableController, evaluator, errorCollector, callback)
private val rootResolver = ExpressionResolverImpl("", mock(), rootVariableController, evaluator, errorCollector, callback)
private val rootRuntime: ExpressionsRuntime =
ExpressionsRuntime(rootResolver, rootVariableController, null, functionProvider, underTest)
@@ -74,7 +74,7 @@ class RuntimeStoreTest {
@Test
fun `setPathToRuntimeWith links path to created runtime`() {
val newResolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
val newResolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
underTest.resolveRuntimeWith(path.fullPath, div, newResolver, newResolver)
Assert.assertNotNull(underTest.getRuntimeWithOrNull(newResolver))
@@ -99,7 +99,7 @@ class RuntimeStoreTest {
@Test
fun `getOrCreateRuntime returns runtime for path if exist`() {
val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
val runtime = ExpressionsRuntime(resolver, mock(), null, functionProvider, underTest)
underTest.putRuntime(runtime, PATH, rootRuntime)
@@ -110,7 +110,7 @@ class RuntimeStoreTest {
@Test
fun `getOrCreateRuntime returns parent runtime for path if no variables provided`() {
val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
val parentVariableController = mock<VariableController>()
val runtime = ExpressionsRuntime(resolver, parentVariableController, null, functionProvider, underTest)
underTest.putRuntime(runtime, PARENT_PATH, rootRuntime)
@@ -121,7 +121,7 @@ class RuntimeStoreTest {
@Test
fun `getOrCreateRuntime returns wrapped parent runtime with new variables if new variables provided`() {
val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
val parentVariableController = mock<VariableController> {
on { getMutableVariable(PARENT_VARIABLE) } doReturn Variable.StringVariable(PARENT_VARIABLE, "123")
}
@@ -153,7 +153,7 @@ class RuntimeStoreTest {
@Test
fun `setPathToRuntimeWith links path to runtime`() {
val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback)
underTest.resolveRuntimeWith(path.fullPath, div, resolver, resolver)
Assert.assertNotNull(runtimeFromCallback)
@@ -174,7 +174,7 @@ class RuntimeStoreTest {
on { getMutableVariable(PARENT_VARIABLE) } doReturn Variable.StringVariable(PARENT_VARIABLE, "123")
}
val resolver = ExpressionResolverImpl(
parentVariableController, evaluator, errorCollector, callback
"", mock(), parentVariableController, evaluator, errorCollector, callback
)
underTest.resolveRuntimeWith(path.fullPath, div, resolver, resolver)
@@ -48,7 +48,7 @@ class TwoWayVariableBinderTest {
private val evaluator = mock<Evaluator> {
on { evaluationContext } doReturn evaluationContext
}
private val expressionResolver = ExpressionResolverImpl(mock(), evaluator, mock(), mock())
private val expressionResolver = ExpressionResolverImpl("", mock(), mock(), evaluator, mock(), mock())
private val expressionsRuntime = ExpressionsRuntime(expressionResolver, variableController, mock(), mock(), store)
private val expressionsRuntimeProvider = mock<ExpressionsRuntimeProvider> {
on { getOrCreate(any(), any(), any()) } doReturn expressionsRuntime