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.util.datalog;
006
007import edu.wpi.first.util.protobuf.Protobuf;
008import edu.wpi.first.util.protobuf.ProtobufBuffer;
009import java.io.IOException;
010import java.nio.ByteBuffer;
011import us.hebi.quickbuf.ProtoMessage;
012
013/**
014 * Log protobuf-encoded values.
015 *
016 * @param <T> value class
017 */
018public final class ProtobufLogEntry<T> extends DataLogEntry {
019  private ProtobufLogEntry(
020      DataLog log, String name, Protobuf<T, ?> proto, String metadata, long timestamp) {
021    super(log, name, proto.getTypeString(), metadata, timestamp);
022    m_buf = ProtobufBuffer.create(proto);
023    m_immutable = proto.isImmutable();
024    m_cloneable = proto.isCloneable();
025    log.addSchema(proto, timestamp);
026  }
027
028  /**
029   * Creates a protobuf-encoded log entry.
030   *
031   * @param <T> value class (inferred from proto)
032   * @param <MessageType> protobuf message type (inferred from proto)
033   * @param log datalog
034   * @param name name of the entry
035   * @param proto protobuf serialization implementation
036   * @param metadata metadata
037   * @param timestamp entry creation timestamp (0=now)
038   * @return ProtobufLogEntry
039   */
040  public static <T, MessageType extends ProtoMessage<?>> ProtobufLogEntry<T> create(
041      DataLog log, String name, Protobuf<T, MessageType> proto, String metadata, long timestamp) {
042    return new ProtobufLogEntry<>(log, name, proto, metadata, timestamp);
043  }
044
045  /**
046   * Creates a protobuf-encoded log entry.
047   *
048   * @param <T> value class (inferred from proto)
049   * @param <MessageType> protobuf message type (inferred from proto)
050   * @param log datalog
051   * @param name name of the entry
052   * @param proto protobuf serialization implementation
053   * @param metadata metadata
054   * @return ProtobufLogEntry
055   */
056  public static <T, MessageType extends ProtoMessage<?>> ProtobufLogEntry<T> create(
057      DataLog log, String name, Protobuf<T, MessageType> proto, String metadata) {
058    return create(log, name, proto, metadata, 0);
059  }
060
061  /**
062   * Creates a protobuf-encoded log entry.
063   *
064   * @param <T> value class (inferred from proto)
065   * @param <MessageType> protobuf message type (inferred from proto)
066   * @param log datalog
067   * @param name name of the entry
068   * @param proto protobuf serialization implementation
069   * @param timestamp entry creation timestamp (0=now)
070   * @return ProtobufLogEntry
071   */
072  public static <T, MessageType extends ProtoMessage<?>> ProtobufLogEntry<T> create(
073      DataLog log, String name, Protobuf<T, MessageType> proto, long timestamp) {
074    return create(log, name, proto, "", timestamp);
075  }
076
077  /**
078   * Creates a protobuf-encoded log entry.
079   *
080   * @param <T> value class (inferred from proto)
081   * @param <MessageType> protobuf message type (inferred from proto)
082   * @param log datalog
083   * @param name name of the entry
084   * @param proto protobuf serialization implementation
085   * @return ProtobufLogEntry
086   */
087  public static <T, MessageType extends ProtoMessage<?>> ProtobufLogEntry<T> create(
088      DataLog log, String name, Protobuf<T, MessageType> proto) {
089    return create(log, name, proto, 0);
090  }
091
092  /**
093   * Appends a record to the log.
094   *
095   * @param value Value to record
096   * @param timestamp Time stamp (0 to indicate now)
097   */
098  public void append(T value, long timestamp) {
099    try {
100      synchronized (m_buf) {
101        ByteBuffer bb = m_buf.write(value);
102        m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
103      }
104    } catch (IOException e) {
105      // ignore
106    }
107  }
108
109  /**
110   * Appends a record to the log.
111   *
112   * @param value Value to record
113   */
114  public void append(T value) {
115    append(value, 0);
116  }
117
118  /**
119   * Updates the last value and appends a record to the log if it has changed.
120   *
121   * <p>Note: the last value is local to this class instance; using update() with two instances
122   * pointing to the same underlying log entry name will likely result in unexpected results.
123   *
124   * @param value Value to record
125   * @param timestamp Time stamp (0 to indicate now)
126   */
127  public void update(T value, long timestamp) {
128    try {
129      synchronized (m_buf) {
130        if (m_immutable || m_cloneable) {
131          if (value.equals(m_lastValue)) {
132            return;
133          }
134          try {
135            if (m_immutable) {
136              m_lastValue = value;
137            } else {
138              m_lastValue = m_buf.getProto().clone(value);
139            }
140            ByteBuffer bb = m_buf.write(value);
141            m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
142            return;
143          } catch (CloneNotSupportedException e) {
144            // fall through
145          }
146        }
147        doUpdate(m_buf.write(value), timestamp);
148      }
149    } catch (IOException e) {
150      // ignore
151    }
152  }
153
154  /**
155   * Updates the last value and appends a record to the log if it has changed.
156   *
157   * <p>Note: the last value is local to this class instance; using update() with two instances
158   * pointing to the same underlying log entry name will likely result in unexpected results.
159   *
160   * @param value Value to record
161   */
162  public void update(T value) {
163    update(value, 0);
164  }
165
166  /**
167   * Gets whether there is a last value.
168   *
169   * <p>Note: the last value is local to this class instance and updated only with update(), not
170   * append().
171   *
172   * @return True if last value exists, false otherwise.
173   */
174  public boolean hasLastValue() {
175    synchronized (m_buf) {
176      if (m_immutable) {
177        return m_lastValue != null;
178      } else if (m_cloneable && m_lastValue != null) {
179        return true;
180      }
181      return m_lastValueBuf != null;
182    }
183  }
184
185  /**
186   * Gets the last value.
187   *
188   * <p>Note: the last value is local to this class instance and updated only with update(), not
189   * append().
190   *
191   * @return Last value, or null if none.
192   */
193  public T getLastValue() {
194    synchronized (m_buf) {
195      if (m_immutable) {
196        return m_lastValue;
197      } else if (m_cloneable && m_lastValue != null) {
198        try {
199          return m_buf.getProto().clone(m_lastValue);
200        } catch (CloneNotSupportedException e) {
201          // fall through
202        }
203      }
204      if (m_lastValueBuf == null) {
205        return null;
206      }
207      try {
208        T val = m_buf.read(m_lastValueBuf);
209        m_lastValueBuf.position(0);
210        return val;
211      } catch (IOException e) {
212        return null;
213      }
214    }
215  }
216
217  private void doUpdate(ByteBuffer bb, long timestamp) {
218    int len = bb.position();
219    bb.limit(len);
220    bb.position(0);
221    if (m_lastValueBuf == null || !bb.equals(m_lastValueBuf)) {
222      // update last value
223      if (m_lastValueBuf == null || m_lastValueBuf.limit() < len) {
224        m_lastValueBuf = ByteBuffer.allocate(len);
225      }
226      bb.get(m_lastValueBuf.array(), 0, len);
227      bb.position(0);
228      m_lastValueBuf.limit(len);
229
230      // append to log
231      m_log.appendRaw(m_entry, bb, 0, len, timestamp);
232    }
233  }
234
235  private final ProtobufBuffer<T, ?> m_buf;
236  private ByteBuffer m_lastValueBuf;
237  private final boolean m_immutable;
238  private final boolean m_cloneable;
239  private T m_lastValue;
240}