From 7a3df6175ec3a0ddab7b08092abcf758d830ac85 Mon Sep 17 00:00:00 2001 From: Ryan Shavell Date: Sat, 31 May 2025 09:38:51 -0400 Subject: [PATCH 1/4] [epilogue] Add superclass field & method logging (#7993) --- .../epilogue/processor/ElementHandler.java | 10 +- .../epilogue/processor/LoggerGenerator.java | 105 +-- .../processor/AnnotationProcessorTest.java | 654 ++++++++++++++++-- 3 files changed, 672 insertions(+), 97 deletions(-) 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 c097d59137..d12acb7ecc 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 @@ -13,7 +13,9 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; /** * Handles logging of fields or methods. An element that passes the {@link #isLoggable(Element)} @@ -126,11 +128,15 @@ public abstract class ElementHandler { } private static String fieldAccess(VariableElement field) { - if (field.getModifiers().contains(Modifier.PRIVATE)) { + if (!field.getModifiers().contains(Modifier.PUBLIC)) { // ((com.example.Foo) $fooField.get(object)) // Extra parentheses so cast evaluates before appended methods // (e.g. when appending .getAsDouble()) - return "((" + field.asType() + ") $" + field.getSimpleName() + ".get(object))"; + TypeMirror type = field.asType(); + if (type.getKind() == TypeKind.TYPEVAR) { + type = ((TypeVariable) type).getUpperBound(); + } + return "((" + type.toString() + ") $" + field.getSimpleName() + ".get(object))"; } else { // object.fooField return "object." + field.getSimpleName(); 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 496a773095..7fbb82a931 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 @@ -114,42 +114,10 @@ public class LoggerGenerator { if (config == null) { config = m_defaultConfig; } - boolean requireExplicitOptIn = config.strategy() == Logged.Strategy.OPT_IN; - Predicate notSkipped = LoggerGenerator::isNotSkipped; - Predicate optedIn = - e -> !requireExplicitOptIn || e.getAnnotation(Logged.class) != null; - - List fieldsToLog; - if (Objects.equals(clazz.getSuperclass().toString(), "java.lang.Record")) { - // Do not log record members - just use the accessor methods - fieldsToLog = List.of(); - } else { - fieldsToLog = - clazz.getEnclosedElements().stream() - .filter(e -> e instanceof VariableElement) - .map(e -> (VariableElement) e) - .filter(notSkipped) - .filter(optedIn) - .filter(e -> !e.getModifiers().contains(Modifier.STATIC)) - .filter(this::isLoggable) - .toList(); - } - - List methodsToLog = - clazz.getEnclosedElements().stream() - .filter(e -> e instanceof ExecutableElement) - .map(e -> (ExecutableElement) e) - .filter(notSkipped) - .filter(optedIn) - .filter(e -> !e.getModifiers().contains(Modifier.STATIC)) - .filter(e -> e.getModifiers().contains(Modifier.PUBLIC)) - .filter(e -> e.getParameters().isEmpty()) - .filter(e -> e.getReceiverType() != null) - .filter(kIsBuiltInJavaMethod.negate()) - .filter(this::isLoggable) - .filter(e -> !isSimpleGetterMethodForLoggedField(e, fieldsToLog)) - .toList(); + List fieldsToLog = new ArrayList<>(); + List methodsToLog = new ArrayList<>(); + collectLoggables(clazz, fieldsToLog, methodsToLog); // Validate no name collisions Map> usedNames = @@ -216,9 +184,9 @@ public class LoggerGenerator { var loggerFile = m_processingEnv.getFiler().createSourceFile(loggerClassName); - var privateFields = - loggableFields.stream().filter(e -> e.getModifiers().contains(Modifier.PRIVATE)).toList(); - boolean requiresVarHandles = !privateFields.isEmpty(); + var varHandleFields = + loggableFields.stream().filter(e -> !e.getModifiers().contains(Modifier.PUBLIC)).toList(); + boolean requiresVarHandles = !varHandleFields.isEmpty(); try (var out = new PrintWriter(loggerFile.openWriter())) { if (packageName != null) { @@ -246,10 +214,10 @@ public class LoggerGenerator { + "> {"); if (requiresVarHandles) { - for (var privateField : privateFields) { + for (var varHandleField : varHandleFields) { // This field needs a VarHandle to access. // Cache it in the class to avoid lookups - out.println(" private static final VarHandle $" + privateField.getSimpleName() + ";"); + out.println(" private static final VarHandle $" + varHandleField.getSimpleName() + ";"); } out.println(); @@ -262,8 +230,8 @@ public class LoggerGenerator { + classReference + ", MethodHandles.lookup());"); - for (var privateField : privateFields) { - var fieldName = privateField.getSimpleName(); + for (var varHandleField : varHandleFields) { + var fieldName = varHandleField.getSimpleName(); out.println( " $" + fieldName @@ -272,7 +240,7 @@ public class LoggerGenerator { + ", \"" + fieldName + "\", " - + m_processingEnv.getTypeUtils().erasure(privateField.asType()) + + m_processingEnv.getTypeUtils().erasure(varHandleField.asType()) + ".class);"); } @@ -347,6 +315,57 @@ public class LoggerGenerator { } } + private void collectLoggables( + TypeElement clazz, List fields, List methods) { + var config = clazz.getAnnotation(Logged.class); + if (config == null) { + config = m_defaultConfig; + } + boolean requireExplicitOptIn = config.strategy() == Logged.Strategy.OPT_IN; + + Predicate notSkipped = LoggerGenerator::isNotSkipped; + Predicate optedIn = + e -> !requireExplicitOptIn || e.getAnnotation(Logged.class) != null; + + List classFields; + if (Objects.equals(clazz.getSuperclass().toString(), "java.lang.Record")) { + // Do not log record members - just use the accessor methods + classFields = List.of(); + } else { + classFields = + clazz.getEnclosedElements().stream() + .filter(e -> e instanceof VariableElement) + .map(e -> (VariableElement) e) + .filter(notSkipped) + .filter(optedIn) + .filter(e -> !e.getModifiers().contains(Modifier.STATIC)) + .filter(this::isLoggable) + .toList(); + } + fields.addAll(classFields); + + methods.addAll( + clazz.getEnclosedElements().stream() + .filter(e -> e instanceof ExecutableElement) + .map(e -> (ExecutableElement) e) + .filter(notSkipped) + .filter(optedIn) + .filter(e -> !e.getModifiers().contains(Modifier.STATIC)) + .filter(e -> e.getModifiers().contains(Modifier.PUBLIC)) + .filter(e -> e.getParameters().isEmpty()) + .filter(e -> e.getReceiverType() != null) + .filter(kIsBuiltInJavaMethod.negate()) + .filter(this::isLoggable) + .filter(e -> !isSimpleGetterMethodForLoggedField(e, classFields)) + .toList()); + + TypeElement superclass = + (TypeElement) m_processingEnv.getTypeUtils().asElement(clazz.getSuperclass()); + if (superclass != null) { + collectLoggables(superclass, fields, methods); + } + } + private boolean isLoggable(Element element) { return m_handlers.stream().anyMatch(h -> h.isLoggable(element)); } 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 8f1d9f2b19..50795a45b0 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,8 +42,21 @@ 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); } @@ -51,7 +64,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); + backend.log("x", ((double) $x.get(object))); } } } @@ -80,8 +93,23 @@ 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); } @@ -89,8 +117,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("y", object.y); + backend.log("x", ((double) $x.get(object))); + backend.log("y", ((int) $y.get(object))); } } } @@ -182,8 +210,23 @@ 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); } @@ -191,8 +234,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("y", object.y); + backend.log("x", ((double) $x.get(object))); + backend.log("y", ((double) $y.get(object))); } } } @@ -378,8 +421,25 @@ 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); } @@ -387,13 +447,13 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("low", object.low); + backend.log("low", ((double) $low.get(object))); } if (Epilogue.shouldLog(Logged.Importance.INFO)) { - backend.log("medium", object.medium); + backend.log("medium", ((int) $medium.get(object))); } if (Epilogue.shouldLog(Logged.Importance.CRITICAL)) { - backend.log("high", object.high); + backend.log("high", ((long) $high.get(object))); } } } @@ -426,8 +486,21 @@ 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); } @@ -435,7 +508,145 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("enumValue", object.enumValue); + backend.log("enumValue", ((edu.wpi.first.epilogue.Example.E) $enumValue.get(object))); + } + } + } + """; + + assertLoggerGenerates(source, expectedGeneratedSource); + } + + @Test + void superclassStillOptIn() { + String source = + """ + package edu.wpi.first.epilogue; + + // nothing should be logged from BaseExample + class BaseExample { + public double x; + public double getValue() { return 2.0; } + } + + @Logged + class Example extends BaseExample { + double y; + } + """; + + 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; + import java.lang.invoke.MethodHandles; + import java.lang.invoke.VarHandle; + + public class ExampleLogger extends ClassSpecificLogger { + private static final VarHandle $y; + + static { + try { + var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup()); + $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); + } + + @Override + public void update(EpilogueBackend backend, Example object) { + if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { + backend.log("y", ((double) $y.get(object))); + } + } + } + """; + + assertLoggerGenerates(source, expectedGeneratedSource); + } + + @Test + void superclass() { + String source = + """ + package edu.wpi.first.epilogue; + + class Grandparent { + @Logged + public double a; + @Logged public double getB() { return 0; } + public double getC() { return 1; } // not annotated, not logged + } + + @Logged + class BaseExample extends Grandparent { + protected double d; + public double e; + private double f; + double g; + public double getValue() { return 2.0; } + private double getOtherValue() { return 3.0; } // private, not logged + } + + @Logged + class Example extends BaseExample { + double h; + } + """; + + 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; + import java.lang.invoke.MethodHandles; + 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; + + 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); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e); + } + } + + public ExampleLogger() { + super(Example.class); + } + + @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("e", object.e); + backend.log("f", ((double) $f.get(object))); + backend.log("g", ((double) $g.get(object))); + backend.log("a", object.a); + backend.log("getValue", object.getValue()); + backend.log("getB", object.getB()); } } } @@ -470,8 +681,23 @@ 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); } @@ -479,8 +705,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("arr1", object.arr1); + backend.log("x", ((byte) $x.get(object))); + backend.log("arr1", ((byte[]) $arr1.get(object))); backend.log("getX", object.getX()); backend.log("getArr1", object.getArr1()); } @@ -517,8 +743,21 @@ 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); } @@ -526,7 +765,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); + backend.log("x", ((char) $x.get(object))); backend.log("getX", object.getX()); } } @@ -562,8 +801,21 @@ 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); } @@ -571,7 +823,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); + backend.log("x", ((short) $x.get(object))); backend.log("getX", object.getX()); } } @@ -607,8 +859,23 @@ 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); } @@ -616,8 +883,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("arr1", object.arr1); + backend.log("x", ((int) $x.get(object))); + backend.log("arr1", ((int[]) $arr1.get(object))); backend.log("getX", object.getX()); backend.log("getArr1", object.getArr1()); } @@ -654,8 +921,23 @@ 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); } @@ -663,8 +945,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("arr1", object.arr1); + backend.log("x", ((long) $x.get(object))); + backend.log("arr1", ((long[]) $arr1.get(object))); backend.log("getX", object.getX()); backend.log("getArr1", object.getArr1()); } @@ -701,8 +983,23 @@ 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); } @@ -710,8 +1007,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("arr1", object.arr1); + backend.log("x", ((float) $x.get(object))); + backend.log("arr1", ((float[]) $arr1.get(object))); backend.log("getX", object.getX()); backend.log("getArr1", object.getArr1()); } @@ -751,8 +1048,23 @@ 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); } @@ -760,8 +1072,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("arr1", object.arr1); + backend.log("x", ((double) $x.get(object))); + backend.log("arr1", ((double[]) $arr1.get(object))); backend.log("getX", object.getX()); backend.log("getArr1", object.getArr1()); } @@ -800,8 +1112,23 @@ 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); } @@ -809,8 +1136,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("arr1", object.arr1); + backend.log("x", ((boolean) $x.get(object))); + backend.log("arr1", ((boolean[]) $arr1.get(object))); backend.log("getX", object.getX()); backend.log("getArr1", object.getArr1()); } @@ -850,8 +1177,25 @@ 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); } @@ -859,9 +1203,9 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); - backend.log("arr1", object.arr1); - backend.log("list", object.list); + 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("getX", object.getX()); backend.log("getArr1", object.getArr1()); } @@ -909,8 +1253,25 @@ 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); } @@ -918,9 +1279,9 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - 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("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("getX", object.getX(), edu.wpi.first.epilogue.Example.Structable.struct); backend.log("getArr1", object.getArr1(), edu.wpi.first.epilogue.Example.Structable.struct); } @@ -960,8 +1321,27 @@ 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); } @@ -969,10 +1349,10 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("list", object.list); - backend.log("set", object.set); - backend.log("queue", object.queue); - backend.log("stack", object.stack); + 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))); } } } @@ -1141,8 +1521,21 @@ 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 $value; + + static { + try { + var lookup = MethodHandles.privateLookupIn(Example.class, MethodHandles.lookup()); + $value = lookup.findVarHandle(Example.class, "value", java.lang.String.class); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("[EPILOGUE] Could not load private fields for logging!", e); + } + } + public ExampleLogger() { super(Example.class); } @@ -1150,7 +1543,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("value", object.value); + backend.log("value", ((java.lang.String) $value.get(object))); backend.log("upcast", object.upcast()); } } @@ -1193,8 +1586,23 @@ 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); } @@ -1202,8 +1610,8 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - Epilogue.childLogger.tryUpdate(backend.getNested("child"), object.child, Epilogue.getConfig().errorHandler); - Epilogue.ioLogger.tryUpdate(backend.getNested("io"), object.io, Epilogue.getConfig().errorHandler); + 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); } } } @@ -1280,8 +1688,27 @@ 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); } @@ -1289,7 +1716,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - var $$asInterface = object.asInterface; + var $$asInterface = ((edu.wpi.first.epilogue.IFace) $asInterface.get(object)); 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) { @@ -1298,9 +1725,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"), object.firstImpl, Epilogue.getConfig().errorHandler); - Epilogue.impl2Logger.tryUpdate(backend.getNested("secondImpl"), object.secondImpl, Epilogue.getConfig().errorHandler); - var $$complex = object.complex; + 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)); 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) { @@ -1343,8 +1770,21 @@ 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); } @@ -1352,7 +1792,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Outer.Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); + backend.log("x", ((double) $x.get(object))); } } } @@ -1389,8 +1829,21 @@ 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); } @@ -1398,7 +1851,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, A.B.C.D.Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); + backend.log("x", ((double) $x.get(object))); } } } @@ -1429,8 +1882,21 @@ 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); } @@ -1438,7 +1904,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Outer.Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); + backend.log("x", ((double) $x.get(object))); } } } @@ -1482,8 +1948,21 @@ 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); } @@ -1491,7 +1970,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - var $$theField = object.theField; + var $$theField = ((edu.wpi.first.epilogue.I) $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 if ($$theField instanceof edu.wpi.first.epilogue.ExtendingInterface edu_wpi_first_epilogue_ExtendingInterface) { @@ -1594,8 +2073,21 @@ 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); } @@ -1603,7 +2095,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - Epilogue.implicitLogger.tryUpdate(backend.getNested("i"), object.i, Epilogue.getConfig().errorHandler); + Epilogue.implicitLogger.tryUpdate(backend.getNested("i"), ((edu.wpi.first.epilogue.Implicit) $i.get(object)), Epilogue.getConfig().errorHandler); } } } @@ -1648,8 +2140,21 @@ 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); } @@ -1657,7 +2162,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - Epilogue.customPointLogger.tryUpdate(backend.getNested("point"), object.point, Epilogue.getConfig().errorHandler); + Epilogue.customPointLogger.tryUpdate(backend.getNested("point"), ((edu.wpi.first.epilogue.Point) $point.get(object)), Epilogue.getConfig().errorHandler); } } } @@ -1703,8 +2208,21 @@ 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); } @@ -1712,7 +2230,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - Epilogue.vectorLogger.tryUpdate(backend.getNested("vec"), object.vec, Epilogue.getConfig().errorHandler); + Epilogue.vectorLogger.tryUpdate(backend.getNested("vec"), ((edu.wpi.first.math.Vector) $vec.get(object)), Epilogue.getConfig().errorHandler); } } } @@ -1899,8 +2417,21 @@ 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); } @@ -1908,7 +2439,7 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - backend.log("x", object.x); + backend.log("x", ((double) $x.get(object))); backend.log("withANoOpTransform", object.withANoOpTransform()); backend.log("withTemp", object.withTemp()); } @@ -1951,8 +2482,27 @@ 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); } @@ -1960,10 +2510,10 @@ class AnnotationProcessorTest { @Override public void update(EpilogueBackend backend, Example object) { if (Epilogue.shouldLog(Logged.Importance.DEBUG)) { - 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("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("The Getter Method", object.getTheGetterMethod()); backend.log("optedOut", object.optedOut()); } From 075cc4a20f44e2a5a61ed6139a855578cceb0b89 Mon Sep 17 00:00:00 2001 From: Michael Lesirge <100492377+MichaelLesirge@users.noreply.github.com> Date: Thu, 5 Jun 2025 22:11:49 -0700 Subject: [PATCH 2/4] [wpimath] Add nearest() method to Pose3d (mirroring Pose2d) (#8010) --- .../edu/wpi/first/math/geometry/Pose3d.java | 19 +++ .../main/native/include/frc/geometry/Pose2d.h | 12 +- .../main/native/include/frc/geometry/Pose3d.h | 50 ++++++++ .../wpi/first/math/geometry/Pose3dTest.java | 67 +++++++++++ .../test/native/cpp/geometry/Pose3dTest.cpp | 108 ++++++++++++++++++ 5 files changed, 254 insertions(+), 2 deletions(-) diff --git a/wpimath/src/main/java/edu/wpi/first/math/geometry/Pose3d.java b/wpimath/src/main/java/edu/wpi/first/math/geometry/Pose3d.java index 16de1dc33b..f08618b566 100644 --- a/wpimath/src/main/java/edu/wpi/first/math/geometry/Pose3d.java +++ b/wpimath/src/main/java/edu/wpi/first/math/geometry/Pose3d.java @@ -21,6 +21,9 @@ import edu.wpi.first.math.numbers.N4; import edu.wpi.first.units.measure.Distance; import edu.wpi.first.util.protobuf.ProtobufSerializable; import edu.wpi.first.util.struct.StructSerializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.Objects; /** Represents a 3D pose containing translational and rotational elements. */ @@ -396,6 +399,22 @@ public class Pose3d implements Interpolatable, ProtobufSerializable, Str return new Pose2d(m_translation.toTranslation2d(), m_rotation.toRotation2d()); } + /** + * Returns the nearest Pose3d from a list of poses. If two or more poses in the list have the same + * distance from this pose, return the one with the closest rotation component. + * + * @param poses The list of poses to find the nearest. + * @return The nearest Pose3d from the list. + */ + public Pose3d nearest(List poses) { + return Collections.min( + poses, + Comparator.comparing( + (Pose3d other) -> this.getTranslation().getDistance(other.getTranslation())) + .thenComparing( + (Pose3d other) -> this.getRotation().minus(other.getRotation()).getAngle())); + } + @Override public String toString() { return String.format("Pose3d(%s, %s)", m_translation, m_rotation); diff --git a/wpimath/src/main/native/include/frc/geometry/Pose2d.h b/wpimath/src/main/native/include/frc/geometry/Pose2d.h index 2d8825caa5..d488e312a7 100644 --- a/wpimath/src/main/native/include/frc/geometry/Pose2d.h +++ b/wpimath/src/main/native/include/frc/geometry/Pose2d.h @@ -242,7 +242,11 @@ class WPILIB_DLLEXPORT Pose2d { } /** - * Returns the nearest Pose2d from a collection of poses + * Returns the nearest Pose2d from a collection of poses. + * + * If two or more poses in the collection have the same distance from this + * pose, return the one with the closest rotation component. + * * @param poses The collection of poses. * @return The nearest Pose2d from the collection. */ @@ -264,7 +268,11 @@ class WPILIB_DLLEXPORT Pose2d { } /** - * Returns the nearest Pose2d from a collection of poses + * Returns the nearest Pose2d from a collection of poses. + * + * If two or more poses in the collection have the same distance from this + * pose, return the one with the closest rotation component. + * * @param poses The collection of poses. * @return The nearest Pose2d from the collection. */ diff --git a/wpimath/src/main/native/include/frc/geometry/Pose3d.h b/wpimath/src/main/native/include/frc/geometry/Pose3d.h index 110faf7355..743ccfbc04 100644 --- a/wpimath/src/main/native/include/frc/geometry/Pose3d.h +++ b/wpimath/src/main/native/include/frc/geometry/Pose3d.h @@ -270,6 +270,56 @@ class WPILIB_DLLEXPORT Pose3d { return Pose2d{m_translation.X(), m_translation.Y(), m_rotation.Z()}; } + /** + * Returns the nearest Pose3d from a collection of poses. + * + * If two or more poses in the collection have the same distance from this + * pose, return the one with the closest rotation component. + * + * @param poses The collection of poses. + * @return The nearest Pose3d from the collection. + */ + constexpr Pose3d Nearest(std::span poses) const { + return *std::min_element( + poses.begin(), poses.end(), [this](const Pose3d& a, const Pose3d& b) { + auto aDistance = this->Translation().Distance(a.Translation()); + auto bDistance = this->Translation().Distance(b.Translation()); + + // If the distances are equal sort by difference in rotation + if (aDistance == bDistance) { + return gcem::abs( + (this->Rotation() - a.Rotation()).Angle().value()) < + gcem::abs((this->Rotation() - b.Rotation()).Angle().value()); + } + return aDistance < bDistance; + }); + } + + /** + * Returns the nearest Pose3d from a collection of poses. + * + * If two or more poses in the collection have the same distance from this + * pose, return the one with the closest rotation component. + * + * @param poses The collection of poses. + * @return The nearest Pose3d from the collection. + */ + constexpr Pose3d Nearest(std::initializer_list poses) const { + return *std::min_element( + poses.begin(), poses.end(), [this](const Pose3d& a, const Pose3d& b) { + auto aDistance = this->Translation().Distance(a.Translation()); + auto bDistance = this->Translation().Distance(b.Translation()); + + // If the distances are equal sort by difference in rotation + if (aDistance == bDistance) { + return gcem::abs( + (this->Rotation() - a.Rotation()).Angle().value()) < + gcem::abs((this->Rotation() - b.Rotation()).Angle().value()); + } + return aDistance < bDistance; + }); + } + private: Translation3d m_translation; Rotation3d m_rotation; diff --git a/wpimath/src/test/java/edu/wpi/first/math/geometry/Pose3dTest.java b/wpimath/src/test/java/edu/wpi/first/math/geometry/Pose3dTest.java index f8fadea6a5..583e125fc0 100644 --- a/wpimath/src/test/java/edu/wpi/first/math/geometry/Pose3dTest.java +++ b/wpimath/src/test/java/edu/wpi/first/math/geometry/Pose3dTest.java @@ -324,4 +324,71 @@ class Pose3dTest { () -> assertFalse(((Double) twist.rz).isNaN())); } } + + @Test + void testNearest() { + var origin = Pose3d.kZero; + + // Distance sort + // poses are in order of closest to farthest away from the origin at various positions in 3D + // space. + final var pose1 = new Pose3d(1, 0, 0, Rotation3d.kZero); + final var pose2 = new Pose3d(0, 2, 0, Rotation3d.kZero); + final var pose3 = new Pose3d(0, 0, 3, Rotation3d.kZero); + final var pose4 = new Pose3d(2, 2, 2, Rotation3d.kZero); + final var pose5 = new Pose3d(3, 3, 3, Rotation3d.kZero); + + assertEquals(pose3, origin.nearest(List.of(pose5, pose3, pose4))); + assertEquals(pose1, origin.nearest(List.of(pose1, pose2, pose3))); + assertEquals(pose2, origin.nearest(List.of(pose4, pose2, pose3))); + + // Rotation component sort (when distance is the same) + // Use the same translation to avoid distance differences + final var translation = new Translation3d(1, 0, 0); + + final var poseA = new Pose3d(translation, Rotation3d.kZero); // No rotation + final var poseB = new Pose3d(translation, new Rotation3d(Math.toRadians(30), 0, 0)); + final var poseC = new Pose3d(translation, new Rotation3d(0, Math.toRadians(45), 0)); + final var poseD = new Pose3d(translation, new Rotation3d(0, 0, Math.toRadians(90))); + final var poseE = new Pose3d(translation, new Rotation3d(Math.toRadians(180), 0, 0)); + + assertEquals( + poseA, new Pose3d(0, 0, 0, Rotation3d.kZero).nearest(List.of(poseA, poseB, poseD))); + assertEquals( + poseB, + new Pose3d(0, 0, 0, new Rotation3d(Math.toRadians(25), 0, 0)) + .nearest(List.of(poseB, poseC, poseD))); + assertEquals( + poseC, + new Pose3d(0, 0, 0, new Rotation3d(0, Math.toRadians(50), 0)) + .nearest(List.of(poseB, poseC, poseD))); + assertEquals( + poseD, + new Pose3d(0, 0, 0, new Rotation3d(0, 0, Math.toRadians(85))) + .nearest(List.of(poseA, poseC, poseD))); + assertEquals( + poseE, + new Pose3d(0, 0, 0, new Rotation3d(Math.toRadians(170), 0, 0)) + .nearest(List.of(poseA, poseD, poseE))); + + // Test with complex 3D rotations (combining roll, pitch, yaw) + final var complexPose1 = + new Pose3d( + translation, + new Rotation3d(Math.toRadians(45), Math.toRadians(30), Math.toRadians(60))); + final var complexPose2 = + new Pose3d( + translation, + new Rotation3d(Math.toRadians(90), Math.toRadians(45), Math.toRadians(90))); + final var complexPose3 = + new Pose3d( + translation, + new Rotation3d(Math.toRadians(10), Math.toRadians(15), Math.toRadians(20))); + + assertEquals( + complexPose3, + new Pose3d( + 0, 0, 0, new Rotation3d(Math.toRadians(5), Math.toRadians(10), Math.toRadians(15))) + .nearest(List.of(complexPose1, complexPose2, complexPose3))); + } } diff --git a/wpimath/src/test/native/cpp/geometry/Pose3dTest.cpp b/wpimath/src/test/native/cpp/geometry/Pose3dTest.cpp index ee3f8fa53a..7a84718ea1 100644 --- a/wpimath/src/test/native/cpp/geometry/Pose3dTest.cpp +++ b/wpimath/src/test/native/cpp/geometry/Pose3dTest.cpp @@ -221,3 +221,111 @@ TEST(Pose3dTest, TwistNaN) { EXPECT_FALSE(std::isnan(twist.rz.value())); } } + +TEST(Pose3dTest, Nearest) { + const Pose3d origin{0_m, 0_m, 0_m, Rotation3d{}}; + + // Distance sort + // poses are in order of closest to farthest away from the origin at + // various positions in 3D space. + const Pose3d pose1{1_m, 0_m, 0_m, Rotation3d{}}; + const Pose3d pose2{0_m, 2_m, 0_m, Rotation3d{}}; + const Pose3d pose3{0_m, 0_m, 3_m, Rotation3d{}}; + const Pose3d pose4{2_m, 2_m, 2_m, Rotation3d{}}; + const Pose3d pose5{3_m, 3_m, 3_m, Rotation3d{}}; + + EXPECT_DOUBLE_EQ(pose3.X().value(), + origin.Nearest({pose5, pose3, pose4}).X().value()); + EXPECT_DOUBLE_EQ(pose3.Y().value(), + origin.Nearest({pose5, pose3, pose4}).Y().value()); + EXPECT_DOUBLE_EQ(pose3.Z().value(), + origin.Nearest({pose5, pose3, pose4}).Z().value()); + + EXPECT_DOUBLE_EQ(pose1.X().value(), + origin.Nearest({pose1, pose2, pose3}).X().value()); + EXPECT_DOUBLE_EQ(pose1.Y().value(), + origin.Nearest({pose1, pose2, pose3}).Y().value()); + EXPECT_DOUBLE_EQ(pose1.Z().value(), + origin.Nearest({pose1, pose2, pose3}).Z().value()); + + EXPECT_DOUBLE_EQ(pose2.X().value(), + origin.Nearest({pose4, pose2, pose3}).X().value()); + EXPECT_DOUBLE_EQ(pose2.Y().value(), + origin.Nearest({pose4, pose2, pose3}).Y().value()); + EXPECT_DOUBLE_EQ(pose2.Z().value(), + origin.Nearest({pose4, pose2, pose3}).Z().value()); + + // Rotation component sort (when distance is the same) + // Use the same translation to avoid distance differences + const Translation3d translation{1_m, 0_m, 0_m}; + + const Pose3d poseA{translation, Rotation3d{}}; // No rotation + const Pose3d poseB{translation, Rotation3d{30_deg, 0_deg, 0_deg}}; + const Pose3d poseC{translation, Rotation3d{0_deg, 45_deg, 0_deg}}; + const Pose3d poseD{translation, Rotation3d{0_deg, 0_deg, 90_deg}}; + const Pose3d poseE{translation, Rotation3d{180_deg, 0_deg, 0_deg}}; + + auto result1 = + Pose3d{0_m, 0_m, 0_m, Rotation3d{}}.Nearest({poseA, poseB, poseD}); + EXPECT_DOUBLE_EQ(poseA.Rotation().X().value(), + result1.Rotation().X().value()); + EXPECT_DOUBLE_EQ(poseA.Rotation().Y().value(), + result1.Rotation().Y().value()); + EXPECT_DOUBLE_EQ(poseA.Rotation().Z().value(), + result1.Rotation().Z().value()); + + auto result2 = + Pose3d{0_m, 0_m, 0_m, Rotation3d{25_deg, 0_deg, 0_deg}}.Nearest( + {poseB, poseC, poseD}); + EXPECT_DOUBLE_EQ(poseB.Rotation().X().value(), + result2.Rotation().X().value()); + EXPECT_DOUBLE_EQ(poseB.Rotation().Y().value(), + result2.Rotation().Y().value()); + EXPECT_DOUBLE_EQ(poseB.Rotation().Z().value(), + result2.Rotation().Z().value()); + + auto result3 = + Pose3d{0_m, 0_m, 0_m, Rotation3d{0_deg, 50_deg, 0_deg}}.Nearest( + {poseB, poseC, poseD}); + EXPECT_DOUBLE_EQ(poseC.Rotation().X().value(), + result3.Rotation().X().value()); + EXPECT_DOUBLE_EQ(poseC.Rotation().Y().value(), + result3.Rotation().Y().value()); + EXPECT_DOUBLE_EQ(poseC.Rotation().Z().value(), + result3.Rotation().Z().value()); + + auto result4 = + Pose3d{0_m, 0_m, 0_m, Rotation3d{0_deg, 0_deg, 85_deg}}.Nearest( + {poseA, poseC, poseD}); + EXPECT_DOUBLE_EQ(poseD.Rotation().X().value(), + result4.Rotation().X().value()); + EXPECT_DOUBLE_EQ(poseD.Rotation().Y().value(), + result4.Rotation().Y().value()); + EXPECT_DOUBLE_EQ(poseD.Rotation().Z().value(), + result4.Rotation().Z().value()); + + auto result5 = + Pose3d{0_m, 0_m, 0_m, Rotation3d{170_deg, 0_deg, 0_deg}}.Nearest( + {poseA, poseD, poseE}); + EXPECT_DOUBLE_EQ(poseE.Rotation().X().value(), + result5.Rotation().X().value()); + EXPECT_DOUBLE_EQ(poseE.Rotation().Y().value(), + result5.Rotation().Y().value()); + EXPECT_DOUBLE_EQ(poseE.Rotation().Z().value(), + result5.Rotation().Z().value()); + + // Test with complex 3D rotations (combining roll, pitch, yaw) + const Pose3d complexPose1{translation, Rotation3d{45_deg, 30_deg, 60_deg}}; + const Pose3d complexPose2{translation, Rotation3d{90_deg, 45_deg, 90_deg}}; + const Pose3d complexPose3{translation, Rotation3d{10_deg, 15_deg, 20_deg}}; + + auto complexResult = + Pose3d{0_m, 0_m, 0_m, Rotation3d{5_deg, 10_deg, 15_deg}}.Nearest( + {complexPose1, complexPose2, complexPose3}); + EXPECT_DOUBLE_EQ(complexPose3.Rotation().X().value(), + complexResult.Rotation().X().value()); + EXPECT_DOUBLE_EQ(complexPose3.Rotation().Y().value(), + complexResult.Rotation().Y().value()); + EXPECT_DOUBLE_EQ(complexPose3.Rotation().Z().value(), + complexResult.Rotation().Z().value()); +} From c01e318370fec77f46fd6e4c780c3d835237c35e Mon Sep 17 00:00:00 2001 From: Michael Lesirge <100492377+MichaelLesirge@users.noreply.github.com> Date: Thu, 12 Jun 2025 22:14:00 -0700 Subject: [PATCH 3/4] [wpimath] Add Translation3d.nearest() (#8015) --- .../first/math/geometry/Translation3d.java | 13 ++++++++ .../main/native/include/frc/geometry/Pose3d.h | 3 ++ .../include/frc/geometry/Translation3d.h | 32 +++++++++++++++++++ .../math/geometry/Translation3dTest.java | 19 +++++++++++ .../native/cpp/geometry/Translation3dTest.cpp | 28 ++++++++++++++++ 5 files changed, 95 insertions(+) diff --git a/wpimath/src/main/java/edu/wpi/first/math/geometry/Translation3d.java b/wpimath/src/main/java/edu/wpi/first/math/geometry/Translation3d.java index efaf92a670..1aad1a892b 100644 --- a/wpimath/src/main/java/edu/wpi/first/math/geometry/Translation3d.java +++ b/wpimath/src/main/java/edu/wpi/first/math/geometry/Translation3d.java @@ -20,6 +20,9 @@ import edu.wpi.first.math.numbers.N3; import edu.wpi.first.units.measure.Distance; import edu.wpi.first.util.protobuf.ProtobufSerializable; import edu.wpi.first.util.struct.StructSerializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.Objects; /** @@ -296,6 +299,16 @@ public class Translation3d return new Translation3d(m_x / scalar, m_y / scalar, m_z / scalar); } + /** + * Returns the nearest Translation3d from a collection of translations. + * + * @param translations The collection of translations to find the nearest. + * @return The nearest Translation3d from the collection. + */ + public Translation3d nearest(List translations) { + return Collections.min(translations, Comparator.comparing(this::getDistance)); + } + @Override public String toString() { return String.format("Translation3d(X: %.2f, Y: %.2f, Z: %.2f)", m_x, m_y, m_z); diff --git a/wpimath/src/main/native/include/frc/geometry/Pose3d.h b/wpimath/src/main/native/include/frc/geometry/Pose3d.h index 743ccfbc04..e2ec7c4609 100644 --- a/wpimath/src/main/native/include/frc/geometry/Pose3d.h +++ b/wpimath/src/main/native/include/frc/geometry/Pose3d.h @@ -4,6 +4,9 @@ #pragma once +#include +#include +#include #include #include #include diff --git a/wpimath/src/main/native/include/frc/geometry/Translation3d.h b/wpimath/src/main/native/include/frc/geometry/Translation3d.h index 72e220f08c..b9876c05a3 100644 --- a/wpimath/src/main/native/include/frc/geometry/Translation3d.h +++ b/wpimath/src/main/native/include/frc/geometry/Translation3d.h @@ -4,6 +4,10 @@ #pragma once +#include +#include +#include + #include #include #include @@ -244,6 +248,34 @@ class WPILIB_DLLEXPORT Translation3d { units::math::abs(m_z - other.m_z) < 1E-9_m; } + /** + * Returns the nearest Translation3d from a collection of translations + * @param translations The collection of translations. + * @return The nearest Translation3d from the collection. + */ + constexpr Translation3d Nearest( + std::span translations) const { + return *std::min_element( + translations.begin(), translations.end(), + [this](const Translation3d& a, const Translation3d& b) { + return this->Distance(a) < this->Distance(b); + }); + } + + /** + * Returns the nearest Translation3d from a collection of translations + * @param translations The collection of translations. + * @return The nearest Translation3d from the collection. + */ + constexpr Translation3d Nearest( + std::initializer_list translations) const { + return *std::min_element( + translations.begin(), translations.end(), + [this](const Translation3d& a, const Translation3d& b) { + return this->Distance(a) < this->Distance(b); + }); + } + private: units::meter_t m_x = 0_m; units::meter_t m_y = 0_m; diff --git a/wpimath/src/test/java/edu/wpi/first/math/geometry/Translation3dTest.java b/wpimath/src/test/java/edu/wpi/first/math/geometry/Translation3dTest.java index 49b146fdd0..95a970ba6f 100644 --- a/wpimath/src/test/java/edu/wpi/first/math/geometry/Translation3dTest.java +++ b/wpimath/src/test/java/edu/wpi/first/math/geometry/Translation3dTest.java @@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import edu.wpi.first.math.VecBuilder; import edu.wpi.first.math.util.Units; +import java.util.List; import org.junit.jupiter.api.Test; class Translation3dTest { @@ -206,4 +207,22 @@ class Translation3dTest { assertEquals(vec, translation.toVector()); } + + @Test + void testNearest() { + var origin = Translation3d.kZero; + + // Distance sort + // translations are in order of closest to farthest away from the origin at various positions + // in 3D space. + final var translation1 = new Translation3d(1, 0, 0); + final var translation2 = new Translation3d(0, 2, 0); + final var translation3 = new Translation3d(0, 0, 3); + final var translation4 = new Translation3d(2, 2, 2); + final var translation5 = new Translation3d(3, 3, 3); + + assertEquals(translation3, origin.nearest(List.of(translation5, translation3, translation4))); + assertEquals(translation1, origin.nearest(List.of(translation1, translation2, translation3))); + assertEquals(translation2, origin.nearest(List.of(translation4, translation2, translation3))); + } } diff --git a/wpimath/src/test/native/cpp/geometry/Translation3dTest.cpp b/wpimath/src/test/native/cpp/geometry/Translation3dTest.cpp index f463c0bfa4..b297f1217c 100644 --- a/wpimath/src/test/native/cpp/geometry/Translation3dTest.cpp +++ b/wpimath/src/test/native/cpp/geometry/Translation3dTest.cpp @@ -186,3 +186,31 @@ TEST(Translation3dTest, Constexpr) { static_assert(projected.X() == 1_m); static_assert(projected.Y() == 2_m); } + +TEST(Translation3dTest, Nearest) { + const Translation3d origin{0_m, 0_m, 0_m}; + + // Distance sort + // translations are in order of closest to farthest away from the origin at + // various positions in 3D space. + const Translation3d translation1{1_m, 0_m, 0_m}; + const Translation3d translation2{0_m, 2_m, 0_m}; + const Translation3d translation3{0_m, 0_m, 3_m}; + const Translation3d translation4{2_m, 2_m, 2_m}; + const Translation3d translation5{3_m, 3_m, 3_m}; + + auto nearest1 = origin.Nearest({translation5, translation3, translation4}); + EXPECT_DOUBLE_EQ(nearest1.X().value(), translation3.X().value()); + EXPECT_DOUBLE_EQ(nearest1.Y().value(), translation3.Y().value()); + EXPECT_DOUBLE_EQ(nearest1.Z().value(), translation3.Z().value()); + + auto nearest2 = origin.Nearest({translation1, translation2, translation3}); + EXPECT_DOUBLE_EQ(nearest2.X().value(), translation1.X().value()); + EXPECT_DOUBLE_EQ(nearest2.Y().value(), translation1.Y().value()); + EXPECT_DOUBLE_EQ(nearest2.Z().value(), translation1.Z().value()); + + auto nearest3 = origin.Nearest({translation4, translation2, translation3}); + EXPECT_DOUBLE_EQ(nearest3.X().value(), translation2.X().value()); + EXPECT_DOUBLE_EQ(nearest3.Y().value(), translation2.Y().value()); + EXPECT_DOUBLE_EQ(nearest3.Z().value(), translation2.Z().value()); +} From 05c080328b07451b6a2d73bda6b10ccad34cc782 Mon Sep 17 00:00:00 2001 From: Michael Lesirge <100492377+MichaelLesirge@users.noreply.github.com> Date: Fri, 13 Jun 2025 18:50:05 -0700 Subject: [PATCH 4/4] [wpimath] Pass Translation2d by const reference instead of by value (#8021) --- .../include/frc/geometry/Translation2d.h | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/wpimath/src/main/native/include/frc/geometry/Translation2d.h b/wpimath/src/main/native/include/frc/geometry/Translation2d.h index aa6c4753ef..a859743988 100644 --- a/wpimath/src/main/native/include/frc/geometry/Translation2d.h +++ b/wpimath/src/main/native/include/frc/geometry/Translation2d.h @@ -238,10 +238,11 @@ class WPILIB_DLLEXPORT Translation2d { */ constexpr Translation2d Nearest( std::span translations) const { - return *std::min_element(translations.begin(), translations.end(), - [this](Translation2d a, Translation2d b) { - return this->Distance(a) < this->Distance(b); - }); + return *std::min_element( + translations.begin(), translations.end(), + [this](const Translation2d& a, const Translation2d& b) { + return this->Distance(a) < this->Distance(b); + }); } /** @@ -251,10 +252,11 @@ class WPILIB_DLLEXPORT Translation2d { */ constexpr Translation2d Nearest( std::initializer_list translations) const { - return *std::min_element(translations.begin(), translations.end(), - [this](Translation2d a, Translation2d b) { - return this->Distance(a) < this->Distance(b); - }); + return *std::min_element( + translations.begin(), translations.end(), + [this](const Translation2d& a, const Translation2d& b) { + return this->Distance(a) < this->Distance(b); + }); } private: