Compare commits

...

202 Commits

Author SHA1 Message Date
Peter Johnson
80c391e182 [wpinet] WebServer: Unescape URI (#7552)
Also provide Content-Disposition filename header in response.

This fixes e.g. filenames with spaces in them.
2024-12-15 12:28:08 -08:00
Joseph Eng
70f36cce7e [commands] Extract common trigger binding logic (#7550)
This makes the logic clearer in the actual binding methods and will hopefully make it less annoying to make changes such as allowing control over initial edges.

Also changes Java to use previous and current to match C++.
2024-12-14 23:13:41 -08:00
Peter Johnson
564c1f2de2 [wpinet] WebServer: Fix Windows (#7551)
The order of evaluation of parameters is not defined; on Windows,
the std::move was executed before the GetBuffer().
2024-12-14 23:07:48 -08:00
Peter Johnson
a1b642a402 [wpinet] Add simple web server (#7527)
Also add EscapeHTML to HttpUtil.
2024-12-14 11:51:21 -08:00
Falon
f9b3efb712 [wpiunits] Refactor MomentOfInertiaUnit constructor to use MomentOfInertiaUnit instead of a Per<> (#7546) 2024-12-14 11:26:26 -08:00
Thad House
782459dff4 [wpiutil] Make runtime loader exception message slightly better (#7536) 2024-12-14 11:25:27 -08:00
Peter Johnson
4bca79b9af [wpimath] Revert "ChassisSpeeds fromRelative and discretize methods made instance methods (#7115)" (#7549)
This reverts commit a3b12b3bd9.
2024-12-14 10:42:49 -08:00
Daniel Chen
68285dae77 [commands] Add withDeadline modifier (#7299)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2024-12-13 18:30:02 -07:00
Daniel Chen
5e3dba672a [wpiutil] Record/Enum struct generation fix (#7538)
ProceduralStructGenerator's genRecord and genEnum were package-private, and only extractClassStruct was made public.
However, this package private visibility rendered them unable to be used by the rest of wpilib(and advanced users).

Here, ProceduralStructGenerator is split into 2 classes: StructGenerator(which generates structs) and StructFetcher(the new namespace for extractClassStruct). In addition, genRecord and genEnum have been made public methods.
2024-12-13 12:25:38 -07:00
Ryan Blue
e943424609 [build] Update shadow plugin (#7540) 2024-12-12 19:19:14 -08:00
Joseph Eng
4225b732fd Remove unnecessary boxing (#7539)
* Remove unnecessary boxing
Also remove unnecessary warning suppression

* Use more idiomatic functional interfaces in NumericalIntegration
2024-12-12 19:18:40 -08:00
Carl Hauser
39d05ebe7c [rtns] Capture exitCode from ssh_channel_get_exit_status (#7541) 2024-12-12 19:18:02 -08:00
Wispy
cc41a0ed24 [wpilib] Replace MecanumDriveMotorVoltages with a functional interface (#6760) 2024-12-08 17:05:06 -08:00
Tyler Veness
d5edb4060d [upstream_utils] Upgrade Sleipnir (#7512)
It now uses SQP for problems without inequality constraints, which is
faster.

main:
```
[ RUN      ] Ellipse2dTest.DistanceToPoint
0.203 ms
[       OK ] Ellipse2dTest.DistanceToPoint (0 ms)
[ RUN      ] Ellipse2dTest.FindNearestPoint
0.019 ms
[       OK ] Ellipse2dTest.FindNearestPoint (0 ms)
```

upgrade:
```
[ RUN      ] Ellipse2dTest.DistanceToPoint
0.197 ms
[       OK ] Ellipse2dTest.DistanceToPoint (0 ms)
[ RUN      ] Ellipse2dTest.FindNearestPoint
0.015 ms
[       OK ] Ellipse2dTest.FindNearestPoint (0 ms)
```
2024-12-07 23:02:39 -08:00
Tyler Veness
e08fdeba21 [wpimath] Rename FindNearestPoint() to Nearest() (#7513)
This is easier to type and follows the naming of Pose2d::Nearest().

Since Ellipse2d and Rectangle2d were added for the 2025 season, we don't
need to add deprecation notices.
2024-12-07 23:01:18 -08:00
Wispy
544553a58f [developerRobot] Add Epilogue and wpiunits to developerRobot (#7510) 2024-12-07 23:00:49 -08:00
Peter Johnson
838c5fbcd7 [ci] Fix labeler yaml (#7523) 2024-12-07 21:37:49 -08:00
Peter Johnson
38a239b531 [ci] Add labeler action to auto-label PRs (#7520) 2024-12-07 21:15:49 -08:00
Thad House
9a1b4245fa [build] Restore Windows constexpr mutex define on wpiutil (#7515) 2024-12-07 18:55:06 -08:00
Tyler Veness
e222efaa01 [wpimath] Add affine transformation constructors and getters to geometry API (#7430)
Fixes #7429.
2024-12-07 15:49:17 -08:00
shueja
f772bb141d [epilogue] Add extra parentheses around cast when using varhandle (#7428)
* [epilogue] Add extra parentheses around cast when using varhandle

* Fixed tests and added one for private suppliers

* Fix tests

* Formatting fixes

* Update epilogue-processor/src/test/java/edu/wpi/first/epilogue/processor/AnnotationProcessorTest.java

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
Co-authored-by: Sam Carlberg <sam@slfc.dev>
2024-12-07 16:09:35 -05:00
Tyler Veness
278efa6384 [upstream_utils] Remove Sleipnir patches no longer needed with GCC 11 (#7491) 2024-12-07 12:36:25 -08:00
Tyler Veness
e876452967 [ci] Upgrade to wpiformat 2024.50 (#7506) 2024-12-07 11:40:24 -08:00
Sam Carlberg
5c95966d44 Fix epilogue loading optional types from the newcommands vendordep (#7505) 2024-12-07 13:56:05 -05:00
Tyler Veness
882233bede [wpimath] Improve SimpleMotorFeedforward argument names and deprecation message (#7489)
Also roll back SimpleMotorFeedforward unit API until 2027.
2024-12-06 22:32:40 -08:00
Peter Johnson
1921d019b7 [build] Update arm builds to bookworm (#7501)
Also bumps native-utils to 2025.9.0.
And upgrades OpenCV to -3; fixes some enum conversion deprecation warnings.
2024-12-06 22:19:00 -08:00
Peter Johnson
c3fc7c829d [build] Upgrade OpenCV to 4.10.0 (#7500) 2024-12-06 15:41:40 -08:00
Ryan Blue
4ce8930342 [developerRobot] Add instructions for developerRobot java sim (NFC) (#7498) 2024-12-06 00:24:26 -08:00
Daniel Chen
60198c0bec [epilogue] Improved opt-in logging support (#7437) 2024-12-03 20:30:55 -08:00
Gold856
54f0e11fc0 [build] Update OpenCV to 2025-4.8.0-2 (#7476) 2024-12-03 20:30:03 -08:00
sciencewhiz
de82ed434d [hal] Add usage reporting for TOF sensors (#7470) 2024-12-01 17:02:20 -08:00
Thad House
4dd3a36d2e [ci] Switch lint task to use base ubuntu image (#7471) 2024-12-01 17:01:19 -08:00
sciencewhiz
92f8c89267 [hal] Add usage reporting for 2025 legal motor controllers (#7467) 2024-11-30 22:25:04 -08:00
Thad House
1eecaaf337 [build] Update OpenCV to 2025 (#7468) 2024-11-30 22:21:41 -08:00
sciencewhiz
892e062316 [hal,wpilib,wpimath] Add Usage Reporting for Choreo and PathWeaver (#7464) 2024-11-30 20:33:09 -08:00
sciencewhiz
9807d60566 [hal,wpilib] Change Power Distribution usage reporting to Instances (#7465)
LabVIEW doesn't appear to report PDP. This should reduce the number of
Unknowns in the parsed UsageReporting.
2024-11-30 20:32:21 -08:00
Thad House
c387a7ecae [build] Update to 2025 compilers (#7462) 2024-11-30 10:07:53 -08:00
Peter Johnson
715cbb6b76 [sim] GUI: Update FMS widget when real DS is connected (#7456) 2024-11-30 09:49:09 -08:00
Ryan Blue
9d40b993f8 [wpiutil] Fix HasNestedStruct docs (NFC) (#7459) 2024-11-30 07:44:19 -08:00
Sam Carlberg
92ee5bc523 [epilogue] Add usage reporting (#7461) 2024-11-30 09:50:34 -05:00
Peter Johnson
5012ad7499 [epilogue] Fix missed EpilogueBackend renames (#7458) 2024-11-30 00:34:41 -08:00
Peter Johnson
145450b73d [ci] Disable processing of 2027 tags (#7457) 2024-11-29 23:51:27 -08:00
Nicholas Armstrong
b91864a5ec [wpilib] Fix acceleration getter for DCMotorSim (#7449) 2024-11-29 22:15:00 -08:00
sciencewhiz
0941251375 [wpilib] Add usage reporting for loggers (#7450) 2024-11-29 22:13:31 -08:00
Sam Carlberg
7d178615fa [epilogue] Allow custom loggers for generic types (#7452)
Support custom loggers for generic types
Improve error messaging for custom loggers with generic type arguments
Consistently start all epilogue processor prints with "[EPILOGUE]"
2024-11-29 22:11:46 -08:00
Sam Carlberg
806d56e564 [epilogue] Rename DataLogger to EpilogueBackend for clarity (#7453)
Update documentation and internal names correspondingly
2024-11-29 22:10:51 -08:00
Tyler Veness
65f3345407 [upstream_utils] Suppress protobuf warnings on GCC 12 too (#7451) 2024-11-29 18:19:59 -08:00
Sam Carlberg
5e1c6a84ce [wpilibj, wpilibc] Fix LED patterns not offsetting reads (#6948)
Was causing bugs when combined with patterns that need to read back from the buffer (eg masks and overlays)

Co-authored-by: Joseph Eng <s-engjo@bsd405.org>
2024-11-28 21:25:54 -08:00
Tyler Veness
a0af0fd572 [wpimath] Remove redundant internal DARE function (#7442) 2024-11-28 21:24:13 -08:00
Kavin Muralikrishnan
f377a9c573 [examples] Fix SysId example references to shooter subsystem (#7392) 2024-11-27 23:04:53 -08:00
Peter Johnson
62338c7287 [sim] Fix DS GUI System Joysticks window auto-hiding (#7431) 2024-11-27 23:03:55 -08:00
sciencewhiz
49e3e4a0be [wpiunits] Fix deprecation javadoc for units negate (#7436)
deprecated javadoc tags aren't inherited
2024-11-27 23:03:23 -08:00
Joseph Eng
59dbfc9c2d [wpimath] Improve C++ SimpleMotorFeedforward unit type support (#7440)
Allow using non-base types
Allow using angles for serde
2024-11-27 23:02:31 -08:00
Sam Carlberg
9607c6c10d [epilogue] Fix epilogue generating incorrect packages for inner classes (#7439) 2024-11-27 23:02:00 -08:00
sciencewhiz
6ef5b85758 [wpiunits] Restore and deprecate divide (#7438)
It was changed to div in #7387, but 2024 used divide.
2024-11-27 23:01:26 -08:00
Peter Johnson
b6de7acbdb [sim] GUI: Don't show Window menu if it has no contents (#7432) 2024-11-25 17:25:55 -08:00
Thad House
fe28fa1ded [wpilibj] Fix ADIS16470 Gyro (#7434) 2024-11-25 17:23:32 -08:00
Tyler Veness
b7eb9fb8f9 [upstream_utils] Add std::is_debugger_present() shim (#7423) 2024-11-22 09:17:23 -08:00
oh-yes-0-fps
1d58c5025e [wpilibj] Add procedural struct generator for enums and records (#7149) 2024-11-21 20:48:11 -08:00
Thad House
b4a8d33486 [ntcoreffi] Use static runtime for ntcoreffi (#7422)
This avoids requiring users of this library to keep up to date with the latest MSVC runtimes.
2024-11-21 16:14:12 -07:00
sciencewhiz
0a3ccf93c6 [wpimath] Add BangBangController Usage Reporting (#7411) 2024-11-20 17:00:54 -08:00
Matt
d92f17b014 [apriltag] Fix AprilTagDetector cols/stride mixup (#7415) 2024-11-20 17:00:08 -08:00
Gold856
4c225ef2c1 [wpimath] Add proto files back to JAR (#7414)
PhotonVision depends on these being in the JAR to generate quickbuf classes.
2024-11-20 07:59:37 -08:00
Thad House
561078ce29 [hal] Cache sim TCP data to update during HAL_RefreshDSData (#7360) 2024-11-18 20:56:32 -08:00
sciencewhiz
d312bccfeb [hal] Add Swerve Instances for RobotDrive usage reporting (#7410) 2024-11-18 20:51:50 -08:00
David Vo
9826539198 [hal] Add MagicBot framework to usage reporting (#7330)
Co-authored-by: sciencewhiz <sciencewhiz@users.noreply.github.com>
2024-11-18 20:51:16 -08:00
sciencewhiz
33f7067216 [hal] Add usage reporting for Rust (#7409)
Co-authored-by: itsmeft24 <57544858+itsmeft24@users.noreply.github.com>
2024-11-18 20:25:33 -08:00
Vignesh Balasubramaniam
ac1836ec44 [wpiunits] Add absolute value and copy sign functionality (#7358) 2024-11-18 17:46:15 -08:00
sciencewhiz
57e10755fd [wpilib] Add usage reporting for dashboards as instances (#7294)
Detects dashboards based on network tables client identity.
2024-11-18 10:16:29 -07:00
Tyler Veness
8ec22b7d5c [wpiutil] Remove unfinished ct_map class (#7406) 2024-11-17 21:31:25 -08:00
Tyler Veness
a04c40f589 Replace std::make_pair with std::pair CTAD (#7405) 2024-11-17 20:29:23 -08:00
Jade
b4bec566f0 [github] Fix templates not labelling (#7400) 2024-11-17 20:28:39 -08:00
sciencewhiz
d76827db48 [build] Update to Gradle 8.11 (#7402) 2024-11-17 20:28:12 -08:00
Tyler Veness
602c4caa02 [upstream_utils] Check patch files are up to date (#7401) 2024-11-17 20:27:53 -08:00
Peter Johnson
661c321fe2 [upstream_utils] Restore Eigen intellisense fix (#7404)
This reverts commit d1de7663d3.

Intellisense is still broken on Windows Athena target with the error
`incomplete type "Eigen::Matrix<double, 3, 3, 0, 3, 3>" is not allowed`.
2024-11-17 19:11:25 -08:00
Tyler Veness
d1de7663d3 [upstream_utils] Remove Eigen intellisense fix (#7397)
FRC Code intellisense continued to work in developerRobot with a QR
decomposition.
2024-11-16 22:16:11 -08:00
Peter Johnson
b040059108 [ntcore] Properly clean up time sync listeners (#7398) 2024-11-16 22:15:40 -08:00
sciencewhiz
0798ac53d0 [build] Generate NI Style UsageReporting header (#7331)
based on wplibsuite/ni-libraries src/include/FRC_NetworkCommunication/UsageReporting.h
2024-11-16 07:58:25 -08:00
Sam Carlberg
ded7c87d63 [wpimath] Remove units from trapezoid profile classes (#7276) 2024-11-16 07:57:38 -08:00
Joseph Eng
2acf111f56 [wpimath] Add 3D odometry and pose estimation (#7119) 2024-11-16 07:56:14 -08:00
Tyler Veness
aa7dd258c4 [wpimath] Replace constexpr coeff() and coeffRef() with operator() (#7391) 2024-11-16 07:44:20 -08:00
Jonah Bonner
ca51197486 [wpilib] Add timestamp getters with configurable time base (#7378) 2024-11-16 07:43:38 -08:00
Gold856
91142ba5fe [build] Remove Windows constexpr mutex define (#7375)
Since we ship a newer runtime, and we also have checks to ensure a valid runtime, we can remove this again.
2024-11-16 07:27:46 -08:00
Thad House
969664ceaa [wpiutil] Faster nanopb submessage encode (#7374)
Due to how submessages are encoded (with a length prefix), nanopb currently does the encoding twice. It encodes once to get the length to write, then writes the length, then reencodes the entire message a 2nd time.

This results in a requirement that each encode always encodes the same. Generally, this is fine, but it'd be nice to not make this a requirement.

The double encode also requires going through the entire set of fields again, which has the possibility to be slow.

Instead of doing this, write to a temporary SmallVector. Then we can just encode the length of that buffer, and do a memcpy into primary stream. Theoretically in most cases, this should be much faster.
2024-11-16 07:26:10 -08:00
Kavin Muralikrishnan
1e545c38a8 [romi] Update Romi for parity with XRP (#7389) 2024-11-16 07:24:58 -08:00
Kavin Muralikrishnan
6eb652e10e [xrp] Copy XRPReference docs from Java to C++ (NFC) (#7388) 2024-11-16 07:24:21 -08:00
Thad House
fff73ee6e1 [ci] Add basic Android build to CI (#7390) 2024-11-16 07:23:32 -08:00
Tim Winters
bade0a8716 [wpiunits] Use div instead of divide for kotlin compatibility (#7387) 2024-11-15 11:49:40 -07:00
Thad House
453335e354 [build] Remove java requirement from cmake (#7395)
It was leftover from the failed protoc stuff
2024-11-15 07:45:20 -07:00
Thad House
c289562a06 Allow compilation on android (#7386) 2024-11-12 16:39:41 -08:00
Kavin Muralikrishnan
07345712dc [wpimath] Document default tolerances of PIDController (#7377) 2024-11-12 18:19:55 -05:00
Joseph Eng
425bf83036 [wpimath] Add 2D to 3D geometry constructors (#7380) 2024-11-12 18:18:37 -05:00
David Vo
6adad7bad7 [wpiutil] Replace StringBuffer usage with StringBuilder (#7376)
This is a local variable that doesn't escape the method, so there's certainly no reason to have synchronization here.
2024-11-10 07:43:43 -08:00
Kavin Muralikrishnan
280d2c7e32 [examples] Update C++ XRP Code to use SI Units (#7366) 2024-11-08 20:24:13 -08:00
Thad House
edc3963955 [wpinet] Fix resolver thread on newer versions of macOS (#7372)
Implicit capture of this is deprecated.
2024-11-08 20:23:17 -08:00
Tyler Veness
c58be2580d [ntcore] Suppress warning false positive (#7370)
```
In copy constructor ‘std::function<_Res(_ArgTypes ...)>::function(const std::function<_Res(_ArgTypes ...)>&) [with _Res = void; _ArgTypes = {unsigned int}]’,
    inlined from ‘nt::server::ServerClient4Base::ServerClient4Base(std::string_view, std::string_view, bool, nt::server::SetPeriodicFunc, nt::server::ServerStorage&, int, wpi::Logger&)’ at /home/tav/frc/wpilib/allwpilib/ntcore/src/main/native/cpp/server/ServerClient4Base.h:24:77,
    inlined from ‘nt::server::ServerClientLocal::ServerClientLocal(nt::server::ServerStorage&, int, wpi::Logger&)’ at /home/tav/frc/wpilib/allwpilib/ntcore/src/main/native/cpp/server/ServerClientLocal.cpp:18:75:
/usr/include/c++/14.2.1/bits/std_function.h:391:17: error: ‘<anonymous>’ may be used uninitialized [-Werror=maybe-uninitialized]
  391 |             __x._M_manager(_M_functor, __x._M_functor, __clone_functor);
      |             ~~~~^~~~~~~~~~
/usr/include/c++/14.2.1/bits/std_function.h: In constructor ‘nt::server::ServerClientLocal::ServerClientLocal(nt::server::ServerStorage&, int, wpi::Logger&)’:
/usr/include/c++/14.2.1/bits/std_function.h:267:7: note: by argument 2 of type ‘const std::_Any_data&’ to ‘static bool std::_Function_handler<_Res(_ArgTypes ...), _Functor>::_M_manager(std::_Any_data&, const std::_Any_data&, std::_Manager_operation) [with _Res = void; _Functor = nt::server::ServerClientLocal::ServerClientLocal(nt::server::ServerStorage&, int, wpi::Logger&)::<lambda(uint32_t)>; _ArgTypes = {unsigned int}]’ declared here
  267 |       _M_manager(_Any_data& __dest, const _Any_data& __source,
      |       ^~~~~~~~~~
/home/tav/frc/wpilib/allwpilib/ntcore/src/main/native/cpp/server/ServerClientLocal.cpp:18:75: note: ‘<anonymous>’ declared here
   18 |     : ServerClient4Base{"", "", true, [](uint32_t) {}, storage, id, logger} {
      |                                                                           ^
```
2024-11-08 20:22:47 -08:00
Peter Johnson
f40bd3593d [wpilib,wpimath] Don't use mutable units for return values (#7369)
It only saves a single allocation and can cause confusing behavior on the
caller (user) side.
2024-11-08 18:29:51 -08:00
Thad House
3cc541f261 Remove generated google protobuf support (#7371)
It's not used anymore, and cleans up the build.
2024-11-08 18:29:30 -08:00
Tyler Veness
811b130968 [docs] Link to Bazel build docs from main readme (#7367)
I had a really hard to finding them because the main readme didn't call
it out.
2024-11-08 14:05:50 -08:00
Thad House
d39dfd64ea [build] Fix cmake build with WITH_PROTOBUF off (#7368)
Protobuf.cpp only uses nanopb, which is fine to have even with WITH_PROTOBUF off.
2024-11-08 14:05:29 -08:00
Tyler Veness
661bae568f [wpimath] Add time-varying RKDP (#7362)
This makes the ground truth for the Taylor series AQ discretization more
accurate.
2024-11-07 23:46:52 -08:00
Peter Johnson
01f85abcfe [ntcore] Use Endian.h in WireEncoder3 2024-11-08 00:46:27 -07:00
Peter Johnson
396f8203ac [ntcore] HandleMap: Use concepts for T 2024-11-08 00:46:27 -07:00
Peter Johnson
4a43ddbacf [ntcore] Split LocalStorage implementation into separate files 2024-11-08 00:46:27 -07:00
Peter Johnson
0921054a28 [ntcore] Split ServerImpl implementation into separate files 2024-11-08 00:46:27 -07:00
Peter Johnson
f738fc92f0 [ntcore] Move ServerImpl to nt::server namespace 2024-11-08 00:46:27 -07:00
Peter Johnson
a0f38f83f9 [ntcore] NetworkOutgoingQueue: Move function defs inside class 2024-11-08 00:46:27 -07:00
Peter Johnson
876be30724 [ntcore] Value: Inline constructors 2024-11-08 00:46:27 -07:00
Peter Johnson
de318fab91 [ntcore] LocalStorage: Move template functions inside class definition 2024-11-08 00:46:27 -07:00
Thad House
8b8b634f65 [wpiutil] Change C++ protobuf to nanopb (#7309)
The Google C++ protobuf implementation has issues with dynamic linkage across DLL boundaries because it uses global variables.  It also has a compile-time dependency because the protoc version must exactly match the libprotobuf version.  Using nanopb with a customized generator fixes both of these issues.

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2024-11-07 22:42:50 -08:00
Thad House
fd2e0c0427 [ntcoreffi] Switch ntcoreffi DLM to WPI_String (#7359)
We forgot to do this originally.
2024-11-07 15:19:37 -07:00
Tyler Veness
a66fa339dc [wpimath] Make controllers and some trajectory classes constexpr (#7343) 2024-11-07 14:02:11 -07:00
Kavin Muralikrishnan
44a45d44e2 [xrp] Update XRP C++ Method/Class Descriptions for Java Parity (#7351)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2024-11-06 16:12:16 -07:00
Ryan Blue
af652817d9 [commands] Revert "WrappedCommand: Call wrapped command initSendable (#6471)" (#7353)
This reverts commit 7bc0380694.

Calling initSendable on the wrapped command is fundamentally flawed as the wrapper is the command being sent.
2024-11-06 16:10:37 -07:00
Ryan Blue
5a16b0e108 [wpilib] Refactor Alert (#7279)
This refactors Alert in both c++ and java to fix the issues with the current c++ implementation and improve performance.

Currently, constructing an Alert adds it to a list of Alerts with the same group and type. Activating an alert sets a flag on the alert. When the SendableAlerts is polled (GetStrings), the entire list is iterated over, filtered, and the filtered list is sorted by timestamp. This leads to a worst case O(m + nlog(n)) time complexity for GetStrings, where m and n are the count of total constructed alerts active alerts respectively. It also allocates intermediate data structures to hold the active alert strings for sorting.

This changes the implementation to improve the performance of GetStrings, by shifting the sorting overhead to Alert.Set
Constructing the Alert only initializes the alert's initial state, and initializes the SendableAlerts for the group if it is not already initialized.
Activating or deactivating an alert sets an internal flag for state tracking, and inserts or removes a structure containing the timestamp and text into a self-sorting data structure (std::set, TreeSet) containing other active alerts with the same group and type. (worst case O(log(n))
Now, SendableAlerts.GetStrings only has to iterate over the structure and copy the strings to the returned array. (amortized O(n))

This also fixes the c++ implementation by removing the need for SendableAlerts to directly access the Alert.

This also adds a helper method to SendableRegistry to force initialization of the instance to prevent static initialization ordering issues.
2024-11-06 16:09:06 -07:00
sciencewhiz
71c050389a [wpiunits] Restore and deprecate measure negate (#7345)
Delegate to unaryMinus.

This ensures there's a deprecation message to help users with migration, vs just a compile error.
2024-11-06 16:07:31 -07:00
Ryan Blue
83fa422338 [wpiutil] DynamicStruct: Fix decoding of signed integers (#7350)
Add tests for both C++ and Java.
2024-11-05 17:45:49 -07:00
Tyler Veness
3113627be6 [wpimath] Fix PIDController error tolerance getters (#7341) 2024-11-05 09:52:22 -07:00
Thad House
f2d2500d1d [wpiutil] Check MSVC Runtime (#7301) 2024-11-05 09:51:48 -07:00
Tyler Veness
63e623d70b [wpimath] Fix case and order of HolonomicDriveController PID getters (#7342) 2024-11-05 09:50:17 -07:00
Tyler Veness
9e8d37c03b [wpimath] Remove unhelpful test fixtures (#7344)
These test fixtures were adding complexity while only saving one line of
object initialization per test. Our other tests like this just make the
object at the top of each test.
2024-11-05 09:49:38 -07:00
truher
ad09d73dd6 [ntcore] Replace "ValueListenerPoller" with "NetworkTableListenerPoller" in docs (#7328) 2024-11-05 09:49:03 -07:00
Ryan Blue
3fd33b1f72 [wpiutil] DynamicStruct: Clear nested fields when updating/adding a schema (#7334) 2024-11-05 09:48:24 -07:00
Jade
043c155087 Fix a few clangd warnings (#7335) 2024-11-05 09:47:54 -07:00
Ryan Blue
7a6c7af412 [wpiutil] Fix dynamic struct decoding for nested structs (#7346)
After a struct-type field descriptor had offsets calculated more than once, IsBitField would return true, causing the second call to CalculateOffsets to calculate incorrect offsets.
2024-11-05 06:43:04 -07:00
Ryan Blue
8588b5e520 [glass] Revert "Storage: Store Value by value" (#7333)
This reverts commit 309b370223.

Storage::GetChildArray never initializes the child array when the entry is new, so it returns invalid references.
2024-11-04 23:34:18 -07:00
Jade
debb52156c [wpilib] Clamp sim battery voltage to 0 (#7325) 2024-11-03 10:37:44 -08:00
Kavin Muralikrishnan
23e71e10e4 [xrp] Add getter for XRP LED (#7327) 2024-11-03 10:32:03 -08:00
Sam Carlberg
44c0bbc4a9 [epilogue] Generate unique names for variables used in instanceof chains (#7323)
Fixes an issue where the variable names would clash with the field names of the associated VarHandles.
2024-11-02 19:11:22 -07:00
Tyler Veness
a48f3c35f4 Remove argv usage from Python scripts (#7311)
argparse will automatically read sys.argv, so we don't need to pass it
in manually. Furthermore, none of our scripts customize argv.
2024-11-02 19:09:32 -07:00
Tyler Veness
7c91b81906 [ci] Upgrade to wpiformat 2024.45 (#7326) 2024-11-02 17:56:55 -07:00
Jade
eb8583596c [ci] Remove parts of Bazel CI (#7324)
We were building huge amounts with bazel we were already building
otherwise. We've been getting heavily backlogged in CI because of the amount
of CI jobs we are running versus our maximum runners quota (particularly on Mac), so this really isn't worth it right now.
2024-11-02 07:05:24 -07:00
Tyler Veness
dfd1084526 [wpimath] Replace pi with symbol in docs (#7322) 2024-11-01 17:16:18 -07:00
Peter Johnson
2cfe114c78 [wpiutil] DynamicStruct: Store StructDescriptor by value 2024-10-31 22:04:13 -07:00
Peter Johnson
caae5357b7 [wpilibc] Mechanism2d: Store roots by value 2024-10-31 22:04:13 -07:00
Peter Johnson
22e91bfacd [wpilibc] ShuffleboardInstance: Store ShuffleboardTab by value 2024-10-31 22:04:13 -07:00
Peter Johnson
67e1b5fe95 [sim] halsim_ws_core: Store SimDeviceValueData by value 2024-10-31 22:04:13 -07:00
Peter Johnson
27e07d6787 [ntcore] ClientImpl3: Store Entry by value 2024-10-31 22:04:13 -07:00
Peter Johnson
e49d452e46 [glass] Context: Store storageRoots by value 2024-10-31 22:04:13 -07:00
Peter Johnson
5c0edc2410 [glass] Field2D: Store field objects by value 2024-10-31 22:04:13 -07:00
Peter Johnson
309b370223 [glass] Storage: Store Value by value
This is possible because std::map has stable value pointers.
2024-10-31 22:04:13 -07:00
Peter Johnson
f620141e0d [wpiutil] Replace LLVM StringMap impl with std::map
As string_view operations on std::map<std::string> won't be integrated
until C++26, placeholder implementations are used which are less efficient
in a couple of situations (e.g. insert with hint).
2024-10-31 22:04:13 -07:00
Ryan Blue
5f3cf517d3 [commands] C++: Allow CommandPtrs to be owned by the scheduler (#7310)
Previously users would have to keep track of dynamically created CommandPtrs. This adds an ownership-taking version of schedule which places the command in a temporary store in the scheduler. The command will be freed when the command's lifecycle ends.
2024-10-31 22:03:12 -07:00
Tyler Veness
328a781040 [wpimath] Port Rotation2d Java tests to C++ (#7318) 2024-10-31 20:40:14 -07:00
Thad House
ebf83e4340 [ntcoreffi] Add ntcoreffi headers zip (#7229) 2024-10-31 20:39:56 -07:00
Tyler Veness
9f6f267f5c [wpimath] DARE: Use wpi::expected instead of exceptions (#7312) 2024-10-31 20:37:57 -07:00
Tyler Veness
21980c7447 [sysid] Clamp feedback measurement delay to zero or higher (#7319)
LQR latency compensation applies exponential decay to the feedback
gains, so a negative latency causes them to exponentially grow.
2024-10-31 20:37:15 -07:00
David Vo
75fc4d18ef [bazel] Fix test attempting to upload to readonly cache (#7302) 2024-10-31 20:36:58 -07:00
Ryan Blue
8f81b7723d [ci] Disable caching for setup-go (#7320)
setup-go is warning because it can't find go.sum, which we don't have.
2024-10-31 20:36:35 -07:00
Sam Carlberg
fe45265a3a [epilogue] Log elements based on the most specific available logger (#7317)
Instead of only logging based on the declared type, loggers will be smarter and do instanceof checks to determine what logger should be used.

Note that diamond inheritance may cause unexpected behavior for non-logged classes that implement multiple logged interfaces.
2024-10-31 20:34:41 -07:00
Sam Carlberg
0f313c672f [epilogue] Autogenerate nicer data names by default, not just raw element names (#7167)
eg "getFoo()" will now be logged as "Foo", or "m_leftMotor" as "Left Motor"

It is now a compilation error to reuse the same logged name for multiple elements (since whatever is declared last would overwrite anything logged before it)

Do not log record fields (just use the accessors). This also fixes an issue where records could never be logged due to identical member and accessor names

Also skips toString, hashCode, and clone methods when generating loggers
2024-10-31 20:34:00 -07:00
Thad House
aaf139320e [upstream_utils] Fix protobuf GNUC_MINOR macro (#7316) 2024-10-30 22:42:24 -07:00
Ryan Blue
89c5d98fe9 [build] Use pathlib in wpiunits generation script (#7306)
Makes wpiunits more consistent with the other generation scripts.

Also sort imports, remove args from main.
2024-10-29 23:05:48 -07:00
Tyler Veness
defcc02806 [wpimath] Clean up LTV controller includes (#7313) 2024-10-29 23:03:17 -07:00
Ryan Blue
e6e928d670 [ci] Increase keychain timeout to 6 hours (#7314) 2024-10-29 23:02:47 -07:00
Ryan Blue
f03e0cdf6a [wpilib] Add explanation for HID GetBumper/Touchpad deprecations (#7304) 2024-10-28 19:29:42 -07:00
Ryan Blue
412c042c6c Use LF in generated files (#7305) 2024-10-28 19:28:58 -07:00
Ryan Blue
f44c3eda43 [ci] Update actions to python 3.12 (#7308) 2024-10-28 18:09:35 -07:00
Ryan Blue
018dcaea4f [ci] Check return code of subprocesses in pregen_all (#7307)
Previously errors were ignored.

Also makes the script more cross-platform by using the current python executable to run the subprocesses.
2024-10-28 18:07:30 -07:00
Jade
85ffb7814b [build] Update Gradle to 8.10.2 (#7164) 2024-10-28 00:44:30 -07:00
Brendan Raykoff
6207992709 [wpilibj] RobotController: Add Java unit support (#7278) 2024-10-27 23:41:15 -07:00
Tyler Veness
42a433b6fa [sysid] Remove unused includes and inline short functions (#7296) 2024-10-27 23:40:26 -07:00
sciencewhiz
2c857cd82a [ntcore] Use NT3 client identity in front of unique id (#7293)
This way all NT3 clients are not identified as just NT3.
2024-10-26 18:56:04 -07:00
Gold856
1c220ebc60 [build] CMake: add Doxygen doc generation (#7286) 2024-10-25 10:45:53 -07:00
Jade
1cfed736ce [build] CMake: Make Protobuf dependency actually optional (#7291) 2024-10-25 08:52:38 -07:00
Ryan Blue
46b5631ba7 [docs] Fix wpiutil thirdparty include roots in docs (#7288) 2024-10-25 06:52:14 -07:00
Jade
d2b19d8928 [docs] Add WITH_PROTOBUF to README-CMAKE (#7289) 2024-10-25 06:51:35 -07:00
Joseph Eng
9a5f73d787 [cscore] Add back VideoProperty handle member initializer (#7283) 2024-10-23 21:46:20 -07:00
Tyler Veness
db552317e7 [dlt, rtns] Fix libssh deprecation warnings (#7284)
```
/home/tav/frc/wpilib/allwpilib/roborioteamnumbersetter/src/main/native/cpp/SshSession.cpp: In member function ‘void rtns::SshSession::Execute(std::string_view)’:
/home/tav/frc/wpilib/allwpilib/roborioteamnumbersetter/src/main/native/cpp/SshSession.cpp:89:44: warning: ‘int ssh_channel_get_exit_status(ssh_channel)’ is deprecated [-Wdeprecated-declarations]
   89 |   INFO("{} {}", ssh_channel_get_exit_status(channel), cmd);
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
```
```
/home/tav/frc/wpilib/allwpilib/roborioteamnumbersetter/src/main/native/cpp/SshSession.cpp:139:44: warning: ‘int ssh_channel_get_exit_status(ssh_channel)’ is deprecated [-Wdeprecated-declarations]
  139 |   INFO("{} {}", ssh_channel_get_exit_status(channel), cmd);
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
```
```
/home/tav/frc/wpilib/allwpilib/roborioteamnumbersetter/src/main/native/cpp/SshSession.cpp:143:46: warning: ‘int ssh_channel_get_exit_status(ssh_channel)’ is deprecated [-Wdeprecated-declarations]
  143 |     *exitStatus = ssh_channel_get_exit_status(channel);
      |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
```
```
/home/tav/frc/wpilib/allwpilib/datalogtool/src/main/native/cpp/Sftp.cpp:79:33: warning: ‘int sftp_async_read_begin(sftp_file, uint32_t)’ is deprecated [-Wdeprecated-declarations]
   79 |   int rv = sftp_async_read_begin(m_handle, len);
      |            ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
```
```
/home/tav/frc/wpilib/allwpilib/datalogtool/src/main/native/cpp/Sftp.cpp: In member function ‘size_t sftp::File::AsyncRead(void*, uint32_t, AsyncId)’:
/home/tav/frc/wpilib/allwpilib/datalogtool/src/main/native/cpp/Sftp.cpp:87:28: warning: ‘int sftp_async_read(sftp_file, void*, uint32_t, uint32_t)’ is deprecated [-Wdeprecated-declarations]
   87 |   auto rv = sftp_async_read(m_handle, data, len, id);
      |             ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
```
2024-10-23 20:34:54 -06:00
Ryan Blue
03cb3c70b4 [epilogue] Set source and target java versions for epilogue-processor tests (#7282) 2024-10-23 17:17:19 -06:00
Nicholas Armstrong
ac907f755a [wpiunits] Add resistance units (#7168) 2024-10-23 08:20:17 -06:00
Nicholas Armstrong
a3b12b3bd9 [wpimath] ChassisSpeeds fromRelative and discretize methods made instance methods (#7115) 2024-10-22 17:23:17 -06:00
Tyler Veness
cbc9264468 [ci] Upgrade to wpiformat 2024.44 (#7256)
This makes the C identifier list scanner correctly handle single quotes
in numeric literals.
2024-10-22 17:19:12 -06:00
Jade
28ac2e3554 Update version in DevelopmentBuilds (#7077) 2024-10-22 09:18:37 -06:00
Jade
58c0cd46b1 [build] Remove cmake toolchains (#7254)
They aren't used and should instead be taken from https://github.com/wpilibsuite/opensdk
2024-10-22 08:36:42 -06:00
Peter Johnson
115a02211c [cscore] HttpCamera: Auto-detect mode from stream if not set (#7248) 2024-10-22 08:35:05 -06:00
Peter Johnson
f553dee6cb [cscore] HttpCamera: Send width/height/fps stream settings (#7247) 2024-10-22 08:33:23 -06:00
Peter Johnson
e8d2d1c39a [wpinet] HttpRequest: Keep params ordered (#7246) 2024-10-22 08:32:41 -06:00
Peter Johnson
7cc7fa1845 [cscore] Fix wakeup on sink destruction (#7245) 2024-10-22 08:02:20 -06:00
Peter Johnson
cbdb4e81f6 [cscore] GetNextFrame: Wake up even if no frames received (#7244) 2024-10-22 08:01:41 -06:00
Tyler Veness
05c7fd929b [wpimath] Make various classes constexpr (#7237) 2024-10-22 07:58:06 -06:00
Tyler Veness
0c824bd447 [wpimath] Take finite difference stencil points by const ref (#7238) 2024-10-21 22:33:56 -07:00
Tyler Veness
ed18b41198 [upstream_utils] Add Eigen geometry module (#7242)
WPICal needs it.
2024-10-21 22:29:35 -07:00
Joseph Eng
6745fc7c2f [wpimath] Fix C++ pose estimator poseEstimate initialization (#7249) 2024-10-21 22:29:04 -07:00
Gold856
40af8db28a [wpilibc] DataLogManager: Rename console log entry to "console" (#7240)
This matches Java.
2024-10-21 06:34:29 -06:00
Tyler Veness
5ac132f6a2 [wpiutil] Make circular buffer classes constexpr (#7232)
The circular buffer class that uses std::vector internally can be used
in a constant expression as long as it doesn't survive to runtime.
2024-10-20 15:33:40 -07:00
Tyler Veness
dd72a78aa4 [upstream_utils] Upgrade to libuv 1.49.2 (#7226) 2024-10-19 09:55:45 -07:00
PJ Reiniger
36e0c9d6db [build] MVP for building with bazel (#6994) 2024-10-19 09:54:49 -07:00
Tyler Veness
95b9bd880b [wpimath] Make geometry classes constexpr (#7222) 2024-10-18 16:08:41 -07:00
Joseph Eng
2054d0f57e [wpimath] Fix pose estimator reset methods (#7225) 2024-10-18 16:06:32 -07:00
Thad House
ee22482f4a [build] ntcoreffi: Don't link to chipobject (#7228) 2024-10-18 16:06:10 -07:00
Étienne Beaulac
796dbd3b86 [wpilib] Add Timer.isRunning() method (#7220) 2024-10-17 17:03:40 -07:00
Gold856
0424e5ba36 [build] Remove unnecessary symbol exclusions (#7221)
The symbol exporter in native-utils was updated and stopped exporting the excluded symbols.
2024-10-17 16:19:19 -07:00
Ryan Heuer
f7dddb8014 [glass] Add Alerts widget (#7219) 2024-10-16 13:45:56 -06:00
Ryan Blue
68715aa484 [wpilibc] SPI & I2C: Use handle wrapper to close port (#7217) 2024-10-16 11:08:44 -06:00
Tyler Veness
fad06ae1e7 Merge .inc files into headers (#7215) 2024-10-15 23:42:57 -07:00
Ryan Heuer
40caabea23 [glass] Align Field2d border and image padding (#7214) 2024-10-15 22:02:08 -07:00
Ryan Blue
59dc9ad8f4 [examples] Rename SysId example to SysIdRoutine (#7213)
CMake target output conflicts with sysid (the application) on windows
2024-10-15 22:01:05 -07:00
Ryan Blue
2b1c5aa4fc [wpilibc] Check for invalid handle in destructors (#7212)
Moved-from objects have invalid handles.
2024-10-15 19:56:13 -07:00
Peter Johnson
0bada2e102 [ntcore] Merge .inc files into headers (#7210) 2024-10-14 22:42:58 -07:00
Tyler Veness
ee281ea448 [wpimath] Merge .inc files into headers (#7209)
Splitting the files didn't help readability or save compilation time and
it confused contributors. Merging them is also in line with how C++
modules will be written.
2024-10-14 16:08:10 -07:00
Peter Johnson
bedfc09268 [ntcore] Add missing multi-subscribe C API functions (#7203)
Also export recently added C API functions.
2024-10-14 09:55:36 -06:00
1275 changed files with 72164 additions and 28096 deletions

20
.bazelignore Normal file
View File

@@ -0,0 +1,20 @@
build_cmake
build-cmake
# Auto generated by vscode
apriltag/bin
cameraserver/bin
cameraserver/multiCameraServer/bin
cscore/bin
fieldImages/bin
hal/bin
developerRobot/bin
ntcore/bin
romiVendordep/bin
wpilibNewCommands/bin
wpilibj/bin
wpimath/bin
wpinet/bin
wpiutil/bin
wpiunits/bin
xrpVendordep/bin

51
.bazelrc Normal file
View File

@@ -0,0 +1,51 @@
try-import %workspace%/bazel_auth.rc
try-import %workspace%/user.bazelrc
common --noenable_bzlmod
build --java_language_version=17
build --java_runtime_version=roboriojdk_17
build --tool_java_language_version=17
build --tool_java_runtime_version=remotejdk_17
test --test_output=errors
test --test_verbose_timeout_warnings
import shared/bazel/compiler_flags/sanitizers.rc
import shared/bazel/compiler_flags/base_linux_flags.rc
import shared/bazel/compiler_flags/linux_flags.rc
import shared/bazel/compiler_flags/osx_flags.rc
import shared/bazel/compiler_flags/roborio_flags.rc
import shared/bazel/compiler_flags/windows_flags.rc
import shared/bazel/compiler_flags/coverage_flags.rc
build:build_java --test_tag_filters=allwpilib-build-java --build_tag_filters=allwpilib-build-java
build:build_cpp --test_tag_filters=+allwpilib-build-cpp --build_tag_filters=+allwpilib-build-cpp
build:no_example --test_tag_filters=-wpi-example --build_tag_filters=-wpi-example
test:no_example --test_tag_filters=-wpi-example --build_tag_filters=-wpi-example
# Build Buddy Cache Setup
build:build_buddy --bes_results_url=https://app.buildbuddy.io/invocation/
build:build_buddy --bes_backend=grpcs://remote.buildbuddy.io
build:build_buddy --remote_cache=grpcs://remote.buildbuddy.io
build:build_buddy --remote_timeout=3600
# Additional suggestions from buildbuddy for speed
build:build_buddy --experimental_remote_cache_compression
build:build_buddy --experimental_remote_cache_compression_threshold=100
build:build_buddy --noslim_profile
build:build_buddy --experimental_profile_include_target_label
build:build_buddy --experimental_profile_include_primary_output
build:build_buddy --nolegacy_important_outputs
common:build_buddy_readonly --noremote_upload_local_results
# This config should be used locally. It downloads more than the CI version
build:remote_user --config=build_buddy
build:remote_user --config=build_buddy_readonly
build:remote_user --remote_download_toplevel
build:ci --config=build_buddy
build:ci --remote_download_minimal
build --build_metadata=REPO_URL=https://github.com/wpilibsuite/allwpilib.git

1
.bazelversion Normal file
View File

@@ -0,0 +1 @@
7.3.1

View File

@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
labels: 'type: bug'
assignees: ''
---

View File

@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
labels: 'type: feature'
assignees: ''
---

View File

@@ -2,7 +2,7 @@
name: Question
about: Ask about features or parts of this project
title: ''
labels: ''
labels: 'type: support'
assignees: ''
---

View File

@@ -0,0 +1,27 @@
name: 'Setup BuildBuddy acache'
description: 'Sets up the build buddy cache to be readonly / writing based on the presence of environment variables'
inputs:
token:
description: 'Build Buddy API token'
runs:
using: "composite"
steps:
- name: Setup without key
env:
API_KEY: ${{ inputs.token }}
if: ${{ env.API_KEY == '' }}
shell: bash
run: |
echo "No API key secret detected, will setup readonly cache"
echo "build:ci --config=build_buddy_readonly" > bazel_auth.rc
- name: Set with key
env:
API_KEY: ${{ inputs.token }}
if: ${{ env.API_KEY != '' }}
shell: bash
run: |
echo "API Key detected!"
echo "build:build_buddy --remote_header=x-buildbuddy-api-key=${{ env.API_KEY }}" > bazel_auth.rc

56
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
'2027':
- base-branch: '2027'
'component: apriltag':
- changed-files:
- any-glob-to-any-file: apriltag/**
'component: command-based':
- changed-files:
- any-glob-to-any-file: wpilibNewCommands/**
'component: cscore':
- changed-files:
- any-glob-to-any-file: cscore/**
'component: datalogtool':
- changed-files:
- any-glob-to-any-file: datalogtool/**
'component: epilogue':
- changed-files:
- any-glob-to-any-file: epilogue-*/**
'component: examples':
- changed-files:
- any-glob-to-any-file: wpilib*Examples/**
'component: glass':
- changed-files:
- any-glob-to-any-file: glass/**
'component: hal':
- changed-files:
- any-glob-to-any-file: hal/**
'component: ntcore':
- changed-files:
- any-glob-to-any-file: ntcore/**
'component: outlineviewer':
- changed-files:
- any-glob-to-any-file: outlineviewer/**
'component: sysid':
- changed-files:
- any-glob-to-any-file: sysid/**
'component: teamnumbersetter':
- changed-files:
- any-glob-to-any-file: roborioteamnumbersetter/**
'component: wpilibc':
- changed-files:
- any-glob-to-any-file: wpilibc/**
'component: wpilibj':
- changed-files:
- any-glob-to-any-file: wpilibj/**
'component: wpimath':
- changed-files:
- any-glob-to-any-file: wpimath/**
'component: wpinet':
- changed-files:
- any-glob-to-any-file: wpinet/**
'component: wpiunits':
- changed-files:
- any-glob-to-any-file: wpiunits/**
'component: wpiutil':
- changed-files:
- any-glob-to-any-file: wpiutil/**

109
.github/workflows/bazel.yml vendored Normal file
View File

@@ -0,0 +1,109 @@
name: Bazel
on: [pull_request, push]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
build-windows:
strategy:
fail-fast: false
matrix:
include:
- { name: "Windows (native)", os: windows-2022, action: "test", config: "--config=windows", }
- { name: "Windows (arm)", os: windows-2022, action: "build", config: "--config=windows_arm", }
name: "Build ${{ matrix.name }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
architecture: x64
- id: Setup_build_buddy
uses: ./.github/actions/setup-build-buddy
with:
token: ${{ secrets.BUILDBUDDY_API_KEY }}
- name: Build Release
run: bazel --output_user_root=C:\\bazelroot ${{ matrix.action }} -k ... --config=ci -c opt ${{ matrix.config }} --verbose_failures
shell: bash
build-mac:
name: "Mac"
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- id: Setup_build_buddy
uses: ./.github/actions/setup-build-buddy
with:
token: ${{ secrets.BUILDBUDDY_API_KEY }}
- name: Build Release
run: bazel test -k ... --config=ci -c opt --config=macos --nojava_header_compilation --verbose_failures
shell: bash
build-linux:
strategy:
fail-fast: false
matrix:
include:
- { name: "Linux (native)", os: ubuntu-22.04, action: "test", config: "--config=linux", }
- { name: "Linux (roborio)", os: ubuntu-22.04, action: "build", config: "--config=roborio", }
name: "${{ matrix.name }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: bazelbuild/setup-bazelisk@v3
- id: Setup_build_buddy
uses: ./.github/actions/setup-build-buddy
with:
token: ${{ secrets.BUILDBUDDY_API_KEY }}
- name: Build and Test Release
run: bazel ${{ matrix.action }} ... --config=ci -c opt ${{ matrix.config }} -k --verbose_failures
buildifier:
name: "buildifier"
runs-on: ubuntu-22.04
steps:
- name: Set up Go 1.15.x
uses: actions/setup-go@v5
with:
cache: false
go-version: 1.15.x
id: go
- name: Install Buildifier
run: |
cd $(mktemp -d)
GO111MODULE=on go get github.com/bazelbuild/buildtools/buildifier@6.0.0
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Run buildifier
run: buildifier -warnings all --lint=fix -r .
- name: Check Output
run: git --no-pager diff --exit-code HEAD
- name: Generate diff
run: git diff HEAD > bazel-lint-fixes.patch
if: ${{ failure() }}
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.platform }}-bazel-lint-fixes
path: bazel-lint-fixes.patch
if: ${{ failure() }}

51
.github/workflows/cmake-android.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: CMake Android
on: [pull_request, push]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
env:
SCCACHE_WEBDAV_ENDPOINT: "https://frcmaven.wpi.edu/artifactory/wpilib-generic-cache-cmake-local"
SCCACHE_WEBDAV_KEY_PREFIX: "sccache"
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
name: Android Arm64
abi: arm64-v8a
- os: ubuntu-22.04
name: Android X64
abi: "x86_64"
name: "Build - ${{ matrix.name }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r27c
add-to-path: false
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.5
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y ninja-build
- name: configure
run: cmake --preset with-sccache -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_WPILIB=OFF -DWITH_GUI=OFF -DWITH_CSCORE=OFF -DWITH_TESTS=OFF -DWITH_SIMULATION_MODULES=OFF -DWITH_PROTOBUF=OFF -DWITH_JAVA=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_TOOLCHAIN_FILE=${{ steps.setup-ndk.outputs.ndk-path }}/build/cmake/android.toolchain.cmake -DANDROID_ABI="${{ matrix.abi }}" -DANDROID_PLATFORM=android-24
- name: build
run: cmake --build build-cmake --parallel $(nproc)

View File

@@ -18,7 +18,7 @@ jobs:
include:
- os: ubuntu-22.04
name: Linux
container: wpilib/roborio-cross-ubuntu:2024-22.04
container: wpilib/roborio-cross-ubuntu:2025-22.04
flags: "--preset with-java-and-sccache -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON"
- os: macOS-14
name: macOS

View File

@@ -33,17 +33,17 @@ jobs:
env:
GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}"
NUMBER: ${{ github.event.issue.number }}
- name: Set up Python 3.10
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.12'
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- name: Install wpiformat
run: pip3 install wpiformat==2024.42
run: pip3 install wpiformat==2024.50
- name: Run wpiformat
run: wpiformat
- name: Run spotlessApply
@@ -81,10 +81,10 @@ jobs:
env:
GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}"
NUMBER: ${{ github.event.issue.number }}
- name: Set up Python 3.9
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: '3.12'
- name: Install jinja
run: python -m pip install jinja2
- name: Install protobuf dependencies

View File

@@ -13,7 +13,7 @@ jobs:
publish:
name: "Documentation - Publish"
runs-on: ubuntu-22.04
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
concurrency: ci-docs-publish
steps:
- uses: actions/checkout@v4
@@ -32,12 +32,12 @@ jobs:
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=beta" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
- name: Set environment variables (Release)
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=release" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta')
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, '2027')
- name: Build with Gradle
run: ./gradlew docs:generateJavaDocs docs:doxygen -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
- name: Install SSH Client 🔑

View File

@@ -12,13 +12,13 @@ jobs:
fail-fast: false
matrix:
include:
- container: wpilib/roborio-cross-ubuntu:2024-22.04
- container: wpilib/roborio-cross-ubuntu:2025-22.04
artifact-name: Athena
build-options: "-Ponlylinuxathena"
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
artifact-name: Arm32
build-options: "-Ponlylinuxarm32"
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
artifact-name: Arm64
build-options: "-Ponlylinuxarm64"
- container: wpilib/ubuntu-base:22.04
@@ -42,7 +42,7 @@ jobs:
fetch-depth: 0
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
- name: Build with Gradle
uses: addnab/docker-run-action@v3
with:
@@ -96,9 +96,16 @@ jobs:
task: "build"
outputs: "build/allOutputs"
- os: windows-2022
artifact-name: Win32
artifact-name: Win32FFI
architecture: x86
task: ":ntcoreffi:build"
build-options: "-Pntcoreffibuild \"-Dorg.gradle.jvmargs=-Xmx1096m\""
outputs: "ntcoreffi/build/outputs"
- os: windows-2022
artifact-name: Win64FFI
architecture: x64
task: ":ntcoreffi:build"
build-options: "-Pntcoreffibuild -Pbuildwinarm64"
outputs: "ntcoreffi/build/outputs"
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ${{ matrix.os }}
@@ -119,16 +126,16 @@ jobs:
keychain-password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
if: |
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
- name: Set Keychain Lock Timeout
run: security set-keychain-settings -lut 3600
run: security set-keychain-settings -lut 21600
if: |
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
shell: bash
if: startsWith(github.ref, 'refs/tags/v')
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
- name: Set Java Heap Size
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
if: matrix.artifact-name == 'Win32'
@@ -159,7 +166,7 @@ jobs:
run: ./gradlew copyAllOutputs --build-cache -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
if: |
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
- name: Check disk free space (Windows)
run: wmic logicaldisk get caption, freespace
if: matrix.os == 'windows-2022'
@@ -184,7 +191,7 @@ jobs:
java-version: 17
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
- name: Build with Gradle
run: ./gradlew docs:zipDocs --build-cache -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
env:
@@ -203,7 +210,7 @@ jobs:
- name: Free Disk Space
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
@@ -216,31 +223,31 @@ jobs:
- uses: actions/checkout@v4
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
with:
repository: wpilibsuite/build-tools
- uses: actions/download-artifact@v4
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
with:
path: combiner/products/build/allOutputs
- name: Flatten Artifacts
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
run: rsync -a --delete combiner/products/build/allOutputs/*/* combiner/products/build/allOutputs/
- name: Check version number exists
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
run: |
cat combiner/products/build/allOutputs/version.txt
test -s combiner/products/build/allOutputs/version.txt
- uses: actions/setup-java@v4
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
with:
distribution: 'temurin'
java-version: 17
@@ -256,7 +263,7 @@ jobs:
- name: Combine (Release)
if: |
github.repository == 'wpilibsuite/allwpilib' &&
startsWith(github.ref, 'refs/tags/v')
startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
run: cd combiner && ./gradlew publish -Pallwpilib -PreleaseRepoPublish
env:
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
@@ -265,7 +272,7 @@ jobs:
- uses: actions/upload-artifact@v4
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
with:
name: Maven
path: ~/releases
@@ -281,7 +288,7 @@ jobs:
- uses: peter-evans/repository-dispatch@v3
if: |
github.repository == 'wpilibsuite/allwpilib' &&
startsWith(github.ref, 'refs/tags/v')
startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
with:
token: ${{ secrets.TOOL_REPO_ACCESS_TOKEN }}
repository: wpilibsuite/${{ matrix.repo }}

12
.github/workflows/labeler.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: "Pull Request Labeler"
on:
- pull_request_target
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5

View File

@@ -22,12 +22,12 @@ jobs:
run: |
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.10
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.12'
- name: Install wpiformat
run: pip3 install wpiformat==2024.42
run: pip3 install wpiformat==2024.50
- name: Run
run: wpiformat
- name: Check output
@@ -51,7 +51,7 @@ jobs:
tidy:
name: "clang-tidy"
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2024-22.04
container: wpilib/ubuntu-base:22.04
steps:
- uses: actions/checkout@v4
with:
@@ -61,12 +61,12 @@ jobs:
git config --global --add safe.directory /__w/allwpilib/allwpilib
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.10
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.12'
- name: Install wpiformat
run: pip3 install wpiformat==2024.42
run: pip3 install wpiformat==2024.50
- name: Create compile_commands.json
run: |
./gradlew generateCompileCommands -Ptoolchain-optional-roboRio

View File

@@ -6,7 +6,7 @@ import sys
from pathlib import Path
def main(argv):
def main():
script_path = Path(__file__).resolve()
REPO_ROOT = script_path.parent.parent.parent
parser = argparse.ArgumentParser()
@@ -15,26 +15,64 @@ def main(argv):
help="Path to the quickbuf protoc plugin",
required=True,
)
args = parser.parse_args(argv)
subprocess.run(["python", f"{REPO_ROOT}/hal/generate_usage_reporting.py"])
subprocess.run(["python", f"{REPO_ROOT}/ntcore/generate_topics.py"])
subprocess.run(["python", f"{REPO_ROOT}/wpimath/generate_numbers.py"])
args = parser.parse_args()
subprocess.run(
[sys.executable, f"{REPO_ROOT}/hal/generate_usage_reporting.py"], check=True
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/ntcore/generate_topics.py"], check=True
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/wpimath/generate_numbers.py"], check=True
)
subprocess.run(
[
"python",
sys.executable,
f"{REPO_ROOT}/wpimath/generate_quickbuf.py",
f"--quickbuf_plugin={args.quickbuf_plugin}",
]
],
check=True,
)
subprocess.run(["python", f"{REPO_ROOT}/wpiunits/generate_units.py"])
subprocess.run(["python", f"{REPO_ROOT}/wpilibc/generate_hids.py"])
subprocess.run(["python", f"{REPO_ROOT}/wpilibj/generate_hids.py"])
subprocess.run(["python", f"{REPO_ROOT}/wpilibNewCommands/generate_hids.py"])
subprocess.run(["python", f"{REPO_ROOT}/wpilibc/generate_pwm_motor_controllers.py"])
subprocess.run(["python", f"{REPO_ROOT}/wpilibj/generate_pwm_motor_controllers.py"])
subprocess.run(["python", f"{REPO_ROOT}/thirdparty/imgui_suite/generate_gl3w.py"])
subprocess.run(f"{REPO_ROOT}/thirdparty/imgui_suite/generate_fonts.sh")
subprocess.run(
[
sys.executable,
f"{REPO_ROOT}/wpimath/generate_nanopb.py",
],
check=True,
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/wpiunits/generate_units.py"], check=True
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/wpilibc/generate_hids.py"], check=True
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/wpilibj/generate_hids.py"], check=True
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/wpilibNewCommands/generate_hids.py"], check=True
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/wpilibc/generate_pwm_motor_controllers.py"],
check=True,
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/wpilibj/generate_pwm_motor_controllers.py"],
check=True,
)
subprocess.run(
[
sys.executable,
f"{REPO_ROOT}/wpiutil/generate_nanopb.py",
],
check=True,
)
subprocess.run(
[sys.executable, f"{REPO_ROOT}/thirdparty/imgui_suite/generate_gl3w.py"],
check=True,
)
subprocess.run(f"{REPO_ROOT}/thirdparty/imgui_suite/generate_fonts.sh", check=True)
if __name__ == "__main__":
main(sys.argv[1:])
main()

View File

@@ -18,12 +18,12 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3.9
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install jinja
run: python -m pip install jinja2
python-version: '3.12'
- name: Install jinja and protobuf
run: python -m pip install jinja2 protobuf grpcio-tools
- name: Install protobuf dependencies
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler && wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe && chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
- name: Regenerate all

View File

@@ -30,7 +30,7 @@ jobs:
ctest-flags: ""
name: "${{ matrix.name }}"
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2024-22.04
container: wpilib/roborio-cross-ubuntu:2025-22.04
steps:
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java clang-14 libprotobuf-dev protobuf-compiler ninja-build

View File

@@ -16,13 +16,13 @@ jobs:
fail-fast: false
matrix:
include:
- container: wpilib/roborio-cross-ubuntu:2024-22.04
- container: wpilib/roborio-cross-ubuntu:2025-22.04
artifact-name: Athena
build-options: "-Ponlylinuxathena"
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
artifact-name: Arm32
build-options: "-Ponlylinuxarm32"
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
artifact-name: Arm64
build-options: "-Ponlylinuxarm64"
- container: wpilib/ubuntu-base:22.04
@@ -119,7 +119,7 @@ jobs:
if: |
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' && github.ref == 'refs/heads/main')
- name: Set Keychain Lock Timeout
run: security set-keychain-settings -lut 3600
run: security set-keychain-settings -lut 21600
if: |
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' && github.ref == 'refs/heads/main')
- name: Set Java Heap Size

View File

@@ -32,7 +32,7 @@ jobs:
- name: Build WPILib with Gradle
uses: addnab/docker-run-action@v3
with:
image: wpilib/roborio-cross-ubuntu:2024-22.04
image: wpilib/roborio-cross-ubuntu:2025-22.04
options: -v ${{ github.workspace }}:/work -w /work -e GITHUB_REF -e CI -e DISPLAY
run: df . && rm -f semicolon_delimited_script && ./gradlew :wpilibc:publish :wpilibj:publish :wpilibNewCommands:publish :hal:publish :cameraserver:publish :ntcore:publish :cscore:publish :wpimath:publish :wpinet:publish :wpiutil:publish :apriltag:publish :wpiunits:publish :simulation:halsim_gui:publish :simulation:halsim_ds_socket:publish :fieldImages:publish :epilogue-processor:publish :epilogue-runtime:publish :thirdparty:googletest:publish -x test -x Javadoc -x doxygen --build-cache && cp -r /root/releases/maven/development /work
- uses: actions/upload-artifact@v4

View File

@@ -22,10 +22,10 @@ jobs:
run: |
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.9
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: '3.12'
- name: Configure committer identity
run: |
git config --global user.email "you@example.com"
@@ -35,102 +35,122 @@ jobs:
cd upstream_utils
./apriltag.py clone
./apriltag.py copy-src
./apriltag.py format-patch
- name: Run argparse_lib.py
run: |
cd upstream_utils
./argparse_lib.py clone
./argparse_lib.py copy-src
./argparse_lib.py format-patch
- name: Run eigen.py
run: |
cd upstream_utils
./eigen.py clone
./eigen.py copy-src
./eigen.py format-patch
- name: Run expected.py
run: |
cd upstream_utils
./expected.py clone
./expected.py copy-src
./expected.py format-patch
- name: Run fmt.py
run: |
cd upstream_utils
./fmt.py clone
./fmt.py copy-src
./fmt.py format-patch
- name: Run gcem.py
run: |
cd upstream_utils
./gcem.py clone
./gcem.py copy-src
./gcem.py format-patch
- name: Run gl3w.py
run: |
cd upstream_utils
./gl3w.py clone
./gl3w.py copy-src
./gl3w.py format-patch
- name: Run glfw.py
run: |
cd upstream_utils
./glfw.py clone
./glfw.py copy-src
./glfw.py format-patch
- name: Run googletest.py
run: |
cd upstream_utils
./googletest.py clone
./googletest.py copy-src
./googletest.py format-patch
- name: Run imgui.py
run: |
cd upstream_utils
./imgui.py clone
./imgui.py copy-src
./imgui.py format-patch
- name: Run implot.py
run: |
cd upstream_utils
./implot.py clone
./implot.py copy-src
./implot.py format-patch
- name: Run json.py
run: |
cd upstream_utils
./json.py clone
./json.py copy-src
./json.py format-patch
- name: Run libuv.py
run: |
cd upstream_utils
./libuv.py clone
./libuv.py copy-src
./libuv.py format-patch
- name: Run llvm.py
run: |
cd upstream_utils
./llvm.py clone
./llvm.py copy-src
./llvm.py format-patch
- name: Run mpack.py
run: |
cd upstream_utils
./mpack.py clone
./mpack.py copy-src
./mpack.py format-patch
- name: Run stack_walker.py
run: |
cd upstream_utils
./stack_walker.py clone
./stack_walker.py copy-src
./stack_walker.py format-patch
- name: Run memory.py
run: |
cd upstream_utils
./memory.py clone
./memory.py copy-src
./memory.py format-patch
- name: Run protobuf.py
run: |
cd upstream_utils
./protobuf.py clone
./protobuf.py copy-src
./protobuf.py format-patch
- name: Run sleipnir.py
run: |
cd upstream_utils
./sleipnir.py clone
./sleipnir.py copy-src
./sleipnir.py format-patch
- name: Run stb.py
run: |
cd upstream_utils
./stb.py clone
./stb.py copy-src
./stb.py format-patch
- name: Add untracked files to index so they count as changes
run: git add -A
- name: Check output
run: git --no-pager diff --exit-code HEAD
run: git --no-pager diff --exit-code HEAD ':!*.bazel'

6
.gitignore vendored
View File

@@ -15,6 +15,8 @@ networktables.json
ntcore/connectionlistenertest.json
ntcore/timesynctest.json
nanopb_pb2.py
# Created by the jenkins test script
test-reports
@@ -249,9 +251,7 @@ imgui.ini
/bazel-*
user.bazelrc
coverage_report/
bazel_auth.rc
# ctest
/Testing/
# protobuf
!wpiprotoplugin.jar

View File

@@ -66,6 +66,14 @@ set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" ON)
option(WITH_JAVA "Include Java and JNI in the build" OFF)
option(WITH_JAVA_SOURCE "Build Java source jars" ${WITH_JAVA})
option(WITH_DOCS "Build Doxygen docs (needs Git for versioning)" OFF)
cmake_dependent_option(
DOCS_WARNINGS_AS_ERRORS
"Make docs warnings into errors"
OFF
WITH_DOCS
OFF
)
option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
option(WITH_NTCORE "Build ntcore" ON)
option(WITH_WPIMATH "Build wpimath" ON)
@@ -123,12 +131,17 @@ set(java_lib_dest java)
if(WITH_JAVA OR WITH_JAVA_SOURCE)
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
find_package(Java REQUIRED COMPONENTS Development)
find_package(JNI REQUIRED COMPONENTS JVM)
else()
# Protoc requires the java runtime
find_package(Java REQUIRED COMPONENTS Runtime)
if(NOT ANDROID)
find_package(JNI REQUIRED COMPONENTS JVM)
endif()
endif()
if(WITH_DOCS)
find_package(Doxygen REQUIRED)
find_package(Git REQUIRED)
include(AddDoxygenDocs)
add_doxygen_docs()
endif()
find_package(LIBSSH CONFIG 0.7.1)
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
@@ -279,8 +292,6 @@ if(WITH_NTCORE)
add_subdirectory(ntcore)
endif()
add_subdirectory(protoplugin)
if(WITH_WPIMATH)
if(WITH_JAVA)
set(WPIUNITS_DEP_REPLACE ${WPIUNITS_DEP_REPLACE_IMPL})

View File

@@ -13,7 +13,7 @@ This article contains instructions on building projects using a development buil
Development builds are the per-commit build hosted every time a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
To build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2024 GradleRIO version, ie `2024.0.0-alpha-1`
To build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2025 GradleRIO version, ie `2025.1.1-beta-1`
```groovy
wpi.maven.useLocal = false
@@ -28,13 +28,13 @@ Java
```groovy
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.0.0-alpha-1"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-1"
}
wpi.maven.useLocal = false
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = '2024.+'
wpi.versions.wpimathVersion = '2024.+'
wpi.versions.wpilibVersion = '2025.+'
wpi.versions.wpimathVersion = '2025.+'
```
C++
@@ -42,13 +42,13 @@ C++
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2024.0.0-alpha-1"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-1"
}
wpi.maven.useLocal = false
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = '2024.+'
wpi.versions.wpimathVersion = '2024.+'
wpi.versions.wpilibVersion = '2025.+'
wpi.versions.wpimathVersion = '2025.+'
```
### Development Build Documentation
@@ -64,7 +64,7 @@ Java
```groovy
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.0.0-alpha-1"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-1"
}
wpi.maven.useLocal = false
@@ -78,7 +78,7 @@ C++
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2024.0.0-alpha-1"
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-1"
}
wpi.maven.useLocal = false

25
README-Bazel.md Normal file
View File

@@ -0,0 +1,25 @@
# WPILib Bazel Support
WPILib is normally built with Gradle, but [Bazel](https://www.bazel.build/) can also be used to increase development speed due to the superior caching ability and the ability to use remote caching and remote execution (on select platforms)
## Prerequisites
- Install [Bazelisk](https://github.com/bazelbuild/bazelisk/releases) and add it to your path. Bazelisk is a wrapper that will download the correct version of bazel specified in the repository. Note: You can alias/rename the binary to `bazel` if you want to keep the familiar `bazel build` vs `bazelisk build` syntax.
## Building
To build the entire repository, simply run `bazel build //...`. To run all of the unit tests, run `bazel test //...`
Other examples:
- `bazel build //wpimath/...` - Builds every target in the wpimath folder
- `bazel test //wpiutil:wpiutil-cpp-test` - Runs only the cpp test target in the wpiutil folder
- `bazel coverage //wpiutil/...` - (*Nix only) - Runs a code coverage report for both C++ and Java on all the targets under wpiutil
## User settings
When invoking bazel, it will check if `user.bazelrc` exists for additional, user specified flags. You can use these settings to do things like always ignore buildin a specific folder, or limiting the CPU/RAM usage during a build.
Examples:
- `build --build_tag_filters=-wpi-example` - Do not build any targets tagged with `wpi-example` (Currently all of the targets in wpilibcExamples and wpilibjExamples contain this tag)
- `build -c opt` - Always build optimized targets. The default compiler flags were chosen to build as fast as possible, and thus don't contain many optimizations
- `build -k` - `-k` is analogous to the MAKE flag `--keep-going`, so the build will not stop on the first error.
- ```
build --local_ram_resources=HOST_RAM*.5 # Don't use more than half my RAM when building
build --local_cpu_resources=HOST_CPUS-1 # Leave one core alone
```

View File

@@ -66,6 +66,8 @@ The following build options are available:
* This option will build the HAL and wpilibc/j during the build. The HAL is the simulation HAL, unless the external HAL options are used. The CMake build has no capability to build for the roboRIO.
* `WITH_WPIMATH` (ON Default)
* This option will build the wpimath library. This option must be on to build wpilib.
* `WITH_PROTOBUF` (ON Default)
* This option will build with the protobuf library.
* `WITH_WPIUNITS` (`WITH_JAVA` Default)
* This option will build the wpiunits library. This option must be on to build the Java wpimath library and requires `WITH_JAVA` to also be on.
* `OPENCV_JAVA_INSTALL_DIR`

View File

@@ -17,6 +17,7 @@ Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WP
- [Custom toolchain location](#custom-toolchain-location)
- [Formatting/Linting](#formattinglinting)
- [CMake](#cmake)
- [Bazel](#bazel)
- [Running examples in simulation](#running-examples-in-simulation)
- [Publishing](#publishing)
- [Structure and Organization](#structure-and-organization)
@@ -149,7 +150,11 @@ Several files within WPILib are generated using Jinja. If a PR is opened that mo
### CMake
CMake is also supported for building. See [README-CMAKE.md](README-CMAKE.md).
CMake is also supported for building. See [README-CMake.md](README-CMake.md).
### Bazel
Bazel is also supported for building. See [README-Bazel.md](README-Bazel.md).
## Running examples in simulation

View File

@@ -43,6 +43,7 @@ Portable File Dialogs wpigui/src/main/native/include/portable-file-dialogs.h
V8 export-template wpiutil/src/main/native/include/wpi/SymbolExports.h
GCEM wpimath/src/main/native/thirdparty/gcem/include/
Sleipnir wpimath/src/main/native/thirdparty/sleipnir
Debugging wpiutil/src/main/native/thirdparty/debugging
==============================================================================
Google Test License
@@ -1224,3 +1225,29 @@ Redistribution and use in source and binary forms, with or without modification,
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=================
Debugging License
=================
MIT License
Copyright (c) 2021-2022 René Ferdinand Rivera Morell
Copyright (c) 2018 Isabella Muerte
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

100
WORKSPACE Normal file
View File

@@ -0,0 +1,100 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Download Extra java rules
http_archive(
name = "rules_jvm_external",
sha256 = "08ea921df02ffe9924123b0686dc04fd0ff875710bfadb7ad42badb931b0fd50",
strip_prefix = "rules_jvm_external-6.1",
url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/6.1/rules_jvm_external-6.1.tar.gz",
)
load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
rules_jvm_external_deps()
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_artifacts = [
"org.ejml:ejml-simple:0.43.1",
"com.fasterxml.jackson.core:jackson-annotations:2.15.2",
"com.fasterxml.jackson.core:jackson-core:2.15.2",
"com.fasterxml.jackson.core:jackson-databind:2.15.2",
"us.hebi.quickbuf:quickbuf-runtime:1.3.3",
"com.google.code.gson:gson:2.10.1",
]
maven_install(
name = "maven",
artifacts = maven_artifacts,
repositories = [
"https://repo1.maven.org/maven2",
"https://frcmaven.wpi.edu/artifactory/release/",
],
)
# Download toolchains
http_archive(
name = "rules_bzlmodrio_toolchains",
sha256 = "2ef1cafce7f4fd4e909bb5de8b0dc771a934646afd55d5f100ff31f6b500df98",
url = "https://github.com/wpilibsuite/rules_bzlmodRio_toolchains/releases/download/2024-1.bcr1/rules_bzlmodRio_toolchains-2024-1.bcr1.tar.gz",
)
load("@rules_bzlmodrio_toolchains//:maven_deps.bzl", "setup_legacy_setup_toolchains_dependencies")
setup_legacy_setup_toolchains_dependencies()
load("@rules_bzlmodrio_toolchains//toolchains:load_toolchains.bzl", "load_toolchains")
load_toolchains()
#
http_archive(
name = "rules_bzlmodrio_jdk",
sha256 = "a00d5fa971fbcad8a17b1968cdc5350688397035e90b0cb94e040d375ecd97b4",
url = "https://github.com/wpilibsuite/rules_bzlmodRio_jdk/releases/download/17.0.8.1-1/rules_bzlmodRio_jdk-17.0.8.1-1.tar.gz",
)
load("@rules_bzlmodrio_jdk//:maven_deps.bzl", "setup_legacy_setup_jdk_dependencies")
setup_legacy_setup_jdk_dependencies()
register_toolchains(
"@local_roborio//:macos",
"@local_roborio//:linux",
"@local_roborio//:windows",
"@local_raspi_32//:macos",
"@local_raspi_32//:linux",
"@local_raspi_32//:windows",
"@local_bullseye_32//:macos",
"@local_bullseye_32//:linux",
"@local_bullseye_32//:windows",
"@local_bullseye_64//:macos",
"@local_bullseye_64//:linux",
"@local_bullseye_64//:windows",
)
setup_legacy_setup_jdk_dependencies()
http_archive(
name = "bzlmodrio-ni",
sha256 = "197fceac88bf44fb8427d5e000b0083118d3346172dd2ad31eccf83a5e61b3ce",
url = "https://github.com/wpilibsuite/bzlmodRio-ni/releases/download/2025.0.0/bzlmodRio-ni-2025.0.0.tar.gz",
)
load("@bzlmodrio-ni//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_ni_cpp_dependencies")
setup_legacy_bzlmodrio_ni_cpp_dependencies()
http_archive(
name = "bzlmodrio-opencv",
sha256 = "5314cce05b49451a46bf3e3140fc401342e53d5f3357612ed4473e59bb616cba",
url = "https://github.com/wpilibsuite/bzlmodRio-opencv/releases/download/2024.4.8.0-4.bcr1/bzlmodRio-opencv-2024.4.8.0-4.bcr1.tar.gz",
)
load("@bzlmodrio-opencv//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_opencv_cpp_dependencies")
setup_legacy_bzlmodrio_opencv_cpp_dependencies()
load("@bzlmodrio-opencv//:maven_java_deps.bzl", "setup_legacy_bzlmodrio_opencv_java_dependencies")
setup_legacy_bzlmodrio_opencv_java_dependencies()

View File

@@ -297,7 +297,7 @@ public class AprilTagDetector implements AutoCloseable {
* @return Results (array of AprilTagDetection)
*/
public AprilTagDetection[] detect(Mat img) {
return AprilTagJNI.detect(m_native, img.cols(), img.rows(), img.cols(), img.dataAddr());
return AprilTagJNI.detect(m_native, img.cols(), img.rows(), (int) img.step1(), img.dataAddr());
}
private long m_native;

View File

@@ -130,7 +130,7 @@ void AprilTagDetector::RemoveFamily(std::string_view fam) {
apriltag_detector_remove_family(
static_cast<apriltag_detector_t*>(m_impl),
static_cast<apriltag_family_t*>(it->second));
DestroyFamily(it->getKey(), it->second);
DestroyFamily(it->first, it->second);
m_families.erase(it);
}
}
@@ -158,7 +158,7 @@ void AprilTagDetector::Destroy() {
void AprilTagDetector::DestroyFamilies() {
for (auto&& entry : m_families) {
DestroyFamily(entry.getKey(), entry.second);
DestroyFamily(entry.first, entry.second);
}
}

View File

@@ -131,6 +131,34 @@ class AprilTagDetectorTest {
return image;
}
@Test
void testDecodeCropped() {
detector.addFamily("tag16h5");
detector.addFamily("tag36h11");
Mat image;
try {
image = loadImage("tag1_640_480.jpg");
} catch (IOException ex) {
fail(ex);
return;
}
// Pre-knowledge -- the tag is within this ROI of this particular test image
var cropped = image.submat(100, 400, 220, 570);
try {
AprilTagDetection[] results = detector.detect(cropped);
assertEquals(1, results.length);
assertEquals("tag36h11", results[0].getFamily());
assertEquals(1, results[0].getId());
assertEquals(0, results[0].getHamming());
} finally {
cropped.release();
image.release();
}
}
@Test
void testDecodeAndPose() {
detector.addFamily("tag16h5");

View File

@@ -18,10 +18,9 @@ plugins {
id 'idea'
id 'visual-studio'
id 'net.ltgt.errorprone' version '3.1.0' apply false
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'com.gradleup.shadow' version '8.3.4' apply false
id 'com.diffplug.spotless' version '6.20.0' apply false
id 'com.github.spotbugs' version '6.0.2' apply false
id 'com.google.protobuf' version '0.9.3' apply false
}
wpilibVersioning.buildServerMode = project.hasProperty('buildServer')
@@ -171,5 +170,5 @@ ext.getCurrentArch = {
}
wrapper {
gradleVersion = '8.5'
gradleVersion = '8.11'
}

View File

@@ -9,5 +9,5 @@ repositories {
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2025.3.0"
implementation "edu.wpi.first:native-utils:2025.9.0"
}

32
cameraserver/BUILD.bazel Normal file
View File

@@ -0,0 +1,32 @@
load("@rules_cc//cc:defs.bzl", "cc_binary")
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
java_library(
name = "cameraserver-java",
srcs = glob(["src/main/java/**/*.java"]),
visibility = ["//visibility:public"],
deps = [
"//cscore:cscore-java",
"//hal:hal-java",
"//ntcore:networktables-java",
"//wpimath:wpimath-java",
"//wpinet:wpinet-java",
"//wpiutil:wpiutil-java",
"@bzlmodrio-opencv//libraries/java/opencv",
],
)
cc_binary(
name = "DevMain-Cpp",
srcs = ["src/dev/native/cpp/main.cpp"],
deps = [
],
)
java_binary(
name = "DevMain-Java",
srcs = ["src/dev/java/edu/wpi/first/cameraserver/DevMain.java"],
main_class = "edu.wpi.first.cameraserver.DevMain",
deps = [
],
)

View File

@@ -30,19 +30,6 @@ apply from: "${rootDir}/shared/opencv.gradle"
nativeUtils.exportsConfigs {
cameraserver {
x64ExcludeSymbols = [
'_CT??_R0?AV_System_error',
'_CT??_R0?AVexception',
'_CT??_R0?AVfailure',
'_CT??_R0?AVruntime_error',
'_CT??_R0?AVsystem_error',
'_CTA5?AVfailure',
'_TI5?AVfailure',
'_CT??_R0?AVout_of_range',
'_CTA3?AVout_of_range',
'_TI3?AVout_of_range',
'_CT??_R0?AVbad_cast'
]
}
}

View File

@@ -0,0 +1,16 @@
load("@rules_java//java:defs.bzl", "java_binary")
java_binary(
name = "multiCameraServer-java",
srcs = ["src/main/java/edu/wpi/Main.java"],
main_class = "edu.wpi.Main",
deps = [
"//cameraserver:cameraserver-java",
"//cscore:cscore-java",
"//hal:hal-java",
"//ntcore:networktables-java",
"//wpimath:wpimath-java",
"//wpiutil:wpiutil-java",
"@maven//:com_google_code_gson_gson",
],
)

View File

@@ -22,7 +22,7 @@ application {
mainClass = 'edu.wpi.Main'
}
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'com.gradleup.shadow'
repositories {
maven {

View File

@@ -22,7 +22,7 @@ class DefaultCameraServerShared : public frc::CameraServerShared {
void ReportDriverStationErrorV(fmt::string_view format,
fmt::format_args args) override {}
std::pair<std::thread::id, bool> GetRobotMainThreadId() const override {
return std::make_pair(std::thread::id(), false);
return std::pair{std::thread::id(), false};
}
};
} // namespace

View File

@@ -9,10 +9,10 @@
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/deprecated.h>
#include "cscore.h"
#include "cscore_cv.h"
namespace frc {
@@ -130,7 +130,9 @@ class CameraServer {
*/
template <typename T>
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::initializer_list<T> hosts);
static cs::AxisCamera AddAxisCamera(std::initializer_list<T> hosts) {
return AddAxisCamera("Axis Camera", hosts);
}
/**
* Adds an Axis IP camera.
@@ -185,7 +187,14 @@ class CameraServer {
template <typename T>
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
std::initializer_list<T> hosts);
std::initializer_list<T> hosts) {
std::vector<std::string> vec;
vec.reserve(hosts.size());
for (const auto& host : hosts) {
vec.emplace_back(host);
}
return AddAxisCamera(name, vec);
}
WPI_UNIGNORE_DEPRECATED
/**
@@ -316,5 +325,3 @@ class CameraServer {
};
} // namespace frc
#include "cameraserver/CameraServer.inc"

View File

@@ -1,33 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string>
#include <vector>
#include "cameraserver/CameraServer.h"
namespace frc {
WPI_IGNORE_DEPRECATED
template <typename T>
inline cs::AxisCamera CameraServer::AddAxisCamera(
std::initializer_list<T> hosts) {
return AddAxisCamera("Axis Camera", hosts);
}
template <typename T>
inline cs::AxisCamera CameraServer::AddAxisCamera(
std::string_view name, std::initializer_list<T> hosts) {
std::vector<std::string> vec;
vec.reserve(hosts.size());
for (const auto& host : hosts) {
vec.emplace_back(host);
}
return AddAxisCamera(name, vec);
}
WPI_UNIGNORE_DEPRECATED
} // namespace frc

View File

@@ -8,7 +8,6 @@
#include <functional>
#include <memory>
#include "cscore.h"
#include "cscore_cv.h"
#include "vision/VisionPipeline.h"
@@ -81,17 +80,35 @@ class VisionRunnerBase {
template <typename T>
class VisionRunner : public VisionRunnerBase {
public:
/**
* Creates a new vision runner. It will take images from the {@code
* videoSource}, send them to the {@code pipeline}, and call the {@code
* listener} when the pipeline has finished to alert user code when it is safe
* to access the pipeline's outputs.
*
* @param videoSource The video source to use to supply images for the
* pipeline
* @param pipeline The vision pipeline to run
* @param listener A function to call after the pipeline has finished
* running
*/
VisionRunner(cs::VideoSource videoSource, T* pipeline,
std::function<void(T&)> listener);
std::function<void(T&)> listener)
: VisionRunnerBase(videoSource),
m_pipeline(pipeline),
m_listener(listener) {}
virtual ~VisionRunner() = default;
protected:
void DoProcess(cv::Mat& image) override;
void DoProcess(cv::Mat& image) override {
m_pipeline->Process(image);
m_listener(*m_pipeline);
}
private:
T* m_pipeline;
std::function<void(T&)> m_listener;
};
} // namespace frc
#include "VisionRunner.inc"
} // namespace frc

View File

@@ -1,36 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <functional>
#include "vision/VisionRunner.h"
namespace frc {
/**
* Creates a new vision runner. It will take images from the {@code
* videoSource}, send them to the {@code pipeline}, and call the {@code
* listener} when the pipeline has finished to alert user code when it is safe
* to access the pipeline's outputs.
*
* @param videoSource The video source to use to supply images for the pipeline
* @param pipeline The vision pipeline to run
* @param listener A function to call after the pipeline has finished running
*/
template <typename T>
VisionRunner<T>::VisionRunner(cs::VideoSource videoSource, T* pipeline,
std::function<void(T&)> listener)
: VisionRunnerBase(videoSource),
m_pipeline(pipeline),
m_listener(listener) {}
template <typename T>
void VisionRunner<T>::DoProcess(cv::Mat& image) {
m_pipeline->Process(image);
m_listener(*m_pipeline);
}
} // namespace frc

View File

@@ -0,0 +1,144 @@
macro(add_doxygen_docs)
set(dirs
apriltag
cameraserver
cscore
fieldImages
hal
ntcore
romiVendordep
wpilibc
wpilibNewCommands
wpimath
wpinet
wpiutil
xrpVendordep
)
foreach(dir ${dirs})
list(APPEND docs_dirs ${dir}/src/main/native/include)
file(GLOB dirs ${dir}/src/main/native/thirdparty/*/include)
list(FILTER dirs EXCLUDE REGEX eigen|protobuf)
set(DOXYGEN_EXCLUDE_PATTERNS "*.pb.h" "**/.clang-tidy" "**/.clang-format")
if(DOCS_WARNINGS_AS_ERRORS)
set(DOXYGEN_WARN_AS_ERROR "FAIL_ON_WARNINGS_PRINT")
list(FILTER dirs EXCLUDE REGEX fmt|memory|units)
list(
APPEND
DOXYGEN_EXCLUDE_PATTERNS
# apriltag
"apriltag_pose.h"
# llvm
"wpi/AlignOf.h"
"wpi/Casting.h"
"wpi/Chrono.h"
"wpi/Compiler.h"
"wpi/ConvertUTF.h"
"wpi/DenseMap.h"
"wpi/DenseMapInfo.h"
"wpi/Endian.h"
"wpi/EpochTracker.h"
"wpi/Errc.h"
"wpi/Errno.h"
"wpi/ErrorHandling.h"
"wpi/bit.h"
"wpi/fs.h"
"wpi/FunctionExtras.h"
"wpi/function_ref.h"
"wpi/Hashing.h"
"wpi/iterator.h"
"wpi/iterator_range.h"
"wpi/ManagedStatic.h"
"wpi/MapVector.h"
"wpi/MathExtras.h"
"wpi/MemAlloc.h"
"wpi/PointerIntPair.h"
"wpi/PointerLikeTypeTraits.h"
"wpi/PointerUnion.h"
"wpi/raw_os_ostream.h"
"wpi/raw_ostream.h"
"wpi/SmallPtrSet.h"
"wpi/SmallSet.h"
"wpi/SmallString.h"
"wpi/SmallVector.h"
"wpi/StringExtras.h"
"wpi/StringMap.h"
"wpi/SwapByteOrder.h"
"wpi/type_traits.h"
"wpi/VersionTuple.h"
"wpi/WindowsError.h"
# libuv
"uv.h"
"uv/**"
# json
"wpi/adl_serializer.h"
"wpi/byte_container_with_subtype.h"
"wpi/json.h"
"wpi/json_fwd.h"
"wpi/ordered_map.h"
# mpack
"wpi/mpack.h"
)
endif()
list(APPEND docs_dirs ${dirs})
list(APPEND docs_dirs ${dir}/src/generated/main/native/include)
endforeach()
set(DOXYGEN_CASE_SENSE_NAMES false)
set(DOXYGEN_EXTENSION_MAPPING inc=C++ no_extension=C++)
set(DOXYGEN_EXTRACT_ALL true)
set(DOXYGEN_EXTRACT_STATIC true)
set(DOXYGEN_FILE_PATTERNS "*")
set(DOXYGEN_FULL_PATH_NAMES true)
set(DOXYGEN_FULL_SIDEBAR false)
set(DOXYGEN_GENERATE_HTML true)
set(DOXYGEN_GENERATE_LATEX false)
set(DOXYGEN_GENERATE_TREEVIEW true)
set(DOXYGEN_HTML_COLORSTYLE "LIGHT")
set(DOXYGEN_HTML_EXTRA_STYLESHEET docs/theme.css)
set(DOXYGEN_JAVADOC_AUTOBRIEF true)
set(DOXYGEN_ALIASES
"effects=\\par <i>Effects:</i>^^"
"notes=\\par <i>Notes:</i>^^"
"requires=\\par <i>Requires:</i>^^"
"requiredbe=\\par <i>Required Behavior:</i>^^"
"concept{2}=<a href=\"md_doc_concepts.html#1\">2</a>"
"defaultbe=\\par <i>Default Behavior:</i>^^"
)
set(DOXYGEN_PROJECT_NAME WPILibC++)
set(DOXYGEN_PROJECT_NUMBER version)
set(DOXYGEN_PROJECT_LOGO wpiutil/src/main/native/resources/wpilib-128.png)
set(DOXYGEN_QUIET true)
set(DOXYGEN_RECURSIVE true)
set(DOXYGEN_STRIP_CODE_COMMENTS false)
set(DOXYGEN_STRIP_FROM_PATH ${docs_dirs})
set(DOXYGEN_STRIP_FROM_INC_PATH ${docs_dirs})
set(DOXYGEN_TIMESTAMP "DATETIME")
set(DOXYGEN_USE_MATHJAX true)
set(DOXYGEN_WARNINGS false)
set(DOXYGEN_WARN_IF_INCOMPLETE_DOC true)
set(DOXYGEN_WARN_IF_UNDOCUMENTED false)
set(DOXYGEN_WARN_NO_PARAMDOC true)
set(DOXYGEN_ENABLE_PREPROCESSING true)
set(DOXYGEN_MACRO_EXPANSION true)
set(DOXYGEN_EXPAND_ONLY_PREDEF true)
set(DOXYGEN_PREDEFINED
"__cplusplus"
"HAL_ENUM(name)=enum name : int32_t"
"DOXYGEN"
"WPI_NOEXCEPT:=noexcept"
"WPI_SFINAE(x):="
"WPI_REQUIRES(x):="
"WPI_REQUIRES_RET(...):="
"WPI_ENABLE_IF(...):="
"WPI_CONSTEXPR:=constexpr"
"WPI_CONSTEXPR_FNC:=constexpr"
"WPI_IMPL_DEFINED(...):=implementation_defined"
"WPI_EBO(...):="
)
execute_process(COMMAND git describe OUTPUT_VARIABLE version)
string(SUBSTRING ${version} 1 -1 version)
set(DOXYGEN_PROJECT_NUMBER ${version})
doxygen_add_docs(docs ${docs_dirs})
endmacro()

View File

@@ -55,9 +55,4 @@ macro(wpilib_target_warnings target)
)
target_compile_options(${target} PRIVATE -gz=zlib)
endif()
# Disable std::mutex constexpr constructor on MSVC
if(MSVC)
target_compile_options(${target} PRIVATE /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
endif()
endmacro()

View File

@@ -1,92 +0,0 @@
function(wpi_protobuf_generate)
set(_singleargs PROTOC_OUT_DIR PLUGIN DEPENDENCIES)
if(COMMAND target_sources)
list(APPEND _singleargs TARGET)
endif()
set(_multiargs PROTOS)
cmake_parse_arguments(
wpi_protobuf_generate
"${_options}"
"${_singleargs}"
"${_multiargs}"
"${ARGN}"
)
if(NOT wpi_protobuf_generate_PROTOS)
message(SEND_ERROR "Error: protobuf_generate called without any targets or source files")
return()
endif()
if(NOT wpi_protobuf_generate_TARGET)
message(SEND_ERROR "Error: wpi_protobuf_generate called without a target")
return()
endif()
if(NOT wpi_protobuf_generate_PROTOC_OUT_DIR)
message(SEND_ERROR "Error: protobuf_generate called without a protoc out directory")
return()
endif()
if(NOT wpi_protobuf_generate_PLUGIN)
message(SEND_ERROR "Error: wpi_protobuf_generate called without a plugin")
return()
endif()
set(_generate_extensions .pb.h .pb.cc)
# Create an include path for each file specified
foreach(_file ${wpi_protobuf_generate_PROTOS})
get_filename_component(_abs_file ${_file} ABSOLUTE)
get_filename_component(_abs_dir ${_abs_file} DIRECTORY)
list(FIND _protobuf_include_path ${_abs_dir} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${_abs_dir})
endif()
endforeach()
set(_generated_srcs_all)
foreach(_proto ${wpi_protobuf_generate_PROTOS})
get_filename_component(_abs_file ${_proto} ABSOLUTE)
get_filename_component(_abs_dir ${_abs_file} DIRECTORY)
get_filename_component(_basename ${_proto} NAME_WLE)
file(RELATIVE_PATH _rel_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_abs_dir})
set(_possible_rel_dir)
set(_generated_srcs)
foreach(_ext ${_generate_extensions})
list(
APPEND
_generated_srcs
"${wpi_protobuf_generate_PROTOC_OUT_DIR}/${_possible_rel_dir}${_basename}${_ext}"
)
endforeach()
list(APPEND _generated_srcs_all ${_generated_srcs})
set(_comment "Running WPILib protocol buffer compiler on ${_proto}")
add_custom_command(
OUTPUT ${_generated_srcs}
COMMAND protobuf::protoc
ARGS
--cpp_out ${wpi_protobuf_generate_PROTOC_OUT_DIR} --wpilib_out
${wpi_protobuf_generate_PROTOC_OUT_DIR}
--plugin=protoc-gen-wpilib=${wpi_protobuf_generate_PLUGIN} ${_protobuf_include_path}
${_abs_file}
DEPENDS
${_abs_file}
protobuf::protoc
${wpi_protobuf_generate_DEPENDENCIES}
${wpi_protobuf_generate_PLUGIN}
COMMENT ${_comment}
VERBATIM
)
endforeach()
set_source_files_properties(${_generated_srcs_all} PROPERTIES GENERATED TRUE)
if(wpi_protobuf_generate_TARGET)
target_sources(${wpi_protobuf_generate_TARGET} PRIVATE ${_generated_srcs_all})
endif()
endfunction()

View File

@@ -1,4 +0,0 @@
set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version")
set(GNU_MACHINE "arm-frc2022-linux-gnueabi" CACHE STRING "GNU compiler triple")
set(SOFTFP yes)
include("${CMAKE_CURRENT_LIST_DIR}/arm.toolchain.cmake")

View File

@@ -1,98 +0,0 @@
set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version")
set(GNU_MACHINE "arm-raspbian10-linux-gnueabi" CACHE STRING "GNU compiler triple")
if(COMMAND toolchain_save_config)
return() # prevent recursive call
endif()
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 1)
if(NOT DEFINED CMAKE_SYSTEM_PROCESSOR)
set(CMAKE_SYSTEM_PROCESSOR arm)
else()
#message("CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}")
endif()
include("${CMAKE_CURRENT_LIST_DIR}/opencv/platforms/linux/gnu.toolchain.cmake")
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm AND NOT ARM_IGNORE_FP)
set(FLOAT_ABI_SUFFIX "")
if(NOT SOFTFP)
set(FLOAT_ABI_SUFFIX "hf")
endif()
endif()
if(NOT "x${GCC_COMPILER_VERSION}" STREQUAL "x")
set(__GCC_VER_SUFFIX "-${GCC_COMPILER_VERSION}")
endif()
if(NOT DEFINED CMAKE_C_COMPILER)
find_program(CMAKE_C_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-gcc${__GCC_VER_SUFFIX})
else()
#message(WARNING "CMAKE_C_COMPILER=${CMAKE_C_COMPILER} is defined")
endif()
if(NOT DEFINED CMAKE_CXX_COMPILER)
find_program(CMAKE_CXX_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-g++${__GCC_VER_SUFFIX})
else()
#message(WARNING "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} is defined")
endif()
if(NOT DEFINED CMAKE_LINKER)
find_program(CMAKE_LINKER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld)
else()
#message(WARNING "CMAKE_LINKER=${CMAKE_LINKER} is defined")
endif()
if(NOT DEFINED CMAKE_AR)
find_program(CMAKE_AR NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar)
else()
#message(WARNING "CMAKE_AR=${CMAKE_AR} is defined")
endif()
if(NOT DEFINED ARM_LINUX_SYSROOT AND DEFINED GNU_MACHINE)
set(ARM_LINUX_SYSROOT /usr/${GNU_MACHINE}${FLOAT_ABI_SUFFIX})
endif()
if(NOT DEFINED CMAKE_CXX_FLAGS)
set(CMAKE_CXX_FLAGS "" CACHE INTERNAL "")
set(CMAKE_C_FLAGS "" CACHE INTERNAL "")
set(CMAKE_SHARED_LINKER_FLAGS "" CACHE INTERNAL "")
set(CMAKE_MODULE_LINKER_FLAGS "" CACHE INTERNAL "")
set(CMAKE_EXE_LINKER_FLAGS "" CACHE INTERNAL "")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,nocopyreloc")
endif()
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm)
set(ARM_LINKER_FLAGS "-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
set(ARM_LINKER_FLAGS "-Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
endif()
set(CMAKE_SHARED_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}")
set(CMAKE_MODULE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}")
else()
#message(WARNING "CMAKE_CXX_FLAGS='${CMAKE_CXX_FLAGS}' is defined")
endif()
if(USE_NEON)
message(WARNING "You use obsolete variable USE_NEON to enable NEON instruction set. Use -DENABLE_NEON=ON instead." )
set(ENABLE_NEON TRUE)
elseif(USE_VFPV3)
message(WARNING "You use obsolete variable USE_VFPV3 to enable VFPV3 instruction set. Use -DENABLE_VFPV3=ON instead." )
set(ENABLE_VFPV3 TRUE)
endif()
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${ARM_LINUX_SYSROOT})
if(EXISTS ${CUDA_TOOLKIT_ROOT_DIR})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${CUDA_TOOLKIT_ROOT_DIR})
endif()
set(TOOLCHAIN_CONFIG_VARS ${TOOLCHAIN_CONFIG_VARS}
ARM_LINUX_SYSROOT
ENABLE_NEON
ENABLE_VFPV3
CUDA_TOOLKIT_ROOT_DIR
)
toolchain_save_config()

View File

@@ -1,97 +0,0 @@
if(COMMAND toolchain_save_config)
return() # prevent recursive call
endif()
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 1)
if(NOT DEFINED CMAKE_SYSTEM_PROCESSOR)
set(CMAKE_SYSTEM_PROCESSOR arm)
else()
#message("CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}")
endif()
include("${CMAKE_CURRENT_LIST_DIR}/gnu.toolchain.cmake")
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm AND NOT ARM_IGNORE_FP)
set(FLOAT_ABI_SUFFIX "")
if(NOT SOFTFP)
set(FLOAT_ABI_SUFFIX "hf")
endif()
endif()
if(NOT "x${GCC_COMPILER_VERSION}" STREQUAL "x")
set(__GCC_VER_SUFFIX "-${GCC_COMPILER_VERSION}")
endif()
if(NOT DEFINED CMAKE_C_COMPILER)
find_program(CMAKE_C_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-gcc${__GCC_VER_SUFFIX})
else()
#message(WARNING "CMAKE_C_COMPILER=${CMAKE_C_COMPILER} is defined")
endif()
if(NOT DEFINED CMAKE_CXX_COMPILER)
find_program(CMAKE_CXX_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-g++${__GCC_VER_SUFFIX})
else()
#message(WARNING "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} is defined")
endif()
if(NOT DEFINED CMAKE_LINKER)
find_program(CMAKE_LINKER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld)
else()
#message(WARNING "CMAKE_LINKER=${CMAKE_LINKER} is defined")
endif()
if(NOT DEFINED CMAKE_AR)
find_program(CMAKE_AR NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar)
else()
#message(WARNING "CMAKE_AR=${CMAKE_AR} is defined")
endif()
if(NOT DEFINED ARM_LINUX_SYSROOT AND DEFINED GNU_MACHINE)
set(ARM_LINUX_SYSROOT /usr/${GNU_MACHINE}${FLOAT_ABI_SUFFIX})
endif()
if(NOT DEFINED CMAKE_CXX_FLAGS)
set(CMAKE_CXX_FLAGS "" CACHE INTERNAL "")
set(CMAKE_C_FLAGS "" CACHE INTERNAL "")
set(CMAKE_SHARED_LINKER_FLAGS "" CACHE INTERNAL "")
set(CMAKE_MODULE_LINKER_FLAGS "" CACHE INTERNAL "")
set(CMAKE_EXE_LINKER_FLAGS "" CACHE INTERNAL "")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm)
set(CMAKE_CXX_FLAGS "-mthumb ${CMAKE_CXX_FLAGS}")
set(CMAKE_C_FLAGS "-mthumb ${CMAKE_C_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,nocopyreloc")
endif()
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm)
set(ARM_LINKER_FLAGS "-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
set(ARM_LINKER_FLAGS "-Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
endif()
set(CMAKE_SHARED_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}")
set(CMAKE_MODULE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}")
else()
#message(WARNING "CMAKE_CXX_FLAGS='${CMAKE_CXX_FLAGS}' is defined")
endif()
if(USE_NEON)
message(WARNING "You use obsolete variable USE_NEON to enable NEON instruction set. Use -DENABLE_NEON=ON instead." )
set(ENABLE_NEON TRUE)
elseif(USE_VFPV3)
message(WARNING "You use obsolete variable USE_VFPV3 to enable VFPV3 instruction set. Use -DENABLE_VFPV3=ON instead." )
set(ENABLE_VFPV3 TRUE)
endif()
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${ARM_LINUX_SYSROOT})
if(EXISTS ${CUDA_TOOLKIT_ROOT_DIR})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${CUDA_TOOLKIT_ROOT_DIR})
endif()
set(TOOLCHAIN_CONFIG_VARS ${TOOLCHAIN_CONFIG_VARS}
ARM_LINUX_SYSROOT
ENABLE_NEON
ENABLE_VFPV3
CUDA_TOOLKIT_ROOT_DIR
)
toolchain_save_config()

View File

@@ -1,134 +0,0 @@
cmake_minimum_required(VERSION 3.11)
# load settings in case of "try compile"
set(TOOLCHAIN_CONFIG_FILE "${WPILIB_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain.config.cmake")
get_property(__IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE)
if(__IN_TRY_COMPILE)
include("${CMAKE_CURRENT_SOURCE_DIR}/../toolchain.config.cmake" OPTIONAL) # WPILIB_BINARY_DIR is different
macro(toolchain_save_config)
# nothing
endmacro()
else()
macro(toolchain_save_config)
set(__config "#message(\"Load TOOLCHAIN config...\")\n")
get_cmake_property(__variableNames VARIABLES)
set(__vars_list ${ARGN})
list(APPEND __vars_list
${TOOLCHAIN_CONFIG_VARS}
CMAKE_SYSTEM_NAME
CMAKE_SYSTEM_VERSION
CMAKE_SYSTEM_PROCESSOR
CMAKE_C_COMPILER
CMAKE_CXX_COMPILER
CMAKE_C_FLAGS
CMAKE_CXX_FLAGS
CMAKE_SHARED_LINKER_FLAGS
CMAKE_MODULE_LINKER_FLAGS
CMAKE_EXE_LINKER_FLAGS
CMAKE_SKIP_RPATH
CMAKE_FIND_ROOT_PATH
GCC_COMPILER_VERSION
)
foreach(__var ${__variableNames})
foreach(_v ${__vars_list})
if("x${__var}" STREQUAL "x${_v}")
if(${__var} MATCHES " ")
set(__config "${__config}set(${__var} \"${${__var}}\")\n")
else()
set(__config "${__config}set(${__var} ${${__var}})\n")
endif()
endif()
endforeach()
endforeach()
if(EXISTS "${TOOLCHAIN_CONFIG_FILE}")
file(READ "${TOOLCHAIN_CONFIG_FILE}" __config_old)
endif()
if("${__config_old}" STREQUAL "${__config}")
# nothing
else()
#message("Update TOOLCHAIN config: ${__config}")
file(WRITE "${TOOLCHAIN_CONFIG_FILE}" "${__config}")
endif()
unset(__config)
unset(__config_old)
unset(__vars_list)
unset(__variableNames)
endmacro()
endif() # IN_TRY_COMPILE
if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
endif()
if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
endif()
if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
endif()
macro(__cmake_find_root_save_and_reset)
foreach(v
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM
)
set(__save_${v} ${${v}})
set(${v} NEVER)
endforeach()
endmacro()
macro(__cmake_find_root_restore)
foreach(v
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM
)
set(${v} ${__save_${v}})
unset(__save_${v})
endforeach()
endmacro()
# macro to find programs on the host OS
macro(find_host_program)
__cmake_find_root_save_and_reset()
if(CMAKE_HOST_WIN32)
SET(WIN32 1)
SET(UNIX)
elseif(CMAKE_HOST_APPLE)
SET(APPLE 1)
SET(UNIX)
endif()
find_program(${ARGN})
SET(WIN32)
SET(APPLE)
SET(UNIX 1)
__cmake_find_root_restore()
endmacro()
# macro to find packages on the host OS
macro(find_host_package)
__cmake_find_root_save_and_reset()
if(CMAKE_HOST_WIN32)
SET(WIN32 1)
SET(UNIX)
elseif(CMAKE_HOST_APPLE)
SET(APPLE 1)
SET(UNIX)
endif()
find_package(${ARGN})
SET(WIN32)
SET(APPLE)
SET(UNIX 1)
__cmake_find_root_restore()
endmacro()
set(CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries.")

21
cscore/BUILD.bazel Normal file
View File

@@ -0,0 +1,21 @@
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
java_library(
name = "cscore-java",
srcs = glob(["src/main/java/**/*.java"]),
visibility = ["//visibility:public"],
deps = [
"//wpiutil:wpiutil-java",
"@bzlmodrio-opencv//libraries/java/opencv",
],
)
java_binary(
name = "DevMain-Java",
srcs = ["src/dev/java/edu/wpi/first/cscore/DevMain.java"],
main_class = "edu.wpi.first.cscore.DevMain",
deps = [
":cscore-java",
"//wpiutil:wpiutil-java",
],
)

View File

@@ -164,19 +164,6 @@ run {
nativeUtils.exportsConfigs {
cscore {
x64ExcludeSymbols = [
'_CT??_R0?AV_System_error',
'_CT??_R0?AVexception',
'_CT??_R0?AVfailure',
'_CT??_R0?AVruntime_error',
'_CT??_R0?AVsystem_error',
'_CTA5?AVfailure',
'_TI5?AVfailure',
'_CT??_R0?AVout_of_range',
'_CTA3?AVout_of_range',
'_TI3?AVout_of_range',
'_CT??_R0?AVbad_cast'
]
}
cscoreJNI {
x64SymbolFilter = symbolFilter

View File

@@ -9,6 +9,7 @@
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <wpi/MemAlloc.h>
#include <wpi/StringExtras.h>
#include <wpi/timestamp.h>
@@ -294,12 +295,27 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
return false;
}
unsigned int contentLength = 0;
int width, height;
if (auto v = wpi::parse_integer<unsigned int>(contentLengthBuf, 10)) {
contentLength = v.value();
// We know how big it is! Just get a frame of the right size and read
// the data directly into it.
unsigned int contentLength = v.value();
auto image =
AllocImage(VideoMode::PixelFormat::kMJPEG, 0, 0, contentLength);
is.read(image->data(), contentLength);
if (!m_active || is.has_error()) {
return false;
}
if (!GetJpegSize(image->str(), &width, &height)) {
SWARNING("did not receive a JPEG image");
PutError("did not receive a JPEG image", wpi::Now());
return false;
}
image->width = width;
image->height = height;
PutFrame(std::move(image), wpi::Now());
} else {
// Ugh, no Content-Length? Read the blocks of the JPEG file.
int width, height;
if (!ReadJpeg(is, imageBuf, &width, &height)) {
SWARNING("did not receive a JPEG image");
PutError("did not receive a JPEG image", wpi::Now());
@@ -307,27 +323,18 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
}
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
wpi::Now());
++m_frameCount;
return true;
}
// We know how big it is! Just get a frame of the right size and read
// the data directly into it.
auto image = AllocImage(VideoMode::PixelFormat::kMJPEG, 0, 0, contentLength);
is.read(image->data(), contentLength);
if (!m_active || is.has_error()) {
return false;
}
int width, height;
if (!GetJpegSize(image->str(), &width, &height)) {
SWARNING("did not receive a JPEG image");
PutError("did not receive a JPEG image", wpi::Now());
return false;
}
image->width = width;
image->height = height;
PutFrame(std::move(image), wpi::Now());
++m_frameCount;
// update video mode if not set
std::scoped_lock lock(m_mutex);
if (m_mode.pixelFormat != VideoMode::PixelFormat::kMJPEG ||
m_mode.width == 0 || m_mode.height == 0) {
m_mode.pixelFormat = VideoMode::PixelFormat::kMJPEG;
m_mode.width = width;
m_mode.height = height;
}
return true;
}
@@ -518,6 +525,14 @@ bool HttpCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
}
std::scoped_lock lock(m_mutex);
m_mode = mode;
m_streamSettings.clear();
if (mode.width != 0 && mode.height != 0) {
m_streamSettings["resolution"] =
fmt::format("{}x{}", mode.width, mode.height);
}
if (mode.fps != 0) {
m_streamSettings["fps"] = fmt::format("{}", mode.fps);
}
m_streamSettingsUpdated = true;
return true;
}

View File

@@ -15,7 +15,6 @@
#include <thread>
#include <vector>
#include <wpi/SmallString.h>
#include <wpi/StringMap.h>
#include <wpi/condition_variable.h>
#include <wpi/raw_istream.h>
@@ -136,10 +135,10 @@ class HttpCameraImpl : public SourceImpl {
wpi::condition_variable m_sinkEnabledCond;
wpi::StringMap<wpi::SmallString<16>> m_settings;
wpi::StringMap<std::string> m_settings;
wpi::condition_variable m_settingsCond;
wpi::StringMap<wpi::SmallString<16>> m_streamSettings;
wpi::StringMap<std::string> m_streamSettings;
std::atomic_bool m_streamSettingsUpdated{false};
wpi::condition_variable m_monitorCond;

View File

@@ -98,6 +98,9 @@ void Instance::DestroySource(CS_Source handle) {
void Instance::DestroySink(CS_Sink handle) {
if (auto data = m_sinks.Free(handle)) {
if (auto source = data->sink->GetSource()) {
source->Wakeup();
}
notifier.NotifySink(data->sink->GetName(), handle, CS_SINK_DESTROYED);
}
}

View File

@@ -79,7 +79,8 @@ Frame SourceImpl::GetCurFrame() {
Frame SourceImpl::GetNextFrame() {
std::unique_lock lock{m_frameMutex};
auto oldTime = m_frame.GetTime();
m_frameCv.wait(lock, [=, this] { return m_frame.GetTime() != oldTime; });
m_frameCv.wait(
lock, [=, this] { return oldTime == 0 || m_frame.GetTime() != oldTime; });
return m_frame;
}

View File

@@ -34,7 +34,7 @@ class Telemetry::Thread : public wpi::SafeThread {
int64_t Telemetry::Thread::GetValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
auto it = m_user.find(std::make_pair(handle, static_cast<int>(kind)));
auto it = m_user.find(std::pair{handle, static_cast<int>(kind)});
if (it == m_user.end()) {
*status = CS_EMPTY_VALUE;
return 0;
@@ -136,8 +136,8 @@ void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) {
return;
}
auto handleData = Instance::GetInstance().FindSource(source);
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
static_cast<int>(CS_SOURCE_BYTES_RECEIVED))] +=
thr->m_current[std::pair{Handle{handleData.first, Handle::kSource},
static_cast<int>(CS_SOURCE_BYTES_RECEIVED)}] +=
quantity;
}
@@ -147,7 +147,7 @@ void Telemetry::RecordSourceFrames(const SourceImpl& source, int quantity) {
return;
}
auto handleData = Instance::GetInstance().FindSource(source);
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
static_cast<int>(CS_SOURCE_FRAMES_RECEIVED))] +=
thr->m_current[std::pair{Handle{handleData.first, Handle::kSource},
static_cast<int>(CS_SOURCE_FRAMES_RECEIVED)}] +=
quantity;
}

View File

@@ -183,10 +183,10 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FindIf(F func) {
for (size_t i = 0; i < m_structures.size(); i++) {
auto& structure = m_structures[i];
if (structure != nullptr && func(*structure)) {
return std::make_pair(MakeHandle(i), structure);
return std::pair{MakeHandle(i), structure};
}
}
return std::make_pair(0, nullptr);
return std::pair{0, nullptr};
}
} // namespace cs

File diff suppressed because it is too large Load Diff

View File

@@ -1,649 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef CSCORE_CSCORE_OO_INC_
#define CSCORE_CSCORE_OO_INC_
#include <functional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/deprecated.h>
#include "cscore_oo.h"
namespace cs {
inline std::string VideoProperty::GetName() const {
m_status = 0;
return GetPropertyName(m_handle, &m_status);
}
inline int VideoProperty::Get() const {
m_status = 0;
return GetProperty(m_handle, &m_status);
}
inline void VideoProperty::Set(int value) {
m_status = 0;
SetProperty(m_handle, value, &m_status);
}
inline int VideoProperty::GetMin() const {
m_status = 0;
return GetPropertyMin(m_handle, &m_status);
}
inline int VideoProperty::GetMax() const {
m_status = 0;
return GetPropertyMax(m_handle, &m_status);
}
inline int VideoProperty::GetStep() const {
m_status = 0;
return GetPropertyStep(m_handle, &m_status);
}
inline int VideoProperty::GetDefault() const {
m_status = 0;
return GetPropertyDefault(m_handle, &m_status);
}
inline std::string VideoProperty::GetString() const {
m_status = 0;
return GetStringProperty(m_handle, &m_status);
}
inline std::string_view VideoProperty::GetString(
wpi::SmallVectorImpl<char>& buf) const {
m_status = 0;
return GetStringProperty(m_handle, buf, &m_status);
}
inline void VideoProperty::SetString(std::string_view value) {
m_status = 0;
SetStringProperty(m_handle, value, &m_status);
}
inline std::vector<std::string> VideoProperty::GetChoices() const {
m_status = 0;
return GetEnumPropertyChoices(m_handle, &m_status);
}
inline VideoProperty::VideoProperty(CS_Property handle) : m_handle(handle) {
m_status = 0;
if (handle == 0) {
m_kind = kNone;
} else {
m_kind =
static_cast<Kind>(static_cast<int>(GetPropertyKind(handle, &m_status)));
}
}
inline VideoProperty::VideoProperty(CS_Property handle, Kind kind)
: m_handle(handle), m_kind(kind) {}
inline VideoSource::VideoSource(const VideoSource& source)
: m_handle(source.m_handle == 0 ? 0
: CopySource(source.m_handle, &m_status)) {}
inline VideoSource::VideoSource(VideoSource&& other) noexcept : VideoSource() {
swap(*this, other);
}
inline VideoSource& VideoSource::operator=(VideoSource other) noexcept {
swap(*this, other);
return *this;
}
inline VideoSource::~VideoSource() {
m_status = 0;
if (m_handle != 0) {
ReleaseSource(m_handle, &m_status);
}
}
inline VideoSource::Kind VideoSource::GetKind() const {
m_status = 0;
return static_cast<VideoSource::Kind>(GetSourceKind(m_handle, &m_status));
}
inline std::string VideoSource::GetName() const {
m_status = 0;
return GetSourceName(m_handle, &m_status);
}
inline std::string VideoSource::GetDescription() const {
m_status = 0;
return GetSourceDescription(m_handle, &m_status);
}
inline uint64_t VideoSource::GetLastFrameTime() const {
m_status = 0;
return GetSourceLastFrameTime(m_handle, &m_status);
}
inline void VideoSource::SetConnectionStrategy(ConnectionStrategy strategy) {
m_status = 0;
SetSourceConnectionStrategy(
m_handle, static_cast<CS_ConnectionStrategy>(static_cast<int>(strategy)),
&m_status);
}
inline bool VideoSource::IsConnected() const {
m_status = 0;
return IsSourceConnected(m_handle, &m_status);
}
inline bool VideoSource::IsEnabled() const {
m_status = 0;
return IsSourceEnabled(m_handle, &m_status);
}
inline VideoProperty VideoSource::GetProperty(std::string_view name) {
m_status = 0;
return VideoProperty{GetSourceProperty(m_handle, name, &m_status)};
}
inline VideoMode VideoSource::GetVideoMode() const {
m_status = 0;
return GetSourceVideoMode(m_handle, &m_status);
}
inline bool VideoSource::SetVideoMode(const VideoMode& mode) {
m_status = 0;
return SetSourceVideoMode(m_handle, mode, &m_status);
}
inline bool VideoSource::SetVideoMode(VideoMode::PixelFormat pixelFormat,
int width, int height, int fps) {
m_status = 0;
return SetSourceVideoMode(
m_handle, VideoMode{pixelFormat, width, height, fps}, &m_status);
}
inline bool VideoSource::SetPixelFormat(VideoMode::PixelFormat pixelFormat) {
m_status = 0;
return SetSourcePixelFormat(m_handle, pixelFormat, &m_status);
}
inline bool VideoSource::SetResolution(int width, int height) {
m_status = 0;
return SetSourceResolution(m_handle, width, height, &m_status);
}
inline bool VideoSource::SetFPS(int fps) {
m_status = 0;
return SetSourceFPS(m_handle, fps, &m_status);
}
inline bool VideoSource::SetConfigJson(std::string_view config) {
m_status = 0;
return SetSourceConfigJson(m_handle, config, &m_status);
}
inline bool VideoSource::SetConfigJson(const wpi::json& config) {
m_status = 0;
return SetSourceConfigJson(m_handle, config, &m_status);
}
inline std::string VideoSource::GetConfigJson() const {
m_status = 0;
return GetSourceConfigJson(m_handle, &m_status);
}
inline double VideoSource::GetActualFPS() const {
m_status = 0;
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_FRAMES_RECEIVED,
&m_status);
}
inline double VideoSource::GetActualDataRate() const {
m_status = 0;
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_BYTES_RECEIVED,
&m_status);
}
inline std::vector<VideoMode> VideoSource::EnumerateVideoModes() const {
CS_Status status = 0;
return EnumerateSourceVideoModes(m_handle, &status);
}
inline void VideoCamera::SetBrightness(int brightness) {
m_status = 0;
SetCameraBrightness(m_handle, brightness, &m_status);
}
inline int VideoCamera::GetBrightness() {
m_status = 0;
return GetCameraBrightness(m_handle, &m_status);
}
inline void VideoCamera::SetWhiteBalanceAuto() {
m_status = 0;
SetCameraWhiteBalanceAuto(m_handle, &m_status);
}
inline void VideoCamera::SetWhiteBalanceHoldCurrent() {
m_status = 0;
SetCameraWhiteBalanceHoldCurrent(m_handle, &m_status);
}
inline void VideoCamera::SetWhiteBalanceManual(int value) {
m_status = 0;
SetCameraWhiteBalanceManual(m_handle, value, &m_status);
}
inline void VideoCamera::SetExposureAuto() {
m_status = 0;
SetCameraExposureAuto(m_handle, &m_status);
}
inline void VideoCamera::SetExposureHoldCurrent() {
m_status = 0;
SetCameraExposureHoldCurrent(m_handle, &m_status);
}
inline void VideoCamera::SetExposureManual(int value) {
m_status = 0;
SetCameraExposureManual(m_handle, value, &m_status);
}
inline UsbCamera::UsbCamera(std::string_view name, int dev) {
m_handle = CreateUsbCameraDev(name, dev, &m_status);
}
inline UsbCamera::UsbCamera(std::string_view name, std::string_view path) {
m_handle = CreateUsbCameraPath(name, path, &m_status);
}
inline std::vector<UsbCameraInfo> UsbCamera::EnumerateUsbCameras() {
CS_Status status = 0;
return ::cs::EnumerateUsbCameras(&status);
}
inline void UsbCamera::SetPath(std::string_view path) {
m_status = 0;
return ::cs::SetUsbCameraPath(m_handle, path, &m_status);
}
inline std::string UsbCamera::GetPath() const {
m_status = 0;
return ::cs::GetUsbCameraPath(m_handle, &m_status);
}
inline UsbCameraInfo UsbCamera::GetInfo() const {
m_status = 0;
return ::cs::GetUsbCameraInfo(m_handle, &m_status);
}
inline void UsbCamera::SetConnectVerbose(int level) {
m_status = 0;
SetProperty(GetSourceProperty(m_handle, "connect_verbose", &m_status), level,
&m_status);
}
inline HttpCamera::HttpCamera(std::string_view name, std::string_view url,
HttpCameraKind kind) {
m_handle = CreateHttpCamera(
name, url, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
inline HttpCamera::HttpCamera(std::string_view name, const char* url,
HttpCameraKind kind) {
m_handle = CreateHttpCamera(
name, url, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
inline HttpCamera::HttpCamera(std::string_view name, const std::string& url,
HttpCameraKind kind)
: HttpCamera(name, std::string_view{url}, kind) {}
inline HttpCamera::HttpCamera(std::string_view name,
std::span<const std::string> urls,
HttpCameraKind kind) {
m_handle = CreateHttpCamera(
name, urls, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
template <typename T>
inline HttpCamera::HttpCamera(std::string_view name,
std::initializer_list<T> urls,
HttpCameraKind kind) {
std::vector<std::string> vec;
vec.reserve(urls.size());
for (const auto& url : urls) {
vec.emplace_back(url);
}
m_handle = CreateHttpCamera(
name, vec, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
&m_status);
}
inline HttpCamera::HttpCameraKind HttpCamera::GetHttpCameraKind() const {
m_status = 0;
return static_cast<HttpCameraKind>(
static_cast<int>(::cs::GetHttpCameraKind(m_handle, &m_status)));
}
inline void HttpCamera::SetUrls(std::span<const std::string> urls) {
m_status = 0;
::cs::SetHttpCameraUrls(m_handle, urls, &m_status);
}
template <typename T>
inline void HttpCamera::SetUrls(std::initializer_list<T> urls) {
std::vector<std::string> vec;
vec.reserve(urls.size());
for (const auto& url : urls) {
vec.emplace_back(url);
}
m_status = 0;
::cs::SetHttpCameraUrls(m_handle, vec, &m_status);
}
inline std::vector<std::string> HttpCamera::GetUrls() const {
m_status = 0;
return ::cs::GetHttpCameraUrls(m_handle, &m_status);
}
WPI_IGNORE_DEPRECATED
inline std::vector<std::string> AxisCamera::HostToUrl(
std::span<const std::string> hosts) {
std::vector<std::string> rv;
rv.reserve(hosts.size());
for (const auto& host : hosts) {
rv.emplace_back(HostToUrl(std::string_view{host}));
}
return rv;
}
template <typename T>
inline std::vector<std::string> AxisCamera::HostToUrl(
std::initializer_list<T> hosts) {
std::vector<std::string> rv;
rv.reserve(hosts.size());
for (const auto& host : hosts) {
rv.emplace_back(HostToUrl(std::string_view{host}));
}
return rv;
}
inline AxisCamera::AxisCamera(std::string_view name, std::string_view host)
: HttpCamera(name, HostToUrl(host), kAxis) {}
inline AxisCamera::AxisCamera(std::string_view name, const char* host)
: HttpCamera(name, HostToUrl(host), kAxis) {}
inline AxisCamera::AxisCamera(std::string_view name, const std::string& host)
: HttpCamera(name, HostToUrl(std::string_view{host}), kAxis) {}
inline AxisCamera::AxisCamera(std::string_view name,
std::span<const std::string> hosts)
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
template <typename T>
inline AxisCamera::AxisCamera(std::string_view name,
std::initializer_list<T> hosts)
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
WPI_UNIGNORE_DEPRECATED
inline void ImageSource::NotifyError(std::string_view msg) {
m_status = 0;
NotifySourceError(m_handle, msg, &m_status);
}
inline void ImageSource::SetConnected(bool connected) {
m_status = 0;
SetSourceConnected(m_handle, connected, &m_status);
}
inline void ImageSource::SetDescription(std::string_view description) {
m_status = 0;
SetSourceDescription(m_handle, description, &m_status);
}
inline VideoProperty ImageSource::CreateProperty(std::string_view name,
VideoProperty::Kind kind,
int minimum, int maximum,
int step, int defaultValue,
int value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(kind)),
minimum, maximum, step, defaultValue, value, &m_status)};
}
inline VideoProperty ImageSource::CreateIntegerProperty(std::string_view name,
int minimum,
int maximum, int step,
int defaultValue,
int value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kInteger)),
minimum, maximum, step, defaultValue, value, &m_status)};
}
inline VideoProperty ImageSource::CreateBooleanProperty(std::string_view name,
bool defaultValue,
bool value) {
m_status = 0;
return VideoProperty{CreateSourceProperty(
m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kBoolean)),
0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0, &m_status)};
}
inline VideoProperty ImageSource::CreateStringProperty(std::string_view name,
std::string_view value) {
m_status = 0;
auto prop = VideoProperty{
CreateSourceProperty(m_handle, name,
static_cast<CS_PropertyKind>(
static_cast<int>(VideoProperty::Kind::kString)),
0, 0, 0, 0, 0, &m_status)};
prop.SetString(value);
return prop;
}
inline void ImageSource::SetEnumPropertyChoices(
const VideoProperty& property, std::span<const std::string> choices) {
m_status = 0;
SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status);
}
template <typename T>
inline void ImageSource::SetEnumPropertyChoices(
const VideoProperty& property, std::initializer_list<T> choices) {
std::vector<std::string> vec;
vec.reserve(choices.size());
for (const auto& choice : choices) {
vec.emplace_back(choice);
}
m_status = 0;
SetSourceEnumPropertyChoices(m_handle, property.m_handle, vec, &m_status);
}
inline VideoSink::VideoSink(const VideoSink& sink)
: m_handle(sink.m_handle == 0 ? 0 : CopySink(sink.m_handle, &m_status)) {}
inline VideoSink::VideoSink(VideoSink&& other) noexcept : VideoSink() {
swap(*this, other);
}
inline VideoSink& VideoSink::operator=(VideoSink other) noexcept {
swap(*this, other);
return *this;
}
inline VideoSink::~VideoSink() {
m_status = 0;
if (m_handle != 0) {
ReleaseSink(m_handle, &m_status);
}
}
inline VideoSink::Kind VideoSink::GetKind() const {
m_status = 0;
return static_cast<VideoSink::Kind>(GetSinkKind(m_handle, &m_status));
}
inline std::string VideoSink::GetName() const {
m_status = 0;
return GetSinkName(m_handle, &m_status);
}
inline std::string VideoSink::GetDescription() const {
m_status = 0;
return GetSinkDescription(m_handle, &m_status);
}
inline VideoProperty VideoSink::GetProperty(std::string_view name) {
m_status = 0;
return VideoProperty{GetSinkProperty(m_handle, name, &m_status)};
}
inline void VideoSink::SetSource(VideoSource source) {
m_status = 0;
if (!source) {
SetSinkSource(m_handle, 0, &m_status);
} else {
SetSinkSource(m_handle, source.m_handle, &m_status);
}
}
inline VideoSource VideoSink::GetSource() const {
m_status = 0;
auto handle = GetSinkSource(m_handle, &m_status);
return VideoSource{handle == 0 ? 0 : CopySource(handle, &m_status)};
}
inline VideoProperty VideoSink::GetSourceProperty(std::string_view name) {
m_status = 0;
return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)};
}
inline bool VideoSink::SetConfigJson(std::string_view config) {
m_status = 0;
return SetSinkConfigJson(m_handle, config, &m_status);
}
inline bool VideoSink::SetConfigJson(const wpi::json& config) {
m_status = 0;
return SetSinkConfigJson(m_handle, config, &m_status);
}
inline std::string VideoSink::GetConfigJson() const {
m_status = 0;
return GetSinkConfigJson(m_handle, &m_status);
}
inline MjpegServer::MjpegServer(std::string_view name,
std::string_view listenAddress, int port) {
m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);
}
inline std::string MjpegServer::GetListenAddress() const {
m_status = 0;
return cs::GetMjpegServerListenAddress(m_handle, &m_status);
}
inline int MjpegServer::GetPort() const {
m_status = 0;
return cs::GetMjpegServerPort(m_handle, &m_status);
}
inline void MjpegServer::SetResolution(int width, int height) {
m_status = 0;
SetProperty(GetSinkProperty(m_handle, "width", &m_status), width, &m_status);
SetProperty(GetSinkProperty(m_handle, "height", &m_status), height,
&m_status);
}
inline void MjpegServer::SetFPS(int fps) {
m_status = 0;
SetProperty(GetSinkProperty(m_handle, "fps", &m_status), fps, &m_status);
}
inline void MjpegServer::SetCompression(int quality) {
m_status = 0;
SetProperty(GetSinkProperty(m_handle, "compression", &m_status), quality,
&m_status);
}
inline void MjpegServer::SetDefaultCompression(int quality) {
m_status = 0;
SetProperty(GetSinkProperty(m_handle, "default_compression", &m_status),
quality, &m_status);
}
inline void ImageSink::SetDescription(std::string_view description) {
m_status = 0;
SetSinkDescription(m_handle, description, &m_status);
}
inline std::string ImageSink::GetError() const {
m_status = 0;
return GetSinkError(m_handle, &m_status);
}
inline void ImageSink::SetEnabled(bool enabled) {
m_status = 0;
SetSinkEnabled(m_handle, enabled, &m_status);
}
inline VideoSource VideoEvent::GetSource() const {
CS_Status status = 0;
return VideoSource{sourceHandle == 0 ? 0 : CopySource(sourceHandle, &status)};
}
inline VideoSink VideoEvent::GetSink() const {
CS_Status status = 0;
return VideoSink{sinkHandle == 0 ? 0 : CopySink(sinkHandle, &status)};
}
inline VideoProperty VideoEvent::GetProperty() const {
return VideoProperty{propertyHandle,
static_cast<VideoProperty::Kind>(propertyKind)};
}
inline VideoListener::VideoListener(
std::function<void(const VideoEvent& event)> callback, int eventMask,
bool immediateNotify) {
CS_Status status = 0;
m_handle = AddListener(
[=](const RawEvent& event) {
callback(static_cast<const VideoEvent&>(event));
},
eventMask, immediateNotify, &status);
}
inline VideoListener::VideoListener(VideoListener&& other) noexcept
: VideoListener() {
swap(*this, other);
}
inline VideoListener& VideoListener::operator=(VideoListener&& other) noexcept {
swap(*this, other);
return *this;
}
inline VideoListener::~VideoListener() {
CS_Status status = 0;
if (m_handle != 0) {
RemoveListener(m_handle, &status);
}
}
} // namespace cs
#endif // CSCORE_CSCORE_OO_INC_

View File

@@ -75,25 +75,6 @@ size_t File::Read(void* buf, uint32_t count) {
return rv;
}
File::AsyncId File::AsyncReadBegin(uint32_t len) const {
int rv = sftp_async_read_begin(m_handle, len);
if (rv < 0) {
throw Exception{m_handle->sftp};
}
return rv;
}
size_t File::AsyncRead(void* data, uint32_t len, AsyncId id) {
auto rv = sftp_async_read(m_handle, data, len, id);
if (rv == SSH_ERROR) {
throw Exception{ssh_get_error(m_handle->sftp->session)};
}
if (rv == SSH_AGAIN) {
return 0;
}
return rv;
}
size_t File::Write(std::span<const uint8_t> data) {
auto rv = sftp_write(m_handle, data.data(), data.size());
if (rv < 0) {

View File

@@ -47,11 +47,7 @@ class File {
void SetNonblocking() { sftp_file_set_nonblocking(m_handle); }
void SetBlocking() { sftp_file_set_blocking(m_handle); }
using AsyncId = uint32_t;
size_t Read(void* buf, uint32_t count);
AsyncId AsyncReadBegin(uint32_t len) const;
size_t AsyncRead(void* data, uint32_t len, AsyncId id);
size_t Write(std::span<const uint8_t> data);
void Seek(uint64_t offset);

View File

@@ -9,7 +9,12 @@ This command builds everything.
## Simulation
This command runs the C++ subproject on desktop.
This command runs the Java project on desktop.
```bash
./gradlew developerRobot:run
```
This command runs the C++ project on desktop.
```bash
./gradlew developerRobot:runCpp
```

View File

@@ -35,7 +35,7 @@ application {
mainClass = 'frc.robot.Main'
}
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'com.gradleup.shadow'
repositories {
maven {
@@ -54,6 +54,9 @@ dependencies {
implementation project(':cameraserver')
implementation project(':wpilibNewCommands')
implementation project(':apriltag')
implementation project(':wpiunits')
implementation project(':epilogue-runtime')
annotationProcessor project(':epilogue-processor')
}
tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach {

View File

@@ -1,6 +1,6 @@
plugins {
id 'java'
id "org.ysb33r.doxygen" version "1.0.3"
id "org.ysb33r.doxygen" version "1.0.4"
}
evaluationDependsOn(':apriltag')

View File

@@ -11,9 +11,12 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
@@ -86,16 +89,19 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Custom logger classes should have a @CustomLoggerFor annotation",
"[EPILOGUE] Custom logger classes should have a @CustomLoggerFor annotation",
e);
});
var loggedTypes = getLoggedTypes(roundEnv);
// Handlers are declared in order of priority. If an element could be logged in more than one
// way (eg a class implements both Sendable and StructSerializable), the order of the handlers
// in this list will determine how it gets logged.
m_handlers =
List.of(
new LoggableHandler(processingEnv), // prioritize epilogue logging over Sendable
new LoggableHandler(
processingEnv, loggedTypes), // prioritize epilogue logging over Sendable
new ConfiguredLoggerHandler(
processingEnv, customLoggers), // then customized logging configs
new ArrayHandler(processingEnv),
@@ -115,12 +121,39 @@ public class AnnotationProcessor extends AbstractProcessor {
.findAny()
.ifPresent(
epilogue -> {
processEpilogue(roundEnv, epilogue);
processEpilogue(roundEnv, epilogue, loggedTypes);
});
return false;
}
/**
* Gets the set of all loggable types in the compilation unit. A type is considered loggable if it
* is directly annotated with {@code @Logged} or contains a field or method with a {@code @Logged}
* annotation.
*
* @param roundEnv the compilation round environment
* @return the set of all loggable types
*/
private Set<TypeElement> getLoggedTypes(RoundEnvironment roundEnv) {
// Fetches everything annotated with @Logged; classes, methods, values, etc.
var annotatedElements = roundEnv.getElementsAnnotatedWith(Logged.class);
return Stream.concat(
// 1. All type elements (classes, interfaces, or enums) with the @Logged annotation
annotatedElements.stream()
.filter(e -> e instanceof TypeElement)
.map(e -> (TypeElement) e),
// 2. All type elements containing a field or method with the @Logged annotation
annotatedElements.stream()
.filter(e -> e instanceof VariableElement || e instanceof ExecutableElement)
.map(Element::getEnclosingElement)
.filter(e -> e instanceof TypeElement)
.map(e -> (TypeElement) e))
.sorted(Comparator.comparing(e -> e.getSimpleName().toString()))
.collect(
Collectors.toCollection(LinkedHashSet::new)); // Collect to a set to avoid duplicates
}
private boolean validateFields(Set<? extends Element> annotatedElements) {
var fields =
annotatedElements.stream()
@@ -281,7 +314,7 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Logger classes must have a public no-argument constructor",
"[EPILOGUE] Logger classes must have a public no-argument constructor",
annotatedElement);
continue;
}
@@ -303,7 +336,17 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Multiple custom loggers detected for type " + targetType,
"[EPILOGUE] Multiple custom loggers detected for type " + targetType,
annotatedElement);
continue;
}
if (annotatedElement instanceof TypeElement t && !t.getTypeParameters().isEmpty()) {
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"[EPILOGUE] Custom logger classes cannot take generic type arguments",
annotatedElement);
continue;
}
@@ -315,7 +358,7 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Not a subclass of ClassSpecificLogger<" + targetType + ">",
"[EPILOGUE] Not a subclass of ClassSpecificLogger<" + targetType + ">",
annotatedElement);
continue;
}
@@ -327,7 +370,8 @@ public class AnnotationProcessor extends AbstractProcessor {
return customLoggers;
}
private void processEpilogue(RoundEnvironment roundEnv, TypeElement epilogueAnnotation) {
private void processEpilogue(
RoundEnvironment roundEnv, TypeElement epilogueAnnotation, Set<TypeElement> loggedTypes) {
var annotatedElements = roundEnv.getElementsAnnotatedWith(epilogueAnnotation);
List<String> loggerClassNames = new ArrayList<>();
@@ -345,12 +389,7 @@ public class AnnotationProcessor extends AbstractProcessor {
return;
}
var classes =
annotatedElements.stream()
.filter(e -> e instanceof TypeElement)
.map(e -> (TypeElement) e)
.toList();
for (TypeElement clazz : classes) {
for (TypeElement clazz : loggedTypes) {
try {
warnOfNonLoggableElements(clazz);
m_loggerGenerator.writeLoggerFile(clazz);
@@ -365,7 +404,7 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Could not write logger file for " + clazz.getQualifiedName(),
"[EPILOGUE] Could not write logger file for " + clazz.getQualifiedName(),
clazz);
e.printStackTrace(System.err);
}
@@ -378,7 +417,7 @@ public class AnnotationProcessor extends AbstractProcessor {
private void warnOfNonLoggableElements(TypeElement clazz) {
var config = clazz.getAnnotation(Logged.class);
if (config.strategy() == Logged.Strategy.OPT_IN) {
if (config == null || config.strategy() == Logged.Strategy.OPT_IN) {
// field and method validations will have already checked everything
return;
}

View File

@@ -60,7 +60,7 @@ public class ArrayHandler extends ElementHandler {
if (m_structHandler.isLoggableType(componentType)) {
// Struct arrays need to pass in the struct serializer
return "dataLogger.log(\""
return "backend.log(\""
+ loggedName(element)
+ "\", "
+ elementAccess(element)
@@ -69,7 +69,7 @@ public class ArrayHandler extends ElementHandler {
+ ")";
} else {
// Primitive or string array
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
}
}
}

View File

@@ -43,7 +43,7 @@ public class CollectionHandler extends ElementHandler {
var componentType = ((DeclaredType) dataType).getTypeArguments().get(0);
if (m_structHandler.isLoggableType(componentType)) {
return "dataLogger.log(\""
return "backend.log(\""
+ loggedName(element)
+ "\", "
+ elementAccess(element)
@@ -51,7 +51,7 @@ public class CollectionHandler extends ElementHandler {
+ m_structHandler.structAccess(componentType)
+ ")";
} else {
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
}
}
}

View File

@@ -22,17 +22,26 @@ public class ConfiguredLoggerHandler extends ElementHandler {
@Override
public boolean isLoggable(Element element) {
return m_customLoggers.containsKey(dataType(element));
return m_customLoggers.keySet().stream()
.anyMatch(m -> m_processingEnv.getTypeUtils().isAssignable(dataType(element), m));
}
@Override
public String logInvocation(Element element) {
var dataType = dataType(element);
var loggerType = m_customLoggers.get(dataType);
var loggerType =
m_customLoggers.entrySet().stream()
.filter(
e -> {
return m_processingEnv.getTypeUtils().isAssignable(dataType, e.getKey());
})
.findFirst()
.orElseThrow()
.getValue();
return "Epilogue."
+ StringUtils.lowerCamelCase(loggerType.asElement().getSimpleName())
+ ".tryUpdate(dataLogger.getSubLogger(\""
+ ".tryUpdate(backend.getNested(\""
+ loggedName(element)
+ "\"), "
+ elementAccess(element)

View File

@@ -6,7 +6,7 @@ package edu.wpi.first.epilogue.processor;
import edu.wpi.first.epilogue.Logged;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.DataLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
@@ -61,15 +61,46 @@ public abstract class ElementHandler {
* @return the name specified in the {@link Logged @Logged} annotation on the element, if present;
* otherwise, the field or method's name with no modifications
*/
public String loggedName(Element element) {
var elementName = element.getSimpleName().toString();
var config = element.getAnnotation(Logged.class);
public static String loggedName(Element element) {
var elementConfig = element.getAnnotation(Logged.class);
if (config != null && !config.name().isBlank()) {
return config.name();
} else {
return elementName;
// Use the name provided on the logged element, if one is present
if (elementConfig != null && !elementConfig.name().isBlank()) {
return elementConfig.name();
}
var config = elementConfig;
if (config == null) {
// Look up the parent class configuration
// We assume one is present, since logged elements should only be found if the enclosing class
// is @Logged itself
Logged parentConfig = null;
for (var parent = element.getEnclosingElement();
parent != null;
parent = parent.getEnclosingElement()) {
parentConfig = parent.getAnnotation(Logged.class);
if (parentConfig != null) {
break;
}
}
config = parentConfig;
}
if (config == null) {
// Uh oh
throw new IllegalStateException(
"Could not generate a name for element "
+ element
+ " without a @Logged annotation AND without being contained within a class with a @Logged annotation!\n\nOpen an issue at https://github.com/wpilibsuite/allwpilib/issues and include a copy of the file that caused this error.");
}
var elementName = element.getSimpleName().toString();
return switch (config.defaultNaming()) {
case USE_CODE_NAME -> elementName;
case USE_HUMAN_NAME -> StringUtils.toHumanName(elementName);
};
}
/**
@@ -96,8 +127,10 @@ public abstract class ElementHandler {
private static String fieldAccess(VariableElement field) {
if (field.getModifiers().contains(Modifier.PRIVATE)) {
// (com.example.Foo) $fooField.get(object)
return "(" + field.asType() + ") $" + field.getSimpleName() + ".get(object)";
// ((com.example.Foo) $fooField.get(object))
// Extra parentheses so cast evaluates before appended methods
// (e.g. when appending .getAsDouble())
return "((" + field.asType() + ") $" + field.getSimpleName() + ".get(object))";
} else {
// object.fooField
return "object." + field.getSimpleName();
@@ -126,7 +159,7 @@ public abstract class ElementHandler {
/**
* Generates a code snippet to place in a generated logger file to log the value of a field or
* method. Log invocations are placed in a generated implementation of {@link
* ClassSpecificLogger#update(DataLogger, Object)}, with access to the data logger and logged
* ClassSpecificLogger#update(EpilogueBackend, Object)}, with access to the backend and logged
* object passed to the method call.
*
* @param element the field or method element to generate the logger call for

View File

@@ -28,6 +28,6 @@ public class EnumHandler extends ElementHandler {
@Override
public String logInvocation(Element element) {
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
}
}

View File

@@ -55,6 +55,10 @@ public class EpilogueGenerator {
out.println("import static edu.wpi.first.units.Units.Seconds;");
out.println();
out.println("import edu.wpi.first.hal.FRCNetComm;");
out.println("import edu.wpi.first.hal.HAL;");
out.println();
loggerClassNames.stream()
.sorted()
.forEach(
@@ -80,6 +84,18 @@ public class EpilogueGenerator {
out.println();
out.println("public final class Epilogue {");
// Usage reporting
out.println(
"""
static {
HAL.report(
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
FRCNetComm.tInstances.kLoggingFramework_Epilogue
);
}
""");
out.println(
" private static final EpilogueConfiguration config = new EpilogueConfiguration();");
out.println();
@@ -154,9 +170,9 @@ public class EpilogueGenerator {
out.println(
" "
+ StringUtils.loggerFieldName(mainRobotClass)
+ ".tryUpdate(config.dataLogger.getSubLogger(config.root), robot, config.errorHandler);");
+ ".tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);");
out.println(
" config.dataLogger.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);");
" config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);");
out.println(" }");
out.println();
@@ -176,7 +192,7 @@ public class EpilogueGenerator {
out.println(" config.loggingPeriod = Seconds.of(robot.getPeriod());");
out.println(" }");
out.println(" if (config.loggingPeriodOffset == null) {");
out.println(" config.loggingPeriodOffset = config.loggingPeriod.divide(2);");
out.println(" config.loggingPeriodOffset = config.loggingPeriod.div(2);");
out.println(" }");
out.println();
out.println(" robot.addPeriodic(() -> {");

View File

@@ -5,39 +5,201 @@
package edu.wpi.first.epilogue.processor;
import edu.wpi.first.epilogue.Logged;
import java.util.Collection;
import java.util.Comparator;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
/** Handles logging for types annotated with the {@link Logged @Logged} annotation. */
public class LoggableHandler extends ElementHandler {
protected LoggableHandler(ProcessingEnvironment processingEnv) {
private final Set<TypeElement> m_loggedTypes;
protected LoggableHandler(
ProcessingEnvironment processingEnv, Collection<? extends Element> loggedTypes) {
super(processingEnv);
m_loggedTypes =
loggedTypes.stream()
.filter(e -> e instanceof TypeElement)
.map(e -> (TypeElement) e)
.collect(Collectors.toSet());
}
@Override
public boolean isLoggable(Element element) {
var dataType = dataType(element);
return dataType.getAnnotation(Logged.class) != null
|| dataType instanceof DeclaredType decl
&& decl.asElement().getAnnotation(Logged.class) != null;
return m_processingEnv.getTypeUtils().asElement(dataType(element)) instanceof TypeElement t
&& m_loggedTypes.contains(t);
}
@Override
public String logInvocation(Element element) {
TypeMirror dataType = dataType(element);
var reflectedType =
var declaredType =
m_processingEnv
.getElementUtils()
.getTypeElement(m_processingEnv.getTypeUtils().erasure(dataType).toString());
return "Epilogue."
+ StringUtils.loggerFieldName(reflectedType)
+ ".tryUpdate(dataLogger.getSubLogger(\""
+ loggedName(element)
+ "\"), "
+ elementAccess(element)
+ ", Epilogue.getConfig().errorHandler)";
// Get the list of known loggable subtypes of the input type. This will include the input type.
// These are sorted by their distance from the declared type such that "more concrete" types are
// checked first so the instanceof chain doesn't check a really generic type first, even if a
// more specific loggable type could be used instead.
var loggableSubtypes =
m_loggedTypes.stream()
.filter(
l -> m_processingEnv.getTypeUtils().isAssignable(l.asType(), declaredType.asType()))
.sorted(inheritanceComparatorFor(declaredType))
.toList();
int size = loggableSubtypes.size();
// If there are no known loggable subtypes, return just the single logger call
if (size == 1) {
return generateLoggerCall(element, declaredType, elementAccess(element));
}
// Otherwise, generate an if-else chain to compare the element with its known loggable subtypes
// and implementations. A subclass without a @Logged annotation will be logged at runtime using
// the generic logger for whatever the field or method's declared type is.
String varName = cacheVariableName(element);
StringBuilder builder = new StringBuilder();
// Cache the value in a variable so it's only read once
builder.append("var %s = %s;\n".formatted(varName, elementAccess(element)));
for (int i = 0; i < size; i++) {
TypeElement type = loggableSubtypes.get(i);
String part;
if (i == 0) {
// First invocation, generate an "if" statement
part = generateIf(type, element, "if", varName);
} else if (i == size - 1) {
// Final invocation, generate an "else" statement
String loggerCall = generateLoggerCall(element, type, varName);
part =
" else {\n // Base type %s\n %s;\n}"
.formatted(declaredType.getQualifiedName(), loggerCall);
} else {
// Somewhere in the middle, generate an "else if" statement
part = generateIf(type, element, " else if", varName);
}
builder.append(part);
}
return builder.toString();
}
/**
* Generates the name of the cache variable to use for a logged element.
*
* @param element the logged element
* @return the cache variable name
*/
private static String cacheVariableName(Element element) {
// Generate unique names in case a field and a method share the same name
if (element instanceof VariableElement) {
return "$$%s".formatted(element.getSimpleName().toString());
} else if (element instanceof ExecutableElement) {
return "__%s".formatted(element.getSimpleName().toString());
} else {
// Generic fallback (shouldn't get here, since only fields and methods are logged)
return element.getSimpleName().toString();
}
}
/**
* Creates a comparator for sorting inheritors of a given type by their distance (farthest first)
* for use in generating if-else instanceof chains. Inheritors at the same distance from the base
* type will be further compared so classes come before interfaces, any any further ties are
* broken alphabetically by fully-qualified type names.
*
* @param declaredType the declared type
* @return the comparator
*/
private Comparator<TypeElement> inheritanceComparatorFor(TypeElement declaredType) {
Comparator<TypeElement> byDistance =
Comparator.comparingInt(
inheritor -> {
return inheritanceDistance(inheritor.asType(), declaredType.asType());
});
return byDistance
.reversed()
.thenComparing(type -> type.getKind() == ElementKind.INTERFACE ? 1 : 0)
.thenComparing(type -> type.getQualifiedName().toString());
}
/**
* Generates an instanceof if or if-else statement that checks the type and logs the element using
* the logger for the given type, if they're compatible.
*
* @param type the type to generate the check for
* @param element the element to be logged
* @param keyword either "if" or " else if"
* @param varName the name of the variable in the "instanceof" check
* @return the if or else-if statement
*/
private String generateIf(TypeElement type, Element element, String keyword, String varName) {
String ref = type.getQualifiedName().toString().replace('.', '_');
String loggerCall = generateLoggerCall(element, type, ref);
return "%s (%s instanceof %s %s) {\n %s;\n}"
.formatted(keyword, varName, type.getQualifiedName(), ref, loggerCall);
}
private String generateLoggerCall(Element element, TypeElement type, String elementReference) {
return ("Epilogue.%s.tryUpdate(backend.getNested(\"%s\"), %s, "
+ "Epilogue.getConfig().errorHandler)")
.formatted(StringUtils.loggerFieldName(type), loggedName(element), elementReference);
}
/**
* Computes the minimum inheritance distance between two types; that is, how many "extends" or
* "implements" clauses are required to get from one to the other.
*
* @param toCheck the type to check
* @param base the base type to check against
* @return the inheritance distance
*/
private int inheritanceDistance(TypeMirror toCheck, TypeMirror base) {
var types = m_processingEnv.getTypeUtils();
if (types.isSameType(toCheck, base)) {
return 0;
}
int distance = 1;
var parent = toCheck;
TypeElement element = m_processingEnv.getElementUtils().getTypeElement(parent.toString());
while (!types.isSameType(parent, base)
&& element.getInterfaces().stream().noneMatch(i -> types.isSameType(i, base))) {
element = m_processingEnv.getElementUtils().getTypeElement(parent.toString());
if (parent.getKind() == TypeKind.NONE) {
// Interface inheritance, there is no superclass
break;
}
parent = element.getSuperclass();
// Handle cases of interface inheritance
distance =
1
+ element.getInterfaces().stream()
.mapToInt(iface -> inheritanceDistance(iface, base))
.min()
.orElse(distance);
}
return distance;
}
}

View File

@@ -7,25 +7,69 @@ package edu.wpi.first.epilogue.processor;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.source.util.Trees;
import edu.wpi.first.epilogue.Logged;
import edu.wpi.first.epilogue.NotLogged;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
/** Generates logger class files for {@link Logged @Logged}-annotated classes. */
public class LoggerGenerator {
public static final Predicate<ExecutableElement> kIsBuiltInJavaMethod =
LoggerGenerator::isBuiltInJavaMethod;
private final ProcessingEnvironment m_processingEnv;
private final List<ElementHandler> m_handlers;
private final Logged m_defaultConfig =
new Logged() {
@Override
public Class<? extends Annotation> annotationType() {
return Logged.class;
}
@Override
public String name() {
return "";
}
@Override
public Strategy strategy() {
return Strategy.OPT_IN;
}
@Override
public Importance importance() {
return Importance.DEBUG;
}
@Override
public Naming defaultNaming() {
return Naming.USE_CODE_NAME;
}
};
public LoggerGenerator(ProcessingEnvironment processingEnv, List<ElementHandler> handlers) {
this.m_processingEnv = processingEnv;
@@ -36,6 +80,19 @@ public class LoggerGenerator {
return e.getAnnotation(NotLogged.class) == null;
}
/**
* Checks if a method is a method declared in java.lang.Object that should not be logged.
*
* @param e the method to check
* @return true if the method is toString, hashCode, or clone; false otherwise
*/
private static boolean isBuiltInJavaMethod(ExecutableElement e) {
Name name = e.getSimpleName();
return name.contentEquals("toString")
|| name.contentEquals("hashCode")
|| name.contentEquals("clone");
}
/**
* Generates the logger class used to handle data objects of the given type. The generated logger
* class will subclass from {@link edu.wpi.first.epilogue.logging.ClassSpecificLogger} and
@@ -47,23 +104,32 @@ public class LoggerGenerator {
*/
public void writeLoggerFile(TypeElement clazz) throws IOException {
var config = clazz.getAnnotation(Logged.class);
if (config == null) {
config = m_defaultConfig;
}
boolean requireExplicitOptIn = config.strategy() == Logged.Strategy.OPT_IN;
Predicate<Element> notSkipped = LoggerGenerator::isNotSkipped;
Predicate<Element> optedIn =
e -> !requireExplicitOptIn || e.getAnnotation(Logged.class) != null;
var fieldsToLog =
clazz.getEnclosedElements().stream()
.filter(e -> e instanceof VariableElement)
.map(e -> (VariableElement) e)
.filter(notSkipped)
.filter(optedIn)
.filter(e -> !e.getModifiers().contains(Modifier.STATIC))
.filter(this::isLoggable)
.toList();
List<VariableElement> fieldsToLog;
if (Objects.equals(clazz.getSuperclass().toString(), "java.lang.Record")) {
// Do not log record members - just use the accessor methods
fieldsToLog = List.of();
} else {
fieldsToLog =
clazz.getEnclosedElements().stream()
.filter(e -> e instanceof VariableElement)
.map(e -> (VariableElement) e)
.filter(notSkipped)
.filter(optedIn)
.filter(e -> !e.getModifiers().contains(Modifier.STATIC))
.filter(this::isLoggable)
.toList();
}
var methodsToLog =
List<ExecutableElement> methodsToLog =
clazz.getEnclosedElements().stream()
.filter(e -> e instanceof ExecutableElement)
.map(e -> (ExecutableElement) e)
@@ -73,39 +139,73 @@ public class LoggerGenerator {
.filter(e -> e.getModifiers().contains(Modifier.PUBLIC))
.filter(e -> e.getParameters().isEmpty())
.filter(e -> e.getReceiverType() != null)
.filter(kIsBuiltInJavaMethod.negate())
.filter(this::isLoggable)
.filter(e -> !isSimpleGetterMethodForLoggedField(e, fieldsToLog))
.toList();
writeLoggerFile(clazz.getQualifiedName().toString(), config, fieldsToLog, methodsToLog);
// Validate no name collisions
Map<String, List<Element>> usedNames =
Stream.concat(fieldsToLog.stream(), methodsToLog.stream())
.reduce(
new HashMap<>(),
(map, element) -> {
String name = ElementHandler.loggedName(element);
map.computeIfAbsent(name, _k -> new ArrayList<>()).add(element);
return map;
},
(left, right) -> {
left.putAll(right);
return left;
});
usedNames.forEach(
(name, elements) -> {
if (elements.size() > 1) {
// Collisions!
for (Element conflictingElement : elements) {
String conflicts =
elements.stream()
.filter(e -> !e.equals(conflictingElement))
.map(e -> e.getEnclosingElement().getSimpleName() + "." + e)
.collect(Collectors.joining(", "));
m_processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"[EPILOGUE] Conflicting name detected: \""
+ name
+ "\" is also used by "
+ conflicts,
conflictingElement);
}
}
});
writeLoggerFile(clazz, config, fieldsToLog, methodsToLog);
}
private void writeLoggerFile(
String className,
TypeElement clazz,
Logged classConfig,
List<VariableElement> loggableFields,
List<ExecutableElement> loggableMethods)
throws IOException {
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
// Walk nesting levels, to support inner classes
Deque<String> nesting = new ArrayDeque<>();
Element enclosing = clazz.getEnclosingElement();
while (!(enclosing instanceof PackageElement p)) {
nesting.addFirst(enclosing.getSimpleName().toString());
enclosing = enclosing.getEnclosingElement();
}
String packageName = p.getQualifiedName().toString();
nesting.addLast(clazz.getSimpleName().toString());
String simpleClassName = String.join(".", nesting);
String simpleClassName = StringUtils.simpleName(className);
String loggerClassName = className + "Logger";
String loggerSimpleClassName = loggerClassName.substring(lastDot + 1);
// Use the name on the class config to set the generated logger names
// This helps to avoid naming conflicts
if (!classConfig.name().isBlank()) {
loggerSimpleClassName =
StringUtils.capitalize(classConfig.name().replaceAll(" ", "")) + "Logger";
if (lastDot > 0) {
loggerClassName = packageName + "." + loggerSimpleClassName;
} else {
loggerClassName = loggerSimpleClassName;
}
}
String loggerClassName = StringUtils.loggerClassName(clazz);
String loggerSimpleClassName = StringUtils.simpleName(loggerClassName);
var loggerFile = m_processingEnv.getFiler().createSourceFile(loggerClassName);
@@ -123,7 +223,7 @@ public class LoggerGenerator {
out.println("import edu.wpi.first.epilogue.Logged;");
out.println("import edu.wpi.first.epilogue.Epilogue;");
out.println("import edu.wpi.first.epilogue.logging.ClassSpecificLogger;");
out.println("import edu.wpi.first.epilogue.logging.DataLogger;");
out.println("import edu.wpi.first.epilogue.logging.EpilogueBackend;");
if (requiresVarHandles) {
out.println("import java.lang.invoke.MethodHandles;");
out.println("import java.lang.invoke.VarHandle;");
@@ -146,13 +246,13 @@ public class LoggerGenerator {
}
out.println();
var clazz = simpleClassName + ".class";
var classReference = simpleClassName + ".class";
out.println(" static {");
out.println(" try {");
out.println(
" var lookup = MethodHandles.privateLookupIn("
+ clazz
+ classReference
+ ", MethodHandles.lookup());");
for (var privateField : privateFields) {
@@ -161,7 +261,7 @@ public class LoggerGenerator {
" $"
+ fieldName
+ " = lookup.findVarHandle("
+ clazz
+ classReference
+ ", \""
+ fieldName
+ "\", "
@@ -184,9 +284,10 @@ public class LoggerGenerator {
out.println();
// @Override
// public void update(DataLogger dataLogger, Foo object) {
// public void update(EpilogueBackend backend, Foo object) {
out.println(" @Override");
out.println(" public void update(DataLogger dataLogger, " + simpleClassName + " object) {");
out.println(
" public void update(EpilogueBackend backend, " + simpleClassName + " object) {");
// Build a map of importance levels to the fields logged at those levels
// e.g. { DEBUG: [fieldA, fieldB], INFO: [fieldC], CRITICAL: [fieldD, fieldE, fieldF] }
@@ -242,4 +343,55 @@ public class LoggerGenerator {
private boolean isLoggable(Element element) {
return m_handlers.stream().anyMatch(h -> h.isLoggable(element));
}
/**
* Checks if a method is a simple "getter" method for a field in the given list. Here, we define
* "getter" as a method with a single return statement that references the name of a field, with
* no other expressions. `getX() { return x; }` would be considered a "getter" method, while
* `getX() { return x.clone(); }` would not be. Note that the method name is irrelevant; only the
* method body is checked.
*
* @param ex the method to check
* @param fieldsToLog the fields that will already be logged
* @return true if the method is a simple "getter" method, false otherwise
*/
private boolean isSimpleGetterMethodForLoggedField(
ExecutableElement ex, List<VariableElement> fieldsToLog) {
var trees = Trees.instance(m_processingEnv);
var methodTree = trees.getTree(ex);
if (methodTree == null) {
// probably a record's synthetic reader method
return false;
}
if (methodTree.getBody() == null) {
// Abstract or native method, can't be determined to be a getter
return false;
}
var statements = methodTree.getBody().getStatements();
if (statements.size() != 1) {
// More complex than a simple `return m_field` statement
return false;
}
var statement = statements.get(0);
if (!(statement instanceof ReturnTree ret)) {
// Shouldn't get here, since we've already filtered for methods that return a value
// and with a single statement - that one statement should be the return
return false;
}
var returnExpression = ret.getExpression();
return returnExpression.accept(
new SimpleTreeVisitor<Boolean, Void>(false) {
@Override
public Boolean visitIdentifier(IdentifierTree identifier, Void unused) {
return fieldsToLog.stream()
.anyMatch(v -> v.getSimpleName().contentEquals(identifier.getName()));
}
},
null);
}
}

View File

@@ -31,7 +31,7 @@ public class MeasureHandler extends ElementHandler {
@Override
public String logInvocation(Element element) {
// DataLogger has builtin support for logging measures
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
// EpilogueBackend has builtin support for logging measures
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
}
}

View File

@@ -36,6 +36,6 @@ public class PrimitiveHandler extends ElementHandler {
@Override
public String logInvocation(Element element) {
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
}
}

View File

@@ -4,24 +4,32 @@
package edu.wpi.first.epilogue.processor;
import java.util.Optional;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
public class SendableHandler extends ElementHandler {
private final TypeMirror m_sendableType;
private final TypeMirror m_commandType;
private final TypeMirror m_subsystemType;
private final Optional<TypeMirror> m_sendableType;
private final Optional<TypeMirror> m_commandType;
private final Optional<TypeMirror> m_subsystemType;
protected SendableHandler(ProcessingEnvironment processingEnv) {
super(processingEnv);
m_sendableType =
lookupTypeElement(processingEnv, "edu.wpi.first.util.sendable.Sendable").asType();
Optional.ofNullable(
lookupTypeElement(processingEnv, "edu.wpi.first.util.sendable.Sendable"))
.map(TypeElement::asType);
m_commandType =
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.Command").asType();
Optional.ofNullable(
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.Command"))
.map(TypeElement::asType);
m_subsystemType =
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.SubsystemBase").asType();
Optional.ofNullable(
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.SubsystemBase"))
.map(TypeElement::asType);
}
@Override
@@ -30,24 +38,32 @@ public class SendableHandler extends ElementHandler {
// Accept any sendable type. However, the log invocation will return null
// for sendable types that should not be logged (commands, subsystems)
return m_processingEnv.getTypeUtils().isAssignable(dataType, m_sendableType);
return m_sendableType
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
.orElse(false);
}
@Override
public String logInvocation(Element element) {
var dataType = dataType(element);
if (m_processingEnv.getTypeUtils().isAssignable(dataType, m_commandType)
|| m_processingEnv.getTypeUtils().isAssignable(dataType, m_subsystemType)) {
// Do not log commands or subsystems via their sendable implementations
// We accept all sendable objects to prevent them from being reported as not loggable,
// but their sendable implementations do not include helpful information.
// Users are free to provide custom logging implementations for commands, and tag their
// subsystems with @Logged to log their contents automatically
// Do not log commands or subsystems via their sendable implementations
// We accept all sendable objects to prevent them from being reported as not loggable,
// but their sendable implementations do not include helpful information.
// Users are free to provide custom logging implementations for commands, and tag their
// subsystems with @Logged to log their contents automatically
if (m_commandType
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
.orElse(false)) {
return null;
}
if (m_subsystemType
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
.orElse(false)) {
return null;
}
return "logSendable(dataLogger.getSubLogger(\""
return "logSendable(backend.getNested(\""
+ loggedName(element)
+ "\"), "
+ elementAccess(element)

View File

@@ -5,6 +5,13 @@
package edu.wpi.first.epilogue.processor;
import edu.wpi.first.epilogue.Logged;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
public final class StringUtils {
@@ -59,6 +66,33 @@ public final class StringUtils {
return builder.toString();
}
/**
* Splits a camel-cased string like "fooBar" into individual words like ["foo", "Bar"].
*
* @param camelCasedString the camel-cased string to split
* @return the individual words in the input
*/
public static List<String> splitToWords(CharSequence camelCasedString) {
// Implementation from https://stackoverflow.com/a/2560017, refactored for readability
// Uppercase letter not followed by the first letter of the next word
// This allows for splitting "IOLayer" into "IO" and "Layer"
String penultimateUppercaseLetter = "(?<=[A-Z])(?=[A-Z][a-z])";
// Any character that's NOT an uppercase letter, immediately followed by an uppercase letter
// This allows for splitting "fooBar" into "foo" and "Bar", or "123Bang" into "123" and "Bang"
String lastNonUppercaseLetter = "(?<=[^A-Z])(?=[A-Z])";
// The final letter in a sequence, followed by a non-alpha character like a number or underscore
// This allows for splitting "foo123" into "foo" and "123"
String finalLetter = "(?<=[A-Za-z])(?=[^A-Za-z])";
String regex =
String.format("%s|%s|%s", penultimateUppercaseLetter, lastNonUppercaseLetter, finalLetter);
return Arrays.asList(camelCasedString.toString().split(regex));
}
/**
* Gets the name of the field used to hold a logger for data of the given type.
*
@@ -78,33 +112,50 @@ public final class StringUtils {
*/
public static String loggerClassName(TypeElement clazz) {
var config = clazz.getAnnotation(Logged.class);
var className = clazz.getQualifiedName().toString();
String packageName;
int lastDot = className.lastIndexOf('.');
if (lastDot <= 0) {
packageName = null;
Deque<String> nesting = new ArrayDeque<>();
Element enclosing = clazz.getEnclosingElement();
while (!(enclosing instanceof PackageElement p)) {
nesting.addFirst(enclosing.getSimpleName().toString());
enclosing = enclosing.getEnclosingElement();
}
nesting.addLast(clazz.getSimpleName().toString());
String packageName = p.getQualifiedName().toString();
String className;
if (config == null || config.name().isEmpty()) {
className = String.join("$", nesting);
} else {
packageName = className.substring(0, lastDot);
className = capitalize(config.name()).replaceAll(" ", "");
}
String loggerClassName;
return packageName + "." + className + "Logger";
}
// Use the name on the class config to set the generated logger names
// This helps to avoid naming conflicts
if (config.name().isBlank()) {
loggerClassName = className + "Logger";
} else {
String cleaned = config.name().replaceAll(" ", "");
/**
* Converts a camelCase element name to separate words, removing common field and method name
* prefixes like "m_" and "get".
*
* @param elementName the camelcased element name
* @return the name split into separate words and sanitized
*/
public static String toHumanName(String elementName) {
// Delete common field prefixes (k_name, m_name, s_name)
var sanitizedName = elementName.replaceFirst("^[msk]_", "");
var loggerSimpleClassName = StringUtils.capitalize(cleaned) + "Logger";
if (packageName != null) {
loggerClassName = packageName + "." + loggerSimpleClassName;
} else {
loggerClassName = loggerSimpleClassName;
}
// Drop leading "k" prefix from fields
// (though normally these should be static, and thus not logged)
if (sanitizedName.matches("^k[A-Z].*$")) {
sanitizedName = sanitizedName.substring(1);
}
return loggerClassName;
// Drop leading "get" from accessor methods
if (sanitizedName.matches("^get[A-Z].*$")) {
sanitizedName = sanitizedName.substring(3);
}
return splitToWords(sanitizedName).stream()
.map(StringUtils::capitalize)
.collect(Collectors.joining(" "));
}
}

View File

@@ -39,7 +39,7 @@ public class StructHandler extends ElementHandler {
@Override
public String logInvocation(Element element) {
return "dataLogger.log(\""
return "backend.log(\""
+ loggedName(element)
+ "\", "
+ elementAccess(element)

View File

@@ -43,7 +43,7 @@ public class SupplierHandler extends ElementHandler {
@Override
public String logInvocation(Element element) {
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
}
@Override

View File

@@ -0,0 +1,13 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.epilogue.processor;
import java.util.List;
public class CompileTestOptions {
public static final int kJavaVersion = 17;
public static final List<Object> kJavaVersionOptions =
List.of("-source", kJavaVersion, "-target", kJavaVersion);
}

View File

@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
import static edu.wpi.first.epilogue.processor.CompileTestOptions.kJavaVersionOptions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.google.testing.compile.Compilation;
@@ -32,9 +33,19 @@ class EpilogueGeneratorTest {
import static edu.wpi.first.units.Units.Seconds;
import edu.wpi.first.hal.FRCNetComm;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.epilogue.ExampleLogger;
public final class Epilogue {
static {
HAL.report(
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
FRCNetComm.tInstances.kLoggingFramework_Epilogue
);
}
private static final EpilogueConfiguration config = new EpilogueConfiguration();
public static final ExampleLogger exampleLogger = new ExampleLogger();
@@ -81,9 +92,19 @@ class EpilogueGeneratorTest {
import static edu.wpi.first.units.Units.Seconds;
import edu.wpi.first.hal.FRCNetComm;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.epilogue.ExampleLogger;
public final class Epilogue {
static {
HAL.report(
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
FRCNetComm.tInstances.kLoggingFramework_Epilogue
);
}
private static final EpilogueConfiguration config = new EpilogueConfiguration();
public static final ExampleLogger exampleLogger = new ExampleLogger();
@@ -125,9 +146,19 @@ class EpilogueGeneratorTest {
import static edu.wpi.first.units.Units.Seconds;
import edu.wpi.first.hal.FRCNetComm;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.epilogue.ExampleLogger;
public final class Epilogue {
static {
HAL.report(
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
FRCNetComm.tInstances.kLoggingFramework_Epilogue
);
}
private static final EpilogueConfiguration config = new EpilogueConfiguration();
public static final ExampleLogger exampleLogger = new ExampleLogger();
@@ -154,8 +185,8 @@ class EpilogueGeneratorTest {
*/
public static void update(edu.wpi.first.epilogue.Example robot) {
long start = System.nanoTime();
exampleLogger.tryUpdate(config.dataLogger.getSubLogger(config.root), robot, config.errorHandler);
config.dataLogger.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
exampleLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);
config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
}
/**
@@ -171,7 +202,7 @@ class EpilogueGeneratorTest {
config.loggingPeriod = Seconds.of(robot.getPeriod());
}
if (config.loggingPeriodOffset == null) {
config.loggingPeriodOffset = config.loggingPeriod.divide(2);
config.loggingPeriodOffset = config.loggingPeriod.div(2);
}
robot.addPeriodic(() -> {
@@ -203,10 +234,20 @@ class EpilogueGeneratorTest {
import static edu.wpi.first.units.Units.Seconds;
import edu.wpi.first.hal.FRCNetComm;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.epilogue.AlphaBotLogger;
import edu.wpi.first.epilogue.BetaBotLogger;
public final class Epilogue {
static {
HAL.report(
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
FRCNetComm.tInstances.kLoggingFramework_Epilogue
);
}
private static final EpilogueConfiguration config = new EpilogueConfiguration();
public static final AlphaBotLogger alphaBotLogger = new AlphaBotLogger();
@@ -234,8 +275,8 @@ class EpilogueGeneratorTest {
*/
public static void update(edu.wpi.first.epilogue.AlphaBot robot) {
long start = System.nanoTime();
alphaBotLogger.tryUpdate(config.dataLogger.getSubLogger(config.root), robot, config.errorHandler);
config.dataLogger.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
alphaBotLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);
config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
}
/**
@@ -251,7 +292,7 @@ class EpilogueGeneratorTest {
config.loggingPeriod = Seconds.of(robot.getPeriod());
}
if (config.loggingPeriodOffset == null) {
config.loggingPeriodOffset = config.loggingPeriod.divide(2);
config.loggingPeriodOffset = config.loggingPeriod.div(2);
}
robot.addPeriodic(() -> {
@@ -266,8 +307,8 @@ class EpilogueGeneratorTest {
*/
public static void update(edu.wpi.first.epilogue.BetaBot robot) {
long start = System.nanoTime();
betaBotLogger.tryUpdate(config.dataLogger.getSubLogger(config.root), robot, config.errorHandler);
config.dataLogger.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
betaBotLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);
config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
}
/**
@@ -283,7 +324,7 @@ class EpilogueGeneratorTest {
config.loggingPeriod = Seconds.of(robot.getPeriod());
}
if (config.loggingPeriodOffset == null) {
config.loggingPeriodOffset = config.loggingPeriod.divide(2);
config.loggingPeriodOffset = config.loggingPeriod.div(2);
}
robot.addPeriodic(() -> {
@@ -313,7 +354,7 @@ class EpilogueGeneratorTest {
public CustomLogger() { super(A.class); }
@Override
public void update(DataLogger logger, A object) {} // implementation is irrelevant
public void update(EpilogueBackend backend, A object) {} // implementation is irrelevant
}
@Logged
@@ -330,10 +371,20 @@ class EpilogueGeneratorTest {
import static edu.wpi.first.units.Units.Seconds;
import edu.wpi.first.hal.FRCNetComm;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.epilogue.ExampleLogger;
import edu.wpi.first.epilogue.CustomLogger;
public final class Epilogue {
static {
HAL.report(
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
FRCNetComm.tInstances.kLoggingFramework_Epilogue
);
}
private static final EpilogueConfiguration config = new EpilogueConfiguration();
public static final ExampleLogger exampleLogger = new ExampleLogger();
@@ -363,6 +414,7 @@ class EpilogueGeneratorTest {
String loggedClassContent, String loggerClassContent) {
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.withProcessors(new AnnotationProcessor())
.compile(JavaFileObjects.forSourceString("", loggedClassContent));

View File

@@ -4,8 +4,10 @@
package edu.wpi.first.epilogue.processor;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.Test;
class StringUtilsTest {
@@ -16,4 +18,22 @@ class StringUtilsTest {
assertEquals("fooBar", StringUtils.lowerCamelCase("FooBar"));
assertEquals("allcaps", StringUtils.lowerCamelCase("ALLCAPS"));
}
@Test
void splitToWords() {
assertAll(
() -> assertEquals(List.of("IO", "Logger"), StringUtils.splitToWords("IOLogger")),
() -> assertEquals(List.of("LED", "Subsystem"), StringUtils.splitToWords("LEDSubsystem")),
() -> assertEquals(List.of("Foo", "Bar"), StringUtils.splitToWords("FooBar")),
() -> assertEquals(List.of("ALLCAPS"), StringUtils.splitToWords("ALLCAPS")),
() ->
assertEquals(List.of("k", "First", "Second"), StringUtils.splitToWords("kFirstSecond")),
() ->
assertEquals(
List.of("there", "Is", "A", "Number", "123", "In", "Here", "VERSION", "456"),
StringUtils.splitToWords("thereIsANumber123InHereVERSION456")),
() ->
assertEquals(
List.of("get", "First", "Second"), StringUtils.splitToWords("getFirstSecond")));
}
}

View File

@@ -4,8 +4,8 @@
package edu.wpi.first.epilogue;
import edu.wpi.first.epilogue.logging.DataLogger;
import edu.wpi.first.epilogue.logging.NTDataLogger;
import edu.wpi.first.epilogue.logging.EpilogueBackend;
import edu.wpi.first.epilogue.logging.NTEpilogueBackend;
import edu.wpi.first.epilogue.logging.errors.ErrorHandler;
import edu.wpi.first.epilogue.logging.errors.ErrorPrinter;
import edu.wpi.first.networktables.NetworkTableInstance;
@@ -18,11 +18,11 @@ import edu.wpi.first.units.measure.Time;
@SuppressWarnings("checkstyle:MemberName")
public class EpilogueConfiguration {
/**
* The data logger implementation for Epilogue to use. By default, this will log data directly to
* The backend implementation for Epilogue to use. By default, this will log data directly to
* NetworkTables. NetworkTable data can be mirrored to a log file on disk by calling {@code
* DataLogManager.start()} in your {@code robotInit} method.
*/
public DataLogger dataLogger = new NTDataLogger(NetworkTableInstance.getDefault());
public EpilogueBackend backend = new NTEpilogueBackend(NetworkTableInstance.getDefault());
/**
* The period Epilogue will log at. By default this is the period that the robot runs at. This is

View File

@@ -89,4 +89,39 @@ public @interface Logged {
* @return the importance of the annotated element
*/
Importance importance() default Importance.DEBUG;
/**
* Different behaviors for how Epilogue will generate the names of logged data points. This only
* applies to automatically generated names; any specific name provided with {@link #name()} will
* take precedence over an automatically generated name.
*/
enum Naming {
/**
* Sets the default naming strategy to use the name of the element as it appears in source code.
* For example, a field {@code double m_x} would be labeled as {@code "m_x"} by default, and a
* {@code getX()} accessor would be labeled as {@code "getX"}.
*/
USE_CODE_NAME,
/**
* Sets the default naming strategy to use a human-readable name based on the name of the name
* of the element as it appears in source code. For example, a field {@code double m_x} would be
* labeled as {@code "X"} by default, and a {@code getX()} accessor would also be labeled as
* {@code "X"}. Because logged names must be unique, this configuration would fail to compile
* and require either one of the fields to be excluded from logs (which, for simple accessors,
* would be ideal to avoid duplicate data), or to rename one or both elements so the logged data
* fields would have unique names.
*/
USE_HUMAN_NAME
}
/**
* The default naming behavior to use. Defaults to {@link Naming#USE_CODE_NAME}, which uses the
* raw code name directly in logs. Any configuration of the {@link #name()} attribute on logged
* fields and methods will take precedence over an automatically generated name.
*
* @return the naming strategy for and annotated field or method, or the default naming strategy
* for all logged fields and methods in an annotated class
*/
Naming defaultNaming() default Naming.USE_CODE_NAME;
}

View File

@@ -42,26 +42,26 @@ public abstract class ClassSpecificLogger<T> {
/**
* Updates an object's fields in a data log.
*
* @param dataLogger the logger to update
* @param backend the backend to update
* @param object the object to update in the log
*/
protected abstract void update(DataLogger dataLogger, T object);
protected abstract void update(EpilogueBackend backend, T object);
/**
* Attempts to update the data log. Will do nothing if the logger is {@link #disable() disabled}.
*
* @param dataLogger the logger to log data to
* @param backend the backend to log data to
* @param object the data object to log
* @param errorHandler the handler to use if logging raised an exception
*/
@SuppressWarnings("PMD.AvoidCatchingGenericException")
public final void tryUpdate(DataLogger dataLogger, T object, ErrorHandler errorHandler) {
public final void tryUpdate(EpilogueBackend backend, T object, ErrorHandler errorHandler) {
if (m_disabled) {
return;
}
try {
update(dataLogger, object);
update(backend, object);
} catch (Exception e) {
errorHandler.handle(e, this);
}
@@ -98,10 +98,10 @@ public abstract class ClassSpecificLogger<T> {
/**
* Logs a sendable type.
*
* @param dataLogger the logger to log data into
* @param backend the backend to log data into
* @param sendable the sendable object to log
*/
protected void logSendable(DataLogger dataLogger, Sendable sendable) {
protected void logSendable(EpilogueBackend backend, Sendable sendable) {
if (sendable == null) {
return;
}
@@ -110,7 +110,7 @@ public abstract class ClassSpecificLogger<T> {
m_sendables.computeIfAbsent(
sendable,
s -> {
var b = new LogBackedSendableBuilder(dataLogger);
var b = new LogBackedSendableBuilder(backend);
s.initSendable(b);
return b;
});

View File

@@ -9,40 +9,40 @@ import edu.wpi.first.units.Unit;
import edu.wpi.first.util.struct.Struct;
import java.util.Collection;
/** A data logger is a generic interface for logging discrete data points. */
public interface DataLogger {
/** A backend is a generic interface for Epilogue to log discrete data points. */
public interface EpilogueBackend {
/**
* Creates a data logger that logs to multiple backends at once. Data reads will still only occur
* once; data is passed to all composed loggers at once.
* Creates a backend that logs to multiple backends at once. Data reads will still only occur
* once; data is passed to all composed backends at once.
*
* @param loggers the loggers to compose together
* @return the multi logger
* @param backends the backends to compose together
* @return the multi backend
*/
static DataLogger multi(DataLogger... loggers) {
return new MultiLogger(loggers);
static EpilogueBackend multi(EpilogueBackend... backends) {
return new MultiBackend(backends);
}
/**
* Creates a lazy version of this logger. A lazy logger will only log data to a field when its
* Creates a lazy version of this backend. A lazy backend will only log data to a field when its
* value changes, which can help keep file size and bandwidth usage in check. However, there is an
* additional CPU and memory overhead associated with tracking the current value of every logged
* entry. The most surefire way to reduce CPU and memory usage associated with logging is to log
* fewer things - which can be done by opting out of logging unnecessary data or increasing the
* minimum logged importance level in the Epilogue configuration.
*
* @return the lazy logger
* @return the lazy backend
*/
default DataLogger lazy() {
return new LazyLogger(this);
default EpilogueBackend lazy() {
return new LazyBackend(this);
}
/**
* Gets a logger that can be used to log nested data underneath a specific path.
* Gets a backend that can be used to log nested data underneath a specific path.
*
* @param path the path to use for logging nested data under
* @return the sub logger
* @return the nested backend
*/
DataLogger getSubLogger(String path);
EpilogueBackend getNested(String path);
/**
* Logs a 32-bit integer data point.

View File

@@ -26,24 +26,24 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
/** A data logger implementation that saves information to a WPILib {@link DataLog} file on disk. */
public class FileLogger implements DataLogger {
/** A backend implementation that saves information to a WPILib {@link DataLog} file on disk. */
public class FileBackend implements EpilogueBackend {
private final DataLog m_dataLog;
private final Map<String, DataLogEntry> m_entries = new HashMap<>();
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
private final Map<String, NestedBackend> m_subLoggers = new HashMap<>();
/**
* Creates a new file logger.
* Creates a new file-based backend.
*
* @param dataLog the data log to save data to
*/
public FileLogger(DataLog dataLog) {
this.m_dataLog = requireNonNullParam(dataLog, "dataLog", "FileLogger");
public FileBackend(DataLog dataLog) {
this.m_dataLog = requireNonNullParam(dataLog, "dataLog", "FileBackend");
}
@Override
public DataLogger getSubLogger(String path) {
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
public EpilogueBackend getNested(String path) {
return m_subLoggers.computeIfAbsent(path, k -> new NestedBackend(k, this));
}
@SuppressWarnings("unchecked")

View File

@@ -11,36 +11,36 @@ import java.util.Map;
import java.util.Objects;
/**
* A data logger implementation that only logs data when it changes. Useful for keeping bandwidth
* and file sizes down. However, because it still needs to check that data has changed, it cannot
* avoid expensive sensor reads.
* A backend implementation that only logs data when it changes. Useful for keeping bandwidth and
* file sizes down. However, because it still needs to check that data has changed, it cannot avoid
* expensive sensor reads.
*/
public class LazyLogger implements DataLogger {
private final DataLogger m_logger;
public class LazyBackend implements EpilogueBackend {
private final EpilogueBackend m_backend;
// Keep a record of the most recent value written to each entry
// Note that this may duplicate a lot of data, and will box primitives.
private final Map<String, Object> m_previousValues = new HashMap<>();
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
private final Map<String, NestedBackend> m_subLoggers = new HashMap<>();
/**
* Creates a new lazy logger wrapper around another logger.
* Creates a new lazy backend wrapper around another backend.
*
* @param logger the logger to delegate to
* @param backend the backend to delegate to
*/
public LazyLogger(DataLogger logger) {
this.m_logger = logger;
public LazyBackend(EpilogueBackend backend) {
this.m_backend = backend;
}
@Override
public DataLogger lazy() {
public EpilogueBackend lazy() {
// Already lazy, don't need to wrap it again
return this;
}
@Override
public DataLogger getSubLogger(String path) {
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
public EpilogueBackend getNested(String path) {
return m_subLoggers.computeIfAbsent(path, k -> new NestedBackend(k, this));
}
@Override
@@ -53,7 +53,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -66,7 +66,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -79,7 +79,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -92,7 +92,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -105,7 +105,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -118,7 +118,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -131,7 +131,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -144,7 +144,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -157,7 +157,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -170,7 +170,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -183,7 +183,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -196,7 +196,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -209,7 +209,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value);
m_backend.log(identifier, value);
}
@Override
@@ -222,7 +222,7 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value, struct);
m_backend.log(identifier, value, struct);
}
@Override
@@ -235,6 +235,6 @@ public class LazyLogger implements DataLogger {
}
m_previousValues.put(identifier, value);
m_logger.log(identifier, value, struct);
m_backend.log(identifier, value, struct);
}
}

View File

@@ -18,24 +18,24 @@ import java.util.function.LongConsumer;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/** A sendable builder implementation that sends data to a {@link DataLogger}. */
@SuppressWarnings("PMD.CouplingBetweenObjects") // most methods simply delegate to the logger
/** A sendable builder implementation that sends data to a {@link EpilogueBackend}. */
@SuppressWarnings("PMD.CouplingBetweenObjects") // most methods simply delegate to the backend
public class LogBackedSendableBuilder implements SendableBuilder {
private final DataLogger m_logger;
private final EpilogueBackend m_backend;
private final Collection<Runnable> m_updates = new ArrayList<>();
/**
* Creates a new sendable builder that delegates writes to an underlying data logger.
* Creates a new sendable builder that delegates writes to an underlying backend.
*
* @param logger the data logger to write the sendable data to
* @param backend the backend to write the sendable data to
*/
public LogBackedSendableBuilder(DataLogger logger) {
this.m_logger = logger;
public LogBackedSendableBuilder(EpilogueBackend backend) {
this.m_backend = backend;
}
@Override
public void setSmartDashboardType(String type) {
m_logger.log(".type", type);
m_backend.log(".type", type);
}
@Override
@@ -50,132 +50,132 @@ public class LogBackedSendableBuilder implements SendableBuilder {
@Override
public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) {
m_updates.add(() -> m_logger.log(key, getter.getAsBoolean()));
m_updates.add(() -> m_backend.log(key, getter.getAsBoolean()));
}
@Override
public void publishConstBoolean(String key, boolean value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) {
m_updates.add(() -> m_logger.log(key, getter.getAsLong()));
m_updates.add(() -> m_backend.log(key, getter.getAsLong()));
}
@Override
public void publishConstInteger(String key, long value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) {
m_updates.add(() -> m_logger.log(key, getter.getAsFloat()));
m_updates.add(() -> m_backend.log(key, getter.getAsFloat()));
}
@Override
public void publishConstFloat(String key, float value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) {
m_updates.add(() -> m_logger.log(key, getter.getAsDouble()));
m_updates.add(() -> m_backend.log(key, getter.getAsDouble()));
}
@Override
public void publishConstDouble(String key, double value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) {
if (getter != null) {
m_updates.add(() -> m_logger.log(key, getter.get()));
m_updates.add(() -> m_backend.log(key, getter.get()));
}
}
@Override
public void publishConstString(String key, String value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addBooleanArrayProperty(
String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter) {
if (getter != null) {
m_updates.add(() -> m_logger.log(key, getter.get()));
m_updates.add(() -> m_backend.log(key, getter.get()));
}
}
@Override
public void publishConstBooleanArray(String key, boolean[] value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addIntegerArrayProperty(
String key, Supplier<long[]> getter, Consumer<long[]> setter) {
if (getter != null) {
m_updates.add(() -> m_logger.log(key, getter.get()));
m_updates.add(() -> m_backend.log(key, getter.get()));
}
}
@Override
public void publishConstIntegerArray(String key, long[] value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addFloatArrayProperty(
String key, Supplier<float[]> getter, Consumer<float[]> setter) {
if (getter != null) {
m_updates.add(() -> m_logger.log(key, getter.get()));
m_updates.add(() -> m_backend.log(key, getter.get()));
}
}
@Override
public void publishConstFloatArray(String key, float[] value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addDoubleArrayProperty(
String key, Supplier<double[]> getter, Consumer<double[]> setter) {
if (getter != null) {
m_updates.add(() -> m_logger.log(key, getter.get()));
m_updates.add(() -> m_backend.log(key, getter.get()));
}
}
@Override
public void publishConstDoubleArray(String key, double[] value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addStringArrayProperty(
String key, Supplier<String[]> getter, Consumer<String[]> setter) {
if (getter != null) {
m_updates.add(() -> m_logger.log(key, getter.get()));
m_updates.add(() -> m_backend.log(key, getter.get()));
}
}
@Override
public void publishConstStringArray(String key, String[] value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override
public void addRawProperty(
String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter) {
if (getter != null) {
m_updates.add(() -> m_logger.log(key, getter.get()));
m_updates.add(() -> m_backend.log(key, getter.get()));
}
}
@Override
public void publishConstRaw(String key, String typeString, byte[] value) {
m_logger.log(key, value);
m_backend.log(key, value);
}
@Override

View File

@@ -0,0 +1,134 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.epilogue.logging;
import edu.wpi.first.util.struct.Struct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A backend implementation that delegates to other backends. Helpful for simultaneous logging to
* multiple data stores at once.
*/
public class MultiBackend implements EpilogueBackend {
private final List<EpilogueBackend> m_backends;
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
// Use EpilogueBackend.multi(...) instead of instantiation directly
MultiBackend(EpilogueBackend... backends) {
this.m_backends = List.of(backends);
}
@Override
public EpilogueBackend getNested(String path) {
return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
}
@Override
public void log(String identifier, int value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, long value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, float value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, double value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, boolean value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, byte[] value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, int[] value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, long[] value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, float[] value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, double[] value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, boolean[] value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, String value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public void log(String identifier, String[] value) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value);
}
}
@Override
public <S> void log(String identifier, S value, Struct<S> struct) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value, struct);
}
}
@Override
public <S> void log(String identifier, S[] value, Struct<S> struct) {
for (EpilogueBackend backend : m_backends) {
backend.log(identifier, value, struct);
}
}
}

View File

@@ -1,134 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.epilogue.logging;
import edu.wpi.first.util.struct.Struct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A data logger implementation that delegates to other loggers. Helpful for simultaneous logging to
* multiple data stores at once.
*/
public class MultiLogger implements DataLogger {
private final List<DataLogger> m_loggers;
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
// Use DataLogger.multi(...) instead of instantiation directly
MultiLogger(DataLogger... loggers) {
this.m_loggers = List.of(loggers);
}
@Override
public DataLogger getSubLogger(String path) {
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
}
@Override
public void log(String identifier, int value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, long value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, float value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, double value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, boolean value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, byte[] value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, int[] value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, long[] value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, float[] value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, double[] value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, boolean[] value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, String value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public void log(String identifier, String[] value) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value);
}
}
@Override
public <S> void log(String identifier, S value, Struct<S> struct) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value, struct);
}
}
@Override
public <S> void log(String identifier, S[] value, Struct<S> struct) {
for (DataLogger logger : m_loggers) {
logger.log(identifier, value, struct);
}
}
}

View File

@@ -24,27 +24,27 @@ import java.util.HashMap;
import java.util.Map;
/**
* A data logger implementation that sends data over network tables. Be careful when using this,
* since sending too much data may cause bandwidth or CPU starvation.
* A backend implementation that sends data over network tables. Be careful when using this, since
* sending too much data may cause bandwidth or CPU starvation.
*/
public class NTDataLogger implements DataLogger {
public class NTEpilogueBackend implements EpilogueBackend {
private final NetworkTableInstance m_nt;
private final Map<String, Publisher> m_publishers = new HashMap<>();
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
/**
* Creates a data logger that sends information to NetworkTables.
* Creates a logging backend that sends information to NetworkTables.
*
* @param nt the NetworkTable instance to use to send data to
*/
public NTDataLogger(NetworkTableInstance nt) {
public NTEpilogueBackend(NetworkTableInstance nt) {
this.m_nt = nt;
}
@Override
public DataLogger getSubLogger(String path) {
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
public EpilogueBackend getNested(String path) {
return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
}
@Override

View File

@@ -9,21 +9,21 @@ import java.util.HashMap;
import java.util.Map;
/**
* A data logger that logs to an underlying logger, prepending all logged data with a specific
* prefix. Useful for logging nested data structures.
* A backend that logs to an underlying backend, prepending all logged data with a specific prefix.
* Useful for logging nested data structures.
*/
public class SubLogger implements DataLogger {
public class NestedBackend implements EpilogueBackend {
private final String m_prefix;
private final DataLogger m_impl;
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
private final EpilogueBackend m_impl;
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
/**
* Creates a new sublogger underneath another logger.
* Creates a new nested backed underneath another backend.
*
* @param prefix the prefix to append to all data logged in the sublogger
* @param impl the data logger to log to
* @param prefix the prefix to append to all data logged in the nested backend
* @param impl the backend to log to
*/
public SubLogger(String prefix, DataLogger impl) {
public NestedBackend(String prefix, EpilogueBackend impl) {
// Add a trailing slash if not already present
if (prefix.endsWith("/")) {
this.m_prefix = prefix;
@@ -34,8 +34,8 @@ public class SubLogger implements DataLogger {
}
@Override
public DataLogger getSubLogger(String path) {
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
public EpilogueBackend getNested(String path) {
return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
}
@Override

View File

@@ -6,14 +6,14 @@ package edu.wpi.first.epilogue.logging;
import edu.wpi.first.util.struct.Struct;
/** Null data logger implementation that logs nothing. */
public class NullLogger implements DataLogger {
/** Null backend implementation that logs nothing. */
public class NullBackend implements EpilogueBackend {
/** Default constructor. */
public NullLogger() {}
public NullBackend() {}
@Override
public DataLogger getSubLogger(String path) {
// Since a sublogger would still log nothing and has no state, we can just return the same
public EpilogueBackend getNested(String path) {
// Since a nested backend would still log nothing and has no state, we can just return the same
// null-logging implementation
return this;
}

View File

@@ -19,10 +19,10 @@ class ClassSpecificLoggerTest {
}
@Override
protected void update(DataLogger dataLogger, Point2d object) {
dataLogger.log("x", object.x);
dataLogger.log("y", object.y);
dataLogger.log("dim", object.dim);
protected void update(EpilogueBackend backend, Point2d object) {
backend.log("x", object.x);
backend.log("y", object.y);
backend.log("dim", object.dim);
}
}
}
@@ -31,14 +31,14 @@ class ClassSpecificLoggerTest {
void testReadPrivate() {
var point = new Point2d(1, 4, 2);
var logger = new Point2d.Logger();
var dataLog = new TestLogger();
logger.update(dataLog.getSubLogger("Point"), point);
var dataLog = new TestBackend();
logger.update(dataLog.getNested("Point"), point);
assertEquals(
List.of(
new TestLogger.LogEntry<>("Point/x", 1.0),
new TestLogger.LogEntry<>("Point/y", 4.0),
new TestLogger.LogEntry<>("Point/dim", 2)),
new TestBackend.LogEntry<>("Point/x", 1.0),
new TestBackend.LogEntry<>("Point/y", 4.0),
new TestBackend.LogEntry<>("Point/dim", 2)),
dataLog.getEntries());
}
}

View File

@@ -10,36 +10,36 @@ import static org.junit.jupiter.api.Assertions.assertSame;
import java.util.List;
import org.junit.jupiter.api.Test;
class LazyLoggerTest {
class LazyBackendTest {
@Test
void lazyOfLazyReturnsSelf() {
var lazy = new LazyLogger(new NullLogger());
var lazy = new LazyBackend(new NullBackend());
assertSame(lazy, lazy.lazy());
}
@Test
void lazyInt() {
var logger = new TestLogger();
var lazy = new LazyLogger(logger);
var backend = new TestBackend();
var lazy = new LazyBackend(backend);
{
// First time logging to "int" should go through
lazy.log("int", 0);
assertEquals(List.of(new TestLogger.LogEntry<>("int", 0)), logger.getEntries());
assertEquals(List.of(new TestBackend.LogEntry<>("int", 0)), backend.getEntries());
}
{
// Logging the current value shouldn't go through
lazy.log("int", 0);
assertEquals(List.of(new TestLogger.LogEntry<>("int", 0)), logger.getEntries());
assertEquals(List.of(new TestBackend.LogEntry<>("int", 0)), backend.getEntries());
}
{
// Logging a new value should go through
lazy.log("int", 1);
assertEquals(
List.of(new TestLogger.LogEntry<>("int", 0), new TestLogger.LogEntry<>("int", 1)),
logger.getEntries());
List.of(new TestBackend.LogEntry<>("int", 0), new TestBackend.LogEntry<>("int", 1)),
backend.getEntries());
}
{
@@ -47,10 +47,10 @@ class LazyLoggerTest {
lazy.log("int", 0);
assertEquals(
List.of(
new TestLogger.LogEntry<>("int", 0),
new TestLogger.LogEntry<>("int", 1),
new TestLogger.LogEntry<>("int", 0)),
logger.getEntries());
new TestBackend.LogEntry<>("int", 0),
new TestBackend.LogEntry<>("int", 1),
new TestBackend.LogEntry<>("int", 0)),
backend.getEntries());
}
}
}

View File

@@ -12,10 +12,10 @@ import java.util.List;
import java.util.Map;
@SuppressWarnings("PMD.TestClassWithoutTestCases") // This is not a test class!
public class TestLogger implements DataLogger {
public class TestBackend implements EpilogueBackend {
public record LogEntry<T>(String identifier, T value) {}
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
private final List<LogEntry<?>> m_entries = new ArrayList<>();
@@ -24,8 +24,8 @@ public class TestLogger implements DataLogger {
}
@Override
public DataLogger getSubLogger(String path) {
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
public EpilogueBackend getNested(String path) {
return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
}
@Override

Some files were not shown because too many files have changed in this diff Show More