diff --git a/wpiutil/CMakeLists.txt b/wpiutil/CMakeLists.txt index 113de0a4bc6..a42934ab802 100644 --- a/wpiutil/CMakeLists.txt +++ b/wpiutil/CMakeLists.txt @@ -96,6 +96,7 @@ if(WITH_JAVA_SOURCE) file(GLOB WPIUTIL_CLEANUP_SOURCES src/main/java/edu/wpi/first/util/cleanup/*.java) file(GLOB WPIUTIL_CONCURRENT_SOURCES src/main/java/edu/wpi/first/util/concurrent/*.java) file(GLOB WPIUTIL_DATALOG_SOURCES src/main/java/edu/wpi/first/util/datalog/*.java) + file(GLOB WPIUTIL_DATALOG_SOURCES src/generated/main/java/edu/wpi/first/util/datalog/*.java) file(GLOB WPIUTIL_FUNCTION_SOURCES src/main/java/edu/wpi/first/util/function/*.java) file(GLOB WPIUTIL_SENDABLE_SOURCES src/main/java/edu/wpi/first/util/sendable/*.java) add_jar( diff --git a/wpiutil/build.gradle b/wpiutil/build.gradle index 941fe2ead36..b8af4106571 100644 --- a/wpiutil/build.gradle +++ b/wpiutil/build.gradle @@ -274,6 +274,8 @@ model { } } +sourceSets.main.java.srcDir "${projectDir}/src/generated/main/java" + sourceSets { printlog } diff --git a/wpiutil/generate_log_entries.py b/wpiutil/generate_log_entries.py new file mode 100755 index 00000000000..433dd6085aa --- /dev/null +++ b/wpiutil/generate_log_entries.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +# 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. +import argparse +import json +import sys +from pathlib import Path +from typing import Dict, Any + +from jinja2 import Environment, FileSystemLoader +from jinja2.environment import Template + + +def render_template( + template: Template, output_dir: Path, filename: str, ty: Dict[str, Any] +): + output_dir.mkdir(parents=True, exist_ok=True) + + output_file = output_dir / filename + output_file.write_text(template.render(ty), encoding="utf-8") + + +def generate_log_entries(output_root, template_root): + with (template_root / "types.json").open(encoding="utf-8") as f: + types = json.load(f) + + env = Environment( + loader=FileSystemLoader(str(template_root)), + autoescape=False, + keep_trailing_newline=True, + ) + + root_path = Path(output_root) / "main/java/edu/wpi/first/util/datalog" + template = env.get_template( + "main/java/edu/wpi/first/util/datalog/LogEntry.java.jinja" + ) + + for ty in types: + entry_name = f"{ty['name']}LogEntry.java" + render_template(template, root_path, entry_name, ty) + + +def main(argv): + script_path = Path(__file__).resolve() + dirname = script_path.parent + + parser = argparse.ArgumentParser() + parser.add_argument( + "--output_directory", + help="Optional. If set, will output the generated files to this directory, otherwise it will use a path relative to the script", + default=dirname / "src/generated", + type=Path, + ) + parser.add_argument( + "--template_root", + help="Optional. If set, will use this directory as the root for the jinja templates", + default=dirname / "src/generate", + type=Path, + ) + args = parser.parse_args(argv) + + generate_log_entries(args.output_directory, args.template_root) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/wpiutil/src/generate/main/java/edu/wpi/first/util/datalog/LogEntry.java.jinja b/wpiutil/src/generate/main/java/edu/wpi/first/util/datalog/LogEntry.java.jinja new file mode 100644 index 00000000000..3ff177e1af0 --- /dev/null +++ b/wpiutil/src/generate/main/java/edu/wpi/first/util/datalog/LogEntry.java.jinja @@ -0,0 +1,111 @@ +// 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. + +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + +package edu.wpi.first.util.datalog; + +/** Log {{ DataType }} values. */ +public class {{ name }}LogEntry extends DataLogEntry { + /** The data type for double values. */ + public static final String kDataType = "{{ DataType }}"; + + /** + * Constructs a {{ DataType }} log entry. + * + * @param log datalog + * @param name name of the entry + * @param metadata metadata + * @param timestamp entry creation timestamp (0=now) + */ + public {{ name }}LogEntry(DataLog log, String name, String metadata, long timestamp) { + super(log, name, kDataType, metadata, timestamp); + } + + /** + * Constructs a {{ DataType }} log entry. + * + * @param log datalog + * @param name name of the entry @param metadata metadata + * @param metadata metadata + */ + public {{ name }}LogEntry(DataLog log, String name, String metadata) { + this(log, name, metadata, 0); + } + + /** + * Constructs a {{ DataType }} log entry. + * + * @param log datalog + * @param name name of the entry + * @param timestamp entry creation timestamp (0=now) + */ + public {{ name }}LogEntry(DataLog log, String name, long timestamp) { + this(log, name, "", timestamp); + } + + /** + * Constructs a {{ DataType }} log entry. + * + * @param log datalog + * @param name name of the entry + */ + public {{ name }}LogEntry(DataLog log, String name) { + this(log, name, 0); + } + + /** + * Appends a record to the log. + * + * @param value Value to record + * @param timestamp Time stamp (0 to indicate now) + */ + public void append({{ AppendType }} value, long timestamp) { + m_log.append{{ name }}(m_entry, value, timestamp); + } + + /** + * Appends a record to the log. + * + * @param value Value to record + */ + public void append({{ AppendType }} value) { + m_log.append{{ name }}(m_entry, value, 0); + } + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param value Value to record + * @param timestamp Time stamp (0 to indicate now) + */ + public synchronized void update({{ AppendType }} 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. + * + * @param value Value to record + */ + public void update({{ AppendType }} value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized {{ AppendType }} getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + {{ AppendType }} m_lastValue; +} diff --git a/wpiutil/src/generate/types.json b/wpiutil/src/generate/types.json new file mode 100644 index 00000000000..e1eddf31efc --- /dev/null +++ b/wpiutil/src/generate/types.json @@ -0,0 +1,52 @@ +[ + { + "name": "Double", + "DataType": "double", + "AppendType": "double" + }, + { + "name": "DoubleArray", + "DataType": "double[]", + "AppendType": "double[]" + }, + { + "name": "Float", + "DataType": "float", + "AppendType": "float" + }, + { + "name": "FloatArray", + "DataType": "float[]", + "AppendType": "float[]" + }, + { + "name": "Integer", + "DataType": "int64", + "AppendType": "long" + }, + { + "name": "IntegerArray", + "DataType": "int64[]", + "AppendType": "long[]" + }, + { + "name": "Boolean", + "DataType": "boolean", + "AppendType": "boolean" + }, + { + "name": "BooleanArray", + "DataType": "boolean[]", + "AppendType": "boolean[]" + }, + { + "name": "String", + "DataType": "String", + "AppendType": "String" + }, + { + "name": "StringArray", + "DataType": "string[]", + "AppendType": "String[]" + } +] diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/BooleanArrayLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/BooleanArrayLogEntry.java similarity index 58% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/BooleanArrayLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/BooleanArrayLogEntry.java index 21ac5963593..5662f0dcfbf 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/BooleanArrayLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/BooleanArrayLogEntry.java @@ -2,15 +2,17 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; -/** Log array of boolean values. */ +/** Log boolean[] values. */ public class BooleanArrayLogEntry extends DataLogEntry { - /** The data type for boolean array values. */ + /** The data type for double values. */ public static final String kDataType = "boolean[]"; /** - * Constructs a boolean array log entry. + * Constructs a boolean[] log entry. * * @param log datalog * @param name name of the entry @@ -22,10 +24,10 @@ public BooleanArrayLogEntry(DataLog log, String name, String metadata, long time } /** - * Constructs a boolean array log entry. + * Constructs a boolean[] log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public BooleanArrayLogEntry(DataLog log, String name, String metadata) { @@ -33,7 +35,7 @@ public BooleanArrayLogEntry(DataLog log, String name, String metadata) { } /** - * Constructs a boolean array log entry. + * Constructs a boolean[] log entry. * * @param log datalog * @param name name of the entry @@ -44,7 +46,7 @@ public BooleanArrayLogEntry(DataLog log, String name, long timestamp) { } /** - * Constructs a boolean array log entry. + * Constructs a boolean[] log entry. * * @param log datalog * @param name name of the entry @@ -71,4 +73,39 @@ public void append(boolean[] value, long timestamp) { 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. + * + * @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. + * + * @param value Value to record + */ + public void update(boolean[] value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized boolean[] getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + boolean[] m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/BooleanLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/BooleanLogEntry.java similarity index 64% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/BooleanLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/BooleanLogEntry.java index c413bfa42a7..1e1d807d39f 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/BooleanLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/BooleanLogEntry.java @@ -2,11 +2,13 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; /** Log boolean values. */ public class BooleanLogEntry extends DataLogEntry { - /** The data type for boolean values. */ + /** The data type for double values. */ public static final String kDataType = "boolean"; /** @@ -25,7 +27,7 @@ public BooleanLogEntry(DataLog log, String name, String metadata, long timestamp * Constructs a boolean log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public BooleanLogEntry(DataLog log, String name, String metadata) { @@ -71,4 +73,39 @@ public void append(boolean value, long timestamp) { 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. + * + * @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. + * + * @param value Value to record + */ + public void update(boolean value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized boolean getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + boolean m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DoubleArrayLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/DoubleArrayLogEntry.java similarity index 58% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/DoubleArrayLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/DoubleArrayLogEntry.java index 485a9c8c82b..2415d6806ba 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DoubleArrayLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/DoubleArrayLogEntry.java @@ -2,15 +2,17 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; -/** Log array of double values. */ +/** Log double[] values. */ public class DoubleArrayLogEntry extends DataLogEntry { - /** The data type for double array values. */ + /** The data type for double values. */ public static final String kDataType = "double[]"; /** - * Constructs a double array log entry. + * Constructs a double[] log entry. * * @param log datalog * @param name name of the entry @@ -22,10 +24,10 @@ public DoubleArrayLogEntry(DataLog log, String name, String metadata, long times } /** - * Constructs a double array log entry. + * Constructs a double[] log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public DoubleArrayLogEntry(DataLog log, String name, String metadata) { @@ -33,7 +35,7 @@ public DoubleArrayLogEntry(DataLog log, String name, String metadata) { } /** - * Constructs a double array log entry. + * Constructs a double[] log entry. * * @param log datalog * @param name name of the entry @@ -44,7 +46,7 @@ public DoubleArrayLogEntry(DataLog log, String name, long timestamp) { } /** - * Constructs a double array log entry. + * Constructs a double[] log entry. * * @param log datalog * @param name name of the entry @@ -71,4 +73,39 @@ public void append(double[] value, long timestamp) { 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. + * + * @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. + * + * @param value Value to record + */ + public void update(double[] value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized double[] getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + double[] m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DoubleLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/DoubleLogEntry.java similarity index 66% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/DoubleLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/DoubleLogEntry.java index a089df2cd8a..8f402ea19c5 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DoubleLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/DoubleLogEntry.java @@ -2,6 +2,8 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; /** Log double values. */ @@ -25,7 +27,7 @@ public DoubleLogEntry(DataLog log, String name, String metadata, long timestamp) * Constructs a double log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public DoubleLogEntry(DataLog log, String name, String metadata) { @@ -71,4 +73,39 @@ public void append(double value, long timestamp) { 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. + * + * @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. + * + * @param value Value to record + */ + public void update(double value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized double getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + double m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/FloatArrayLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/FloatArrayLogEntry.java similarity index 59% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/FloatArrayLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/FloatArrayLogEntry.java index be25970ffcd..70224263138 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/FloatArrayLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/FloatArrayLogEntry.java @@ -2,15 +2,17 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; -/** Log array of float values. */ +/** Log float[] values. */ public class FloatArrayLogEntry extends DataLogEntry { - /** The data type for float array values. */ + /** The data type for double values. */ public static final String kDataType = "float[]"; /** - * Constructs a float array log entry. + * Constructs a float[] log entry. * * @param log datalog * @param name name of the entry @@ -22,10 +24,10 @@ public FloatArrayLogEntry(DataLog log, String name, String metadata, long timest } /** - * Constructs a float array log entry. + * Constructs a float[] log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public FloatArrayLogEntry(DataLog log, String name, String metadata) { @@ -33,7 +35,7 @@ public FloatArrayLogEntry(DataLog log, String name, String metadata) { } /** - * Constructs a float array log entry. + * Constructs a float[] log entry. * * @param log datalog * @param name name of the entry @@ -44,7 +46,7 @@ public FloatArrayLogEntry(DataLog log, String name, long timestamp) { } /** - * Constructs a float array log entry. + * Constructs a float[] log entry. * * @param log datalog * @param name name of the entry @@ -71,4 +73,39 @@ public void append(float[] value, long timestamp) { 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. + * + * @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. + * + * @param value Value to record + */ + public void update(float[] value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized float[] getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + float[] m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/FloatLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/FloatLogEntry.java similarity index 64% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/FloatLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/FloatLogEntry.java index 28f83cb6a60..bbe07835d09 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/FloatLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/FloatLogEntry.java @@ -2,11 +2,13 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; /** Log float values. */ public class FloatLogEntry extends DataLogEntry { - /** The data type for float values. */ + /** The data type for double values. */ public static final String kDataType = "float"; /** @@ -25,7 +27,7 @@ public FloatLogEntry(DataLog log, String name, String metadata, long timestamp) * Constructs a float log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public FloatLogEntry(DataLog log, String name, String metadata) { @@ -71,4 +73,39 @@ public void append(float value, long timestamp) { 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. + * + * @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. + * + * @param value Value to record + */ + public void update(float value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized float getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + float m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/IntegerArrayLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/IntegerArrayLogEntry.java similarity index 59% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/IntegerArrayLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/IntegerArrayLogEntry.java index d2f8f0ea52f..b94100d4e1f 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/IntegerArrayLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/IntegerArrayLogEntry.java @@ -2,15 +2,17 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; -/** Log array of integer values. */ +/** Log int64[] values. */ public class IntegerArrayLogEntry extends DataLogEntry { - /** The data type for integer array values. */ + /** The data type for double values. */ public static final String kDataType = "int64[]"; /** - * Constructs a integer array log entry. + * Constructs a int64[] log entry. * * @param log datalog * @param name name of the entry @@ -22,10 +24,10 @@ public IntegerArrayLogEntry(DataLog log, String name, String metadata, long time } /** - * Constructs a integer array log entry. + * Constructs a int64[] log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public IntegerArrayLogEntry(DataLog log, String name, String metadata) { @@ -33,7 +35,7 @@ public IntegerArrayLogEntry(DataLog log, String name, String metadata) { } /** - * Constructs a integer array log entry. + * Constructs a int64[] log entry. * * @param log datalog * @param name name of the entry @@ -44,7 +46,7 @@ public IntegerArrayLogEntry(DataLog log, String name, long timestamp) { } /** - * Constructs a integer array log entry. + * Constructs a int64[] log entry. * * @param log datalog * @param name name of the entry @@ -71,4 +73,39 @@ public void append(long[] value, long timestamp) { 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. + * + * @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. + * + * @param value Value to record + */ + public void update(long[] value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized long[] getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + long[] m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/IntegerLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/IntegerLogEntry.java similarity index 59% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/IntegerLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/IntegerLogEntry.java index 395a208f74c..fdbc89d1d6f 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/IntegerLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/IntegerLogEntry.java @@ -2,15 +2,17 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; -/** Log integer values. */ +/** Log int64 values. */ public class IntegerLogEntry extends DataLogEntry { - /** The data type for integer values. */ + /** The data type for double values. */ public static final String kDataType = "int64"; /** - * Constructs a integer log entry. + * Constructs a int64 log entry. * * @param log datalog * @param name name of the entry @@ -22,10 +24,10 @@ public IntegerLogEntry(DataLog log, String name, String metadata, long timestamp } /** - * Constructs a integer log entry. + * Constructs a int64 log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public IntegerLogEntry(DataLog log, String name, String metadata) { @@ -33,7 +35,7 @@ public IntegerLogEntry(DataLog log, String name, String metadata) { } /** - * Constructs a integer log entry. + * Constructs a int64 log entry. * * @param log datalog * @param name name of the entry @@ -44,7 +46,7 @@ public IntegerLogEntry(DataLog log, String name, long timestamp) { } /** - * Constructs a integer log entry. + * Constructs a int64 log entry. * * @param log datalog * @param name name of the entry @@ -71,4 +73,39 @@ public void append(long value, long timestamp) { 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. + * + * @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. + * + * @param value Value to record + */ + public void update(long value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized long getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + long m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/StringArrayLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/StringArrayLogEntry.java similarity index 58% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/StringArrayLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/StringArrayLogEntry.java index f0a6dde7377..319609df5bd 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/StringArrayLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/StringArrayLogEntry.java @@ -2,15 +2,17 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; -/** Log array of string values. */ +/** Log string[] values. */ public class StringArrayLogEntry extends DataLogEntry { - /** The data type for string array values. */ + /** The data type for double values. */ public static final String kDataType = "string[]"; /** - * Constructs a string array log entry. + * Constructs a string[] log entry. * * @param log datalog * @param name name of the entry @@ -22,10 +24,10 @@ public StringArrayLogEntry(DataLog log, String name, String metadata, long times } /** - * Constructs a string array log entry. + * Constructs a string[] log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public StringArrayLogEntry(DataLog log, String name, String metadata) { @@ -33,7 +35,7 @@ public StringArrayLogEntry(DataLog log, String name, String metadata) { } /** - * Constructs a string array log entry. + * Constructs a string[] log entry. * * @param log datalog * @param name name of the entry @@ -44,7 +46,7 @@ public StringArrayLogEntry(DataLog log, String name, long timestamp) { } /** - * Constructs a string array log entry. + * Constructs a string[] log entry. * * @param log datalog * @param name name of the entry @@ -71,4 +73,39 @@ public void append(String[] value, long timestamp) { 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. + * + * @param value Value to record + * @param timestamp Time stamp (0 to indicate now) + */ + public synchronized void update(String[] 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. + * + * @param value Value to record + */ + public void update(String[] value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized String[] getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + String[] m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/StringLogEntry.java b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/StringLogEntry.java similarity index 61% rename from wpiutil/src/main/java/edu/wpi/first/util/datalog/StringLogEntry.java rename to wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/StringLogEntry.java index 27c8aefef57..c95d05d5ea2 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/StringLogEntry.java +++ b/wpiutil/src/generated/main/java/edu/wpi/first/util/datalog/StringLogEntry.java @@ -2,37 +2,14 @@ // 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. +// THIS FILE WAS GENERATED BY generate_log_entries.py DO NOT EDIT IT MANUALLY + package edu.wpi.first.util.datalog; -/** Log string values. */ +/** Log String values. */ public class StringLogEntry extends DataLogEntry { - /** The data type for string values. */ - public static final String kDataType = "string"; - - /** - * Constructs a String log entry. - * - * @param log datalog - * @param name name of the entry - * @param metadata metadata - * @param type Data type - * @param timestamp entry creation timestamp (0=now) - */ - public StringLogEntry(DataLog log, String name, String metadata, String type, long timestamp) { - super(log, name, type, metadata, timestamp); - } - - /** - * Constructs a String log entry. - * - * @param log datalog - * @param name name of the entry - * @param metadata metadata - * @param type Data type - */ - public StringLogEntry(DataLog log, String name, String metadata, String type) { - this(log, name, metadata, type, 0); - } + /** The data type for double values. */ + public static final String kDataType = "String"; /** * Constructs a String log entry. @@ -43,14 +20,14 @@ public StringLogEntry(DataLog log, String name, String metadata, String type) { * @param timestamp entry creation timestamp (0=now) */ public StringLogEntry(DataLog log, String name, String metadata, long timestamp) { - this(log, name, metadata, kDataType, timestamp); + super(log, name, kDataType, metadata, timestamp); } /** * Constructs a String log entry. * * @param log datalog - * @param name name of the entry + * @param name name of the entry @param metadata metadata * @param metadata metadata */ public StringLogEntry(DataLog log, String name, String metadata) { @@ -96,4 +73,39 @@ public void append(String value, long timestamp) { 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. + * + * @param value Value to record + * @param timestamp Time stamp (0 to indicate now) + */ + public synchronized void update(String 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. + * + * @param value Value to record + */ + public void update(String value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or 0 if none. + */ + public synchronized String getLastValue() { + return m_lastValue; + } + + boolean m_hasLastValue; + String m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLog.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLog.java index bec3a7bde6a..4a89542cb75 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLog.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLog.java @@ -269,6 +269,12 @@ public void setMetadata(int entry, String metadata) { 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 void appendRaw(int entry, ByteBuffer data, int start, int len, long times 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. * diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/ProtobufLogEntry.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/ProtobufLogEntry.java index 9e7fa43b344..61de93eae15 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/ProtobufLogEntry.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/ProtobufLogEntry.java @@ -20,6 +20,8 @@ private ProtobufLogEntry( DataLog log, String name, Protobuf 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,102 @@ public void append(T value) { append(value, 0); } + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @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. + * + * @param value Value to record + */ + public void update(T value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or null if none. + */ + public T getLastValue() { + synchronized (m_buf) { + if (m_immutable) { + return m_lastValue; + } + if (m_cloneable) { + if (m_lastValue == null) { + return 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 m_buf; + private ByteBuffer m_lastValueBuf; + private final boolean m_immutable; + private final boolean m_cloneable; + private T m_lastValue; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/RawLogEntry.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/RawLogEntry.java index a9e3373335a..64ad65b7128 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/RawLogEntry.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/RawLogEntry.java @@ -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 void append(byte[] value, int start, int len) { /** * 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 void append(ByteBuffer value, long timestamp) { /** * 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,164 @@ public void append(ByteBuffer value, int start, int len, long timestamp) { 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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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. + * + * @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 the last value. + * + * @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); + } + + 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; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/StructArrayLogEntry.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/StructArrayLogEntry.java index e208f2c0d0d..e46d912d91d 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/StructArrayLogEntry.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/StructArrayLogEntry.java @@ -6,7 +6,9 @@ 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 @@ private StructArrayLogEntry( DataLog log, String name, Struct 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 static StructArrayLogEntry create(DataLog log, String name, Struct * @param nelem number of elements */ public void reserve(int nelem) { - synchronized (this) { + synchronized (m_buf) { m_buf.reserve(nelem); } } @@ -99,7 +103,7 @@ public void reserve(int nelem) { * @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 void append(T[] value) { * @param timestamp Time stamp (0 to indicate now) */ public void append(Collection 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,210 @@ public void append(Collection value) { append(value, 0); } + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @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. + * + * @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. + * + * @param value Value to record + * @param timestamp Time stamp (0 to indicate now) + */ + public void update(Collection 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. + * + * @param value Value to record + */ + public void update(Collection value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or null if none. + */ + @SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull") + public T[] getLastValue() { + synchronized (m_buf) { + if (m_immutable || m_cloneable) { + if (m_lastValue == null) { + return null; + } + if (m_immutable) { + return Arrays.copyOf(m_lastValue, m_lastValueLen); + } else { + try { + return cloneArray(m_lastValue); + } 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 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) throws CloneNotSupportedException { + Struct s = m_buf.getStruct(); + @SuppressWarnings("unchecked") + T[] arr = (T[]) Array.newInstance(s.getTypeClass(), in.length); + for (int i = 0; i < in.length; i++) { + arr[i] = s.clone(in[i]); + } + return arr; + } + + private T[] cloneArray(Collection in) throws CloneNotSupportedException { + Struct 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); + } + } else { + if (m_immutable) { + System.arraycopy(value, 0, m_lastValue, 0, value.length); + } else { + Struct 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 value) throws CloneNotSupportedException { + if (m_lastValue == null || m_lastValue.length < value.size()) { + if (m_immutable) { + Struct 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 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 m_buf; + private ByteBuffer m_lastValueBuf; + private final boolean m_immutable; + private final boolean m_cloneable; + private T[] m_lastValue; + private int m_lastValueLen; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/StructLogEntry.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/StructLogEntry.java index 0d09182315e..08077d39b63 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/datalog/StructLogEntry.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/StructLogEntry.java @@ -18,6 +18,8 @@ private StructLogEntry( DataLog log, String name, Struct 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,94 @@ public void append(T value) { append(value, 0); } + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @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. + * + * @param value Value to record + */ + public void update(T value) { + update(value, 0); + } + + /** + * Gets the last value. + * + * @return Last value, or null if none. + */ + public T getLastValue() { + synchronized (m_buf) { + if (m_immutable) { + return m_lastValue; + } + if (m_cloneable) { + if (m_lastValue == null) { + return 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 m_buf; + private ByteBuffer m_lastValueBuf; + private final boolean m_immutable; + private final boolean m_cloneable; + private T m_lastValue; } diff --git a/wpiutil/src/main/native/cpp/DataLog.cpp b/wpiutil/src/main/native/cpp/DataLog.cpp index 5234c8e7059..dd717cc6c0a 100644 --- a/wpiutil/src/main/native/cpp/DataLog.cpp +++ b/wpiutil/src/main/native/cpp/DataLog.cpp @@ -4,6 +4,7 @@ #include "wpi/DataLog.h" +#include #include #include #include @@ -618,6 +619,107 @@ void DataLog::AppendStringArray(int entry, } } +template +inline bool UpdateImpl(std::optional>& lastValue, + std::span 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{data.begin(), data.end()}; + } + return true; + } + return false; +} + +template +inline bool UpdateImpl(std::optional>& lastValue, + std::span data) { + if (!lastValue || !std::equal(data.begin(), data.end(), lastValue->begin(), + lastValue->end(), [](auto a, auto b) { + return a == static_cast(b); + })) { + if (lastValue) { + lastValue->assign(data.begin(), data.end()); + } else { + lastValue = std::vector{data.begin(), data.end()}; + } + return true; + } + return false; +} + +void RawLogEntry::Update(std::span data, int64_t timestamp) { + std::scoped_lock lock{m_mutex}; + if (UpdateImpl(m_lastValue, data)) { + Append(data, timestamp); + } +} + +void BooleanArrayLogEntry::Update(std::span arr, + int64_t timestamp) { + std::scoped_lock lock{m_mutex}; + if (UpdateImpl(m_lastValue, arr)) { + Append(arr, timestamp); + } +} + +void BooleanArrayLogEntry::Update(std::span arr, int64_t timestamp) { + std::scoped_lock lock{m_mutex}; + if (UpdateImpl(m_lastValue, arr)) { + Append(arr, timestamp); + } +} + +void BooleanArrayLogEntry::Update(std::span arr, + int64_t timestamp) { + std::scoped_lock lock{m_mutex}; + if (UpdateImpl(m_lastValue, arr)) { + Append(arr, timestamp); + } +} + +void IntegerArrayLogEntry::Update(std::span arr, + int64_t timestamp) { + std::scoped_lock lock{m_mutex}; + if (UpdateImpl(m_lastValue, arr)) { + Append(arr, timestamp); + } +} + +void FloatArrayLogEntry::Update(std::span arr, int64_t timestamp) { + std::scoped_lock lock{m_mutex}; + if (UpdateImpl(m_lastValue, arr)) { + Append(arr, timestamp); + } +} + +void DoubleArrayLogEntry::Update(std::span arr, + int64_t timestamp) { + std::scoped_lock lock{m_mutex}; + if (UpdateImpl(m_lastValue, arr)) { + Append(arr, timestamp); + } +} + +void StringArrayLogEntry::Update(std::span arr, + int64_t timestamp) { + std::scoped_lock lock{m_mutex}; + if (UpdateImpl(m_lastValue, arr)) { + Append(arr, timestamp); + } +} + +void StringArrayLogEntry::Update(std::span 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) { diff --git a/wpiutil/src/main/native/include/wpi/DataLog.h b/wpiutil/src/main/native/include/wpi/DataLog.h index 9fa209b2447..8401e3538c9 100644 --- a/wpiutil/src/main/native/include/wpi/DataLog.h +++ b/wpiutil/src/main/native/include/wpi/DataLog.h @@ -6,8 +6,10 @@ #include +#include #include #include +#include #include #include #include @@ -575,10 +577,48 @@ class DataLogEntry { int m_entry = 0; }; +template +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 the last value. Note that Append() calls do not update the last + * value. + * + * @return Last value (empty if no last value) + */ + std::optional GetLastValue() const { + std::scoped_lock lock{m_mutex}; + return m_lastValue; + } + + protected: + mutable wpi::mutex m_mutex; + std::optional m_lastValue; +}; + /** * Log arbitrary byte data. */ -class RawLogEntry : public DataLogEntry { +class RawLogEntry : public DataLogValueEntryImpl> { public: static constexpr std::string_view kDataType = "raw"; @@ -590,7 +630,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 +641,20 @@ class RawLogEntry : public DataLogEntry { void Append(std::span 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. + * + * @param data Data to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span data, int64_t timestamp = 0); }; /** * Log boolean values. */ -class BooleanLogEntry : public DataLogEntry { +class BooleanLogEntry : public DataLogValueEntryImpl { public: static constexpr std::string_view kDataType = "boolean"; @@ -615,7 +663,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 +674,26 @@ 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. + * + * @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 { public: static constexpr std::string_view kDataType = "int64"; @@ -640,7 +702,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 +713,26 @@ 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. + * + * @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 { public: static constexpr std::string_view kDataType = "float"; @@ -665,7 +741,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 +752,26 @@ 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. + * + * @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 { public: static constexpr std::string_view kDataType = "double"; @@ -690,7 +780,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 +791,26 @@ 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. + * + * @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 { public: static constexpr const char* kDataType = "string"; @@ -718,7 +822,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 +833,26 @@ 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. + * + * @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> { public: static constexpr const char* kDataType = "boolean[]"; @@ -744,7 +862,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 +914,57 @@ class BooleanArrayLogEntry : public DataLogEntry { void Append(std::span 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. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span arr, int64_t timestamp = 0); + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::initializer_list 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. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span arr, int64_t timestamp = 0); + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::initializer_list 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. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span arr, int64_t timestamp = 0); }; /** * Log array of integer values. */ -class IntegerArrayLogEntry : public DataLogEntry { +class IntegerArrayLogEntry + : public DataLogValueEntryImpl> { public: static constexpr const char* kDataType = "int64[]"; @@ -811,7 +974,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 +995,30 @@ class IntegerArrayLogEntry : public DataLogEntry { void Append(std::initializer_list 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. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span arr, int64_t timestamp = 0); + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::initializer_list arr, int64_t timestamp = 0) { + Update({arr.begin(), arr.end()}, timestamp); + } }; /** * Log array of float values. */ -class FloatArrayLogEntry : public DataLogEntry { +class FloatArrayLogEntry : public DataLogValueEntryImpl> { public: static constexpr const char* kDataType = "float[]"; @@ -846,7 +1027,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 +1048,30 @@ class FloatArrayLogEntry : public DataLogEntry { void Append(std::initializer_list 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. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span arr, int64_t timestamp = 0); + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::initializer_list arr, int64_t timestamp = 0) { + Update({arr.begin(), arr.end()}, timestamp); + } }; /** * Log array of double values. */ -class DoubleArrayLogEntry : public DataLogEntry { +class DoubleArrayLogEntry : public DataLogValueEntryImpl> { public: static constexpr const char* kDataType = "double[]"; @@ -882,7 +1081,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 +1102,31 @@ class DoubleArrayLogEntry : public DataLogEntry { void Append(std::initializer_list 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. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span arr, int64_t timestamp = 0); + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::initializer_list arr, int64_t timestamp = 0) { + Update({arr.begin(), arr.end()}, timestamp); + } }; /** * Log array of string values. */ -class StringArrayLogEntry : public DataLogEntry { +class StringArrayLogEntry + : public DataLogValueEntryImpl> { public: static constexpr const char* kDataType = "string[]"; @@ -918,7 +1136,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 +1169,34 @@ class StringArrayLogEntry : public DataLogEntry { Append(std::span{arr.begin(), arr.end()}, timestamp); } + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span arr, int64_t timestamp = 0); + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span arr, int64_t timestamp = 0); + + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param arr Values to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::initializer_list arr, + int64_t timestamp = 0) { + Update(std::span{arr.begin(), arr.end()}, + timestamp); + } }; /** @@ -974,6 +1220,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 +1254,57 @@ 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. + * + * @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 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 the last value. Note that Append() calls do not update the last + * value. + * + * @return Last value (empty if no last value) + */ + std::optional GetLastValue() const { + std::scoped_lock lock{m_mutex}; + if (m_lastValue.empty()) { + return std::nullopt; + } + return std::apply( + [&](const I&... info) { S::Unpack(m_lastValue, info...); }, m_info); + } + private: + mutable wpi::mutex m_mutex; + std::vector m_lastValue; [[no_unique_address]] std::tuple m_info; }; @@ -1024,6 +1333,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 +1388,61 @@ class StructArrayLogEntry : public DataLogEntry { m_info); } + /** + * Updates the last value and appends a record to the log if it has changed. + * + * @param data Data to record + * @param timestamp Time stamp (may be 0 to indicate now) + */ + void Update(std::span 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.empty() || + !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 the last value. Note that Append() calls do not update the last + * value. + * + * @return Last value (empty if no last value) + */ + std::optional> GetLastValue() const { + std::scoped_lock lock{m_mutex}; + if (m_lastValue.empty()) { + return std::nullopt; + } + size_t size = std::apply(S::GetSize, m_info); + std::vector rv; + rv.value.reserve(m_lastValue.size() / size); + for (auto in = m_lastValue.begin(), end = m_lastValue.end(); in < end; + in += size) { + std::apply( + [&](const I&... info) { + rv.value.emplace_back(S::Unpack( + std::span{std::to_address(in), size}, info...)); + }, + m_info); + } + return rv; + } + private: + mutable wpi::mutex m_mutex; StructArrayBuffer m_buf; + std::vector m_lastValue; [[no_unique_address]] std::tuple m_info; }; @@ -1102,9 +1480,42 @@ 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. + * + * @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 buf; + m_msg.Pack(buf, data); + 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 the last value. Note that Append() calls do not update the last + * value. + * + * @return Last value (empty if no last value) + */ + std::optional GetLastValue() const { + std::scoped_lock lock{m_mutex}; + if (m_lastValue.empty()) { + return std::nullopt; + } + return m_msg.Unpack(m_lastValue); + } + private: - wpi::mutex m_mutex; + mutable wpi::mutex m_mutex; ProtobufMessage m_msg; + std::vector m_lastValue; }; } // namespace wpi::log diff --git a/wpiutil/src/test/native/cpp/DataLogTest.cpp b/wpiutil/src/test/native/cpp/DataLogTest.cpp index f4389abcb02..0973964fc45 100644 --- a/wpiutil/src/test/native/cpp/DataLogTest.cpp +++ b/wpiutil/src/test/native/cpp/DataLogTest.cpp @@ -136,6 +136,168 @@ TEST_F(DataLogTest, SimpleInt) { ASSERT_EQ(data.size(), 66u); } +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.0, 7); + log.Flush(); + ASSERT_EQ(data.size(), 47u); + ASSERT_TRUE(entry.GetLastValue().has_value()); + ASSERT_EQ(entry.GetLastValue().value(), 0.0); + entry.Update(0.0, 8); + log.Flush(); + ASSERT_EQ(data.size(), 47u); + entry.Update(0.1, 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.GetLastValue().has_value()); + 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"); +} + +TEST_F(DataLogTest, BooleanArrayAppendEmpty) { + wpi::log::BooleanArrayLogEntry entry{log, "a", 5}; + entry.Append(std::span{}, 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{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{true}); + entry.Update(std::span{}, 10); + log.Flush(); + ASSERT_EQ(data.size(), 57u); + ASSERT_TRUE(entry.GetLastValue().has_value()); + ASSERT_EQ(entry.GetLastValue().value(), std::vector{}); +} + TEST_F(DataLogTest, StructA) { [[maybe_unused]] wpi::log::StructLogEntry entry0;