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