Skip to content

Commit f2d7e78

Browse files
authored
feat: add caching to trip info drawer (#163)
1 parent 1a2f85b commit f2d7e78

1 file changed

Lines changed: 145 additions & 78 deletions

File tree

screen_renderer/src/main/java/org/obd/graphs/renderer/trip/TripInfoDrawer.kt

Lines changed: 145 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ import org.obd.graphs.mapRange
2828
import org.obd.graphs.renderer.AbstractDrawer
2929
import org.obd.graphs.renderer.MARGIN_END
3030
import org.obd.graphs.renderer.api.ScreenSettings
31+
import org.obd.graphs.renderer.cache.TextCache
3132
import org.obd.graphs.renderer.giulia.GiuliaDrawer
33+
import org.obd.graphs.toNumber
3234

3335
private const val CURRENT_MIN = 22f
3436
private const val CURRENT_MAX = 72f
@@ -37,27 +39,82 @@ private const val NEW_MIN = 0.6f
3739

3840
const val MAX_ITEM_IN_THE_ROW = 6
3941

40-
internal data class TripMetricConfig(
41-
val metric: Metric,
42+
internal class TripMetricDescriptor(
43+
val fetcher: (TripInfoDetails) -> Metric?,
4244
val castToInt: Boolean = false,
4345
val statsEnabled: Boolean = true,
4446
val unitEnabled: Boolean = true,
4547
val valueDoublePrecision: Int = 2,
4648
val statsDoublePrecision: Int = 2
4749
)
4850

51+
internal class BottomMetricDescriptor(
52+
val fetcher: (TripInfoDetails) -> Metric?,
53+
val castToInt: Boolean
54+
)
55+
56+
internal class TripInfoLayoutCache {
57+
val area = Rect()
58+
var valueTextSize: Float = 0f
59+
var textSizeBase: Float = 0f
60+
var bottomRowTextSizeBase: Float = 0f
61+
var bottomColWidth: Float = 0f
62+
var activeBottomMetricsCount: Int = -1
63+
64+
fun requiresLayoutUpdate(newArea: Rect, newBottomMetricsCount: Int): Boolean {
65+
return area != newArea || activeBottomMetricsCount != newBottomMetricsCount
66+
}
67+
}
68+
4969
@Suppress("NOTHING_TO_INLINE")
5070
internal class TripInfoDrawer(
5171
context: Context,
5272
settings: ScreenSettings
5373
) : AbstractDrawer(context, settings) {
5474
private val metricBuilder = MetricsBuilder()
55-
5675
private val giuliaDrawer = GiuliaDrawer(context, settings)
5776

77+
private val layoutCache = TripInfoLayoutCache()
78+
private val textCache = TextCache()
79+
private val defaultTypeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
80+
81+
private val topMetricDescriptors = listOf(
82+
TripMetricDescriptor({ info: TripInfoDetails -> info.airTemp }, castToInt = true),
83+
TripMetricDescriptor({ info: TripInfoDetails -> info.coolantTemp }, castToInt = true),
84+
TripMetricDescriptor({ info: TripInfoDetails -> info.oilTemp }, castToInt = true),
85+
TripMetricDescriptor({ info: TripInfoDetails -> info.exhaustTemp }, castToInt = true),
86+
TripMetricDescriptor({ info: TripInfoDetails -> info.gearboxOilTemp }, castToInt = true),
87+
TripMetricDescriptor({ info: TripInfoDetails -> info.distance?.let { d -> metricBuilder.buildDiff(d) } }, statsEnabled = false),
88+
TripMetricDescriptor({ info: TripInfoDetails -> info.fuellevel }, valueDoublePrecision = 1, statsDoublePrecision = 1),
89+
TripMetricDescriptor({ info: TripInfoDetails -> info.fuelConsumption }, unitEnabled = false, statsDoublePrecision = 1),
90+
TripMetricDescriptor({ info: TripInfoDetails -> info.batteryVoltage }),
91+
TripMetricDescriptor({ info: TripInfoDetails -> info.ibs }, castToInt = true),
92+
TripMetricDescriptor({ info: TripInfoDetails -> info.oilLevel }),
93+
TripMetricDescriptor({ info: TripInfoDetails -> info.totalMisfires }, castToInt = true, unitEnabled = false, statsEnabled = false),
94+
TripMetricDescriptor({ info: TripInfoDetails -> info.oilDegradation }, unitEnabled = false),
95+
TripMetricDescriptor({ info: TripInfoDetails -> info.engineSpeed }, unitEnabled = false),
96+
TripMetricDescriptor({ info: TripInfoDetails -> info.vehicleSpeed }, unitEnabled = false),
97+
TripMetricDescriptor({ info: TripInfoDetails -> info.gearEngaged }, unitEnabled = false)
98+
)
99+
100+
private val bottomMetricDescriptors = listOf(
101+
BottomMetricDescriptor({ info: TripInfoDetails -> info.intakePressure }, true),
102+
BottomMetricDescriptor({ info: TripInfoDetails -> info.oilPressure }, false),
103+
BottomMetricDescriptor({ info: TripInfoDetails -> info.torque }, true)
104+
)
105+
58106
override fun invalidate() {
59107
super.invalidate()
60108
giuliaDrawer.invalidate()
109+
textCache.clear()
110+
layoutCache.area.setEmpty()
111+
layoutCache.activeBottomMetricsCount = -1
112+
}
113+
114+
override fun recycle() {
115+
super.recycle()
116+
giuliaDrawer.recycle()
117+
textCache.clear()
61118
}
62119

63120
inline fun drawScreen(
@@ -67,51 +124,46 @@ internal class TripInfoDrawer(
67124
top: Float,
68125
tripInfo: TripInfoDetails
69126
) {
70-
val (valueTextSize, textSizeBase) = calculateFontSize(area)
71-
72-
val topMetrics =
73-
listOfNotNull(
74-
tripInfo.airTemp?.let { TripMetricConfig(it, castToInt = true) },
75-
tripInfo.coolantTemp?.let { TripMetricConfig(it, castToInt = true) },
76-
tripInfo.oilTemp?.let { TripMetricConfig(it, castToInt = true) },
77-
tripInfo.exhaustTemp?.let { TripMetricConfig(it, castToInt = true) },
78-
tripInfo.gearboxOilTemp?.let { TripMetricConfig(it, castToInt = true) },
79-
tripInfo.distance?.let { TripMetricConfig(metricBuilder.buildDiff(it), statsEnabled = false) },
80-
tripInfo.fuellevel?.let { TripMetricConfig(it, valueDoublePrecision = 1, statsDoublePrecision = 1) },
81-
tripInfo.fuelConsumption?.let { TripMetricConfig(it, unitEnabled = false, statsDoublePrecision = 1) },
82-
tripInfo.batteryVoltage?.let { TripMetricConfig(it) },
83-
tripInfo.ibs?.let { TripMetricConfig(it, castToInt = true) },
84-
tripInfo.oilLevel?.let { TripMetricConfig(it) },
85-
tripInfo.totalMisfires?.let { TripMetricConfig(it, castToInt = true, unitEnabled = false) },
86-
tripInfo.oilDegradation?.let { TripMetricConfig(it, unitEnabled = false) },
87-
tripInfo.engineSpeed?.let { TripMetricConfig(it, unitEnabled = false) },
88-
tripInfo.vehicleSpeed?.let { TripMetricConfig(it, unitEnabled = false) },
89-
tripInfo.gearEngaged?.let { TripMetricConfig(it, unitEnabled = false) }
90-
)
127+
var currentBottomCount = 0
128+
for (i in bottomMetricDescriptors.indices) {
129+
if (bottomMetricDescriptors[i].fetcher.invoke(tripInfo) != null) {
130+
currentBottomCount++
131+
}
132+
}
133+
134+
if (layoutCache.requiresLayoutUpdate(area, currentBottomCount)) {
135+
calculateLayout(area, tripInfo, currentBottomCount)
136+
}
137+
138+
val textSizeBase = layoutCache.textSizeBase
139+
val valueTextSize = layoutCache.valueTextSize
140+
val dynamicPadding = textSizeBase * 0.1f
141+
val x = maxItemWidth(area)
91142

92143
var rowTop = top + (textSizeBase * 0.3f)
93144
var colIndex = 0
94-
val x = maxItemWidth(area)
95-
val dynamicPadding = textSizeBase * 0.1f
96145

97-
topMetrics.forEach { config ->
146+
for (i in topMetricDescriptors.indices) {
147+
val descriptor = topMetricDescriptors[i]
148+
val metric = descriptor.fetcher.invoke(tripInfo) ?: continue
149+
98150
if (colIndex >= MAX_ITEM_IN_THE_ROW) {
99151
colIndex = 0
100152
rowTop += (textSizeBase * 1.8f)
101153
}
102154

103155
drawMetric(
104-
metric = config.metric,
156+
metric = metric,
105157
top = rowTop,
106158
left = left + (colIndex * x) + dynamicPadding,
107159
canvas = canvas,
108160
textSizeBase = textSizeBase,
109-
statsEnabled = config.statsEnabled,
110-
unitEnabled = config.unitEnabled,
161+
statsEnabled = descriptor.statsEnabled,
162+
unitEnabled = descriptor.unitEnabled,
111163
area = area,
112-
valueDoublePrecision = config.valueDoublePrecision,
113-
statsDoublePrecision = config.statsDoublePrecision,
114-
castToInt = config.castToInt
164+
valueDoublePrecision = descriptor.valueDoublePrecision,
165+
statsDoublePrecision = descriptor.statsDoublePrecision,
166+
castToInt = descriptor.castToInt
115167
)
116168
colIndex++
117169
}
@@ -128,42 +180,67 @@ internal class TripInfoDrawer(
128180

129181
rowTop += 6
130182

131-
val bottomMetrics =
132-
listOfNotNull(
133-
tripInfo.intakePressure?.let { Pair(it, true) },
134-
tripInfo.oilPressure?.let { Pair(it, false) },
135-
tripInfo.torque?.let { Pair(it, true) }
183+
var drawnBottomCount = 0
184+
for (i in bottomMetricDescriptors.indices) {
185+
val descriptor = bottomMetricDescriptors[i]
186+
val metric = descriptor.fetcher.invoke(tripInfo) ?: continue
187+
188+
drawBottomMetric(
189+
metric = metric,
190+
castToInt = descriptor.castToInt,
191+
left = left,
192+
index = drawnBottomCount,
193+
area = area,
194+
dynamicPadding = dynamicPadding,
195+
colWidth = layoutCache.bottomColWidth,
196+
canvas = canvas,
197+
rowTextSizeBase = layoutCache.bottomRowTextSizeBase,
198+
valueTextSize = valueTextSize,
199+
rowTop = rowTop
136200
)
201+
drawnBottomCount++
202+
}
203+
}
137204

138-
if (bottomMetrics.isNotEmpty()) {
139-
val itemsCount = bottomMetrics.size
140-
val colWidth = area.width() / itemsCount.toFloat()
205+
private fun calculateLayout(area: Rect, tripInfo: TripInfoDetails, validBottomMetricsCount: Int) {
206+
layoutCache.area.set(area)
207+
layoutCache.activeBottomMetricsCount = validBottomMetricsCount
141208

142-
var rowTextSizeBase = textSizeBase
143-
bottomMetrics.forEach { (metric, _) ->
144-
val description =
145-
metric.source.command.pid.longDescription
146-
?.takeIf { it.isNotEmpty() } ?: metric.source.command.pid.description
209+
val scaleRatio = getScaleRatio()
210+
val areaWidth = area.width()
211+
212+
layoutCache.valueTextSize = (areaWidth / 17f) * scaleRatio
213+
layoutCache.textSizeBase = (areaWidth / 22f) * scaleRatio
214+
215+
if (validBottomMetricsCount > 0) {
216+
val colWidth = areaWidth / validBottomMetricsCount.toFloat()
217+
layoutCache.bottomColWidth = colWidth
147218

219+
var rowTextSizeBase = layoutCache.textSizeBase
220+
221+
bottomMetricDescriptors.forEach { descriptor ->
222+
val metric = descriptor.fetcher.invoke(tripInfo) ?: return@forEach
223+
val pid = metric.source.command.pid
224+
225+
val description = pid.longDescription?.takeIf { it.isNotEmpty() } ?: pid.description
148226
val longestLine = description.split("\n").maxByOrNull { it.length } ?: description
149-
titlePaint.textSize = textSizeBase
227+
228+
titlePaint.textSize = layoutCache.textSizeBase
150229
val titleWidth = getTextWidth(longestLine, titlePaint)
151230
val maxTitleWidth = colWidth * 0.75f
152231

153232
if (titleWidth > maxTitleWidth && titleWidth > 0f) {
154233
val scaleFactor = maxTitleWidth / titleWidth
155-
rowTextSizeBase = minOf(rowTextSizeBase, textSizeBase * scaleFactor)
234+
rowTextSizeBase = minOf(rowTextSizeBase, layoutCache.textSizeBase * scaleFactor)
156235
}
157236
}
158-
159-
bottomMetrics.forEachIndexed { index, paired ->
160-
drawMetric(paired, left, index, area, dynamicPadding, colWidth, canvas, rowTextSizeBase, valueTextSize, rowTop)
161-
}
237+
layoutCache.bottomRowTextSizeBase = rowTextSizeBase
162238
}
163239
}
164240

165-
fun drawMetric(
166-
paired: Pair<Metric, Boolean>,
241+
fun drawBottomMetric(
242+
metric: Metric,
243+
castToInt: Boolean,
167244
left: Float,
168245
index: Int,
169246
area: Rect,
@@ -174,14 +251,9 @@ internal class TripInfoDrawer(
174251
valueTextSize: Float,
175252
rowTop: Float
176253
) {
177-
val metric = paired.first
178-
val castToInt = paired.second
179-
180254
val metricLeft = left + (index * colWidth) + dynamicPadding
181255
val metricRight = metricLeft + colWidth - dynamicPadding
182-
183256
val valueLeft = metricRight - MARGIN_END
184-
185257
val boundedArea = Rect(metricLeft.toInt(), area.top, metricRight.toInt(), area.bottom)
186258

187259
giuliaDrawer.drawMetric(
@@ -197,15 +269,6 @@ internal class TripInfoDrawer(
197269
)
198270
}
199271

200-
private inline fun calculateFontSize(area: Rect): Pair<Float, Float> {
201-
val scaleRatio = getScaleRatio()
202-
203-
val areaWidth = area.width()
204-
val valueTextSize = (areaWidth / 17f) * scaleRatio
205-
val textSizeBase = (areaWidth / 22f) * scaleRatio
206-
return Pair(valueTextSize, textSizeBase)
207-
}
208-
209272
private inline fun getScaleRatio() =
210273
settings.getTripInfoScreenSettings().fontSize.toFloat().mapRange(
211274
CURRENT_MIN,
@@ -220,28 +283,29 @@ internal class TripInfoDrawer(
220283
top: Float,
221284
textSize: Float,
222285
left: Float,
223-
typeface: Typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL),
224286
statsEnabled: Boolean,
225287
unitEnabled: Boolean,
226288
area: Rect,
227289
valueDoublePrecision: Int = 2,
228290
statsDoublePrecision: Int = 2,
229291
castToInt: Boolean = false
230292
) {
231-
valuePaint.typeface = typeface
293+
valuePaint.typeface = defaultTypeface
232294
valuePaint.color = valueColorScheme(metric)
233-
234295
valuePaint.setShadowLayer(80f, 0f, 0f, Color.WHITE)
235296
valuePaint.textSize = textSize
236-
val text = metric.source.format(castToInt = castToInt, precision = valueDoublePrecision)
297+
298+
val text = textCache.value.get(metric.pid.id, metric.source.toNumber()) {
299+
metric.source.format(castToInt = castToInt, precision = valueDoublePrecision)
300+
}
237301

238302
val textPadding = textSize * 0.05f
239303

240304
canvas.drawText(text, left, top, valuePaint)
241305
var textWidth = getTextWidth(text, valuePaint) + textPadding
242306

243307
if (unitEnabled) {
244-
metric.source.command.pid.units.let {
308+
metric.source.command.pid.units?.let {
245309
valuePaint.color = Color.LTGRAY
246310
valuePaint.textSize = (textSize * 0.4).toFloat()
247311
canvas.drawText(it, (left + textWidth), top, valuePaint)
@@ -253,8 +317,12 @@ internal class TripInfoDrawer(
253317
valuePaint.textSize = (textSize * 0.60).toFloat()
254318
val pid = metric.pid
255319

256-
val minText = metric.min.format(pid = pid, precision = statsDoublePrecision, castToInt = castToInt)
257-
val maxText = metric.max.format(pid = pid, precision = statsDoublePrecision, castToInt = castToInt)
320+
val minText = textCache.min.get(pid.id, metric.min) {
321+
metric.min.format(pid = pid, precision = statsDoublePrecision, castToInt = castToInt)
322+
}
323+
val maxText = textCache.max.get(pid.id, metric.max) {
324+
metric.max.format(pid = pid, precision = statsDoublePrecision, castToInt = castToInt)
325+
}
258326

259327
val minWidth = getTextWidth(minText, valuePaint)
260328
val maxWidth = getTextWidth(maxText, valuePaint)
@@ -293,12 +361,11 @@ internal class TripInfoDrawer(
293361
castToInt: Boolean = false
294362
) {
295363
drawValue(
296-
canvas,
297-
metric,
364+
canvas = canvas,
365+
metric = metric,
298366
top = top,
299367
textSize = textSizeBase * 0.8f,
300368
left = left,
301-
typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL),
302369
statsEnabled = statsEnabled,
303370
unitEnabled = unitEnabled,
304371
area = area,

0 commit comments

Comments
 (0)