From 02c07393a9c9328bc017dae5f8edee2a7125e92b Mon Sep 17 00:00:00 2001 From: Kevin Cooney Date: Sat, 3 Jan 2026 18:27:17 -0800 Subject: [PATCH] Store int preferences as NetworkTableType.kInteger --- .../preferences/PersistedConfiguration.java | 89 ++++++++++-- .../PersistedConfigurationTest.java | 128 ++++++++++++------ 2 files changed, 165 insertions(+), 52 deletions(-) diff --git a/lib/src/main/java/com/team2813/lib2813/preferences/PersistedConfiguration.java b/lib/src/main/java/com/team2813/lib2813/preferences/PersistedConfiguration.java index 7df3ecca..5c4ab105 100644 --- a/lib/src/main/java/com/team2813/lib2813/preferences/PersistedConfiguration.java +++ b/lib/src/main/java/com/team2813/lib2813/preferences/PersistedConfiguration.java @@ -20,6 +20,7 @@ import edu.wpi.first.networktables.NetworkTable; import edu.wpi.first.networktables.NetworkTableEntry; import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.NetworkTableType; import edu.wpi.first.wpilibj.DataLogManager; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.Preferences; @@ -31,8 +32,8 @@ /** * Initializes the fields of a Record Class from values stored in {@link Preferences}. * - *

The Preference values can be updated in the Elastic. Updated values will be stored in the - * flash storage for the robot. + *

The Preference values can be updated in Elastic and other dashboards. Updated values will be + * stored in the flash storage for the robot. * *

Example use: * @@ -41,7 +42,7 @@ * * public record DriveConfiguration( * boolean addVisionMeasurements, long robotWeight, - * DoubleSupplier powerMultiplier) { + * double maxSpeed, String name) { * * public static DriveConfiguration fromPreferences() { * return PersistedConfiguration.fromPreferences("Drive", DriveConfiguration.class); @@ -56,7 +57,8 @@ *

* *

If no value is stored in Preferences for a key, the default value returned (and initialized in @@ -67,9 +69,30 @@ *

* + *

The record class could also contain suppliers: + * + *

{@code
+ * public final class Drive {
+ *
+ *   public record DriveConfiguration(
+ *       boolean addVisionMeasurements, LongSupplier robotWeight,
+ *       DoubleSupplier powerMultiplier) {
+ *
+ *     public static DriveConfiguration fromPreferences() {
+ *       return PersistedConfiguration.fromPreferences("Drive", DriveConfiguration.class);
+ *     }
+ *   }
+ * }
+ * }
+ * + *

In the above example, {@code fromPreferences()} would return a record instance that contained + * suppliers that, when queried, would return the current value in the "Preferences" NetworkTables + * table. + * *

The caller could specify different default values by passing an instance of the record class: * *

{@code
@@ -97,15 +120,17 @@
  *   
  • {@code "Drive/maxAngularVelocity"} (default value: {@code 3.14}) * * + *

    Note that {@code PersistedConfiguration} will use the default record constructor to create + * record instances, so any parameter validation should be done in a custom constructor; see Custom Constructor in Java + * Records for details. + * *

    For record classes with many component values of the same type, it is strongly recommended * that a builder is provided to construct the record, to avoid callers passing the parameters in * the wrong order. To make generation of a builder easier, consider using {@code @AutoBuilder} * from Google Auto or {@code @Builder} - * from Project Lombok. Note that {@code PersistedConfiguration} will use the default record - * constructor to create record instances, so any parameter validation should be done in a custom - * constructor; see Custom - * Constructor in Java Records for details. + * from Project Lombok. * * @since 2.0.0 */ @@ -164,7 +189,7 @@ public static T fromPreferences(String preferenceName, T conf * * *

    The default values for the preferences will be Java defaults (for example, zero for - * integers). + * integers, an empty string for strings, etc.). * * @param preferenceName Preference subtable to use to get the values. * @param recordClass Type of the record instance to populate from preferences. @@ -421,10 +446,10 @@ private static int intFactory( if (defaultValue == null) { defaultValue = 0; } - Preferences.initInt(key, defaultValue); + initIntegerPreference(key, defaultValue); return defaultValue; } - return Preferences.getInt(key, 0); + return getIntegerPreference(key); } /** Gets a IntSupplier value from Preferences for the given component. */ @@ -432,9 +457,9 @@ private static IntSupplier intSupplierFactory( RecordComponent component, String key, IntSupplier defaultValueSupplier, boolean initialize) { if (initialize) { int defaultValue = defaultValueSupplier != null ? defaultValueSupplier.getAsInt() : 0; - Preferences.initInt(key, defaultValue); + initIntegerPreference(key, defaultValue); } - return () -> Preferences.getInt(key, 0); + return () -> getIntegerPreference(key); } /** Gets a long value from Preferences for the given component. */ @@ -550,6 +575,42 @@ private static void deleteLegacyKeys() { } } + /** + * Puts the given int into the preferences table if it doesn't already exist. + * + *

    Unlike with {@link Preferences#initInt(String, int)}, the value is stored as an integer, not + * a double. + * + * @param key The key + * @param value The value + */ + private static void initIntegerPreference(String key, int value) { + NetworkTable table = Preferences.getNetworkTable(); + NetworkTableEntry entry = table.getEntry(key); + if (NetworkTableType.kDouble.equals(entry.getType())) { + // If we get here that should mean there is a value for this key, so the default value should + // be ignored. We update the default value just in case, if nothing else than to be + // consistent with the Preferences "init" methods. + entry.setDefaultDouble(value); + } else { + entry.setDefaultInteger(value); + } + entry.setPersistent(); + } + + /** + * Returns the value at the given key, as an int. If this table does not have a value for that + * position, then a value of zero will be returned. + * + * @param key the key + * @return either the value in the table, or zero + */ + private static int getIntegerPreference(String key) { + NetworkTable table = Preferences.getNetworkTable(); + NetworkTableEntry entry = table.getEntry(key); + return (int) entry.getInteger(0); + } + /** * Emits a warning, usually via {@link DataLogManager}. * diff --git a/lib/src/test/java/com/team2813/lib2813/preferences/PersistedConfigurationTest.java b/lib/src/test/java/com/team2813/lib2813/preferences/PersistedConfigurationTest.java index f54c32a8..b1ddebf2 100644 --- a/lib/src/test/java/com/team2813/lib2813/preferences/PersistedConfigurationTest.java +++ b/lib/src/test/java/com/team2813/lib2813/preferences/PersistedConfigurationTest.java @@ -79,6 +79,35 @@ protected enum ValuesKind { UPDATED_VALUES } + private NetworkTableEntry getTableEntry(String key, NetworkTableType expectedType) { + NetworkTableEntry entry = isolatedPreferences.getPreferencesTable().getEntry(key); + assertThat(entry.getType()).isEqualTo(expectedType); + return entry; + } + + protected final boolean getBooleanValue(String key, boolean defaultValue) { + return getTableEntry(key, NetworkTableType.kBoolean).getBoolean(defaultValue); + } + + protected final long getIntegerValue(String key) { + return getTableEntry(key, NetworkTableType.kInteger).getInteger(-1); + } + + protected final double getDoubleValue(String key) { + return getTableEntry(key, NetworkTableType.kDouble).getDouble(-1); + } + + protected final String getStringValue(String key) { + return getTableEntry(key, NetworkTableType.kString).getString("defaultValue"); + } + + protected final void setIntegerValue(String key, int value) { + NetworkTable table = isolatedPreferences.getPreferencesTable(); + NetworkTableEntry entry = table.getEntry(key); + entry.setInteger(value); + entry.setPersistent(); + } + protected final void assertHasNoChangesSince(Map previousValues) { var preferenceValues = preferenceValues(); assertWithMessage("Unexpected no changes to preference values") @@ -359,7 +388,7 @@ protected void assertPreferencesHaveConfiguredDefaults() { assertThat(preferenceKeys()) .containsExactly(BOOLEAN_VALUE_KEY, BOOLEAN_SUPPLIER_KEY, SUPPLIER_BOOLEAN_KEY); for (String key : ALL_KEYS) { - assertThat(Preferences.getBoolean(key, !defaultValue)).isEqualTo(defaultValue); + assertThat(getBooleanValue(key, !defaultValue)).isEqualTo(defaultValue); } } @@ -375,7 +404,7 @@ protected void assertPreferencesHaveJavaDefaults() { assertThat(preferenceKeys()) .containsExactly(BOOLEAN_VALUE_KEY, BOOLEAN_SUPPLIER_KEY, SUPPLIER_BOOLEAN_KEY); for (String key : ALL_KEYS) { - assertThat(Preferences.getBoolean(key, true)).isFalse(); + assertThat(getBooleanValue(key, true)).isFalse(); } } @@ -410,15 +439,23 @@ protected void assertSuppliersHaveUpdatedValues(RecordWithBooleans record) { } } + @RunWith(Parameterized.class) public static class IntPreferencesTest extends PreferencesRegistryTestCase { static final String PREFERENCE_NAME = "Integers"; static final String INT_VALUE_KEY = "Integers/intValue"; static final String INT_SUPPLIER_KEY = "Integers/intSupplier"; static final String SUPPLIER_INT_KEY = "Integers/supplierInt"; + final boolean storeAsDoubles; + + @Parameters(name = "storeAsDoubles={0}") + public static Object[] data() { + return new Object[] {true, false}; + } - public IntPreferencesTest() { + public IntPreferencesTest(boolean storeAsDoubles) { super(PREFERENCE_NAME, RecordWithInts.class); + this.storeAsDoubles = storeAsDoubles; } /** Test record for testing classes that contain int fields. */ @@ -447,9 +484,9 @@ protected void assertHasConfiguredDefaults(RecordWithInts record) { protected void assertPreferencesHaveConfiguredDefaults() { assertThat(preferenceKeys()) .containsExactly(INT_VALUE_KEY, INT_SUPPLIER_KEY, SUPPLIER_INT_KEY); - assertThat(Preferences.getInt(INT_VALUE_KEY, -1)).isEqualTo(1); - assertThat(Preferences.getInt(INT_SUPPLIER_KEY, -1)).isEqualTo(2); - assertThat(Preferences.getInt(SUPPLIER_INT_KEY, -1)).isEqualTo(3); + assertThat(getIntegerValue(INT_VALUE_KEY)).isEqualTo(1); + assertThat(getIntegerValue(INT_SUPPLIER_KEY)).isEqualTo(2); + assertThat(getIntegerValue(SUPPLIER_INT_KEY)).isEqualTo(3); } @Override @@ -463,23 +500,38 @@ protected void assertHasJavaDefaults(RecordWithInts record) { protected void assertPreferencesHaveJavaDefaults() { assertThat(preferenceKeys()) .containsExactly(INT_VALUE_KEY, INT_SUPPLIER_KEY, SUPPLIER_INT_KEY); - assertThat(Preferences.getInt(INT_VALUE_KEY, -1)).isEqualTo(0); - assertThat(Preferences.getInt(INT_SUPPLIER_KEY, -1)).isEqualTo(0); - assertThat(Preferences.getInt(SUPPLIER_INT_KEY, -1)).isEqualTo(0); + assertThat(getIntegerValue(INT_VALUE_KEY)).isEqualTo(0); + assertThat(getIntegerValue(INT_SUPPLIER_KEY)).isEqualTo(0); + assertThat(getIntegerValue(SUPPLIER_INT_KEY)).isEqualTo(0); } @Override protected void updatePreferenceValues(ValuesKind kind) { - switch (kind) { - case INITIAL_VALUES -> { - Preferences.setInt(INT_VALUE_KEY, 101); - Preferences.setInt(INT_SUPPLIER_KEY, 102); - Preferences.setInt(SUPPLIER_INT_KEY, 103); + if (storeAsDoubles) { + switch (kind) { + case INITIAL_VALUES -> { + Preferences.setDouble(INT_VALUE_KEY, 101); + Preferences.setDouble(INT_SUPPLIER_KEY, 102); + Preferences.setDouble(SUPPLIER_INT_KEY, 103); + } + case UPDATED_VALUES -> { + Preferences.setDouble(INT_VALUE_KEY, 201); + Preferences.setDouble(INT_SUPPLIER_KEY, 202); + Preferences.setDouble(SUPPLIER_INT_KEY, 203); + } } - case UPDATED_VALUES -> { - Preferences.setInt(INT_VALUE_KEY, 201); - Preferences.setInt(INT_SUPPLIER_KEY, 202); - Preferences.setInt(SUPPLIER_INT_KEY, 203); + } else { + switch (kind) { + case INITIAL_VALUES -> { + setIntegerValue(INT_VALUE_KEY, 101); + setIntegerValue(INT_SUPPLIER_KEY, 102); + setIntegerValue(SUPPLIER_INT_KEY, 103); + } + case UPDATED_VALUES -> { + setIntegerValue(INT_VALUE_KEY, 201); + setIntegerValue(INT_SUPPLIER_KEY, 202); + setIntegerValue(SUPPLIER_INT_KEY, 203); + } } } } @@ -544,9 +596,9 @@ protected void assertHasConfiguredDefaults(RecordWithLongs record) { protected void assertPreferencesHaveConfiguredDefaults() { assertThat(preferenceKeys()) .containsExactly(LONG_VALUE_KEY, LONG_SUPPLIER_KEY, SUPPLIER_LONG_KEY); - assertThat(Preferences.getLong(LONG_VALUE_KEY, -1)).isEqualTo(1); - assertThat(Preferences.getLong(LONG_SUPPLIER_KEY, -1)).isEqualTo(2); - assertThat(Preferences.getLong(SUPPLIER_LONG_KEY, -1)).isEqualTo(3); + assertThat(getIntegerValue(LONG_VALUE_KEY)).isEqualTo(1); + assertThat(getIntegerValue(LONG_SUPPLIER_KEY)).isEqualTo(2); + assertThat(getIntegerValue(SUPPLIER_LONG_KEY)).isEqualTo(3); } @Override @@ -560,9 +612,9 @@ protected void assertHasJavaDefaults(RecordWithLongs record) { protected void assertPreferencesHaveJavaDefaults() { assertThat(preferenceKeys()) .containsExactly(LONG_VALUE_KEY, LONG_SUPPLIER_KEY, SUPPLIER_LONG_KEY); - assertThat(Preferences.getLong(LONG_VALUE_KEY, -1)).isEqualTo(0); - assertThat(Preferences.getLong(LONG_SUPPLIER_KEY, -1)).isEqualTo(0); - assertThat(Preferences.getLong(SUPPLIER_LONG_KEY, -1)).isEqualTo(0); + assertThat(getIntegerValue(LONG_VALUE_KEY)).isEqualTo(0); + assertThat(getIntegerValue(LONG_SUPPLIER_KEY)).isEqualTo(0); + assertThat(getIntegerValue(SUPPLIER_LONG_KEY)).isEqualTo(0); } @Override @@ -642,9 +694,9 @@ protected void assertHasConfiguredDefaults(RecordWithDoubles record) { protected void assertPreferencesHaveConfiguredDefaults() { assertThat(preferenceKeys()) .containsExactly(DOUBLE_VALUE_KEY, DOUBLE_SUPPLIER_KEY, SUPPLIER_DOUBLE_KEY); - assertThat(Preferences.getDouble(DOUBLE_VALUE_KEY, -1)).isWithin(EPSILON).of(3.14159); - assertThat(Preferences.getDouble(DOUBLE_SUPPLIER_KEY, -1)).isWithin(EPSILON).of(2.71828); - assertThat(Preferences.getDouble(SUPPLIER_DOUBLE_KEY, -1)).isWithin(EPSILON).of(6.28318); + assertThat(getDoubleValue(DOUBLE_VALUE_KEY)).isWithin(EPSILON).of(3.14159); + assertThat(getDoubleValue(DOUBLE_SUPPLIER_KEY)).isWithin(EPSILON).of(2.71828); + assertThat(getDoubleValue(SUPPLIER_DOUBLE_KEY)).isWithin(EPSILON).of(6.28318); } @Override @@ -658,9 +710,9 @@ protected void assertHasJavaDefaults(RecordWithDoubles record) { protected void assertPreferencesHaveJavaDefaults() { assertThat(preferenceKeys()) .containsExactly(DOUBLE_VALUE_KEY, DOUBLE_SUPPLIER_KEY, SUPPLIER_DOUBLE_KEY); - assertThat(Preferences.getDouble(DOUBLE_VALUE_KEY, -1)).isWithin(EPSILON).of(0); - assertThat(Preferences.getDouble(DOUBLE_SUPPLIER_KEY, -1)).isWithin(EPSILON).of(0); - assertThat(Preferences.getDouble(SUPPLIER_DOUBLE_KEY, -1)).isWithin(EPSILON).of(0); + assertThat(getDoubleValue(DOUBLE_VALUE_KEY)).isWithin(EPSILON).of(0); + assertThat(getDoubleValue(DOUBLE_SUPPLIER_KEY)).isWithin(EPSILON).of(0); + assertThat(getDoubleValue(SUPPLIER_DOUBLE_KEY)).isWithin(EPSILON).of(0); } @Override @@ -733,8 +785,8 @@ protected void assertHasConfiguredDefaults(RecordWithStrings record) { @Override protected void assertPreferencesHaveConfiguredDefaults() { assertThat(preferenceKeys()).containsExactly(STRING_VALUE_KEY, SUPPLIER_STRING_KEY); - assertThat(Preferences.getString(STRING_VALUE_KEY, "")).isEqualTo("chicken"); - assertThat(Preferences.getString(SUPPLIER_STRING_KEY, "")).isEqualTo("bus"); + assertThat(getStringValue(STRING_VALUE_KEY)).isEqualTo("chicken"); + assertThat(getStringValue(SUPPLIER_STRING_KEY)).isEqualTo("bus"); } @Override @@ -746,8 +798,8 @@ protected void assertHasJavaDefaults(RecordWithStrings record) { @Override protected void assertPreferencesHaveJavaDefaults() { assertThat(preferenceKeys()).containsExactly(STRING_VALUE_KEY, SUPPLIER_STRING_KEY); - assertThat(Preferences.getString(STRING_VALUE_KEY, "default")).isEmpty(); - assertThat(Preferences.getString(SUPPLIER_STRING_KEY, "default")).isEmpty(); + assertThat(getStringValue(STRING_VALUE_KEY)).isEmpty(); + assertThat(getStringValue(SUPPLIER_STRING_KEY)).isEmpty(); } @Override @@ -814,8 +866,8 @@ protected void assertHasConfiguredDefaults(RecordWithRecords record) { @Override protected void assertPreferencesHaveConfiguredDefaults() { assertThat(preferenceKeys()).containsExactly(stringValueKey, longValueKey); - assertThat(Preferences.getString(stringValueKey, "")).isEqualTo("The Answer"); - assertThat(Preferences.getLong(longValueKey, -1)).isEqualTo(42); + assertThat(getStringValue(stringValueKey)).isEqualTo("The Answer"); + assertThat(getIntegerValue(longValueKey)).isEqualTo(42); } @Override @@ -827,8 +879,8 @@ protected void assertHasJavaDefaults(RecordWithRecords record) { @Override protected void assertPreferencesHaveJavaDefaults() { assertThat(preferenceKeys()).containsExactly(stringValueKey, longValueKey); - assertThat(Preferences.getString(stringValueKey, "default")).isEmpty(); - assertThat(Preferences.getLong(longValueKey, -1)).isEqualTo(0); + assertThat(getStringValue(stringValueKey)).isEmpty(); + assertThat(getIntegerValue(longValueKey)).isEqualTo(0); } @Override