-
Notifications
You must be signed in to change notification settings - Fork 617
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
[wpiutil] Add new Telemetry implementation #7773
base: 2027
Are you sure you want to change the base?
Conversation
Imported from https://github.com/nielsbasjes/prefixmap with the following changes: - Removed serialization - Removed case-insensitive matching - Formatting to placate spotbugs and spotless
} else if (value instanceof Boolean) { | ||
log(name, ((Boolean) value).booleanValue()); |
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.
} else if (value instanceof Boolean) { | |
log(name, ((Boolean) value).booleanValue()); | |
} else if (value instanceof Boolean b) { | |
log(name, b.booleanValue()); |
I know this is still a draft, but I figured I might as well mention that this is an option.
(See 14.30 and 15.20.2 in https://docs.oracle.com/javase/specs/jls/se17/jls17.pdf if you want to see details)
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 don't see any locking to make sure this works from multiple threads.
I also don't understand why there are a lot of individual functions with things like "log" naming.
* | ||
* <p>For more advanced use cases, use the NetworkTables or DataLog APIs directly. | ||
*/ | ||
public final class Telemetry { |
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 make this final so teams can't subclass it?
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 would you like to subclass it? This is designed for end users to use as-is.
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 argue that everything should be subclass-able unless there is a a safety reason why not. It makes it easier for teams to extend and it is better practice for anyone providing a library.
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.
There's been discussion about this over at #3237. It goes over the position's of the maintainers far better than I could. If you still have questions, feel free to ask.
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.
In general, yes, letting users subclass library classes is better, there is no reason to subclass a utility class that only provides static methods and which cannot be instantiated (and thus any subclasses would not be able to subclass it). If you really don't want to type Telemetry.
in front of your method calls, you can use import static edu.wpi.first.wpilibj.Telemetry.log;
or import static edu.wpi.first.wpilibj.Telemetry.*;
.
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.
To clarify: Although it has the same name, the Telemetry
in this PR is not
analogous to the FTC Telemetry
class. The FTC Telemetry
class is most analogous to TelemetryTable
in this PR.
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.
@ThadHouse - I don't agree that having everything be static is easier to teach. Since the instance variable is part of OpMode that all opmodes must derive from, it isn't any more complicated for a student to write telemetry.log
than to write Telemetry.log
I am not sure what benefit we are getting by making a static utility class.
Perhaps we should consider renaming (at the least) as making it named the same as something people coming from FTC will know but works very differently seems like it is asking for confusion.
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.
If it was us writing it, that telemetry variable in the opmode would have been final, and wouldn't have allowed overwriting anyway. Overwriting that built in variable breaks a ton of encapsulation. So if the instance variable is final and not allowed to be changed, theres not much of a difference between static methods or instance methods.
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.
Re: naming, we need to pick names that make the most sense for the long term. We will have 1 or 2 years' worth of students who remember the "old" way and potentially be confused for a bit--but it's worse to live with a bad name for 5-10 years.
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.
@ThadHouse - I don't agree that having everything be static is easier to teach. Since the instance variable is part of OpMode that all opmodes must derive from, it isn't any more complicated for a student to write
telemetry.log
than to writeTelemetry.log
I am not sure what benefit we are getting by making a static utility class.
Perhaps we should consider renaming (at the least) as making it named the same as something people coming from FTC will know but works very differently seems like it is asking for confusion.
Hi current FRC and FTC student here, when I was learning programming inheritance was much more confusing than concepts like functions or even objects. While there may be some initial confusion coming from FTC this will last at most a few years before that lot of students graduates then this will be MUCH easier to learn and use.
*/ | ||
public final class Telemetry { | ||
/** The root {@link TelemetryTable}. */ | ||
private static final TelemetryTable m_root = new TelemetryTable("/"); |
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.
Suggest protected instead of private so subclasses can get to it.
What do you mean by this? Are you asking why everything is called |
I wasn't clear here. This is in reference to TelemetryBackEnd.java |
I haven't finished implementing the backends, but basically all the backends have type safety and type-specific functionality. So writing a double to a datalog file or NetworkTables is different than writing a string (or an integer). If you're asking why they are uniquely named instead of overloaded, overloading is bad practice with virtual functions (particularly in C++). A bunch of overloaded "log" functions on the user-facing side makes sense for ease of use, but is a potential footgun on the backend. |
It should be thread safe as written already. The backends will have locking/atomics included as needed (there are some synchronized blocks there already in the start of the DataLogSendableBackend implementation). We use ConcurrentMap etc to avoid explicit locking on the frontend (e.g. TelemetryTable caching uses ConcurrentMap), at least in Java--in C++, we'll use explicit mutexes. |
Significantly simplified version of #6453. The design approach here is to make Telemetry purely imperative/immediate and write only. Read capabilities would be added as a separate Tunable implementation.
Telemetry
is a utility class with only static functions to allow simple use such asTelemetry.log("name", value);
(alaSystem.out.println()
), and is intended as the primary user-facing class. Nested (structured) telemetry is available viaTelemetryTable
, instances of which can be gotten by callingTelemetry.getTable()
(orTelemetryTable.getTable()
for further nesting).The
Sendable
concept equivalent isTelemetryLoggable
(not a great name, but naming is hard). Unlike the currentSendable
/SmartDashboard.putData()
, this is designed to be immediate, not callback-based. Thelog(TelemetryTable)
function of aTelemetryLoggable
should immediately log the desired data to the providedTelemetryTable
and not store the table for later use.While we aim to fast-path most types through specific overloads, we do have a generic
Object
-taking overload to avoid potential overload ambiguity (particularly betweenStructSerializable
andProtobufSerializable
, where many types implement both) and provide a fallback toString path. There's a type registry mechanism to register specific type handlers; the intent here is to use this for things like Unit types, where you might want to both set a property (indicating the unit type) and provide the value as a double.Backends can be configured at any level; the most specific backend is used based on longest prefix match of the telemetry path (this allows for e.g. setting up NT logging at the top level, but making some tables DataLog-only). In the current implementation, backends must be configured before telemetry of matching entries starts (TelemetryTable caches entries, and this caching is not invalidated when a backend is added).
The initial backend implementation takes the type of the first
log()
call as the "forever" type for that particular name. Trying to later log to the same name with a different type is ignored (TBD: probably should emit a warning). Changing types dynamically both significantly increases implementation complexity and will likely result in difficult-to-debug behavior in downstream tooling; it's hard to see the user benefits to supporting this.TODO: