diff --git a/.github/workflows/aql/wpilib-mvn-development_unused.aql b/.github/workflows/aql/wpilib-mvn-development_unused.aql index 365094310c..7dc91c9af9 100644 --- a/.github/workflows/aql/wpilib-mvn-development_unused.aql +++ b/.github/workflows/aql/wpilib-mvn-development_unused.aql @@ -3,7 +3,10 @@ { "aql": { "items.find": { - "repo": "wpilib-mvn-development-local", + "$or":[ + { "repo": "wpilib-mvn-development-local" }, + { "repo": "wpilib-mvn-development-2027-local" } + ], "path": { "$nmatch":"*edu/wpi/first/thirdparty*" }, "$or":[ { diff --git a/.github/workflows/cmake-android.yml b/.github/workflows/cmake-android.yml index 2f3a8157a9..e35d20fc68 100644 --- a/.github/workflows/cmake-android.yml +++ b/.github/workflows/cmake-android.yml @@ -46,6 +46,12 @@ jobs: - name: configure run: cmake --preset with-sccache -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_WPILIB=OFF -DWITH_GUI=OFF -DWITH_CSCORE=OFF -DWITH_TESTS=OFF -DWITH_SIMULATION_MODULES=OFF -DWITH_JAVA=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_TOOLCHAIN_FILE=${{ steps.setup-ndk.outputs.ndk-path }}/build/cmake/android.toolchain.cmake -DANDROID_ABI="${{ matrix.abi }}" -DANDROID_PLATFORM=android-24 + env: + SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} - name: build run: cmake --build build-cmake --parallel $(nproc) + env: + SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} diff --git a/.github/workflows/sentinel-build.yml b/.github/workflows/sentinel-build.yml index 22122a5d74..73a682f867 100644 --- a/.github/workflows/sentinel-build.yml +++ b/.github/workflows/sentinel-build.yml @@ -102,6 +102,12 @@ jobs: architecture: aarch64 task: "build" outputs: "build/allOutputs" + - os: windows-2022 + artifact-name: Win64FFI + architecture: x64 + task: ":ntcoreffi:build" + build-options: "-Pntcoreffibuild -Pbuildwinarm64" + outputs: "ntcoreffi/build/outputs" name: "Build - ${{ matrix.artifact-name }}" runs-on: ${{ matrix.os }} needs: [validation] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f04042415e..9ca8d049d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,39 +51,6 @@ Have an idea to make WPILib better? Here's some steps to go from idea to impleme WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome. -### Math documentation - -When writing math expressions in documentation, use https://www.unicodeit.net/ to convert LaTeX to a Unicode equivalent that's easier to read. Not all expressions will translate (e.g., superscripts of superscripts) so focus on making it readable by someone who isn't familiar with LaTeX. If content on multiple lines needs to be aligned in Doxygen/Javadoc comments (e.g., integration/summation limits, matrices packed with square brackets and superscripts for them), put them in @verbatim/@endverbatim blocks in Doxygen or `
` tags in Javadoc so they render with monospace font.
-
-The LaTeX to Unicode conversions can also be done locally via the unicodeit Python package. To install it, execute:
-```bash
-pip install --user unicodeit
-```
-
-Here's example usage:
-```bash
-$ python -m unicodeit.cli 'x_{k+1} = Ax_k + Bu_k'
-xₖ₊₁ = Axₖ + Buₖ
-```
-
-On Linux, this process can be streamlined further by adding the following Bash function to your .bashrc (requires `wl-clipboard` on Wayland or `xclip` on X11):
-```bash
-# Converts LaTeX to Unicode, prints the result, and copies it to the clipboard
-uc() {
- if [ $WAYLAND_DISPLAY ]; then
- python -m unicodeit.cli $@ | tee >(wl-copy -n)
- else
- python -m unicodeit.cli $@ | tee >(xclip -sel)
- fi
-}
-```
-
-Here's example usage:
-```bash
-$ uc 'x_{k+1} = Ax_k + Bu_k'
-xₖ₊₁ = Axₖ + Buₖ
-```
-
## Submitting Changes
### Pull Request Format
diff --git a/WORKSPACE b/WORKSPACE
index 36b1be850f..bfadc7f104 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -178,12 +178,12 @@ load("@rules_jvm_external//:defs.bzl", "maven_install")
load("@rules_jvm_external//:specs.bzl", "maven")
maven_artifacts = [
- "org.ejml:ejml-simple:0.43.1",
- "com.fasterxml.jackson.core:jackson-annotations:2.15.2",
- "com.fasterxml.jackson.core:jackson-core:2.15.2",
- "com.fasterxml.jackson.core:jackson-databind:2.15.2",
- "us.hebi.quickbuf:quickbuf-runtime:1.3.3",
- "com.google.code.gson:gson:2.10.1",
+ "org.ejml:ejml-simple:0.44.0",
+ "com.fasterxml.jackson.core:jackson-annotations:2.19.2",
+ "com.fasterxml.jackson.core:jackson-core:2.19.2",
+ "com.fasterxml.jackson.core:jackson-databind:2.19.2",
+ "us.hebi.quickbuf:quickbuf-runtime:1.4",
+ "com.google.code.gson:gson:2.13.1",
"edu.wpi.first.thirdparty.frc2025.opencv:opencv-java:4.10.0-3",
maven.artifact(
"org.junit.jupiter",
diff --git a/docs/build.gradle b/docs/build.gradle
index 1e634ca649..184df1c6dd 100644
--- a/docs/build.gradle
+++ b/docs/build.gradle
@@ -165,6 +165,7 @@ doxygen.sourceSets.main {
tasks.register("zipCppDocs", Zip) {
archiveBaseName = zipBaseNameCpp
+ archiveVersion = ""
destinationDirectory = outputsFolder
dependsOn doxygenDox
from ("$buildDir/docs/doxygen/html")
@@ -203,6 +204,7 @@ task generateJavaDocs(type: Javadoc) {
"-edu.wpi.first.math.system.plant.proto," +
"-edu.wpi.first.math.system.plant.struct," +
"-edu.wpi.first.math.trajectory.proto," +
+ "-edu.wpi.first.math.trajectory.struct," +
// The .measure package contains generated source files for which automatic javadoc
// generation is very difficult to do meaningfully.
"-edu.wpi.first.units.measure", true)
@@ -243,6 +245,7 @@ task generateJavaDocs(type: Javadoc) {
tasks.register("zipJavaDocs", Zip) {
archiveBaseName = zipBaseNameJava
+ archiveVersion = ""
destinationDirectory = outputsFolder
dependsOn generateJavaDocs
from ("$buildDir/docs/javadoc")
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ArrayHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ArrayHandler.java
index 8c31197e8d..37b9c421fd 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ArrayHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ArrayHandler.java
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
@@ -52,7 +53,7 @@ public class ArrayHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
+ public String logInvocation(Element element, TypeElement loggedClass) {
var dataType = dataType(element);
// known to be an array type (assuming isLoggable is checked first); this is a safe cast
@@ -63,13 +64,17 @@ public class ArrayHandler extends ElementHandler {
return "backend.log(\""
+ loggedName(element)
+ "\", "
- + elementAccess(element)
+ + elementAccess(element, loggedClass)
+ ", "
+ m_structHandler.structAccess(componentType)
+ ")";
} else {
// Primitive or string array
- return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
+ return "backend.log(\""
+ + loggedName(element)
+ + "\", "
+ + elementAccess(element, loggedClass)
+ + ")";
}
}
}
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/CollectionHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/CollectionHandler.java
index 3fe4abb3aa..d864f093d6 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/CollectionHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/CollectionHandler.java
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
@@ -38,7 +39,7 @@ public class CollectionHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
+ public String logInvocation(Element element, TypeElement loggedClass) {
var dataType = dataType(element);
var componentType = ((DeclaredType) dataType).getTypeArguments().get(0);
@@ -46,12 +47,16 @@ public class CollectionHandler extends ElementHandler {
return "backend.log(\""
+ loggedName(element)
+ "\", "
- + elementAccess(element)
+ + elementAccess(element, loggedClass)
+ ", "
+ m_structHandler.structAccess(componentType)
+ ")";
} else {
- return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
+ return "backend.log(\""
+ + loggedName(element)
+ + "\", "
+ + elementAccess(element, loggedClass)
+ + ")";
}
}
}
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ConfiguredLoggerHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ConfiguredLoggerHandler.java
index f7e6744edf..086e7fbe40 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ConfiguredLoggerHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ConfiguredLoggerHandler.java
@@ -7,6 +7,7 @@ package edu.wpi.first.epilogue.processor;
import java.util.Map;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
@@ -27,7 +28,7 @@ public class ConfiguredLoggerHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
+ public String logInvocation(Element element, TypeElement loggedClass) {
var dataType = dataType(element);
var loggerType =
m_customLoggers.entrySet().stream()
@@ -44,7 +45,7 @@ public class ConfiguredLoggerHandler extends ElementHandler {
+ ".tryUpdate(backend.getNested(\""
+ loggedName(element)
+ "\"), "
- + elementAccess(element)
+ + elementAccess(element, loggedClass)
+ ", Epilogue.getConfig().errorHandler)";
}
}
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ElementHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ElementHandler.java
index d12acb7ecc..61e3d49974 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ElementHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ElementHandler.java
@@ -117,9 +117,9 @@ public abstract class ElementHandler {
* @param element the element to generate the access for
* @return the generated access snippet
*/
- public String elementAccess(Element element) {
+ public String elementAccess(Element element, TypeElement loggedClass) {
if (element instanceof VariableElement field) {
- return fieldAccess(field);
+ return fieldAccess(field, loggedClass);
} else if (element instanceof ExecutableElement method) {
return methodAccess(method);
} else {
@@ -127,8 +127,20 @@ public abstract class ElementHandler {
}
}
- private static String fieldAccess(VariableElement field) {
- if (!field.getModifiers().contains(Modifier.PUBLIC)) {
+ private static String fieldAccess(VariableElement field, TypeElement loggedClass) {
+ var mods = field.getModifiers();
+
+ // To be directly accessible, the field needs to be:
+ // - public; or
+ // - protected or package-private, and declared by a superclass in the same package
+ // However, we can't cleanly access package information, so we'll always emit a VarHandle
+ // for any field declared in a superclass unless it's public and we know we can read it.
+ boolean isVarHandle =
+ field.getEnclosingElement().equals(loggedClass)
+ ? mods.contains(Modifier.PRIVATE)
+ : !mods.contains(Modifier.PUBLIC);
+
+ if (isVarHandle) {
// ((com.example.Foo) $fooField.get(object))
// Extra parentheses so cast evaluates before appended methods
// (e.g. when appending .getAsDouble())
@@ -136,7 +148,7 @@ public abstract class ElementHandler {
if (type.getKind() == TypeKind.TYPEVAR) {
type = ((TypeVariable) type).getUpperBound();
}
- return "((" + type.toString() + ") $" + field.getSimpleName() + ".get(object))";
+ return "((" + type.toString() + ") " + LoggerGenerator.varHandleName(field) + ".get(object))";
} else {
// object.fooField
return "object." + field.getSimpleName();
@@ -171,5 +183,5 @@ public abstract class ElementHandler {
* @param element the field or method element to generate the logger call for
* @return the generated log invocation
*/
- public abstract String logInvocation(Element element);
+ public abstract String logInvocation(Element element, TypeElement loggedClass);
}
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/EnumHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/EnumHandler.java
index 78b14b4636..f8c05670b0 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/EnumHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/EnumHandler.java
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
public class EnumHandler extends ElementHandler {
@@ -27,7 +28,11 @@ public class EnumHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
- return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
+ public String logInvocation(Element element, TypeElement loggedClass) {
+ return "backend.log(\""
+ + loggedName(element)
+ + "\", "
+ + elementAccess(element, loggedClass)
+ + ")";
}
}
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggableHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggableHandler.java
index ba14022a2c..001716a637 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggableHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggableHandler.java
@@ -39,7 +39,7 @@ public class LoggableHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
+ public String logInvocation(Element element, TypeElement loggedClass) {
TypeMirror dataType = dataType(element);
var declaredType =
m_processingEnv
@@ -61,7 +61,7 @@ public class LoggableHandler extends ElementHandler {
// If there are no known loggable subtypes, return just the single logger call
if (size == 1) {
- return generateLoggerCall(element, declaredType, elementAccess(element));
+ return generateLoggerCall(element, declaredType, elementAccess(element, loggedClass));
}
// Otherwise, generate an if-else chain to compare the element with its known loggable subtypes
@@ -73,7 +73,7 @@ public class LoggableHandler extends ElementHandler {
StringBuilder builder = new StringBuilder();
// Cache the value in a variable so it's only read once
- builder.append("var %s = %s;\n".formatted(varName, elementAccess(element)));
+ builder.append("var %s = %s;\n".formatted(varName, elementAccess(element, loggedClass)));
for (int i = 0; i < size; i++) {
TypeElement type = loggableSubtypes.get(i);
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java
index 7fbb82a931..20b1e1cebc 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java
@@ -18,9 +18,11 @@ import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Deque;
import java.util.EnumMap;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -185,7 +187,21 @@ public class LoggerGenerator {
var loggerFile = m_processingEnv.getFiler().createSourceFile(loggerClassName);
var varHandleFields =
- loggableFields.stream().filter(e -> !e.getModifiers().contains(Modifier.PUBLIC)).toList();
+ loggableFields.stream()
+ .filter(
+ e -> {
+ if (e.getEnclosingElement().equals(clazz)) {
+ // The generated logger is in the same package as the logged class, so the
+ // only fields it can't read are private ones.
+ return e.getModifiers().contains(Modifier.PRIVATE);
+ } else {
+ // Logging from a superclass. Can only read public fields, unless the superclass
+ // is in the same package, in which case protected and package-private fields
+ // are also readable.
+ return !e.getModifiers().contains(Modifier.PUBLIC);
+ }
+ })
+ .toList();
boolean requiresVarHandles = !varHandleFields.isEmpty();
try (var out = new PrintWriter(loggerFile.openWriter())) {
@@ -214,41 +230,67 @@ public class LoggerGenerator {
+ "> {");
if (requiresVarHandles) {
- for (var varHandleField : varHandleFields) {
+ for (var privateField : varHandleFields) {
// This field needs a VarHandle to access.
// Cache it in the class to avoid lookups
- out.println(" private static final VarHandle $" + varHandleField.getSimpleName() + ";");
+ out.printf(
+ " // Accesses private or superclass field %s.%s%n",
+ privateField.getEnclosingElement(), privateField.getSimpleName());
+ out.printf(" private static final VarHandle %s;%n", varHandleName(privateField));
}
out.println();
+ }
- var classReference = simpleClassName + ".class";
-
+ // Static initializer block to load VarHandles and reflection fields
+ if (requiresVarHandles) {
out.println(" static {");
- out.println(" try {");
- out.println(
- " var lookup = MethodHandles.privateLookupIn("
- + classReference
- + ", MethodHandles.lookup());");
- for (var varHandleField : varHandleFields) {
- var fieldName = varHandleField.getSimpleName();
- out.println(
- " $"
- + fieldName
- + " = lookup.findVarHandle("
- + classReference
- + ", \""
- + fieldName
- + "\", "
- + m_processingEnv.getTypeUtils().erasure(varHandleField.asType())
- + ".class);");
- }
+ out.println(" try {");
+
+ out.println(" var rootLookup = MethodHandles.lookup();");
+
+ // Group private fields by class, then generate a private lookup for each class
+ // and a VarHandle for each field using that lookup. Sorting and then collecting into a
+ // LinkedHashMap gives deterministic output ordering (mostly for tests, which check exact
+ // file contents, but also results in less churn when regenerating files for users who like
+ // to read the generated logger classes).
+ //
+ // This lets us read private fields from superclasses.
+ Map> privateFieldsByClass =
+ varHandleFields.stream()
+ .sorted(Comparator.comparing(e -> e.getSimpleName().toString()))
+ .collect(
+ Collectors.groupingBy(
+ VariableElement::getEnclosingElement,
+ LinkedHashMap::new,
+ Collectors.toList()));
+
+ privateFieldsByClass.forEach(
+ (enclosingClass, fields) -> {
+ String className = enclosingClass.toString();
+ String lookupName = "lookup$$" + className.replace(".", "_");
+ out.printf(
+ " var %s = MethodHandles.privateLookupIn(%s.class, rootLookup);%n",
+ lookupName, className);
+
+ for (var field : fields) {
+ var fieldname = field.getSimpleName();
+ out.printf(
+ " %s = %s.findVarHandle(%s.class, \"%s\", %s.class);%n",
+ varHandleName(field),
+ lookupName,
+ className,
+ fieldname,
+ m_processingEnv.getTypeUtils().erasure(field.asType()));
+ }
+ });
out.println(" } catch (ReflectiveOperationException e) {");
out.println(
" throw new RuntimeException("
+ "\"[EPILOGUE] Could not load private fields for logging!\", e);");
out.println(" }");
+
out.println(" }");
out.println();
}
@@ -300,7 +342,7 @@ public class LoggerGenerator {
// to be logged. For example, the sendable handler consumes all sendable types
// but does not log commands or subsystems, to prevent excessive warnings about
// unloggable commands.
- var logInvocation = h.logInvocation(loggableElement);
+ var logInvocation = h.logInvocation(loggableElement, clazz);
if (logInvocation != null) {
out.println(logInvocation.indent(6).stripTrailing() + ";");
}
@@ -315,6 +357,18 @@ public class LoggerGenerator {
}
}
+ /**
+ * Generates the name of a VarHandle for access to the given field. The VarHandle variable's name
+ * is guaranteed to be unique.
+ *
+ * @param field The field to generate a VarHandle for
+ * @return The name of the generated VarHandle variable
+ */
+ public static String varHandleName(VariableElement field) {
+ return "$%s_%s"
+ .formatted(field.getEnclosingElement().toString().replace(".", "_"), field.getSimpleName());
+ }
+
private void collectLoggables(
TypeElement clazz, List fields, List methods) {
var config = clazz.getAnnotation(Logged.class);
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/MeasureHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/MeasureHandler.java
index 8933306d65..bff3e8533a 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/MeasureHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/MeasureHandler.java
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
public class MeasureHandler extends ElementHandler {
@@ -30,8 +31,12 @@ public class MeasureHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
+ public String logInvocation(Element element, TypeElement loggedClass) {
// EpilogueBackend has builtin support for logging measures
- return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
+ return "backend.log(\""
+ + loggedName(element)
+ + "\", "
+ + elementAccess(element, loggedClass)
+ + ")";
}
}
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/PrimitiveHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/PrimitiveHandler.java
index 5563bd9b31..8da8ece405 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/PrimitiveHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/PrimitiveHandler.java
@@ -16,6 +16,7 @@ import static javax.lang.model.type.TypeKind.SHORT;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
public class PrimitiveHandler extends ElementHandler {
@@ -35,7 +36,11 @@ public class PrimitiveHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
- return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
+ public String logInvocation(Element element, TypeElement loggedClass) {
+ return "backend.log(\""
+ + loggedName(element)
+ + "\", "
+ + elementAccess(element, loggedClass)
+ + ")";
}
}
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SendableHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SendableHandler.java
index d25ce09d31..635b565940 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SendableHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SendableHandler.java
@@ -44,7 +44,7 @@ public class SendableHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
+ public String logInvocation(Element element, TypeElement loggedClass) {
var dataType = dataType(element);
// Do not log commands or subsystems via their sendable implementations
@@ -66,7 +66,7 @@ public class SendableHandler extends ElementHandler {
return "logSendable(backend.getNested(\""
+ loggedName(element)
+ "\"), "
- + elementAccess(element)
+ + elementAccess(element, loggedClass)
+ ")";
}
}
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/StructHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/StructHandler.java
index ddd753a818..1c658818e6 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/StructHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/StructHandler.java
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
@@ -38,11 +39,11 @@ public class StructHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
+ public String logInvocation(Element element, TypeElement loggedClass) {
return "backend.log(\""
+ loggedName(element)
+ "\", "
- + elementAccess(element)
+ + elementAccess(element, loggedClass)
+ ", "
+ structAccess(dataType(element))
+ ")";
diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SupplierHandler.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SupplierHandler.java
index 173e371c05..49f2e06935 100644
--- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SupplierHandler.java
+++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SupplierHandler.java
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
public class SupplierHandler extends ElementHandler {
@@ -42,15 +43,19 @@ public class SupplierHandler extends ElementHandler {
}
@Override
- public String logInvocation(Element element) {
- return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
+ public String logInvocation(Element element, TypeElement loggedClass) {
+ return "backend.log(\""
+ + loggedName(element)
+ + "\", "
+ + elementAccess(element, loggedClass)
+ + ")";
}
@Override
- public String elementAccess(Element element) {
+ public String elementAccess(Element element, TypeElement loggedClass) {
var typeUtils = m_processingEnv.getTypeUtils();
var dataType = dataType(element);
- String base = super.elementAccess(element);
+ String base = super.elementAccess(element, loggedClass);
if (typeUtils.isAssignable(dataType, m_booleanSupplier)) {
return base + ".getAsBoolean()";
diff --git a/epilogue-processor/src/test/java/edu/wpi/first/epilogue/processor/AnnotationProcessorTest.java b/epilogue-processor/src/test/java/edu/wpi/first/epilogue/processor/AnnotationProcessorTest.java
index bc6189bf0b..65a7a2e736 100644
--- a/epilogue-processor/src/test/java/edu/wpi/first/epilogue/processor/AnnotationProcessorTest.java
+++ b/epilogue-processor/src/test/java/edu/wpi/first/epilogue/processor/AnnotationProcessorTest.java
@@ -42,21 +42,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", double.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -64,7 +51,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
+ backend.log("x", object.x);
}
}
}
@@ -93,23 +80,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $y;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", double.class);
- $y = lookup.findVarHandle(Example.class, "y", int.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -117,8 +89,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
- backend.log("y", ((int) $y.get(object)));
+ backend.log("x", object.x);
+ backend.log("y", object.y);
}
}
}
@@ -210,23 +182,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $y;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", double.class);
- $y = lookup.findVarHandle(Example.class, "y", double.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -234,8 +191,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
- backend.log("y", ((double) $y.get(object)));
+ backend.log("x", object.x);
+ backend.log("y", object.y);
}
}
}
@@ -268,12 +225,14 @@ class AnnotationProcessorTest {
import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
+ // Accesses private or superclass field edu.wpi.first.epilogue.Example.x
+ private static final VarHandle $edu_wpi_first_epilogue_Example_x;
static {
try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", double.class);
+ var rootLookup = MethodHandles.lookup();
+ var lookup$$edu_wpi_first_epilogue_Example = MethodHandles.privateLookupIn(edu.wpi.first.epilogue.Example.class, rootLookup);
+ $edu_wpi_first_epilogue_Example_x = lookup$$edu_wpi_first_epilogue_Example.findVarHandle(edu.wpi.first.epilogue.Example.class, "x", double.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
}
@@ -286,7 +245,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
+ backend.log("x", ((double) $edu_wpi_first_epilogue_Example_x.get(object)));
}
}
}
@@ -321,12 +280,14 @@ class AnnotationProcessorTest {
import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
+ // Accesses private or superclass field edu.wpi.first.epilogue.Example.x
+ private static final VarHandle $edu_wpi_first_epilogue_Example_x;
static {
try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", java.util.function.DoubleSupplier.class);
+ var rootLookup = MethodHandles.lookup();
+ var lookup$$edu_wpi_first_epilogue_Example = MethodHandles.privateLookupIn(edu.wpi.first.epilogue.Example.class, rootLookup);
+ $edu_wpi_first_epilogue_Example_x = lookup$$edu_wpi_first_epilogue_Example.findVarHandle(edu.wpi.first.epilogue.Example.class, "x", java.util.function.DoubleSupplier.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
}
@@ -339,7 +300,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((java.util.function.DoubleSupplier) $x.get(object)).getAsDouble());
+ backend.log("x", ((java.util.function.DoubleSupplier) $edu_wpi_first_epilogue_Example_x.get(object)).getAsDouble());
}
}
}
@@ -372,12 +333,14 @@ class AnnotationProcessorTest {
import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $chooser;
+ // Accesses private or superclass field edu.wpi.first.epilogue.Example.chooser
+ private static final VarHandle $edu_wpi_first_epilogue_Example_chooser;
static {
try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $chooser = lookup.findVarHandle(Example.class, "chooser", edu.wpi.first.wpilibj.smartdashboard.SendableChooser.class);
+ var rootLookup = MethodHandles.lookup();
+ var lookup$$edu_wpi_first_epilogue_Example = MethodHandles.privateLookupIn(edu.wpi.first.epilogue.Example.class, rootLookup);
+ $edu_wpi_first_epilogue_Example_chooser = lookup$$edu_wpi_first_epilogue_Example.findVarHandle(edu.wpi.first.epilogue.Example.class, "chooser", edu.wpi.first.wpilibj.smartdashboard.SendableChooser.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
}
@@ -390,7 +353,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- logSendable(backend.getNested("chooser"), ((edu.wpi.first.wpilibj.smartdashboard.SendableChooser) $chooser.get(object)));
+ logSendable(backend.getNested("chooser"), ((edu.wpi.first.wpilibj.smartdashboard.SendableChooser) $edu_wpi_first_epilogue_Example_chooser.get(object)));
}
}
}
@@ -421,25 +384,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $low;
- private static final VarHandle $medium;
- private static final VarHandle $high;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $low = lookup.findVarHandle(Example.class, "low", double.class);
- $medium = lookup.findVarHandle(Example.class, "medium", int.class);
- $high = lookup.findVarHandle(Example.class, "high", long.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -447,13 +393,13 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("low", ((double) $low.get(object)));
+ backend.log("low", object.low);
}
if (Epilogue.shouldLog(Logged.Importance.INFO)) {
- backend.log("medium", ((int) $medium.get(object)));
+ backend.log("medium", object.medium);
}
if (Epilogue.shouldLog(Logged.Importance.CRITICAL)) {
- backend.log("high", ((long) $high.get(object)));
+ backend.log("high", object.high);
}
}
}
@@ -486,21 +432,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $enumValue;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $enumValue = lookup.findVarHandle(Example.class, "enumValue", edu.wpi.first.epilogue.Example.E.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -508,7 +441,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("enumValue", ((edu.wpi.first.epilogue.Example.E) $enumValue.get(object)));
+ backend.log("enumValue", object.enumValue);
}
}
}
@@ -535,6 +468,52 @@ class AnnotationProcessorTest {
}
""";
+ String expectedGeneratedSource =
+ """
+ package edu.wpi.first.epilogue;
+
+ import edu.wpi.first.epilogue.Logged;
+ import edu.wpi.first.epilogue.Epilogue;
+ import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
+ import edu.wpi.first.epilogue.logging.EpilogueBackend;
+
+ public class ExampleLogger extends ClassSpecificLogger {
+ public ExampleLogger() {
+ super(Example.class);
+ }
+
+ @Override
+ public void update(EpilogueBackend backend, Example object) {
+ if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
+ backend.log("y", object.y);
+ }
+ }
+ }
+ """;
+
+ assertLoggerGenerates(source, expectedGeneratedSource);
+ }
+
+ @Test
+ void simpleSuperclass() {
+ String source =
+ """
+ package edu.wpi.first.epilogue;
+
+ @Logged
+ class BaseExample {
+ public double a;
+ protected double b;
+ private double c;
+ double d;
+ }
+
+ @Logged
+ class Example extends BaseExample {
+ double e;
+ }
+ """;
+
String expectedGeneratedSource =
"""
package edu.wpi.first.epilogue;
@@ -547,12 +526,20 @@ class AnnotationProcessorTest {
import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $y;
+ // Accesses private or superclass field edu.wpi.first.epilogue.BaseExample.b
+ private static final VarHandle $edu_wpi_first_epilogue_BaseExample_b;
+ // Accesses private or superclass field edu.wpi.first.epilogue.BaseExample.c
+ private static final VarHandle $edu_wpi_first_epilogue_BaseExample_c;
+ // Accesses private or superclass field edu.wpi.first.epilogue.BaseExample.d
+ private static final VarHandle $edu_wpi_first_epilogue_BaseExample_d;
static {
try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $y = lookup.findVarHandle(Example.class, "y", double.class);
+ var rootLookup = MethodHandles.lookup();
+ var lookup$$edu_wpi_first_epilogue_BaseExample = MethodHandles.privateLookupIn(edu.wpi.first.epilogue.BaseExample.class, rootLookup);
+ $edu_wpi_first_epilogue_BaseExample_b = lookup$$edu_wpi_first_epilogue_BaseExample.findVarHandle(edu.wpi.first.epilogue.BaseExample.class, "b", double.class);
+ $edu_wpi_first_epilogue_BaseExample_c = lookup$$edu_wpi_first_epilogue_BaseExample.findVarHandle(edu.wpi.first.epilogue.BaseExample.class, "c", double.class);
+ $edu_wpi_first_epilogue_BaseExample_d = lookup$$edu_wpi_first_epilogue_BaseExample.findVarHandle(edu.wpi.first.epilogue.BaseExample.class, "d", double.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
}
@@ -565,7 +552,11 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("y", ((double) $y.get(object)));
+ backend.log("e", object.e);
+ backend.log("a", object.a);
+ backend.log("b", ((double) $edu_wpi_first_epilogue_BaseExample_b.get(object)));
+ backend.log("c", ((double) $edu_wpi_first_epilogue_BaseExample_c.get(object)));
+ backend.log("d", ((double) $edu_wpi_first_epilogue_BaseExample_d.get(object)));
}
}
}
@@ -575,7 +566,7 @@ class AnnotationProcessorTest {
}
@Test
- void superclass() {
+ void complexSuperclass() {
String source =
"""
package edu.wpi.first.epilogue;
@@ -600,6 +591,7 @@ class AnnotationProcessorTest {
@Logged
class Example extends BaseExample {
double h;
+ private double i;
}
""";
@@ -615,18 +607,24 @@ class AnnotationProcessorTest {
import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $h;
- private static final VarHandle $d;
- private static final VarHandle $f;
- private static final VarHandle $g;
+ // Accesses private or superclass field edu.wpi.first.epilogue.Example.i
+ private static final VarHandle $edu_wpi_first_epilogue_Example_i;
+ // Accesses private or superclass field edu.wpi.first.epilogue.BaseExample.d
+ private static final VarHandle $edu_wpi_first_epilogue_BaseExample_d;
+ // Accesses private or superclass field edu.wpi.first.epilogue.BaseExample.f
+ private static final VarHandle $edu_wpi_first_epilogue_BaseExample_f;
+ // Accesses private or superclass field edu.wpi.first.epilogue.BaseExample.g
+ private static final VarHandle $edu_wpi_first_epilogue_BaseExample_g;
static {
try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $h = lookup.findVarHandle(Example.class, "h", double.class);
- $d = lookup.findVarHandle(Example.class, "d", double.class);
- $f = lookup.findVarHandle(Example.class, "f", double.class);
- $g = lookup.findVarHandle(Example.class, "g", double.class);
+ var rootLookup = MethodHandles.lookup();
+ var lookup$$edu_wpi_first_epilogue_BaseExample = MethodHandles.privateLookupIn(edu.wpi.first.epilogue.BaseExample.class, rootLookup);
+ $edu_wpi_first_epilogue_BaseExample_d = lookup$$edu_wpi_first_epilogue_BaseExample.findVarHandle(edu.wpi.first.epilogue.BaseExample.class, "d", double.class);
+ $edu_wpi_first_epilogue_BaseExample_f = lookup$$edu_wpi_first_epilogue_BaseExample.findVarHandle(edu.wpi.first.epilogue.BaseExample.class, "f", double.class);
+ $edu_wpi_first_epilogue_BaseExample_g = lookup$$edu_wpi_first_epilogue_BaseExample.findVarHandle(edu.wpi.first.epilogue.BaseExample.class, "g", double.class);
+ var lookup$$edu_wpi_first_epilogue_Example = MethodHandles.privateLookupIn(edu.wpi.first.epilogue.Example.class, rootLookup);
+ $edu_wpi_first_epilogue_Example_i = lookup$$edu_wpi_first_epilogue_Example.findVarHandle(edu.wpi.first.epilogue.Example.class, "i", double.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
}
@@ -639,11 +637,12 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("h", ((double) $h.get(object)));
- backend.log("d", ((double) $d.get(object)));
+ backend.log("h", object.h);
+ backend.log("i", ((double) $edu_wpi_first_epilogue_Example_i.get(object)));
+ backend.log("d", ((double) $edu_wpi_first_epilogue_BaseExample_d.get(object)));
backend.log("e", object.e);
- backend.log("f", ((double) $f.get(object)));
- backend.log("g", ((double) $g.get(object)));
+ backend.log("f", ((double) $edu_wpi_first_epilogue_BaseExample_f.get(object)));
+ backend.log("g", ((double) $edu_wpi_first_epilogue_BaseExample_g.get(object)));
backend.log("a", object.a);
backend.log("getValue", object.getValue());
backend.log("getB", object.getB());
@@ -681,23 +680,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $arr1;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", byte.class);
- $arr1 = lookup.findVarHandle(Example.class, "arr1", byte[].class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -705,8 +689,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((byte) $x.get(object)));
- backend.log("arr1", ((byte[]) $arr1.get(object)));
+ backend.log("x", object.x);
+ backend.log("arr1", object.arr1);
backend.log("getX", object.getX());
backend.log("getArr1", object.getArr1());
}
@@ -743,21 +727,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", char.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -765,7 +736,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((char) $x.get(object)));
+ backend.log("x", object.x);
backend.log("getX", object.getX());
}
}
@@ -801,21 +772,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", short.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -823,7 +781,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((short) $x.get(object)));
+ backend.log("x", object.x);
backend.log("getX", object.getX());
}
}
@@ -859,23 +817,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $arr1;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", int.class);
- $arr1 = lookup.findVarHandle(Example.class, "arr1", int[].class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -883,8 +826,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((int) $x.get(object)));
- backend.log("arr1", ((int[]) $arr1.get(object)));
+ backend.log("x", object.x);
+ backend.log("arr1", object.arr1);
backend.log("getX", object.getX());
backend.log("getArr1", object.getArr1());
}
@@ -921,23 +864,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $arr1;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", long.class);
- $arr1 = lookup.findVarHandle(Example.class, "arr1", long[].class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -945,8 +873,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((long) $x.get(object)));
- backend.log("arr1", ((long[]) $arr1.get(object)));
+ backend.log("x", object.x);
+ backend.log("arr1", object.arr1);
backend.log("getX", object.getX());
backend.log("getArr1", object.getArr1());
}
@@ -983,23 +911,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $arr1;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", float.class);
- $arr1 = lookup.findVarHandle(Example.class, "arr1", float[].class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1007,8 +920,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((float) $x.get(object)));
- backend.log("arr1", ((float[]) $arr1.get(object)));
+ backend.log("x", object.x);
+ backend.log("arr1", object.arr1);
backend.log("getX", object.getX());
backend.log("getArr1", object.getArr1());
}
@@ -1048,23 +961,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $arr1;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", double.class);
- $arr1 = lookup.findVarHandle(Example.class, "arr1", double[].class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1072,8 +970,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
- backend.log("arr1", ((double[]) $arr1.get(object)));
+ backend.log("x", object.x);
+ backend.log("arr1", object.arr1);
backend.log("getX", object.getX());
backend.log("getArr1", object.getArr1());
}
@@ -1112,23 +1010,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $arr1;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", boolean.class);
- $arr1 = lookup.findVarHandle(Example.class, "arr1", boolean[].class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1136,8 +1019,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((boolean) $x.get(object)));
- backend.log("arr1", ((boolean[]) $arr1.get(object)));
+ backend.log("x", object.x);
+ backend.log("arr1", object.arr1);
backend.log("getX", object.getX());
backend.log("getArr1", object.getArr1());
}
@@ -1177,25 +1060,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $arr1;
- private static final VarHandle $list;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", java.lang.String.class);
- $arr1 = lookup.findVarHandle(Example.class, "arr1", java.lang.String[].class);
- $list = lookup.findVarHandle(Example.class, "list", java.util.List.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1203,9 +1069,9 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((java.lang.String) $x.get(object)));
- backend.log("arr1", ((java.lang.String[]) $arr1.get(object)));
- backend.log("list", ((java.util.List) $list.get(object)));
+ backend.log("x", object.x);
+ backend.log("arr1", object.arr1);
+ backend.log("list", object.list);
backend.log("getX", object.getX());
backend.log("getArr1", object.getArr1());
}
@@ -1253,25 +1119,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
- private static final VarHandle $arr1;
- private static final VarHandle $list;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", edu.wpi.first.epilogue.Example.Structable.class);
- $arr1 = lookup.findVarHandle(Example.class, "arr1", edu.wpi.first.epilogue.Example.Structable[].class);
- $list = lookup.findVarHandle(Example.class, "list", java.util.List.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1279,9 +1128,9 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((edu.wpi.first.epilogue.Example.Structable) $x.get(object)), edu.wpi.first.epilogue.Example.Structable.struct);
- backend.log("arr1", ((edu.wpi.first.epilogue.Example.Structable[]) $arr1.get(object)), edu.wpi.first.epilogue.Example.Structable.struct);
- backend.log("list", ((java.util.List) $list.get(object)), edu.wpi.first.epilogue.Example.Structable.struct);
+ backend.log("x", object.x, edu.wpi.first.epilogue.Example.Structable.struct);
+ backend.log("arr1", object.arr1, edu.wpi.first.epilogue.Example.Structable.struct);
+ backend.log("list", object.list, edu.wpi.first.epilogue.Example.Structable.struct);
backend.log("getX", object.getX(), edu.wpi.first.epilogue.Example.Structable.struct);
backend.log("getArr1", object.getArr1(), edu.wpi.first.epilogue.Example.Structable.struct);
}
@@ -1321,27 +1170,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $list;
- private static final VarHandle $set;
- private static final VarHandle $queue;
- private static final VarHandle $stack;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $list = lookup.findVarHandle(Example.class, "list", java.util.List.class);
- $set = lookup.findVarHandle(Example.class, "set", java.util.Set.class);
- $queue = lookup.findVarHandle(Example.class, "queue", java.util.Queue.class);
- $stack = lookup.findVarHandle(Example.class, "stack", java.util.Stack.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1349,10 +1179,10 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("list", ((java.util.List) $list.get(object)));
- backend.log("set", ((java.util.Set) $set.get(object)));
- backend.log("queue", ((java.util.Queue) $queue.get(object)));
- backend.log("stack", ((java.util.Stack) $stack.get(object)));
+ backend.log("list", object.list);
+ backend.log("set", object.set);
+ backend.log("queue", object.queue);
+ backend.log("stack", object.stack);
}
}
}
@@ -1507,9 +1337,10 @@ class AnnotationProcessorTest {
@Logged
class Example {
- T value;
+ T valueA;
+ private T valueB;
- public S upcast() { return (S) value; }
+ public S upcast() { return (S) valueA; }
}
""";
@@ -1525,12 +1356,14 @@ class AnnotationProcessorTest {
import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $value;
+ // Accesses private or superclass field edu.wpi.first.epilogue.Example.valueB
+ private static final VarHandle $edu_wpi_first_epilogue_Example_valueB;
static {
try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $value = lookup.findVarHandle(Example.class, "value", java.lang.String.class);
+ var rootLookup = MethodHandles.lookup();
+ var lookup$$edu_wpi_first_epilogue_Example = MethodHandles.privateLookupIn(edu.wpi.first.epilogue.Example.class, rootLookup);
+ $edu_wpi_first_epilogue_Example_valueB = lookup$$edu_wpi_first_epilogue_Example.findVarHandle(edu.wpi.first.epilogue.Example.class, "valueB", java.lang.String.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
}
@@ -1543,7 +1376,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("value", ((java.lang.String) $value.get(object)));
+ backend.log("valueA", object.valueA);
+ backend.log("valueB", ((java.lang.String) $edu_wpi_first_epilogue_Example_valueB.get(object)));
backend.log("upcast", object.upcast());
}
}
@@ -1586,23 +1420,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $child;
- private static final VarHandle $io;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $child = lookup.findVarHandle(Example.class, "child", edu.wpi.first.epilogue.Child.class);
- $io = lookup.findVarHandle(Example.class, "io", edu.wpi.first.epilogue.IO.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1610,8 +1429,8 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- Epilogue.childLogger.tryUpdate(backend.getNested("child"), ((edu.wpi.first.epilogue.Child) $child.get(object)), Epilogue.getConfig().errorHandler);
- Epilogue.ioLogger.tryUpdate(backend.getNested("io"), ((edu.wpi.first.epilogue.IO) $io.get(object)), Epilogue.getConfig().errorHandler);
+ Epilogue.childLogger.tryUpdate(backend.getNested("child"), object.child, Epilogue.getConfig().errorHandler);
+ Epilogue.ioLogger.tryUpdate(backend.getNested("io"), object.io, Epilogue.getConfig().errorHandler);
}
}
}
@@ -1688,27 +1507,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $asInterface;
- private static final VarHandle $firstImpl;
- private static final VarHandle $secondImpl;
- private static final VarHandle $complex;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $asInterface = lookup.findVarHandle(Example.class, "asInterface", edu.wpi.first.epilogue.IFace.class);
- $firstImpl = lookup.findVarHandle(Example.class, "firstImpl", edu.wpi.first.epilogue.Impl1.class);
- $secondImpl = lookup.findVarHandle(Example.class, "secondImpl", edu.wpi.first.epilogue.Impl2.class);
- $complex = lookup.findVarHandle(Example.class, "complex", edu.wpi.first.epilogue.I.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1716,7 +1516,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- var $$asInterface = ((edu.wpi.first.epilogue.IFace) $asInterface.get(object));
+ var $$asInterface = object.asInterface;
if ($$asInterface instanceof edu.wpi.first.epilogue.Impl1 edu_wpi_first_epilogue_Impl1) {
Epilogue.impl1Logger.tryUpdate(backend.getNested("asInterface"), edu_wpi_first_epilogue_Impl1, Epilogue.getConfig().errorHandler);
} else if ($$asInterface instanceof edu.wpi.first.epilogue.Impl2 edu_wpi_first_epilogue_Impl2) {
@@ -1725,9 +1525,9 @@ class AnnotationProcessorTest {
// Base type edu.wpi.first.epilogue.IFace
Epilogue.iFaceLogger.tryUpdate(backend.getNested("asInterface"), $$asInterface, Epilogue.getConfig().errorHandler);
};
- Epilogue.impl1Logger.tryUpdate(backend.getNested("firstImpl"), ((edu.wpi.first.epilogue.Impl1) $firstImpl.get(object)), Epilogue.getConfig().errorHandler);
- Epilogue.impl2Logger.tryUpdate(backend.getNested("secondImpl"), ((edu.wpi.first.epilogue.Impl2) $secondImpl.get(object)), Epilogue.getConfig().errorHandler);
- var $$complex = ((edu.wpi.first.epilogue.I) $complex.get(object));
+ Epilogue.impl1Logger.tryUpdate(backend.getNested("firstImpl"), object.firstImpl, Epilogue.getConfig().errorHandler);
+ Epilogue.impl2Logger.tryUpdate(backend.getNested("secondImpl"), object.secondImpl, Epilogue.getConfig().errorHandler);
+ var $$complex = object.complex;
if ($$complex instanceof edu.wpi.first.epilogue.ConcreteLogged edu_wpi_first_epilogue_ConcreteLogged) {
Epilogue.concreteLoggedLogger.tryUpdate(backend.getNested("complex"), edu_wpi_first_epilogue_ConcreteLogged, Epilogue.getConfig().errorHandler);
} else if ($$complex instanceof edu.wpi.first.epilogue.I4 edu_wpi_first_epilogue_I4) {
@@ -1770,21 +1570,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class Outer$ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Outer.Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Outer.Example.class, "x", double.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public Outer$ExampleLogger() {
super(Outer.Example.class);
}
@@ -1792,7 +1579,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Outer.Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
+ backend.log("x", object.x);
}
}
}
@@ -1829,21 +1616,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class A$B$C$D$ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(A.B.C.D.Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(A.B.C.D.Example.class, "x", double.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public A$B$C$D$ExampleLogger() {
super(A.B.C.D.Example.class);
}
@@ -1851,7 +1625,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, A.B.C.D.Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
+ backend.log("x", object.x);
}
}
}
@@ -1882,21 +1656,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class CustomExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Outer.Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Outer.Example.class, "x", double.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public CustomExampleLogger() {
super(Outer.Example.class);
}
@@ -1904,7 +1665,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Outer.Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
+ backend.log("x", object.x);
}
}
}
@@ -1948,21 +1709,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $theField;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $theField = lookup.findVarHandle(Example.class, "theField", edu.wpi.first.epilogue.I.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -1970,7 +1718,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- var $$theField = ((edu.wpi.first.epilogue.I) $theField.get(object));
+ var $$theField = object.theField;
if ($$theField instanceof edu.wpi.first.epilogue.Base edu_wpi_first_epilogue_Base) {
Epilogue.baseLogger.tryUpdate(backend.getNested("theField"), edu_wpi_first_epilogue_Base, Epilogue.getConfig().errorHandler);
} else if ($$theField instanceof edu.wpi.first.epilogue.ExtendingInterface edu_wpi_first_epilogue_ExtendingInterface) {
@@ -2017,12 +1765,14 @@ class AnnotationProcessorTest {
import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $theField;
+ // Accesses private or superclass field edu.wpi.first.epilogue.Example.theField
+ private static final VarHandle $edu_wpi_first_epilogue_Example_theField;
static {
try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $theField = lookup.findVarHandle(Example.class, "theField", edu.wpi.first.epilogue.I.class);
+ var rootLookup = MethodHandles.lookup();
+ var lookup$$edu_wpi_first_epilogue_Example = MethodHandles.privateLookupIn(edu.wpi.first.epilogue.Example.class, rootLookup);
+ $edu_wpi_first_epilogue_Example_theField = lookup$$edu_wpi_first_epilogue_Example.findVarHandle(edu.wpi.first.epilogue.Example.class, "theField", edu.wpi.first.epilogue.I.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
}
@@ -2035,7 +1785,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- var $$theField = ((edu.wpi.first.epilogue.I) $theField.get(object));
+ var $$theField = ((edu.wpi.first.epilogue.I) $edu_wpi_first_epilogue_Example_theField.get(object));
if ($$theField instanceof edu.wpi.first.epilogue.Base edu_wpi_first_epilogue_Base) {
Epilogue.baseLogger.tryUpdate(backend.getNested("theField"), edu_wpi_first_epilogue_Base, Epilogue.getConfig().errorHandler);
} else {
@@ -2073,21 +1823,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $i;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $i = lookup.findVarHandle(Example.class, "i", edu.wpi.first.epilogue.Implicit.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -2095,7 +1832,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- Epilogue.implicitLogger.tryUpdate(backend.getNested("i"), ((edu.wpi.first.epilogue.Implicit) $i.get(object)), Epilogue.getConfig().errorHandler);
+ Epilogue.implicitLogger.tryUpdate(backend.getNested("i"), object.i, Epilogue.getConfig().errorHandler);
}
}
}
@@ -2140,21 +1877,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $point;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $point = lookup.findVarHandle(Example.class, "point", edu.wpi.first.epilogue.Point.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -2162,7 +1886,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- Epilogue.customPointLogger.tryUpdate(backend.getNested("point"), ((edu.wpi.first.epilogue.Point) $point.get(object)), Epilogue.getConfig().errorHandler);
+ Epilogue.customPointLogger.tryUpdate(backend.getNested("point"), object.point, Epilogue.getConfig().errorHandler);
}
}
}
@@ -2208,21 +1932,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $vec;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $vec = lookup.findVarHandle(Example.class, "vec", edu.wpi.first.math.Vector.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -2230,7 +1941,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- Epilogue.vectorLogger.tryUpdate(backend.getNested("vec"), ((edu.wpi.first.math.Vector) $vec.get(object)), Epilogue.getConfig().errorHandler);
+ Epilogue.vectorLogger.tryUpdate(backend.getNested("vec"), object.vec, Epilogue.getConfig().errorHandler);
}
}
}
@@ -2417,21 +2128,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $x;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $x = lookup.findVarHandle(Example.class, "x", double.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -2439,7 +2137,7 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("x", ((double) $x.get(object)));
+ backend.log("x", object.x);
backend.log("withANoOpTransform", object.withANoOpTransform());
backend.log("withTemp", object.withTemp());
}
@@ -2482,27 +2180,8 @@ class AnnotationProcessorTest {
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
- import java.lang.invoke.MethodHandles;
- import java.lang.invoke.VarHandle;
public class ExampleLogger extends ClassSpecificLogger {
- private static final VarHandle $m_memberPrefix;
- private static final VarHandle $kConstantPrefix;
- private static final VarHandle $k_otherConstantPrefix;
- private static final VarHandle $s_otherPrefix;
-
- static {
- try {
- var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup());
- $m_memberPrefix = lookup.findVarHandle(Example.class, "m_memberPrefix", double.class);
- $kConstantPrefix = lookup.findVarHandle(Example.class, "kConstantPrefix", double.class);
- $k_otherConstantPrefix = lookup.findVarHandle(Example.class, "k_otherConstantPrefix", double.class);
- $s_otherPrefix = lookup.findVarHandle(Example.class, "s_otherPrefix", double.class);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e);
- }
- }
-
public ExampleLogger() {
super(Example.class);
}
@@ -2510,10 +2189,10 @@ class AnnotationProcessorTest {
@Override
public void update(EpilogueBackend backend, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
- backend.log("Member Prefix", ((double) $m_memberPrefix.get(object)));
- backend.log("Constant Prefix", ((double) $kConstantPrefix.get(object)));
- backend.log("Other Constant Prefix", ((double) $k_otherConstantPrefix.get(object)));
- backend.log("Other Prefix", ((double) $s_otherPrefix.get(object)));
+ backend.log("Member Prefix", object.m_memberPrefix);
+ backend.log("Constant Prefix", object.kConstantPrefix);
+ backend.log("Other Constant Prefix", object.k_otherConstantPrefix);
+ backend.log("Other Prefix", object.s_otherPrefix);
backend.log("The Getter Method", object.getTheGetterMethod());
backend.log("optedOut", object.optedOut());
}
diff --git a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/ClassSpecificLogger.java b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/ClassSpecificLogger.java
index 55f71a69d3..4f5b0bac3d 100644
--- a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/ClassSpecificLogger.java
+++ b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/ClassSpecificLogger.java
@@ -105,14 +105,13 @@ public abstract class ClassSpecificLogger {
return;
}
- var builder =
- m_sendables.computeIfAbsent(
- sendable,
- s -> {
- var b = new LogBackedSendableBuilder(backend);
- s.initSendable(b);
- return b;
- });
- builder.update();
+ if (m_sendables.containsKey(sendable)) {
+ m_sendables.get(sendable).update();
+ } else {
+ var builder = new LogBackedSendableBuilder(backend);
+ sendable.initSendable(builder);
+ m_sendables.put(sendable, builder);
+ builder.update();
+ }
}
}
diff --git a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/FileBackend.java b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/FileBackend.java
index b9fbbb95dc..c575cd5b35 100644
--- a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/FileBackend.java
+++ b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/FileBackend.java
@@ -23,7 +23,9 @@ import edu.wpi.first.datalog.StructArrayLogEntry;
import edu.wpi.first.datalog.StructLogEntry;
import edu.wpi.first.util.struct.Struct;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.function.BiFunction;
/** A backend implementation that saves information to a WPILib {@link DataLog} file on disk. */
@@ -31,6 +33,7 @@ public class FileBackend implements EpilogueBackend {
private final DataLog m_dataLog;
private final Map m_entries = new HashMap<>();
private final Map m_subLoggers = new HashMap<>();
+ private final Set> m_seenSchemas = new HashSet<>();
/**
* Creates a new file-based backend.
@@ -43,7 +46,13 @@ public class FileBackend implements EpilogueBackend {
@Override
public EpilogueBackend getNested(String path) {
- return m_subLoggers.computeIfAbsent(path, k -> new NestedBackend(k, this));
+ if (!m_subLoggers.containsKey(path)) {
+ var nested = new NestedBackend(path, this);
+ m_subLoggers.put(path, nested);
+ return nested;
+ }
+
+ return m_subLoggers.get(path);
}
@SuppressWarnings("unchecked")
@@ -131,14 +140,30 @@ public class FileBackend implements EpilogueBackend {
@Override
@SuppressWarnings("unchecked")
public void log(String identifier, S value, Struct struct) {
- m_dataLog.addSchema(struct);
- getEntry(identifier, (log, k) -> StructLogEntry.create(log, k, struct)).append(value);
+ // DataLog.addSchema has checks that we're able to skip, avoiding allocations
+ if (m_seenSchemas.add(struct)) {
+ m_dataLog.addSchema(struct);
+ }
+
+ if (!m_entries.containsKey(identifier)) {
+ m_entries.put(identifier, StructLogEntry.create(m_dataLog, identifier, struct));
+ }
+
+ ((StructLogEntry) m_entries.get(identifier)).append(value);
}
@Override
@SuppressWarnings("unchecked")
public void log(String identifier, S[] value, Struct struct) {
- m_dataLog.addSchema(struct);
- getEntry(identifier, (log, k) -> StructArrayLogEntry.create(log, k, struct)).append(value);
+ // DataLog.addSchema has checks that we're able to skip, avoiding allocations
+ if (m_seenSchemas.add(struct)) {
+ m_dataLog.addSchema(struct);
+ }
+
+ if (!m_entries.containsKey(identifier)) {
+ m_entries.put(identifier, StructArrayLogEntry.create(m_dataLog, identifier, struct));
+ }
+
+ ((StructArrayLogEntry) m_entries.get(identifier)).append(value);
}
}
diff --git a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/LazyBackend.java b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/LazyBackend.java
index adad963e07..bd925165e0 100644
--- a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/LazyBackend.java
+++ b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/LazyBackend.java
@@ -40,7 +40,13 @@ public class LazyBackend implements EpilogueBackend {
@Override
public EpilogueBackend getNested(String path) {
- return m_subLoggers.computeIfAbsent(path, k -> new NestedBackend(k, this));
+ if (!m_subLoggers.containsKey(path)) {
+ var nested = new NestedBackend(path, this);
+ m_subLoggers.put(path, nested);
+ return nested;
+ }
+
+ return m_subLoggers.get(path);
}
@Override
diff --git a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/MultiBackend.java b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/MultiBackend.java
index 408d65aad6..575fde05b2 100644
--- a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/MultiBackend.java
+++ b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/MultiBackend.java
@@ -24,7 +24,13 @@ public class MultiBackend implements EpilogueBackend {
@Override
public EpilogueBackend getNested(String path) {
- return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
+ if (!m_nestedBackends.containsKey(path)) {
+ var nested = new NestedBackend(path, this);
+ m_nestedBackends.put(path, nested);
+ return nested;
+ }
+
+ return m_nestedBackends.get(path);
}
@Override
diff --git a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/NTEpilogueBackend.java b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/NTEpilogueBackend.java
index cf381a2f02..e398172e77 100644
--- a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/NTEpilogueBackend.java
+++ b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/NTEpilogueBackend.java
@@ -21,7 +21,10 @@ import edu.wpi.first.networktables.StructArrayPublisher;
import edu.wpi.first.networktables.StructPublisher;
import edu.wpi.first.util.struct.Struct;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
/**
* A backend implementation that sends data over network tables. Be careful when using this, since
@@ -32,61 +35,81 @@ public class NTEpilogueBackend implements EpilogueBackend {
private final Map m_publishers = new HashMap<>();
private final Map m_nestedBackends = new HashMap<>();
+ private final Set> m_seenSchemas = new HashSet<>();
+ private final Function m_createIntPublisher;
+ private final Function m_createFloatPublisher;
+ private final Function m_createDoublePublisher;
+ private final Function m_createBooleanPublisher;
+ private final Function m_createRawPublisher;
+ private final Function m_createIntegerArrayPublisher;
+ private final Function m_createFloatArrayPublisher;
+ private final Function m_createDoubleArrayPublisher;
+ private final Function m_createBooleanArrayPublisher;
+ private final Function m_createStringPublisher;
+ private final Function m_createStringArrayPublisher;
/**
* Creates a logging backend that sends information to NetworkTables.
*
* @param nt the NetworkTable instance to use to send data to
*/
+ @SuppressWarnings("unchecked")
public NTEpilogueBackend(NetworkTableInstance nt) {
this.m_nt = nt;
+ m_createIntPublisher = identifier -> m_nt.getIntegerTopic(identifier).publish();
+ m_createFloatPublisher = identifier -> m_nt.getFloatTopic(identifier).publish();
+ m_createDoublePublisher = identifier -> m_nt.getDoubleTopic(identifier).publish();
+ m_createBooleanPublisher = identifier -> m_nt.getBooleanTopic(identifier).publish();
+ m_createRawPublisher = identifier -> m_nt.getRawTopic(identifier).publish("raw");
+ m_createIntegerArrayPublisher = identifier -> m_nt.getIntegerArrayTopic(identifier).publish();
+ m_createFloatArrayPublisher = identifier -> m_nt.getFloatArrayTopic(identifier).publish();
+ m_createDoubleArrayPublisher = identifier -> m_nt.getDoubleArrayTopic(identifier).publish();
+ m_createBooleanArrayPublisher = identifier -> m_nt.getBooleanArrayTopic(identifier).publish();
+ m_createStringPublisher = identifier -> m_nt.getStringTopic(identifier).publish();
+ m_createStringArrayPublisher = identifier -> m_nt.getStringArrayTopic(identifier).publish();
}
@Override
public EpilogueBackend getNested(String path) {
- return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
+ if (!m_nestedBackends.containsKey(path)) {
+ var nested = new NestedBackend(path, this);
+ m_nestedBackends.put(path, nested);
+ return nested;
+ }
+
+ return m_nestedBackends.get(path);
}
@Override
public void log(String identifier, int value) {
- ((IntegerPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getIntegerTopic(k).publish()))
- .set(value);
+ ((IntegerPublisher) m_publishers.computeIfAbsent(identifier, m_createIntPublisher)).set(value);
}
@Override
public void log(String identifier, long value) {
- ((IntegerPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getIntegerTopic(k).publish()))
- .set(value);
+ ((IntegerPublisher) m_publishers.computeIfAbsent(identifier, m_createIntPublisher)).set(value);
}
@Override
public void log(String identifier, float value) {
- ((FloatPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getFloatTopic(k).publish()))
- .set(value);
+ ((FloatPublisher) m_publishers.computeIfAbsent(identifier, m_createFloatPublisher)).set(value);
}
@Override
public void log(String identifier, double value) {
- ((DoublePublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getDoubleTopic(k).publish()))
+ ((DoublePublisher) m_publishers.computeIfAbsent(identifier, m_createDoublePublisher))
.set(value);
}
@Override
public void log(String identifier, boolean value) {
- ((BooleanPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getBooleanTopic(k).publish()))
+ ((BooleanPublisher) m_publishers.computeIfAbsent(identifier, m_createBooleanPublisher))
.set(value);
}
@Override
public void log(String identifier, byte[] value) {
- ((RawPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getRawTopic(k).publish("raw")))
- .set(value);
+ ((RawPublisher) m_publishers.computeIfAbsent(identifier, m_createRawPublisher)).set(value);
}
@Override
@@ -100,68 +123,79 @@ public class NTEpilogueBackend implements EpilogueBackend {
}
((IntegerArrayPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getIntegerArrayTopic(k).publish()))
+ m_publishers.computeIfAbsent(identifier, m_createIntegerArrayPublisher))
.set(widened);
}
@Override
public void log(String identifier, long[] value) {
((IntegerArrayPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getIntegerArrayTopic(k).publish()))
+ m_publishers.computeIfAbsent(identifier, m_createIntegerArrayPublisher))
.set(value);
}
@Override
public void log(String identifier, float[] value) {
- ((FloatArrayPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getFloatArrayTopic(k).publish()))
+ ((FloatArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createFloatArrayPublisher))
.set(value);
}
@Override
public void log(String identifier, double[] value) {
- ((DoubleArrayPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getDoubleArrayTopic(k).publish()))
+ ((DoubleArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createDoubleArrayPublisher))
.set(value);
}
@Override
public void log(String identifier, boolean[] value) {
((BooleanArrayPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getBooleanArrayTopic(k).publish()))
+ m_publishers.computeIfAbsent(identifier, m_createBooleanArrayPublisher))
.set(value);
}
@Override
public void log(String identifier, String value) {
- ((StringPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getStringTopic(k).publish()))
+ ((StringPublisher) m_publishers.computeIfAbsent(identifier, m_createStringPublisher))
.set(value);
}
@Override
public void log(String identifier, String[] value) {
- ((StringArrayPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getStringArrayTopic(k).publish()))
+ ((StringArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createStringArrayPublisher))
.set(value);
}
@Override
@SuppressWarnings("unchecked")
public void log(String identifier, S value, Struct struct) {
- m_nt.addSchema(struct);
- ((StructPublisher)
- m_publishers.computeIfAbsent(identifier, k -> m_nt.getStructTopic(k, struct).publish()))
- .set(value);
+ // NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations
+ if (m_seenSchemas.add(struct)) {
+ m_nt.addSchema(struct);
+ }
+
+ if (m_publishers.containsKey(identifier)) {
+ ((StructPublisher) m_publishers.get(identifier)).set(value);
+ } else {
+ StructPublisher publisher = m_nt.getStructTopic(identifier, struct).publish();
+ m_publishers.put(identifier, publisher);
+ publisher.set(value);
+ }
}
@Override
@SuppressWarnings("unchecked")
public void log(String identifier, S[] value, Struct struct) {
- m_nt.addSchema(struct);
- ((StructArrayPublisher)
- m_publishers.computeIfAbsent(
- identifier, k -> m_nt.getStructArrayTopic(k, struct).publish()))
- .set(value);
+ // NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations
+ if (m_seenSchemas.add(struct)) {
+ m_nt.addSchema(struct);
+ }
+
+ if (m_publishers.containsKey(identifier)) {
+ ((StructArrayPublisher) m_publishers.get(identifier)).set(value);
+ } else {
+ StructArrayPublisher publisher = m_nt.getStructArrayTopic(identifier, struct).publish();
+ m_publishers.put(identifier, publisher);
+ publisher.set(value);
+ }
}
}
diff --git a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/NestedBackend.java b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/NestedBackend.java
index 50003b24ca..f288566085 100644
--- a/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/NestedBackend.java
+++ b/epilogue-runtime/src/main/java/edu/wpi/first/epilogue/logging/NestedBackend.java
@@ -17,6 +17,15 @@ public class NestedBackend implements EpilogueBackend {
private final EpilogueBackend m_impl;
private final Map m_nestedBackends = new HashMap<>();
+ // String concatenation can be expensive, especially for deeply nested hierarchies with many
+ // logged fields. For example, logging a hypothetical Robot.elevator.io.getHeight() would result
+ // in "/Robot/" + "elevator/" + "io/" + "getHeight"; three concatenations and string and byte
+ // array allocations that need to be cleaned up by the GC. Caching the results means those
+ // allocations only occur once, resulting in no GC (the strings are always referenced in the
+ // cache), and minimal time costs (the String object caches its own hash code, so all we do is an
+ // O(1) table lookup per concatenation)
+ private final Map m_prefixedIdentifiers = new HashMap<>();
+
/**
* Creates a new nested backed underneath another backend.
*
@@ -33,83 +42,109 @@ public class NestedBackend implements EpilogueBackend {
this.m_impl = impl;
}
+ /**
+ * Fast lookup to avoid redundant `m_prefix + identifier` concatenations. If the identifier has
+ * not been seen before, we compute the concatenation and cache the result for later invocations
+ * to read. This avoids redundantly recomputing the same concatenations every loop and
+ * significantly cuts down on the CPU and memory overhead of the Epilogue library.
+ *
+ * @param identifier The identifier to prepend with {@link #m_prefix}.
+ * @return The concatenated string.
+ */
+ private String withPrefix(String identifier) {
+ // Using computeIfAbsent would result in a new lambda object allocation on every call
+ if (m_prefixedIdentifiers.containsKey(identifier)) {
+ return m_prefixedIdentifiers.get(identifier);
+ }
+
+ String result = m_prefix + identifier;
+ m_prefixedIdentifiers.put(identifier, result);
+ return result;
+ }
+
@Override
public EpilogueBackend getNested(String path) {
- return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
+ if (!m_nestedBackends.containsKey(path)) {
+ var nested = new NestedBackend(path, this);
+ m_nestedBackends.put(path, nested);
+ return nested;
+ }
+
+ return m_nestedBackends.get(path);
}
@Override
public void log(String identifier, int value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, long value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, float value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, double value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, boolean value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, byte[] value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, int[] value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, long[] value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, float[] value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, double[] value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, boolean[] value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, String value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, String[] value) {
- m_impl.log(m_prefix + identifier, value);
+ m_impl.log(withPrefix(identifier), value);
}
@Override
public void log(String identifier, S value, Struct struct) {
- m_impl.log(m_prefix + identifier, value, struct);
+ m_impl.log(withPrefix(identifier), value, struct);
}
@Override
public void log(String identifier, S[] value, Struct struct) {
- m_impl.log(m_prefix + identifier, value, struct);
+ m_impl.log(withPrefix(identifier), value, struct);
}
}
diff --git a/epilogue-runtime/src/test/java/edu/wpi/first/epilogue/logging/NestedBackendTest.java b/epilogue-runtime/src/test/java/edu/wpi/first/epilogue/logging/NestedBackendTest.java
new file mode 100644
index 0000000000..e9ee64ac6a
--- /dev/null
+++ b/epilogue-runtime/src/test/java/edu/wpi/first/epilogue/logging/NestedBackendTest.java
@@ -0,0 +1,180 @@
+// 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.epilogue.logging;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.junit.jupiter.api.Test;
+
+class NestedBackendTest {
+ @Test
+ void prefixesAppliedAndNested() {
+ var root = new TestBackend();
+ var nested = new NestedBackend("/Robot", root);
+
+ nested.log("int", 1);
+ nested.log("string", "hello");
+
+ var arm = nested.getNested("arm");
+ arm.log("position", 2.0);
+ arm.log("enabled", true);
+
+ assertEquals(4, root.getEntries().size());
+ assertEquals("/Robot/int", root.getEntries().get(0).identifier());
+ assertEquals(1, root.getEntries().get(0).value());
+
+ assertEquals("/Robot/string", root.getEntries().get(1).identifier());
+ assertEquals("hello", root.getEntries().get(1).value());
+
+ assertEquals("/Robot/arm/position", root.getEntries().get(2).identifier());
+ assertEquals(2.0, root.getEntries().get(2).value());
+
+ assertEquals("/Robot/arm/enabled", root.getEntries().get(3).identifier());
+ assertEquals(true, root.getEntries().get(3).value());
+ }
+
+ @Test
+ void handlesTrailingSlashOnPrefix() {
+ var root = new TestBackend();
+ var a = new NestedBackend("/Robot", root);
+ var b = new NestedBackend("/Robot/", root);
+
+ a.log("x", 1);
+ b.log("y", 2);
+
+ assertEquals("/Robot/x", root.getEntries().get(0).identifier());
+ assertEquals("/Robot/y", root.getEntries().get(1).identifier());
+ }
+
+ @Test
+ void getNestedIsCached() {
+ var root = new TestBackend();
+ var nested = new NestedBackend("/Robot", root);
+
+ var arm1 = nested.getNested("arm");
+ var arm2 = nested.getNested("arm");
+
+ assertSame(arm1, arm2);
+ }
+
+ @Test
+ void usesPrefixedIdentifierCacheForSameField() {
+ var root = new TestBackend();
+ var nested = new NestedBackend("/Robot", root);
+
+ // Same field logged multiple times - identifier object should be the same (cached)
+ // We use assertSame to check that the references are identical
+ nested.log("x", 0);
+ nested.log("x", 1);
+
+ String id0 = root.getEntries().get(0).identifier();
+ String id1 = root.getEntries().get(1).identifier();
+ assertSame(
+ id0,
+ id1,
+ "Identifier %s (id: %d) was not reused (new id: %d)"
+ .formatted(id0, System.identityHashCode(id0), System.identityHashCode(id1)));
+
+ // Also verify through a nested backend path
+ var arm = nested.getNested("arm");
+ arm.log("position", 0.0);
+ arm.log("position", 1.0);
+
+ String id2 = root.getEntries().get(2).identifier();
+ String id3 = root.getEntries().get(3).identifier();
+ assertSame(
+ id2,
+ id3,
+ "Identifier %s (id: %d) was not reused (new id: %d)"
+ .formatted(id2, System.identityHashCode(id2), System.identityHashCode(id3)));
+
+ // Sanity check actual full values
+ assertEquals("/Robot/x", id0);
+ assertEquals("/Robot/arm/position", id2);
+ }
+
+ @Test
+ void logsAllOverloads() {
+ var root = new TestBackend();
+ var nested = new NestedBackend("/Robot", root);
+
+ // Scalars
+ nested.log("int", 1);
+ nested.log("long", 2L);
+ nested.log("float", 3.0f);
+ nested.log("double", 4.0);
+ nested.log("boolean", true);
+ nested.log("string", "hello");
+
+ // Arrays
+ nested.log("bytes", new byte[] {1, 2});
+ nested.log("ints", new int[] {3, 4});
+ nested.log("longs", new long[] {5L, 6L});
+ nested.log("floats", new float[] {7.0f, 8.0f});
+ nested.log("doubles", new double[] {9.0, 10.0});
+ nested.log("booleans", new boolean[] {true, false});
+ nested.log("strings", new String[] {"x", "y"});
+
+ // Structs
+ nested.log("customStruct", new CustomStruct(7), CustomStruct.struct);
+ nested.log(
+ "customStructs",
+ new CustomStruct[] {new CustomStruct(0), new CustomStruct(1)},
+ CustomStruct.struct);
+
+ var entries = root.getEntries();
+ int idx = 0;
+
+ // Scalars
+ assertEquals(new TestBackend.LogEntry<>("/Robot/int", 1), entries.get(idx++));
+ assertEquals(new TestBackend.LogEntry<>("/Robot/long", 2L), entries.get(idx++));
+ assertEquals(new TestBackend.LogEntry<>("/Robot/float", 3.0f), entries.get(idx++));
+ assertEquals(new TestBackend.LogEntry<>("/Robot/double", 4.0), entries.get(idx++));
+ assertEquals(new TestBackend.LogEntry<>("/Robot/boolean", true), entries.get(idx++));
+ assertEquals(new TestBackend.LogEntry<>("/Robot/string", "hello"), entries.get(idx++));
+
+ // Arrays
+ assertEquals("/Robot/bytes", entries.get(idx).identifier());
+ assertArrayEquals(new byte[] {1, 2}, (byte[]) entries.get(idx++).value());
+
+ assertEquals("/Robot/ints", entries.get(idx).identifier());
+ assertArrayEquals(new int[] {3, 4}, (int[]) entries.get(idx++).value());
+
+ assertEquals("/Robot/longs", entries.get(idx).identifier());
+ assertArrayEquals(new long[] {5L, 6L}, (long[]) entries.get(idx++).value());
+
+ assertEquals("/Robot/floats", entries.get(idx).identifier());
+ assertArrayEquals(new float[] {7.0f, 8.0f}, (float[]) entries.get(idx++).value());
+
+ assertEquals("/Robot/doubles", entries.get(idx).identifier());
+ assertArrayEquals(new double[] {9.0, 10.0}, (double[]) entries.get(idx++).value());
+
+ assertEquals("/Robot/booleans", entries.get(idx).identifier());
+ assertArrayEquals(new boolean[] {true, false}, (boolean[]) entries.get(idx++).value());
+
+ assertEquals("/Robot/strings", entries.get(idx).identifier());
+ assertArrayEquals(new String[] {"x", "y"}, (String[]) entries.get(idx++).value());
+
+ // Structs are serialized to bytes
+ assertEquals("/Robot/customStruct", entries.get(idx).identifier());
+ assertArrayEquals(new byte[] {0x07, 0x00, 0x00, 0x00}, (byte[]) entries.get(idx++).value());
+
+ assertEquals("/Robot/customStructs", entries.get(idx).identifier());
+ // two int32 values, little-endian
+ assertArrayEquals(
+ new byte[] {
+ 0x00, 0x00, 0x00, 0x00, // 0 (first element)
+ 0x01, 0x00, 0x00, 0x00, // 1 (second element)
+ 0x00, 0x00, 0x00, 0x00, // 0 (empty space allocated by StructBuffer)
+ 0x00, 0x00, 0x00, 0x00 // 0 (empty space allocated by StructBuffer)
+ },
+ (byte[]) entries.get(idx++).value());
+
+ // Ensure we covered all calls
+ assertEquals(idx, entries.size());
+ }
+}
diff --git a/hal/src/main/java/edu/wpi/first/hal/CANAPITypes.java b/hal/src/main/java/edu/wpi/first/hal/CANAPITypes.java
index cef69b65b2..f97cc18f92 100644
--- a/hal/src/main/java/edu/wpi/first/hal/CANAPITypes.java
+++ b/hal/src/main/java/edu/wpi/first/hal/CANAPITypes.java
@@ -35,10 +35,10 @@ public final class CANAPITypes {
kGyroSensor(4),
/** Accelerometer. */
kAccelerometer(5),
- /** Ultrasonic sensor. */
- kUltrasonicSensor(6),
- /** Gear tooth sensor. */
- kGearToothSensor(7),
+ /** Distance sensor. */
+ kDistanceSensor(6),
+ /** Encoder. */
+ kEncoder(7),
/** Power distribution. */
kPowerDistribution(8),
/** Pneumatics. */
@@ -49,6 +49,8 @@ public final class CANAPITypes {
kIOBreakout(11),
/** Servo Controller. */
kServoController(12),
+ /** Color Sensor. */
+ kColorSensor(13),
/** Firmware update. */
kFirmwareUpdate(31);
@@ -105,7 +107,15 @@ public final class CANAPITypes {
/** AndyMark. */
kAndyMark(15),
/** Vivid-Hosting. */
- kVividHosting(16);
+ kVividHosting(16),
+ /** Vertos Robotics. */
+ kVertosRobotics(17),
+ /** SWYFT Robotics. */
+ kSWYFTRobotics(18),
+ /** Lumyn Labs. */
+ kLumynLabs(19),
+ /** Brushland Labs. */
+ kBrushlandLabs(20);
/** The manufacturer ID. */
@SuppressWarnings("MemberName")
diff --git a/hal/src/main/native/include/hal/CANAPITypes.h b/hal/src/main/native/include/hal/CANAPITypes.h
index baec978727..2048a7028e 100644
--- a/hal/src/main/native/include/hal/CANAPITypes.h
+++ b/hal/src/main/native/include/hal/CANAPITypes.h
@@ -32,10 +32,10 @@ HAL_ENUM(HAL_CANDeviceType) {
HAL_CAN_Dev_kGyroSensor = 4,
/// Accelerometer.
HAL_CAN_Dev_kAccelerometer = 5,
- /// Ultrasonic sensor.
- HAL_CAN_Dev_kUltrasonicSensor = 6,
- /// Gear tooth sensor.
- HAL_CAN_Dev_kGearToothSensor = 7,
+ /// Distance sensor.
+ HAL_CAN_Dev_kDistanceSensor = 6,
+ /// Encoder.
+ HAL_CAN_Dev_kEncoder = 7,
/// Power distribution.
HAL_CAN_Dev_kPowerDistribution = 8,
/// Pneumatics.
@@ -44,8 +44,10 @@ HAL_ENUM(HAL_CANDeviceType) {
HAL_CAN_Dev_kMiscellaneous = 10,
/// IO breakout.
HAL_CAN_Dev_kIOBreakout = 11,
- // Servo controller.
+ /// Servo controller.
HAL_CAN_Dev_kServoController = 12,
+ /// Color Sensor.
+ HAL_CAN_Dev_ColorSensor = 13,
/// Firmware update.
HAL_CAN_Dev_kFirmwareUpdate = 31
};
@@ -89,7 +91,15 @@ HAL_ENUM(HAL_CANManufacturer) {
/// AndyMark.
HAL_CAN_Man_kAndyMark = 15,
/// Vivid-Hosting.
- HAL_CAN_Man_kVividHosting = 16
+ HAL_CAN_Man_kVividHosting = 16,
+ /// Vertos Robotics.
+ HAL_CAN_Man_kVertosRobotics = 17,
+ /// SWYFT Robotics.
+ HAL_CAN_Man_kSWYFTRobotics = 18,
+ /// Lumyn Labs.
+ HAL_CAN_Man_kLumynLabs = 19,
+ /// Brushland Labs
+ HAL_CAN_Man_kBrushlandLabs = 20
};
/**
diff --git a/processstarter/publish.gradle b/processstarter/publish.gradle
index b287bf930a..1fca11019c 100644
--- a/processstarter/publish.gradle
+++ b/processstarter/publish.gradle
@@ -42,6 +42,7 @@ model {
}
from(applicationPath)
+ into(nativeUtils.getPlatformPath(binary))
}
task.dependsOn binary.tasks.link
diff --git a/upstream_utils/eigen.py b/upstream_utils/eigen.py
index fdd9f580f9..6cb3facfa9 100755
--- a/upstream_utils/eigen.py
+++ b/upstream_utils/eigen.py
@@ -38,6 +38,10 @@ def eigen_inclusions(dp: Path, f: str):
if "MKL" in f:
return False
+ # Exclude HIP CUDA support
+ if "GpuHip" in f:
+ return False
+
# Include architectures we care about by filtering for Core/arch
if "Core" in dp.parts and "arch" in dp.parts:
return (
@@ -140,8 +144,8 @@ def copy_upstream_src(wpilib_root: Path):
def main():
name = "eigen"
url = "https://gitlab.com/libeigen/eigen.git"
- # master on 2025-05-18
- tag = "d81aa18f4dc56264b2cd7e2f230807d776a2d385"
+ # master on 2025-09-08
+ tag = "e0a59e5a66e6d16fa93ab4f5e48bf539205e837f"
eigen = Lib(name, url, tag, copy_upstream_src)
eigen.main()
diff --git a/upstream_utils/eigen_patches/0002-Intellisense-fix.patch b/upstream_utils/eigen_patches/0002-Intellisense-fix.patch
index 5522d69109..7f2aaeea10 100644
--- a/upstream_utils/eigen_patches/0002-Intellisense-fix.patch
+++ b/upstream_utils/eigen_patches/0002-Intellisense-fix.patch
@@ -8,7 +8,7 @@ Subject: [PATCH 2/2] Intellisense fix
1 file changed, 7 insertions(+)
diff --git a/Eigen/src/Core/util/ConfigureVectorization.h b/Eigen/src/Core/util/ConfigureVectorization.h
-index 49f307c734e937f013e659e931286a17ef6756f9..a9430716a320327aed81ea0cdffabc051aeb0ce2 100644
+index c2546a083898154a1d4bd741722a5544cbdb1d92..8b5cc16b2092a73804af87ed7f59722ae3fdab0c 100644
--- a/Eigen/src/Core/util/ConfigureVectorization.h
+++ b/Eigen/src/Core/util/ConfigureVectorization.h
@@ -178,6 +178,13 @@
diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Subsystem.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Subsystem.java
index c0e694774c..4a0c056149 100644
--- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Subsystem.java
+++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/Subsystem.java
@@ -98,6 +98,15 @@ public interface Subsystem {
CommandScheduler.getInstance().registerSubsystem(this);
}
+ /**
+ * Constructs a command that does nothing until interrupted. Requires this subsystem.
+ *
+ * @return the command
+ */
+ default Command idle() {
+ return Commands.idle(this);
+ }
+
/**
* Constructs a command that runs an action once and finishes. Requires this subsystem.
*
diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/Subsystem.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/Subsystem.cpp
index 876269a1b4..8282682b91 100644
--- a/wpilibNewCommands/src/main/native/cpp/frc2/command/Subsystem.cpp
+++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/Subsystem.cpp
@@ -46,6 +46,10 @@ void Subsystem::Register() {
return CommandScheduler::GetInstance().RegisterSubsystem(this);
}
+CommandPtr Subsystem::Idle() {
+ return cmd::Idle({this});
+}
+
CommandPtr Subsystem::RunOnce(std::function action) {
return cmd::RunOnce(std::move(action), {this});
}
diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/Subsystem.h b/wpilibNewCommands/src/main/native/include/frc2/command/Subsystem.h
index ea7fbb7591..2ec07f5d88 100644
--- a/wpilibNewCommands/src/main/native/include/frc2/command/Subsystem.h
+++ b/wpilibNewCommands/src/main/native/include/frc2/command/Subsystem.h
@@ -121,6 +121,15 @@ class Subsystem {
*/
void Register();
+ /**
+ * Constructs a command that does nothing until interrupted. Requires this
+ * subsystem.
+ *
+ * @return the command
+ */
+ [[nodiscard]]
+ CommandPtr Idle();
+
/**
* Constructs a command that runs an action once and finishes. Requires this
* subsystem.
diff --git a/wpimath/CMakeLists.txt b/wpimath/CMakeLists.txt
index c5be554abb..8f068dbbae 100644
--- a/wpimath/CMakeLists.txt
+++ b/wpimath/CMakeLists.txt
@@ -21,39 +21,39 @@ file(
if(WITH_JAVA)
include(UseJava)
- if(NOT EXISTS "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml/ejml-simple-0.43.1.jar")
+ if(NOT EXISTS "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml/ejml-simple-0.44.0.jar")
set(BASE_URL "https://search.maven.org/remotecontent?filepath=")
set(JAR_ROOT "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml")
message(STATUS "Downloading EJML jarfiles...")
download_and_check(
- "${BASE_URL}org/ejml/ejml-cdense/0.43.1/ejml-cdense-0.43.1.jar"
- "${JAR_ROOT}/ejml-cdense-0.43.1.jar"
+ "${BASE_URL}org/ejml/ejml-cdense/0.44.0/ejml-cdense-0.44.0.jar"
+ "${JAR_ROOT}/ejml-cdense-0.44.0.jar"
)
download_and_check(
- "${BASE_URL}org/ejml/ejml-core/0.43.1/ejml-core-0.43.1.jar"
- "${JAR_ROOT}/ejml-core-0.43.1.jar"
+ "${BASE_URL}org/ejml/ejml-core/0.44.0/ejml-core-0.44.0.jar"
+ "${JAR_ROOT}/ejml-core-0.44.0.jar"
)
download_and_check(
- "${BASE_URL}org/ejml/ejml-ddense/0.43.1/ejml-ddense-0.43.1.jar"
- "${JAR_ROOT}/ejml-ddense-0.43.1.jar"
+ "${BASE_URL}org/ejml/ejml-ddense/0.44.0/ejml-ddense-0.44.0.jar"
+ "${JAR_ROOT}/ejml-ddense-0.44.0.jar"
)
download_and_check(
- "${BASE_URL}org/ejml/ejml-dsparse/0.43.1/ejml-dsparse-0.43.1.jar"
- "${JAR_ROOT}/ejml-dsparse-0.43.1.jar"
+ "${BASE_URL}org/ejml/ejml-dsparse/0.44.0/ejml-dsparse-0.44.0.jar"
+ "${JAR_ROOT}/ejml-dsparse-0.44.0.jar"
)
download_and_check(
- "${BASE_URL}org/ejml/ejml-fdense/0.43.1/ejml-fdense-0.43.1.jar"
- "${JAR_ROOT}/ejml-fdense-0.43.1.jar"
+ "${BASE_URL}org/ejml/ejml-fdense/0.44.0/ejml-fdense-0.44.0.jar"
+ "${JAR_ROOT}/ejml-fdense-0.44.0.jar"
)
download_and_check(
- "${BASE_URL}org/ejml/ejml-simple/0.43.1/ejml-simple-0.43.1.jar"
- "${JAR_ROOT}/ejml-simple-0.43.1.jar"
+ "${BASE_URL}org/ejml/ejml-simple/0.44.0/ejml-simple-0.44.0.jar"
+ "${JAR_ROOT}/ejml-simple-0.44.0.jar"
)
download_and_check(
- "${BASE_URL}org/ejml/ejml-zdense/0.43.1/ejml-zdense-0.43.1.jar"
- "${JAR_ROOT}/ejml-zdense-0.43.1.jar"
+ "${BASE_URL}org/ejml/ejml-zdense/0.44.0/ejml-zdense-0.44.0.jar"
+ "${JAR_ROOT}/ejml-zdense-0.44.0.jar"
)
message(STATUS "All files downloaded.")
diff --git a/wpimath/README.md b/wpimath/README.md
new file mode 100644
index 0000000000..c4de2c78bd
--- /dev/null
+++ b/wpimath/README.md
@@ -0,0 +1,66 @@
+# wpimath
+
+wpimath contains utilities for robot control (feedforward/feedback), state estimation (filters, Kalman and otherwise), 2D/3D geometry, kinematics, trajectory generation, and trajectory optimization.
+
+## Implementation guidelines
+
+A lot of wpimath features directly implement equations from books or papers. The following guidelines make that code easier to maintain and audit for correctness.
+
+### Citations
+
+Cite source books/papers at the top of the function (e.g., `See section 5.6 of "book name".`). If multiple items from a given work are referenced, write a bibliography entry for the work to reference later.
+
+Cite the equation numbers each line of code implements, if applicable. For example, `See equation (#.#) of [1].` where `[1]` is a bibliography reference number.
+
+### Comments
+
+Comment each line of code with its pretty-printed math equivalent.
+```cpp
+// xₖ₊₁ = Axₖ + Buₖ
+x = A * x + B * u;
+```
+
+Link to explanatory material where appropriate to explain background knowledge and/or notation choice.
+
+### Variable naming
+
+Follow established mathematical convention where possible (e.g., use A, B, C, D for state-space notation instead of `stateTransitionMatrix`).
+
+Use math symbols in variable names (see [Unicodeit](#Unicodeit)) to match source papers. This usually entails some Greek letters (α), but diacritics, superscripts, and subscripts need to be spelled out (`ẋ` → `x_dot`, `αₖ²` → `α_k_sq`) since compilers reject them, and the small features make variable names difficult to read.
+
+### Derivations
+
+Put small derivations in comments within the function. Put large derivations in algorithms.md and link to them.
+
+## Unicodeit
+
+When writing math expressions in documentation, use https://www.unicodeit.net/ to convert LaTeX to a Unicode equivalent that's easier to read. Not all expressions will translate (e.g., superscripts of superscripts) so focus on making it readable by someone who isn't familiar with LaTeX. If content on multiple lines needs to be aligned in Doxygen/Javadoc comments (e.g., integration/summation limits, matrices packed with square brackets and superscripts for them), put them in @verbatim/@endverbatim blocks in Doxygen or `` tags in Javadoc so they render with monospace font.
+
+The LaTeX to Unicode conversions can also be done locally via the unicodeit Python package. To install it, execute:
+```bash
+pip install --user unicodeit
+```
+
+Here's example usage:
+```bash
+$ python -m unicodeit.cli 'x_{k+1} = Ax_k + Bu_k'
+xₖ₊₁ = Axₖ + Buₖ
+```
+
+On Linux, this process can be streamlined further by adding the following Bash function to your .bashrc (requires `wl-clipboard` on Wayland or `xclip` on X11):
+```bash
+# Converts LaTeX to Unicode, prints the result, and copies it to the clipboard
+uc() {
+ if [ $WAYLAND_DISPLAY ]; then
+ python -m unicodeit.cli $@ | tee >(wl-copy -n)
+ else
+ python -m unicodeit.cli $@ | tee >(xclip -sel)
+ fi
+}
+```
+
+Here's example usage:
+```bash
+$ uc 'x_{k+1} = Ax_k + Bu_k'
+xₖ₊₁ = Axₖ + Buₖ
+```
diff --git a/wpimath/build.gradle b/wpimath/build.gradle
index 90174d2693..6f7dbc3fa1 100644
--- a/wpimath/build.gradle
+++ b/wpimath/build.gradle
@@ -89,11 +89,11 @@ nativeUtils.exportsConfigs {
dependencies {
api project(":wpiunits")
- api "org.ejml:ejml-simple:0.43.1"
- api "com.fasterxml.jackson.core:jackson-annotations:2.15.2"
- api "com.fasterxml.jackson.core:jackson-core:2.15.2"
- api "com.fasterxml.jackson.core:jackson-databind:2.15.2"
- api "us.hebi.quickbuf:quickbuf-runtime:1.3.3"
+ api "org.ejml:ejml-simple:0.44.0"
+ api "com.fasterxml.jackson.core:jackson-annotations:2.19.2"
+ api "com.fasterxml.jackson.core:jackson-core:2.19.2"
+ api "com.fasterxml.jackson.core:jackson-databind:2.19.2"
+ api "us.hebi.quickbuf:quickbuf-runtime:1.4"
}
sourceSets.main.java.srcDir "${projectDir}/src/generated/main/java"
diff --git a/wpimath/src/main/java/edu/wpi/first/math/system/plant/LinearSystemId.java b/wpimath/src/main/java/edu/wpi/first/math/system/plant/LinearSystemId.java
index fd2e70a1b0..e771d7c866 100644
--- a/wpimath/src/main/java/edu/wpi/first/math/system/plant/LinearSystemId.java
+++ b/wpimath/src/main/java/edu/wpi/first/math/system/plant/LinearSystemId.java
@@ -124,7 +124,8 @@ public final class LinearSystemId {
* @param kA The acceleration gain, in volts/(unit/sec²)
* @return A LinearSystem representing the given characterized constants.
* @throws IllegalArgumentException if kV < 0 or kA <= 0.
- * @see https://github.com/wpilibsuite/sysid
+ * @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem createDCMotorSystem(double kV, double kA) {
if (kV < 0.0) {
@@ -235,7 +236,8 @@ public final class LinearSystemId {
* @param kA The acceleration gain, in volts/(unit/sec²)
* @return A LinearSystem representing the given characterized constants.
* @throws IllegalArgumentException if kV < 0 or kA <= 0.
- * @see https://github.com/wpilibsuite/sysid
+ * @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem identifyVelocitySystem(double kV, double kA) {
if (kV < 0.0) {
@@ -268,7 +270,8 @@ public final class LinearSystemId {
* @param kA The acceleration gain, in volts/(unit/sec²)
* @return A LinearSystem representing the given characterized constants.
* @throws IllegalArgumentException if kV < 0 or kA <= 0.
- * @see https://github.com/wpilibsuite/sysid
+ * @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem identifyPositionSystem(double kV, double kA) {
if (kV < 0.0) {
@@ -301,7 +304,8 @@ public final class LinearSystemId {
* @return A LinearSystem representing the given characterized constants.
* @throws IllegalArgumentException if kVLinear <= 0, kALinear <= 0, kVAngular <= 0, or
* kAAngular <= 0.
- * @see https://github.com/wpilibsuite/sysid
+ * @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem identifyDrivetrainSystem(
double kVLinear, double kALinear, double kVAngular, double kAAngular) {
@@ -348,7 +352,8 @@ public final class LinearSystemId {
* @return A LinearSystem representing the given characterized constants.
* @throws IllegalArgumentException if kVLinear <= 0, kALinear <= 0, kVAngular <= 0,
* kAAngular <= 0, or trackwidth <= 0.
- * @see https://github.com/wpilibsuite/sysid
+ * @see https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
public static LinearSystem identifyDrivetrainSystem(
double kVLinear, double kALinear, double kVAngular, double kAAngular, double trackwidth) {
diff --git a/wpimath/src/main/java/edu/wpi/first/math/trajectory/ExponentialProfile.java b/wpimath/src/main/java/edu/wpi/first/math/trajectory/ExponentialProfile.java
index db6f8f7a1c..abb811b781 100644
--- a/wpimath/src/main/java/edu/wpi/first/math/trajectory/ExponentialProfile.java
+++ b/wpimath/src/main/java/edu/wpi/first/math/trajectory/ExponentialProfile.java
@@ -4,6 +4,8 @@
package edu.wpi.first.math.trajectory;
+import edu.wpi.first.math.trajectory.struct.ExponentialProfileStateStruct;
+import edu.wpi.first.util.struct.StructSerializable;
import java.util.Objects;
/**
@@ -128,7 +130,10 @@ public class ExponentialProfile {
}
/** Profile state. */
- public static class State {
+ public static class State implements StructSerializable {
+ /** The struct that serializes this class. */
+ public static final ExponentialProfileStateStruct struct = new ExponentialProfileStateStruct();
+
/** The position at this state. */
public double position;
diff --git a/wpimath/src/main/java/edu/wpi/first/math/trajectory/TrapezoidProfile.java b/wpimath/src/main/java/edu/wpi/first/math/trajectory/TrapezoidProfile.java
index cbd7ead518..0653fea02e 100644
--- a/wpimath/src/main/java/edu/wpi/first/math/trajectory/TrapezoidProfile.java
+++ b/wpimath/src/main/java/edu/wpi/first/math/trajectory/TrapezoidProfile.java
@@ -5,6 +5,7 @@
package edu.wpi.first.math.trajectory;
import edu.wpi.first.math.MathSharedStore;
+import edu.wpi.first.math.trajectory.struct.TrapezoidProfileStateStruct;
import java.util.Objects;
/**
@@ -75,6 +76,9 @@ public class TrapezoidProfile {
/** Profile state. */
public static class State {
+ /** The struct used to serialize this class. */
+ public static final TrapezoidProfileStateStruct struct = new TrapezoidProfileStateStruct();
+
/** The position at this state. */
public double position;
diff --git a/wpimath/src/main/java/edu/wpi/first/math/trajectory/struct/ExponentialProfileStateStruct.java b/wpimath/src/main/java/edu/wpi/first/math/trajectory/struct/ExponentialProfileStateStruct.java
new file mode 100644
index 0000000000..55e7557380
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/trajectory/struct/ExponentialProfileStateStruct.java
@@ -0,0 +1,42 @@
+// 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.math.trajectory.struct;
+
+import edu.wpi.first.math.trajectory.ExponentialProfile;
+import edu.wpi.first.util.struct.Struct;
+import java.nio.ByteBuffer;
+
+public class ExponentialProfileStateStruct implements Struct {
+ @Override
+ public Class getTypeClass() {
+ return ExponentialProfile.State.class;
+ }
+
+ @Override
+ public String getTypeName() {
+ return "ExponentialProfileState";
+ }
+
+ @Override
+ public int getSize() {
+ return kSizeDouble * 2;
+ }
+
+ @Override
+ public String getSchema() {
+ return "double position;double velocity";
+ }
+
+ @Override
+ public ExponentialProfile.State unpack(ByteBuffer bb) {
+ return new ExponentialProfile.State(bb.getDouble(), bb.getDouble());
+ }
+
+ @Override
+ public void pack(ByteBuffer bb, ExponentialProfile.State value) {
+ bb.putDouble(value.position);
+ bb.putDouble(value.velocity);
+ }
+}
diff --git a/wpimath/src/main/java/edu/wpi/first/math/trajectory/struct/TrapezoidProfileStateStruct.java b/wpimath/src/main/java/edu/wpi/first/math/trajectory/struct/TrapezoidProfileStateStruct.java
new file mode 100644
index 0000000000..b952790af3
--- /dev/null
+++ b/wpimath/src/main/java/edu/wpi/first/math/trajectory/struct/TrapezoidProfileStateStruct.java
@@ -0,0 +1,42 @@
+// 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.math.trajectory.struct;
+
+import edu.wpi.first.math.trajectory.TrapezoidProfile;
+import edu.wpi.first.util.struct.Struct;
+import java.nio.ByteBuffer;
+
+public class TrapezoidProfileStateStruct implements Struct {
+ @Override
+ public Class getTypeClass() {
+ return TrapezoidProfile.State.class;
+ }
+
+ @Override
+ public String getTypeName() {
+ return "TrapezoidProfileState";
+ }
+
+ @Override
+ public int getSize() {
+ return kSizeDouble * 2;
+ }
+
+ @Override
+ public String getSchema() {
+ return "double position;double velocity";
+ }
+
+ @Override
+ public TrapezoidProfile.State unpack(ByteBuffer bb) {
+ return new TrapezoidProfile.State(bb.getDouble(), bb.getDouble());
+ }
+
+ @Override
+ public void pack(ByteBuffer bb, TrapezoidProfile.State value) {
+ bb.putDouble(value.position);
+ bb.putDouble(value.velocity);
+ }
+}
diff --git a/wpimath/src/main/native/include/frc/system/plant/LinearSystemId.h b/wpimath/src/main/native/include/frc/system/plant/LinearSystemId.h
index c61b287e05..fd4f843769 100644
--- a/wpimath/src/main/native/include/frc/system/plant/LinearSystemId.h
+++ b/wpimath/src/main/native/include/frc/system/plant/LinearSystemId.h
@@ -122,7 +122,7 @@ class WPILIB_DLLEXPORT LinearSystemId {
* @param kA The acceleration gain, in volts/(unit/sec²).
* @throws std::domain_error if kV < 0 or kA <= 0.
* @see https://github.com/wpilibsuite/sysid
+ * href="https://github.com/wpilibsuite/allwpilib/tree/main/sysid">https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
template
requires std::same_as ||
@@ -165,7 +165,7 @@ class WPILIB_DLLEXPORT LinearSystemId {
*
* @throws std::domain_error if kV < 0 or kA <= 0.
* @see https://github.com/wpilibsuite/sysid
+ * href="https://github.com/wpilibsuite/allwpilib/tree/main/sysid">https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
template
requires std::same_as ||
@@ -208,7 +208,7 @@ class WPILIB_DLLEXPORT LinearSystemId {
* @throws domain_error if kVLinear <= 0, kALinear <= 0, kVAngular <= 0,
* or kAAngular <= 0.
* @see https://github.com/wpilibsuite/sysid
+ * href="https://github.com/wpilibsuite/allwpilib/tree/main/sysid">https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
static constexpr LinearSystem<2, 2, 2> IdentifyDrivetrainSystem(
decltype(1_V / 1_mps) kVLinear, decltype(1_V / 1_mps_sq) kALinear,
@@ -269,7 +269,7 @@ class WPILIB_DLLEXPORT LinearSystemId {
* @throws domain_error if kVLinear <= 0, kALinear <= 0, kVAngular <= 0,
* kAAngular <= 0, or trackwidth <= 0.
* @see https://github.com/wpilibsuite/sysid
+ * href="https://github.com/wpilibsuite/allwpilib/tree/main/sysid">https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
static constexpr LinearSystem<2, 2, 2> IdentifyDrivetrainSystem(
decltype(1_V / 1_mps) kVLinear, decltype(1_V / 1_mps_sq) kALinear,
@@ -346,7 +346,7 @@ class WPILIB_DLLEXPORT LinearSystemId {
* @param gearing Gear ratio from motor to output.
* @throws std::domain_error if J <= 0 or gearing <= 0.
* @see https://github.com/wpilibsuite/sysid
+ * href="https://github.com/wpilibsuite/allwpilib/tree/main/sysid">https://github.com/wpilibsuite/allwpilib/tree/main/sysid
*/
static constexpr LinearSystem<2, 1, 2> DCMotorSystem(
DCMotor motor, units::kilogram_square_meter_t J, double gearing) {
diff --git a/wpimath/src/main/native/thirdparty/eigen/include/Eigen/Core b/wpimath/src/main/native/thirdparty/eigen/include/Eigen/Core
index 69aa1f8e5c..2fedfd3b80 100644
--- a/wpimath/src/main/native/thirdparty/eigen/include/Eigen/Core
+++ b/wpimath/src/main/native/thirdparty/eigen/include/Eigen/Core
@@ -92,6 +92,7 @@
#include
#include
+#include
#include
// for std::is_nothrow_move_assignable
@@ -102,6 +103,11 @@
#include
#endif
+// for std::bit_cast()
+#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
+#include
+#endif
+
// for outputting debug info
#ifdef EIGEN_DEBUG_ASSIGN
#include
@@ -121,7 +127,6 @@
#undef isfinite
#include
#include