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}