-
Notifications
You must be signed in to change notification settings - Fork 609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add fluent builders for more flexibly adding data to Shuffleboard #1022
Add fluent builders for more flexibly adding data to Shuffleboard #1022
Conversation
@Override | ||
public void teleopPeriodic() { | ||
Shuffleboard.update(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to add this example to the examples.xml (actually by the time this merges I hope to have replaced that with the json equivalent). Also, these updates should be in robotPeriodic, not the individual periodic methods. robotPeriodic was specificially added for purposes like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll wait on the VSCode JSON stuff before merging.
I see the point of using robotPeriodic for consistency, but I do like specifying both autonomous and teleop periodic functions because those are what users will be using in their real programs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but I would worry about teams forgetting about it in test mode and wondering why its not running. robotPeriodic is specifically for things that should always be updated every loop. I've been trying to suggest it more to teams when helping for dashboard things. I'm actually debating adding it to the default project as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, cool. In that case, I'll definitely move it over
// Update sendables | ||
// Basic entries do not need to be updated by us; they are set by users via the | ||
// NetworkTableEntry instances they get when completing an add() operation | ||
m_sendables.forEach(data -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this allocate a lambda per call? Why not just a standard loop? Since this is called once per robot loop, we should avoid allocations as much as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a non-capturing lambda, so there's only one object allocated.
NetworkTable baseTable = getBaseTable(); | ||
// Update tabs | ||
NetworkTable tabsTable = baseTable.getSubTable(".tabs"); | ||
m_builders.stream() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question about the allocations here, but this one has a ton more. We should try and store these instead of having to reupdate every loop. And should definitely try and avoid some of the spurious allocations if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
b -> b.getTab().getName()
is non-capturing, so only one object is allocated.
The lambda in the It is now noncapturingforEach
is capturing, but could easily be rewritten to be noncapturing. And doing this stream in a conventional loop would be a bit nasty.
Can specify a layout to place the data and its properties Can specify the widget and its properties 100% unit test coverage
Uses resource ID 63
244dc58
to
25697bd
Compare
() -> assertThrows(NullPointerException.class, () -> add("foo", (NetworkTableType) null)), | ||
() -> assertThrows(IllegalArgumentException.class, () -> add("", kDouble)), | ||
() -> assertThrows(IllegalArgumentException.class, () -> add("a/b", kDouble))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One test per test case please. If you want them grouped use nested test cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, that's new. Would you recommend grouping entries vs sendables, or have something more finely grained?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make this a parameterized test called invalidInputsToAddEntryTest
. Parameters would be the expected exception, the key for the add method, and the object for the add method.
Add comments to shuffleboard example robot
I'd like to move away from the singleton model for classes like this (and SmartDashboard) because it makes code hard to test. Instead of having static methods in Shuffleboard.java, can we make this an object instead with 2 constructors? One constructor with no arguments would use the default nt table, the second one would take a nt table. |
It's a tradeoff for API simplicity, and to follow the style of SmartDashboard and LiveWindow to make it easier to transition to. |
I don't know if I agree with the API simplicity argument because the rest of our API uses objects rather than statics. |
SmartDashboard and LiveWindow both use statics, so that's what I'm going with here |
Those statics, I believe, just point to the default dynamically created instance of NetworkTables. And aren’t there non-static versions of the NetworkTables calls?
…On Jun 26, 2018, 12:37 PM -0400, Sam Carlberg ***@***.***>, wrote:
SmartDashboard and LiveWindow both use statics, so that's what I'm going with here
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
SmartDashboard only references the default NetworkTables instance. Shuffleboard can (and does) use new instances in tests |
private final ShuffleboardContainer m_container; | ||
private final Set<String> m_usedTitles = new HashSet<>(); | ||
private final List<ShuffleboardComponent<?>> m_components = new ArrayList<>(); | ||
private final Map<String, ShuffleboardLayout> m_layouts = new LinkedHashMap<>(); // NOPMD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove NOPMD when #1243 is merged
import edu.wpi.first.networktables.NetworkTableInstance; | ||
|
||
final class ShuffleboardInstance implements ShuffleboardRoot { | ||
private final Map<String, ShuffleboardTab> m_tabs = new LinkedHashMap<>(); // NOPMD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove NOPMD when #1243 is merged
* A helper class for Shuffleboard containers to handle common child operations. | ||
*/ | ||
final class ContainerHelper { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is wpiformat not catching this blank line @calcmogul?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a bug. I reproduced it locally, and it has to do with improper handling of comments (the comment contains the class keyword).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh... lol
…berg/allwpilib into shuffleboard-integration
Forces buildMetadata to be called on first initialization. Makes layouts work Add a test to help prevent regressions
So we do not overwrite existing data in NetworkTables when robot code starts
Rename `data` parameter to `defaultValue` on some methods for clarity
Add a test for that case
* Applies the function {@code func} to all complex widgets in {@code container}. Helper method | ||
* for {@link #applyToAllComplexWidgets}. | ||
*/ | ||
private void apply(ShuffleboardContainer container, Consumer<ComplexWidget> func) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be static?
Add shuffleboard C++ API
@PeterJohnson Any further review comments? |
Here's a potential fix for clang:
|
Overview
Motivation
A big question people have had about Shuffleboard (and, to an extent, SmartDashboard) is how to configure the dashboard from their robot code. Specifically, how to use something like
SmartDashboard.getX
without having to also remember to add a call toSmartDashboard.putX
at the beginning of the program.Unfortunately, simply using
SmartDashboard.getX
doesn't work without doing some legwork ahead of time without introducing a custom compile task to scan for those method calls - this would not be a worthwhile solution. A solution that could be done in the current version of WPILib, or with just minor tweaks, would to be to useNetworkTableEntry
instances for these variables; for example:An advantage to this method is that typos are eliminated; there's only one instance of the key string, and a typo made to the variable name would cause a compiler error. However, this still limits how data gets displayed in Shuffleboard; everything is still in the "Smartdashboard" tab.
Using the simple entry approach would fail for defining a Shuffleboard configuration. Users should not have to keep track of the names of the tab or layouts that contain the data they really want to read or update. It also doesn't address the issue of how the configuration should be sent to the dashboard.
Setup of the builders
The builders do two things: makes it easy to define where and how a piece of data is displayed or controlled, and gives a handle to a NetworkTableEntry for that data, when applicable (Sendables do not get entry handles, since they update and read their own tables). Widgets and layouts also have their properties configurable via the builders. For ease of use, whitespace and capitalization are ignored so users don't have to know the actual property names, which are not what gets displayed in the property editor dialogs in the dashboard.
Data, tab, and layout names are prohibited from containing forward slashes
/
; typically, these are used for defining subtables under the base SmartDashboard table. This is unnecessary in Shuffleboard, since these values can now be placed in specific tabs or layouts.Internally, the Shuffleboard class will handle specifying the configuration in NetworkTables for the NetworkTables plugin to read on the client side.
Adding an entry
Adding a sendable
The two builders look identical, except that the builder for entries has a terminal
getEntry()
function. This can be called at any point during the chain, but must be called in order to get a handle to theNetworkTableEntry
being added. A call toShuffleboard.update
after completing a builder is required to set up the configuration metadata (tab name, layout info, widget info, etc), as well as update sendables.Keeping variables around
Example use
Adding a slider to a tab named "Drivers Tab" to control the maximum speed of the drivetrain
Still to do
Waiting on
Map.of
for easily setting propertiesPossible feature improvements
Allow default values to be specified for entriesAddedAllow for nested layoutsAddedAllow for addingNot feasibleNetworkTableEntry
instances directly i.e.myTab.add(myEntry)
More documentation to base classes and interfaces.Specifying exact layout properties (tab's grid size, tile's position and size in the grid)Added