[wpiutil] DataLog: Add last value and change detection (#6674)

Update() checks/updates the last value and appends only if changed.
GetLastValue() gets the last value.

Also add OutputStream support to Java DataLogWriter.
This commit is contained in:
Peter Johnson
2024-07-21 13:08:15 -07:00
committed by GitHub
parent 57205c8d15
commit 4c7fe73f69
23 changed files with 3398 additions and 40 deletions

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.util.datalog;
import java.util.Arrays;
/** Log array of boolean values. */
public class BooleanArrayLogEntry extends DataLogEntry {
/** The data type for boolean array values. */
@@ -71,4 +73,79 @@ public class BooleanArrayLogEntry extends DataLogEntry {
public void append(boolean[] value) {
m_log.appendBooleanArray(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(boolean[] value, long timestamp) {
if (!equalsLast(value)) {
copyToLast(value);
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(boolean[] value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_lastValue != null;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or null if none.
*/
@SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
public synchronized boolean[] getLastValue() {
if (m_lastValue == null) {
return null;
}
return Arrays.copyOf(m_lastValue, m_lastValueLen);
}
private boolean equalsLast(boolean[] value) {
if (m_lastValue == null || m_lastValueLen != value.length) {
return false;
}
return Arrays.equals(m_lastValue, 0, value.length, value, 0, value.length);
}
private void copyToLast(boolean[] value) {
if (m_lastValue == null || m_lastValue.length < value.length) {
m_lastValue = Arrays.copyOf(value, value.length);
} else {
System.arraycopy(value, 0, m_lastValue, 0, value.length);
}
m_lastValueLen = value.length;
}
private boolean[] m_lastValue;
private int m_lastValueLen;
}

View File

@@ -71,4 +71,60 @@ public class BooleanLogEntry extends DataLogEntry {
public void append(boolean value) {
m_log.appendBoolean(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(boolean value, long timestamp) {
if (!m_hasLastValue || m_lastValue != value) {
m_lastValue = value;
m_hasLastValue = true;
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(boolean value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_hasLastValue;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or false if none.
*/
public synchronized boolean getLastValue() {
return m_lastValue;
}
private boolean m_hasLastValue;
private boolean m_lastValue;
}

View File

@@ -269,6 +269,12 @@ public class DataLog implements AutoCloseable {
setMetadata(entry, metadata, 0);
}
@Override
public void close() {
DataLogJNI.close(m_impl);
m_impl = 0;
}
/**
* Appends a raw record to the log.
*
@@ -318,12 +324,6 @@ public class DataLog implements AutoCloseable {
DataLogJNI.appendRaw(m_impl, entry, data, start, len, timestamp);
}
@Override
public void close() {
DataLogJNI.close(m_impl);
m_impl = 0;
}
/**
* Appends a boolean record to the log.
*

View File

@@ -46,6 +46,14 @@ public class DataLogJNI extends WPIUtilJNI {
*/
static native long fgCreate(String filename, String extraHeader) throws IOException;
/**
* Create a new Data Log foreground writer to a memory buffer.
*
* @param extraHeader extra header data
* @return data log writer implementation handle
*/
static native long fgCreateMemory(String extraHeader);
/**
* Explicitly flushes the log data to disk.
*
@@ -53,6 +61,16 @@ public class DataLogJNI extends WPIUtilJNI {
*/
static native void flush(long impl);
/**
* Flushes the log data to a memory buffer (only valid with fgCreateMemory data logs).
*
* @param impl data log background writer implementation handle
* @param buf output data buffer
* @param pos position in write buffer to start copying from
* @return Number of bytes written to buffer; 0 if no more to copy
*/
static native int copyWriteBuffer(long impl, byte[] buf, int pos);
/**
* Pauses appending of data records to the log. While paused, no data records are saved (e.g.
* AppendX is a no-op). Has no effect on entry starts / finishes / metadata changes.

View File

@@ -5,6 +5,7 @@
package edu.wpi.first.util.datalog;
import java.io.IOException;
import java.io.OutputStream;
/** A data log writer that flushes the data log to a file when flush() is called. */
public class DataLogWriter extends DataLog {
@@ -17,6 +18,8 @@ public class DataLogWriter extends DataLog {
*/
public DataLogWriter(String filename, String extraHeader) throws IOException {
super(DataLogJNI.fgCreate(filename, extraHeader));
m_os = null;
m_buf = null;
}
/**
@@ -28,4 +31,54 @@ public class DataLogWriter extends DataLog {
public DataLogWriter(String filename) throws IOException {
this(filename, "");
}
/**
* Construct a new Data Log with an output stream. Prefer the filename version if possible; this
* is much slower!
*
* @param os output stream
* @param extraHeader extra header data
*/
public DataLogWriter(OutputStream os, String extraHeader) {
super(DataLogJNI.fgCreateMemory(extraHeader));
m_os = os;
m_buf = new byte[kBufferSize];
}
/**
* Construct a new Data Log with an output stream.
*
* @param os output stream
*/
public DataLogWriter(OutputStream os) {
this(os, "");
}
/** Explicitly flushes the log data to disk. */
@Override
public void flush() {
DataLogJNI.flush(m_impl);
if (m_os == null) {
return;
}
try {
int pos = 0;
for (; ; ) {
int qty = DataLogJNI.copyWriteBuffer(m_impl, m_buf, pos);
if (qty == 0) {
break;
}
pos += qty;
m_os.write(m_buf, 0, qty);
}
m_os.flush();
} catch (IOException e) {
// ignore
}
}
private static final int kBufferSize = 16 * 1024;
private final OutputStream m_os;
private final byte[] m_buf;
}

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.util.datalog;
import java.util.Arrays;
/** Log array of double values. */
public class DoubleArrayLogEntry extends DataLogEntry {
/** The data type for double array values. */
@@ -71,4 +73,79 @@ public class DoubleArrayLogEntry extends DataLogEntry {
public void append(double[] value) {
m_log.appendDoubleArray(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(double[] value, long timestamp) {
if (!equalsLast(value)) {
copyToLast(value);
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(double[] value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_lastValue != null;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or false if none.
*/
@SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
public synchronized double[] getLastValue() {
if (m_lastValue == null) {
return null;
}
return Arrays.copyOf(m_lastValue, m_lastValueLen);
}
private boolean equalsLast(double[] value) {
if (m_lastValue == null || m_lastValueLen != value.length) {
return false;
}
return Arrays.equals(m_lastValue, 0, value.length, value, 0, value.length);
}
private void copyToLast(double[] value) {
if (m_lastValue == null || m_lastValue.length < value.length) {
m_lastValue = Arrays.copyOf(value, value.length);
} else {
System.arraycopy(value, 0, m_lastValue, 0, value.length);
}
m_lastValueLen = value.length;
}
private double[] m_lastValue;
private int m_lastValueLen;
}

View File

@@ -71,4 +71,60 @@ public class DoubleLogEntry extends DataLogEntry {
public void append(double value) {
m_log.appendDouble(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(double value, long timestamp) {
if (!m_hasLastValue || m_lastValue != value) {
m_lastValue = value;
m_hasLastValue = true;
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(double value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_hasLastValue;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or 0 if none.
*/
public synchronized double getLastValue() {
return m_lastValue;
}
boolean m_hasLastValue;
double m_lastValue;
}

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.util.datalog;
import java.util.Arrays;
/** Log array of float values. */
public class FloatArrayLogEntry extends DataLogEntry {
/** The data type for float array values. */
@@ -71,4 +73,79 @@ public class FloatArrayLogEntry extends DataLogEntry {
public void append(float[] value) {
m_log.appendFloatArray(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(float[] value, long timestamp) {
if (!equalsLast(value)) {
copyToLast(value);
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(float[] value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_lastValue != null;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or false if none.
*/
@SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
public synchronized float[] getLastValue() {
if (m_lastValue == null) {
return null;
}
return Arrays.copyOf(m_lastValue, m_lastValueLen);
}
private boolean equalsLast(float[] value) {
if (m_lastValue == null || m_lastValueLen != value.length) {
return false;
}
return Arrays.equals(m_lastValue, 0, value.length, value, 0, value.length);
}
private void copyToLast(float[] value) {
if (m_lastValue == null || m_lastValue.length < value.length) {
m_lastValue = Arrays.copyOf(value, value.length);
} else {
System.arraycopy(value, 0, m_lastValue, 0, value.length);
}
m_lastValueLen = value.length;
}
private float[] m_lastValue;
private int m_lastValueLen;
}

View File

@@ -71,4 +71,60 @@ public class FloatLogEntry extends DataLogEntry {
public void append(float value) {
m_log.appendFloat(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(float value, long timestamp) {
if (!m_hasLastValue || m_lastValue != value) {
m_lastValue = value;
m_hasLastValue = true;
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(float value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_hasLastValue;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or 0 if none.
*/
public synchronized float getLastValue() {
return m_lastValue;
}
boolean m_hasLastValue;
float m_lastValue;
}

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.util.datalog;
import java.util.Arrays;
/** Log array of integer values. */
public class IntegerArrayLogEntry extends DataLogEntry {
/** The data type for integer array values. */
@@ -71,4 +73,79 @@ public class IntegerArrayLogEntry extends DataLogEntry {
public void append(long[] value) {
m_log.appendIntegerArray(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(long[] value, long timestamp) {
if (!equalsLast(value)) {
copyToLast(value);
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(long[] value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_lastValue != null;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or false if none.
*/
@SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
public synchronized long[] getLastValue() {
if (m_lastValue == null) {
return null;
}
return Arrays.copyOf(m_lastValue, m_lastValueLen);
}
private boolean equalsLast(long[] value) {
if (m_lastValue == null || m_lastValueLen != value.length) {
return false;
}
return Arrays.equals(m_lastValue, 0, value.length, value, 0, value.length);
}
private void copyToLast(long[] value) {
if (m_lastValue == null || m_lastValue.length < value.length) {
m_lastValue = Arrays.copyOf(value, value.length);
} else {
System.arraycopy(value, 0, m_lastValue, 0, value.length);
}
m_lastValueLen = value.length;
}
private long[] m_lastValue;
private int m_lastValueLen;
}

View File

@@ -71,4 +71,60 @@ public class IntegerLogEntry extends DataLogEntry {
public void append(long value) {
m_log.appendInteger(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(long value, long timestamp) {
if (!m_hasLastValue || m_lastValue != value) {
m_lastValue = value;
m_hasLastValue = true;
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(long value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_hasLastValue;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or 0 if none.
*/
public synchronized long getLastValue() {
return m_lastValue;
}
boolean m_hasLastValue;
long m_lastValue;
}

View File

@@ -20,6 +20,8 @@ public final class ProtobufLogEntry<T> extends DataLogEntry {
DataLog log, String name, Protobuf<T, ?> proto, String metadata, long timestamp) {
super(log, name, proto.getTypeString(), metadata, timestamp);
m_buf = ProtobufBuffer.create(proto);
m_immutable = proto.isImmutable();
m_cloneable = proto.isCloneable();
log.addSchema(proto, timestamp);
}
@@ -113,5 +115,126 @@ public final class ProtobufLogEntry<T> extends DataLogEntry {
append(value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public void update(T value, long timestamp) {
try {
synchronized (m_buf) {
if (m_immutable || m_cloneable) {
if (value.equals(m_lastValue)) {
return;
}
try {
if (m_immutable) {
m_lastValue = value;
} else {
m_lastValue = m_buf.getProto().clone(value);
}
ByteBuffer bb = m_buf.write(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
return;
} catch (CloneNotSupportedException e) {
// fall through
}
}
doUpdate(m_buf.write(value), timestamp);
}
} catch (IOException e) {
// ignore
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(T value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public boolean hasLastValue() {
synchronized (m_buf) {
if (m_immutable) {
return m_lastValue != null;
} else if (m_cloneable && m_lastValue != null) {
return true;
}
return m_lastValueBuf != null;
}
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or null if none.
*/
public T getLastValue() {
synchronized (m_buf) {
if (m_immutable) {
return m_lastValue;
} else if (m_cloneable && m_lastValue != null) {
try {
return m_buf.getProto().clone(m_lastValue);
} catch (CloneNotSupportedException e) {
// fall through
}
}
if (m_lastValueBuf == null) {
return null;
}
try {
T val = m_buf.read(m_lastValueBuf);
m_lastValueBuf.position(0);
return val;
} catch (IOException e) {
return null;
}
}
}
private void doUpdate(ByteBuffer bb, long timestamp) {
int len = bb.position();
bb.limit(len);
bb.position(0);
if (m_lastValueBuf == null || !bb.equals(m_lastValueBuf)) {
// update last value
if (m_lastValueBuf == null || m_lastValueBuf.limit() < len) {
m_lastValueBuf = ByteBuffer.allocate(len);
}
bb.get(m_lastValueBuf.array(), 0, len);
bb.position(0);
m_lastValueBuf.limit(len);
// append to log
m_log.appendRaw(m_entry, bb, 0, len, timestamp);
}
}
private final ProtobufBuffer<T, ?> m_buf;
private ByteBuffer m_lastValueBuf;
private final boolean m_immutable;
private final boolean m_cloneable;
private T m_lastValue;
}

View File

@@ -5,6 +5,7 @@
package edu.wpi.first.util.datalog;
import java.nio.ByteBuffer;
import java.util.Arrays;
/** Log raw byte array values. */
public class RawLogEntry extends DataLogEntry {
@@ -125,7 +126,7 @@ public class RawLogEntry extends DataLogEntry {
/**
* Appends a record to the log.
*
* @param value Data to record; will send from value.position() to value.capacity()
* @param value Data to record; will send from value.position() to value.limit()
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(ByteBuffer value, long timestamp) {
@@ -135,7 +136,7 @@ public class RawLogEntry extends DataLogEntry {
/**
* Appends a record to the log.
*
* @param value Data to record; will send from value.position() to value.capacity()
* @param value Data to record; will send from value.position() to value.limit()
*/
public void append(ByteBuffer value) {
append(value, 0);
@@ -163,4 +164,204 @@ public class RawLogEntry extends DataLogEntry {
public void append(ByteBuffer value, int start, int len) {
append(value, start, len, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record; will send entire array contents
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(byte[] value, long timestamp) {
if (!equalsLast(value, 0, value.length)) {
copyToLast(value, 0, value.length);
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record; will send entire array contents
*/
public void update(byte[] value) {
update(value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Data to record
* @param start Start position of data (in byte array)
* @param len Length of data (must be less than or equal to value.length - offset)
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(byte[] value, int start, int len, long timestamp) {
if (!equalsLast(value, start, len)) {
copyToLast(value, start, len);
append(value, start, len, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Data to record
* @param start Start position of data (in byte array)
* @param len Length of data (must be less than or equal to value.length - offset)
*/
public void update(byte[] value, int start, int len) {
update(value, start, len, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Data to record; will send from value.position() to value.limit()
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(ByteBuffer value, long timestamp) {
if (!equalsLast(value)) {
int start = value.position();
int len = value.limit() - start;
copyToLast(value, start, len);
append(value, start, len, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Data to record; will send from value.position() to value.limit()
*/
public void update(ByteBuffer value) {
update(value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Data to record
* @param start Start position of data (in value buffer)
* @param len Length of data (must be less than or equal to value.length - offset)
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(ByteBuffer value, int start, int len, long timestamp) {
if (!equalsLast(value, start, len)) {
copyToLast(value, start, len);
append(value, start, len, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Data to record
* @param start Start position of data (in value buffer)
* @param len Length of data (must be less than or equal to value.length - offset)
*/
public void update(ByteBuffer value, int start, int len) {
update(value, start, len, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_lastValue != null;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or null if none.
*/
@SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
public synchronized byte[] getLastValue() {
if (m_lastValue == null) {
return null;
}
return Arrays.copyOf(m_lastValue.array(), m_lastValue.limit());
}
private boolean equalsLast(byte[] value, int start, int len) {
if (m_lastValue == null || m_lastValue.limit() != len) {
return false;
}
return Arrays.equals(m_lastValue.array(), 0, len, value, start, start + len);
}
@SuppressWarnings("PMD.SimplifyBooleanReturns")
private boolean equalsLast(ByteBuffer value) {
if (m_lastValue == null) {
return false;
}
return value.equals(m_lastValue);
}
private boolean equalsLast(ByteBuffer value, int start, int len) {
if (m_lastValue == null || m_lastValue.limit() != len) {
return false;
}
int origpos = value.position();
value.position(start);
int origlimit = value.limit();
value.limit(start + len);
boolean eq = value.equals(m_lastValue);
value.position(origpos);
value.limit(origlimit);
return eq;
}
private void copyToLast(byte[] value, int start, int len) {
if (m_lastValue == null || m_lastValue.limit() < len) {
m_lastValue = ByteBuffer.allocate(len);
}
System.arraycopy(value, start, m_lastValue.array(), 0, len);
m_lastValue.limit(len);
}
private void copyToLast(ByteBuffer value, int start, int len) {
if (m_lastValue == null || m_lastValue.limit() < len) {
m_lastValue = ByteBuffer.allocate(len);
}
int origpos = value.position();
value.position(start);
value.get(m_lastValue.array(), 0, len);
value.position(origpos);
m_lastValue.limit(len);
}
private ByteBuffer m_lastValue;
}

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.util.datalog;
import java.util.Arrays;
/** Log array of string values. */
public class StringArrayLogEntry extends DataLogEntry {
/** The data type for string array values. */
@@ -71,4 +73,79 @@ public class StringArrayLogEntry extends DataLogEntry {
public void append(String[] value) {
m_log.appendStringArray(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(String[] value, long timestamp) {
if (!equalsLast(value)) {
copyToLast(value);
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(String[] value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_lastValue != null;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or false if none.
*/
@SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
public synchronized String[] getLastValue() {
if (m_lastValue == null) {
return null;
}
return Arrays.copyOf(m_lastValue, m_lastValueLen);
}
private boolean equalsLast(String[] value) {
if (m_lastValue == null || m_lastValueLen != value.length) {
return false;
}
return Arrays.equals(m_lastValue, 0, value.length, value, 0, value.length);
}
private void copyToLast(String[] value) {
if (m_lastValue == null || m_lastValue.length < value.length) {
m_lastValue = Arrays.copyOf(value, value.length);
} else {
System.arraycopy(value, 0, m_lastValue, 0, value.length);
}
m_lastValueLen = value.length;
}
private String[] m_lastValue;
private int m_lastValueLen;
}

View File

@@ -96,4 +96,58 @@ public class StringLogEntry extends DataLogEntry {
public void append(String value) {
m_log.appendString(m_entry, value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public synchronized void update(String value, long timestamp) {
if (m_lastValue == null || !value.equals(m_lastValue)) {
m_lastValue = value;
append(value, timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(String value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public synchronized boolean hasLastValue() {
return m_lastValue != null;
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or null if none.
*/
public synchronized String getLastValue() {
return m_lastValue;
}
private String m_lastValue;
}

View File

@@ -6,7 +6,9 @@ package edu.wpi.first.util.datalog;
import edu.wpi.first.util.struct.Struct;
import edu.wpi.first.util.struct.StructBuffer;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
/**
@@ -19,6 +21,8 @@ public final class StructArrayLogEntry<T> extends DataLogEntry {
DataLog log, String name, Struct<T> struct, String metadata, long timestamp) {
super(log, name, struct.getTypeString() + "[]", metadata, timestamp);
m_buf = StructBuffer.create(struct);
m_immutable = struct.isImmutable();
m_cloneable = struct.isCloneable();
log.addSchema(struct, timestamp);
}
@@ -87,7 +91,7 @@ public final class StructArrayLogEntry<T> extends DataLogEntry {
* @param nelem number of elements
*/
public void reserve(int nelem) {
synchronized (this) {
synchronized (m_buf) {
m_buf.reserve(nelem);
}
}
@@ -99,7 +103,7 @@ public final class StructArrayLogEntry<T> extends DataLogEntry {
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(T[] value, long timestamp) {
synchronized (this) {
synchronized (m_buf) {
ByteBuffer bb = m_buf.writeArray(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
}
@@ -121,7 +125,7 @@ public final class StructArrayLogEntry<T> extends DataLogEntry {
* @param timestamp Time stamp (0 to indicate now)
*/
public void append(Collection<T> value, long timestamp) {
synchronized (this) {
synchronized (m_buf) {
ByteBuffer bb = m_buf.writeArray(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
}
@@ -136,5 +140,242 @@ public final class StructArrayLogEntry<T> extends DataLogEntry {
append(value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public void update(T[] value, long timestamp) {
synchronized (m_buf) {
if (m_immutable || m_cloneable) {
if (m_lastValue != null
&& m_lastValueLen == value.length
&& Arrays.equals(m_lastValue, 0, value.length, value, 0, value.length)) {
return;
}
try {
copyToLast(value);
ByteBuffer bb = m_buf.writeArray(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
return;
} catch (CloneNotSupportedException e) {
// fall through
}
}
doUpdate(m_buf.writeArray(value), timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(T[] value) {
update(value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public void update(Collection<T> value, long timestamp) {
synchronized (m_buf) {
if (m_immutable || m_cloneable) {
if (equalsLast(value)) {
return;
}
try {
copyToLast(value);
ByteBuffer bb = m_buf.writeArray(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
return;
} catch (CloneNotSupportedException e) {
// fall through
}
}
doUpdate(m_buf.writeArray(value), timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(Collection<T> value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public boolean hasLastValue() {
synchronized (m_buf) {
if (m_immutable) {
return m_lastValue != null;
} else if (m_cloneable && m_lastValue != null) {
return true;
}
return m_lastValueBuf != null;
}
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or null if none.
*/
@SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
public T[] getLastValue() {
synchronized (m_buf) {
if (m_immutable) {
if (m_lastValue == null) {
return null;
}
return Arrays.copyOf(m_lastValue, m_lastValueLen);
} else if (m_cloneable && m_lastValue != null) {
try {
return cloneArray(m_lastValue, m_lastValueLen);
} catch (CloneNotSupportedException e) {
// fall through
}
}
if (m_lastValueBuf == null) {
return null;
}
T[] val = m_buf.readArray(m_lastValueBuf);
m_lastValueBuf.position(0);
return val;
}
}
private void doUpdate(ByteBuffer bb, long timestamp) {
int len = bb.position();
bb.limit(len);
bb.position(0);
if (m_lastValueBuf == null || !bb.equals(m_lastValueBuf)) {
// update last value
if (m_lastValueBuf == null || m_lastValueBuf.limit() < len) {
m_lastValueBuf = ByteBuffer.allocate(len);
}
bb.get(m_lastValueBuf.array(), 0, len);
bb.position(0);
m_lastValueBuf.limit(len);
// append to log
m_log.appendRaw(m_entry, bb, 0, len, timestamp);
}
}
private boolean equalsLast(Collection<T> arr) {
if (m_lastValue == null) {
return false;
}
if (m_lastValueLen != arr.size()) {
return false;
}
int i = 0;
for (T elem : arr) {
if (!m_lastValue[i].equals(elem)) {
return false;
}
i++;
}
return true;
}
private T[] cloneArray(T[] in, int len) throws CloneNotSupportedException {
Struct<T> s = m_buf.getStruct();
@SuppressWarnings("unchecked")
T[] arr = (T[]) Array.newInstance(s.getTypeClass(), len);
for (int i = 0; i < len; i++) {
arr[i] = s.clone(in[i]);
}
return arr;
}
private T[] cloneArray(Collection<T> in) throws CloneNotSupportedException {
Struct<T> s = m_buf.getStruct();
@SuppressWarnings("unchecked")
T[] arr = (T[]) Array.newInstance(s.getTypeClass(), in.size());
int i = 0;
for (T elem : in) {
arr[i++] = s.clone(elem);
}
return arr;
}
private void copyToLast(T[] value) throws CloneNotSupportedException {
if (m_lastValue == null || m_lastValue.length < value.length) {
if (m_immutable) {
m_lastValue = Arrays.copyOf(value, value.length);
} else {
m_lastValue = cloneArray(value, value.length);
}
} else {
if (m_immutable) {
System.arraycopy(value, 0, m_lastValue, 0, value.length);
} else {
Struct<T> s = m_buf.getStruct();
for (int i = 0; i < value.length; i++) {
m_lastValue[i] = s.clone(value[i]);
}
}
}
m_lastValueLen = value.length;
}
private void copyToLast(Collection<T> value) throws CloneNotSupportedException {
if (m_lastValue == null || m_lastValue.length < value.size()) {
if (m_immutable) {
Struct<T> s = m_buf.getStruct();
@SuppressWarnings("unchecked")
T[] arr = (T[]) Array.newInstance(s.getTypeClass(), value.size());
int i = 0;
for (T elem : value) {
arr[i++] = elem;
}
} else {
m_lastValue = cloneArray(value);
}
} else {
Struct<T> s = m_buf.getStruct();
int i = 0;
for (T elem : value) {
m_lastValue[i++] = m_immutable ? elem : s.clone(elem);
}
}
m_lastValueLen = value.size();
}
private final StructBuffer<T> m_buf;
private ByteBuffer m_lastValueBuf;
private final boolean m_immutable;
private final boolean m_cloneable;
private T[] m_lastValue;
private int m_lastValueLen;
}

View File

@@ -18,6 +18,8 @@ public final class StructLogEntry<T> extends DataLogEntry {
DataLog log, String name, Struct<T> struct, String metadata, long timestamp) {
super(log, name, struct.getTypeString(), metadata, timestamp);
m_buf = StructBuffer.create(struct);
m_immutable = struct.isImmutable();
m_cloneable = struct.isCloneable();
log.addSchema(struct, timestamp);
}
@@ -102,5 +104,118 @@ public final class StructLogEntry<T> extends DataLogEntry {
append(value, 0);
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
* @param timestamp Time stamp (0 to indicate now)
*/
public void update(T value, long timestamp) {
synchronized (m_buf) {
if (m_immutable || m_cloneable) {
if (value.equals(m_lastValue)) {
return;
}
try {
if (m_immutable) {
m_lastValue = value;
} else {
m_lastValue = m_buf.getStruct().clone(value);
}
ByteBuffer bb = m_buf.write(value);
m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
return;
} catch (CloneNotSupportedException e) {
// fall through
}
}
doUpdate(m_buf.write(value), timestamp);
}
}
/**
* Updates the last value and appends a record to the log if it has changed.
*
* <p>Note: the last value is local to this class instance; using update() with two instances
* pointing to the same underlying log entry name will likely result in unexpected results.
*
* @param value Value to record
*/
public void update(T value) {
update(value, 0);
}
/**
* Gets whether there is a last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return True if last value exists, false otherwise.
*/
public boolean hasLastValue() {
synchronized (m_buf) {
if (m_immutable) {
return m_lastValue != null;
} else if (m_cloneable && m_lastValue != null) {
return true;
}
return m_lastValueBuf != null;
}
}
/**
* Gets the last value.
*
* <p>Note: the last value is local to this class instance and updated only with update(), not
* append().
*
* @return Last value, or null if none.
*/
public T getLastValue() {
synchronized (m_buf) {
if (m_immutable) {
return m_lastValue;
} else if (m_cloneable && m_lastValue != null) {
try {
return m_buf.getStruct().clone(m_lastValue);
} catch (CloneNotSupportedException e) {
// fall through
}
}
if (m_lastValueBuf == null) {
return null;
}
T val = m_buf.read(m_lastValueBuf);
m_lastValueBuf.position(0);
return val;
}
}
private void doUpdate(ByteBuffer bb, long timestamp) {
int len = bb.position();
bb.limit(len);
bb.position(0);
if (m_lastValueBuf == null || !bb.equals(m_lastValueBuf)) {
// update last value
if (m_lastValueBuf == null || m_lastValueBuf.limit() < len) {
m_lastValueBuf = ByteBuffer.allocate(len);
}
bb.get(m_lastValueBuf.array(), 0, len);
bb.position(0);
m_lastValueBuf.limit(len);
// append to log
m_log.appendRaw(m_entry, bb, 0, len, timestamp);
}
}
private final StructBuffer<T> m_buf;
private ByteBuffer m_lastValueBuf;
private final boolean m_immutable;
private final boolean m_cloneable;
private T m_lastValue;
}