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>(
25     val name: String,
26     private val captureFn: (T) -> DataPoint<V>,
27 ) {
capturenull28     fun capture(observed: T) = captureFn(observed)
29 }
30 
31 class TimeSeriesCaptureScope<T>(
32     private val observing: T?,
33     private val valueCollector: MutableMap<String, MutableList<DataPoint<*>>>,
34 ) {
35 
36     /**
37      * Records a [DataPoint] from [observing], extracted [using] the specified [FeatureCapture] and
38      * stored in the time-series as [name].
39      *
40      * If the backing [observing] object cannot be resolved during an animation frame,
41      * `DataPoint.notFound` is recorded in the time-series.
42      *
43      * @param using extracts a [DataPoint] from [observing]
44      * @param name unique, human-readable label under which the feature is stored in the time-series
45      */
46     fun feature(using: FeatureCapture<in T, *>, name: String = using.name) {
47         val dataPoint = if (observing != null) using.capture(observing) else DataPoint.notFound()
48         valueCollector.computeIfAbsent(name) { mutableListOf() }.add(dataPoint)
49     }
50 
51     /**
52      * Captures features on other, related objects.
53      *
54      * @param resolveRelated finds the related object on which to capture features, invoked once per
55      *   animation frame. Can return null if the related object is not currently available in the
56      *   scene.
57      * @param nestedTimeSeriesCapture captures features on the related object.
58      */
59     fun <U> on(
60         resolveRelated: (T) -> U?,
61         nestedTimeSeriesCapture: TimeSeriesCaptureScope<U>.() -> Unit
62     ) {
63         with(TimeSeriesCaptureScope(observing?.let(resolveRelated), valueCollector)) {
64             nestedTimeSeriesCapture()
65         }
66     }
67 }
68