From 4fa5dacfc4979c7bd22f8c21467e650b195021c8 Mon Sep 17 00:00:00 2001 From: Sam Carlberg Date: Fri, 29 May 2026 00:14:30 -0400 Subject: [PATCH] [epilogue] Improve support for robot base classes in epilogue (#8886) Generate an `update` method for any logged Robot class, not just TimedRobot Continue to generate a `bind` method for TimedRobot subclasses Also removes unnecessary import statements for generated loggers, since they're using fully-qualified names in the generated Epilogue class now. --- .../processor/AnnotationProcessor.java | 12 +- .../epilogue/processor/EpilogueGenerator.java | 131 ++++++++---------- .../processor/EpilogueGeneratorTest.java | 45 +++--- 3 files changed, 88 insertions(+), 100 deletions(-) diff --git a/epilogue-processor/src/main/java/org/wpilib/epilogue/processor/AnnotationProcessor.java b/epilogue-processor/src/main/java/org/wpilib/epilogue/processor/AnnotationProcessor.java index fb264967cb..843bfaf63e 100644 --- a/epilogue-processor/src/main/java/org/wpilib/epilogue/processor/AnnotationProcessor.java +++ b/epilogue-processor/src/main/java/org/wpilib/epilogue/processor/AnnotationProcessor.java @@ -382,9 +382,12 @@ public class AnnotationProcessor extends AbstractProcessor { List loggerClassNames = new ArrayList<>(); var mainRobotClasses = new ArrayList(); + var timedRobotClasses = new ArrayList(); // Used to check for a main robot class var robotBaseClass = + processingEnv.getElementUtils().getTypeElement("org.wpilib.framework.RobotBase").asType(); + var timedRobotClass = processingEnv.getElementUtils().getTypeElement("org.wpilib.framework.TimedRobot").asType(); boolean validFields = validateFields(annotatedElements); @@ -403,6 +406,9 @@ public class AnnotationProcessor extends AbstractProcessor { if (processingEnv.getTypeUtils().isAssignable(clazz.getSuperclass(), robotBaseClass)) { mainRobotClasses.add(clazz); } + if (processingEnv.getTypeUtils().isAssignable(clazz.getSuperclass(), timedRobotClass)) { + timedRobotClasses.add(clazz); + } loggerClassNames.add(StringUtils.loggerClassName(clazz)); } catch (IOException e) { @@ -417,8 +423,10 @@ public class AnnotationProcessor extends AbstractProcessor { } // Sort alphabetically - mainRobotClasses.sort(Comparator.comparing(c -> c.getSimpleName().toString())); - m_epilogueGenerator.writeEpilogueFile(loggerClassNames, mainRobotClasses); + loggerClassNames.sort(Comparator.naturalOrder()); + mainRobotClasses.sort(Comparator.comparing(TypeElement::toString)); + timedRobotClasses.sort(Comparator.comparing(TypeElement::toString)); + m_epilogueGenerator.writeEpilogueFile(loggerClassNames, mainRobotClasses, timedRobotClasses); } private void warnOfNonLoggableElements(TypeElement clazz) { diff --git a/epilogue-processor/src/main/java/org/wpilib/epilogue/processor/EpilogueGenerator.java b/epilogue-processor/src/main/java/org/wpilib/epilogue/processor/EpilogueGenerator.java index 3eb7b973a9..a66254bba2 100644 --- a/epilogue-processor/src/main/java/org/wpilib/epilogue/processor/EpilogueGenerator.java +++ b/epilogue-processor/src/main/java/org/wpilib/epilogue/processor/EpilogueGenerator.java @@ -38,12 +38,16 @@ public class EpilogueGenerator { * * @param loggerClassNames the names of the generated logger classes. Each of these will be * instantiated in a public static field on the Epilogue class. - * @param mainRobotClasses the main robot classes. May be empty. Used to generate a {@code bind()} - * method to add a callback hook to a TimedRobot to log itself. + * @param mainRobotClasses the main robot classes. May be empty. Used to generate a {@code + * update()} method for an easy entry point. + * @param timedRobotClasses the main robot classes that extend from TimedRobot. Used to generate a + * {@code bind()} method to add a callback hook to a TimedRobot to log itself. */ @SuppressWarnings("checkstyle:LineLength") // Source code templates exceed the line length limit public void writeEpilogueFile( - List loggerClassNames, Collection mainRobotClasses) { + List loggerClassNames, + Collection mainRobotClasses, + Collection timedRobotClasses) { try { var centralStore = m_processingEnv.getFiler().createSourceFile("org.wpilib.epilogue.Epilogue"); @@ -58,30 +62,6 @@ public class EpilogueGenerator { out.println("import org.wpilib.hardware.hal.HAL;"); out.println(); - loggerClassNames.stream() - .sorted() - .forEach( - name -> { - if (!name.contains(".")) { - // Logger is in the global namespace, don't need to import - return; - } - - out.println("import " + name + ";"); - }); - m_customLoggers.values().stream() - .distinct() - .forEach( - loggerType -> { - var name = loggerType.asElement().toString(); - if (!name.contains(".")) { - // Logger is in the global namespace, don't need to import - return; - } - out.println("import " + name + ";"); - }); - out.println(); - out.println("public final class Epilogue {"); // Usage reporting @@ -136,56 +116,57 @@ public class EpilogueGenerator { """ .stripTrailing()); - // Only generate a binding if the robot class is a TimedRobot - if (!mainRobotClasses.isEmpty()) { - for (TypeElement mainRobotClass : mainRobotClasses) { - String robotClassName = mainRobotClass.getQualifiedName().toString(); + for (TypeElement mainRobotClass : mainRobotClasses) { + String robotClassName = mainRobotClass.getQualifiedName().toString(); - out.println(); - out.print( - """ - /** - * Updates Epilogue. This must be called periodically in order for Epilogue to record - * new values. Alternatively, {@code bind()} can be used to update at an offset from - * the main robot loop. - */ - """); - out.println(" public static void update(" + robotClassName + " robot) {"); - out.println(" long start = System.nanoTime();"); - out.println( - " " - + StringUtils.loggerFieldName(mainRobotClass) - + ".tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);"); - out.println( - " config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);"); - out.println(" }"); + out.println(); + out.print( + """ + /** + * Updates Epilogue. This must be called periodically in order for Epilogue to record + * new values. Alternatively, {@code bind()} can be used to update at an offset from + * the main robot loop. + */ + """); + out.println(" public static void update(" + robotClassName + " robot) {"); + out.println(" long start = System.nanoTime();"); + out.println( + " " + + StringUtils.loggerFieldName(mainRobotClass) + + ".tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);"); + out.println( + " config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);"); + out.println(" }"); + } - out.println(); - out.print( - """ - /** - * Binds Epilogue updates to a timed robot's update period. Log calls will be made at the - * same update rate as the robot's loop function, but will be offset by a full phase - * (for example, a 20ms update rate but 10ms offset from the main loop invocation) to - * help avoid high CPU loads. However, this does mean that any logged data that reads - * directly from sensors will be slightly different from data used in the main robot - * loop. - */ - """); - out.println(" public static void bind(" + robotClassName + " robot) {"); - out.println(" if (config.loggingPeriod == null) {"); - out.println(" config.loggingPeriod = Seconds.of(robot.getPeriod());"); - out.println(" }"); - out.println(" if (config.loggingPeriodOffset == null) {"); - out.println(" config.loggingPeriodOffset = config.loggingPeriod.div(2);"); - out.println(" }"); - out.println(); - out.println(" robot.addPeriodic(() -> {"); - out.println(" update(robot);"); - out.println( - " }, config.loggingPeriod.in(Seconds), config.loggingPeriodOffset.in(Seconds));"); - out.println(" }"); - } + for (TypeElement timedRobotClass : timedRobotClasses) { + String robotClassName = timedRobotClass.getQualifiedName().toString(); + + out.println(); + out.print( + """ + /** + * Binds Epilogue updates to a timed robot's update period. Log calls will be made at the + * same update rate as the robot's loop function, but will be offset by a full phase + * (for example, a 20ms update rate but 10ms offset from the main loop invocation) to + * help avoid high CPU loads. However, this does mean that any logged data that reads + * directly from sensors will be slightly different from data used in the main robot + * loop. + */ + """); + out.println(" public static void bind(" + robotClassName + " robot) {"); + out.println(" if (config.loggingPeriod == null) {"); + out.println(" config.loggingPeriod = Seconds.of(robot.getPeriod());"); + out.println(" }"); + out.println(" if (config.loggingPeriodOffset == null) {"); + out.println(" config.loggingPeriodOffset = config.loggingPeriod.div(2);"); + out.println(" }"); + out.println(); + out.println(" robot.addPeriodic(() -> {"); + out.println(" update(robot);"); + out.println( + " }, config.loggingPeriod.in(Seconds), config.loggingPeriodOffset.in(Seconds));"); + out.println(" }"); } out.println("}"); diff --git a/epilogue-processor/src/test/java/org/wpilib/epilogue/processor/EpilogueGeneratorTest.java b/epilogue-processor/src/test/java/org/wpilib/epilogue/processor/EpilogueGeneratorTest.java index 3fe6ea3334..2c5911c11c 100644 --- a/epilogue-processor/src/test/java/org/wpilib/epilogue/processor/EpilogueGeneratorTest.java +++ b/epilogue-processor/src/test/java/org/wpilib/epilogue/processor/EpilogueGeneratorTest.java @@ -35,8 +35,6 @@ class EpilogueGeneratorTest { import org.wpilib.hardware.hal.HAL; - import org.wpilib.epilogue.ExampleLogger; - public final class Epilogue { static { HAL.reportUsage("Epilogue", ""); @@ -90,8 +88,6 @@ class EpilogueGeneratorTest { import org.wpilib.hardware.hal.HAL; - import org.wpilib.epilogue.ExampleLogger; - public final class Epilogue { static { HAL.reportUsage("Epilogue", ""); @@ -115,6 +111,17 @@ class EpilogueGeneratorTest { public static boolean shouldLog(Logged.Importance importance) { return importance.compareTo(config.minimumImportance) >= 0; } + + /** + * Updates Epilogue. This must be called periodically in order for Epilogue to record + * new values. Alternatively, {@code bind()} can be used to update at an offset from + * the main robot loop. + */ + public static void update(org.wpilib.epilogue.Example robot) { + long start = System.nanoTime(); + org_wpilib_epilogue_ExampleLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler); + config.backend.log("Epilogue/Stats/Last Run", (System.nanoTime() - start) / 1e6); + } } """; @@ -140,8 +147,6 @@ class EpilogueGeneratorTest { import org.wpilib.hardware.hal.HAL; - import org.wpilib.epilogue.ExampleLogger; - public final class Epilogue { static { HAL.reportUsage("Epilogue", ""); @@ -224,9 +229,6 @@ class EpilogueGeneratorTest { import org.wpilib.hardware.hal.HAL; - import org.wpilib.epilogue.AlphaBotLogger; - import org.wpilib.epilogue.BetaBotLogger; - public final class Epilogue { static { HAL.reportUsage("Epilogue", ""); @@ -263,6 +265,17 @@ class EpilogueGeneratorTest { config.backend.log("Epilogue/Stats/Last Run", (System.nanoTime() - start) / 1e6); } + /** + * Updates Epilogue. This must be called periodically in order for Epilogue to record + * new values. Alternatively, {@code bind()} can be used to update at an offset from + * the main robot loop. + */ + public static void update(org.wpilib.epilogue.BetaBot robot) { + long start = System.nanoTime(); + org_wpilib_epilogue_BetaBotLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler); + config.backend.log("Epilogue/Stats/Last Run", (System.nanoTime() - start) / 1e6); + } + /** * Binds Epilogue updates to a timed robot's update period. Log calls will be made at the * same update rate as the robot's loop function, but will be offset by a full phase @@ -284,17 +297,6 @@ class EpilogueGeneratorTest { }, config.loggingPeriod.in(Seconds), config.loggingPeriodOffset.in(Seconds)); } - /** - * Updates Epilogue. This must be called periodically in order for Epilogue to record - * new values. Alternatively, {@code bind()} can be used to update at an offset from - * the main robot loop. - */ - public static void update(org.wpilib.epilogue.BetaBot robot) { - long start = System.nanoTime(); - org_wpilib_epilogue_BetaBotLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler); - config.backend.log("Epilogue/Stats/Last Run", (System.nanoTime() - start) / 1e6); - } - /** * Binds Epilogue updates to a timed robot's update period. Log calls will be made at the * same update rate as the robot's loop function, but will be offset by a full phase @@ -357,9 +359,6 @@ class EpilogueGeneratorTest { import org.wpilib.hardware.hal.HAL; - import org.wpilib.epilogue.ExampleLogger; - import org.wpilib.epilogue.CustomLogger; - public final class Epilogue { static { HAL.reportUsage("Epilogue", "");