Skip to content

Commit dc8145f

Browse files
committed
Update PersistentConfiguration to use listeners for suppliers
1 parent 7a5035f commit dc8145f

2 files changed

Lines changed: 185 additions & 37 deletions

File tree

core/src/main/java/com/team2813/lib2813/preferences/PersistedConfiguration.java

Lines changed: 175 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,21 @@
1919

2020
import edu.wpi.first.networktables.NetworkTable;
2121
import edu.wpi.first.networktables.NetworkTableEntry;
22+
import edu.wpi.first.networktables.NetworkTableEvent;
2223
import edu.wpi.first.networktables.NetworkTableInstance;
2324
import edu.wpi.first.networktables.NetworkTableType;
25+
import edu.wpi.first.networktables.NetworkTableValue;
2426
import edu.wpi.first.wpilibj.DataLogManager;
2527
import edu.wpi.first.wpilibj.DriverStation;
2628
import edu.wpi.first.wpilibj.Preferences;
2729
import java.lang.reflect.*;
30+
import java.util.EnumSet;
2831
import java.util.HashMap;
2932
import 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;
3037
import 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

Comments
 (0)