mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "wpi/DataLog.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -618,6 +619,107 @@ void DataLog::AppendStringArray(int entry,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename V1, typename V2>
|
||||
inline bool UpdateImpl(std::optional<std::vector<V1>>& lastValue,
|
||||
std::span<const V2> data) {
|
||||
if (!lastValue || !std::equal(data.begin(), data.end(), lastValue->begin(),
|
||||
lastValue->end())) {
|
||||
if (lastValue) {
|
||||
lastValue->assign(data.begin(), data.end());
|
||||
} else {
|
||||
lastValue = std::vector<V1>{data.begin(), data.end()};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename V1>
|
||||
inline bool UpdateImpl(std::optional<std::vector<V1>>& lastValue,
|
||||
std::span<const bool> data) {
|
||||
if (!lastValue || !std::equal(data.begin(), data.end(), lastValue->begin(),
|
||||
lastValue->end(), [](auto a, auto b) {
|
||||
return a == static_cast<bool>(b);
|
||||
})) {
|
||||
if (lastValue) {
|
||||
lastValue->assign(data.begin(), data.end());
|
||||
} else {
|
||||
lastValue = std::vector<V1>{data.begin(), data.end()};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RawLogEntry::Update(std::span<const uint8_t> data, int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, data)) {
|
||||
Append(data, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void BooleanArrayLogEntry::Update(std::span<const bool> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void BooleanArrayLogEntry::Update(std::span<const int> arr, int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void BooleanArrayLogEntry::Update(std::span<const uint8_t> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegerArrayLogEntry::Update(std::span<const int64_t> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void FloatArrayLogEntry::Update(std::span<const float> arr, int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleArrayLogEntry::Update(std::span<const double> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void StringArrayLogEntry::Update(std::span<const std::string> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void StringArrayLogEntry::Update(std::span<const std::string_view> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void WPI_DataLog_Release(struct WPI_DataLog* datalog) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
@@ -18,6 +19,18 @@
|
||||
using namespace wpi::java;
|
||||
using namespace wpi::log;
|
||||
|
||||
namespace {
|
||||
class buf_ostream : public wpi::raw_uvector_ostream {
|
||||
private:
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
public:
|
||||
buf_ostream() : raw_uvector_ostream{data} {}
|
||||
|
||||
void clear() { data.clear(); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
@@ -96,6 +109,24 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_fgCreate
|
||||
return reinterpret_cast<jlong>(writer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: fgCreateMemory
|
||||
* Signature: (Ljava/lang/String;)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_fgCreateMemory
|
||||
(JNIEnv* env, jclass, jstring extraHeader)
|
||||
{
|
||||
if (!extraHeader) {
|
||||
wpi::ThrowNullPointerException(env, "extraHeader is null");
|
||||
return 0;
|
||||
}
|
||||
auto writer = new DataLogWriter{std::make_unique<buf_ostream>(),
|
||||
JStringRef{env, extraHeader}};
|
||||
return reinterpret_cast<jlong>(writer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: flush
|
||||
@@ -109,7 +140,34 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_flush
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLogBackgroundWriter*>(impl)->Flush();
|
||||
reinterpret_cast<DataLog*>(impl)->Flush();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: copyWriteBuffer
|
||||
* Signature: (J[BI)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_copyWriteBuffer
|
||||
(JNIEnv* env, jclass, jlong impl, jbyteArray buf, jint start)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return 0;
|
||||
}
|
||||
auto writer = reinterpret_cast<DataLogWriter*>(impl);
|
||||
writer->Flush();
|
||||
auto& stream = static_cast<buf_ostream&>(writer->GetStream());
|
||||
JSpan<jbyte> jbuf{env, buf};
|
||||
auto arr = stream.array();
|
||||
if (start < 0 || static_cast<size_t>(start) >= arr.size()) {
|
||||
stream.clear();
|
||||
return 0;
|
||||
}
|
||||
size_t qty = (std::min)(jbuf.size(), arr.size() - start);
|
||||
std::copy(arr.begin(), arr.begin() + qty, jbuf.begin());
|
||||
return qty;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -125,7 +183,7 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_pause
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLogBackgroundWriter*>(impl)->Pause();
|
||||
reinterpret_cast<DataLog*>(impl)->Pause();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -141,7 +199,7 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_resume
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLogBackgroundWriter*>(impl)->Resume();
|
||||
reinterpret_cast<DataLog*>(impl)->Resume();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -157,7 +215,7 @@ Java_edu_wpi_first_util_datalog_DataLogJNI_stop
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLogBackgroundWriter*>(impl)->Stop();
|
||||
reinterpret_cast<DataLog*>(impl)->Stop();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <string>
|
||||
@@ -575,10 +577,60 @@ class DataLogEntry {
|
||||
int m_entry = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class DataLogValueEntryImpl : public DataLogEntry {
|
||||
protected:
|
||||
DataLogValueEntryImpl() = default;
|
||||
DataLogValueEntryImpl(DataLog& log, std::string_view name,
|
||||
std::string_view type, std::string_view metadata = {},
|
||||
int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, type, metadata, timestamp} {}
|
||||
|
||||
public:
|
||||
DataLogValueEntryImpl(DataLogValueEntryImpl&& rhs)
|
||||
: DataLogEntry{std::move(rhs)} {
|
||||
std::scoped_lock lock{rhs.m_mutex};
|
||||
m_lastValue = std::move(rhs.m_lastValue);
|
||||
}
|
||||
DataLogValueEntryImpl& operator=(DataLogValueEntryImpl&& rhs) {
|
||||
DataLogEntry::operator=(std::move(rhs));
|
||||
std::scoped_lock lock{m_mutex, rhs.m_mutex};
|
||||
m_lastValue = std::move(rhs.m_lastValue);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether there is a last value.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
bool HasLastValue() const { return m_lastValue.has_value(); }
|
||||
|
||||
/**
|
||||
* Gets the last value.
|
||||
*
|
||||
* @note The last value is local to this class instance and updated only with
|
||||
* Update(), not Append().
|
||||
*
|
||||
* @return Last value (empty if no last value)
|
||||
*/
|
||||
std::optional<T> GetLastValue() const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_lastValue;
|
||||
}
|
||||
|
||||
protected:
|
||||
mutable wpi::mutex m_mutex;
|
||||
std::optional<T> m_lastValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Log arbitrary byte data.
|
||||
*/
|
||||
class RawLogEntry : public DataLogEntry {
|
||||
class RawLogEntry : public DataLogValueEntryImpl<std::vector<uint8_t>> {
|
||||
public:
|
||||
static constexpr std::string_view kDataType = "raw";
|
||||
|
||||
@@ -590,7 +642,7 @@ class RawLogEntry : public DataLogEntry {
|
||||
: RawLogEntry{log, name, metadata, kDataType, timestamp} {}
|
||||
RawLogEntry(DataLog& log, std::string_view name, std::string_view metadata,
|
||||
std::string_view type, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, type, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, type, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -601,12 +653,24 @@ class RawLogEntry : public DataLogEntry {
|
||||
void Append(std::span<const uint8_t> data, int64_t timestamp = 0) {
|
||||
m_log->AppendRaw(m_entry, data, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 data Data to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const uint8_t> data, int64_t timestamp = 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Log boolean values.
|
||||
*/
|
||||
class BooleanLogEntry : public DataLogEntry {
|
||||
class BooleanLogEntry : public DataLogValueEntryImpl<bool> {
|
||||
public:
|
||||
static constexpr std::string_view kDataType = "boolean";
|
||||
|
||||
@@ -615,7 +679,7 @@ class BooleanLogEntry : public DataLogEntry {
|
||||
: BooleanLogEntry{log, name, {}, timestamp} {}
|
||||
BooleanLogEntry(DataLog& log, std::string_view name,
|
||||
std::string_view metadata, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -626,12 +690,30 @@ class BooleanLogEntry : public DataLogEntry {
|
||||
void Append(bool value, int64_t timestamp = 0) {
|
||||
m_log->AppendBoolean(m_entry, value, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(bool value, int64_t timestamp = 0) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_lastValue != value) {
|
||||
m_lastValue = value;
|
||||
Append(value, timestamp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log integer values.
|
||||
*/
|
||||
class IntegerLogEntry : public DataLogEntry {
|
||||
class IntegerLogEntry : public DataLogValueEntryImpl<int64_t> {
|
||||
public:
|
||||
static constexpr std::string_view kDataType = "int64";
|
||||
|
||||
@@ -640,7 +722,7 @@ class IntegerLogEntry : public DataLogEntry {
|
||||
: IntegerLogEntry{log, name, {}, timestamp} {}
|
||||
IntegerLogEntry(DataLog& log, std::string_view name,
|
||||
std::string_view metadata, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -651,12 +733,30 @@ class IntegerLogEntry : public DataLogEntry {
|
||||
void Append(int64_t value, int64_t timestamp = 0) {
|
||||
m_log->AppendInteger(m_entry, value, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(int64_t value, int64_t timestamp = 0) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_lastValue != value) {
|
||||
m_lastValue = value;
|
||||
Append(value, timestamp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log float values.
|
||||
*/
|
||||
class FloatLogEntry : public DataLogEntry {
|
||||
class FloatLogEntry : public DataLogValueEntryImpl<float> {
|
||||
public:
|
||||
static constexpr std::string_view kDataType = "float";
|
||||
|
||||
@@ -665,7 +765,7 @@ class FloatLogEntry : public DataLogEntry {
|
||||
: FloatLogEntry{log, name, {}, timestamp} {}
|
||||
FloatLogEntry(DataLog& log, std::string_view name, std::string_view metadata,
|
||||
int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -676,12 +776,30 @@ class FloatLogEntry : public DataLogEntry {
|
||||
void Append(float value, int64_t timestamp = 0) {
|
||||
m_log->AppendFloat(m_entry, value, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(float value, int64_t timestamp = 0) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_lastValue != value) {
|
||||
m_lastValue = value;
|
||||
Append(value, timestamp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log double values.
|
||||
*/
|
||||
class DoubleLogEntry : public DataLogEntry {
|
||||
class DoubleLogEntry : public DataLogValueEntryImpl<double> {
|
||||
public:
|
||||
static constexpr std::string_view kDataType = "double";
|
||||
|
||||
@@ -690,7 +808,7 @@ class DoubleLogEntry : public DataLogEntry {
|
||||
: DoubleLogEntry{log, name, {}, timestamp} {}
|
||||
DoubleLogEntry(DataLog& log, std::string_view name, std::string_view metadata,
|
||||
int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -701,12 +819,30 @@ class DoubleLogEntry : public DataLogEntry {
|
||||
void Append(double value, int64_t timestamp = 0) {
|
||||
m_log->AppendDouble(m_entry, value, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(double value, int64_t timestamp = 0) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_lastValue != value) {
|
||||
m_lastValue = value;
|
||||
Append(value, timestamp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log string values.
|
||||
*/
|
||||
class StringLogEntry : public DataLogEntry {
|
||||
class StringLogEntry : public DataLogValueEntryImpl<std::string> {
|
||||
public:
|
||||
static constexpr const char* kDataType = "string";
|
||||
|
||||
@@ -718,7 +854,7 @@ class StringLogEntry : public DataLogEntry {
|
||||
: StringLogEntry{log, name, metadata, kDataType, timestamp} {}
|
||||
StringLogEntry(DataLog& log, std::string_view name, std::string_view metadata,
|
||||
std::string_view type, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, type, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, type, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -729,12 +865,30 @@ class StringLogEntry : public DataLogEntry {
|
||||
void Append(std::string_view value, int64_t timestamp = 0) {
|
||||
m_log->AppendString(m_entry, value, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::string value, int64_t timestamp = 0) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_lastValue != value) {
|
||||
m_lastValue = value;
|
||||
Append(value, timestamp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log array of boolean values.
|
||||
*/
|
||||
class BooleanArrayLogEntry : public DataLogEntry {
|
||||
class BooleanArrayLogEntry : public DataLogValueEntryImpl<std::vector<int>> {
|
||||
public:
|
||||
static constexpr const char* kDataType = "boolean[]";
|
||||
|
||||
@@ -744,7 +898,7 @@ class BooleanArrayLogEntry : public DataLogEntry {
|
||||
: BooleanArrayLogEntry{log, name, {}, timestamp} {}
|
||||
BooleanArrayLogEntry(DataLog& log, std::string_view name,
|
||||
std::string_view metadata, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log. For find functions to work, timestamp
|
||||
@@ -796,12 +950,77 @@ class BooleanArrayLogEntry : public DataLogEntry {
|
||||
void Append(std::span<const uint8_t> arr, int64_t timestamp = 0) {
|
||||
m_log->AppendBooleanArray(m_entry, arr, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const bool> arr, int64_t timestamp = 0);
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::initializer_list<bool> arr, int64_t timestamp = 0) {
|
||||
Update(std::span{arr.begin(), arr.end()}, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const int> arr, int64_t timestamp = 0);
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::initializer_list<int> arr, int64_t timestamp = 0) {
|
||||
Update(std::span{arr.begin(), arr.end()}, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const uint8_t> arr, int64_t timestamp = 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Log array of integer values.
|
||||
*/
|
||||
class IntegerArrayLogEntry : public DataLogEntry {
|
||||
class IntegerArrayLogEntry
|
||||
: public DataLogValueEntryImpl<std::vector<int64_t>> {
|
||||
public:
|
||||
static constexpr const char* kDataType = "int64[]";
|
||||
|
||||
@@ -811,7 +1030,7 @@ class IntegerArrayLogEntry : public DataLogEntry {
|
||||
: IntegerArrayLogEntry{log, name, {}, timestamp} {}
|
||||
IntegerArrayLogEntry(DataLog& log, std::string_view name,
|
||||
std::string_view metadata, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -832,12 +1051,38 @@ class IntegerArrayLogEntry : public DataLogEntry {
|
||||
void Append(std::initializer_list<int64_t> arr, int64_t timestamp = 0) {
|
||||
Append({arr.begin(), arr.end()}, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const int64_t> arr, int64_t timestamp = 0);
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::initializer_list<int64_t> arr, int64_t timestamp = 0) {
|
||||
Update({arr.begin(), arr.end()}, timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log array of float values.
|
||||
*/
|
||||
class FloatArrayLogEntry : public DataLogEntry {
|
||||
class FloatArrayLogEntry : public DataLogValueEntryImpl<std::vector<float>> {
|
||||
public:
|
||||
static constexpr const char* kDataType = "float[]";
|
||||
|
||||
@@ -846,7 +1091,7 @@ class FloatArrayLogEntry : public DataLogEntry {
|
||||
: FloatArrayLogEntry{log, name, {}, timestamp} {}
|
||||
FloatArrayLogEntry(DataLog& log, std::string_view name,
|
||||
std::string_view metadata, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -867,12 +1112,38 @@ class FloatArrayLogEntry : public DataLogEntry {
|
||||
void Append(std::initializer_list<float> arr, int64_t timestamp = 0) {
|
||||
Append({arr.begin(), arr.end()}, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const float> arr, int64_t timestamp = 0);
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::initializer_list<float> arr, int64_t timestamp = 0) {
|
||||
Update({arr.begin(), arr.end()}, timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log array of double values.
|
||||
*/
|
||||
class DoubleArrayLogEntry : public DataLogEntry {
|
||||
class DoubleArrayLogEntry : public DataLogValueEntryImpl<std::vector<double>> {
|
||||
public:
|
||||
static constexpr const char* kDataType = "double[]";
|
||||
|
||||
@@ -882,7 +1153,7 @@ class DoubleArrayLogEntry : public DataLogEntry {
|
||||
: DoubleArrayLogEntry{log, name, {}, timestamp} {}
|
||||
DoubleArrayLogEntry(DataLog& log, std::string_view name,
|
||||
std::string_view metadata, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -903,12 +1174,39 @@ class DoubleArrayLogEntry : public DataLogEntry {
|
||||
void Append(std::initializer_list<double> arr, int64_t timestamp = 0) {
|
||||
Append({arr.begin(), arr.end()}, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const double> arr, int64_t timestamp = 0);
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::initializer_list<double> arr, int64_t timestamp = 0) {
|
||||
Update({arr.begin(), arr.end()}, timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log array of string values.
|
||||
*/
|
||||
class StringArrayLogEntry : public DataLogEntry {
|
||||
class StringArrayLogEntry
|
||||
: public DataLogValueEntryImpl<std::vector<std::string>> {
|
||||
public:
|
||||
static constexpr const char* kDataType = "string[]";
|
||||
|
||||
@@ -918,7 +1216,7 @@ class StringArrayLogEntry : public DataLogEntry {
|
||||
: StringArrayLogEntry{log, name, {}, timestamp} {}
|
||||
StringArrayLogEntry(DataLog& log, std::string_view name,
|
||||
std::string_view metadata, int64_t timestamp = 0)
|
||||
: DataLogEntry{log, name, kDataType, metadata, timestamp} {}
|
||||
: DataLogValueEntryImpl{log, name, kDataType, metadata, timestamp} {}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
@@ -951,6 +1249,46 @@ class StringArrayLogEntry : public DataLogEntry {
|
||||
Append(std::span<const std::string_view>{arr.begin(), arr.end()},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const std::string> arr, int64_t timestamp = 0);
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const std::string_view> arr, int64_t timestamp = 0);
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 arr Values to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::initializer_list<std::string_view> arr,
|
||||
int64_t timestamp = 0) {
|
||||
Update(std::span<const std::string_view>{arr.begin(), arr.end()},
|
||||
timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -974,6 +1312,19 @@ class StructLogEntry : public DataLogEntry {
|
||||
m_entry = log.Start(name, S::GetTypeString(info...), metadata, timestamp);
|
||||
}
|
||||
|
||||
StructLogEntry(StructLogEntry&& rhs)
|
||||
: DataLogEntry{std::move(rhs)}, m_info{std::move(rhs.m_info)} {
|
||||
std::scoped_lock lock{rhs.m_mutex};
|
||||
m_lastValue = std::move(rhs.m_lastValue);
|
||||
}
|
||||
StructLogEntry& operator=(StructLogEntry&& rhs) {
|
||||
DataLogEntry::operator=(std::move(rhs));
|
||||
m_info = std::move(rhs.m_info);
|
||||
std::scoped_lock lock{m_mutex, rhs.m_mutex};
|
||||
m_lastValue = std::move(rhs.m_lastValue);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
*
|
||||
@@ -995,7 +1346,74 @@ class StructLogEntry : public DataLogEntry {
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 data Data to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(const T& data, int64_t timestamp = 0) {
|
||||
if constexpr (sizeof...(I) == 0) {
|
||||
if constexpr (wpi::is_constexpr([] { S::GetSize(); })) {
|
||||
uint8_t buf[S::GetSize()];
|
||||
S::Pack(buf, data);
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_lastValue.empty() ||
|
||||
!std::equal(buf, buf + S::GetSize(), m_lastValue.begin(),
|
||||
m_lastValue.end())) {
|
||||
m_lastValue.assign(buf, buf + S::GetSize());
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
buf.resize_for_overwrite(std::apply(S::GetSize, m_info));
|
||||
std::apply([&](const I&... info) { S::Pack(buf, data, info...); }, m_info);
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_lastValue.empty() ||
|
||||
!std::equal(buf.begin(), buf.end(), m_lastValue.begin(),
|
||||
m_lastValue.end())) {
|
||||
m_lastValue.assign(buf.begin(), buf.end());
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether there is a last value.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
bool HasLastValue() const { return !m_lastValue.empty(); }
|
||||
|
||||
/**
|
||||
* Gets the last value.
|
||||
*
|
||||
* @note The last value is local to this class instance and updated only with
|
||||
* Update(), not Append().
|
||||
*
|
||||
* @return Last value (empty if no last value)
|
||||
*/
|
||||
std::optional<T> GetLastValue() const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_lastValue.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::apply(
|
||||
[&](const I&... info) { return S::Unpack(m_lastValue, info...); },
|
||||
m_info);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable wpi::mutex m_mutex;
|
||||
std::vector<uint8_t> m_lastValue;
|
||||
[[no_unique_address]]
|
||||
std::tuple<I...> m_info;
|
||||
};
|
||||
@@ -1024,6 +1442,22 @@ class StructArrayLogEntry : public DataLogEntry {
|
||||
metadata, timestamp);
|
||||
}
|
||||
|
||||
StructArrayLogEntry(StructArrayLogEntry&& rhs)
|
||||
: DataLogEntry{std::move(rhs)},
|
||||
m_buf{std::move(rhs.m_buf)},
|
||||
m_info{std::move(rhs.m_info)} {
|
||||
std::scoped_lock lock{rhs.m_mutex};
|
||||
m_lastValue = std::move(rhs.m_lastValue);
|
||||
}
|
||||
StructArrayLogEntry& operator=(StructArrayLogEntry&& rhs) {
|
||||
DataLogEntry::operator=(std::move(rhs));
|
||||
m_buf = std::move(rhs.m_buf);
|
||||
m_info = std::move(rhs.m_info);
|
||||
std::scoped_lock lock{m_mutex, rhs.m_mutex};
|
||||
m_lastValue = std::move(rhs.m_lastValue);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a record to the log.
|
||||
*
|
||||
@@ -1063,8 +1497,81 @@ class StructArrayLogEntry : public DataLogEntry {
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 data Data to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(std::span<const T> data, int64_t timestamp = 0) {
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
m_buf.Write(
|
||||
data,
|
||||
[&](auto bytes) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (!m_lastValue.has_value()) {
|
||||
m_lastValue = std::vector(bytes.begin(), bytes.end());
|
||||
m_log->AppendRaw(m_entry, bytes, timestamp);
|
||||
} else if (!std::equal(bytes.begin(), bytes.end(),
|
||||
m_lastValue->begin(),
|
||||
m_lastValue->end())) {
|
||||
m_lastValue->assign(bytes.begin(), bytes.end());
|
||||
m_log->AppendRaw(m_entry, bytes, timestamp);
|
||||
}
|
||||
},
|
||||
info...);
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether there is a last value.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
bool HasLastValue() const { return m_lastValue.has_value(); }
|
||||
|
||||
/**
|
||||
* Gets the last value.
|
||||
*
|
||||
* @note The last value is local to this class instance and updated only with
|
||||
* Update(), not Append().
|
||||
*
|
||||
* @return Last value (empty if no last value)
|
||||
*/
|
||||
std::optional<std::vector<T>> GetLastValue() const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (!m_lastValue.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto& lastValue = m_lastValue.value();
|
||||
size_t size = std::apply(S::GetSize, m_info);
|
||||
std::vector<T> rv;
|
||||
rv.reserve(lastValue.size() / size);
|
||||
for (auto in = lastValue.begin(), end = lastValue.end(); in < end;
|
||||
in += size) {
|
||||
std::apply(
|
||||
[&](const I&... info) {
|
||||
rv.emplace_back(S::Unpack(
|
||||
std::span<const uint8_t>{std::to_address(in), size}, info...));
|
||||
},
|
||||
m_info);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable wpi::mutex m_mutex;
|
||||
StructArrayBuffer<T, I...> m_buf;
|
||||
std::optional<std::vector<uint8_t>> m_lastValue;
|
||||
[[no_unique_address]]
|
||||
std::tuple<I...> m_info;
|
||||
};
|
||||
@@ -1102,9 +1609,60 @@ class ProtobufLogEntry : public DataLogEntry {
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last value and appends a record to the log if it has changed.
|
||||
*
|
||||
* @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 data Data to record
|
||||
* @param timestamp Time stamp (may be 0 to indicate now)
|
||||
*/
|
||||
void Update(const T& data, int64_t timestamp = 0) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
wpi::SmallVector<uint8_t, 128> buf;
|
||||
m_msg.Pack(buf, data);
|
||||
if (!m_lastValue.has_value()) {
|
||||
m_lastValue = std::vector(buf.begin(), buf.end());
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
} else if (!std::equal(buf.begin(), buf.end(), m_lastValue->begin(),
|
||||
m_lastValue->end())) {
|
||||
m_lastValue->assign(buf.begin(), buf.end());
|
||||
m_log->AppendRaw(m_entry, buf, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether there is a last value.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
bool HasLastValue() const { return m_lastValue.has_value(); }
|
||||
|
||||
/**
|
||||
* Gets the last value.
|
||||
*
|
||||
* @note The last value is local to this class instance and updated only with
|
||||
* Update(), not Append().
|
||||
*
|
||||
* @return Last value (empty if no last value)
|
||||
*/
|
||||
std::optional<T> GetLastValue() const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (!m_lastValue.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return m_msg.Unpack(m_lastValue);
|
||||
}
|
||||
|
||||
private:
|
||||
wpi::mutex m_mutex;
|
||||
mutable wpi::mutex m_mutex;
|
||||
ProtobufMessage<T> m_msg;
|
||||
std::optional<std::vector<uint8_t>> m_lastValue;
|
||||
};
|
||||
|
||||
} // namespace wpi::log
|
||||
|
||||
@@ -81,6 +81,13 @@ class DataLogWriter final : public DataLog {
|
||||
*/
|
||||
void Stop() final;
|
||||
|
||||
/**
|
||||
* Gets the output stream.
|
||||
*
|
||||
* @return output stream
|
||||
*/
|
||||
wpi::raw_ostream& GetStream() { return *m_os; }
|
||||
|
||||
private:
|
||||
bool BufferFull() final;
|
||||
|
||||
|
||||
@@ -0,0 +1,799 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util.datalog;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import edu.wpi.first.util.struct.StructSerializable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@SuppressWarnings("AvoidEscapedUnicodeCharacters")
|
||||
class DataLogTest {
|
||||
static class ImmutableThingStruct implements Struct<ImmutableThing> {
|
||||
@Override
|
||||
public Class<ImmutableThing> getTypeClass() {
|
||||
return ImmutableThing.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeString() {
|
||||
return "struct:Thing";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchema() {
|
||||
return "uint8 value";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableThing unpack(ByteBuffer bb) {
|
||||
return new ImmutableThing(bb.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pack(ByteBuffer bb, ImmutableThing value) {
|
||||
bb.put(value.m_x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmutable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static class ImmutableThing implements StructSerializable {
|
||||
byte m_x;
|
||||
|
||||
ImmutableThing(int x) {
|
||||
m_x = (byte) x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof ImmutableThing other && other.m_x == m_x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(m_x);
|
||||
}
|
||||
|
||||
public static final ImmutableThingStruct struct = new ImmutableThingStruct();
|
||||
}
|
||||
|
||||
static class CloneableThingStruct implements Struct<CloneableThing> {
|
||||
@Override
|
||||
public Class<CloneableThing> getTypeClass() {
|
||||
return CloneableThing.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeString() {
|
||||
return "struct:Thing";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchema() {
|
||||
return "uint8 value";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloneableThing unpack(ByteBuffer bb) {
|
||||
return new CloneableThing(bb.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pack(ByteBuffer bb, CloneableThing value) {
|
||||
bb.put(value.m_x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCloneable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloneableThing clone(CloneableThing obj) throws CloneNotSupportedException {
|
||||
return obj.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private static int cloneCalls;
|
||||
|
||||
static class CloneableThing implements StructSerializable, Cloneable {
|
||||
byte m_x;
|
||||
|
||||
CloneableThing(int x) {
|
||||
m_x = (byte) x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof CloneableThing other && other.m_x == m_x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(m_x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloneableThing clone() throws CloneNotSupportedException {
|
||||
CloneableThing thing = (CloneableThing) super.clone();
|
||||
cloneCalls++;
|
||||
return thing;
|
||||
}
|
||||
|
||||
public static final CloneableThingStruct struct = new CloneableThingStruct();
|
||||
}
|
||||
|
||||
static class ThingStruct implements Struct<Thing> {
|
||||
@Override
|
||||
public Class<Thing> getTypeClass() {
|
||||
return Thing.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeString() {
|
||||
return "struct:Thing";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchema() {
|
||||
return "uint8 value";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thing unpack(ByteBuffer bb) {
|
||||
return new Thing(bb.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pack(ByteBuffer bb, Thing value) {
|
||||
bb.put(value.m_x);
|
||||
}
|
||||
}
|
||||
|
||||
static class Thing implements StructSerializable {
|
||||
byte m_x;
|
||||
|
||||
Thing(int x) {
|
||||
m_x = (byte) x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Thing other && other.m_x == m_x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(m_x);
|
||||
}
|
||||
|
||||
public static final ThingStruct struct = new ThingStruct();
|
||||
}
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private ByteArrayOutputStream data;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
private DataLog log;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
data = new ByteArrayOutputStream();
|
||||
log = new DataLogWriter(data);
|
||||
cloneCalls = 0;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void shutdown() {
|
||||
log.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSimpleInt() {
|
||||
int entry = log.start("test", "int64", "", 1);
|
||||
log.appendInteger(entry, 1, 2);
|
||||
log.flush();
|
||||
assertEquals(54, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBooleanAppend() {
|
||||
BooleanLogEntry entry = new BooleanLogEntry(log, "a", 5);
|
||||
entry.append(false, 7);
|
||||
log.flush();
|
||||
assertEquals(46, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBooleanUpdate() {
|
||||
BooleanLogEntry entry = new BooleanLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(false, 7);
|
||||
log.flush();
|
||||
assertEquals(46, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertFalse(entry.getLastValue());
|
||||
entry.update(false, 8);
|
||||
log.flush();
|
||||
assertEquals(46, data.size());
|
||||
entry.update(true, 9);
|
||||
log.flush();
|
||||
assertEquals(51, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertTrue(entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntegerAppend() {
|
||||
IntegerLogEntry entry = new IntegerLogEntry(log, "a", 5);
|
||||
entry.append(5, 7);
|
||||
log.flush();
|
||||
assertEquals(51, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntegerUpdate() {
|
||||
IntegerLogEntry entry = new IntegerLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(0, 7);
|
||||
log.flush();
|
||||
assertEquals(51, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(0, entry.getLastValue());
|
||||
entry.update(0, 8);
|
||||
log.flush();
|
||||
assertEquals(51, data.size());
|
||||
entry.update(2, 9);
|
||||
log.flush();
|
||||
assertEquals(63, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(2, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFloatAppend() {
|
||||
FloatLogEntry entry = new FloatLogEntry(log, "a", 5);
|
||||
entry.append(5.0f, 7);
|
||||
log.flush();
|
||||
assertEquals(47, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFloatUpdate() {
|
||||
FloatLogEntry entry = new FloatLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(0.0f, 7);
|
||||
log.flush();
|
||||
assertEquals(47, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(0.0f, entry.getLastValue());
|
||||
entry.update(0.0f, 8);
|
||||
log.flush();
|
||||
assertEquals(47, data.size());
|
||||
entry.update(0.1f, 9);
|
||||
log.flush();
|
||||
assertEquals(55, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(0.1f, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoubleAppend() {
|
||||
DoubleLogEntry entry = new DoubleLogEntry(log, "a", 5);
|
||||
entry.append(5.0, 7);
|
||||
log.flush();
|
||||
assertEquals(52, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoubleUpdate() {
|
||||
DoubleLogEntry entry = new DoubleLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(0.0, 7);
|
||||
log.flush();
|
||||
assertEquals(52, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(0.0, entry.getLastValue());
|
||||
entry.update(0.0, 8);
|
||||
log.flush();
|
||||
assertEquals(52, data.size());
|
||||
entry.update(0.1, 9);
|
||||
log.flush();
|
||||
assertEquals(64, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(0.1, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringAppend() {
|
||||
StringLogEntry entry = new StringLogEntry(log, "a", 5);
|
||||
entry.append("x", 7);
|
||||
log.flush();
|
||||
assertEquals(45, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringUpdate() {
|
||||
StringLogEntry entry = new StringLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
|
||||
entry.update("x", 7);
|
||||
log.flush();
|
||||
assertEquals(45, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals("x", entry.getLastValue());
|
||||
|
||||
entry.update("x", 8);
|
||||
log.flush();
|
||||
assertEquals(45, data.size());
|
||||
|
||||
entry.update("y", 9);
|
||||
log.flush();
|
||||
assertEquals(50, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals("y", entry.getLastValue());
|
||||
|
||||
entry.update("yy", 10);
|
||||
log.flush();
|
||||
assertEquals(56, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals("yy", entry.getLastValue());
|
||||
|
||||
entry.update("", 11);
|
||||
log.flush();
|
||||
assertEquals(60, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals("", entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRawAppend() {
|
||||
RawLogEntry entry = new RawLogEntry(log, "a", 5);
|
||||
entry.append(new byte[] {5}, 7);
|
||||
log.flush();
|
||||
assertEquals(42, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRawUpdate() {
|
||||
RawLogEntry entry = new RawLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
|
||||
entry.update(new byte[] {5}, 7);
|
||||
log.flush();
|
||||
assertEquals(42, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new byte[] {5}, entry.getLastValue());
|
||||
|
||||
entry.update(new byte[] {5}, 8);
|
||||
log.flush();
|
||||
assertEquals(42, data.size());
|
||||
|
||||
entry.update(new byte[] {6}, 9);
|
||||
log.flush();
|
||||
assertEquals(47, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new byte[] {6}, entry.getLastValue());
|
||||
|
||||
entry.update(new byte[] {6, 6}, 10);
|
||||
log.flush();
|
||||
assertEquals(53, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new byte[] {6, 6}, entry.getLastValue());
|
||||
|
||||
entry.update(new byte[] {}, 11);
|
||||
log.flush();
|
||||
assertEquals(57, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new byte[] {}, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBooleanArrayAppendEmpty() {
|
||||
BooleanArrayLogEntry entry = new BooleanArrayLogEntry(log, "a", 5);
|
||||
entry.append(new boolean[] {}, 7);
|
||||
log.flush();
|
||||
assertEquals(47, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBooleanArrayAppend() {
|
||||
BooleanArrayLogEntry entry = new BooleanArrayLogEntry(log, "a", 5);
|
||||
entry.append(new boolean[] {false}, 7);
|
||||
log.flush();
|
||||
assertEquals(48, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBooleanArrayUpdate() {
|
||||
BooleanArrayLogEntry entry = new BooleanArrayLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(new boolean[] {false}, 7);
|
||||
log.flush();
|
||||
assertEquals(48, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new boolean[] {false}, entry.getLastValue());
|
||||
entry.update(new boolean[] {false}, 8);
|
||||
log.flush();
|
||||
assertEquals(48, data.size());
|
||||
entry.update(new boolean[] {true}, 9);
|
||||
log.flush();
|
||||
assertEquals(53, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new boolean[] {true}, entry.getLastValue());
|
||||
entry.update(new boolean[] {}, 10);
|
||||
log.flush();
|
||||
assertEquals(57, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new boolean[] {}, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntegerArrayAppendEmpty() {
|
||||
IntegerArrayLogEntry entry = new IntegerArrayLogEntry(log, "a", 5);
|
||||
entry.append(new long[] {}, 7);
|
||||
log.flush();
|
||||
assertEquals(45, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntegerArrayAppend() {
|
||||
IntegerArrayLogEntry entry = new IntegerArrayLogEntry(log, "a", 5);
|
||||
entry.append(new long[] {1}, 7);
|
||||
log.flush();
|
||||
assertEquals(53, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntegerArrayUpdate() {
|
||||
IntegerArrayLogEntry entry = new IntegerArrayLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(new long[] {1}, 7);
|
||||
log.flush();
|
||||
assertEquals(53, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new long[] {1}, entry.getLastValue());
|
||||
entry.update(new long[] {1}, 8);
|
||||
log.flush();
|
||||
assertEquals(53, data.size());
|
||||
entry.update(new long[] {2}, 9);
|
||||
log.flush();
|
||||
assertEquals(65, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new long[] {2}, entry.getLastValue());
|
||||
entry.update(new long[] {}, 10);
|
||||
log.flush();
|
||||
assertEquals(69, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new long[] {}, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoubleArrayAppendEmpty() {
|
||||
DoubleArrayLogEntry entry = new DoubleArrayLogEntry(log, "a", 5);
|
||||
entry.append(new double[] {}, 7);
|
||||
log.flush();
|
||||
assertEquals(46, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoubleArrayAppend() {
|
||||
DoubleArrayLogEntry entry = new DoubleArrayLogEntry(log, "a", 5);
|
||||
entry.append(new double[] {1.0}, 7);
|
||||
log.flush();
|
||||
assertEquals(54, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoubleArrayUpdate() {
|
||||
DoubleArrayLogEntry entry = new DoubleArrayLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(new double[] {1.0}, 7);
|
||||
log.flush();
|
||||
assertEquals(54, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new double[] {1.0}, entry.getLastValue());
|
||||
entry.update(new double[] {1.0}, 8);
|
||||
log.flush();
|
||||
assertEquals(54, data.size());
|
||||
entry.update(new double[] {2.0}, 9);
|
||||
log.flush();
|
||||
assertEquals(66, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new double[] {2}, entry.getLastValue());
|
||||
entry.update(new double[] {}, 10);
|
||||
log.flush();
|
||||
assertEquals(70, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new double[] {}, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFloatArrayAppendEmpty() {
|
||||
FloatArrayLogEntry entry = new FloatArrayLogEntry(log, "a", 5);
|
||||
entry.append(new float[] {}, 7);
|
||||
log.flush();
|
||||
assertEquals(45, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFloatArrayAppend() {
|
||||
FloatArrayLogEntry entry = new FloatArrayLogEntry(log, "a", 5);
|
||||
entry.append(new float[] {1.0f}, 7);
|
||||
log.flush();
|
||||
assertEquals(49, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFloatArrayUpdate() {
|
||||
FloatArrayLogEntry entry = new FloatArrayLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(new float[] {1.0f}, 7);
|
||||
log.flush();
|
||||
assertEquals(49, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new float[] {1.0f}, entry.getLastValue());
|
||||
entry.update(new float[] {1.0f}, 8);
|
||||
log.flush();
|
||||
assertEquals(49, data.size());
|
||||
entry.update(new float[] {2.0f}, 9);
|
||||
log.flush();
|
||||
assertEquals(57, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new float[] {2.0f}, entry.getLastValue());
|
||||
entry.update(new float[] {}, 10);
|
||||
log.flush();
|
||||
assertEquals(61, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new float[] {}, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringArrayAppendEmpty() {
|
||||
StringArrayLogEntry entry = new StringArrayLogEntry(log, "a", 5);
|
||||
entry.append(new String[] {}, 7);
|
||||
entry.append(new String[] {}, 7);
|
||||
log.flush();
|
||||
assertEquals(58, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringArrayAppend() {
|
||||
StringArrayLogEntry entry = new StringArrayLogEntry(log, "a", 5);
|
||||
entry.append(new String[] {"x"}, 7);
|
||||
log.flush();
|
||||
assertEquals(55, data.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringArrayUpdate() {
|
||||
StringArrayLogEntry entry = new StringArrayLogEntry(log, "a", 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
entry.update(new String[] {"x"}, 7);
|
||||
log.flush();
|
||||
assertEquals(55, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new String[] {"x"}, entry.getLastValue());
|
||||
entry.update(new String[] {"x"}, 8);
|
||||
log.flush();
|
||||
assertEquals(55, data.size());
|
||||
entry.update(new String[] {"y"}, 9);
|
||||
log.flush();
|
||||
assertEquals(68, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new String[] {"y"}, entry.getLastValue());
|
||||
entry.update(new String[] {}, 10);
|
||||
log.flush();
|
||||
assertEquals(76, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new String[] {}, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStruct() {
|
||||
StructLogEntry<Thing> entry = StructLogEntry.create(log, "a", Thing.struct, 5);
|
||||
entry.append(new Thing((byte) 1), 6);
|
||||
entry.append(new Thing((byte) 0), 7);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStructUpdate() {
|
||||
StructLogEntry<Thing> entry = StructLogEntry.create(log, "a", Thing.struct, 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
|
||||
entry.update(new Thing(1), 7);
|
||||
log.flush();
|
||||
assertEquals(120, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(new Thing(1), entry.getLastValue());
|
||||
|
||||
entry.update(new Thing(1), 8);
|
||||
log.flush();
|
||||
assertEquals(120, data.size());
|
||||
|
||||
entry.update(new Thing(2), 9);
|
||||
log.flush();
|
||||
assertEquals(125, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(new Thing(2), entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCloneableStructUpdate() {
|
||||
StructLogEntry<CloneableThing> entry =
|
||||
StructLogEntry.create(log, "a", CloneableThing.struct, 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
|
||||
entry.update(new CloneableThing(1), 7);
|
||||
assertEquals(1, cloneCalls);
|
||||
log.flush();
|
||||
assertEquals(120, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(new CloneableThing(1), entry.getLastValue());
|
||||
assertEquals(2, cloneCalls);
|
||||
|
||||
entry.update(new CloneableThing(1), 8);
|
||||
assertEquals(2, cloneCalls);
|
||||
log.flush();
|
||||
assertEquals(120, data.size());
|
||||
|
||||
entry.update(new CloneableThing(2), 9);
|
||||
assertEquals(3, cloneCalls);
|
||||
log.flush();
|
||||
assertEquals(125, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(new CloneableThing(2), entry.getLastValue());
|
||||
assertEquals(4, cloneCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImmutableStructUpdate() {
|
||||
StructLogEntry<ImmutableThing> entry =
|
||||
StructLogEntry.create(log, "a", ImmutableThing.struct, 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
|
||||
entry.update(new ImmutableThing(1), 7);
|
||||
log.flush();
|
||||
assertEquals(120, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(new ImmutableThing(1), entry.getLastValue());
|
||||
|
||||
entry.update(new ImmutableThing(1), 8);
|
||||
log.flush();
|
||||
assertEquals(120, data.size());
|
||||
|
||||
entry.update(new ImmutableThing(2), 9);
|
||||
log.flush();
|
||||
assertEquals(125, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertEquals(new ImmutableThing(2), entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStructArrayUpdate() {
|
||||
StructArrayLogEntry<Thing> entry = StructArrayLogEntry.create(log, "a", Thing.struct, 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
|
||||
entry.update(new Thing[] {new Thing(1), new Thing(2)}, 7);
|
||||
log.flush();
|
||||
assertEquals(123, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new Thing[] {new Thing(1), new Thing(2)}, entry.getLastValue());
|
||||
|
||||
entry.update(new Thing[] {new Thing(1), new Thing(2)}, 8);
|
||||
log.flush();
|
||||
assertEquals(123, data.size());
|
||||
|
||||
entry.update(new Thing[] {new Thing(1), new Thing(3)}, 9);
|
||||
log.flush();
|
||||
assertEquals(129, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new Thing[] {new Thing(1), new Thing(3)}, entry.getLastValue());
|
||||
|
||||
entry.update(new Thing[] {}, 10);
|
||||
log.flush();
|
||||
assertEquals(133, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new Thing[] {}, entry.getLastValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCloneableStructArrayUpdate() {
|
||||
StructArrayLogEntry<CloneableThing> entry =
|
||||
StructArrayLogEntry.create(log, "a", CloneableThing.struct, 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
|
||||
entry.update(new CloneableThing[] {new CloneableThing(1), new CloneableThing(2)}, 7);
|
||||
assertEquals(2, cloneCalls);
|
||||
log.flush();
|
||||
assertEquals(123, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(
|
||||
new CloneableThing[] {new CloneableThing(1), new CloneableThing(2)}, entry.getLastValue());
|
||||
assertEquals(4, cloneCalls);
|
||||
|
||||
entry.update(new CloneableThing[] {new CloneableThing(1), new CloneableThing(2)}, 8);
|
||||
assertEquals(4, cloneCalls);
|
||||
log.flush();
|
||||
assertEquals(123, data.size());
|
||||
|
||||
entry.update(new CloneableThing[] {new CloneableThing(1), new CloneableThing(3)}, 9);
|
||||
assertEquals(6, cloneCalls);
|
||||
log.flush();
|
||||
assertEquals(129, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(
|
||||
new CloneableThing[] {new CloneableThing(1), new CloneableThing(3)}, entry.getLastValue());
|
||||
assertEquals(8, cloneCalls);
|
||||
|
||||
entry.update(new CloneableThing[] {}, 10);
|
||||
assertEquals(8, cloneCalls);
|
||||
log.flush();
|
||||
assertEquals(133, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new CloneableThing[] {}, entry.getLastValue());
|
||||
assertEquals(8, cloneCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImmutableStructArrayUpdate() {
|
||||
StructArrayLogEntry<ImmutableThing> entry =
|
||||
StructArrayLogEntry.create(log, "a", ImmutableThing.struct, 5);
|
||||
assertFalse(entry.hasLastValue());
|
||||
|
||||
entry.update(new ImmutableThing[] {new ImmutableThing(1), new ImmutableThing(2)}, 7);
|
||||
log.flush();
|
||||
assertEquals(123, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(
|
||||
new ImmutableThing[] {new ImmutableThing(1), new ImmutableThing(2)}, entry.getLastValue());
|
||||
|
||||
entry.update(new ImmutableThing[] {new ImmutableThing(1), new ImmutableThing(2)}, 8);
|
||||
log.flush();
|
||||
assertEquals(123, data.size());
|
||||
|
||||
entry.update(new ImmutableThing[] {new ImmutableThing(1), new ImmutableThing(3)}, 9);
|
||||
log.flush();
|
||||
assertEquals(129, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(
|
||||
new ImmutableThing[] {new ImmutableThing(1), new ImmutableThing(3)}, entry.getLastValue());
|
||||
|
||||
entry.update(new ImmutableThing[] {}, 10);
|
||||
log.flush();
|
||||
assertEquals(133, data.size());
|
||||
assertTrue(entry.hasLastValue());
|
||||
assertArrayEquals(new ImmutableThing[] {}, entry.getLastValue());
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ struct ThingA {
|
||||
int x = 0;
|
||||
};
|
||||
|
||||
inline bool operator==(const ThingA& a, const ThingA& b) {
|
||||
return a.x == b.x;
|
||||
}
|
||||
|
||||
struct ThingB {
|
||||
int x = 0;
|
||||
};
|
||||
@@ -130,10 +134,376 @@ class DataLogTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(DataLogTest, SimpleInt) {
|
||||
int entry = log.Start("test", "int64");
|
||||
log.AppendInteger(entry, 1, 0);
|
||||
int entry = log.Start("test", "int64", "", 1);
|
||||
log.AppendInteger(entry, 1, 2);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 54u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, BooleanAppend) {
|
||||
wpi::log::BooleanLogEntry entry{log, "a", 5};
|
||||
entry.Append(false, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 46u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, BooleanUpdate) {
|
||||
wpi::log::BooleanLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update(false, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 46u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), false);
|
||||
entry.Update(false, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 46u);
|
||||
entry.Update(true, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 51u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), true);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, IntegerAppend) {
|
||||
wpi::log::IntegerLogEntry entry{log, "a", 5};
|
||||
entry.Append(5, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 51u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, IntegerUpdate) {
|
||||
wpi::log::IntegerLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update(0, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 51u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), 0);
|
||||
entry.Update(0, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 51u);
|
||||
entry.Update(2, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 63u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), 2);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, FloatAppend) {
|
||||
wpi::log::FloatLogEntry entry{log, "a", 5};
|
||||
entry.Append(5.0, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 47u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, FloatUpdate) {
|
||||
wpi::log::FloatLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update(0.0f, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 47u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), 0.0f);
|
||||
entry.Update(0.0f, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 47u);
|
||||
entry.Update(0.1f, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 55u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), 0.1f);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, DoubleAppend) {
|
||||
wpi::log::DoubleLogEntry entry{log, "a", 5};
|
||||
entry.Append(5.0, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 52u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, DoubleUpdate) {
|
||||
wpi::log::DoubleLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update(0.0, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 52u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), 0.0);
|
||||
entry.Update(0.0, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 52u);
|
||||
entry.Update(0.1, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 64u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), 0.1);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StringAppend) {
|
||||
wpi::log::StringLogEntry entry{log, "a", 5};
|
||||
entry.Append("x", 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 45u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StringUpdate) {
|
||||
wpi::log::StringLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.HasLastValue());
|
||||
|
||||
entry.Update("x", 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 45u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), "x");
|
||||
|
||||
entry.Update("x", 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 45u);
|
||||
|
||||
entry.Update("y", 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 50u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), "y");
|
||||
|
||||
entry.Update("yy", 10);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 56u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), "yy");
|
||||
|
||||
entry.Update("", 11);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 60u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), "");
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, RawAppend) {
|
||||
wpi::log::RawLogEntry entry{log, "a", 5};
|
||||
entry.Append({{5}}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 42u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, RawUpdate) {
|
||||
wpi::log::RawLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.HasLastValue());
|
||||
|
||||
entry.Update({{5}}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 42u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<uint8_t>{5});
|
||||
|
||||
entry.Update({{5}}, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 42u);
|
||||
|
||||
entry.Update({{6}}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 47u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<uint8_t>{6});
|
||||
|
||||
entry.Update({{6, 6}}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 53u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), (std::vector<uint8_t>{6, 6}));
|
||||
|
||||
entry.Update(std::span<const uint8_t>{}, 10);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 57u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<uint8_t>{});
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, BooleanArrayAppendEmpty) {
|
||||
wpi::log::BooleanArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append(std::span<const bool>{}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 47u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, BooleanArrayAppend) {
|
||||
wpi::log::BooleanArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append({false}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 48u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, BooleanArrayUpdate) {
|
||||
wpi::log::BooleanArrayLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update({false}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 48u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<int>{false});
|
||||
entry.Update({false}, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 48u);
|
||||
entry.Update({true}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 53u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<int>{true});
|
||||
entry.Update(std::span<const bool>{}, 10);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 57u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<int>{});
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, IntegerArrayAppendEmpty) {
|
||||
wpi::log::IntegerArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append(std::span<const int64_t>{}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 45u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, IntegerArrayAppend) {
|
||||
wpi::log::IntegerArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append({1}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 53u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, IntegerArrayUpdate) {
|
||||
wpi::log::IntegerArrayLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update({1}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 53u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<int64_t>{1});
|
||||
entry.Update({1}, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 53u);
|
||||
entry.Update({2}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 65u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<int64_t>{2});
|
||||
entry.Update(std::span<const int64_t>{}, 10);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 69u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<int64_t>{});
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, DoubleArrayAppendEmpty) {
|
||||
wpi::log::DoubleArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append(std::span<const double>{}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 46u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, DoubleArrayAppend) {
|
||||
wpi::log::DoubleArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append({1.0}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 54u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, DoubleArrayUpdate) {
|
||||
wpi::log::DoubleArrayLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update({1.0}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 54u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<double>{1.0});
|
||||
entry.Update({1.0}, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 54u);
|
||||
entry.Update({2.0}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 66u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<double>{2});
|
||||
entry.Update(std::span<const double>{}, 10);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 70u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<double>{});
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, FloatArrayAppendEmpty) {
|
||||
wpi::log::FloatArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append(std::span<const float>{}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 45u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, FloatArrayAppend) {
|
||||
wpi::log::FloatArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append({1.0f}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 49u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, FloatArrayUpdate) {
|
||||
wpi::log::FloatArrayLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update({1.0f}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 49u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<float>{1.0f});
|
||||
entry.Update({1.0f}, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 49u);
|
||||
entry.Update({2.0f}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 57u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<float>{2.0f});
|
||||
entry.Update(std::span<const float>{}, 10);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 61u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<float>{});
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StringArrayAppendEmpty) {
|
||||
wpi::log::StringArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append(std::span<const std::string>{}, 7);
|
||||
entry.Append(std::span<const std::string_view>{}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 58u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StringArrayAppend) {
|
||||
wpi::log::StringArrayLogEntry entry{log, "a", 5};
|
||||
entry.Append({"x"}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 55u);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StringArrayUpdate) {
|
||||
wpi::log::StringArrayLogEntry entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
entry.Update({"x"}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 55u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<std::string>{"x"});
|
||||
entry.Update({"x"}, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 55u);
|
||||
entry.Update({"y"}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 68u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<std::string>{"y"});
|
||||
entry.Update(std::span<const std::string_view>{}, 10);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 76u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<std::string>{});
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StructA) {
|
||||
@@ -144,6 +514,27 @@ TEST_F(DataLogTest, StructA) {
|
||||
entry.Append(ThingA{}, 7);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StructUpdate) {
|
||||
wpi::log::StructLogEntry<ThingA> entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
|
||||
entry.Update(ThingA{}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 122u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), ThingA{});
|
||||
|
||||
entry.Update(ThingA{}, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 122u);
|
||||
|
||||
entry.Update(ThingA{.x = 1}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 127u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), ThingA{.x = 1});
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StructArrayA) {
|
||||
[[maybe_unused]]
|
||||
wpi::log::StructArrayLogEntry<ThingA> entry0;
|
||||
@@ -152,6 +543,35 @@ TEST_F(DataLogTest, StructArrayA) {
|
||||
entry.Append({{ThingA{}, ThingA{}}}, 7);
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StructArrayUpdate) {
|
||||
wpi::log::StructArrayLogEntry<ThingA> entry{log, "a", 5};
|
||||
ASSERT_FALSE(entry.GetLastValue().has_value());
|
||||
|
||||
entry.Update({{ThingA{}, ThingA{.x = 1}}}, 7);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 125u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(),
|
||||
(std::vector<ThingA>{ThingA{}, ThingA{.x = 1}}));
|
||||
|
||||
entry.Update({{ThingA{}, ThingA{.x = 1}}}, 8);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 125u);
|
||||
|
||||
entry.Update({{ThingA{}, ThingA{.x = 2}}}, 9);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 131u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(),
|
||||
(std::vector<ThingA>{ThingA{}, ThingA{.x = 2}}));
|
||||
|
||||
entry.Update(std::span<const ThingA>{}, 10);
|
||||
log.Flush();
|
||||
ASSERT_EQ(data.size(), 135u);
|
||||
ASSERT_TRUE(entry.GetLastValue().has_value());
|
||||
ASSERT_EQ(entry.GetLastValue().value(), std::vector<ThingA>{});
|
||||
}
|
||||
|
||||
TEST_F(DataLogTest, StructFixedArrayA) {
|
||||
[[maybe_unused]]
|
||||
wpi::log::StructArrayLogEntry<std::array<ThingA, 2>> entry0;
|
||||
|
||||
Reference in New Issue
Block a user