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}