xref: /aosp_15_r20/platform_testing/libraries/motion/src/platform/test/motion/golden/TimeSeriesCapture.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package platform.test.motion.golden
18 
19 /**
20  * Captures a time-series feature of an observed [T].
21  *
22  * A [DataPoint] of type [V] is recorded at each frame.
23  */
24 class FeatureCapture<T, V : Any>(val name: String, private val captureFn: (T) -> DataPoint<V>) {
capturenull25     fun capture(observed: T) = captureFn(observed)
26 }
27 
28 class TimeSeriesCaptureScope<T>(
29     private val observing: T?,
30     private val valueCollector: MutableMap<String, MutableList<DataPoint<*>>>,
31 ) {
32 
33     /**
34      * Records a [DataPoint] from [observing], extracted [using] the specified [FeatureCapture] and
35      * stored in the time-series as [name].
36      *
37      * If the backing [observing] object cannot be resolved during an animation frame,
38      * `DataPoint.notFound` is recorded in the time-series.
39      *
40      * @param using extracts a [DataPoint] from [observing]
41      * @param name unique, human-readable label under which the feature is stored in the time-series
42      */
43     fun feature(using: FeatureCapture<in T, *>, name: String = using.name) {
44         val dataPoint = if (observing != null) using.capture(observing) else DataPoint.notFound()
45         valueCollector.computeIfAbsent(name) { mutableListOf() }.add(dataPoint)
46     }
47 
48     /**
49      * Captures features on other, related objects.
50      *
51      * @param resolveRelated finds the related object on which to capture features, invoked once per
52      *   animation frame. Can return null if the related object is not currently available in the
53      *   scene.
54      * @param nestedTimeSeriesCapture captures features on the related object.
55      */
56     fun <U> on(
57         resolveRelated: (T) -> U?,
58         nestedTimeSeriesCapture: TimeSeriesCaptureScope<U>.() -> Unit,
59     ) {
60         with(TimeSeriesCaptureScope(observing?.let(resolveRelated), valueCollector)) {
61             nestedTimeSeriesCapture()
62         }
63     }
64 }
65