1919
2020import edu .wpi .first .networktables .NetworkTable ;
2121import edu .wpi .first .networktables .NetworkTableEntry ;
22+ import edu .wpi .first .networktables .NetworkTableEvent ;
2223import edu .wpi .first .networktables .NetworkTableInstance ;
2324import edu .wpi .first .networktables .NetworkTableType ;
25+ import edu .wpi .first .networktables .NetworkTableValue ;
2426import edu .wpi .first .wpilibj .DataLogManager ;
2527import edu .wpi .first .wpilibj .DriverStation ;
2628import edu .wpi .first .wpilibj .Preferences ;
2729import java .lang .reflect .*;
30+ import java .util .EnumSet ;
2831import java .util .HashMap ;
2932import java .util .Map ;
33+ import java .util .concurrent .atomic .AtomicBoolean ;
34+ import java .util .concurrent .atomic .AtomicInteger ;
35+ import java .util .concurrent .atomic .AtomicLong ;
36+ import java .util .concurrent .atomic .AtomicReference ;
3037import java .util .function .*;
3138
3239/**
@@ -218,8 +225,11 @@ private static <T extends Record> T fromPreferences(
218225 NetworkTableInstance ntInstance = Preferences .getNetworkTable ().getInstance ();
219226 verifyNotRegisteredToAnotherClass (ntInstance , preferenceName , recordClass );
220227
228+ ListenerRegistry listenerRegistry = new ListenerRegistry (ntInstance , preferenceName );
229+
221230 try {
222- return createFromPreferences (preferenceName , recordClass , configWithDefaults );
231+ return createFromPreferences (
232+ preferenceName , recordClass , configWithDefaults , listenerRegistry );
223233 } catch (ReflectiveOperationException e ) {
224234 if (throwExceptions ) {
225235 throw new RuntimeException (e ); // For self-tests.
@@ -277,11 +287,15 @@ private static void verifyNotRegisteredToAnotherClass(
277287 * @param prefix String to prepend to record field names to get the Preference key.
278288 * @param clazz Record class type.
279289 * @param configWithDefaults Default values to use if there are no values stored in NetworkTables.
290+ * @param listenerRegistry Registry that supports adding Preference value listeners.
280291 * @return An instance of the record class, populated with data from the Preferences table.
281292 * @throws ReflectiveOperationException If the fields of the class cannot be read via reflection.
282293 */
283294 private static <T > T createFromPreferences (
284- String prefix , Class <? extends T > clazz , T configWithDefaults )
295+ String prefix ,
296+ Class <? extends T > clazz ,
297+ T configWithDefaults ,
298+ ListenerRegistry listenerRegistry )
285299 throws ReflectiveOperationException {
286300 var components = clazz .getRecordComponents ();
287301 Object [] params = new Object [components .length ];
@@ -313,17 +327,24 @@ private static <T> T createFromPreferences(
313327 Field defaultValueField = clazz .getDeclaredField (name );
314328 defaultValueField .setAccessible (true );
315329 componentValue = defaultValueField .get (configWithDefaults );
330+ if (componentValue == null ) {
331+ warn ("Cannot get initial value for '%s'; value is null" , component .getName ());
332+ }
316333 }
317334
318335 if (isRecordField ) {
319- params [i ] = createFromPreferences (key , type , componentValue );
336+ params [i ] = createFromPreferences (key , type , componentValue , listenerRegistry );
320337 } else if (fetcher == null ) {
321338 warn ("Cannot store '%s' in Preferences; type %s is unsupported" , name , type );
322339 params [i ] = componentValue ;
323340 } else {
324341 params [i ] =
325342 fetcher .getValue (
326- component , key , componentValue , /* initializePreference= */ needComponentValue );
343+ component ,
344+ key ,
345+ componentValue ,
346+ /* initializePreference= */ needComponentValue ,
347+ listenerRegistry );
327348 }
328349 i ++;
329350 }
@@ -344,9 +365,15 @@ private interface GenericPreferenceFetcher<T> {
344365 * @param key The Preference key that should be used when initializing the Preference.
345366 * @param defaultValue The default value that should be used when initializing the Preference.
346367 * @param initializePreference Whether the preference should be initialized.
368+ * @param listenerRegistry Registry that supports adding Preference value listeners.
347369 * @return The value; will match the type in "component";
348370 */
349- T getValue (RecordComponent component , String key , T defaultValue , boolean initializePreference );
371+ T getValue (
372+ RecordComponent component ,
373+ String key ,
374+ T defaultValue ,
375+ boolean initializePreference ,
376+ ListenerRegistry listenerRegistry );
350377 }
351378
352379 /**
@@ -363,10 +390,15 @@ private interface PreferenceFetcher {
363390 * @param key The Preference key that should be used when initializing the Preference.
364391 * @param defaultValue The default value that should be used when initializing the Preference.
365392 * @param initializePreference Whether the preference should be initialized.
393+ * @param listenerRegistry Registry that supports adding Preference value listeners.
366394 * @return The value; will match the type in "component";
367395 */
368396 Object getValue (
369- RecordComponent component , String key , Object defaultValue , boolean initializePreference );
397+ RecordComponent component ,
398+ String key ,
399+ Object defaultValue ,
400+ boolean initializePreference ,
401+ ListenerRegistry listenerRegistry );
370402 }
371403
372404 private static final Map <Type , PreferenceFetcher > TYPE_TO_FETCHER = new HashMap <>();
@@ -380,8 +412,9 @@ Object getValue(
380412 @ SuppressWarnings ("unchecked" )
381413 private static <T > void register (Class <T > type , GenericPreferenceFetcher <T > simpleFetcher ) {
382414 PreferenceFetcher fetcher =
383- (component , key , defaultValue , initializePreference ) ->
384- simpleFetcher .getValue (component , key , (T ) defaultValue , initializePreference );
415+ (component , key , defaultValue , initializePreference , listenerConsumers ) ->
416+ simpleFetcher .getValue (
417+ component , key , (T ) defaultValue , initializePreference , listenerConsumers );
385418 TYPE_TO_FETCHER .put (type , fetcher );
386419 }
387420
@@ -400,7 +433,11 @@ private static <T> void register(Class<T> type, GenericPreferenceFetcher<T> simp
400433
401434 /** Gets a boolean value from Preferences for the given component. */
402435 private static boolean booleanFetcher (
403- RecordComponent component , String key , Boolean defaultValue , boolean initialize ) {
436+ RecordComponent component ,
437+ String key ,
438+ Boolean defaultValue ,
439+ boolean initialize ,
440+ ListenerRegistry listenerRegistry ) {
404441 if (initialize ) {
405442 if (defaultValue == null ) {
406443 defaultValue = Boolean .FALSE ;
@@ -416,20 +453,31 @@ private static BooleanSupplier booleanSupplierFetcher(
416453 RecordComponent component ,
417454 String key ,
418455 BooleanSupplier defaultValueSupplier ,
419- boolean initialize ) {
456+ boolean initialize ,
457+ ListenerRegistry listenerRegistry ) {
458+ boolean initialValue = false ;
420459 if (initialize ) {
421- boolean defaultValue = false ;
422460 if (defaultValueSupplier != null ) {
423- defaultValue = defaultValueSupplier .getAsBoolean ();
461+ initialValue = defaultValueSupplier .getAsBoolean ();
424462 }
425- Preferences .initBoolean (key , defaultValue );
463+ Preferences .initBoolean (key , initialValue );
464+ } else {
465+ initialValue = Preferences .getBoolean (key , false );
426466 }
427- return () -> Preferences .getBoolean (key , false );
467+
468+ AtomicBoolean lastestValue = new AtomicBoolean (initialValue );
469+ listenerRegistry .addListener (
470+ key , (NetworkTableValue tableValue ) -> lastestValue .set (tableValue .getBoolean ()));
471+ return lastestValue ::get ;
428472 }
429473
430474 /** Gets an int value from Preferences for the given component. */
431475 private static int intFetcher (
432- RecordComponent component , String key , Integer defaultValue , boolean initialize ) {
476+ RecordComponent component ,
477+ String key ,
478+ Integer defaultValue ,
479+ boolean initialize ,
480+ ListenerRegistry listenerRegistry ) {
433481 if (initialize ) {
434482 if (defaultValue == null ) {
435483 defaultValue = 0 ;
@@ -442,17 +490,39 @@ private static int intFetcher(
442490
443491 /** Gets a IntSupplier value from Preferences for the given component. */
444492 private static IntSupplier intSupplierFetcher (
445- RecordComponent component , String key , IntSupplier defaultValueSupplier , boolean initialize ) {
493+ RecordComponent component ,
494+ String key ,
495+ IntSupplier defaultValueSupplier ,
496+ boolean initialize ,
497+ ListenerRegistry listenerRegistry ) {
498+ int initialValue = 0 ;
446499 if (initialize ) {
447- int defaultValue = defaultValueSupplier != null ? defaultValueSupplier .getAsInt () : 0 ;
448- initIntegerPreference (key , defaultValue );
500+ if (defaultValueSupplier != null ) {
501+ initialValue = defaultValueSupplier .getAsInt ();
502+ }
503+ initIntegerPreference (key , initialValue );
504+ } else {
505+ initialValue = getIntegerPreference (key );
449506 }
450- return () -> getIntegerPreference (key );
507+
508+ AtomicInteger lastestValue = new AtomicInteger (initialValue );
509+ listenerRegistry .addListener (
510+ key ,
511+ (NetworkTableValue tableValue ) -> {
512+ int value =
513+ (int ) (tableValue .isInteger () ? tableValue .getInteger () : tableValue .getDouble ());
514+ lastestValue .set (value );
515+ });
516+ return lastestValue ::get ;
451517 }
452518
453519 /** Gets a long value from Preferences for the given component. */
454520 private static long longFetcher (
455- RecordComponent component , String key , Long defaultValue , boolean initialize ) {
521+ RecordComponent component ,
522+ String key ,
523+ Long defaultValue ,
524+ boolean initialize ,
525+ ListenerRegistry listenerRegistry ) {
456526 if (initialize ) {
457527 if (defaultValue == null ) {
458528 defaultValue = 0L ;
@@ -468,17 +538,31 @@ private static LongSupplier longSupplierFetcher(
468538 RecordComponent component ,
469539 String key ,
470540 LongSupplier defaultValueSupplier ,
471- boolean initialize ) {
541+ boolean initialize ,
542+ ListenerRegistry listenerRegistry ) {
543+ long initialValue = 0 ;
472544 if (initialize ) {
473- long defaultValue = defaultValueSupplier != null ? defaultValueSupplier .getAsLong () : 0 ;
474- Preferences .initLong (key , defaultValue );
545+ if (defaultValueSupplier != null ) {
546+ initialValue = defaultValueSupplier .getAsLong ();
547+ }
548+ Preferences .initLong (key , initialValue );
549+ } else {
550+ initialValue = Preferences .getLong (key , 0 );
475551 }
476- return () -> Preferences .getLong (key , 0 );
552+
553+ AtomicLong lastestValue = new AtomicLong (initialValue );
554+ listenerRegistry .addListener (
555+ key , (NetworkTableValue tableValue ) -> lastestValue .set (tableValue .getInteger ()));
556+ return lastestValue ::get ;
477557 }
478558
479559 /** Gets a double value from Preferences for the given component. */
480560 private static double doubleFetcher (
481- RecordComponent component , String key , Double defaultValue , boolean initialize ) {
561+ RecordComponent component ,
562+ String key ,
563+ Double defaultValue ,
564+ boolean initialize ,
565+ ListenerRegistry listenerRegistry ) {
482566 if (initialize ) {
483567 if (defaultValue == null ) {
484568 defaultValue = 0.0 ;
@@ -494,17 +578,31 @@ private static DoubleSupplier doubleSupplierFetcher(
494578 RecordComponent component ,
495579 String key ,
496580 DoubleSupplier defaultValueSupplier ,
497- boolean initialize ) {
581+ boolean initialize ,
582+ ListenerRegistry listenerRegistry ) {
583+ double initialValue = 0 ;
498584 if (initialize ) {
499- double defaultValue = defaultValueSupplier != null ? defaultValueSupplier .getAsDouble () : 0 ;
500- Preferences .initDouble (key , defaultValue );
585+ if (defaultValueSupplier != null ) {
586+ initialValue = defaultValueSupplier .getAsDouble ();
587+ }
588+ Preferences .initDouble (key , initialValue );
589+ } else {
590+ initialValue = Preferences .getDouble (key , 0 );
501591 }
502- return () -> Preferences .getDouble (key , 0 );
592+
593+ AtomicReference <Double > lastestValue = new AtomicReference <>(initialValue );
594+ listenerRegistry .addListener (
595+ key , (NetworkTableValue tableValue ) -> lastestValue .set (tableValue .getDouble ()));
596+ return lastestValue ::get ;
503597 }
504598
505599 /** Gets a String value from Preferences for the given component. */
506600 private static String stringFetcher (
507- RecordComponent component , String key , String defaultValue , boolean initialize ) {
601+ RecordComponent component ,
602+ String key ,
603+ String defaultValue ,
604+ boolean initialize ,
605+ ListenerRegistry listenerRegistry ) {
508606 if (initialize ) {
509607 if (defaultValue == null ) {
510608 defaultValue = "" ;
@@ -520,7 +618,8 @@ private static Supplier<String> supplierFetcher(
520618 RecordComponent component ,
521619 String key ,
522620 Supplier <String > defaultValueSupplier ,
523- boolean initialize ) {
621+ boolean initialize ,
622+ ListenerRegistry listenerRegistry ) {
524623 Type supplierType =
525624 ((ParameterizedType ) component .getGenericType ()).getActualTypeArguments ()[0 ];
526625 if (!String .class .equals (supplierType )) {
@@ -532,15 +631,25 @@ private static Supplier<String> supplierFetcher(
532631 return defaultValueSupplier ;
533632 }
534633
634+ String initialValue = "" ;
535635 if (initialize ) {
536- String defaultValue = defaultValueSupplier != null ? defaultValueSupplier .get () : "" ;
537- if (defaultValue == null ) {
538- defaultValue = "" ;
539- warn ("Cannot get initial value for '%s'; Supplier returned null" , component .getName ());
636+ if (defaultValueSupplier != null ) {
637+ String defaultValue = defaultValueSupplier .get ();
638+ if (defaultValue == null ) {
639+ warn ("Cannot get initial value for '%s'; Supplier returned null" , component .getName ());
640+ } else {
641+ initialValue = defaultValue ;
642+ }
540643 }
541- Preferences .initString (key , defaultValue );
644+ Preferences .initString (key , initialValue );
645+ } else {
646+ initialValue = Preferences .getString (key , "" );
542647 }
543- return () -> Preferences .getString (key , "" );
648+
649+ AtomicReference <String > lastestValue = new AtomicReference <>(initialValue );
650+ listenerRegistry .addListener (
651+ key , (NetworkTableValue tableValue ) -> lastestValue .set (tableValue .getString ()));
652+ return lastestValue ::get ;
544653 }
545654
546655 /** Deletes Preferences that were created by older versions of this class. */
@@ -608,4 +717,33 @@ private static void warn(String format, Object... args) {
608717 private PersistedConfiguration () {
609718 throw new AssertionError ("Not instantiable" );
610719 }
720+
721+ /** Registry that supports adding Preference value listeners. */
722+ private static class ListenerRegistry {
723+ private static final String PREFERENCE_TABLE_NAME = "Preferences" ;
724+ private final Map <Integer , Consumer <NetworkTableValue >> topicToConsumer = new HashMap <>();
725+ private final NetworkTable preferencesTable ;
726+
727+ ListenerRegistry (NetworkTableInstance ntInstance , String preferenceName ) {
728+ this .preferencesTable = ntInstance .getTable (PREFERENCE_TABLE_NAME );
729+ String prefix = this .preferencesTable .getPath () + "/" + preferenceName + "/" ;
730+
731+ ntInstance .addListener (
732+ new String [] {prefix },
733+ EnumSet .of (NetworkTableEvent .Kind .kValueAll ),
734+ event -> {
735+ if (event .valueData != null ) {
736+ var listener = topicToConsumer .get (event .valueData .topic );
737+ if (listener != null ) {
738+ listener .accept (event .valueData .value );
739+ }
740+ }
741+ });
742+ }
743+
744+ void addListener (String key , Consumer <NetworkTableValue > listener ) {
745+ int handle = preferencesTable .getTopic (key ).getHandle ();
746+ topicToConsumer .put (handle , listener );
747+ }
748+ }
611749}
0 commit comments