001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.epilogue.logging;
006
007import edu.wpi.first.util.protobuf.Protobuf;
008import edu.wpi.first.util.struct.Struct;
009import java.util.Arrays;
010import java.util.HashMap;
011import java.util.Map;
012import java.util.Objects;
013import us.hebi.quickbuf.ProtoMessage;
014
015/**
016 * A backend implementation that only logs data when it changes. Useful for keeping bandwidth and
017 * file sizes down. However, because it still needs to check that data has changed, it cannot avoid
018 * expensive sensor reads.
019 */
020public class LazyBackend implements EpilogueBackend {
021  private final EpilogueBackend m_backend;
022
023  // Keep a record of the most recent value written to each entry
024  // Note that this may duplicate a lot of data, and will box primitives.
025  private final Map<String, Object> m_previousValues = new HashMap<>();
026  private final Map<String, NestedBackend> m_subLoggers = new HashMap<>();
027
028  /**
029   * Creates a new lazy backend wrapper around another backend.
030   *
031   * @param backend the backend to delegate to
032   */
033  public LazyBackend(EpilogueBackend backend) {
034    this.m_backend = backend;
035  }
036
037  @Override
038  public EpilogueBackend lazy() {
039    // Already lazy, don't need to wrap it again
040    return this;
041  }
042
043  @Override
044  public EpilogueBackend getNested(String path) {
045    if (!m_subLoggers.containsKey(path)) {
046      var nested = new NestedBackend(path, this);
047      m_subLoggers.put(path, nested);
048      return nested;
049    }
050
051    return m_subLoggers.get(path);
052  }
053
054  @Override
055  public void log(String identifier, int value) {
056    var previous = m_previousValues.get(identifier);
057
058    if (previous instanceof Integer oldValue && oldValue == value) {
059      // no change
060      return;
061    }
062
063    m_previousValues.put(identifier, value);
064    m_backend.log(identifier, value);
065  }
066
067  @Override
068  public void log(String identifier, long value) {
069    var previous = m_previousValues.get(identifier);
070
071    if (previous instanceof Long oldValue && oldValue == value) {
072      // no change
073      return;
074    }
075
076    m_previousValues.put(identifier, value);
077    m_backend.log(identifier, value);
078  }
079
080  @Override
081  public void log(String identifier, float value) {
082    var previous = m_previousValues.get(identifier);
083
084    if (previous instanceof Float oldValue && oldValue == value) {
085      // no change
086      return;
087    }
088
089    m_previousValues.put(identifier, value);
090    m_backend.log(identifier, value);
091  }
092
093  @Override
094  public void log(String identifier, double value) {
095    var previous = m_previousValues.get(identifier);
096
097    if (previous instanceof Double oldValue && oldValue == value) {
098      // no change
099      return;
100    }
101
102    m_previousValues.put(identifier, value);
103    m_backend.log(identifier, value);
104  }
105
106  @Override
107  public void log(String identifier, boolean value) {
108    var previous = m_previousValues.get(identifier);
109
110    if (previous instanceof Boolean oldValue && oldValue == value) {
111      // no change
112      return;
113    }
114
115    m_previousValues.put(identifier, value);
116    m_backend.log(identifier, value);
117  }
118
119  @Override
120  public void log(String identifier, byte[] value) {
121    var previous = m_previousValues.get(identifier);
122
123    if (previous instanceof byte[] oldValue && Arrays.equals(oldValue, value)) {
124      // no change
125      return;
126    }
127
128    m_previousValues.put(identifier, value.clone());
129    m_backend.log(identifier, value);
130  }
131
132  @Override
133  public void log(String identifier, int[] value) {
134    var previous = m_previousValues.get(identifier);
135
136    if (previous instanceof int[] oldValue && Arrays.equals(oldValue, value)) {
137      // no change
138      return;
139    }
140
141    m_previousValues.put(identifier, value.clone());
142    m_backend.log(identifier, value);
143  }
144
145  @Override
146  public void log(String identifier, long[] value) {
147    var previous = m_previousValues.get(identifier);
148
149    if (previous instanceof long[] oldValue && Arrays.equals(oldValue, value)) {
150      // no change
151      return;
152    }
153
154    m_previousValues.put(identifier, value.clone());
155    m_backend.log(identifier, value);
156  }
157
158  @Override
159  public void log(String identifier, float[] value) {
160    var previous = m_previousValues.get(identifier);
161
162    if (previous instanceof float[] oldValue && Arrays.equals(oldValue, value)) {
163      // no change
164      return;
165    }
166
167    m_previousValues.put(identifier, value.clone());
168    m_backend.log(identifier, value);
169  }
170
171  @Override
172  public void log(String identifier, double[] value) {
173    var previous = m_previousValues.get(identifier);
174
175    if (previous instanceof double[] oldValue && Arrays.equals(oldValue, value)) {
176      // no change
177      return;
178    }
179
180    m_previousValues.put(identifier, value.clone());
181    m_backend.log(identifier, value);
182  }
183
184  @Override
185  public void log(String identifier, boolean[] value) {
186    var previous = m_previousValues.get(identifier);
187
188    if (previous instanceof boolean[] oldValue && Arrays.equals(oldValue, value)) {
189      // no change
190      return;
191    }
192
193    m_previousValues.put(identifier, value.clone());
194    m_backend.log(identifier, value);
195  }
196
197  @Override
198  public void log(String identifier, String value) {
199    var previous = m_previousValues.get(identifier);
200
201    if (previous instanceof String oldValue && oldValue.equals(value)) {
202      // no change
203      return;
204    }
205
206    m_previousValues.put(identifier, value);
207    m_backend.log(identifier, value);
208  }
209
210  @Override
211  public void log(String identifier, String[] value) {
212    var previous = m_previousValues.get(identifier);
213
214    if (previous instanceof String[] oldValue && Arrays.equals(oldValue, value)) {
215      // no change
216      return;
217    }
218
219    m_previousValues.put(identifier, value.clone());
220    m_backend.log(identifier, value);
221  }
222
223  @Override
224  public <S> void log(String identifier, S value, Struct<S> struct) {
225    var previous = m_previousValues.get(identifier);
226
227    if (Objects.equals(previous, value)) {
228      // no change
229      return;
230    }
231
232    m_previousValues.put(identifier, value);
233    m_backend.log(identifier, value, struct);
234  }
235
236  @Override
237  public <S> void log(String identifier, S[] value, Struct<S> struct) {
238    var previous = m_previousValues.get(identifier);
239
240    if (previous instanceof Object[] oldValue && Arrays.equals(oldValue, value)) {
241      // no change
242      return;
243    }
244
245    m_previousValues.put(identifier, value.clone());
246    m_backend.log(identifier, value, struct);
247  }
248
249  @Override
250  public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) {
251    var previous = m_previousValues.get(identifier);
252
253    if (Objects.equals(previous, value)) {
254      // no change
255      return;
256    }
257
258    m_previousValues.put(identifier, value);
259    m_backend.log(identifier, value, proto);
260  }
261}