Squashed 'third_party/allwpilib/' changes from e4b91005cf..83f1860047
83f1860047 [wpilib] Add/update documentation to PneumaticBase and subclasses (NFC) (#4881)
9872e676d8 [commands] Make Subsystem destructor virtual (#4892)
25db20e49d [hal] Fix segfault in various HAL functions (#4891)
b0c6724eed [glass] Add hamburger menu icon to titlebars (#4874)
f0fa8205ac Add missing compiler flags and fix warnings (#4889)
42fc4cb6bc [wpiutil] SafeThread: Provide start/stop hooks (#4880)
cc166c98d2 [templates] Add Command-based skeleton template (#4861)
3f51f10ad3 [build] Update to 2023v3 image (#4886)
1562eae74a [ntcore] Refactor meta-topic decoding from glass (#4809)
b632b288a3 Fix usages of std::min and std::max to be windows safe (#4887)
c11bd2720f [wpilibc] Add internal function to reset Shuffleboard instance (#4884)
f1151d375f [ntcore] Add method to get server time offset (#4847)
fe1b62647f [hal,wpilib] Update documentation for getComments (NFC) (#4879)
c49a45abbd [build] Fix examples linking in incorrect jni library (#4873)
bc3d01a721 [build] Add platform check to doxygen plugin (#4862)
bc473240ae Add Jetbrains Fleet folder to .gitignore (#4872)
2121bd5fb8 [wpimath] Remove RKF45 (#4870)
835f8470d6 [build] Fix roborio cross-compiler on arm hosts (#4864)
6cfe5de00d [ntcore] Don't deadlock server on early destroy (#4863)
2ac41f3edc [hal, wpilib] Add RobotController.getComments() (#4463)
26bdbf3d41 Java optimization and formatting fixes (#4857)
92149efa11 Spelling and grammar cleanups (#4849)
176fddeb4c [commands] Add functions to HID classes to allow use of axes as BooleanEvents/Triggers (#4762)
87a34af367 [templates] Add bindings to command-based template (#4838)
4534e75787 [examples] Remove redundant MotorControl example (#4837)
1cbebaa2f7 [commands] Remove final semicolon from test macro definition (#4859)
6efb9ee405 [commands] Add constructor for SwerveControllerCommand that takes a HolonomicDriveController (#4785)
1e7fcd5637 [cscore] Change run loop functions to not be mac specific (#4854)
1f940e2b60 [apriltag] Add C++ wrappers, rewrite Java/JNI to match (#4842)
a6d127aedf [build] Add missing task dependency in wpilibjExamples (#4852)
b893b3d6d3 [cscore] Add support for USB cameras on macOS (#4846)
1696a490fa [glass] Add support for alternate NT ports (#4848)
40a22d69bc [glass] Add support for alternate NT ports (#4848)
e84dbfede0 [wpilib] GenericHID: Add rumble both option (#4843)
8aa9dbfa90 [examples] Link apriltag package in examples build.gradle (#4845)
eda2fa8a17 [build] Update Spotless (#4840)
d20594db0d Fix typos (#4839)
dd8ecfdd54 [commands] Fix typo in waitUntil docs (NFC) (#4841)
17ceebfff4 [apriltag] Clean up apriltag JNI (#4823)
8b74ab389d [examples] RapidReactCommandBot: Fix array indices (#4833)
1aad3489c2 [sim] Implement PD total current and power (#4830)
2744991771 [wpimath] Fix docs in SwerveModulePosition (#4825)
ffbf6a1fa2 [commands] Disable regularly failing unit test (#4824)
fbabd0ef15 [commands] Enhance Command Sendable implementations (#4822)
7713f68772 [hal] Use atomic rather then mutex for DS Data updates (#4787)
701995d6cc [examples] Update Command-based starter project (#4778)
bf7068ac27 [wpilibc] Add missing PPS implementation for C++ (#4821)
aae0f52ca6 [ntcore] NetworkTable: fix visibility of get/set value (#4820)
ee02fb7ba7 [hal] Add support for Pulse-Per-Second signal (#4819)
518916ba02 [wpilib] Fix DS mode thread event being manual reset accidentally (#4818)
3997c6635b [hal] Update to new image, use new TCP notify callback and new duty cycle API (#4774)
cc8675a4e5 [examples] Add comment on how to view elevator sim (NFC) (#4482)
fb2c170b6e [ntcore] Simplify local startup (#4803)
7ba8a9ee1f [wpimath] ProfiledPIDController: Add to SendableRegistry (#4656)
c569d8e523 [wpilib] Joystick.getMagnitude(): use hypot() function (#4816)
2a5e89fa97 [apriltag] Improve description of pose coordinates (NFC) (#4810)
cc003c6c38 [apriltag] Fix AprilTagFieldLayout JSON name (#4814)
5522916123 [commands] CommandXBoxController bumper documentation fix (NFC) (#4815)
967b30de3a [glass] Fix NT view UpdateClients() bug (#4808)
3270d4fc86 [wpimath] Rewrite pose estimator docs (#4807)
be39678447 [apriltag] Add test to ensure apriltagjni loads (#4805)
61c75deb2a [commands] Test no-op behavior of scheduling a scheduled command (#4806)
a865f48e96 [ntcore] Pass pub/sub options as a unified PubSubOptions struct (#4794)
f66a667321 [commands] Fix incorrect Trigger docs (NFC) (#4792)
f8d4e9866e [ntcore] Clean up ntcore_test.h (#4804)
7e84ea891f [wpimath] Fix ComputerVisionUtil transform example in parameter docs (NFC) (#4800)
da3ec1be10 [wpimath] Change terminology for ArmFeedforward gravity gain (NFC) (#4791)
944dd7265d [wpilibc] Add C++ Notifier error handling, update java notifier error message (#4795)
6948cea67a [wpiutil] Fix MemoryBuffer initialization (#4797)
a31459bce6 [wpiutil] Fix UnescapeCString overflow when inputSize < 2 (#4796)
4a0ad6b48c [wpimath] Rotation2d: Add reference to angleModulus in docs (NFC) (#4786)
e6552d272e [ntcore] Remove table multi-subscriber (#4789)
bde383f763 [hal] Replace const char* with std::string_view in Driver Station sim functions (#4532)
5a52b51443 [hal] Add RobotController.getSerialNumber() (#4783)
69a66ec5ec [wpilib] Fix multiple motor safety issues (#4784)
989c9fb29a [wpimath] Revert Rotation2D change that limits angles (#4781)
0f5b08ec69 [wpigui] Update imgui to 1.89.1+ (#4780)
fba191099c [examples] AddressableLED: Add unit test (#4779)
b390cad095 [wpilibj] Consistently use ErrorMessages.requireNonNullParam (#4776)
b9772214d9 [wpilib] Sendable: Don't call setter for getter changes
342c375a71 [ntcore] Add subscriber option to exclude single publisher
b0e4053087 [ntcore] Use int for options instead of double
f3e666b7bb [cscore] Convert YUYV and UYVY directly to grayscale (#4777)
b300518bd1 [hal] Add CAN Stream API to Java through JNI bindings (#4193)
be27171236 [wpilibj] Shuffleboard: Check for null sendable (#4772)
4bbdbdfb48 [commands] Move GroupedCommands to CommandScheduler (#4728)
f18fd41ac3 [wpimath] Remove broken and obsoleted ComputerVisionUtil functions (#4775)
2d0faecf4f [glass] DataSource: Add spinlock to protect value (#4771)
348bd107fc [hal] Add CANManufacturer for The Thrifty Bot (#4773)
3149dc64b8 [examples] HatchbotInlined: Use Subsystem factories (#4765)
8618dd4160 [glass, wpilib] Replace remaining references to Speed Controller with Motor Controller (#4769)
72e21a1ed1 [apriltag] Use wpilibsuite fork of apriltag (#4764)
eab0d929e6 [commands] CommandGenericHID POV methods: Fix docs (NFC) (#4760)
6789869663 [wpilib] Call set(0) rather than disable for stopMotor (#4763)
c9dea2968d [cscore] Emit warning that USB Camera isn't supported on OSX (#4766)
8f402645f5 [commands] Fix PIDSubsystem setSetpoint behavior (#4759)
f24ad1d715 [build] Upgrade to googletest 1.12.1 (#4752)
ff88756864 [wpimath] Add new DCMotor functions for alternative calculations and reduction calculation (#4749)
f58873db8e [wpimath] Remove extra terms in matrix for pose estimator docs (#4756)
37e969b41a [wpimath] Add constructors to pose estimators with default standard deviations (#4754)
13cdc29382 [ci] Rename comment command from "/wpiformat" to "/format" (#4755)
6e23985ae6 [examples] Add main include directory to test builds (#4751)
66bb0ffb2c [examples] Add unit testing infrastructure (#4646)
74cc86c4c5 [wpimath] Make transform tests use pose/transform equality operators (#4675)
e22d8cc343 [wpimath] Use Odometry for internal state in Pose Estimation (#4668)
68dba92630 [ci] Update mac and windows builds to Java 17 (#4750)
23bfc2d9ab [sim] Remove unmaintained Gazebo support (#4736)
1f1461e254 [wpilib] Add method to enable/disable LiveWindow in test mode (#4678)
eae68fc165 [wpimath] Add tolerance for Rotation3d rotation matrix special orthogonality (#4744)
4c4545fb4b [apriltag] Suppress warning (#4743)
16ffaa754d [docs] Generate docs for apriltag subproject (#4745)
5e74ff26d8 [apriltag, build] Update native utils, add apriltag impl and JNI (#4733)
53875419a1 [hal] Allow overriding stderr printing by HAL_SendError (#4742)
aa6499e920 [ntcore] Fix special topic multi-subscriber handling (#4740)
df70351107 [build] Fix cmake install of thirdparty includes (#4741)
e9bd50ff9b [glass] NT view: clear meta-topic info on disconnect (#4732)
9b319fd56b [ntcore] Add sub option for local vs remote changes (#4731)
18d28ec5e3 [ntcore] Remove duplicate value checking from ClientImpl
bdfb625211 [ntcore] Send duplicate values to network if necessary
21003e34eb [commands] Update Subsystem factories and example to return CommandBase (#4729)
70080457d5 [commands] Refactor ProxyScheduleCommand, SelectCommand into ProxyCommand (#4534)
e82cd5147b [wpilib] Tweak Color HSV formula and use in AddressableLED (#4724)
ec124bb662 [commands] Allow unsetting a subsystem's default command (#4621)
2b2aa8eef7 [examples] Update all examples to use NWU coordinate conventions (#4725)
cb38bacfe8 [commands] Revert to original Trigger implementation (#4673)
15561338d5 [commands] Remove one more default command isFinished check (#4727)
ca35a2e097 Add simgui files to .gitignore (#4726)
20dbae0cee [examples] Renovate command-based examples (#4409)
1a59737f40 [commands] Add convenience factories (#4460)
42b6d4e3f7 Use defaulted comparison operators in C++ (#4723)
135c13958f [wpigui] Add FontAwesome (#4713)
ffbfc61532 [ntcore] Add NetworkTable table-specific listeners (#4640)
8958b2a4da [commands] Add property tests for command compositions (#4715)
e4ac09077c [wpilib] Add link to MotorSafety article (#4720)
f40de0c120 [commands] Add C++ factory templates (#4686)
51fa3e851f [build] cmake: Use FetchContent instead of ExternalProject (#4714)
1da84b2255 [wpigui] Reload fonts to scale rather than preloading (#4712)
e43e2fbc84 [wpiutil] StringExtras: Add UnescapeCString (#4707)
5804d8fa84 [ntcore] Server: Properly handle multiple subscribers (#4717)
169ef5fabf [glass] Update NT view for topicsOnly and sendAll changes (#4718)
148759ef54 [examples] CANPDP: Expand properties shown (#4687)
58ed112b51 [commands] RepeatCommand: restart on following iteration (#4706)
dd1da77d20 [readme] Fix broken CI badge (#4710)
7cda85df20 [build] Check Gradle plugin repo last to fix CI (#4711)
7ed9b13277 [build] Bump version plugin to fix null tag (#4705)
6b4f26225d [apriltag] Fix pluralization of apriltag artifacts (#4671)
b2d2924b72 [cscore] Add Y16 image support (#4702)
34ec89c041 [wpilibc] Shuffleboard SimpleWidget: Return pointer instead of reference (#4703)
e15200068d [ci] Disable HW testbench runs (#4704)
d5200db6cd [wpimath] Rename HolonomicDriveController.calculate params (#4683)
2ee3d86de4 [wpimath] Clarify Rotation3d roll-pitch-yaw direction (#4699)
9f0a8b930f [cscore] Use MFVideoFormat_L8 for Gray on Windows (#4701)
2bca43779e [cscore] Add UYVY image support (#4700)
4307d0ee8b [glass] Plot: allow for more than 11 plots (#4685)
3fe8d355a1 [examples] StateSpaceDifferentialDriveSimulation: Use encoder reversed constants (#4682)
b44034dadc [ntcore] Allow duplicate client IDs on server (#4676)
52d2c53888 [commands] Rename Java factory wait() to waitSeconds() (#4684)
76e918f71e [build] Fix JNI artifacts linking to incorrect libraries (#4680)
0bee875aff [commands] Change C++ CommandPtr to use CommandBase (#4677)
98e922313b [glass] Don't check IsConnected for NT widgets (#4674)
9a36373b8f [apriltag] Switch 2022 apriltag layout length and width values (#4670)
cf8faa9e67 [wpilib] Update values on controllable sendables (#4667)
5ec067c1f8 [ntcore] Implement keep duplicates pub/sub flag (#4666)
e962fd2916 [ntcore] Allow numeric-compatible value sets (#4620)
88bd67e7de [ci] Update clang repositories to jammy (#4665)
902e8686d3 [wpimath] Rework odometry APIs to improve feature parity (#4645)
e2d49181da Update to native utils 2023.8.0 (#4664)
149bac55b1 [cscore] Add Arducam OV9281 exposure quirk (#4663)
88f7a3ccb9 [wpimath] Fix Pose relativeTo documentation (#4661)
8acce443f0 [examples] Fix swerve examples to use getDistance for turning encoder (#4652)
295a1f8f3b [ntcore] Fix WaitForListenerQueue (#4662)
388e7a4265 [ntcore] Provide mechanism to reset internals of NT instance (#4653)
13aceea8dc [apriltag] Fix FieldDimensions argument order (#4659)
c203f3f0a9 [apriltag] Fix documentation for AprilTagFieldLayout (#4657)
f54d495c90 Fix non initialized hal functionality during motor safety init (#4658)
e6392a1570 [cmd] Change factories return type to CommandBase (#4655)
53904e7cf4 [apriltag] Split AprilTag functionality to a separate library (#4578)
2e88a496c2 [wpimath] Add support for swerve joystick normalization (#4516)
ce4c45df13 [wpimath] Rework function signatures for Pose Estimation / Odometry (#4642)
0401597d3b [readme] Add wpinet to MavenArtifacts.md (#4651)
2e5f9e45bb [wpimath] Remove encoder reset comments on Swerve, Mecanum Odometry and Pose Estimation (#4643)
e4b5795fc7 [docs] Disable Doxygen for memory to fix search (#4636)
03d0ea188c [build] cmake: Add missing wpinet to installed config file (#4637)
3082bd236b [build] Move version file to its own source set (#4638)
b7ca860417 [build] Use build cache for sign step (#4635)
64838e6367 [commands] Remove unsafe default command isFinished check (#4411)
1269d2b901 [myRobot] Disable spotbugs (#4565)
14d8506b72 [wpimath] Fix units docs for LinearSystemId::IdentifyDrivetrainSystem() (#4600)
d1d458db2b [wpimath] Constrain Rotation2d range to -pi to pi (#4611)
f656e99245 [readme] Add links to development build documentation (#4481)
6dd937cef7 [commands] Fix Trigger API docs (NFC) (#4599)
49047c85b9 [commands] Report error on C++ CommandPtr use-after-move (#4575)
d07267fed1 [ci] Upgrade containers to Ubuntu 22.04 and remove libclang installation (#4633)
b53ce1d3f0 [build, wpiutil] Switch macos to universal binaries (#4628)
5a320c326b [upstream_util, wpiutil] Refactor python scripts (#4614)
c4e526d315 [glass] Fix NT Mechanism2D (#4626)
d122e4254f [ci] Run spotlessApply after wpiformat in comment command (#4623)
5a1e7ea036 [wpilibj] FieldObject2d: Add null check to close() (#4619)
179f569113 [ntcore] Notify locally on SetDefault (#4617)
b0f6dc199d [wpilibc] ShuffleboardComponent.WithProperties: Update type (#4615)
7836f661cd [wpimath] Add missing open curly brace to units/base.h (#4613)
dbcc1de37f [wpimath] Add DifferentialDriveFeedforward classes which wrap LinearPlantInversionFeedforward (#4598)
93890c528b [wpimath] Add additional angular acceleration units (#4610)
3d8d5936f9 [wpimath] Add macro for disabling units fmt support (#4609)
2b04159dec [wpimath] Update units/base.h license header (#4608)
2764004fad [wpinet] Fix incorrect jni definitions (#4605)
85f1bb8f2b [wpiutil] Reenable jni check task (#4606)
231ae2c353 [glass] Plot: Fix Y-axis not being saved (#4594)
e92b6dd5f9 [wpilib] Fix AprilTagFieldLayout JSON property name typos (#4597)
2a8e0e1cc8 Update all dependencies that use grgit (#4596)
7d06e517e9 [commands] Move SelectCommand factory impl to header (#4581)
323524fed6 [wpimath] Remove deprecated units/units.h header (#4572)
d426873ed1 [commands] Add missing PS4 triangle methods (#4576)
5be5869b2f [apriltags] Use map as internal data model (#4577)
b1b4c1e9e7 [wpimath] Fix Pose3d transformBy rotation type (#4545)
a4054d702f [commands] Allow composing two triggers directly (#4580)
0190301e09 [wpilibc] Explicitly mark EventLoop as non-copyable/non-movable (#4579)
9d1ce6a6d9 [ntcore] Catch file open error when saving preferences (#4571)
5005e2ca04 [ntcore] Change Java event mask to EnumSet (#4564)
fa44a07938 [upstream-utils][mpack] Add upstream util for mpack (#4500)
4ba16db645 [ntcore] Various fixes and cleanups (#4544)
837415abfd [hal] Fix joysticks either crashing or returning 0 (#4570)
2c20fd0d09 [wpilib] SingleJointedArmSim: Check angle equals limit on wouldHit (#4567)
64a7136e08 [wpimath] SwerveDrivePoseEstimator: Restore comment about encoder reset (#4569)
b2b473b24a [wpilib] Add AprilTag and AprilTagFieldLayout (#4421)
7aab8fa93a [build] Update to Native Utils 2023.6.0 (#4563)
12c2851856 [commands] WrapperCommand: inherit from CommandBase (#4561)
0da169dd84 [wpimath] Remove template argument from ElevatorFeedforward (#4554)
2416827c25 [wpimath] Fix docs for pose estimator local measurement models (#4558)
1177a3522e [wpilib] Fix Xbox/PS4 POV sim for port number constructors (#4548)
102344e27a [commands] HID classes: Add missing methods, tweak return types (#4557)
1831ef3e19 [wpilib] Fix Shuffleboard SuppliedValueWidget (#4559)
a9606ce870 [wpilib] Fix Xbox/PS4 POV sim (#4546)
6c80d5eab3 [wpimath] Remove unused SymbolExports.h include from units/base.h (#4541)
b114006543 [ntcore] Unify listeners (#4536)
32fbfb7da6 [build] cmake: Install ntcore generated include files (#4540)
02465920fb [build] Update native utils to 2023.4.0 (#4539)
3a5a376465 [wpimath] Increase constexpr support in geometry data types (#4231)
1c3c86e9f1 [ntcore] Cache GetEntry(name) values (#4531)
dcda09f90a [command] Rename trigger methods (#4210)
66157397c1 [wpilib] Make drive classes follow NWU axes convention (#4079)
9e22ffbebf [ntcore] Fix null deref in NT3 client (#4530)
648ab6115c [wpigui,dlt,glass,ov] Support arm in GUI tools (#4527)
8bc3b04f5b [wpimath] Make ComputerVisionUtil use 3D geometry classes (#4528)
cfb84a6083 [wpilibc] Don't hang waiting for NT server to start (#4524)
02c47726e1 [wpimath] Remove unused odometry instance from DifferentialDrivePoseEstimator test (#4522)
b2a0093294 [ci] Revert upgrade of github-pages-deploy-action (#4521)
2a98d6b5d7 [wpimath] PIDController: Add getters for position & velocity tolerances (#4458)
9f36301dc8 [ci] Write wpiformat patch to job summary (#4519)
901fc555f4 [wpimath] Position Delta Odometry for Mecanum (#4514)
4170ec6107 [wpimath] Position Delta Odometry for Swerve (#4493)
fe400f68c5 [docs] Add wpinet to docs build (#4517)
794669b346 [ntcore] Revamp listeners (#4511)
dcfa85a5d5 [ci] Build sanitizers with clang-14 (#4518)
15ad855f1d [ntcore] Add UnitTopic<T> (C++ only) (#4497)
11244a49d9 [wpilib] Add IsConnected function to all gyros (#4465)
1d2e8eb153 [build] Update myRobot deployment (#4515)
ad53fb19b4 [hal] Use new HMB api for addressable LED (#4479)
ba850bac3b [hal] Add more shutdown checks and motor safety shutdown (#4510)
023a5989f8 [ntcore] Fix typo in NetworkServer client connect message (#4512)
c970011ccc [docs] Add Doxygen aliases used by Foonathan memory (#4509)
07a43c3d9a [readme] Document clang-format version and /wpiformat (#4503)
a05b212b04 [ci] Revert changes to wpiformat task from #4501 (#4508)
09faf31b67 [commands] Replace Command HID inheritance with delegation (#4470)
9e1f9c1133 [commands] Add command factories (#4476)
f19d2b9b84 [ci] Add NUMBER environment variable to comment command commit script (#4507)
a28f93863c [ci] Push comment command commit directly to PR (#4506)
c9f61669b8 [ci] Fix comment command commit push (#4505)
dcce5ad3b3 [ci] Update github-script API usage (#4504)
6836e5923d [wpilibc] Restore get duty cycle scale factor (#4502)
335188c652 [dlt] Add deselect/select all buttons to download view (#4499)
60a29dcb99 [glass] Field2D: Add "hidden" option for objects (#4498)
b55d5b3034 [ci] Update deprecated github actions (#4501)
10ed4b3969 [ntcore] Various NT4 fixes (#4474)
4a401b89d7 [hal, wpilib] New DS thread model and implementation (#3787)
c195b4fc46 [wpimath] Clean up PoseEstimator nominal dt docs (#4496)
8f2e34c6a3 [build] Remove wpilib prefix from CMake flat install (#4492)
150d692df7 [wpimath] Remove unused private PoseEstimator function (#4495)
3e5bfff1b5 [wpimath] FromFieldRelativeSpeeds: Add ChassisSpeeds overload (#4494)
9c7e66a27d [commands] C++: Add CommandPtr overload for SetDefaultCommand (#4488)
0ca274866b [build] Fix CMake system library opt-ins (#4487)
dc037f8d41 [commands] Remove EndlessCommand (#4483)
16cdc741cf [wpimath] Add Pose3d(Pose2d) constructor (#4485)
9d5055176d [build] cmake: Allow disabling ntcore build (#4486)
d1e66e1296 [build] Compile all java code with inline string concatenation (#4490)
1fc098e696 Enable log macros to work with no args (#4475)
878cc8defb [wpilib] LiveWindow: Add enableAllTelemetry() (#4480)
8153911160 [build] Fix MSVC runtime archiver to grab default runtime (#4478)
fbdc810887 Upgrade to C++20 (#4239)
396143004c [ntcore] Add ntcoreffi binary (#4471)
1f45732700 [build] Update to 2023.2.4 native-utils and new dependencies (#4473)
574cb41c18 [ntcore] Various fixes (#4469)
d9d6c425e7 [build] Force Java 11 source compatibility (#4472)
58b6484dbe Switch away from NI interrupt manager to custom implementation (#3705)
ca43fe2798 [wpimath] Use Units conversions in ComputerVisionUtil docs (NFC) (#4464)
87a64ccedc [hal] Convert DutyCycle Raw output to be a high time measurement (#4466)
89a3d00297 [commands] Add FinallyDo and HandleInterrupt decorators (#4412)
1497665f96 [commands] Add C++ versions of Java-only decorators (#4457)
27b173374e [wpimath] Add minLinearAccel parameter to DifferentialDriveAccelerationLimiter (#4422)
2a13dba8ac [wpilib] TrajectoryUtil: Fix ambiguous documentation (NFC) (#4461)
77301b126c [ntcore] NetworkTables 4 (#3217)
90cfa00115 [build] cmake: Fix libssh include directory order (#4459)
5cf961edb9 [commands] Refactor lambda-based commands to inherit FunctionalCommand (#4451)
b2276e47de [wpimath] Enable continuous angle input for HolonomicDriveController (#4453)
893b46139a [fieldImages] Add utilities to simplify loading of fields (#4456)
60e29627c0 [commands] C++ unique_ptr migration (#4319)
3b81cf6c35 [wpilib] Improve Color.toString (#4450)
5c067d30a0 [wpinet] WebSocket: Add SendFrames() (#4445)
ceaf493811 [wpiutil] MakeJByteArray: Use span<uint8> instead of string_view (#4446)
10e04e2b13 [examples] FrisbeeBot: Fix reference capture (#4449)
726f67c64b [build] Add exeSplitSetup (#4444)
c7b7624c1c [wpiutil] Add MessagePack utility functions (#4448)
d600529ec0 [wpinet] uv::Async: Add UnsafeSend() (#4447)
b53b3526a2 [wpimath] Add CoordinateSystem conversion for Transform3d (#4443)
38bb23eb18 [wpimath] Add scalar multiply and divide operators to all geometry classes (#4438)
3937ff8221 [wpilib] Remove deprecated Controller class (#4440)
abbfe244b5 [wpilib] Improve Color FromHSV (#4439)
4ddb8aa0dd [sim] Provide function that resets all simulation data (#4016)
a791470de7 Clean up Java warning suppressions (#4433)
17f504f548 [hal,wpilib] Fix SPI Mode Setting (#4434)
773198537c [wpiutil] Add wpi::scope_exit (#4432)
5ac658c8f0 [wpiutil] Logger: Conditionalize around WPI_LOG (#4431)
8767e4a941 [wpiutil] DataLog: Fix SetMetadata output (#4430)
8c4af073f4 [wpiutil] Synchronization: shutdown race protection (#4429)
c79f38584a [build] Fix Java integration tests (#4428)
36c08dd97c [build] Fix cmake install of fmtlib (#4426)
69b7b3dd7d [ci] Remove the Windows cmake job (#4425)
738c75fed8 [readme] Fix formatting/linting link (#4423)
4eb1d03fb3 [wpimath] Document C++ LinearFilter exception (#4417)
ba4ec6c967 [build] Fix clang-tidy false positive on Linux (#4406)
97836f0e55 [commands] Fix ProfiledPIDSubsystem setGoal behavior (#4414)
fdfb85f695 [wpimath] Remove Java LQR constructor that takes a controller gain matrix (#4419)
ab1baf4832 [wpimath] Add rotation matrix constructor to Rotation3d (#4413)
9730032866 [wpimath] Document LQR and KalmanFilter exceptions (#4418)
5b656eecf6 [wpimath] Fix HTML5 entity (#4420)
9ae38eaa7c [commands] Add owning overload to ProxyScheduleCommand (#4405)
cb33bd71df [commands] deprecate withInterrupt decorator (#4407)
d9b4e7b8bf [commands] Revert "Change grouping decorator impl to flatten nested group structures (#3335)" (#4402)
0389bf5214 [hal] REVPH: Improve handling of disconnected CAN Bus (#4169)
4267fa08d1 [wpilibc] ADIS IMUs: Fix memory leak (#4170)
65c8fbd452 [wpilib] MotorControllerGroup: Override setVoltage (#4403)
f36162fddc [wpimath] Improve Discretization internal docs (#4400)
5149f7d894 [wpimath] Add two-vector Rotation3d constructor (#4398)
20b5bed1cb [wpimath] Clean up Java Quaternion class (#4399)
f18dd1905d [build] Include all thirdparty sources in distribution (#4397)
aa9d7f1cdc [wpiutil] Import foonathan memory (#4306)
2742662254 [ci] Remove a couple of obsolete clang-tidy checks (#4396)
a5df391166 [hal, wpilib] Fix up DIO pulse API (#4387)
59e6706b75 [glass] Turn on docking by default
8461bb1e03 [glass] Add support for saving docking info
b873e208b4 [wpigui] Add support for imgui config flags
873e72df8c [build] Update imgui to 1.88 docking branch
c8bd6fc5b4 [ci] Fix comment-command (take 2) (#4395)
fed68b83b4 [ci] Fix comment-command action not running runners (#4393)
0ef8a4e1df [wpimath] Support formatting more Eigen types (#4391)
c393b3b367 [build] Update to native utils 2023.1.0 and Gradle 7.5.1 (#4392)
b5a17f762c [wpimath] Add direction to slew rate limiter (#4377)
fafc81ed1a [wpiutil] Upgrade to fmt 9.1.0 (#4389)
cc56bdc787 [wpiutil] SafeThread: Add Synchronization object variant (#4382)
4254438d8d [commands] Mark command group lifecycle methods as final (#4385)
97c15af238 [wpimath] LinearSystemId: Fix docs, move C++ impls out of header (#4388)
d22ff8a158 [wpiutil] Add JNI access to C++ stderr (#4381)
fdb5a2791f [wpiutil] jni_util: Add Mac-friendly MakeJLongArray/JArrayRef (#4383)
c3a93fb995 [commands] Revamp Interruptible (#4192)
f2a8d38d2a [commands] Rename Command.repeat to repeatedly (#4379)
9e24c6eac0 [wpiutil] Logger: paren-protect instance usage in macro (#4384)
fe4d12ce22 [wpimath] Add LTV controller derivations and make enums private (#4380)
eb08486039 [build] Fix MacOS binary rpath generation (#4376)
ccf83c634a [build] Use native-utils platform names instead of raw strings (#4375)
3fd69749e7 [docs] Upgrade to doxygen 1.9.4 (#4370)
594df5fc08 [wpinet] uv/util.h: Pull in ws2_32.lib on Windows for ntohs (#4371)
539070820d [ci] Enable asan for wpinet and wpiutil (#4369)
564a56d99b [wpinet] Fix memory leak in WorkerThreadTest (#4368)
5adf50d93c [upstream_utils] Refactor upstream_utils scripts (#4367)
d80e8039d7 [wpiutil] Suppress fmtlib clang-tidy warning in C++20 consteval contexts (#4364)
0e6d67b23b [upstream_utils] Remove yapf format disable comment (#4366)
be5270697a [build] Suppress enum-enum deprecation warning in OpenCV (#4365)
8d28851263 Add Rosetta install command to build requirements (#4363)
3d2115c93e [wpinet] include-what-you-use in MulticastTest (#4360)
91002ae3cc [wpimath] Upgrade to Drake 1.6.0 (#4361)
148c18e658 [wpinet] Upgrade to libuv 1.44.2 (#4362)
a2a5c926b6 Fix clang-tidy warnings (#4359)
ea6b1d8449 [wpiutil] Remove unused ManagedStatic class (#4358)
ac9be78e27 Use stricter C++ type conversions (#4357)
151dabb2af [wpiutil] Upgrade to fmt 9.0.0 (#4337)
340465c929 [ci] Upgrade to clang-format and clang-tidy 14 (NFC) (#4347)
d45bcddd15 [examples] Add comments to StateSpaceDifferentialDrive (#4341)
0e0786331a Update LLVM libraries to 14.0.6 (#4350)
c5db23f296 [wpimath] Add Eigen sparse matrix and iterative solver support (#4349)
44abc8dfa6 [upstream_utils] Remove git version from upstream patches (#4351)
3fdb2f767d [wpimath] Add comments with Ramsete equations (#4348)
0485f05da9 [wpilibjExamples] Upgrade jacoco to match allwpilib (#4346)
0a5eb65231 [wpinet] Handle empty txt block for mdns announcer (#4072)
19ffebaf3e [wpilib] Add reference to I2C Lockup to API Docs (NFC) (#4340)
ce1a90d639 [hal] Replace SerialHelper "goto done" with continue (#4342)
d25af48797 [ci] Make upstream_utils CI fail on untracked files (#4339)
ebb836dacb [examples] Fix negations in event loop examples (#4334)
d83e202f00 [upstream_utils] Update paths in update_fmt.py (#4338)
3ccf806064 [wpimath] Remove redundant LinearFilter.finiteDifference() argument (#4335)
6f1e01f8bd [wpimath] Document example of online filtering for LinearFilter.finiteDifference() (#4336)
1023c34b1c [readme] Update location of ni-libraries (#4333)
faa29d596c [wpilib] Improve Notifier docs (NFC) (#4326)
add00a96ed [wpimath] Improve DifferentialDriveAccelerationLimiter docs (NFC) (#4323)
82fac41244 [wpimath] Better document trackwidth parameters (NFC) (#4324)
5eb44e22a9 Format Python scripts with black (NFC) (#4325)
2e09fa7325 [build] Fix mpack cmake (#4322)
fe3c24b1ee [command] Add ignoringDisable decorator (#4305)
aa221597bc [build] Add M1 builds, change arm name, update to 2023 deps (#4315)
579a8ee229 [ci] Use one worker for Windows release Gradle build (#4318)
5105c5eab6 [wpilibj] Change "final" to "exit" in the IterativeRobotBase JavaDoc (NFC) (#4317)
787fe6e7a5 [wpiutil] Separate third party libraries (#4190)
6671f8d099 [wpigui] Update portable file dialogs (#4316)
9ac9b69aa2 [command] Reorder Scheduler operations (#4261)
e61028cb18 [build] halsim_gui: Add wpinet dependency (#4313)
661d23eaf5 [glass] Add precision setting for NetworkTable view (#4311)
666040e3e5 [hal] Throw exceptions for invalid sizes in I2C and SPI JNI (#4312)
aebc272449 [build] Upgrade to spotbugs Gradle plugin 5.0.8 (#4310)
fd884581e4 [wpilib] Add BooleanEvent/Trigger factories on HID classes (#4247)
9b1bf5c7f1 [wpimath] Move Drake and Eigen to thirdparty folders (#4307)
c9e620a920 [wpilibc] Change EventLoop data structure to vector (#4304)
41d40dd62f [wpinet] Fix libuv unused variable warning on Mac (#4299)
30f5b68264 [wpinet] Fix JNI loading error (#4295)
f7b3f4b90e [examples] Getting Started: Change Joystick to XboxController (#4194)
a99c11c14c [wpimath] Replace UKF implementation with square root form (#4168)
45b7fc445b [wpilib] Add EventLoop (#4104)
16a4888c52 [wpilib] Default off LiveWindow telemetry (#4301)
17752f1337 [ci] Split debug and release Windows builds (#4277)
abb45a68db [commands] Remove custom test wrappers (#4296)
1280a54ef3 [upstream_utils]: Make work with Python 3.8 (#4298)
f2d243fa68 [build] Change defaults for Java lints (#4300)
a4787130f4 Update using development build to work with 2023 gradlerio (#4294)
af7985e46c [wpiutil] Use invoke_result_t instead of result_of in future.h (#4293)
e9d1b5c2d0 [hal] Remove deprecated SimDevice functions (#4209)
45b598d236 [wpilibj] Add toString() methods to Color and Color8Bit (#4286)
fc37265da5 [wpimath] Add angle measurement convention to ArmFeedforward docs (NFC) (#4285)
a4ec13eb0e [wpilibjexamples] Remove unnecessary voltage desaturation
2fa52007af [wpilibc] Use GetBatteryVoltage() in MotorController::SetVoltage
d9f9cd1140 [wpimath] Reset prev_time on pose estimator reset (#4283)
8b6df88783 [wpilibj] Tachometer.getFrequency(): Fix bug (#4281)
345cff08c0 [wpiutil] Make wpi::array constexpr (#4278)
57428112ac [wpimath] Upgrade to Drake v1.3.0 (#4279)
a18d4ff154 [build] Fix tools not being copied when built with -Ponly* (#4276)
d1cd07b9f3 [wpigui] Add OpenURL (#4273)
e67f8e917a [glass] Use glfwSetKeyCallback for Enter key remap (#4275)
be2fedfe50 [wpimath] Add stdexcept include for std::invalid_argument (IWYU) (#4274)
7ad2be172e [build] Update native-utils to 2023.0.1 (#4272)
abc605c9c9 [ci] Update workflows to 20.04 base image (#4271)
3e94805220 [wpiutil] Reduce llvm collections patches (#4268)
db2e1d170e [upstream_utils] Document how to update thirdparty libraries (#4253)
96ebdcaf16 [wpimath] Remove unused Eigen AutoDiff module (#4267)
553b2a3b12 [upstream_utils] Fix stackwalker (#4265)
3e13ef42eb [wpilibc] Add missing std::array #include (include-what-you-use) (#4266)
d651a1fcec Fix internal deprecation warnings (#4257)
b193b318c1 [commands] Add unless() decorator (#4244)
ef3714223b [commands] Remove docs reference to obsolete interrupted() method (NFC) (#4262)
3d8dbbbac3 [readme] Add quickstart (#4225)
013efdde25 [wpinet] Wrap a number of newer libuv features (#4260)
816aa4e465 [wpilib] Add Pneumatics sim classes (#4033)
046c2c8972 [wpilibc] Rename SpeedControllerGroupTest.cpp (#4258)
d80e9cdf64 [upstream_utils] Use shallow clones for thirdparty repos (#4255)
7576136b4a [upstream_utils] Make update_llvm.py executable (#4254)
c3b223ce60 [wpiutil] Vendor llvm and update to 13.0.0 (#4224)
5aa67f56e6 [wpimath] Clean up math comments (#4252)
fff4d1f44e [wpimath] Extend Eigen warning suppression to GCC 12 (#4251)
0d9956273c [wpimath] Add CoordinateSystem.convert() translation and rotation overloads (#4227)
3fada4e0b4 [wpinet] Update to libuv 1.44.1 (#4232)
65b23ac45e [wpilibc] Fix return value of DriverStation::GetJoystickAxisType() (#4230)
4ac34c0141 [upstream_utils] Cleanup update_libuv.py (#4249)
8bd614bb1e [upstream_utils] Use "git am" instead of "git apply" for patches (#4248)
4253d6d5f0 [upstream_utils] Apply "git am" patches individually (#4250)
6a4752dcdc Fix GCC 12.1 warning false positives (#4246)
5876b40f08 [wpimath] Memoize CoordinateSystem and CoordinateAxis statics (#4241)
5983434a70 [cameraserver] Replace IterativeRobot in comment sample code with TimedRobot (#4238)
a3d44a1e69 [wpimath] Add Translation2d.getAngle() (#4217)
d364bbd5a7 [upstream_utils] Give vendor update scripts execute permissions (#4226)
f341e1b2be [wpimath] Document standard coordinate systems better (NFC) (#4228)
9af389b200 [wpinet] AddrToName: Initialize name (#4229)
2ae4adf2d7 [ci] Add wpiformat command to PRs (#4223)
178b2a1e88 Contributing.md: Correct version of clang-format used (#4222)
18db343cdc [wpiutil, wpinet] Vendor libuv, stack walker (#4219)
f0c821282a [build] Use artifactory mirror (#4220)
d673ead481 [wpinet] Move network portions of wpiutil into new wpinet library (#4077)
b33715db15 [wpimath] Add CoordinateSystem class (#4214)
99424ad562 [sim] Allow creating a PWMSim object from a PWMMotorController (#4039)
dc6f641fd2 [wpimath] PIDController: Reset position and velocity error when reset() is called. (#4064)
f20a20f3f1 [wpimath] Add 3D geometry classes (#4175)
708a4bc3bc [wpimath] Conserve previously calculated swerve module angles when updating states for stationary ChassisSpeeds (#4208)
ef7ed21a9d [wpimath] Improve accuracy of ComputerVisionUtil.calculateDistanceToTarget() (#4215)
b1abf455c1 [wpimath] LTVUnicycleController: Use LUT, provide default hyperparameters (#4213)
d5456cf278 [wpimath] LTVDifferentialDriveController: Remove unused variable (#4212)
99343d40ba [command] Remove old command-based framework (#4211)
ee03a7ad3b Remove most 2022 deprecations (#4205)
ce1a7d698a [wpimath] Refactor WheelVoltages inner class to a separate file (#4203)
87bf70fa8e [wpimath] Add LTV controllers (#4094)
ebd2a303bf [wpimath] Remove deprecated MakeMatrix() function (#4202)
e28776d361 [wpimath] LinearSystemLoop: Add extern templates for common cases
dac1429aa9 [wpimath] LQR: Use extern template instead of Impl class
e767605e94 [wpimath] Add typedefs for common types
97c493241f [wpimath] UnscentedKalmanFilter: Move implementation out-of-line
8ea90d8bc9 [wpimath] ExtendedKalmanFilter: Move implementation out-of-line
ae7b1851ec [wpimath] KalmanFilter: Use extern template instead of Impl class
e3d62c22d3 [wpimath] Add extern templates for common cases
7200c4951d [wpiutil] SymbolExports: Add WPILIB_IMPORTS for dllimport
84056c9347 [wpiutil] SymbolExports: Add EXPORT_TEMPLATE_DECLARE/DEFINE
09cf6eeecb [wpimath] ApplyDeadband: add a scale param (#3865)
03230fc842 [build,ci] Enable artifactory build cache (#4200)
63cf3aaa3f [examples] Don't square ArcadeDrive inputs in auto (#4201)
18ff694f02 [wpimath] Add Rotation2d.fromRadians factory (#4178)
4f79ceedd9 [wpilibc] Add missing #include (#4198)
f7ca72fb41 [command] Rename PerpetualCommand to EndlessCommand (#4177)
a06b3f0307 [hal] Correct documentation on updateNotifierAlarm (#4156)
d926dd1610 [wpimath] Fix pose estimator performance (#4111)
51bc893bc5 [wpiutil] CircularBuffer: Change Java package-private methods to public (#4181)
fbe761f7f6 [build] Increase Gradle JVM heap size (#4172)
5ebe911933 [wpimath] Add DifferentialDriveAccelerationLimiter (#4091)
3919250da2 [wpilibj] Remove finalizers (#4158)
b3aee28388 [commands] Allow BooleanSupplier for Trigger operations (#4103)
9d20ab3024 [wpilib] Allow disabling ElevatorSim gravity (#4145)
aaa69f6717 [ci] Remove 32-bit Windows builds (#4078)
355a11a414 Update Java linters and fix new PMD errors (#4157)
ffc69d406c [examples] Reduce suggested acceleration in Ramsete example (#4171)
922d50079a [wpimath] Units: fix comment in degreesToRotations (NFC) (#4159)
dd163b62ae [wpimath] Rotation2d: Add factory method that uses rotations (#4166)
bd80e220b9 [ci] Upgrade CMake actions (#4161)
aef4b16d4c [wpimath] Remove unnecessary NOLINT in LinearPlantInversionFeedforward (NFC) (#4155)
975171609e [wpilib] Compressor: Rename enabled to isEnabled (#4147)
5bf46a9093 [wpimath] Add ComputerVisionUtil (#4124)
f27a1f9bfb [commands] Fix JoystickButton.getAsBoolean (#4131)
1b26e2d5da [commands] Add RepeatCommand (#4009)
88222daa3d [hal] Fix misspelling in AnalogInput/Output docs (NFC) (#4153)
81c5b41ce1 [wpilibj] Document MechanismLigament2d angle unit (NFC) (#4142)
9650e6733e [wpiutil] DataLog: Document finish and thread safety (NFC) (#4140)
c8905ec29a [wpimath] Remove ImplicitModelFollower dt argument (#4119)
b4620f01f9 [wpimath] Fix Rotation2d interpolation in Java (#4125)
2e462a19d3 [wpimath] Constexprify units unary operators (#4138)
069f932e59 [build] Fix gl3w cmake build (#4139)
126e3de91a [wpilibc] Remove unused SetPriority() call from Ultrasonic (#4123)
ba0dccaae4 [wpimath] Fix reference to Rotation2d.fromRadians() (#4118)
e1b6e5f212 [wpilib] Improve MotorSafety documentation (NFC) (#4120)
8d79dc8738 [wpimath] Add ImplicitModelFollower (#4056)
78108c2aba [wpimath] Fix PIDController having incorrect error after calling SetSetpoint() (#4070)
cdafc723fb [examples] Remove unused LinearPlantInversionFeedforward includes (#4069)
0d70884dce [wpimath] Add InterpolatedTreeMap (#4073)
765efa325e [wpimath] Remove redundant column index from vectors (#4116)
89ffcbbe41 [wpimath] Update TrapezoidProfile class name in comment (NFC) (#4107)
95ae23b0e7 [wpimath] Improve EKF numerical stability (#4093)
d5cb6fed67 [wpimath] Support zero cost entries in MakeCostMatrix() (#4100)
d0fef18378 [wpimath] Remove redundant `this.` from ExtendedKalmanFilter.java (#4115)
d640c0f41f [wpimath] Fix pose estimator local measurement standard deviation docs (NFC) (#4113)
a2fa5e3ff7 [wpilibc] BatterySim: Provide non-initializer list versions of Calculate (#4076)
a3eea9958e [hal] Add link to FRC CAN Spec (NFC) (#4086)
db27331d7b [wpilib] Update DifferentialDrive docs (NFC) (#4085)
fdfb31f164 [dlt] Export boolean[] values (#4082)
f93c3331b3 [wpigui] disable changing directory when initializing on MacOS (#4092)
ab7ac4fbb9 [build] Fix various warnings in cmake builds (#4081)
bc39a1a293 [wpilibc] Fix moved pneumatics objects not destructing properly (#4068)
2668130e70 [wpimath] Remove SwerveDrivePoseEstimator encoder reset warning (#4066)
d27ed3722b [ci] Set actions workflow concurrency (#4060)
dae18308c9 [wpimath] Minor fixes to Rotation2d docs (NFC) (#4055)
d66555e42f [datalogtool] Add datalogtool
9f52d8a3b1 [wpilib] DriverStation: Add DataLog support for modes and joystick data
757ea91932 [wpilib] Add DataLogManager
02a804f1c5 [ntcore] Add DataLog support
9b500df0d9 [wpiutil] Add high speed data logging
5a89575b3a [wpiutil] Import customized LLVM MemoryBuffer
b8c4d7527b [wpiutil] Add MappedFileRegion
ac5d46cfa7 [wpilibc] Fix ProfiledPID SetTolerance default velocity value (#4054)
bc9e96e86f [wpilib] Absolute Encoder API and behavior fixes (#4052)
f88c435dd0 [hal] Add mechanism to cancel all periodic callbacks (#4049)
Change-Id: I49aa5b08abbefc7a045e99e19d48ce2cd8fc4d1b
git-subtree-dir: third_party/allwpilib
git-subtree-split: 83f1860047c86aa3330fcb41caf3b2047e074804
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/glass/src/app/native/cpp/main.cpp b/glass/src/app/native/cpp/main.cpp
index b1569b8..a20ff8b 100644
--- a/glass/src/app/native/cpp/main.cpp
+++ b/glass/src/app/native/cpp/main.cpp
@@ -42,6 +42,7 @@
static std::unique_ptr<glass::NetworkTablesSettings> gNetworkTablesSettings;
static glass::LogData gNetworkTablesLog;
static std::unique_ptr<glass::Window> gNetworkTablesWindow;
+static std::unique_ptr<glass::Window> gNetworkTablesInfoWindow;
static std::unique_ptr<glass::Window> gNetworkTablesSettingsWindow;
static std::unique_ptr<glass::Window> gNetworkTablesLogWindow;
@@ -49,48 +50,61 @@
static bool gAbout = false;
static bool gSetEnterKey = false;
static bool gKeyEdit = false;
+static int* gEnterKey;
+static void (*gPrevKeyCallback)(GLFWwindow*, int, int, int, int);
+
+static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
+ int action, int mods) {
+ if (action == GLFW_PRESS || action == GLFW_RELEASE) {
+ if (gKeyEdit) {
+ *gEnterKey = key;
+ gKeyEdit = false;
+ } else if (*gEnterKey == key) {
+ key = GLFW_KEY_ENTER;
+ }
+ }
+
+ if (gPrevKeyCallback) {
+ gPrevKeyCallback(window, key, scancode, action, mods);
+ }
+}
static void NtInitialize() {
- // update window title when connection status changes
auto inst = nt::GetDefaultInstance();
- auto poller = nt::CreateConnectionListenerPoller(inst);
- nt::AddPolledConnectionListener(poller, true);
+ auto poller = nt::CreateListenerPoller(inst);
+ nt::AddPolledListener(
+ poller, inst,
+ NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE);
gui::AddEarlyExecute([poller] {
auto win = gui::GetSystemWindow();
if (!win) {
return;
}
- bool timedOut;
- for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
- if (event.connected) {
- glfwSetWindowTitle(
- win, fmt::format("Glass - Connected ({})", event.conn.remote_ip)
- .c_str());
- } else {
- glfwSetWindowTitle(win, "Glass - DISCONNECTED");
+ for (auto&& event : nt::ReadListenerQueue(poller)) {
+ if (auto connInfo = event.GetConnectionInfo()) {
+ // update window title when connection status changes
+ if ((event.flags & NT_EVENT_CONNECTED) != 0) {
+ glfwSetWindowTitle(
+ win, fmt::format("Glass - Connected ({})", connInfo->remote_ip)
+ .c_str());
+ } else {
+ glfwSetWindowTitle(win, "Glass - DISCONNECTED");
+ }
+ } else if (auto msg = event.GetLogMessage()) {
+ const char* level = "";
+ if (msg->level >= NT_LOG_CRITICAL) {
+ level = "CRITICAL: ";
+ } else if (msg->level >= NT_LOG_ERROR) {
+ level = "ERROR: ";
+ } else if (msg->level >= NT_LOG_WARNING) {
+ level = "WARNING: ";
+ }
+ gNetworkTablesLog.Append(fmt::format(
+ "{}{} ({}:{})\n", level, msg->message, msg->filename, msg->line));
}
}
});
- // handle NetworkTables log messages
- auto logPoller = nt::CreateLoggerPoller(inst);
- nt::AddPolledLogger(logPoller, NT_LOG_INFO, 100);
- gui::AddEarlyExecute([logPoller] {
- bool timedOut;
- for (auto&& msg : nt::PollLogger(logPoller, 0, &timedOut)) {
- const char* level = "";
- if (msg.level >= NT_LOG_CRITICAL) {
- level = "CRITICAL: ";
- } else if (msg.level >= NT_LOG_ERROR) {
- level = "ERROR: ";
- } else if (msg.level >= NT_LOG_WARNING) {
- level = "WARNING: ";
- }
- gNetworkTablesLog.Append(fmt::format("{}{} ({}:{})\n", level, msg.message,
- msg.filename, msg.line));
- }
- });
-
gNetworkTablesLogWindow = std::make_unique<glass::Window>(
glass::GetStorageRoot().GetChild("NetworkTables Log"),
"NetworkTables Log", glass::Window::kHide);
@@ -114,9 +128,21 @@
gNetworkTablesWindow->DisableRenamePopup();
gui::AddLateExecute([] { gNetworkTablesWindow->Display(); });
+ // NetworkTables info window
+ gNetworkTablesInfoWindow = std::make_unique<glass::Window>(
+ glass::GetStorageRoot().GetChild("NetworkTables Info"),
+ "NetworkTables Info");
+ gNetworkTablesInfoWindow->SetView(glass::MakeFunctionView(
+ [&] { glass::DisplayNetworkTablesInfo(gNetworkTablesModel.get()); }));
+ gNetworkTablesInfoWindow->SetDefaultPos(250, 130);
+ gNetworkTablesInfoWindow->SetDefaultSize(750, 145);
+ gNetworkTablesInfoWindow->SetDefaultVisibility(glass::Window::kHide);
+ gNetworkTablesInfoWindow->DisableRenamePopup();
+ gui::AddLateExecute([] { gNetworkTablesInfoWindow->Display(); });
+
// NetworkTables settings window
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>(
- glass::GetStorageRoot().GetChild("NetworkTables Settings"));
+ "glass", glass::GetStorageRoot().GetChild("NetworkTables Settings"));
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
gNetworkTablesSettingsWindow = std::make_unique<glass::Window>(
@@ -161,6 +187,11 @@
gui::AddIcon(glass::GetResource_glass_256_png());
gui::AddIcon(glass::GetResource_glass_512_png());
+ gui::AddEarlyExecute(
+ [] { ImGui::DockSpaceOverViewport(ImGui::GetMainViewport()); });
+
+ gui::AddInit([] { ImGui::GetIO().ConfigDockingWithShift = true; });
+
gPlotProvider = std::make_unique<glass::PlotProvider>(
glass::GetStorageRoot().GetChild("Plots"));
gNtProvider = std::make_unique<glass::NetworkTablesProvider>(
@@ -195,6 +226,9 @@
if (gNetworkTablesWindow) {
gNetworkTablesWindow->DisplayMenuItem("NetworkTables View");
}
+ if (gNetworkTablesInfoWindow) {
+ gNetworkTablesInfoWindow->DisplayMenuItem("NetworkTables Info");
+ }
if (gNetworkTablesLogWindow) {
gNetworkTablesLogWindow->DisplayMenuItem("NetworkTables Log");
}
@@ -237,11 +271,6 @@
ImGui::EndPopup();
}
- int& enterKey = glass::GetStorageRoot().GetInt("enterKey", GLFW_KEY_ENTER);
-
- ImGuiIO& io = ImGui::GetIO();
- io.KeyMap[ImGuiKey_Enter] = enterKey;
-
if (gSetEnterKey) {
ImGui::OpenPopup("Set Enter Key");
gSetEnterKey = false;
@@ -251,24 +280,13 @@
ImGui::Text("This is useful to edit values without the DS disabling");
ImGui::Separator();
- if (gKeyEdit) {
- for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); ++i) {
- if (io.KeysDown[i]) {
- // remove all other uses
- enterKey = i;
- gKeyEdit = false;
- break;
- }
- }
- }
-
ImGui::Text("Key:");
ImGui::SameLine();
char editLabel[40];
char nameBuf[32];
- const char* name = glfwGetKeyName(enterKey, 0);
+ const char* name = glfwGetKeyName(*gEnterKey, 0);
if (!name) {
- std::snprintf(nameBuf, sizeof(nameBuf), "%d", enterKey);
+ std::snprintf(nameBuf, sizeof(nameBuf), "%d", *gEnterKey);
name = nameBuf;
}
std::snprintf(editLabel, sizeof(editLabel), "%s###edit",
@@ -278,7 +296,7 @@
}
ImGui::SameLine();
if (ImGui::SmallButton("Reset")) {
- enterKey = GLFW_KEY_ENTER;
+ *gEnterKey = GLFW_KEY_ENTER;
}
if (ImGui::Button("Close")) {
@@ -289,7 +307,12 @@
}
});
- gui::Initialize("Glass - DISCONNECTED", 1024, 768);
+ gui::Initialize("Glass - DISCONNECTED", 1024, 768,
+ ImGuiConfigFlags_DockingEnable);
+ gEnterKey = &glass::GetStorageRoot().GetInt("enterKey", GLFW_KEY_ENTER);
+ if (auto win = gui::GetSystemWindow()) {
+ gPrevKeyCallback = glfwSetKeyCallback(win, RemapEnterKeyCallback);
+ }
gui::Main();
gNetworkTablesSettingsWindow.reset();
diff --git a/glass/src/lib/native/cpp/Context.cpp b/glass/src/lib/native/cpp/Context.cpp
index 1de4af8..a55cf82 100644
--- a/glass/src/lib/native/cpp/Context.cpp
+++ b/glass/src/lib/native/cpp/Context.cpp
@@ -69,6 +69,9 @@
wpi::raw_string_ostream ini{iniStr};
for (auto&& jsection : jfile.items()) {
+ if (jsection.key() == "Docking") {
+ continue;
+ }
if (!jsection.value().is_object()) {
ImGui::LogText("%s section %s is not object", filename,
jsection.key().c_str());
@@ -95,6 +98,31 @@
ini << '\n';
}
}
+
+ // emit Docking section last
+ auto docking = jfile.find("Docking");
+ if (docking != jfile.end()) {
+ for (auto&& jsubsection : docking->items()) {
+ if (!jsubsection.value().is_array()) {
+ ImGui::LogText("%s section %s subsection %s is not array", filename,
+ "Docking", jsubsection.key().c_str());
+ return false;
+ }
+ ini << "[Docking][" << jsubsection.key() << "]\n";
+ for (auto&& jv : jsubsection.value()) {
+ try {
+ auto& value = jv.get_ref<const std::string&>();
+ ini << value << "\n";
+ } catch (wpi::json::exception&) {
+ ImGui::LogText("%s section %s subsection %s value is not string",
+ filename, "Docking", jsubsection.key().c_str());
+ return false;
+ }
+ }
+ ini << '\n';
+ }
+ }
+
ini.flush();
ImGui::LoadIniSettingsFromMemory(iniStr.data(), iniStr.size());
@@ -204,7 +232,11 @@
}
curSection = &jsection[subsection];
if (curSection->is_null()) {
- *curSection = wpi::json::object();
+ if (section == "Docking") {
+ *curSection = wpi::json::array();
+ } else {
+ *curSection = wpi::json::object();
+ }
}
} else {
// value
@@ -212,7 +244,11 @@
continue; // shouldn't happen, but just in case
}
auto [name, value] = wpi::split(line, '=');
- (*curSection)[name] = value;
+ if (curSection->is_object()) {
+ (*curSection)[name] = value;
+ } else if (curSection->is_array()) {
+ curSection->emplace_back(line);
+ }
}
}
@@ -510,15 +546,24 @@
bool glass::PopupEditName(const char* label, std::string* name) {
bool rv = false;
if (ImGui::BeginPopupContextItem(label)) {
- ImGui::Text("Edit name:");
- if (ImGui::InputText("##editname", name)) {
- rv = true;
- }
- if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
- ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
- ImGui::CloseCurrentPopup();
- }
+ rv = ItemEditName(name);
+
ImGui::EndPopup();
}
return rv;
}
+
+bool glass::ItemEditName(std::string* name) {
+ bool rv = false;
+
+ ImGui::Text("Edit name:");
+ if (ImGui::InputText("##editname", name)) {
+ rv = true;
+ }
+ if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
+ ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
+ ImGui::CloseCurrentPopup();
+ }
+
+ return rv;
+}
diff --git a/glass/src/lib/native/cpp/Storage.cpp b/glass/src/lib/native/cpp/Storage.cpp
index 28af20b..add6203 100644
--- a/glass/src/lib/native/cpp/Storage.cpp
+++ b/glass/src/lib/native/cpp/Storage.cpp
@@ -254,7 +254,7 @@
} \
\
std::vector<ArrCType>& Storage::Get##CapsName##Array( \
- std::string_view key, wpi::span<const ArrCType> defaultVal) { \
+ std::string_view key, std::span<const ArrCType> defaultVal) { \
auto& valuePtr = m_values[key]; \
bool setValue = false; \
if (!valuePtr) { \
diff --git a/glass/src/lib/native/cpp/View.cpp b/glass/src/lib/native/cpp/View.cpp
index e01c4df..3f28200 100644
--- a/glass/src/lib/native/cpp/View.cpp
+++ b/glass/src/lib/native/cpp/View.cpp
@@ -25,3 +25,9 @@
}
void View::Hidden() {}
+
+void View::Settings() {}
+
+bool View::HasSettings() {
+ return false;
+}
diff --git a/glass/src/lib/native/cpp/Window.cpp b/glass/src/lib/native/cpp/Window.cpp
index 6296fab..f43c0ee 100644
--- a/glass/src/lib/native/cpp/Window.cpp
+++ b/glass/src/lib/native/cpp/Window.cpp
@@ -4,11 +4,13 @@
#include "glass/Window.h"
+#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/StringExtras.h>
#include "glass/Context.h"
#include "glass/Storage.h"
+#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
@@ -59,9 +61,57 @@
m_id.c_str());
if (Begin(label, &m_visible, m_flags)) {
- if (m_renamePopupEnabled) {
- PopupEditName(nullptr, &m_name);
+ if (m_renamePopupEnabled || m_view->HasSettings()) {
+ bool isClicked = (ImGui::IsMouseReleased(ImGuiMouseButton_Right) &&
+ ImGui::IsItemHovered());
+ ImGuiWindow* window = ImGui::GetCurrentWindow();
+
+ bool settingsButtonClicked = false;
+ // Not docked, and window has just enough for the circles not to be
+ // touching
+ if (!ImGui::IsWindowDocked() &&
+ ImGui::GetWindowWidth() > (ImGui::GetFontSize() + 2) * 3 +
+ ImGui::GetStyle().FramePadding.x * 2) {
+ const ImGuiItemFlags itemFlagsRestore =
+ ImGui::GetCurrentContext()->CurrentItemFlags;
+
+ ImGui::GetCurrentContext()->CurrentItemFlags |=
+ ImGuiItemFlags_NoNavDefaultFocus;
+ window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
+
+ // Allow to draw outside of normal window
+ ImGui::PushClipRect(window->OuterRectClipped.Min,
+ window->OuterRectClipped.Max, false);
+
+ const ImRect titleBarRect = ImGui::GetCurrentWindow()->TitleBarRect();
+ const ImVec2 position = {titleBarRect.Max.x -
+ (ImGui::GetStyle().FramePadding.x * 3) -
+ (ImGui::GetFontSize() * 2),
+ titleBarRect.Min.y};
+ settingsButtonClicked =
+ HamburgerButton(ImGui::GetID("#SETTINGS"), position);
+
+ ImGui::PopClipRect();
+
+ ImGui::GetCurrentContext()->CurrentItemFlags = itemFlagsRestore;
+ }
+ if (settingsButtonClicked || isClicked) {
+ ImGui::OpenPopup(window->ID);
+ }
+
+ if (ImGui::BeginPopupEx(window->ID,
+ ImGuiWindowFlags_AlwaysAutoResize |
+ ImGuiWindowFlags_NoTitleBar |
+ ImGuiWindowFlags_NoSavedSettings)) {
+ if (m_renamePopupEnabled) {
+ ItemEditName(&m_name);
+ }
+ m_view->Settings();
+
+ ImGui::EndPopup();
+ }
}
+
m_view->Display();
} else {
m_view->Hidden();
diff --git a/glass/src/lib/native/cpp/hardware/Gyro.cpp b/glass/src/lib/native/cpp/hardware/Gyro.cpp
index 36a3525..607b251 100644
--- a/glass/src/lib/native/cpp/hardware/Gyro.cpp
+++ b/glass/src/lib/native/cpp/hardware/Gyro.cpp
@@ -10,7 +10,7 @@
#include <imgui.h>
#include <imgui_internal.h>
-#include <wpi/numbers>
+#include <numbers>
#include "glass/Context.h"
#include "glass/DataSource.h"
@@ -54,7 +54,7 @@
// Draw the spokes at every 5 degrees and a "major" spoke every 45 degrees.
for (int i = -175; i <= 180; i += 5) {
- double radians = i * 2 * wpi::numbers::pi / 360.0;
+ double radians = i * 2 * std::numbers::pi / 360.0;
ImVec2 direction(std::sin(radians), -std::cos(radians));
bool major = i % 45 == 0;
@@ -74,7 +74,7 @@
draw->AddCircleFilled(center, radius * 0.075, secondaryColor, 50);
- double radians = value * 2 * wpi::numbers::pi / 360.0;
+ double radians = value * 2 * std::numbers::pi / 360.0;
draw->AddLine(
center - ImVec2(1, 0),
center + ImVec2(std::sin(radians), -std::cos(radians)) * radius * 0.95f,
diff --git a/glass/src/lib/native/cpp/hardware/SpeedController.cpp b/glass/src/lib/native/cpp/hardware/MotorController.cpp
similarity index 89%
rename from glass/src/lib/native/cpp/hardware/SpeedController.cpp
rename to glass/src/lib/native/cpp/hardware/MotorController.cpp
index b278401..96181b2 100644
--- a/glass/src/lib/native/cpp/hardware/SpeedController.cpp
+++ b/glass/src/lib/native/cpp/hardware/MotorController.cpp
@@ -2,7 +2,7 @@
// 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.
-#include "glass/hardware/SpeedController.h"
+#include "glass/hardware/MotorController.h"
#include <imgui.h>
#include <imgui_internal.h>
@@ -12,13 +12,13 @@
using namespace glass;
-void glass::DisplaySpeedController(SpeedControllerModel* m) {
+void glass::DisplayMotorController(MotorControllerModel* m) {
// Get duty cycle data from the model and do not display anything if the data
// is null.
auto dc = m->GetPercentData();
if (!dc || !m->Exists()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
- ImGui::Text("Unknown SpeedController");
+ ImGui::Text("Unknown MotorController");
ImGui::PopStyleColor();
return;
}
diff --git a/glass/src/lib/native/cpp/other/Drive.cpp b/glass/src/lib/native/cpp/other/Drive.cpp
index a73c6de..d54b2da 100644
--- a/glass/src/lib/native/cpp/other/Drive.cpp
+++ b/glass/src/lib/native/cpp/other/Drive.cpp
@@ -11,7 +11,7 @@
#include <imgui.h>
#include <imgui_internal.h>
-#include <wpi/numbers>
+#include <numbers>
#include "glass/Context.h"
#include "glass/DataSource.h"
@@ -55,11 +55,11 @@
draw->AddTriangleFilled(
arrowPos,
arrowPos + ImRotate(ImVec2(0.0f, 7.5f),
- std::cos(angle + wpi::numbers::pi / 4),
- std::sin(angle + wpi::numbers::pi / 4)),
+ std::cos(angle + std::numbers::pi / 4),
+ std::sin(angle + std::numbers::pi / 4)),
arrowPos + ImRotate(ImVec2(0.0f, 7.5f),
- std::cos(angle - wpi::numbers::pi / 4),
- std::sin(angle - wpi::numbers::pi / 4)),
+ std::cos(angle - std::numbers::pi / 4),
+ std::sin(angle - std::numbers::pi / 4)),
color);
};
@@ -88,30 +88,30 @@
if (rotation != 0) {
float radius = 60.0f;
double a1 = 0.0;
- double a2 = wpi::numbers::pi / 2 * rotation;
+ double a2 = std::numbers::pi / 2 * rotation;
// PathArcTo requires a_min <= a_max, and rotation can be negative
if (a1 > a2) {
draw->PathArcTo(center, radius, a2, a1, 20);
draw->PathStroke(color, false);
- draw->PathArcTo(center, radius, a2 + wpi::numbers::pi,
- a1 + wpi::numbers::pi, 20);
+ draw->PathArcTo(center, radius, a2 + std::numbers::pi,
+ a1 + std::numbers::pi, 20);
draw->PathStroke(color, false);
} else {
draw->PathArcTo(center, radius, a1, a2, 20);
draw->PathStroke(color, false);
- draw->PathArcTo(center, radius, a1 + wpi::numbers::pi,
- a2 + wpi::numbers::pi, 20);
+ draw->PathArcTo(center, radius, a1 + std::numbers::pi,
+ a2 + std::numbers::pi, 20);
draw->PathStroke(color, false);
}
- double adder = rotation < 0 ? wpi::numbers::pi : 0;
+ double adder = rotation < 0 ? std::numbers::pi : 0;
auto arrowPos =
center + ImVec2(radius * -std::cos(a2), radius * -std::sin(a2));
drawArrow(arrowPos, a2 + adder);
- a2 += wpi::numbers::pi;
+ a2 += std::numbers::pi;
arrowPos = center + ImVec2(radius * -std::cos(a2), radius * -std::sin(a2));
drawArrow(arrowPos, a2 + adder);
}
diff --git a/glass/src/lib/native/cpp/other/Field2D.cpp b/glass/src/lib/native/cpp/other/Field2D.cpp
index c3ef860..66e90b1 100644
--- a/glass/src/lib/native/cpp/other/Field2D.cpp
+++ b/glass/src/lib/native/cpp/other/Field2D.cpp
@@ -93,7 +93,7 @@
SelectedTargetInfo* GetTarget() { return &m_target; }
FieldObjectModel* GetInsertModel() { return m_insertModel; }
- wpi::span<const frc::Pose2d> GetInsertPoses() const { return m_insertPoses; }
+ std::span<const frc::Pose2d> GetInsertPoses() const { return m_insertPoses; }
void Display(Field2DModel* model, const FieldFrameData& ffd);
@@ -113,7 +113,7 @@
struct DisplayOptions {
explicit DisplayOptions(const gui::Texture& texture) : texture{texture} {}
- enum Style { kBoxImage = 0, kLine, kLineClosed, kTrack };
+ enum Style { kBoxImage = 0, kLine, kLineClosed, kTrack, kHidden };
static constexpr Style kDefaultStyle = kBoxImage;
static constexpr float kDefaultWeight = 4.0f;
@@ -189,7 +189,7 @@
DisplayOptions GetDisplayOptions() const;
void DisplaySettings();
- void DrawLine(ImDrawList* drawList, wpi::span<const ImVec2> points) const;
+ void DrawLine(ImDrawList* drawList, std::span<const ImVec2> points) const;
void LoadImage();
const gui::Texture& GetTexture() const { return m_texture; }
@@ -547,7 +547,7 @@
DisplayOptions::kDefaultLength.to<float>())},
m_style{storage.GetString("style"),
DisplayOptions::kDefaultStyle,
- {"Box/Image", "Line", "Line (Closed)", "Track"}},
+ {"Box/Image", "Line", "Line (Closed)", "Track", "Hidden"}},
m_weight{storage.GetFloat("weight", DisplayOptions::kDefaultWeight)},
m_color{
storage.GetFloatArray("color", DisplayOptions::kDefaultColorFloat)},
@@ -617,7 +617,7 @@
}
void ObjectInfo::DrawLine(ImDrawList* drawList,
- wpi::span<const ImVec2> points) const {
+ std::span<const ImVec2> points) const {
if (points.empty()) {
return;
}
@@ -840,6 +840,8 @@
left->emplace_back(m_corners[4]);
right->emplace_back(m_corners[5]);
break;
+ case DisplayOptions::kHidden:
+ break;
}
if (m_displayOptions.arrows) {
@@ -1215,10 +1217,14 @@
}
void Field2DView::Display() {
- if (ImGui::BeginPopupContextItem()) {
- DisplayField2DSettings(m_model);
- ImGui::EndPopup();
- }
DisplayField2D(m_model, ImGui::GetWindowContentRegionMax() -
ImGui::GetWindowContentRegionMin());
}
+
+void Field2DView::Settings() {
+ DisplayField2DSettings(m_model);
+}
+
+bool Field2DView::HasSettings() {
+ return true;
+}
diff --git a/glass/src/lib/native/cpp/other/Log.cpp b/glass/src/lib/native/cpp/other/Log.cpp
index 9a1d2c5..accf024 100644
--- a/glass/src/lib/native/cpp/other/Log.cpp
+++ b/glass/src/lib/native/cpp/other/Log.cpp
@@ -62,18 +62,21 @@
}
void LogView::Display() {
- if (ImGui::BeginPopupContextItem()) {
- ImGui::Checkbox("Auto-scroll", &m_autoScroll);
- if (ImGui::Selectable("Clear")) {
- m_data->Clear();
- }
- const auto& buf = m_data->GetBuffer();
- if (ImGui::Selectable("Copy to Clipboard", false,
- buf.empty() ? ImGuiSelectableFlags_Disabled : 0)) {
- ImGui::SetClipboardText(buf.c_str());
- }
- ImGui::EndPopup();
- }
-
DisplayLog(m_data, m_autoScroll);
}
+
+void LogView::Settings() {
+ ImGui::Checkbox("Auto-scroll", &m_autoScroll);
+ if (ImGui::Selectable("Clear")) {
+ m_data->Clear();
+ }
+ const auto& buf = m_data->GetBuffer();
+ if (ImGui::Selectable("Copy to Clipboard", false,
+ buf.empty() ? ImGuiSelectableFlags_Disabled : 0)) {
+ ImGui::SetClipboardText(buf.c_str());
+ }
+}
+
+bool LogView::HasSettings() {
+ return true;
+}
diff --git a/glass/src/lib/native/cpp/other/Mechanism2D.cpp b/glass/src/lib/native/cpp/other/Mechanism2D.cpp
index aa801a7..722585b 100644
--- a/glass/src/lib/native/cpp/other/Mechanism2D.cpp
+++ b/glass/src/lib/native/cpp/other/Mechanism2D.cpp
@@ -249,10 +249,14 @@
}
void Mechanism2DView::Display() {
- if (ImGui::BeginPopupContextItem()) {
- DisplayMechanism2DSettings(m_model);
- ImGui::EndPopup();
- }
DisplayMechanism2D(m_model, ImGui::GetWindowContentRegionMax() -
ImGui::GetWindowContentRegionMin());
}
+
+void Mechanism2DView::Settings() {
+ DisplayMechanism2DSettings(m_model);
+}
+
+bool Mechanism2DView::HasSettings() {
+ return true;
+}
diff --git a/glass/src/lib/native/cpp/other/Plot.cpp b/glass/src/lib/native/cpp/other/Plot.cpp
index bff55b7..13d7c96 100644
--- a/glass/src/lib/native/cpp/other/Plot.cpp
+++ b/glass/src/lib/native/cpp/other/Plot.cpp
@@ -53,11 +53,12 @@
};
class PlotSeries {
- explicit PlotSeries(Storage& storage, int yAxis = 0);
+ explicit PlotSeries(Storage& storage);
public:
PlotSeries(Storage& storage, std::string_view id);
- PlotSeries(Storage& storage, DataSource* source, int yAxis = 0);
+ PlotSeries(Storage& storage, DataSource* source);
+ PlotSeries(Storage& storage, DataSource* source, int yAxis);
const std::string& GetId() const { return m_id; }
@@ -83,7 +84,7 @@
return m_digital.GetValue() == kDigital ||
(m_digital.GetValue() == kAuto && m_source && m_source->IsDigital());
}
- void AppendValue(double value, uint64_t time);
+ void AppendValue(double value, int64_t time);
// source linkage
DataSource* m_source = nullptr;
@@ -149,7 +150,6 @@
bool& m_legendHorizontal;
int& m_legendLocation;
bool& m_crosshairs;
- bool& m_antialiased;
bool& m_mousePosition;
bool& m_yAxis2;
bool& m_yAxis3;
@@ -182,6 +182,8 @@
PlotView(PlotProvider* provider, Storage& storage);
void Display() override;
+ void Settings() override;
+ bool HasSettings() override;
void MovePlot(PlotView* fromView, size_t fromIndex, size_t toIndex);
@@ -196,7 +198,7 @@
} // namespace
-PlotSeries::PlotSeries(Storage& storage, int yAxis)
+PlotSeries::PlotSeries(Storage& storage)
: m_id{storage.GetString("id")},
m_name{storage.GetString("name")},
m_yAxis{storage.GetInt("yAxis", 0)},
@@ -209,12 +211,10 @@
m_digital{
storage.GetString("digital"), kAuto, {"Auto", "Digital", "Analog"}},
m_digitalBitHeight{storage.GetInt("digitalBitHeight", 8)},
- m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {
- m_yAxis = yAxis;
-}
+ m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {}
PlotSeries::PlotSeries(Storage& storage, std::string_view id)
- : PlotSeries{storage, 0} {
+ : PlotSeries{storage} {
m_id = id;
if (DataSource* source = DataSource::Find(id)) {
SetSource(source);
@@ -223,12 +223,17 @@
CheckSource();
}
-PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
- : PlotSeries{storage, yAxis} {
+PlotSeries::PlotSeries(Storage& storage, DataSource* source)
+ : PlotSeries{storage} {
SetSource(source);
m_id = source->GetId();
}
+PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
+ : PlotSeries{storage, source} {
+ m_yAxis = yAxis;
+}
+
void PlotSeries::CheckSource() {
if (!m_newValueConn.connected() && !m_sourceCreatedConn.connected()) {
m_source = nullptr;
@@ -249,10 +254,10 @@
AppendValue(source->GetValue(), 0);
m_newValueConn = source->valueChanged.connect_connection(
- [this](double value, uint64_t time) { AppendValue(value, time); });
+ [this](double value, int64_t time) { AppendValue(value, time); });
}
-void PlotSeries::AppendValue(double value, uint64_t timeUs) {
+void PlotSeries::AppendValue(double value, int64_t timeUs) {
double time = (timeUs != 0 ? timeUs : wpi::Now()) * 1.0e-6;
if (IsDigital()) {
if (m_size < kMaxSize) {
@@ -327,7 +332,7 @@
int offset;
};
GetterData getterData = {now, GetZeroTime() * 1.0e-6, m_data, size, offset};
- auto getter = [](void* data, int idx) {
+ auto getter = [](int idx, void* data) {
auto d = static_cast<GetterData*>(data);
if (idx == d->size) {
return ImPlotPoint{
@@ -487,7 +492,6 @@
m_legendLocation{
storage.GetInt("legendLocation", ImPlotLocation_NorthWest)},
m_crosshairs{storage.GetBool("crosshairs", false)},
- m_antialiased{storage.GetBool("antialiased", false)},
m_mousePosition{storage.GetBool("mousePosition", true)},
m_yAxis2{storage.GetBool("yaxis2", false)},
m_yAxis3{storage.GetBool("yaxis3", false)},
@@ -573,7 +577,6 @@
static_cast<int>(i));
ImPlotFlags plotFlags = (m_legend ? 0 : ImPlotFlags_NoLegend) |
(m_crosshairs ? ImPlotFlags_Crosshairs : 0) |
- (m_antialiased ? ImPlotFlags_AntiAliased : 0) |
(m_mousePosition ? 0 : ImPlotFlags_NoMouseText);
if (ImPlot::BeginPlot(label, ImVec2(-1, m_height), plotFlags)) {
@@ -608,7 +611,6 @@
(m_axis[i].lockMin ? ImPlotAxisFlags_LockMin : 0) |
(m_axis[i].lockMax ? ImPlotAxisFlags_LockMax : 0) |
(m_axis[i].autoFit ? ImPlotAxisFlags_AutoFit : 0) |
- (m_axis[i].logScale ? ImPlotAxisFlags_AutoFit : 0) |
(m_axis[i].invert ? ImPlotAxisFlags_Invert : 0) |
(m_axis[i].opposite ? ImPlotAxisFlags_Opposite : 0) |
(m_axis[i].gridLines ? 0 : ImPlotAxisFlags_NoGridLines) |
@@ -620,6 +622,9 @@
ImPlot::SetupAxisLimits(
ImAxis_Y1 + i, m_axis[i].min, m_axis[i].max,
m_axis[i].apply ? ImGuiCond_Always : ImGuiCond_Once);
+ ImPlot::SetupAxisScale(ImAxis_Y1 + i, m_axis[i].logScale
+ ? ImPlotScale_Log10
+ : ImPlotScale_Linear);
m_axis[i].apply = false;
}
@@ -656,7 +661,6 @@
// copy plot settings back to storage
m_legend = (plot->Flags & ImPlotFlags_NoLegend) == 0;
m_crosshairs = (plot->Flags & ImPlotFlags_Crosshairs) != 0;
- m_antialiased = (plot->Flags & ImPlotFlags_AntiAliased) != 0;
m_legendOutside =
(plot->Items.Legend.Flags & ImPlotLegendFlags_Outside) != 0;
m_legendHorizontal =
@@ -671,12 +675,12 @@
m_axis[i].lockMin = (flags & ImPlotAxisFlags_LockMin) != 0;
m_axis[i].lockMax = (flags & ImPlotAxisFlags_LockMax) != 0;
m_axis[i].autoFit = (flags & ImPlotAxisFlags_AutoFit) != 0;
- m_axis[i].logScale = (flags & ImPlotAxisFlags_LogScale) != 0;
m_axis[i].invert = (flags & ImPlotAxisFlags_Invert) != 0;
m_axis[i].opposite = (flags & ImPlotAxisFlags_Opposite) != 0;
m_axis[i].gridLines = (flags & ImPlotAxisFlags_NoGridLines) == 0;
m_axis[i].tickMarks = (flags & ImPlotAxisFlags_NoTickMarks) == 0;
m_axis[i].tickLabels = (flags & ImPlotAxisFlags_NoTickLabels) == 0;
+ m_axis[i].logScale = plot->Axes[ImAxis_Y1 + i].Scale == ImPlotScale_Log10;
}
}
}
@@ -765,71 +769,6 @@
}
void PlotView::Display() {
- if (ImGui::BeginPopupContextItem()) {
- if (ImGui::Button("Add plot")) {
- m_plotsStorage.emplace_back(std::make_unique<Storage>());
- m_plots.emplace_back(std::make_unique<Plot>(*m_plotsStorage.back()));
- }
-
- for (size_t i = 0; i < m_plots.size(); ++i) {
- auto& plot = m_plots[i];
- ImGui::PushID(i);
-
- char name[64];
- if (!plot->GetName().empty()) {
- std::snprintf(name, sizeof(name), "%s", plot->GetName().c_str());
- } else {
- std::snprintf(name, sizeof(name), "Plot %d", static_cast<int>(i));
- }
-
- char label[90];
- std::snprintf(label, sizeof(label), "%s###header%d", name,
- static_cast<int>(i));
-
- bool open = ImGui::CollapsingHeader(label);
-
- // DND source and target for Plot
- if (ImGui::BeginDragDropSource()) {
- PlotSeriesRef ref = {this, i, 0};
- ImGui::SetDragDropPayload("Plot", &ref, sizeof(ref));
- ImGui::TextUnformatted(name);
- ImGui::EndDragDropSource();
- }
- plot->DragDropTarget(*this, i, false);
-
- if (open) {
- if (ImGui::Button("Move Up")) {
- if (i > 0) {
- std::swap(m_plotsStorage[i - 1], m_plotsStorage[i]);
- std::swap(m_plots[i - 1], plot);
- }
- }
-
- ImGui::SameLine();
- if (ImGui::Button("Move Down")) {
- if (i < (m_plots.size() - 1)) {
- std::swap(m_plotsStorage[i], m_plotsStorage[i + 1]);
- std::swap(plot, m_plots[i + 1]);
- }
- }
-
- ImGui::SameLine();
- if (ImGui::Button("Delete")) {
- m_plotsStorage.erase(m_plotsStorage.begin() + i);
- m_plots.erase(m_plots.begin() + i);
- ImGui::PopID();
- continue;
- }
-
- plot->EmitSettings(i);
- }
-
- ImGui::PopID();
- }
-
- ImGui::EndPopup();
- }
-
if (m_plots.empty()) {
if (ImGui::Button("Add plot")) {
m_plotsStorage.emplace_back(std::make_unique<Storage>());
@@ -966,6 +905,73 @@
});
}
+void PlotView::Settings() {
+ if (ImGui::Button("Add plot")) {
+ m_plotsStorage.emplace_back(std::make_unique<Storage>());
+ m_plots.emplace_back(std::make_unique<Plot>(*m_plotsStorage.back()));
+ }
+
+ for (size_t i = 0; i < m_plots.size(); ++i) {
+ auto& plot = m_plots[i];
+ ImGui::PushID(i);
+
+ char name[64];
+ if (!plot->GetName().empty()) {
+ std::snprintf(name, sizeof(name), "%s", plot->GetName().c_str());
+ } else {
+ std::snprintf(name, sizeof(name), "Plot %d", static_cast<int>(i));
+ }
+
+ char label[90];
+ std::snprintf(label, sizeof(label), "%s###header%d", name,
+ static_cast<int>(i));
+
+ bool open = ImGui::CollapsingHeader(label);
+
+ // DND source and target for Plot
+ if (ImGui::BeginDragDropSource()) {
+ PlotSeriesRef ref = {this, i, 0};
+ ImGui::SetDragDropPayload("Plot", &ref, sizeof(ref));
+ ImGui::TextUnformatted(name);
+ ImGui::EndDragDropSource();
+ }
+ plot->DragDropTarget(*this, i, false);
+
+ if (open) {
+ if (ImGui::Button("Move Up")) {
+ if (i > 0) {
+ std::swap(m_plotsStorage[i - 1], m_plotsStorage[i]);
+ std::swap(m_plots[i - 1], plot);
+ }
+ }
+
+ ImGui::SameLine();
+ if (ImGui::Button("Move Down")) {
+ if (i < (m_plots.size() - 1)) {
+ std::swap(m_plotsStorage[i], m_plotsStorage[i + 1]);
+ std::swap(plot, m_plots[i + 1]);
+ }
+ }
+
+ ImGui::SameLine();
+ if (ImGui::Button("Delete")) {
+ m_plotsStorage.erase(m_plotsStorage.begin() + i);
+ m_plots.erase(m_plots.begin() + i);
+ ImGui::PopID();
+ continue;
+ }
+
+ plot->EmitSettings(i);
+ }
+
+ ImGui::PopID();
+ }
+}
+
+bool PlotView::HasSettings() {
+ return true;
+}
+
PlotProvider::~PlotProvider() = default;
void PlotProvider::DisplayMenu() {
@@ -989,7 +995,7 @@
for (size_t i = 0; i <= numWindows; ++i) {
std::snprintf(id, sizeof(id), "Plot <%d>", static_cast<int>(i));
bool match = false;
- for (size_t j = i; j < numWindows; ++j) {
+ for (size_t j = 0; j < numWindows; ++j) {
if (m_windows[j]->GetId() == id) {
match = true;
break;
diff --git a/glass/src/lib/native/cpp/support/EnumSetting.cpp b/glass/src/lib/native/cpp/support/EnumSetting.cpp
index 848b588..b863b70 100644
--- a/glass/src/lib/native/cpp/support/EnumSetting.cpp
+++ b/glass/src/lib/native/cpp/support/EnumSetting.cpp
@@ -10,16 +10,13 @@
EnumSetting::EnumSetting(std::string& str, int defaultValue,
std::initializer_list<const char*> choices)
- : m_str{str}, m_choices{choices}, m_value{defaultValue} {
- // override default value if str is one of the choices
- int i = 0;
- for (auto choice : choices) {
- if (str == choice) {
- m_value = i;
- break;
- }
- ++i;
+ : m_str{str}, m_choices{choices}, m_defaultValue{defaultValue} {}
+
+int EnumSetting::GetValue() const {
+ if (m_value == -1) {
+ UpdateValue();
}
+ return m_value;
}
void EnumSetting::SetValue(int value) {
@@ -29,6 +26,9 @@
bool EnumSetting::Combo(const char* label, int numOptions,
int popup_max_height_in_items) {
+ if (m_value == -1) {
+ UpdateValue();
+ }
if (ImGui::Combo(
label, &m_value, m_choices.data(),
numOptions < 0 ? m_choices.size() : static_cast<size_t>(numOptions),
@@ -38,3 +38,17 @@
}
return false;
}
+
+void EnumSetting::UpdateValue() const {
+ // override default value if str is one of the choices
+ int i = 0;
+ for (auto choice : m_choices) {
+ if (m_str == choice) {
+ m_value = i;
+ return;
+ }
+ ++i;
+ }
+ // no match, default it
+ m_value = m_defaultValue;
+}
diff --git a/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp b/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp
index 56f4e77..191634e 100644
--- a/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp
+++ b/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp
@@ -4,6 +4,8 @@
#include "glass/support/ExtraGuiWidgets.h"
+#include <imgui.h>
+
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>
@@ -174,4 +176,46 @@
return rv;
}
+bool HamburgerButton(const ImGuiID id, const ImVec2 position) {
+ const ImGuiStyle& style = ImGui::GetStyle();
+
+ ImGuiWindow* window = ImGui::GetCurrentWindow();
+
+ // Frame padding on both sides, then one character in the middle
+ const ImRect bb{
+ position, position + ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize()) +
+ style.FramePadding * 2.0f};
+
+ ImGui::ItemAdd(bb, id);
+
+ bool hovered, held;
+ bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held);
+
+ const ImU32 bgCol =
+ ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
+ const ImVec2 center = bb.GetCenter();
+ if (hovered) {
+ window->DrawList->AddCircleFilled(
+ center, ImMax(2.0f, ImGui::GetFontSize() * 0.5f + 1.0f), bgCol, 12);
+ }
+
+ const ImU32 fgCol = ImGui::GetColorU32(ImGuiCol_Text);
+
+ const float halfLineWidth = ImGui::GetFontSize() * 0.5 * 0.7071;
+ const float halfTotalHeight = halfLineWidth * 0.875;
+ ImVec2 lineStart = {center.x - halfLineWidth, center.y - halfTotalHeight};
+ ImVec2 lineEnd = {center.x + halfLineWidth, center.y - halfTotalHeight};
+
+ ImVec2 increment = {0.0, halfTotalHeight};
+
+ for (int i = 0; i < 3; i++) {
+ window->DrawList->AddLine(lineStart, lineEnd, fgCol);
+
+ lineStart += increment;
+ lineEnd += increment;
+ }
+
+ return pressed;
+}
+
} // namespace glass
diff --git a/glass/src/lib/native/include/glass/Context.h b/glass/src/lib/native/include/glass/Context.h
index 14ade2c..e8dada3 100644
--- a/glass/src/lib/native/include/glass/Context.h
+++ b/glass/src/lib/native/include/glass/Context.h
@@ -201,4 +201,6 @@
bool PopupEditName(const char* label, std::string* name);
+bool ItemEditName(std::string* name);
+
} // namespace glass
diff --git a/glass/src/lib/native/include/glass/DataSource.h b/glass/src/lib/native/include/glass/DataSource.h
index 5eebb3c..7e0c673 100644
--- a/glass/src/lib/native/include/glass/DataSource.h
+++ b/glass/src/lib/native/include/glass/DataSource.h
@@ -6,7 +6,6 @@
#include <stdint.h>
-#include <atomic>
#include <string>
#include <string_view>
@@ -38,11 +37,22 @@
void SetDigital(bool digital) { m_digital = digital; }
bool IsDigital() const { return m_digital; }
- void SetValue(double value, uint64_t time = 0) {
+ void SetValue(double value, int64_t time = 0) {
+ std::scoped_lock lock{m_valueMutex};
m_value = value;
+ m_valueTime = time;
valueChanged(value, time);
}
- double GetValue() const { return m_value; }
+
+ double GetValue() const {
+ std::scoped_lock lock{m_valueMutex};
+ return m_value;
+ }
+
+ int64_t GetValueTime() const {
+ std::scoped_lock lock{m_valueMutex};
+ return m_valueTime;
+ }
// drag source helpers
void LabelText(const char* label, const char* fmt, ...) const IM_FMTARGS(3);
@@ -59,7 +69,7 @@
ImGuiInputTextFlags flags = 0) const;
void EmitDrag(ImGuiDragDropFlags flags = 0) const;
- wpi::sig::SignalBase<wpi::spinlock, double, uint64_t> valueChanged;
+ wpi::sig::SignalBase<wpi::spinlock, double, int64_t> valueChanged;
static DataSource* Find(std::string_view id);
@@ -69,7 +79,9 @@
std::string m_id;
std::string& m_name;
bool m_digital = false;
- std::atomic<double> m_value = 0;
+ mutable wpi::spinlock m_valueMutex;
+ double m_value = 0;
+ int64_t m_valueTime = 0;
};
} // namespace glass
diff --git a/glass/src/lib/native/include/glass/Storage.h b/glass/src/lib/native/include/glass/Storage.h
index 004b8b4..7ebfa6d 100644
--- a/glass/src/lib/native/include/glass/Storage.h
+++ b/glass/src/lib/native/include/glass/Storage.h
@@ -8,6 +8,7 @@
#include <functional>
#include <memory>
+#include <span>
#include <string>
#include <string_view>
#include <utility>
@@ -15,7 +16,6 @@
#include <wpi/StringMap.h>
#include <wpi/iterator_range.h>
-#include <wpi/span.h>
namespace wpi {
class json;
@@ -137,17 +137,17 @@
std::string_view defaultVal = {});
std::vector<int>& GetIntArray(std::string_view key,
- wpi::span<const int> defaultVal = {});
+ std::span<const int> defaultVal = {});
std::vector<int64_t>& GetInt64Array(std::string_view key,
- wpi::span<const int64_t> defaultVal = {});
+ std::span<const int64_t> defaultVal = {});
std::vector<int>& GetBoolArray(std::string_view key,
- wpi::span<const int> defaultVal = {});
+ std::span<const int> defaultVal = {});
std::vector<float>& GetFloatArray(std::string_view key,
- wpi::span<const float> defaultVal = {});
+ std::span<const float> defaultVal = {});
std::vector<double>& GetDoubleArray(std::string_view key,
- wpi::span<const double> defaultVal = {});
+ std::span<const double> defaultVal = {});
std::vector<std::string>& GetStringArray(
- std::string_view key, wpi::span<const std::string> defaultVal = {});
+ std::string_view key, std::span<const std::string> defaultVal = {});
std::vector<std::unique_ptr<Storage>>& GetChildArray(std::string_view key);
Value* FindValue(std::string_view key);
diff --git a/glass/src/lib/native/include/glass/View.h b/glass/src/lib/native/include/glass/View.h
index 886c29e..f52ebc6 100644
--- a/glass/src/lib/native/include/glass/View.h
+++ b/glass/src/lib/native/include/glass/View.h
@@ -35,6 +35,17 @@
* ImGui::Begin() returns false).
*/
virtual void Hidden();
+
+ /**
+ * Called from within ImGui::BeginContextPopupItem() and ImGui::EndPopup().
+ * Used to display the settings for the view
+ */
+ virtual void Settings();
+
+ /**
+ * If the view has settings and if the result of Settings should be displayed.
+ */
+ virtual bool HasSettings();
};
/**
diff --git a/glass/src/lib/native/include/glass/hardware/LEDDisplay.h b/glass/src/lib/native/include/glass/hardware/LEDDisplay.h
index ddd3c27..3aee6ae 100644
--- a/glass/src/lib/native/include/glass/hardware/LEDDisplay.h
+++ b/glass/src/lib/native/include/glass/hardware/LEDDisplay.h
@@ -4,8 +4,9 @@
#pragma once
+#include <span>
+
#include <wpi/function_ref.h>
-#include <wpi/span.h>
#include "glass/Model.h"
@@ -27,7 +28,7 @@
virtual bool IsRunning() = 0;
- virtual wpi::span<const Data> GetData(wpi::SmallVectorImpl<Data>& buf) = 0;
+ virtual std::span<const Data> GetData(wpi::SmallVectorImpl<Data>& buf) = 0;
};
class LEDDisplaysModel : public glass::Model {
diff --git a/glass/src/lib/native/include/glass/hardware/SpeedController.h b/glass/src/lib/native/include/glass/hardware/MotorController.h
similarity index 83%
rename from glass/src/lib/native/include/glass/hardware/SpeedController.h
rename to glass/src/lib/native/include/glass/hardware/MotorController.h
index 033f27d..5fc831f 100644
--- a/glass/src/lib/native/include/glass/hardware/SpeedController.h
+++ b/glass/src/lib/native/include/glass/hardware/MotorController.h
@@ -8,12 +8,12 @@
namespace glass {
class DataSource;
-class SpeedControllerModel : public Model {
+class MotorControllerModel : public Model {
public:
virtual const char* GetName() const = 0;
virtual const char* GetSimDevice() const = 0;
virtual DataSource* GetPercentData() = 0;
virtual void SetPercent(double value) = 0;
};
-void DisplaySpeedController(SpeedControllerModel* m);
+void DisplayMotorController(MotorControllerModel* m);
} // namespace glass
diff --git a/glass/src/lib/native/include/glass/other/FMS.h b/glass/src/lib/native/include/glass/other/FMS.h
index a920f96..5970e93 100644
--- a/glass/src/lib/native/include/glass/other/FMS.h
+++ b/glass/src/lib/native/include/glass/other/FMS.h
@@ -38,7 +38,7 @@
virtual void SetEnabled(bool val) = 0;
virtual void SetTest(bool val) = 0;
virtual void SetAutonomous(bool val) = 0;
- virtual void SetGameSpecificMessage(const char* val) = 0;
+ virtual void SetGameSpecificMessage(std::string_view val) = 0;
};
/**
diff --git a/glass/src/lib/native/include/glass/other/Field2D.h b/glass/src/lib/native/include/glass/other/Field2D.h
index 9399876..9c9f72a 100644
--- a/glass/src/lib/native/include/glass/other/Field2D.h
+++ b/glass/src/lib/native/include/glass/other/Field2D.h
@@ -4,6 +4,7 @@
#pragma once
+#include <span>
#include <string_view>
#include <frc/geometry/Pose2d.h>
@@ -11,7 +12,6 @@
#include <frc/geometry/Translation2d.h>
#include <imgui.h>
#include <wpi/function_ref.h>
-#include <wpi/span.h>
#include "glass/Model.h"
#include "glass/View.h"
@@ -22,8 +22,8 @@
public:
virtual const char* GetName() const = 0;
- virtual wpi::span<const frc::Pose2d> GetPoses() = 0;
- virtual void SetPoses(wpi::span<const frc::Pose2d> poses) = 0;
+ virtual std::span<const frc::Pose2d> GetPoses() = 0;
+ virtual void SetPoses(std::span<const frc::Pose2d> poses) = 0;
virtual void SetPose(size_t i, frc::Pose2d pose) = 0;
virtual void SetPosition(size_t i, frc::Translation2d pos) = 0;
virtual void SetRotation(size_t i, frc::Rotation2d rot) = 0;
@@ -46,6 +46,8 @@
explicit Field2DView(Field2DModel* model) : m_model{model} {}
void Display() override;
+ void Settings() override;
+ bool HasSettings() override;
private:
Field2DModel* m_model;
diff --git a/glass/src/lib/native/include/glass/other/Log.h b/glass/src/lib/native/include/glass/other/Log.h
index 3d9c59b..f054e66 100644
--- a/glass/src/lib/native/include/glass/other/Log.h
+++ b/glass/src/lib/native/include/glass/other/Log.h
@@ -35,6 +35,8 @@
explicit LogView(LogData* data) : m_data{data} {}
void Display() override;
+ void Settings() override;
+ bool HasSettings() override;
private:
LogData* m_data;
diff --git a/glass/src/lib/native/include/glass/other/Mechanism2D.h b/glass/src/lib/native/include/glass/other/Mechanism2D.h
index 7617e6f..ab5ccdc 100644
--- a/glass/src/lib/native/include/glass/other/Mechanism2D.h
+++ b/glass/src/lib/native/include/glass/other/Mechanism2D.h
@@ -55,6 +55,8 @@
explicit Mechanism2DView(Mechanism2DModel* model) : m_model{model} {}
void Display() override;
+ void Settings() override;
+ bool HasSettings() override;
private:
Mechanism2DModel* m_model;
diff --git a/glass/src/lib/native/include/glass/other/StringChooser.h b/glass/src/lib/native/include/glass/other/StringChooser.h
index 77f9ac2..066c444 100644
--- a/glass/src/lib/native/include/glass/other/StringChooser.h
+++ b/glass/src/lib/native/include/glass/other/StringChooser.h
@@ -8,8 +8,6 @@
#include <string_view>
#include <vector>
-#include <wpi/span.h>
-
#include "glass/Model.h"
namespace glass {
@@ -21,10 +19,7 @@
virtual const std::string& GetActive() = 0;
virtual const std::vector<std::string>& GetOptions() = 0;
- virtual void SetDefault(std::string_view val) = 0;
virtual void SetSelected(std::string_view val) = 0;
- virtual void SetActive(std::string_view val) = 0;
- virtual void SetOptions(wpi::span<const std::string> val) = 0;
};
void DisplayStringChooser(StringChooserModel* model);
diff --git a/glass/src/lib/native/include/glass/support/EnumSetting.h b/glass/src/lib/native/include/glass/support/EnumSetting.h
index c4f0c26..eaf308f 100644
--- a/glass/src/lib/native/include/glass/support/EnumSetting.h
+++ b/glass/src/lib/native/include/glass/support/EnumSetting.h
@@ -15,7 +15,7 @@
EnumSetting(std::string& str, int defaultValue,
std::initializer_list<const char*> choices);
- int GetValue() const { return m_value; }
+ int GetValue() const;
void SetValue(int value);
// updates internal value, returns true on change
@@ -23,9 +23,12 @@
int popup_max_height_in_items = -1);
private:
+ void UpdateValue() const;
+
std::string& m_str;
wpi::SmallVector<const char*, 8> m_choices;
- int m_value;
+ int m_defaultValue;
+ mutable int m_value = -1;
};
} // namespace glass
diff --git a/glass/src/lib/native/include/glass/support/ExtraGuiWidgets.h b/glass/src/lib/native/include/glass/support/ExtraGuiWidgets.h
index 3a35335..6788434 100644
--- a/glass/src/lib/native/include/glass/support/ExtraGuiWidgets.h
+++ b/glass/src/lib/native/include/glass/support/ExtraGuiWidgets.h
@@ -93,4 +93,9 @@
*/
bool HeaderDeleteButton(const char* label);
+/**
+ * Settings button similar to ImGui::CloseButton.
+ */
+bool HamburgerButton(const ImGuiID id, const ImVec2 position);
+
} // namespace glass
diff --git a/glass/src/libnt/native/cpp/NTCommandScheduler.cpp b/glass/src/libnt/native/cpp/NTCommandScheduler.cpp
index ccc6412..26a5740 100644
--- a/glass/src/libnt/native/cpp/NTCommandScheduler.cpp
+++ b/glass/src/libnt/native/cpp/NTCommandScheduler.cpp
@@ -10,49 +10,38 @@
using namespace glass;
NTCommandSchedulerModel::NTCommandSchedulerModel(std::string_view path)
- : NTCommandSchedulerModel(nt::GetDefaultInstance(), path) {}
+ : NTCommandSchedulerModel(nt::NetworkTableInstance::GetDefault(), path) {}
-NTCommandSchedulerModel::NTCommandSchedulerModel(NT_Inst instance,
+NTCommandSchedulerModel::NTCommandSchedulerModel(nt::NetworkTableInstance inst,
std::string_view path)
- : m_nt(instance),
- m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
- m_commands(m_nt.GetEntry(fmt::format("{}/Names", path))),
- m_ids(m_nt.GetEntry(fmt::format("{}/Ids", path))),
- m_cancel(m_nt.GetEntry(fmt::format("{}/Cancel", path))),
- m_nameValue(wpi::rsplit(path, '/').second) {
- m_nt.AddListener(m_name);
- m_nt.AddListener(m_commands);
- m_nt.AddListener(m_ids);
- m_nt.AddListener(m_cancel);
-}
+ : m_inst{inst},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
+ m_commands{inst.GetStringArrayTopic(fmt::format("{}/Names", path))
+ .Subscribe({})},
+ m_ids{
+ inst.GetIntegerArrayTopic(fmt::format("{}/Ids", path)).Subscribe({})},
+ m_cancel{
+ inst.GetIntegerArrayTopic(fmt::format("{}/Cancel", path)).Publish()},
+ m_nameValue{wpi::rsplit(path, '/').second} {}
void NTCommandSchedulerModel::CancelCommand(size_t index) {
if (index < m_idsValue.size()) {
- nt::SetEntryValue(
- m_cancel, nt::NetworkTableValue::MakeDoubleArray({m_idsValue[index]}));
+ m_cancel.Set({{m_idsValue[index]}});
}
}
void NTCommandSchedulerModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- } else if (event.entry == m_commands) {
- if (event.value && event.value->IsStringArray()) {
- auto arr = event.value->GetStringArray();
- m_commandsValue.assign(arr.begin(), arr.end());
- }
- } else if (event.entry == m_ids) {
- if (event.value && event.value->IsDoubleArray()) {
- auto arr = event.value->GetDoubleArray();
- m_idsValue.assign(arr.begin(), arr.end());
- }
- }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
+ }
+ for (auto&& v : m_commands.ReadQueue()) {
+ m_commandsValue = std::move(v.value);
+ }
+ for (auto&& v : m_ids.ReadQueue()) {
+ m_idsValue = std::move(v.value);
}
}
bool NTCommandSchedulerModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_commands) != NT_UNASSIGNED;
+ return m_commands.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTCommandSelector.cpp b/glass/src/libnt/native/cpp/NTCommandSelector.cpp
index efcbac2..64c616e 100644
--- a/glass/src/libnt/native/cpp/NTCommandSelector.cpp
+++ b/glass/src/libnt/native/cpp/NTCommandSelector.cpp
@@ -10,38 +10,32 @@
using namespace glass;
NTCommandSelectorModel::NTCommandSelectorModel(std::string_view path)
- : NTCommandSelectorModel(nt::GetDefaultInstance(), path) {}
+ : NTCommandSelectorModel(nt::NetworkTableInstance::GetDefault(), path) {}
-NTCommandSelectorModel::NTCommandSelectorModel(NT_Inst instance,
+NTCommandSelectorModel::NTCommandSelectorModel(nt::NetworkTableInstance inst,
std::string_view path)
- : m_nt(instance),
- m_running(m_nt.GetEntry(fmt::format("{}/running", path))),
- m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
- m_runningData(fmt::format("NTCmd:{}", path)),
- m_nameValue(wpi::rsplit(path, '/').second) {
+ : m_inst{inst},
+ m_running{inst.GetBooleanTopic(fmt::format("{}/running", path))
+ .GetEntry(false)},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
+ m_runningData{fmt::format("NTCmd:{}", path)},
+ m_nameValue{wpi::rsplit(path, '/').second} {
m_runningData.SetDigital(true);
- m_nt.AddListener(m_running);
- m_nt.AddListener(m_name);
}
void NTCommandSelectorModel::SetRunning(bool run) {
- nt::SetEntryValue(m_running, nt::NetworkTableValue::MakeBoolean(run));
+ m_running.Set(run);
}
void NTCommandSelectorModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_running) {
- if (event.value && event.value->IsBoolean()) {
- m_runningData.SetValue(event.value->GetBoolean());
- }
- } else if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- }
+ for (auto&& v : m_running.ReadQueue()) {
+ m_runningData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
}
}
bool NTCommandSelectorModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_running) != NT_UNASSIGNED;
+ return m_running.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTDifferentialDrive.cpp b/glass/src/libnt/native/cpp/NTDifferentialDrive.cpp
index b44ea07..57d1fa8 100644
--- a/glass/src/libnt/native/cpp/NTDifferentialDrive.cpp
+++ b/glass/src/libnt/native/cpp/NTDifferentialDrive.cpp
@@ -12,46 +12,40 @@
using namespace glass;
NTDifferentialDriveModel::NTDifferentialDriveModel(std::string_view path)
- : NTDifferentialDriveModel(nt::GetDefaultInstance(), path) {}
+ : NTDifferentialDriveModel(nt::NetworkTableInstance::GetDefault(), path) {}
-NTDifferentialDriveModel::NTDifferentialDriveModel(NT_Inst instance,
- std::string_view path)
- : m_nt(instance),
- m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
- m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
- m_lPercent(m_nt.GetEntry(fmt::format("{}/Left Motor Speed", path))),
- m_rPercent(m_nt.GetEntry(fmt::format("{}/Right Motor Speed", path))),
- m_nameValue(wpi::rsplit(path, '/').second),
- m_lPercentData(fmt::format("NTDiffDriveL:{}", path)),
- m_rPercentData(fmt::format("NTDiffDriveR:{}", path)) {
- m_nt.AddListener(m_name);
- m_nt.AddListener(m_controllable);
- m_nt.AddListener(m_lPercent);
- m_nt.AddListener(m_rPercent);
+NTDifferentialDriveModel::NTDifferentialDriveModel(
+ nt::NetworkTableInstance inst, std::string_view path)
+ : m_inst{inst},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
+ m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
+ .Subscribe(false)},
+ m_lPercent{inst.GetDoubleTopic(fmt::format("{}/Left Motor Speed", path))
+ .GetEntry(0)},
+ m_rPercent{inst.GetDoubleTopic(fmt::format("{}/Right Motor Speed", path))
+ .GetEntry(0)},
+ m_nameValue{wpi::rsplit(path, '/').second},
+ m_lPercentData{fmt::format("NTDiffDriveL:{}", path)},
+ m_rPercentData{fmt::format("NTDiffDriveR:{}", path)} {
+ m_wheels.emplace_back("L % Output", &m_lPercentData,
+ [this](auto value) { m_lPercent.Set(value); });
- m_wheels.emplace_back("L % Output", &m_lPercentData, [this](auto value) {
- nt::SetEntryValue(m_lPercent, nt::NetworkTableValue::MakeDouble(value));
- });
-
- m_wheels.emplace_back("R % Output", &m_rPercentData, [this](auto value) {
- nt::SetEntryValue(m_rPercent, nt::NetworkTableValue::MakeDouble(value));
- });
+ m_wheels.emplace_back("R % Output", &m_rPercentData,
+ [this](auto value) { m_rPercent.Set(value); });
}
void NTDifferentialDriveModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_name && event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- } else if (event.entry == m_lPercent && event.value &&
- event.value->IsDouble()) {
- m_lPercentData.SetValue(event.value->GetDouble());
- } else if (event.entry == m_rPercent && event.value &&
- event.value->IsDouble()) {
- m_rPercentData.SetValue(event.value->GetDouble());
- } else if (event.entry == m_controllable && event.value &&
- event.value->IsBoolean()) {
- m_controllableValue = event.value->GetBoolean();
- }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
+ }
+ for (auto&& v : m_lPercent.ReadQueue()) {
+ m_lPercentData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_rPercent.ReadQueue()) {
+ m_rPercentData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_controllable.ReadQueue()) {
+ m_controllableValue = v.value;
}
double l = m_lPercentData.GetValue();
@@ -62,5 +56,5 @@
}
bool NTDifferentialDriveModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_lPercent) != NT_UNASSIGNED;
+ return m_lPercent.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTDigitalInput.cpp b/glass/src/libnt/native/cpp/NTDigitalInput.cpp
index 5de6c29..28b916c 100644
--- a/glass/src/libnt/native/cpp/NTDigitalInput.cpp
+++ b/glass/src/libnt/native/cpp/NTDigitalInput.cpp
@@ -10,34 +10,28 @@
using namespace glass;
NTDigitalInputModel::NTDigitalInputModel(std::string_view path)
- : NTDigitalInputModel{nt::GetDefaultInstance(), path} {}
+ : NTDigitalInputModel{nt::NetworkTableInstance::GetDefault(), path} {}
-NTDigitalInputModel::NTDigitalInputModel(NT_Inst inst, std::string_view path)
- : m_nt{inst},
- m_value{m_nt.GetEntry(fmt::format("{}/Value", path))},
- m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
+NTDigitalInputModel::NTDigitalInputModel(nt::NetworkTableInstance inst,
+ std::string_view path)
+ : m_inst{inst},
+ m_value{
+ inst.GetBooleanTopic(fmt::format("{}/Value", path)).Subscribe(false)},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
m_valueData{fmt::format("NT_DIn:{}", path)},
m_nameValue{wpi::rsplit(path, '/').second} {
- m_nt.AddListener(m_value);
- m_nt.AddListener(m_name);
-
m_valueData.SetDigital(true);
}
void NTDigitalInputModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_value) {
- if (event.value && event.value->IsBoolean()) {
- m_valueData.SetValue(event.value->GetBoolean());
- }
- } else if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- }
+ for (auto&& v : m_value.ReadQueue()) {
+ m_valueData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
}
}
bool NTDigitalInputModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
+ return m_value.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTDigitalOutput.cpp b/glass/src/libnt/native/cpp/NTDigitalOutput.cpp
index a09d424..aa9200d 100644
--- a/glass/src/libnt/native/cpp/NTDigitalOutput.cpp
+++ b/glass/src/libnt/native/cpp/NTDigitalOutput.cpp
@@ -9,43 +9,36 @@
using namespace glass;
NTDigitalOutputModel::NTDigitalOutputModel(std::string_view path)
- : NTDigitalOutputModel{nt::GetDefaultInstance(), path} {}
+ : NTDigitalOutputModel{nt::NetworkTableInstance::GetDefault(), path} {}
-NTDigitalOutputModel::NTDigitalOutputModel(NT_Inst inst, std::string_view path)
- : m_nt{inst},
- m_value{m_nt.GetEntry(fmt::format("{}/Value", path))},
- m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
- m_controllable{m_nt.GetEntry(fmt::format("{}/.controllable", path))},
+NTDigitalOutputModel::NTDigitalOutputModel(nt::NetworkTableInstance inst,
+ std::string_view path)
+ : m_inst{inst},
+ m_value{
+ inst.GetBooleanTopic(fmt::format("{}/Value", path)).GetEntry(false)},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
+ m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
+ .Subscribe(false)},
m_valueData{fmt::format("NT_DOut:{}", path)} {
- m_nt.AddListener(m_value);
- m_nt.AddListener(m_name);
- m_nt.AddListener(m_controllable);
-
m_valueData.SetDigital(true);
}
void NTDigitalOutputModel::SetValue(bool val) {
- nt::SetEntryValue(m_value, nt::Value::MakeBoolean(val));
+ m_value.Set(val);
}
void NTDigitalOutputModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_value) {
- if (event.value && event.value->IsBoolean()) {
- m_valueData.SetValue(event.value->GetBoolean());
- }
- } else if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- } else if (event.entry == m_controllable) {
- if (event.value && event.value->IsBoolean()) {
- m_controllableValue = event.value->GetBoolean();
- }
- }
+ for (auto&& v : m_value.ReadQueue()) {
+ m_valueData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
+ }
+ for (auto&& v : m_controllable.ReadQueue()) {
+ m_controllableValue = v.value;
}
}
bool NTDigitalOutputModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
+ return m_value.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTFMS.cpp b/glass/src/libnt/native/cpp/NTFMS.cpp
index 84c1ce7..65ffa6a 100644
--- a/glass/src/libnt/native/cpp/NTFMS.cpp
+++ b/glass/src/libnt/native/cpp/NTFMS.cpp
@@ -13,15 +13,19 @@
using namespace glass;
NTFMSModel::NTFMSModel(std::string_view path)
- : NTFMSModel{nt::GetDefaultInstance(), path} {}
+ : NTFMSModel{nt::NetworkTableInstance::GetDefault(), path} {}
-NTFMSModel::NTFMSModel(NT_Inst inst, std::string_view path)
- : m_nt{inst},
+NTFMSModel::NTFMSModel(nt::NetworkTableInstance inst, std::string_view path)
+ : m_inst{inst},
m_gameSpecificMessage{
- m_nt.GetEntry(fmt::format("{}/GameSpecificMessage", path))},
- m_alliance{m_nt.GetEntry(fmt::format("{}/IsRedAlliance", path))},
- m_station{m_nt.GetEntry(fmt::format("{}/StationNumber", path))},
- m_controlWord{m_nt.GetEntry(fmt::format("{}/FMSControlData", path))},
+ inst.GetStringTopic(fmt::format("{}/GameSpecificMessage", path))
+ .Subscribe("")},
+ m_alliance{inst.GetBooleanTopic(fmt::format("{}/IsRedAlliance", path))
+ .Subscribe(false)},
+ m_station{inst.GetIntegerTopic(fmt::format("{}/StationNumber", path))
+ .Subscribe(0)},
+ m_controlWord{inst.GetIntegerTopic(fmt::format("{}/FMSControlData", path))
+ .Subscribe(0)},
m_fmsAttached{fmt::format("NT_FMS:FMSAttached:{}", path)},
m_dsAttached{fmt::format("NT_FMS:DSAttached:{}", path)},
m_allianceStationId{fmt::format("NT_FMS:AllianceStationID:{}", path)},
@@ -29,10 +33,6 @@
m_enabled{fmt::format("NT_FMS:RobotEnabled:{}", path)},
m_test{fmt::format("NT_FMS:TestMode:{}", path)},
m_autonomous{fmt::format("NT_FMS:AutonomousMode:{}", path)} {
- m_nt.AddListener(m_alliance);
- m_nt.AddListener(m_station);
- m_nt.AddListener(m_controlWord);
-
m_fmsAttached.SetDigital(true);
m_dsAttached.SetDigital(true);
m_estop.SetDigital(true);
@@ -43,49 +43,35 @@
std::string_view NTFMSModel::GetGameSpecificMessage(
wpi::SmallVectorImpl<char>& buf) {
- buf.clear();
- auto value = nt::GetEntryValue(m_gameSpecificMessage);
- if (value && value->IsString()) {
- auto str = value->GetString();
- buf.append(str.begin(), str.end());
- }
- return std::string_view{buf.data(), buf.size()};
+ return m_gameSpecificMessage.Get(buf, "");
}
void NTFMSModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_alliance) {
- if (event.value && event.value->IsBoolean()) {
- int allianceStationId = m_allianceStationId.GetValue();
- allianceStationId %= 3;
- // true if red
- allianceStationId += 3 * (event.value->GetBoolean() ? 0 : 1);
- m_allianceStationId.SetValue(allianceStationId);
- }
- } else if (event.entry == m_station) {
- if (event.value && event.value->IsDouble()) {
- int allianceStationId = m_allianceStationId.GetValue();
- bool isRed = (allianceStationId < 3);
- // the NT value is 1-indexed
- m_allianceStationId.SetValue(event.value->GetDouble() - 1 +
- 3 * (isRed ? 0 : 1));
- }
- } else if (event.entry == m_controlWord) {
- if (event.value && event.value->IsDouble()) {
- uint32_t controlWord = event.value->GetDouble();
- // See HAL_ControlWord definition
- auto time = wpi::Now();
- m_enabled.SetValue(((controlWord & 0x01) != 0) ? 1 : 0, time);
- m_autonomous.SetValue(((controlWord & 0x02) != 0) ? 1 : 0, time);
- m_test.SetValue(((controlWord & 0x04) != 0) ? 1 : 0, time);
- m_estop.SetValue(((controlWord & 0x08) != 0) ? 1 : 0, time);
- m_fmsAttached.SetValue(((controlWord & 0x10) != 0) ? 1 : 0, time);
- m_dsAttached.SetValue(((controlWord & 0x20) != 0) ? 1 : 0, time);
- }
- }
+ for (auto&& v : m_alliance.ReadQueue()) {
+ int allianceStationId = m_allianceStationId.GetValue();
+ allianceStationId %= 3;
+ // true if red
+ allianceStationId += 3 * (v.value ? 0 : 1);
+ m_allianceStationId.SetValue(allianceStationId, v.time);
+ }
+ for (auto&& v : m_station.ReadQueue()) {
+ int allianceStationId = m_allianceStationId.GetValue();
+ bool isRed = (allianceStationId < 3);
+ // the NT value is 1-indexed
+ m_allianceStationId.SetValue(v.value - 1 + 3 * (isRed ? 0 : 1), v.time);
+ }
+ for (auto&& v : m_controlWord.ReadQueue()) {
+ uint32_t controlWord = v.value;
+ // See HAL_ControlWord definition
+ m_enabled.SetValue(((controlWord & 0x01) != 0) ? 1 : 0, v.time);
+ m_autonomous.SetValue(((controlWord & 0x02) != 0) ? 1 : 0, v.time);
+ m_test.SetValue(((controlWord & 0x04) != 0) ? 1 : 0, v.time);
+ m_estop.SetValue(((controlWord & 0x08) != 0) ? 1 : 0, v.time);
+ m_fmsAttached.SetValue(((controlWord & 0x10) != 0) ? 1 : 0, v.time);
+ m_dsAttached.SetValue(((controlWord & 0x20) != 0) ? 1 : 0, v.time);
}
}
bool NTFMSModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_controlWord) != NT_UNASSIGNED;
+ return m_controlWord.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTField2D.cpp b/glass/src/libnt/native/cpp/NTField2D.cpp
index 47fa9a7..745e9e2 100644
--- a/glass/src/libnt/native/cpp/NTField2D.cpp
+++ b/glass/src/libnt/native/cpp/NTField2D.cpp
@@ -8,6 +8,8 @@
#include <vector>
#include <fmt/format.h>
+#include <networktables/DoubleArrayTopic.h>
+#include <networktables/MultiSubscriber.h>
#include <ntcore_cpp.h>
#include <wpi/Endian.h>
#include <wpi/MathExtras.h>
@@ -18,24 +20,20 @@
class NTField2DModel::ObjectModel : public FieldObjectModel {
public:
- ObjectModel(std::string_view name, NT_Entry entry)
- : m_name{name}, m_entry{entry} {}
+ ObjectModel(std::string_view name, nt::DoubleArrayTopic topic)
+ : m_name{name}, m_topic{topic} {}
const char* GetName() const override { return m_name.c_str(); }
- NT_Entry GetEntry() const { return m_entry; }
+ nt::DoubleArrayTopic GetTopic() const { return m_topic; }
void NTUpdate(const nt::Value& value);
- void Update() override {
- if (auto value = nt::GetEntryValue(m_entry)) {
- NTUpdate(*value);
- }
- }
- bool Exists() override { return nt::GetEntryType(m_entry) != NT_UNASSIGNED; }
+ void Update() override {}
+ bool Exists() override { return m_topic.Exists(); }
bool IsReadOnly() override { return false; }
- wpi::span<const frc::Pose2d> GetPoses() override { return m_poses; }
- void SetPoses(wpi::span<const frc::Pose2d> poses) override;
+ std::span<const frc::Pose2d> GetPoses() override { return m_poses; }
+ void SetPoses(std::span<const frc::Pose2d> poses) override;
void SetPose(size_t i, frc::Pose2d pose) override;
void SetPosition(size_t i, frc::Translation2d pos) override;
void SetRotation(size_t i, frc::Rotation2d rot) override;
@@ -44,7 +42,8 @@
void UpdateNT();
std::string m_name;
- NT_Entry m_entry;
+ nt::DoubleArrayTopic m_topic;
+ nt::DoubleArrayPublisher m_pub;
std::vector<frc::Pose2d> m_poses;
};
@@ -62,66 +61,24 @@
units::meter_t{arr[i * 3 + 0]}, units::meter_t{arr[i * 3 + 1]},
frc::Rotation2d{units::degree_t{arr[i * 3 + 2]}}};
}
- } else if (value.IsRaw()) {
- // treat it simply as an array of doubles
- std::string_view data = value.GetRaw();
-
- // must be triples of doubles
- auto size = data.size();
- if ((size % (3 * 8)) != 0) {
- return;
- }
- m_poses.resize(size / (3 * 8));
- const char* p = data.data();
- for (size_t i = 0; i < size / (3 * 8); ++i) {
- double x = wpi::BitsToDouble(
- wpi::support::endian::readNext<uint64_t, wpi::support::big,
- wpi::support::unaligned>(p));
- double y = wpi::BitsToDouble(
- wpi::support::endian::readNext<uint64_t, wpi::support::big,
- wpi::support::unaligned>(p));
- double rot = wpi::BitsToDouble(
- wpi::support::endian::readNext<uint64_t, wpi::support::big,
- wpi::support::unaligned>(p));
- m_poses[i] = frc::Pose2d{units::meter_t{x}, units::meter_t{y},
- frc::Rotation2d{units::degree_t{rot}}};
- }
}
}
void NTField2DModel::ObjectModel::UpdateNT() {
- if (m_poses.size() < (255 / 3)) {
- wpi::SmallVector<double, 9> arr;
- for (auto&& pose : m_poses) {
- auto& translation = pose.Translation();
- arr.push_back(translation.X().value());
- arr.push_back(translation.Y().value());
- arr.push_back(pose.Rotation().Degrees().value());
- }
- nt::SetEntryTypeValue(m_entry, nt::Value::MakeDoubleArray(arr));
- } else {
- // send as raw array of doubles if too big for NT array
- std::vector<char> arr;
- arr.resize(m_poses.size() * 3 * 8);
- char* p = arr.data();
- for (auto&& pose : m_poses) {
- auto& translation = pose.Translation();
- wpi::support::endian::write64be(
- p, wpi::DoubleToBits(translation.X().value()));
- p += 8;
- wpi::support::endian::write64be(
- p, wpi::DoubleToBits(translation.Y().value()));
- p += 8;
- wpi::support::endian::write64be(
- p, wpi::DoubleToBits(pose.Rotation().Degrees().value()));
- p += 8;
- }
- nt::SetEntryTypeValue(m_entry,
- nt::Value::MakeRaw({arr.data(), arr.size()}));
+ wpi::SmallVector<double, 9> arr;
+ for (auto&& pose : m_poses) {
+ auto& translation = pose.Translation();
+ arr.push_back(translation.X().value());
+ arr.push_back(translation.Y().value());
+ arr.push_back(pose.Rotation().Degrees().value());
}
+ if (!m_pub) {
+ m_pub = m_topic.Publish();
+ }
+ m_pub.Set(arr);
}
-void NTField2DModel::ObjectModel::SetPoses(wpi::span<const frc::Pose2d> poses) {
+void NTField2DModel::ObjectModel::SetPoses(std::span<const frc::Pose2d> poses) {
m_poses.assign(poses.begin(), poses.end());
UpdateNT();
}
@@ -149,69 +106,69 @@
}
NTField2DModel::NTField2DModel(std::string_view path)
- : NTField2DModel{nt::GetDefaultInstance(), path} {}
+ : NTField2DModel{nt::NetworkTableInstance::GetDefault(), path} {}
-NTField2DModel::NTField2DModel(NT_Inst inst, std::string_view path)
- : m_nt{inst},
- m_path{fmt::format("{}/", path)},
- m_name{m_nt.GetEntry(fmt::format("{}/.name", path))} {
- m_nt.AddListener(m_path, NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
- NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE);
+NTField2DModel::NTField2DModel(nt::NetworkTableInstance inst,
+ std::string_view path)
+ : m_path{fmt::format("{}/", path)},
+ m_inst{inst},
+ m_tableSub{inst, {{m_path}}, {.periodic = 0.05, .sendAll = true}},
+ m_nameTopic{inst.GetTopic(fmt::format("{}/.name", path))},
+ m_poller{inst} {
+ m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic |
+ nt::EventFlags::kValueAll |
+ nt::EventFlags::kImmediate);
}
NTField2DModel::~NTField2DModel() = default;
void NTField2DModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- // .name
- if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- continue;
- }
-
- // common case: update of existing entry; search by entry
- if (event.flags & NT_NOTIFY_UPDATE) {
- auto it = std::find_if(
- m_objects.begin(), m_objects.end(),
- [&](const auto& e) { return e->GetEntry() == event.entry; });
- if (it != m_objects.end()) {
- (*it)->NTUpdate(*event.value);
- continue;
- }
- }
-
- // handle create/delete
- std::string_view name = event.name;
- if (wpi::starts_with(name, m_path)) {
- name.remove_prefix(m_path.size());
+ for (auto&& event : m_poller.ReadQueue()) {
+ if (auto info = event.GetTopicInfo()) {
+ // handle publish/unpublish
+ auto name = wpi::drop_front(info->name, m_path.size());
if (name.empty() || name[0] == '.') {
continue;
}
- auto [it, match] = Find(event.name);
- if (event.flags & NT_NOTIFY_DELETE) {
+ auto [it, match] = Find(info->name);
+ if (event.flags & nt::EventFlags::kUnpublish) {
if (match) {
m_objects.erase(it);
}
continue;
- } else if (event.flags & NT_NOTIFY_NEW) {
+ } else if (event.flags & nt::EventFlags::kPublish) {
if (!match) {
it = m_objects.emplace(
- it, std::make_unique<ObjectModel>(event.name, event.entry));
+ it, std::make_unique<ObjectModel>(
+ info->name, nt::DoubleArrayTopic{info->topic}));
}
} else if (!match) {
continue;
}
- if (event.flags & (NT_NOTIFY_NEW | NT_NOTIFY_UPDATE)) {
- (*it)->NTUpdate(*event.value);
+ } else if (auto valueData = event.GetValueEventData()) {
+ // update values
+ // .name
+ if (valueData->topic == m_nameTopic.GetHandle()) {
+ if (valueData->value && valueData->value.IsString()) {
+ m_nameValue = valueData->value.GetString();
+ }
+ continue;
+ }
+
+ auto it =
+ std::find_if(m_objects.begin(), m_objects.end(), [&](const auto& e) {
+ return e->GetTopic().GetHandle() == valueData->topic;
+ });
+ if (it != m_objects.end()) {
+ (*it)->NTUpdate(valueData->value);
+ continue;
}
}
}
}
bool NTField2DModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_name) != NT_UNASSIGNED;
+ return m_nameTopic.Exists();
}
bool NTField2DModel::IsReadOnly() {
@@ -222,8 +179,9 @@
auto fullName = fmt::format("{}{}", m_path, name);
auto [it, match] = Find(fullName);
if (!match) {
- it = m_objects.emplace(
- it, std::make_unique<ObjectModel>(fullName, m_nt.GetEntry(fullName)));
+ it = m_objects.emplace(it,
+ std::make_unique<ObjectModel>(
+ fullName, m_inst.GetDoubleArrayTopic(fullName)));
}
return it->get();
}
@@ -231,7 +189,6 @@
void NTField2DModel::RemoveFieldObject(std::string_view name) {
auto [it, match] = Find(fmt::format("{}{}", m_path, name));
if (match) {
- nt::DeleteEntry((*it)->GetEntry());
m_objects.erase(it);
}
}
diff --git a/glass/src/libnt/native/cpp/NTGyro.cpp b/glass/src/libnt/native/cpp/NTGyro.cpp
index 7651d2c..a036b39 100644
--- a/glass/src/libnt/native/cpp/NTGyro.cpp
+++ b/glass/src/libnt/native/cpp/NTGyro.cpp
@@ -10,32 +10,24 @@
using namespace glass;
NTGyroModel::NTGyroModel(std::string_view path)
- : NTGyroModel(nt::GetDefaultInstance(), path) {}
+ : NTGyroModel(nt::NetworkTableInstance::GetDefault(), path) {}
-NTGyroModel::NTGyroModel(NT_Inst instance, std::string_view path)
- : m_nt(instance),
- m_angle(m_nt.GetEntry(fmt::format("{}/Value", path))),
- m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
- m_angleData(fmt::format("NT_Gyro:{}", path)),
- m_nameValue(wpi::rsplit(path, '/').second) {
- m_nt.AddListener(m_angle);
- m_nt.AddListener(m_name);
-}
+NTGyroModel::NTGyroModel(nt::NetworkTableInstance inst, std::string_view path)
+ : m_inst{inst},
+ m_angle{inst.GetDoubleTopic(fmt::format("{}/Value", path)).Subscribe(0)},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe({})},
+ m_angleData{fmt::format("NT_Gyro:{}", path)},
+ m_nameValue{wpi::rsplit(path, '/').second} {}
void NTGyroModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_angle) {
- if (event.value && event.value->IsDouble()) {
- m_angleData.SetValue(event.value->GetDouble());
- }
- } else if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
+ }
+ for (auto&& v : m_angle.ReadQueue()) {
+ m_angleData.SetValue(v.value, v.time);
}
}
bool NTGyroModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_angle) != NT_UNASSIGNED;
+ return m_angle.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTMecanumDrive.cpp b/glass/src/libnt/native/cpp/NTMecanumDrive.cpp
index 28c0a67..cb564e8 100644
--- a/glass/src/libnt/native/cpp/NTMecanumDrive.cpp
+++ b/glass/src/libnt/native/cpp/NTMecanumDrive.cpp
@@ -12,69 +12,62 @@
using namespace glass;
NTMecanumDriveModel::NTMecanumDriveModel(std::string_view path)
- : NTMecanumDriveModel(nt::GetDefaultInstance(), path) {}
+ : NTMecanumDriveModel(nt::NetworkTableInstance::GetDefault(), path) {}
-NTMecanumDriveModel::NTMecanumDriveModel(NT_Inst instance,
+NTMecanumDriveModel::NTMecanumDriveModel(nt::NetworkTableInstance inst,
std::string_view path)
- : m_nt(instance),
- m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
- m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
- m_flPercent(
- m_nt.GetEntry(fmt::format("{}/Front Left Motor Speed", path))),
- m_frPercent(
- m_nt.GetEntry(fmt::format("{}/Front Right Motor Speed", path))),
- m_rlPercent(m_nt.GetEntry(fmt::format("{}/Rear Left Motor Speed", path))),
- m_rrPercent(
- m_nt.GetEntry(fmt::format("{}/Rear Right Motor Speed", path))),
- m_nameValue(wpi::rsplit(path, '/').second),
- m_flPercentData(fmt::format("NTMcnmDriveFL:{}", path)),
- m_frPercentData(fmt::format("NTMcnmDriveFR:{}", path)),
- m_rlPercentData(fmt::format("NTMcnmDriveRL:{}", path)),
- m_rrPercentData(fmt::format("NTMcnmDriveRR:{}", path)) {
- m_nt.AddListener(m_name);
- m_nt.AddListener(m_controllable);
- m_nt.AddListener(m_flPercent);
- m_nt.AddListener(m_frPercent);
- m_nt.AddListener(m_rlPercent);
- m_nt.AddListener(m_rrPercent);
+ : m_inst{inst},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
+ m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
+ .Subscribe(0)},
+ m_flPercent{
+ inst.GetDoubleTopic(fmt::format("{}/Front Left Motor Speed", path))
+ .GetEntry(0)},
+ m_frPercent{
+ inst.GetDoubleTopic(fmt::format("{}/Front Right Motor Speed", path))
+ .GetEntry(0)},
+ m_rlPercent{
+ inst.GetDoubleTopic(fmt::format("{}/Rear Left Motor Speed", path))
+ .GetEntry(0)},
+ m_rrPercent{
+ inst.GetDoubleTopic(fmt::format("{}/Rear Right Motor Speed", path))
+ .GetEntry(0)},
+ m_nameValue{wpi::rsplit(path, '/').second},
+ m_flPercentData{fmt::format("NTMcnmDriveFL:{}", path)},
+ m_frPercentData{fmt::format("NTMcnmDriveFR:{}", path)},
+ m_rlPercentData{fmt::format("NTMcnmDriveRL:{}", path)},
+ m_rrPercentData{fmt::format("NTMcnmDriveRR:{}", path)} {
+ m_wheels.emplace_back("FL % Output", &m_flPercentData,
+ [this](auto value) { m_flPercent.Set(value); });
- m_wheels.emplace_back("FL % Output", &m_flPercentData, [this](auto value) {
- nt::SetEntryValue(m_flPercent, nt::NetworkTableValue::MakeDouble(value));
- });
+ m_wheels.emplace_back("FR % Output", &m_frPercentData,
+ [this](auto value) { m_frPercent.Set(value); });
- m_wheels.emplace_back("FR % Output", &m_frPercentData, [this](auto value) {
- nt::SetEntryValue(m_frPercent, nt::NetworkTableValue::MakeDouble(value));
- });
+ m_wheels.emplace_back("RL % Output", &m_rlPercentData,
+ [this](auto value) { m_rlPercent.Set(value); });
- m_wheels.emplace_back("RL % Output", &m_rlPercentData, [this](auto value) {
- nt::SetEntryValue(m_rlPercent, nt::NetworkTableValue::MakeDouble(value));
- });
-
- m_wheels.emplace_back("RR % Output", &m_rrPercentData, [this](auto value) {
- nt::SetEntryValue(m_rrPercent, nt::NetworkTableValue::MakeDouble(value));
- });
+ m_wheels.emplace_back("RR % Output", &m_rrPercentData,
+ [this](auto value) { m_rrPercent.Set(value); });
}
void NTMecanumDriveModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_name && event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- } else if (event.entry == m_flPercent && event.value &&
- event.value->IsDouble()) {
- m_flPercentData.SetValue(event.value->GetDouble());
- } else if (event.entry == m_frPercent && event.value &&
- event.value->IsDouble()) {
- m_frPercentData.SetValue(event.value->GetDouble());
- } else if (event.entry == m_rlPercent && event.value &&
- event.value->IsDouble()) {
- m_rlPercentData.SetValue(event.value->GetDouble());
- } else if (event.entry == m_rrPercent && event.value &&
- event.value->IsDouble()) {
- m_rrPercentData.SetValue(event.value->GetDouble());
- } else if (event.entry == m_controllable && event.value &&
- event.value->IsBoolean()) {
- m_controllableValue = event.value->GetBoolean();
- }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
+ }
+ for (auto&& v : m_flPercent.ReadQueue()) {
+ m_flPercentData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_frPercent.ReadQueue()) {
+ m_frPercentData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_rlPercent.ReadQueue()) {
+ m_rlPercentData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_rrPercent.ReadQueue()) {
+ m_rrPercentData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_controllable.ReadQueue()) {
+ m_controllableValue = v.value;
}
double fl = m_flPercentData.GetValue();
@@ -88,5 +81,5 @@
}
bool NTMecanumDriveModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_flPercent) != NT_UNASSIGNED;
+ return m_flPercent.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTMechanism2D.cpp b/glass/src/libnt/native/cpp/NTMechanism2D.cpp
index 9c73af2..32ed276 100644
--- a/glass/src/libnt/native/cpp/NTMechanism2D.cpp
+++ b/glass/src/libnt/native/cpp/NTMechanism2D.cpp
@@ -34,16 +34,17 @@
class NTMechanismGroupImpl final {
public:
- NTMechanismGroupImpl(NT_Inst inst, std::string_view path,
+ NTMechanismGroupImpl(nt::NetworkTableInstance inst, std::string_view path,
std::string_view name)
: m_inst{inst}, m_path{path}, m_name{name} {}
const char* GetName() const { return m_name.c_str(); }
void ForEachObject(wpi::function_ref<void(MechanismObjectModel& model)> func);
- void NTUpdate(const nt::EntryNotification& event, std::string_view name);
+
+ void NTUpdate(const nt::Event& event, std::string_view name);
protected:
- NT_Inst m_inst;
+ nt::NetworkTableInstance m_inst;
std::string m_path;
std::string m_name;
std::vector<std::unique_ptr<NTMechanismObjectModel>> m_objects;
@@ -51,14 +52,14 @@
class NTMechanismObjectModel final : public MechanismObjectModel {
public:
- NTMechanismObjectModel(NT_Inst inst, std::string_view path,
+ NTMechanismObjectModel(nt::NetworkTableInstance inst, std::string_view path,
std::string_view name)
: m_group{inst, path, name},
- m_type{nt::GetEntry(inst, fmt::format("{}/.type", path))},
- m_color{nt::GetEntry(inst, fmt::format("{}/color", path))},
- m_weight{nt::GetEntry(inst, fmt::format("{}/weight", path))},
- m_angle{nt::GetEntry(inst, fmt::format("{}/angle", path))},
- m_length{nt::GetEntry(inst, fmt::format("{}/length", path))} {}
+ m_typeTopic{inst.GetTopic(fmt::format("{}/.type", path))},
+ m_colorTopic{inst.GetTopic(fmt::format("{}/color", path))},
+ m_weightTopic{inst.GetTopic(fmt::format("{}/weight", path))},
+ m_angleTopic{inst.GetTopic(fmt::format("{}/angle", path))},
+ m_lengthTopic{inst.GetTopic(fmt::format("{}/length", path))} {}
const char* GetName() const final { return m_group.GetName(); }
void ForEachObject(
@@ -72,16 +73,16 @@
frc::Rotation2d GetAngle() const final { return m_angleValue; }
units::meter_t GetLength() const final { return m_lengthValue; }
- bool NTUpdate(const nt::EntryNotification& event, std::string_view childName);
+ bool NTUpdate(const nt::Event& event, std::string_view name);
private:
NTMechanismGroupImpl m_group;
- NT_Entry m_type;
- NT_Entry m_color;
- NT_Entry m_weight;
- NT_Entry m_angle;
- NT_Entry m_length;
+ nt::Topic m_typeTopic;
+ nt::Topic m_colorTopic;
+ nt::Topic m_weightTopic;
+ nt::Topic m_angleTopic;
+ nt::Topic m_lengthTopic;
std::string m_typeValue;
ImU32 m_colorValue = IM_COL32_WHITE;
@@ -99,7 +100,7 @@
}
}
-void NTMechanismGroupImpl::NTUpdate(const nt::EntryNotification& event,
+void NTMechanismGroupImpl::NTUpdate(const nt::Event& event,
std::string_view name) {
if (name.empty()) {
return;
@@ -115,58 +116,76 @@
[](const auto& e, std::string_view name) { return e->GetName() < name; });
bool match = it != m_objects.end() && (*it)->GetName() == name;
- if (event.flags & NT_NOTIFY_NEW) {
- if (!match) {
- it = m_objects.emplace(
- it, std::make_unique<NTMechanismObjectModel>(
- m_inst, fmt::format("{}/{}", m_path, name), name));
- match = true;
+ if (event.GetTopicInfo()) {
+ if (event.flags & nt::EventFlags::kPublish) {
+ if (!match) {
+ it = m_objects.emplace(
+ it, std::make_unique<NTMechanismObjectModel>(
+ m_inst, fmt::format("{}/{}", m_path, name), name));
+ match = true;
+ }
}
- }
- if (match) {
- if ((*it)->NTUpdate(event, childName)) {
- m_objects.erase(it);
+
+ if (match) {
+ if ((*it)->NTUpdate(event, childName)) {
+ m_objects.erase(it);
+ }
+ }
+ } else if (event.GetValueEventData()) {
+ if (match) {
+ (*it)->NTUpdate(event, childName);
}
}
}
-bool NTMechanismObjectModel::NTUpdate(const nt::EntryNotification& event,
+bool NTMechanismObjectModel::NTUpdate(const nt::Event& event,
std::string_view childName) {
- if (event.entry == m_type) {
- if ((event.flags & NT_NOTIFY_DELETE) != 0) {
- return true;
+ if (auto info = event.GetTopicInfo()) {
+ if (info->topic == m_typeTopic.GetHandle()) {
+ if (event.flags & nt::EventFlags::kUnpublish) {
+ return true;
+ }
+ } else if (info->topic != m_colorTopic.GetHandle() &&
+ info->topic != m_weightTopic.GetHandle() &&
+ info->topic != m_angleTopic.GetHandle() &&
+ info->topic != m_lengthTopic.GetHandle()) {
+ m_group.NTUpdate(event, childName);
}
- if (event.value && event.value->IsString()) {
- m_typeValue = event.value->GetString();
+ } else if (auto valueData = event.GetValueEventData()) {
+ if (valueData->topic == m_typeTopic.GetHandle()) {
+ if (valueData->value && valueData->value.IsString()) {
+ m_typeValue = valueData->value.GetString();
+ }
+ } else if (valueData->topic == m_colorTopic.GetHandle()) {
+ if (valueData->value && valueData->value.IsString()) {
+ ConvertColor(valueData->value.GetString(), &m_colorValue);
+ }
+ } else if (valueData->topic == m_weightTopic.GetHandle()) {
+ if (valueData->value && valueData->value.IsDouble()) {
+ m_weightValue = valueData->value.GetDouble();
+ }
+ } else if (valueData->topic == m_angleTopic.GetHandle()) {
+ if (valueData->value && valueData->value.IsDouble()) {
+ m_angleValue = units::degree_t{valueData->value.GetDouble()};
+ }
+ } else if (valueData->topic == m_lengthTopic.GetHandle()) {
+ if (valueData->value && valueData->value.IsDouble()) {
+ m_lengthValue = units::meter_t{valueData->value.GetDouble()};
+ }
+ } else {
+ m_group.NTUpdate(event, childName);
}
- } else if (event.entry == m_color) {
- if (event.value && event.value->IsString()) {
- ConvertColor(event.value->GetString(), &m_colorValue);
- }
- } else if (event.entry == m_weight) {
- if (event.value && event.value->IsDouble()) {
- m_weightValue = event.value->GetDouble();
- }
- } else if (event.entry == m_angle) {
- if (event.value && event.value->IsDouble()) {
- m_angleValue = units::degree_t{event.value->GetDouble()};
- }
- } else if (event.entry == m_length) {
- if (event.value && event.value->IsDouble()) {
- m_lengthValue = units::meter_t{event.value->GetDouble()};
- }
- } else {
- m_group.NTUpdate(event, childName);
}
return false;
}
class NTMechanism2DModel::RootModel final : public MechanismRootModel {
public:
- RootModel(NT_Inst inst, std::string_view path, std::string_view name)
+ RootModel(nt::NetworkTableInstance inst, std::string_view path,
+ std::string_view name)
: m_group{inst, path, name},
- m_x{nt::GetEntry(inst, fmt::format("{}/x", path))},
- m_y{nt::GetEntry(inst, fmt::format("{}/y", path))} {}
+ m_xTopic{inst.GetTopic(fmt::format("{}/x", path))},
+ m_yTopic{inst.GetTopic(fmt::format("{}/y", path))} {}
const char* GetName() const final { return m_group.GetName(); }
void ForEachObject(
@@ -174,85 +193,70 @@
m_group.ForEachObject(func);
}
- bool NTUpdate(const nt::EntryNotification& event, std::string_view childName);
+ bool NTUpdate(const nt::Event& event, std::string_view childName);
frc::Translation2d GetPosition() const override { return m_pos; };
private:
NTMechanismGroupImpl m_group;
- NT_Entry m_x;
- NT_Entry m_y;
+ nt::Topic m_xTopic;
+ nt::Topic m_yTopic;
frc::Translation2d m_pos;
};
-bool NTMechanism2DModel::RootModel::NTUpdate(const nt::EntryNotification& event,
+bool NTMechanism2DModel::RootModel::NTUpdate(const nt::Event& event,
std::string_view childName) {
- if ((event.flags & NT_NOTIFY_DELETE) != 0 &&
- (event.entry == m_x || event.entry == m_y)) {
- return true;
- } else if (event.entry == m_x) {
- if (event.value && event.value->IsDouble()) {
- m_pos = frc::Translation2d{units::meter_t{event.value->GetDouble()},
- m_pos.Y()};
+ if (auto info = event.GetTopicInfo()) {
+ if (info->topic == m_xTopic.GetHandle() ||
+ info->topic == m_yTopic.GetHandle()) {
+ if (event.flags & nt::EventFlags::kUnpublish) {
+ return true;
+ }
+ } else {
+ m_group.NTUpdate(event, childName);
}
- } else if (event.entry == m_y) {
- if (event.value && event.value->IsDouble()) {
- m_pos = frc::Translation2d{m_pos.X(),
- units::meter_t{event.value->GetDouble()}};
+ } else if (auto valueData = event.GetValueEventData()) {
+ if (valueData->topic == m_xTopic.GetHandle()) {
+ if (valueData->value && valueData->value.IsDouble()) {
+ m_pos = frc::Translation2d{units::meter_t{valueData->value.GetDouble()},
+ m_pos.Y()};
+ }
+ } else if (valueData->topic == m_yTopic.GetHandle()) {
+ if (valueData->value && valueData->value.IsDouble()) {
+ m_pos = frc::Translation2d{
+ m_pos.X(), units::meter_t{valueData->value.GetDouble()}};
+ }
+ } else {
+ m_group.NTUpdate(event, childName);
}
- } else {
- m_group.NTUpdate(event, childName);
}
return false;
}
NTMechanism2DModel::NTMechanism2DModel(std::string_view path)
- : NTMechanism2DModel{nt::GetDefaultInstance(), path} {}
+ : NTMechanism2DModel{nt::NetworkTableInstance::GetDefault(), path} {}
-NTMechanism2DModel::NTMechanism2DModel(NT_Inst inst, std::string_view path)
- : m_nt{inst},
+NTMechanism2DModel::NTMechanism2DModel(nt::NetworkTableInstance inst,
+ std::string_view path)
+ : m_inst{inst},
m_path{fmt::format("{}/", path)},
- m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
- m_dimensions{m_nt.GetEntry(fmt::format("{}/dims", path))},
- m_bgColor{m_nt.GetEntry(fmt::format("{}/backgroundColor", path))},
+ m_tableSub{inst, {{m_path}}, {.periodic = 0.05, .sendAll = true}},
+ m_nameTopic{m_inst.GetTopic(fmt::format("{}/.name", path))},
+ m_dimensionsTopic{m_inst.GetTopic(fmt::format("{}/dims", path))},
+ m_bgColorTopic{m_inst.GetTopic(fmt::format("{}/backgroundColor", path))},
+ m_poller{m_inst},
m_dimensionsValue{1_m, 1_m} {
- m_nt.AddListener(m_path, NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
- NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE);
+ m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic |
+ nt::EventFlags::kValueAll |
+ nt::EventFlags::kImmediate);
}
NTMechanism2DModel::~NTMechanism2DModel() = default;
void NTMechanism2DModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- // .name
- if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- continue;
- }
-
- // dims
- if (event.entry == m_dimensions) {
- if (event.value && event.value->IsDoubleArray()) {
- auto arr = event.value->GetDoubleArray();
- if (arr.size() == 2) {
- m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
- units::meter_t{arr[1]}};
- }
- }
- }
-
- // backgroundColor
- if (event.entry == m_bgColor) {
- if (event.value && event.value->IsString()) {
- ConvertColor(event.value->GetString(), &m_bgColorValue);
- }
- }
-
- std::string_view name = event.name;
- if (wpi::starts_with(name, m_path)) {
- name.remove_prefix(m_path.size());
+ for (auto&& event : m_poller.ReadQueue()) {
+ if (auto info = event.GetTopicInfo()) {
+ auto name = wpi::drop_front(info->name, m_path.size());
if (name.empty() || name[0] == '.') {
continue;
}
@@ -268,12 +272,11 @@
});
bool match = it != m_roots.end() && (*it)->GetName() == name;
- if (event.flags & NT_NOTIFY_NEW) {
+ if (event.flags & nt::EventFlags::kPublish) {
if (!match) {
it = m_roots.emplace(
- it,
- std::make_unique<RootModel>(
- m_nt.GetInstance(), fmt::format("{}{}", m_path, name), name));
+ it, std::make_unique<RootModel>(
+ m_inst, fmt::format("{}{}", m_path, name), name));
match = true;
}
}
@@ -282,12 +285,54 @@
m_roots.erase(it);
}
}
+ } else if (auto valueData = event.GetValueEventData()) {
+ if (valueData->topic == m_nameTopic.GetHandle()) {
+ // .name
+ if (valueData->value && valueData->value.IsString()) {
+ m_nameValue = valueData->value.GetString();
+ }
+ } else if (valueData->topic == m_dimensionsTopic.GetHandle()) {
+ // dims
+ if (valueData->value && valueData->value.IsDoubleArray()) {
+ auto arr = valueData->value.GetDoubleArray();
+ if (arr.size() == 2) {
+ m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
+ units::meter_t{arr[1]}};
+ }
+ }
+ } else if (valueData->topic == m_bgColorTopic.GetHandle()) {
+ // backgroundColor
+ if (valueData->value && valueData->value.IsString()) {
+ ConvertColor(valueData->value.GetString(), &m_bgColorValue);
+ }
+ } else {
+ auto fullName = nt::Topic{valueData->topic}.GetName();
+ auto name = wpi::drop_front(fullName, m_path.size());
+ if (name.empty() || name[0] == '.') {
+ continue;
+ }
+ std::string_view childName;
+ std::tie(name, childName) = wpi::split(name, '/');
+ if (childName.empty()) {
+ continue;
+ }
+
+ auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
+ [](const auto& e, std::string_view name) {
+ return e->GetName() < name;
+ });
+ if (it != m_roots.end() && (*it)->GetName() == name) {
+ if ((*it)->NTUpdate(event, childName)) {
+ m_roots.erase(it);
+ }
+ }
+ }
}
}
}
bool NTMechanism2DModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_name) != NT_UNASSIGNED;
+ return m_nameTopic.Exists();
}
bool NTMechanism2DModel::IsReadOnly() {
diff --git a/glass/src/libnt/native/cpp/NTMotorController.cpp b/glass/src/libnt/native/cpp/NTMotorController.cpp
new file mode 100644
index 0000000..1de6714
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTMotorController.cpp
@@ -0,0 +1,43 @@
+// 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.
+
+#include "glass/networktables/NTMotorController.h"
+
+#include <fmt/format.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTMotorControllerModel::NTMotorControllerModel(std::string_view path)
+ : NTMotorControllerModel(nt::NetworkTableInstance::GetDefault(), path) {}
+
+NTMotorControllerModel::NTMotorControllerModel(nt::NetworkTableInstance inst,
+ std::string_view path)
+ : m_inst{inst},
+ m_value{inst.GetDoubleTopic(fmt::format("{}/Value", path)).GetEntry(0)},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
+ m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
+ .Subscribe(false)},
+ m_valueData{fmt::format("NT_SpdCtrl:{}", path)},
+ m_nameValue{wpi::rsplit(path, '/').second} {}
+
+void NTMotorControllerModel::SetPercent(double value) {
+ m_value.Set(value);
+}
+
+void NTMotorControllerModel::Update() {
+ for (auto&& v : m_value.ReadQueue()) {
+ m_valueData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
+ }
+ for (auto&& v : m_controllable.ReadQueue()) {
+ m_controllableValue = v.value;
+ }
+}
+
+bool NTMotorControllerModel::Exists() {
+ return m_value.Exists();
+}
diff --git a/glass/src/libnt/native/cpp/NTPIDController.cpp b/glass/src/libnt/native/cpp/NTPIDController.cpp
index 7936057..1dde27d 100644
--- a/glass/src/libnt/native/cpp/NTPIDController.cpp
+++ b/glass/src/libnt/native/cpp/NTPIDController.cpp
@@ -10,76 +10,62 @@
using namespace glass;
NTPIDControllerModel::NTPIDControllerModel(std::string_view path)
- : NTPIDControllerModel(nt::GetDefaultInstance(), path) {}
+ : NTPIDControllerModel(nt::NetworkTableInstance::GetDefault(), path) {}
-NTPIDControllerModel::NTPIDControllerModel(NT_Inst instance,
+NTPIDControllerModel::NTPIDControllerModel(nt::NetworkTableInstance inst,
std::string_view path)
- : m_nt(instance),
- m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
- m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
- m_p(m_nt.GetEntry(fmt::format("{}/p", path))),
- m_i(m_nt.GetEntry(fmt::format("{}/i", path))),
- m_d(m_nt.GetEntry(fmt::format("{}/d", path))),
- m_setpoint(m_nt.GetEntry(fmt::format("{}/setpoint", path))),
- m_pData(fmt::format("NTPIDCtrlP:{}", path)),
- m_iData(fmt::format("NTPIDCtrlI:{}", path)),
- m_dData(fmt::format("NTPIDCtrlD:{}", path)),
- m_setpointData(fmt::format("NTPIDCtrlStpt:{}", path)),
- m_nameValue(wpi::rsplit(path, '/').second) {
- m_nt.AddListener(m_name);
- m_nt.AddListener(m_controllable);
- m_nt.AddListener(m_p);
- m_nt.AddListener(m_i);
- m_nt.AddListener(m_d);
- m_nt.AddListener(m_setpoint);
-}
+ : m_inst{inst},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
+ m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
+ .Subscribe(false)},
+ m_p{inst.GetDoubleTopic(fmt::format("{}/p", path)).GetEntry(0)},
+ m_i{inst.GetDoubleTopic(fmt::format("{}/i", path)).GetEntry(0)},
+ m_d{inst.GetDoubleTopic(fmt::format("{}/d", path)).GetEntry(0)},
+ m_setpoint{
+ inst.GetDoubleTopic(fmt::format("{}/setpoint", path)).GetEntry(0)},
+ m_pData{fmt::format("NTPIDCtrlP:{}", path)},
+ m_iData{fmt::format("NTPIDCtrlI:{}", path)},
+ m_dData{fmt::format("NTPIDCtrlD:{}", path)},
+ m_setpointData{fmt::format("NTPIDCtrlStpt:{}", path)},
+ m_nameValue{wpi::rsplit(path, '/').second} {}
void NTPIDControllerModel::SetP(double value) {
- nt::SetEntryValue(m_p, nt::NetworkTableValue::MakeDouble(value));
+ m_p.Set(value);
}
void NTPIDControllerModel::SetI(double value) {
- nt::SetEntryValue(m_i, nt::NetworkTableValue::MakeDouble(value));
+ m_i.Set(value);
}
void NTPIDControllerModel::SetD(double value) {
- nt::SetEntryValue(m_d, nt::NetworkTableValue::MakeDouble(value));
+ m_d.Set(value);
}
void NTPIDControllerModel::SetSetpoint(double value) {
- nt::SetEntryValue(m_setpoint, nt::NetworkTableValue::MakeDouble(value));
+ m_setpoint.Set(value);
}
void NTPIDControllerModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- } else if (event.entry == m_p) {
- if (event.value && event.value->IsDouble()) {
- m_pData.SetValue(event.value->GetDouble());
- }
- } else if (event.entry == m_i) {
- if (event.value && event.value->IsDouble()) {
- m_iData.SetValue(event.value->GetDouble());
- }
- } else if (event.entry == m_d) {
- if (event.value && event.value->IsDouble()) {
- m_dData.SetValue(event.value->GetDouble());
- }
- } else if (event.entry == m_setpoint) {
- if (event.value && event.value->IsDouble()) {
- m_setpointData.SetValue(event.value->GetDouble());
- }
- } else if (event.entry == m_controllable) {
- if (event.value && event.value->IsBoolean()) {
- m_controllableValue = event.value->GetBoolean();
- }
- }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
+ }
+ for (auto&& v : m_p.ReadQueue()) {
+ m_pData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_i.ReadQueue()) {
+ m_iData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_d.ReadQueue()) {
+ m_dData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_setpoint.ReadQueue()) {
+ m_setpointData.SetValue(v.value, v.time);
+ }
+ for (auto&& v : m_controllable.ReadQueue()) {
+ m_controllableValue = v.value;
}
}
bool NTPIDControllerModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_setpoint) != NT_UNASSIGNED;
+ return m_setpoint.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTSpeedController.cpp b/glass/src/libnt/native/cpp/NTSpeedController.cpp
deleted file mode 100644
index 3dc351a..0000000
--- a/glass/src/libnt/native/cpp/NTSpeedController.cpp
+++ /dev/null
@@ -1,52 +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.
-
-#include "glass/networktables/NTSpeedController.h"
-
-#include <fmt/format.h>
-#include <wpi/StringExtras.h>
-
-using namespace glass;
-
-NTSpeedControllerModel::NTSpeedControllerModel(std::string_view path)
- : NTSpeedControllerModel(nt::GetDefaultInstance(), path) {}
-
-NTSpeedControllerModel::NTSpeedControllerModel(NT_Inst instance,
- std::string_view path)
- : m_nt(instance),
- m_value(m_nt.GetEntry(fmt::format("{}/Value", path))),
- m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
- m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
- m_valueData(fmt::format("NT_SpdCtrl:{}", path)),
- m_nameValue(wpi::rsplit(path, '/').second) {
- m_nt.AddListener(m_value);
- m_nt.AddListener(m_name);
- m_nt.AddListener(m_controllable);
-}
-
-void NTSpeedControllerModel::SetPercent(double value) {
- nt::SetEntryValue(m_value, nt::NetworkTableValue::MakeDouble(value));
-}
-
-void NTSpeedControllerModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_value) {
- if (event.value && event.value->IsDouble()) {
- m_valueData.SetValue(event.value->GetDouble());
- }
- } else if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- } else if (event.entry == m_controllable) {
- if (event.value && event.value->IsBoolean()) {
- m_controllableValue = event.value->GetBoolean();
- }
- }
- }
-}
-
-bool NTSpeedControllerModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
-}
diff --git a/glass/src/libnt/native/cpp/NTStringChooser.cpp b/glass/src/libnt/native/cpp/NTStringChooser.cpp
index e6a97fa..b892a2c 100644
--- a/glass/src/libnt/native/cpp/NTStringChooser.cpp
+++ b/glass/src/libnt/native/cpp/NTStringChooser.cpp
@@ -9,67 +9,56 @@
using namespace glass;
NTStringChooserModel::NTStringChooserModel(std::string_view path)
- : NTStringChooserModel{nt::GetDefaultInstance(), path} {}
+ : NTStringChooserModel{nt::NetworkTableInstance::GetDefault(), path} {}
-NTStringChooserModel::NTStringChooserModel(NT_Inst inst, std::string_view path)
- : m_nt{inst},
- m_default{m_nt.GetEntry(fmt::format("{}/default", path))},
- m_selected{m_nt.GetEntry(fmt::format("{}/selected", path))},
- m_active{m_nt.GetEntry(fmt::format("{}/active", path))},
- m_options{m_nt.GetEntry(fmt::format("{}/options", path))} {
- m_nt.AddListener(m_default);
- m_nt.AddListener(m_selected);
- m_nt.AddListener(m_active);
- m_nt.AddListener(m_options);
-}
-
-void NTStringChooserModel::SetDefault(std::string_view val) {
- nt::SetEntryValue(m_default, nt::Value::MakeString(val));
+NTStringChooserModel::NTStringChooserModel(nt::NetworkTableInstance inst,
+ std::string_view path)
+ : m_inst{inst},
+ m_default{
+ m_inst.GetStringTopic(fmt::format("{}/default", path)).Subscribe("")},
+ m_selected{
+ m_inst.GetStringTopic(fmt::format("{}/selected", path)).GetEntry("")},
+ m_active{
+ m_inst.GetStringTopic(fmt::format("{}/active", path)).Subscribe("")},
+ m_options{m_inst.GetStringArrayTopic(fmt::format("{}/options", path))
+ .Subscribe({})} {
+ m_selected.GetTopic().SetRetained(true);
}
void NTStringChooserModel::SetSelected(std::string_view val) {
- nt::SetEntryValue(m_selected, nt::Value::MakeString(val));
-}
-
-void NTStringChooserModel::SetActive(std::string_view val) {
- nt::SetEntryValue(m_active, nt::Value::MakeString(val));
-}
-
-void NTStringChooserModel::SetOptions(wpi::span<const std::string> val) {
- nt::SetEntryValue(m_options, nt::Value::MakeStringArray(val));
+ m_selected.Set(val);
}
void NTStringChooserModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_default) {
- if ((event.flags & NT_NOTIFY_DELETE) != 0) {
- m_defaultValue.clear();
- } else if (event.value && event.value->IsString()) {
- m_defaultValue = event.value->GetString();
- }
- } else if (event.entry == m_selected) {
- if ((event.flags & NT_NOTIFY_DELETE) != 0) {
- m_selectedValue.clear();
- } else if (event.value && event.value->IsString()) {
- m_selectedValue = event.value->GetString();
- }
- } else if (event.entry == m_active) {
- if ((event.flags & NT_NOTIFY_DELETE) != 0) {
- m_activeValue.clear();
- } else if (event.value && event.value->IsString()) {
- m_activeValue = event.value->GetString();
- }
- } else if (event.entry == m_options) {
- if ((event.flags & NT_NOTIFY_DELETE) != 0) {
- m_optionsValue.clear();
- } else if (event.value && event.value->IsStringArray()) {
- auto arr = event.value->GetStringArray();
- m_optionsValue.assign(arr.begin(), arr.end());
- }
- }
+ if (!m_default.Exists()) {
+ m_defaultValue.clear();
+ }
+ for (auto&& v : m_default.ReadQueue()) {
+ m_defaultValue = std::move(v.value);
+ }
+
+ if (!m_selected.Exists()) {
+ m_selectedValue.clear();
+ }
+ for (auto&& v : m_selected.ReadQueue()) {
+ m_selectedValue = std::move(v.value);
+ }
+
+ if (!m_active.Exists()) {
+ m_activeValue.clear();
+ }
+ for (auto&& v : m_active.ReadQueue()) {
+ m_activeValue = std::move(v.value);
+ }
+
+ if (!m_options.Exists()) {
+ m_optionsValue.clear();
+ }
+ for (auto&& v : m_options.ReadQueue()) {
+ m_optionsValue = std::move(v.value);
}
}
bool NTStringChooserModel::Exists() {
- return m_nt.IsConnected() && nt::GetEntryType(m_options) != NT_UNASSIGNED;
+ return m_options.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NTSubsystem.cpp b/glass/src/libnt/native/cpp/NTSubsystem.cpp
index b2bdf8c..3078f87 100644
--- a/glass/src/libnt/native/cpp/NTSubsystem.cpp
+++ b/glass/src/libnt/native/cpp/NTSubsystem.cpp
@@ -9,37 +9,30 @@
using namespace glass;
NTSubsystemModel::NTSubsystemModel(std::string_view path)
- : NTSubsystemModel(nt::GetDefaultInstance(), path) {}
+ : NTSubsystemModel(nt::NetworkTableInstance::GetDefault(), path) {}
-NTSubsystemModel::NTSubsystemModel(NT_Inst instance, std::string_view path)
- : m_nt(instance),
- m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
- m_defaultCommand(m_nt.GetEntry(fmt::format("{}/.default", path))),
- m_currentCommand(m_nt.GetEntry(fmt::format("{}/.command", path))) {
- m_nt.AddListener(m_name);
- m_nt.AddListener(m_defaultCommand);
- m_nt.AddListener(m_currentCommand);
+NTSubsystemModel::NTSubsystemModel(nt::NetworkTableInstance inst,
+ std::string_view path)
+ : m_inst{inst},
+ m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
+ m_defaultCommand{
+ inst.GetStringTopic(fmt::format("{}/.default", path)).Subscribe("")},
+ m_currentCommand{
+ inst.GetStringTopic(fmt::format("{}/.command", path)).Subscribe("")} {
}
void NTSubsystemModel::Update() {
- for (auto&& event : m_nt.PollListener()) {
- if (event.entry == m_name) {
- if (event.value && event.value->IsString()) {
- m_nameValue = event.value->GetString();
- }
- } else if (event.entry == m_defaultCommand) {
- if (event.value && event.value->IsString()) {
- m_defaultCommandValue = event.value->GetString();
- }
- } else if (event.entry == m_currentCommand) {
- if (event.value && event.value->IsString()) {
- m_currentCommandValue = event.value->GetString();
- }
- }
+ for (auto&& v : m_name.ReadQueue()) {
+ m_nameValue = std::move(v.value);
+ }
+ for (auto&& v : m_defaultCommand.ReadQueue()) {
+ m_defaultCommandValue = std::move(v.value);
+ }
+ for (auto&& v : m_currentCommand.ReadQueue()) {
+ m_currentCommandValue = std::move(v.value);
}
}
bool NTSubsystemModel::Exists() {
- return m_nt.IsConnected() &&
- nt::GetEntryType(m_defaultCommand) != NT_UNASSIGNED;
+ return m_defaultCommand.Exists();
}
diff --git a/glass/src/libnt/native/cpp/NetworkTables.cpp b/glass/src/libnt/native/cpp/NetworkTables.cpp
index bce2b0a..d368359 100644
--- a/glass/src/libnt/native/cpp/NetworkTables.cpp
+++ b/glass/src/libnt/native/cpp/NetworkTables.cpp
@@ -4,32 +4,61 @@
#include "glass/networktables/NetworkTables.h"
-#include <networktables/NetworkTableValue.h>
-
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <initializer_list>
#include <memory>
+#include <span>
#include <string_view>
#include <vector>
#include <fmt/format.h>
#include <imgui.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/NetworkTableValue.h>
+#include <ntcore_c.h>
#include <ntcore_cpp.h>
+#include <ntcore_cpp_types.h>
+#include <wpi/MessagePack.h>
#include <wpi/SmallString.h>
#include <wpi/SpanExtras.h>
#include <wpi/StringExtras.h>
+#include <wpi/mpack.h>
#include <wpi/raw_ostream.h>
-#include <wpi/span.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
using namespace glass;
+using namespace mpack;
-static std::string BooleanArrayToString(wpi::span<const int> in) {
+namespace {
+enum ShowCategory {
+ ShowPersistent,
+ ShowRetained,
+ ShowTransitory,
+ ShowAll,
+};
+} // namespace
+
+static bool IsVisible(ShowCategory category, bool persistent, bool retained) {
+ switch (category) {
+ case ShowPersistent:
+ return persistent;
+ case ShowRetained:
+ return retained && !persistent;
+ case ShowTransitory:
+ return !retained && !persistent;
+ case ShowAll:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static std::string BooleanArrayToString(std::span<const int> in) {
std::string rv;
wpi::raw_string_ostream os{rv};
os << '[';
@@ -49,11 +78,17 @@
return rv;
}
-static std::string DoubleArrayToString(wpi::span<const double> in) {
+static std::string IntegerArrayToString(std::span<const int64_t> in) {
+ return fmt::format("[{:d}]", fmt::join(in, ","));
+}
+
+template <typename T>
+static std::string FloatArrayToString(std::span<const T> in) {
+ static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);
return fmt::format("[{:.6f}]", fmt::join(in, ","));
}
-static std::string StringArrayToString(wpi::span<const std::string> in) {
+static std::string StringArrayToString(std::span<const std::string> in) {
std::string rv;
wpi::raw_string_ostream os{rv};
os << '[';
@@ -72,91 +107,399 @@
}
NetworkTablesModel::NetworkTablesModel()
- : NetworkTablesModel{nt::GetDefaultInstance()} {}
+ : NetworkTablesModel{nt::NetworkTableInstance::GetDefault()} {}
-NetworkTablesModel::NetworkTablesModel(NT_Inst inst)
- : m_inst{inst}, m_poller{nt::CreateEntryListenerPoller(inst)} {
- nt::AddPolledEntryListener(m_poller, "",
- NT_NOTIFY_LOCAL | NT_NOTIFY_NEW |
- NT_NOTIFY_UPDATE | NT_NOTIFY_DELETE |
- NT_NOTIFY_FLAGS | NT_NOTIFY_IMMEDIATE);
+NetworkTablesModel::NetworkTablesModel(nt::NetworkTableInstance inst)
+ : m_inst{inst}, m_poller{inst} {
+ m_poller.AddListener({{"", "$"}}, nt::EventFlags::kTopic |
+ nt::EventFlags::kValueAll |
+ nt::EventFlags::kImmediate);
}
-NetworkTablesModel::~NetworkTablesModel() {
- nt::DestroyEntryListenerPoller(m_poller);
+NetworkTablesModel::Entry::~Entry() {
+ if (publisher != 0) {
+ nt::Unpublish(publisher);
+ }
}
-NetworkTablesModel::Entry::Entry(nt::EntryNotification&& event)
- : entry{event.entry},
- name{std::move(event.name)},
- value{std::move(event.value)},
- flags{nt::GetEntryFlags(event.entry)} {
- UpdateValue();
+void NetworkTablesModel::Entry::UpdateInfo(nt::TopicInfo&& info_) {
+ info = std::move(info_);
+ properties = info.GetProperties();
+
+ persistent = false;
+ auto it = properties.find("persistent");
+ if (it != properties.end()) {
+ if (auto v = it->get_ptr<const bool*>()) {
+ persistent = *v;
+ }
+ }
+
+ retained = false;
+ it = properties.find("retained");
+ if (it != properties.end()) {
+ if (auto v = it->get_ptr<const bool*>()) {
+ retained = *v;
+ }
+ }
}
-void NetworkTablesModel::Entry::UpdateValue() {
- switch (value->type()) {
+static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
+ mpack_reader_t& r, std::string_view name,
+ int64_t time) {
+ mpack_tag_t tag = mpack_read_tag(&r);
+ switch (mpack_tag_type(&tag)) {
+ case mpack::mpack_type_bool:
+ out->UpdateFromValue(
+ nt::Value::MakeBoolean(mpack_tag_bool_value(&tag), time), name, "");
+ break;
+ case mpack::mpack_type_int:
+ out->UpdateFromValue(
+ nt::Value::MakeInteger(mpack_tag_int_value(&tag), time), name, "");
+ break;
+ case mpack::mpack_type_uint:
+ out->UpdateFromValue(
+ nt::Value::MakeInteger(mpack_tag_uint_value(&tag), time), name, "");
+ break;
+ case mpack::mpack_type_float:
+ out->UpdateFromValue(
+ nt::Value::MakeFloat(mpack_tag_float_value(&tag), time), name, "");
+ break;
+ case mpack::mpack_type_double:
+ out->UpdateFromValue(
+ nt::Value::MakeDouble(mpack_tag_double_value(&tag), time), name, "");
+ break;
+ case mpack::mpack_type_str: {
+ std::string str;
+ mpack_read_str(&r, &tag, &str);
+ out->UpdateFromValue(nt::Value::MakeString(std::move(str), time), name,
+ "");
+ break;
+ }
+ case mpack::mpack_type_bin:
+ // just skip it
+ mpack_skip_bytes(&r, mpack_tag_bin_length(&tag));
+ mpack_done_bin(&r);
+ break;
+ case mpack::mpack_type_array: {
+ if (out->valueChildrenMap) {
+ out->valueChildren.clear();
+ out->valueChildrenMap = false;
+ }
+ out->valueChildren.resize(mpack_tag_array_count(&tag));
+ unsigned int i = 0;
+ for (auto&& child : out->valueChildren) {
+ if (child.name.empty()) {
+ child.name = fmt::format("[{}]", i);
+ child.path = fmt::format("{}{}", name, child.name);
+ }
+ ++i;
+ UpdateMsgpackValueSource(&child, r, child.path, time); // recurse
+ }
+ mpack_done_array(&r);
+ break;
+ }
+ case mpack::mpack_type_map: {
+ if (!out->valueChildrenMap) {
+ out->valueChildren.clear();
+ out->valueChildrenMap = true;
+ }
+ wpi::StringMap<size_t> elems;
+ for (size_t i = 0, size = out->valueChildren.size(); i < size; ++i) {
+ elems[out->valueChildren[i].name] = i;
+ }
+ bool added = false;
+ uint32_t count = mpack_tag_map_count(&tag);
+ for (uint32_t i = 0; i < count; ++i) {
+ std::string key;
+ if (mpack_expect_str(&r, &key) == mpack_ok) {
+ auto it = elems.find(key);
+ if (it != elems.end()) {
+ auto& child = out->valueChildren[it->second];
+ UpdateMsgpackValueSource(&child, r, child.path, time);
+ elems.erase(it);
+ } else {
+ added = true;
+ out->valueChildren.emplace_back();
+ auto& child = out->valueChildren.back();
+ child.name = std::move(key);
+ child.path = fmt::format("{}/{}", name, child.name);
+ UpdateMsgpackValueSource(&child, r, child.path, time);
+ }
+ }
+ }
+ // erase unmatched keys
+ out->valueChildren.erase(
+ std::remove_if(
+ out->valueChildren.begin(), out->valueChildren.end(),
+ [&](const auto& child) { return elems.count(child.name) > 0; }),
+ out->valueChildren.end());
+ if (added) {
+ // sort by name
+ std::sort(out->valueChildren.begin(), out->valueChildren.end(),
+ [](const auto& a, const auto& b) { return a.name < b.name; });
+ }
+ mpack_done_map(&r);
+ break;
+ }
+ default:
+ out->value = {};
+ mpack_done_type(&r, mpack_tag_type(&tag));
+ break;
+ }
+}
+
+static void UpdateJsonValueSource(NetworkTablesModel::ValueSource* out,
+ const wpi::json& j, std::string_view name,
+ int64_t time) {
+ switch (j.type()) {
+ case wpi::json::value_t::object: {
+ if (!out->valueChildrenMap) {
+ out->valueChildren.clear();
+ out->valueChildrenMap = true;
+ }
+ wpi::StringMap<size_t> elems;
+ for (size_t i = 0, size = out->valueChildren.size(); i < size; ++i) {
+ elems[out->valueChildren[i].name] = i;
+ }
+ bool added = false;
+ for (auto&& kv : j.items()) {
+ auto it = elems.find(kv.key());
+ if (it != elems.end()) {
+ auto& child = out->valueChildren[it->second];
+ UpdateJsonValueSource(&child, kv.value(), child.path, time);
+ elems.erase(it);
+ } else {
+ added = true;
+ out->valueChildren.emplace_back();
+ auto& child = out->valueChildren.back();
+ child.name = kv.key();
+ child.path = fmt::format("{}/{}", name, child.name);
+ UpdateJsonValueSource(&child, kv.value(), child.path, time);
+ }
+ }
+ // erase unmatched keys
+ out->valueChildren.erase(
+ std::remove_if(
+ out->valueChildren.begin(), out->valueChildren.end(),
+ [&](const auto& child) { return elems.count(child.name) > 0; }),
+ out->valueChildren.end());
+ if (added) {
+ // sort by name
+ std::sort(out->valueChildren.begin(), out->valueChildren.end(),
+ [](const auto& a, const auto& b) { return a.name < b.name; });
+ }
+ break;
+ }
+ case wpi::json::value_t::array: {
+ if (out->valueChildrenMap) {
+ out->valueChildren.clear();
+ out->valueChildrenMap = false;
+ }
+ out->valueChildren.resize(j.size());
+ unsigned int i = 0;
+ for (auto&& child : out->valueChildren) {
+ if (child.name.empty()) {
+ child.name = fmt::format("[{}]", i);
+ child.path = fmt::format("{}{}", name, child.name);
+ }
+ ++i;
+ UpdateJsonValueSource(&child, j[i], child.path, time); // recurse
+ }
+ break;
+ }
+ case wpi::json::value_t::string:
+ out->UpdateFromValue(
+ nt::Value::MakeString(j.get_ref<const std::string&>(), time), name,
+ "");
+ break;
+ case wpi::json::value_t::boolean:
+ out->UpdateFromValue(nt::Value::MakeBoolean(j.get<bool>(), time), name,
+ "");
+ break;
+ case wpi::json::value_t::number_integer:
+ out->UpdateFromValue(nt::Value::MakeInteger(j.get<int64_t>(), time), name,
+ "");
+ break;
+ case wpi::json::value_t::number_unsigned:
+ out->UpdateFromValue(nt::Value::MakeInteger(j.get<uint64_t>(), time),
+ name, "");
+ break;
+ case wpi::json::value_t::number_float:
+ out->UpdateFromValue(nt::Value::MakeDouble(j.get<double>(), time), name,
+ "");
+ break;
+ default:
+ out->value = {};
+ break;
+ }
+}
+
+void NetworkTablesModel::ValueSource::UpdateFromValue(
+ nt::Value&& v, std::string_view name, std::string_view typeStr) {
+ value = v;
+ switch (value.type()) {
case NT_BOOLEAN:
+ valueChildren.clear();
if (!source) {
source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
}
- source->SetValue(value->GetBoolean() ? 1 : 0);
+ source->SetValue(value.GetBoolean() ? 1 : 0, value.last_change());
source->SetDigital(true);
break;
- case NT_DOUBLE:
+ case NT_INTEGER:
+ valueChildren.clear();
if (!source) {
source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
}
- source->SetValue(value->GetDouble());
+ source->SetValue(value.GetInteger(), value.last_change());
+ source->SetDigital(false);
+ break;
+ case NT_FLOAT:
+ valueChildren.clear();
+ if (!source) {
+ source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
+ }
+ source->SetValue(value.GetFloat(), value.last_change());
+ source->SetDigital(false);
+ break;
+ case NT_DOUBLE:
+ valueChildren.clear();
+ if (!source) {
+ source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
+ }
+ source->SetValue(value.GetDouble(), value.last_change());
source->SetDigital(false);
break;
case NT_BOOLEAN_ARRAY:
- valueStr = BooleanArrayToString(value->GetBooleanArray());
+ valueChildren.clear();
+ valueStr = BooleanArrayToString(value.GetBooleanArray());
+ break;
+ case NT_INTEGER_ARRAY:
+ valueChildren.clear();
+ valueStr = IntegerArrayToString(value.GetIntegerArray());
+ break;
+ case NT_FLOAT_ARRAY:
+ valueChildren.clear();
+ valueStr = FloatArrayToString(value.GetFloatArray());
break;
case NT_DOUBLE_ARRAY:
- valueStr = DoubleArrayToString(value->GetDoubleArray());
+ valueChildren.clear();
+ valueStr = FloatArrayToString(value.GetDoubleArray());
break;
case NT_STRING_ARRAY:
- valueStr = StringArrayToString(value->GetStringArray());
+ valueChildren.clear();
+ valueStr = StringArrayToString(value.GetStringArray());
+ break;
+ case NT_STRING:
+ if (typeStr == "json") {
+ try {
+ UpdateJsonValueSource(this, wpi::json::parse(value.GetString()), name,
+ value.last_change());
+ } catch (wpi::json::exception&) {
+ // ignore
+ }
+ } else {
+ valueChildren.clear();
+ }
+ break;
+ case NT_RAW:
+ if (typeStr == "msgpack") {
+ mpack_reader_t r;
+ mpack_reader_init_data(&r, value.GetRaw());
+ UpdateMsgpackValueSource(this, r, name, value.last_change());
+
+ mpack_reader_destroy(&r);
+ } else {
+ valueChildren.clear();
+ }
break;
default:
+ valueChildren.clear();
break;
}
}
void NetworkTablesModel::Update() {
- bool timedOut = false;
bool updateTree = false;
- for (auto&& event : nt::PollEntryListener(m_poller, 0, &timedOut)) {
- auto& entry = m_entries[event.entry];
- if (event.flags & NT_NOTIFY_NEW) {
- if (!entry) {
- entry = std::make_unique<Entry>(std::move(event));
- m_sortedEntries.emplace_back(entry.get());
+ for (auto&& event : m_poller.ReadQueue()) {
+ if (auto info = event.GetTopicInfo()) {
+ auto& entry = m_entries[info->topic];
+ if (event.flags & nt::EventFlags::kPublish) {
+ if (!entry) {
+ entry = std::make_unique<Entry>();
+ m_sortedEntries.emplace_back(entry.get());
+ updateTree = true;
+ }
+ }
+ if (event.flags & nt::EventFlags::kUnpublish) {
+ // meta topic handling
+ if (wpi::starts_with(info->name, '$')) {
+ // meta topic handling
+ if (info->name == "$clients") {
+ m_clients.clear();
+ } else if (info->name == "$serverpub") {
+ m_server.publishers.clear();
+ } else if (info->name == "$serversub") {
+ m_server.subscribers.clear();
+ } else if (wpi::starts_with(info->name, "$clientpub$")) {
+ auto it = m_clients.find(wpi::drop_front(info->name, 11));
+ if (it != m_clients.end()) {
+ it->second.publishers.clear();
+ }
+ } else if (wpi::starts_with(info->name, "$clientsub$")) {
+ auto it = m_clients.find(wpi::drop_front(info->name, 11));
+ if (it != m_clients.end()) {
+ it->second.subscribers.clear();
+ }
+ }
+ }
+ auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
+ entry.get());
+ // will be removed completely below
+ if (it != m_sortedEntries.end()) {
+ *it = nullptr;
+ }
+ m_entries.erase(info->topic);
updateTree = true;
continue;
}
- }
- if (!entry) {
- continue;
- }
- if (event.flags & NT_NOTIFY_DELETE) {
- auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
- entry.get());
- // will be removed completely below
- if (it != m_sortedEntries.end()) {
- *it = nullptr;
+ if (event.flags & nt::EventFlags::kProperties) {
+ updateTree = true;
}
- m_entries.erase(event.entry);
- updateTree = true;
- continue;
- }
- if (event.flags & NT_NOTIFY_UPDATE) {
- entry->value = std::move(event.value);
- entry->UpdateValue();
- }
- if (event.flags & NT_NOTIFY_FLAGS) {
- entry->flags = nt::GetEntryFlags(event.entry);
+ if (entry) {
+ entry->UpdateTopic(std::move(event));
+ }
+ } else if (auto valueData = event.GetValueEventData()) {
+ auto& entry = m_entries[valueData->topic];
+ if (entry) {
+ entry->UpdateFromValue(std::move(valueData->value), entry->info.name,
+ entry->info.type_str);
+ if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() &&
+ entry->info.type_str == "msgpack") {
+ // meta topic handling
+ if (entry->info.name == "$clients") {
+ // need to remove deleted entries as UpdateClients() uses GetEntry()
+ if (updateTree) {
+ std::erase(m_sortedEntries, nullptr);
+ }
+ UpdateClients(entry->value.GetRaw());
+ } else if (entry->info.name == "$serverpub") {
+ m_server.UpdatePublishers(entry->value.GetRaw());
+ } else if (entry->info.name == "$serversub") {
+ m_server.UpdateSubscribers(entry->value.GetRaw());
+ } else if (wpi::starts_with(entry->info.name, "$clientpub$")) {
+ auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
+ if (it != m_clients.end()) {
+ it->second.UpdatePublishers(entry->value.GetRaw());
+ }
+ } else if (wpi::starts_with(entry->info.name, "$clientsub$")) {
+ auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
+ if (it != m_clients.end()) {
+ it->second.UpdateSubscribers(entry->value.GetRaw());
+ }
+ }
+ }
+ }
}
}
@@ -166,20 +509,34 @@
}
// remove deleted entries
- m_sortedEntries.erase(
- std::remove(m_sortedEntries.begin(), m_sortedEntries.end(), nullptr),
- m_sortedEntries.end());
+ std::erase(m_sortedEntries, nullptr);
+ RebuildTree();
+}
+
+void NetworkTablesModel::RebuildTree() {
// sort by name
- std::sort(m_sortedEntries.begin(), m_sortedEntries.end(),
- [](const auto& a, const auto& b) { return a->name < b->name; });
+ std::sort(
+ m_sortedEntries.begin(), m_sortedEntries.end(),
+ [](const auto& a, const auto& b) { return a->info.name < b->info.name; });
- // rebuild tree
- m_root.clear();
+ RebuildTreeImpl(&m_root, ShowAll);
+ RebuildTreeImpl(&m_persistentRoot, ShowPersistent);
+ RebuildTreeImpl(&m_retainedRoot, ShowRetained);
+ RebuildTreeImpl(&m_transitoryRoot, ShowTransitory);
+}
+
+void NetworkTablesModel::RebuildTreeImpl(std::vector<TreeNode>* tree,
+ int category) {
+ tree->clear();
wpi::SmallVector<std::string_view, 16> parts;
for (auto& entry : m_sortedEntries) {
+ if (!IsVisible(static_cast<ShowCategory>(category), entry->persistent,
+ entry->retained)) {
+ continue;
+ }
parts.clear();
- wpi::split(entry->name, parts, '/', -1, false);
+ wpi::split(entry->info.name, parts, '/', -1, false);
// ignore a raw "/" key
if (parts.empty()) {
@@ -187,8 +544,8 @@
}
// get to leaf
- auto nodes = &m_root;
- for (auto part : wpi::drop_back(wpi::span{parts.begin(), parts.end()})) {
+ auto nodes = tree;
+ for (auto part : wpi::drop_back(std::span{parts.begin(), parts.end()})) {
auto it =
std::find_if(nodes->begin(), nodes->end(),
[&](const auto& node) { return node.name == part; });
@@ -196,9 +553,10 @@
nodes->emplace_back(part);
// path is from the beginning of the string to the end of the current
// part; this works because part is a reference to the internals of
- // entry->name
+ // entry->info.name
nodes->back().path.assign(
- entry->name.data(), part.data() + part.size() - entry->name.data());
+ entry->info.name.data(),
+ part.data() + part.size() - entry->info.name.data());
it = nodes->end() - 1;
}
nodes = &it->children;
@@ -217,14 +575,97 @@
}
bool NetworkTablesModel::Exists() {
- return nt::IsConnected(m_inst);
+ return true;
}
-static std::shared_ptr<nt::Value> StringToBooleanArray(std::string_view in) {
+NetworkTablesModel::Entry* NetworkTablesModel::GetEntry(std::string_view name) {
+ auto entryIt = std::lower_bound(
+ m_sortedEntries.begin(), m_sortedEntries.end(), name,
+ [](auto&& entry, auto&& name) { return entry->info.name < name; });
+ if (entryIt == m_sortedEntries.end() || (*entryIt)->info.name != name) {
+ return nullptr;
+ }
+ return *entryIt;
+}
+
+NetworkTablesModel::Entry* NetworkTablesModel::AddEntry(NT_Topic topic) {
+ auto& entry = m_entries[topic];
+ if (!entry) {
+ entry = std::make_unique<Entry>();
+ entry->info = nt::GetTopicInfo(topic);
+ entry->properties = entry->info.GetProperties();
+ m_sortedEntries.emplace_back(entry.get());
+ }
+ RebuildTree();
+ return entry.get();
+}
+
+NetworkTablesModel::Client::Subscriber::Subscriber(
+ nt::meta::ClientSubscriber&& oth)
+ : ClientSubscriber{std::move(oth)},
+ topicsStr{StringArrayToString(topics)} {}
+
+void NetworkTablesModel::Client::UpdatePublishers(
+ std::span<const uint8_t> data) {
+ if (auto pubs = nt::meta::DecodeClientPublishers(data)) {
+ publishers = std::move(*pubs);
+ } else {
+ fmt::print(stderr, "Failed to update publishers\n");
+ }
+}
+
+void NetworkTablesModel::Client::UpdateSubscribers(
+ std::span<const uint8_t> data) {
+ if (auto subs = nt::meta::DecodeClientSubscribers(data)) {
+ subscribers.clear();
+ subscribers.reserve(subs->size());
+ for (auto&& sub : *subs) {
+ subscribers.emplace_back(std::move(sub));
+ }
+ } else {
+ fmt::print(stderr, "Failed to update subscribers\n");
+ }
+}
+
+void NetworkTablesModel::UpdateClients(std::span<const uint8_t> data) {
+ auto clientsArr = nt::meta::DecodeClients(data);
+ if (!clientsArr) {
+ return;
+ }
+
+ // we need to create a new map so deletions are reflected
+ std::map<std::string, Client, std::less<>> newClients;
+ for (auto&& client : *clientsArr) {
+ auto& newClient = newClients[client.id];
+ newClient = std::move(client);
+ auto it = m_clients.find(newClient.id);
+ if (it != m_clients.end()) {
+ // transfer from existing
+ newClient.publishers = std::move(it->second.publishers);
+ newClient.subscribers = std::move(it->second.subscribers);
+ } else {
+ // initially populate
+ if (Entry* entry = GetEntry(fmt::format("$clientpub${}", newClient.id))) {
+ if (entry->value.IsRaw() && entry->info.type_str == "msgpack") {
+ newClient.UpdatePublishers(entry->value.GetRaw());
+ }
+ }
+ if (Entry* entry = GetEntry(fmt::format("$clientsub${}", newClient.id))) {
+ if (entry->value.IsRaw() && entry->info.type_str == "msgpack") {
+ newClient.UpdateSubscribers(entry->value.GetRaw());
+ }
+ }
+ }
+ }
+
+ // replace map
+ m_clients = std::move(newClients);
+}
+
+static bool StringToBooleanArray(std::string_view in, std::vector<int>* out) {
in = wpi::trim(in);
if (in.empty()) {
- return nt::NetworkTableValue::MakeBooleanArray(
- std::initializer_list<bool>{});
+ return false;
}
if (in.front() == '[') {
in.remove_prefix(1);
@@ -235,30 +676,29 @@
in = wpi::trim(in);
wpi::SmallVector<std::string_view, 16> inSplit;
- wpi::SmallVector<int, 16> out;
wpi::split(in, inSplit, ',', -1, false);
for (auto val : inSplit) {
val = wpi::trim(val);
if (wpi::equals_lower(val, "true")) {
- out.emplace_back(1);
+ out->emplace_back(1);
} else if (wpi::equals_lower(val, "false")) {
- out.emplace_back(0);
+ out->emplace_back(0);
} else {
fmt::print(stderr,
"GUI: NetworkTables: Could not understand value '{}'\n", val);
- return nullptr;
+ return false;
}
}
- return nt::NetworkTableValue::MakeBooleanArray(out);
+ return true;
}
-static std::shared_ptr<nt::Value> StringToDoubleArray(std::string_view in) {
+static bool StringToIntegerArray(std::string_view in,
+ std::vector<int64_t>* out) {
in = wpi::trim(in);
if (in.empty()) {
- return nt::NetworkTableValue::MakeDoubleArray(
- std::initializer_list<double>{});
+ return false;
}
if (in.front() == '[') {
in.remove_prefix(1);
@@ -269,75 +709,27 @@
in = wpi::trim(in);
wpi::SmallVector<std::string_view, 16> inSplit;
- wpi::SmallVector<double, 16> out;
wpi::split(in, inSplit, ',', -1, false);
for (auto val : inSplit) {
- if (auto num = wpi::parse_float<double>(wpi::trim(val))) {
- out.emplace_back(num.value());
+ if (auto num = wpi::parse_integer<int64_t>(wpi::trim(val), 0)) {
+ out->emplace_back(num.value());
} else {
fmt::print(stderr,
"GUI: NetworkTables: Could not understand value '{}'\n", val);
- return nullptr;
+ return false;
}
}
- return nt::NetworkTableValue::MakeDoubleArray(out);
+ return true;
}
-static int fromxdigit(char ch) {
- if (ch >= 'a' && ch <= 'f') {
- return (ch - 'a' + 10);
- } else if (ch >= 'A' && ch <= 'F') {
- return (ch - 'A' + 10);
- } else {
- return ch - '0';
- }
-}
-
-static std::string_view UnescapeString(std::string_view source,
- wpi::SmallVectorImpl<char>& buf) {
- assert(source.size() >= 2 && source.front() == '"' && source.back() == '"');
- buf.clear();
- buf.reserve(source.size() - 2);
- for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
- if (*s != '\\') {
- buf.push_back(*s);
- continue;
- }
- switch (*++s) {
- case 't':
- buf.push_back('\t');
- break;
- case 'n':
- buf.push_back('\n');
- break;
- case 'x': {
- if (!isxdigit(*(s + 1))) {
- buf.push_back('x'); // treat it like a unknown escape
- break;
- }
- int ch = fromxdigit(*++s);
- if (std::isxdigit(*(s + 1))) {
- ch <<= 4;
- ch |= fromxdigit(*++s);
- }
- buf.push_back(static_cast<char>(ch));
- break;
- }
- default:
- buf.push_back(*s);
- break;
- }
- }
- return {buf.data(), buf.size()};
-}
-
-static std::shared_ptr<nt::Value> StringToStringArray(std::string_view in) {
+template <typename T>
+static bool StringToFloatArray(std::string_view in, std::vector<T>* out) {
+ static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);
in = wpi::trim(in);
if (in.empty()) {
- return nt::NetworkTableValue::MakeStringArray(
- std::initializer_list<std::string>{});
+ return false;
}
if (in.front() == '[') {
in.remove_prefix(1);
@@ -348,7 +740,36 @@
in = wpi::trim(in);
wpi::SmallVector<std::string_view, 16> inSplit;
- std::vector<std::string> out;
+
+ wpi::split(in, inSplit, ',', -1, false);
+ for (auto val : inSplit) {
+ if (auto num = wpi::parse_float<T>(wpi::trim(val))) {
+ out->emplace_back(num.value());
+ } else {
+ fmt::print(stderr,
+ "GUI: NetworkTables: Could not understand value '{}'\n", val);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool StringToStringArray(std::string_view in,
+ std::vector<std::string>* out) {
+ in = wpi::trim(in);
+ if (in.empty()) {
+ return false;
+ }
+ if (in.front() == '[') {
+ in.remove_prefix(1);
+ }
+ if (in.back() == ']') {
+ in.remove_suffix(1);
+ }
+ in = wpi::trim(in);
+
+ wpi::SmallVector<std::string_view, 16> inSplit;
wpi::SmallString<32> buf;
wpi::split(in, inSplit, ',', -1, false);
@@ -360,49 +781,81 @@
if (val.front() != '"' || val.back() != '"') {
fmt::print(stderr,
"GUI: NetworkTables: Could not understand value '{}'\n", val);
- return nullptr;
+ return false;
}
- out.emplace_back(UnescapeString(val, buf));
+ val.remove_prefix(1);
+ val.remove_suffix(1);
+ out->emplace_back(wpi::UnescapeCString(val, buf).first);
}
- return nt::NetworkTableValue::MakeStringArray(std::move(out));
+ return true;
}
-static void EmitEntryValueReadonly(NetworkTablesModel::Entry& entry) {
+static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
+ const char* typeStr,
+ NetworkTablesFlags flags) {
auto& val = entry.value;
if (!val) {
return;
}
- switch (val->type()) {
+ switch (val.type()) {
case NT_BOOLEAN:
- ImGui::LabelText("boolean", "%s", val->GetBoolean() ? "true" : "false");
+ ImGui::LabelText(typeStr ? typeStr : "boolean", "%s",
+ val.GetBoolean() ? "true" : "false");
break;
- case NT_DOUBLE:
- ImGui::LabelText("double", "%.6f", val->GetDouble());
+ case NT_INTEGER:
+ ImGui::LabelText(typeStr ? typeStr : "int", "%" PRId64, val.GetInteger());
break;
+ case NT_FLOAT:
+ ImGui::LabelText(typeStr ? typeStr : "double", "%.6f", val.GetFloat());
+ break;
+ case NT_DOUBLE: {
+ unsigned char precision = (flags & NetworkTablesFlags_Precision) >>
+ kNetworkTablesFlags_PrecisionBitShift;
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+ ImGui::LabelText(typeStr ? typeStr : "double",
+ fmt::format("%.{}f", precision).c_str(),
+ val.GetDouble());
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ break;
+ }
case NT_STRING: {
// GetString() comes from a std::string, so it's null terminated
- ImGui::LabelText("string", "%s", val->GetString().data());
+ ImGui::LabelText(typeStr ? typeStr : "string", "%s",
+ val.GetString().data());
break;
}
case NT_BOOLEAN_ARRAY:
- ImGui::LabelText("boolean[]", "%s", entry.valueStr.c_str());
+ ImGui::LabelText(typeStr ? typeStr : "boolean[]", "%s",
+ entry.valueStr.c_str());
+ break;
+ case NT_INTEGER_ARRAY:
+ ImGui::LabelText(typeStr ? typeStr : "int[]", "%s",
+ entry.valueStr.c_str());
+ break;
+ case NT_FLOAT_ARRAY:
+ ImGui::LabelText(typeStr ? typeStr : "float[]", "%s",
+ entry.valueStr.c_str());
break;
case NT_DOUBLE_ARRAY:
- ImGui::LabelText("double[]", "%s", entry.valueStr.c_str());
+ ImGui::LabelText(typeStr ? typeStr : "double[]", "%s",
+ entry.valueStr.c_str());
break;
case NT_STRING_ARRAY:
- ImGui::LabelText("string[]", "%s", entry.valueStr.c_str());
+ ImGui::LabelText(typeStr ? typeStr : "string[]", "%s",
+ entry.valueStr.c_str());
break;
case NT_RAW:
- ImGui::LabelText("raw", "[...]");
- break;
- case NT_RPC:
- ImGui::LabelText("rpc", "[...]");
+ ImGui::LabelText(typeStr ? typeStr : "raw", "[...]");
break;
default:
- ImGui::LabelText("other", "?");
+ ImGui::LabelText(typeStr ? typeStr : "other", "?");
break;
}
}
@@ -417,85 +870,188 @@
return textBuffer;
}
-static void EmitEntryValueEditable(NetworkTablesModel::Entry& entry) {
+static void EmitEntryValueEditable(NetworkTablesModel::Entry& entry,
+ NetworkTablesFlags flags) {
auto& val = entry.value;
if (!val) {
return;
}
- ImGui::PushID(entry.name.c_str());
- switch (val->type()) {
+ const char* typeStr =
+ entry.info.type_str.empty() ? nullptr : entry.info.type_str.c_str();
+ ImGui::PushID(entry.info.name.c_str());
+ switch (val.type()) {
case NT_BOOLEAN: {
static const char* boolOptions[] = {"false", "true"};
- int v = val->GetBoolean() ? 1 : 0;
- if (ImGui::Combo("boolean", &v, boolOptions, 2)) {
- nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeBoolean(v));
+ int v = val.GetBoolean() ? 1 : 0;
+ if (ImGui::Combo(typeStr ? typeStr : "boolean", &v, boolOptions, 2)) {
+ if (entry.publisher == 0) {
+ entry.publisher =
+ nt::Publish(entry.info.topic, NT_BOOLEAN, "boolean");
+ }
+ nt::SetBoolean(entry.publisher, v);
+ }
+ break;
+ }
+ case NT_INTEGER: {
+ int64_t v = val.GetInteger();
+ if (ImGui::InputScalar(typeStr ? typeStr : "int", ImGuiDataType_S64, &v,
+ nullptr, nullptr, nullptr,
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ if (entry.publisher == 0) {
+ entry.publisher = nt::Publish(entry.info.topic, NT_INTEGER, "int");
+ }
+ nt::SetInteger(entry.publisher, v);
+ }
+ break;
+ }
+ case NT_FLOAT: {
+ float v = val.GetFloat();
+ if (ImGui::InputFloat(typeStr ? typeStr : "float", &v, 0, 0, "%.6f",
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ if (entry.publisher == 0) {
+ entry.publisher = nt::Publish(entry.info.topic, NT_FLOAT, "float");
+ }
+ nt::SetFloat(entry.publisher, v);
}
break;
}
case NT_DOUBLE: {
- double v = val->GetDouble();
- if (ImGui::InputDouble("double", &v, 0, 0, "%.6f",
+ double v = val.GetDouble();
+ unsigned char precision = (flags & NetworkTablesFlags_Precision) >>
+ kNetworkTablesFlags_PrecisionBitShift;
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+ if (ImGui::InputDouble(typeStr ? typeStr : "double", &v, 0, 0,
+ fmt::format("%.{}f", precision).c_str(),
ImGuiInputTextFlags_EnterReturnsTrue)) {
- nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeDouble(v));
+ if (entry.publisher == 0) {
+ entry.publisher = nt::Publish(entry.info.topic, NT_DOUBLE, "double");
+ }
+ nt::SetDouble(entry.publisher, v);
}
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
break;
}
case NT_STRING: {
- char* v = GetTextBuffer(val->GetString());
- if (ImGui::InputText("string", v, kTextBufferSize,
+ char* v = GetTextBuffer(val.GetString());
+ if (ImGui::InputText(typeStr ? typeStr : "string", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
- nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeString(v));
+ if (entry.publisher == 0) {
+ entry.publisher = nt::Publish(entry.info.topic, NT_STRING, "string");
+ }
+ nt::SetString(entry.publisher, v);
}
break;
}
case NT_BOOLEAN_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
- if (ImGui::InputText("boolean[]", v, kTextBufferSize,
+ if (ImGui::InputText(typeStr ? typeStr : "boolean[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
- if (auto outv = StringToBooleanArray(v)) {
- nt::SetEntryValue(entry.entry, std::move(outv));
+ std::vector<int> outv;
+ if (StringToBooleanArray(v, &outv)) {
+ if (entry.publisher == 0) {
+ entry.publisher =
+ nt::Publish(entry.info.topic, NT_BOOLEAN_ARRAY, "boolean[]");
+ }
+ nt::SetBooleanArray(entry.publisher, outv);
+ }
+ }
+ break;
+ }
+ case NT_INTEGER_ARRAY: {
+ char* v = GetTextBuffer(entry.valueStr);
+ if (ImGui::InputText(typeStr ? typeStr : "int[]", v, kTextBufferSize,
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ std::vector<int64_t> outv;
+ if (StringToIntegerArray(v, &outv)) {
+ if (entry.publisher == 0) {
+ entry.publisher =
+ nt::Publish(entry.info.topic, NT_INTEGER_ARRAY, "int[]");
+ }
+ nt::SetIntegerArray(entry.publisher, outv);
+ }
+ }
+ break;
+ }
+ case NT_FLOAT_ARRAY: {
+ char* v = GetTextBuffer(entry.valueStr);
+ if (ImGui::InputText(typeStr ? typeStr : "float[]", v, kTextBufferSize,
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ std::vector<float> outv;
+ if (StringToFloatArray(v, &outv)) {
+ if (entry.publisher == 0) {
+ entry.publisher =
+ nt::Publish(entry.info.topic, NT_DOUBLE_ARRAY, "float[]");
+ }
+ nt::SetFloatArray(entry.publisher, outv);
}
}
break;
}
case NT_DOUBLE_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
- if (ImGui::InputText("double[]", v, kTextBufferSize,
+ if (ImGui::InputText(typeStr ? typeStr : "double[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
- if (auto outv = StringToDoubleArray(v)) {
- nt::SetEntryValue(entry.entry, std::move(outv));
+ std::vector<double> outv;
+ if (StringToFloatArray(v, &outv)) {
+ if (entry.publisher == 0) {
+ entry.publisher =
+ nt::Publish(entry.info.topic, NT_DOUBLE_ARRAY, "double[]");
+ }
+ nt::SetDoubleArray(entry.publisher, outv);
}
}
break;
}
case NT_STRING_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
- if (ImGui::InputText("string[]", v, kTextBufferSize,
+ if (ImGui::InputText(typeStr ? typeStr : "string[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
- if (auto outv = StringToStringArray(v)) {
- nt::SetEntryValue(entry.entry, std::move(outv));
+ std::vector<std::string> outv;
+ if (StringToStringArray(v, &outv)) {
+ if (entry.publisher == 0) {
+ entry.publisher =
+ nt::Publish(entry.info.topic, NT_STRING_ARRAY, "string[]");
+ }
+ nt::SetStringArray(entry.publisher, outv);
}
}
break;
}
case NT_RAW:
- ImGui::LabelText("raw", "[...]");
+ ImGui::LabelText(typeStr ? typeStr : "raw",
+ val.GetRaw().empty() ? "[]" : "[...]");
break;
case NT_RPC:
- ImGui::LabelText("rpc", "[...]");
+ ImGui::LabelText(typeStr ? typeStr : "rpc", "[...]");
break;
default:
- ImGui::LabelText("other", "?");
+ ImGui::LabelText(typeStr ? typeStr : "other", "?");
break;
}
ImGui::PopID();
}
-static void EmitParentContextMenu(const std::string& path,
+static void CreateTopicMenuItem(NetworkTablesModel* model,
+ std::string_view path, NT_Type type,
+ const char* typeStr, bool enabled) {
+ if (ImGui::MenuItem(typeStr, nullptr, false, enabled)) {
+ auto entry =
+ model->AddEntry(nt::GetTopic(model->GetInstance().GetHandle(), path));
+ if (entry->publisher == 0) {
+ entry->publisher = nt::Publish(entry->info.topic, type, typeStr);
+ }
+ }
+}
+
+static void EmitParentContextMenu(NetworkTablesModel* model,
+ const std::string& path,
NetworkTablesFlags flags) {
- // Workaround https://github.com/ocornut/imgui/issues/331
- bool openWarningPopup = false;
static char nameBuffer[kTextBufferSize];
if (ImGui::BeginPopupContextItem(path.c_str())) {
ImGui::Text("%s", path.c_str());
@@ -517,216 +1073,367 @@
ImGui::Text("Adding: %s", fullNewPath.c_str());
ImGui::Separator();
- auto entry = nt::GetEntry(nt::GetDefaultInstance(), fullNewPath);
+ auto entry = model->GetEntry(fullNewPath);
+ bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
nameBuffer[0] != '\0') &&
- nt::GetEntryType(entry) == NT_Type::NT_UNASSIGNED;
- if (ImGui::MenuItem("string", nullptr, false, enabled)) {
- if (!nt::SetEntryValue(entry, nt::Value::MakeString(""))) {
- openWarningPopup = true;
- }
- }
- if (ImGui::MenuItem("double", nullptr, false, enabled)) {
- if (!nt::SetEntryValue(entry, nt::Value::MakeDouble(0.0))) {
- openWarningPopup = true;
- }
- }
- if (ImGui::MenuItem("boolean", nullptr, false, enabled)) {
- if (!nt::SetEntryValue(entry, nt::Value::MakeBoolean(false))) {
- openWarningPopup = true;
- }
- }
- if (ImGui::MenuItem("string[]", nullptr, false, enabled)) {
- if (!nt::SetEntryValue(entry, nt::Value::MakeStringArray({""}))) {
- openWarningPopup = true;
- }
- }
- if (ImGui::MenuItem("double[]", nullptr, false, enabled)) {
- if (!nt::SetEntryValue(entry, nt::Value::MakeDoubleArray({0.0}))) {
- openWarningPopup = true;
- }
- }
- if (ImGui::MenuItem("boolean[]", nullptr, false, enabled)) {
- if (!nt::SetEntryValue(entry, nt::Value::MakeBooleanArray({false}))) {
- openWarningPopup = true;
- }
- }
+ !exists;
+
+ CreateTopicMenuItem(model, fullNewPath, NT_STRING, "string", enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_INTEGER, "int", enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_FLOAT, "float", enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE, "double", enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN, "boolean", enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_STRING_ARRAY, "string[]",
+ enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_INTEGER_ARRAY, "int[]",
+ enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_FLOAT_ARRAY, "float[]",
+ enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE_ARRAY, "double[]",
+ enabled);
+ CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN_ARRAY, "boolean[]",
+ enabled);
ImGui::EndMenu();
}
- ImGui::Separator();
- if (ImGui::MenuItem("Remove All")) {
- for (auto&& entry : nt::GetEntries(nt::GetDefaultInstance(), path, 0)) {
- nt::DeleteEntry(entry);
- }
- }
- ImGui::EndPopup();
- }
-
- // Workaround https://github.com/ocornut/imgui/issues/331
- if (openWarningPopup) {
- ImGui::OpenPopup("Value exists");
- }
- if (ImGui::BeginPopupModal("Value exists", nullptr,
- ImGuiWindowFlags_AlwaysAutoResize)) {
- ImGui::Text("The provided name %s already exists in the tree!", nameBuffer);
- ImGui::Separator();
-
- if (ImGui::Button("OK", ImVec2(120, 0))) {
- ImGui::CloseCurrentPopup();
- }
- ImGui::SetItemDefaultFocus();
ImGui::EndPopup();
}
}
-static void EmitEntry(NetworkTablesModel::Entry& entry, const char* name,
- NetworkTablesFlags flags) {
- if (entry.source) {
+static void EmitValueName(DataSource* source, const char* name,
+ const char* path) {
+ if (source) {
ImGui::Selectable(name);
- entry.source->EmitDrag();
+ source->EmitDrag();
} else {
- ImGui::Text("%s", name);
+ ImGui::TextUnformatted(name);
}
- if (ImGui::BeginPopupContextItem(entry.name.c_str())) {
- ImGui::Text("%s", entry.name.c_str());
- ImGui::Separator();
- if (ImGui::MenuItem("Remove")) {
- nt::DeleteEntry(entry.entry);
- }
+ if (ImGui::BeginPopupContextItem(path)) {
+ ImGui::TextUnformatted(path);
ImGui::EndPopup();
}
- ImGui::NextColumn();
+}
- if (flags & NetworkTablesFlags_ReadOnly) {
- EmitEntryValueReadonly(entry);
- } else {
- EmitEntryValueEditable(entry);
- }
- ImGui::NextColumn();
-
- if (flags & NetworkTablesFlags_ShowFlags) {
- if ((entry.flags & NT_PERSISTENT) != 0) {
- ImGui::Text("Persistent");
- } else if (entry.flags != 0) {
- ImGui::Text("%02x", entry.flags);
+static void EmitValueTree(
+ const std::vector<NetworkTablesModel::EntryValueTreeNode>& children,
+ NetworkTablesFlags flags) {
+ for (auto&& child : children) {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ EmitValueName(child.source.get(), child.name.c_str(), child.path.c_str());
+ ImGui::TableNextColumn();
+ if (!child.valueChildren.empty()) {
+ char label[64];
+ std::snprintf(label, sizeof(label),
+ child.valueChildrenMap ? "{...}##v_%s" : "[...]##v_%s",
+ child.name.c_str());
+ if (TreeNodeEx(label, ImGuiTreeNodeFlags_SpanFullWidth)) {
+ EmitValueTree(child.valueChildren, flags);
+ TreePop();
+ }
+ } else {
+ EmitEntryValueReadonly(child, nullptr, flags);
}
- ImGui::NextColumn();
+ }
+}
+
+static void EmitEntry(NetworkTablesModel* model,
+ NetworkTablesModel::Entry& entry, const char* name,
+ NetworkTablesFlags flags, ShowCategory category) {
+ if (!IsVisible(category, entry.persistent, entry.retained)) {
+ return;
+ }
+
+ bool valueChildrenOpen = false;
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ EmitValueName(entry.source.get(), name, entry.info.name.c_str());
+
+ ImGui::TableNextColumn();
+ if (!entry.valueChildren.empty()) {
+ auto pos = ImGui::GetCursorPos();
+ char label[64];
+ std::snprintf(label, sizeof(label),
+ entry.valueChildrenMap ? "{...}##v_%s" : "[...]##v_%s",
+ entry.info.name.c_str());
+ valueChildrenOpen =
+ TreeNodeEx(label, ImGuiTreeNodeFlags_SpanFullWidth |
+ ImGuiTreeNodeFlags_AllowItemOverlap);
+ // make it look like a normal label w/type
+ ImGui::SetCursorPos(pos);
+ ImGui::LabelText(entry.info.type_str.c_str(), "%s", "");
+ } else if (flags & NetworkTablesFlags_ReadOnly) {
+ EmitEntryValueReadonly(
+ entry,
+ entry.info.type_str.empty() ? nullptr : entry.info.type_str.c_str(),
+ flags);
+ } else {
+ EmitEntryValueEditable(entry, flags);
+ }
+
+ if (flags & NetworkTablesFlags_ShowProperties) {
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", entry.info.properties.c_str());
+ if (ImGui::BeginPopupContextItem(entry.info.name.c_str())) {
+ if (ImGui::Checkbox("persistent", &entry.persistent)) {
+ nt::SetTopicPersistent(entry.info.topic, entry.persistent);
+ }
+ if (ImGui::Checkbox("retained", &entry.retained)) {
+ if (entry.retained) {
+ nt::SetTopicProperty(entry.info.topic, "retained", true);
+ } else {
+ nt::DeleteTopicProperty(entry.info.topic, "retained");
+ }
+ }
+ ImGui::EndPopup();
+ }
}
if (flags & NetworkTablesFlags_ShowTimestamp) {
+ ImGui::TableNextColumn();
if (entry.value) {
- ImGui::Text("%f", (entry.value->last_change() * 1.0e-6) -
+ ImGui::Text("%f", (entry.value.last_change() * 1.0e-6) -
(GetZeroTime() * 1.0e-6));
} else {
ImGui::TextUnformatted("");
}
- ImGui::NextColumn();
}
- ImGui::Separator();
+
+ if (flags & NetworkTablesFlags_ShowServerTimestamp) {
+ ImGui::TableNextColumn();
+ if (entry.value && entry.value.server_time() != 0) {
+ if (entry.value.server_time() == 1) {
+ ImGui::TextUnformatted("---");
+ } else {
+ ImGui::Text("%f", entry.value.server_time() * 1.0e-6);
+ }
+ } else {
+ ImGui::TextUnformatted("");
+ }
+ }
+
+ if (valueChildrenOpen) {
+ EmitValueTree(entry.valueChildren, flags);
+ TreePop();
+ }
}
-static void EmitTree(const std::vector<NetworkTablesModel::TreeNode>& tree,
- NetworkTablesFlags flags) {
+static void EmitTree(NetworkTablesModel* model,
+ const std::vector<NetworkTablesModel::TreeNode>& tree,
+ NetworkTablesFlags flags, ShowCategory category,
+ bool root) {
for (auto&& node : tree) {
+ if (root && (flags & NetworkTablesFlags_ShowSpecial) == 0 &&
+ wpi::starts_with(node.name, '$')) {
+ continue;
+ }
if (node.entry) {
- EmitEntry(*node.entry, node.name.c_str(), flags);
+ EmitEntry(model, *node.entry, node.name.c_str(), flags, category);
}
if (!node.children.empty()) {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
bool open =
TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
- EmitParentContextMenu(node.path, flags);
- ImGui::NextColumn();
- ImGui::NextColumn();
- if (flags & NetworkTablesFlags_ShowFlags) {
- ImGui::NextColumn();
- }
- if (flags & NetworkTablesFlags_ShowTimestamp) {
- ImGui::NextColumn();
- }
- ImGui::Separator();
+ EmitParentContextMenu(model, node.path, flags);
if (open) {
- EmitTree(node.children, flags);
+ EmitTree(model, node.children, flags, category, false);
TreePop();
}
}
}
}
-void glass::DisplayNetworkTables(NetworkTablesModel* model,
- NetworkTablesFlags flags) {
- auto inst = model->GetInstance();
-
- if (flags & NetworkTablesFlags_ShowConnections) {
- if (CollapsingHeader("Connections")) {
- ImGui::Columns(4, "connections");
- ImGui::Text("Id");
- ImGui::NextColumn();
- ImGui::Text("Address");
- ImGui::NextColumn();
- ImGui::Text("Updated");
- ImGui::NextColumn();
- ImGui::Text("Proto");
- ImGui::NextColumn();
- ImGui::Separator();
- for (auto&& i : nt::GetConnections(inst)) {
- ImGui::Text("%s", i.remote_id.c_str());
- ImGui::NextColumn();
- ImGui::Text("%s", i.remote_ip.c_str());
- ImGui::NextColumn();
- ImGui::Text("%llu",
- static_cast<unsigned long long>( // NOLINT(runtime/int)
- i.last_update));
- ImGui::NextColumn();
- ImGui::Text("%d.%d", i.protocol_version >> 8,
- i.protocol_version & 0xff);
- ImGui::NextColumn();
- }
- ImGui::Columns();
- }
-
- if (!CollapsingHeader("Values", ImGuiTreeNodeFlags_DefaultOpen)) {
- return;
- }
+static void DisplayTable(NetworkTablesModel* model,
+ const std::vector<NetworkTablesModel::TreeNode>& tree,
+ NetworkTablesFlags flags, ShowCategory category) {
+ if (tree.empty()) {
+ return;
}
- const bool showFlags = (flags & NetworkTablesFlags_ShowFlags);
+ const bool showProperties = (flags & NetworkTablesFlags_ShowProperties);
const bool showTimestamp = (flags & NetworkTablesFlags_ShowTimestamp);
+ const bool showServerTimestamp =
+ (flags & NetworkTablesFlags_ShowServerTimestamp);
- static bool first = true;
- ImGui::Columns(2 + (showFlags ? 1 : 0) + (showTimestamp ? 1 : 0), "values");
- if (first) {
- ImGui::SetColumnWidth(-1, 0.5f * ImGui::GetWindowWidth());
- }
- ImGui::Text("Name");
- EmitParentContextMenu("/", flags);
- ImGui::NextColumn();
- ImGui::Text("Value");
- ImGui::NextColumn();
- if (showFlags) {
- if (first) {
- ImGui::SetColumnWidth(-1, 12 * ImGui::GetFontSize());
- }
- ImGui::Text("Flags");
- ImGui::NextColumn();
+ ImGui::BeginTable("values",
+ 2 + (showProperties ? 1 : 0) + (showTimestamp ? 1 : 0) +
+ (showServerTimestamp ? 1 : 0),
+ ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit |
+ ImGuiTableFlags_BordersInner);
+ ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed,
+ 0.35f * ImGui::GetWindowWidth());
+ ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed,
+ 12 * ImGui::GetFontSize());
+ if (showProperties) {
+ ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed,
+ 12 * ImGui::GetFontSize());
}
if (showTimestamp) {
- ImGui::Text("Changed");
- ImGui::NextColumn();
+ ImGui::TableSetupColumn("Time");
}
- ImGui::Separator();
- first = false;
+ if (showServerTimestamp) {
+ ImGui::TableSetupColumn("Server Time");
+ }
+ ImGui::TableHeadersRow();
+ // EmitParentContextMenu(model, "/", flags);
if (flags & NetworkTablesFlags_TreeView) {
- EmitTree(model->GetTreeRoot(), flags);
+ switch (category) {
+ case ShowPersistent:
+ PushID("persistent");
+ break;
+ case ShowRetained:
+ PushID("retained");
+ break;
+ case ShowTransitory:
+ PushID("transitory");
+ break;
+ default:
+ break;
+ }
+ EmitTree(model, tree, flags, category, true);
+ if (category != ShowAll) {
+ PopID();
+ }
} else {
for (auto entry : model->GetEntries()) {
- EmitEntry(*entry, entry->name.c_str(), flags);
+ if ((flags & NetworkTablesFlags_ShowSpecial) != 0 ||
+ !wpi::starts_with(entry->info.name, '$')) {
+ EmitEntry(model, *entry, entry->info.name.c_str(), flags, category);
+ }
}
}
- ImGui::Columns();
+ ImGui::EndTable();
+}
+
+static void DisplayClient(const NetworkTablesModel::Client& client) {
+ if (CollapsingHeader("Publishers")) {
+ ImGui::BeginTable("publishers", 2, ImGuiTableFlags_Resizable);
+ ImGui::TableSetupColumn("UID", ImGuiTableColumnFlags_WidthFixed,
+ 10 * ImGui::GetFontSize());
+ ImGui::TableSetupColumn("Topic");
+ ImGui::TableHeadersRow();
+ for (auto&& pub : client.publishers) {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("%" PRId64, pub.uid);
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", pub.topic.c_str());
+ }
+ ImGui::EndTable();
+ }
+ if (CollapsingHeader("Subscribers")) {
+ ImGui::BeginTable(
+ "subscribers", 6,
+ ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp);
+ ImGui::TableSetupColumn("UID", ImGuiTableColumnFlags_WidthFixed,
+ 10 * ImGui::GetFontSize());
+ ImGui::TableSetupColumn("Topics", ImGuiTableColumnFlags_WidthStretch, 6.0f);
+ ImGui::TableSetupColumn("Periodic", ImGuiTableColumnFlags_WidthStretch,
+ 1.0f);
+ ImGui::TableSetupColumn("Topics Only", ImGuiTableColumnFlags_WidthStretch,
+ 1.0f);
+ ImGui::TableSetupColumn("Send All", ImGuiTableColumnFlags_WidthStretch,
+ 1.0f);
+ ImGui::TableSetupColumn("Prefix Match", ImGuiTableColumnFlags_WidthStretch,
+ 1.0f);
+ ImGui::TableHeadersRow();
+ for (auto&& sub : client.subscribers) {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("%" PRId64, sub.uid);
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", sub.topicsStr.c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%0.3f", sub.options.periodic);
+ ImGui::TableNextColumn();
+ ImGui::Text(sub.options.topicsOnly ? "Yes" : "No");
+ ImGui::TableNextColumn();
+ ImGui::Text(sub.options.sendAll ? "Yes" : "No");
+ ImGui::TableNextColumn();
+ ImGui::Text(sub.options.prefixMatch ? "Yes" : "No");
+ }
+ ImGui::EndTable();
+ }
+}
+
+void glass::DisplayNetworkTablesInfo(NetworkTablesModel* model) {
+ auto inst = model->GetInstance();
+
+ if (CollapsingHeader("Connections")) {
+ ImGui::BeginTable("connections", 4, ImGuiTableFlags_Resizable);
+ ImGui::TableSetupColumn("Id");
+ ImGui::TableSetupColumn("Address");
+ ImGui::TableSetupColumn("Updated");
+ ImGui::TableSetupColumn("Proto");
+ ImGui::TableSetupScrollFreeze(1, 0);
+ ImGui::TableHeadersRow();
+ for (auto&& i : inst.GetConnections()) {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", i.remote_id.c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%s", i.remote_ip.c_str());
+ ImGui::TableNextColumn();
+ ImGui::Text("%llu",
+ static_cast<unsigned long long>( // NOLINT(runtime/int)
+ i.last_update));
+ ImGui::TableNextColumn();
+ ImGui::Text("%d.%d", i.protocol_version >> 8, i.protocol_version & 0xff);
+ }
+ ImGui::EndTable();
+ }
+
+ auto netMode = inst.GetNetworkMode();
+ if (netMode == NT_NET_MODE_SERVER || netMode == NT_NET_MODE_CLIENT4) {
+ if (CollapsingHeader("Server")) {
+ PushID("Server");
+ ImGui::Indent();
+ DisplayClient(model->GetServer());
+ ImGui::Unindent();
+ PopID();
+ }
+ if (CollapsingHeader("Clients")) {
+ ImGui::Indent();
+ for (auto&& client : model->GetClients()) {
+ if (CollapsingHeader(client.second.id.c_str())) {
+ PushID(client.second.id.c_str());
+ ImGui::Indent();
+ ImGui::Text("%s (version %u.%u)", client.second.conn.c_str(),
+ client.second.version >> 8, client.second.version & 0xff);
+ DisplayClient(client.second);
+ ImGui::Unindent();
+ PopID();
+ }
+ }
+ ImGui::Unindent();
+ }
+ }
+}
+
+void glass::DisplayNetworkTables(NetworkTablesModel* model,
+ NetworkTablesFlags flags) {
+ if (flags & NetworkTablesFlags_CombinedView) {
+ DisplayTable(model, model->GetTreeRoot(), flags, ShowAll);
+ } else {
+ if (CollapsingHeader("Persistent Values", ImGuiTreeNodeFlags_DefaultOpen)) {
+ DisplayTable(model, model->GetPersistentTreeRoot(), flags,
+ ShowPersistent);
+ }
+
+ if (CollapsingHeader("Retained Values", ImGuiTreeNodeFlags_DefaultOpen)) {
+ DisplayTable(model, model->GetRetainedTreeRoot(), flags, ShowRetained);
+ }
+
+ if (CollapsingHeader("Transitory Values", ImGuiTreeNodeFlags_DefaultOpen)) {
+ DisplayTable(model, model->GetTransitoryTreeRoot(), flags,
+ ShowTransitory);
+ }
+ }
}
void NetworkTablesFlagsSettings::Update() {
@@ -734,28 +1441,41 @@
auto& storage = GetStorage();
m_pTreeView =
&storage.GetBool("tree", m_defaultFlags & NetworkTablesFlags_TreeView);
- m_pShowConnections = &storage.GetBool(
- "connections", m_defaultFlags & NetworkTablesFlags_ShowConnections);
- m_pShowFlags = &storage.GetBool(
- "flags", m_defaultFlags & NetworkTablesFlags_ShowFlags);
+ m_pCombinedView = &storage.GetBool(
+ "combined", m_defaultFlags & NetworkTablesFlags_CombinedView);
+ m_pShowSpecial = &storage.GetBool(
+ "special", m_defaultFlags & NetworkTablesFlags_ShowSpecial);
+ m_pShowProperties = &storage.GetBool(
+ "properties", m_defaultFlags & NetworkTablesFlags_ShowProperties);
m_pShowTimestamp = &storage.GetBool(
"timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp);
+ m_pShowServerTimestamp = &storage.GetBool(
+ "serverTimestamp",
+ m_defaultFlags & NetworkTablesFlags_ShowServerTimestamp);
m_pCreateNoncanonicalKeys = &storage.GetBool(
"createNonCanonical",
m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys);
+ m_pPrecision = &storage.GetInt(
+ "precision", (m_defaultFlags & NetworkTablesFlags_Precision) >>
+ kNetworkTablesFlags_PrecisionBitShift);
}
- m_flags &=
- ~(NetworkTablesFlags_TreeView | NetworkTablesFlags_ShowConnections |
- NetworkTablesFlags_ShowFlags | NetworkTablesFlags_ShowTimestamp |
- NetworkTablesFlags_CreateNoncanonicalKeys);
+ m_flags &= ~(
+ NetworkTablesFlags_TreeView | NetworkTablesFlags_CombinedView |
+ NetworkTablesFlags_ShowSpecial | NetworkTablesFlags_ShowProperties |
+ NetworkTablesFlags_ShowTimestamp |
+ NetworkTablesFlags_ShowServerTimestamp |
+ NetworkTablesFlags_CreateNoncanonicalKeys | NetworkTablesFlags_Precision);
m_flags |=
(*m_pTreeView ? NetworkTablesFlags_TreeView : 0) |
- (*m_pShowConnections ? NetworkTablesFlags_ShowConnections : 0) |
- (*m_pShowFlags ? NetworkTablesFlags_ShowFlags : 0) |
+ (*m_pCombinedView ? NetworkTablesFlags_CombinedView : 0) |
+ (*m_pShowSpecial ? NetworkTablesFlags_ShowSpecial : 0) |
+ (*m_pShowProperties ? NetworkTablesFlags_ShowProperties : 0) |
(*m_pShowTimestamp ? NetworkTablesFlags_ShowTimestamp : 0) |
+ (*m_pShowServerTimestamp ? NetworkTablesFlags_ShowServerTimestamp : 0) |
(*m_pCreateNoncanonicalKeys ? NetworkTablesFlags_CreateNoncanonicalKeys
- : 0);
+ : 0) |
+ (*m_pPrecision << kNetworkTablesFlags_PrecisionBitShift);
}
void NetworkTablesFlagsSettings::DisplayMenu() {
@@ -763,9 +1483,22 @@
return;
}
ImGui::MenuItem("Tree View", "", m_pTreeView);
- ImGui::MenuItem("Show Connections", "", m_pShowConnections);
- ImGui::MenuItem("Show Flags", "", m_pShowFlags);
+ ImGui::MenuItem("Combined View", "", m_pCombinedView);
+ ImGui::MenuItem("Show Special", "", m_pShowSpecial);
+ ImGui::MenuItem("Show Properties", "", m_pShowProperties);
ImGui::MenuItem("Show Timestamp", "", m_pShowTimestamp);
+ ImGui::MenuItem("Show Server Timestamp", "", m_pShowServerTimestamp);
+ if (ImGui::BeginMenu("Decimal Precision")) {
+ static const char* precisionOptions[] = {"1", "2", "3", "4", "5",
+ "6", "7", "8", "9", "10"};
+ for (int i = 1; i <= 10; i++) {
+ if (ImGui::MenuItem(precisionOptions[i - 1], nullptr,
+ i == *m_pPrecision)) {
+ *m_pPrecision = i;
+ }
+ }
+ ImGui::EndMenu();
+ }
ImGui::Separator();
ImGui::MenuItem("Allow creation of non-canonical keys", "",
m_pCreateNoncanonicalKeys);
@@ -773,9 +1506,13 @@
void NetworkTablesView::Display() {
m_flags.Update();
- if (ImGui::BeginPopupContextItem()) {
- m_flags.DisplayMenu();
- ImGui::EndPopup();
- }
DisplayNetworkTables(m_model, m_flags.GetFlags());
}
+
+void NetworkTablesView::Settings() {
+ m_flags.DisplayMenu();
+}
+
+bool NetworkTablesView::HasSettings() {
+ return true;
+}
diff --git a/glass/src/libnt/native/cpp/NetworkTablesHelper.cpp b/glass/src/libnt/native/cpp/NetworkTablesHelper.cpp
deleted file mode 100644
index 5cb5bbc..0000000
--- a/glass/src/libnt/native/cpp/NetworkTablesHelper.cpp
+++ /dev/null
@@ -1,19 +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.
-
-#include "glass/networktables/NetworkTablesHelper.h"
-
-using namespace glass;
-
-NetworkTablesHelper::NetworkTablesHelper(NT_Inst inst)
- : m_inst{inst}, m_poller{nt::CreateEntryListenerPoller(inst)} {}
-
-NetworkTablesHelper::~NetworkTablesHelper() {
- nt::DestroyEntryListenerPoller(m_poller);
-}
-
-bool NetworkTablesHelper::IsConnected() const {
- return nt::GetNetworkMode(m_inst) == NT_NET_MODE_SERVER ||
- nt::IsConnected(m_inst);
-}
diff --git a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp
index 9ccbb2e..413899e 100644
--- a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp
+++ b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp
@@ -17,16 +17,16 @@
using namespace glass;
NetworkTablesProvider::NetworkTablesProvider(Storage& storage)
- : NetworkTablesProvider{storage, nt::GetDefaultInstance()} {}
+ : NetworkTablesProvider{storage, nt::NetworkTableInstance::GetDefault()} {}
-NetworkTablesProvider::NetworkTablesProvider(Storage& storage, NT_Inst inst)
+NetworkTablesProvider::NetworkTablesProvider(Storage& storage,
+ nt::NetworkTableInstance inst)
: Provider{storage.GetChild("windows")},
- m_nt{inst},
+ m_inst{inst},
+ m_poller{inst},
m_typeCache{storage.GetChild("types")} {
storage.SetCustomApply([this] {
- m_listener =
- m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW |
- NT_NOTIFY_DELETE | NT_NOTIFY_IMMEDIATE);
+ m_listener = m_poller.AddListener({{""}}, nt::EventFlags::kTopic);
for (auto&& childIt : m_storage.GetChildren()) {
auto id = childIt.key();
auto typePtr = m_typeCache.FindValue(id);
@@ -41,15 +41,14 @@
}
auto entry = GetOrCreateView(
- builderIt->second,
- nt::GetEntry(m_nt.GetInstance(), fmt::format("{}/.type", id)), id);
+ builderIt->second, m_inst.GetTopic(fmt::format("{}/.type", id)), id);
if (entry) {
Show(entry, nullptr);
}
}
});
storage.SetCustomClear([this, &storage] {
- nt::RemoveEntryListener(m_listener);
+ m_poller.RemoveListener(m_listener);
m_listener = 0;
for (auto&& modelEntry : m_modelEntries) {
modelEntry->model.reset();
@@ -100,35 +99,58 @@
void NetworkTablesProvider::Update() {
Provider::Update();
- // add/remove entries from NT changes
- for (auto&& event : m_nt.PollListener()) {
- // look for .type fields
- std::string_view eventName{event.name};
- if (!wpi::ends_with(eventName, "/.type") || !event.value ||
- !event.value->IsString()) {
- continue;
- }
- auto tableName = wpi::drop_back(eventName, 6);
-
- // only handle ones where we have a builder
- auto builderIt = m_typeMap.find(event.value->GetString());
- if (builderIt == m_typeMap.end()) {
- continue;
- }
-
- if (event.flags & NT_NOTIFY_DELETE) {
- auto it = std::find_if(
- m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
- return static_cast<Entry*>(elem->modelEntry)->typeEntry ==
- event.entry;
- });
- if (it != m_viewEntries.end()) {
- m_viewEntries.erase(it);
+ for (auto&& event : m_poller.ReadQueue()) {
+ if (auto info = event.GetTopicInfo()) {
+ // add/remove entries from NT changes
+ // look for .type fields
+ if (!wpi::ends_with(info->name, "/.type") || info->type != NT_STRING ||
+ info->type_str != "string") {
+ continue;
}
- } else if (event.flags & NT_NOTIFY_NEW) {
- GetOrCreateView(builderIt->second, event.entry, tableName);
+
+ if (event.flags & nt::EventFlags::kUnpublish) {
+ auto it = m_topicMap.find(info->topic);
+ if (it != m_topicMap.end()) {
+ m_poller.RemoveListener(it->second.listener);
+ m_topicMap.erase(it);
+ }
+
+ auto it2 = std::find_if(
+ m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
+ return static_cast<Entry*>(elem->modelEntry)
+ ->typeTopic.GetHandle() == info->topic;
+ });
+ if (it2 != m_viewEntries.end()) {
+ m_viewEntries.erase(it2);
+ }
+ } else if (event.flags & nt::EventFlags::kPublish) {
+ // subscribe to it; use a subscriber so we only get string values
+ SubListener sublistener;
+ sublistener.subscriber = nt::StringTopic{info->topic}.Subscribe("");
+ sublistener.listener = m_poller.AddListener(
+ sublistener.subscriber,
+ nt::EventFlags::kValueAll | nt::EventFlags::kImmediate);
+ m_topicMap.try_emplace(info->topic, std::move(sublistener));
+ }
+ } else if (auto valueData = event.GetValueEventData()) {
+ // handle actual .type strings
+ if (!valueData->value.IsString()) {
+ continue;
+ }
+
+ // only handle ones where we have a builder
+ auto builderIt = m_typeMap.find(valueData->value.GetString());
+ if (builderIt == m_typeMap.end()) {
+ continue;
+ }
+
+ auto topicName = nt::GetTopicName(valueData->topic);
+ auto tableName = wpi::drop_back(topicName, 6);
+
+ GetOrCreateView(builderIt->second, nt::Topic{valueData->topic},
+ tableName);
// cache the type
- m_typeCache.SetString(tableName, event.value->GetString());
+ m_typeCache.SetString(tableName, valueData->value.GetString());
}
}
}
@@ -149,7 +171,7 @@
// get or create model
if (!entry->modelEntry->model) {
entry->modelEntry->model =
- entry->modelEntry->createModel(m_nt.GetInstance(), entry->name.c_str());
+ entry->modelEntry->createModel(m_inst, entry->name.c_str());
}
if (!entry->modelEntry->model) {
return;
@@ -180,22 +202,22 @@
}
NetworkTablesProvider::ViewEntry* NetworkTablesProvider::GetOrCreateView(
- const Builder& builder, NT_Entry typeEntry, std::string_view name) {
+ const Builder& builder, nt::Topic typeTopic, std::string_view name) {
// get view entry if it already exists
auto viewIt = FindViewEntry(name);
if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) {
// make sure typeEntry is set in model
- static_cast<Entry*>((*viewIt)->modelEntry)->typeEntry = typeEntry;
+ static_cast<Entry*>((*viewIt)->modelEntry)->typeTopic = typeTopic;
return viewIt->get();
}
// get or create model entry
auto modelIt = FindModelEntry(name);
if (modelIt != m_modelEntries.end() && (*modelIt)->name == name) {
- static_cast<Entry*>(modelIt->get())->typeEntry = typeEntry;
+ static_cast<Entry*>(modelIt->get())->typeTopic = typeTopic;
} else {
modelIt = m_modelEntries.emplace(
- modelIt, std::make_unique<Entry>(typeEntry, name, builder));
+ modelIt, std::make_unique<Entry>(typeTopic, name, builder));
}
// create new view entry
diff --git a/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp b/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp
index d1fb341..fd6bd52 100644
--- a/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp
+++ b/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp
@@ -43,50 +43,66 @@
// if just changing servers in client mode, no need to stop and restart
unsigned int curMode = nt::GetNetworkMode(m_inst);
- if (mode != 1 || (curMode & NT_NET_MODE_SERVER) != 0) {
+ if ((mode == 0 || mode == 3) ||
+ (mode == 1 && (curMode & NT_NET_MODE_CLIENT4) == 0) ||
+ (mode == 2 && (curMode & NT_NET_MODE_CLIENT3) == 0)) {
nt::StopClient(m_inst);
nt::StopServer(m_inst);
nt::StopLocal(m_inst);
}
- if (m_mode != 1 || !dsClient) {
+ if ((m_mode == 0 || m_mode == 3) || !dsClient) {
nt::StopDSClient(m_inst);
}
lock.lock();
} while (mode != m_mode || dsClient != m_dsClient);
- if (m_mode == 1) {
+ if (m_mode == 1 || m_mode == 2) {
std::string_view serverTeam{m_serverTeam};
std::optional<unsigned int> team;
+ if (m_mode == 1) {
+ nt::StartClient4(m_inst, m_clientName);
+ } else if (m_mode == 2) {
+ nt::StartClient3(m_inst, m_clientName);
+ }
+
+ unsigned int port = m_mode == 1 ? m_port4 : m_port3;
if (!wpi::contains(serverTeam, '.') &&
(team = wpi::parse_integer<unsigned int>(serverTeam, 10))) {
- nt::StartClientTeam(m_inst, team.value(), NT_DEFAULT_PORT);
+ nt::SetServerTeam(m_inst, team.value(), port);
} else {
wpi::SmallVector<std::string_view, 4> serverNames;
- wpi::SmallVector<std::pair<std::string_view, unsigned int>, 4> servers;
+ std::vector<std::pair<std::string_view, unsigned int>> servers;
wpi::split(serverTeam, serverNames, ',', -1, false);
for (auto&& serverName : serverNames) {
- servers.emplace_back(serverName, NT_DEFAULT_PORT);
+ servers.emplace_back(serverName, port);
}
- nt::StartClient(m_inst, servers);
+ nt::SetServer(m_inst, servers);
}
if (m_dsClient) {
- nt::StartDSClient(m_inst, NT_DEFAULT_PORT);
+ nt::StartDSClient(m_inst, port);
}
- } else if (m_mode == 2) {
+ } else if (m_mode == 3) {
nt::StartServer(m_inst, m_iniName.c_str(), m_listenAddress.c_str(),
- NT_DEFAULT_PORT);
+ m_port3, m_port4);
}
}
}
-NetworkTablesSettings::NetworkTablesSettings(Storage& storage, NT_Inst inst)
- : m_mode{storage.GetString("mode"), 0, {"Disabled", "Client", "Server"}},
- m_iniName{storage.GetString("iniName", "networktables.ini")},
+NetworkTablesSettings::NetworkTablesSettings(std::string_view clientName,
+ Storage& storage, NT_Inst inst)
+ : m_mode{storage.GetString("mode"),
+ 0,
+ {"Disabled", "Client (NT4)", "Client (NT3)", "Server"}},
+ m_persistentFilename{
+ storage.GetString("persistentFilename", "networktables.json")},
m_serverTeam{storage.GetString("serverTeam")},
m_listenAddress{storage.GetString("listenAddress")},
+ m_clientName{storage.GetString("clientName", clientName)},
+ m_port3{storage.GetInt("port3", NT_DEFAULT_PORT3)},
+ m_port4{storage.GetInt("port4", NT_DEFAULT_PORT4)},
m_dsClient{storage.GetBool("dsClient", true)} {
m_thread.Start(inst);
}
@@ -101,23 +117,59 @@
auto thr = m_thread.GetThread();
thr->m_restart = true;
thr->m_mode = m_mode.GetValue();
- thr->m_iniName = m_iniName;
+ thr->m_iniName = m_persistentFilename;
thr->m_serverTeam = m_serverTeam;
thr->m_listenAddress = m_listenAddress;
+ thr->m_clientName = m_clientName;
+ thr->m_port3 = m_port3;
+ thr->m_port4 = m_port4;
thr->m_dsClient = m_dsClient;
thr->m_cond.notify_one();
}
+static void LimitPortRange(int* port) {
+ if (*port < 0) {
+ *port = 0;
+ } else if (*port > 65535) {
+ *port = 65535;
+ }
+}
+
bool NetworkTablesSettings::Display() {
- m_mode.Combo("Mode", m_serverOption ? 3 : 2);
+ m_mode.Combo("Mode", m_serverOption ? 4 : 3);
switch (m_mode.GetValue()) {
case 1:
+ case 2: {
ImGui::InputText("Team/IP", &m_serverTeam);
+ int* port = m_mode.GetValue() == 1 ? &m_port4 : &m_port3;
+ if (ImGui::InputInt("Port", port)) {
+ LimitPortRange(port);
+ }
+ ImGui::SameLine();
+ if (ImGui::SmallButton("Default")) {
+ *port = m_mode.GetValue() == 1 ? NT_DEFAULT_PORT4 : NT_DEFAULT_PORT3;
+ }
+ ImGui::InputText("Network Identity", &m_clientName);
ImGui::Checkbox("Get Address from DS", &m_dsClient);
break;
- case 2:
+ }
+ case 3:
ImGui::InputText("Listen Address", &m_listenAddress);
- ImGui::InputText("ini Filename", &m_iniName);
+ if (ImGui::InputInt("NT3 port", &m_port3)) {
+ LimitPortRange(&m_port3);
+ }
+ ImGui::SameLine();
+ if (ImGui::SmallButton("Default##default3")) {
+ m_port3 = NT_DEFAULT_PORT3;
+ }
+ if (ImGui::InputInt("NT4 port", &m_port4)) {
+ LimitPortRange(&m_port4);
+ }
+ ImGui::SameLine();
+ if (ImGui::SmallButton("Default##default4")) {
+ m_port4 = NT_DEFAULT_PORT4;
+ }
+ ImGui::InputText("Persistent Filename", &m_persistentFilename);
break;
default:
break;
diff --git a/glass/src/libnt/native/cpp/StandardNetworkTables.cpp b/glass/src/libnt/native/cpp/StandardNetworkTables.cpp
index 0a5f234..ef61025 100644
--- a/glass/src/libnt/native/cpp/StandardNetworkTables.cpp
+++ b/glass/src/libnt/native/cpp/StandardNetworkTables.cpp
@@ -12,8 +12,8 @@
#include "glass/networktables/NTGyro.h"
#include "glass/networktables/NTMecanumDrive.h"
#include "glass/networktables/NTMechanism2D.h"
+#include "glass/networktables/NTMotorController.h"
#include "glass/networktables/NTPIDController.h"
-#include "glass/networktables/NTSpeedController.h"
#include "glass/networktables/NTStringChooser.h"
#include "glass/networktables/NTSubsystem.h"
#include "glass/networktables/NetworkTablesProvider.h"
@@ -23,7 +23,7 @@
void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
provider.Register(
NTCommandSchedulerModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTCommandSchedulerModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
@@ -34,7 +34,7 @@
});
provider.Register(
NTCommandSelectorModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTCommandSelectorModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
@@ -45,7 +45,7 @@
});
provider.Register(
NTDifferentialDriveModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTDifferentialDriveModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
@@ -56,7 +56,7 @@
});
provider.Register(
NTFMSModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTFMSModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
@@ -66,7 +66,7 @@
});
provider.Register(
NTDigitalInputModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTDigitalInputModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
@@ -77,7 +77,7 @@
});
provider.Register(
NTDigitalOutputModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTDigitalOutputModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
@@ -88,7 +88,7 @@
});
provider.Register(
NTField2DModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTField2DModel>(inst, path);
},
[=](Window* win, Model* model, const char* path) {
@@ -100,7 +100,7 @@
});
provider.Register(
NTGyroModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTGyroModel>(inst, path);
},
[](Window* win, Model* model, const char* path) {
@@ -110,7 +110,7 @@
});
provider.Register(
NTMecanumDriveModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTMecanumDriveModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
@@ -120,7 +120,7 @@
});
provider.Register(
NTMechanism2DModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTMechanism2DModel>(inst, path);
},
[=](Window* win, Model* model, const char* path) {
@@ -132,7 +132,7 @@
});
provider.Register(
NTPIDControllerModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTPIDControllerModel>(inst, path);
},
[](Window* win, Model* model, const char* path) {
@@ -142,19 +142,19 @@
});
});
provider.Register(
- NTSpeedControllerModel::kType,
- [](NT_Inst inst, const char* path) {
- return std::make_unique<NTSpeedControllerModel>(inst, path);
+ NTMotorControllerModel::kType,
+ [](nt::NetworkTableInstance inst, const char* path) {
+ return std::make_unique<NTMotorControllerModel>(inst, path);
},
[](Window* win, Model* model, const char* path) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
return MakeFunctionView([=] {
- DisplaySpeedController(static_cast<NTSpeedControllerModel*>(model));
+ DisplayMotorController(static_cast<NTMotorControllerModel*>(model));
});
});
provider.Register(
NTStringChooserModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTStringChooserModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
@@ -165,7 +165,7 @@
});
provider.Register(
NTSubsystemModel::kType,
- [](NT_Inst inst, const char* path) {
+ [](nt::NetworkTableInstance inst, const char* path) {
return std::make_unique<NTSubsystemModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
diff --git a/glass/src/libnt/native/include/glass/networktables/NTCommandScheduler.h b/glass/src/libnt/native/include/glass/networktables/NTCommandScheduler.h
index 54dc778..980e1f5 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTCommandScheduler.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTCommandScheduler.h
@@ -4,14 +4,18 @@
#pragma once
+#include <stdint.h>
+
#include <string>
#include <string_view>
#include <vector>
-#include <ntcore_cpp.h>
+#include <networktables/IntegerArrayTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringArrayTopic.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/CommandScheduler.h"
namespace glass {
@@ -20,7 +24,7 @@
static constexpr const char* kType = "Scheduler";
explicit NTCommandSchedulerModel(std::string_view path);
- NTCommandSchedulerModel(NT_Inst instance, std::string_view path);
+ NTCommandSchedulerModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
const std::vector<std::string>& GetCurrentCommands() override {
@@ -34,14 +38,14 @@
bool IsReadOnly() override { return false; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_name;
- NT_Entry m_commands;
- NT_Entry m_ids;
- NT_Entry m_cancel;
+ nt::NetworkTableInstance m_inst;
+ nt::StringSubscriber m_name;
+ nt::StringArraySubscriber m_commands;
+ nt::IntegerArraySubscriber m_ids;
+ nt::IntegerArrayPublisher m_cancel;
std::string m_nameValue;
std::vector<std::string> m_commandsValue;
- std::vector<double> m_idsValue;
+ std::vector<int64_t> m_idsValue;
};
} // namespace glass
diff --git a/glass/src/libnt/native/include/glass/networktables/NTCommandSelector.h b/glass/src/libnt/native/include/glass/networktables/NTCommandSelector.h
index c936665..ab35484 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTCommandSelector.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTCommandSelector.h
@@ -7,10 +7,11 @@
#include <string>
#include <string_view>
-#include <ntcore_cpp.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/CommandSelector.h"
namespace glass {
@@ -19,7 +20,7 @@
static constexpr const char* kType = "Command";
explicit NTCommandSelectorModel(std::string_view path);
- NTCommandSelectorModel(NT_Inst instance, std::string_view path);
+ NTCommandSelectorModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
DataSource* GetRunningData() override { return &m_runningData; }
@@ -30,9 +31,9 @@
bool IsReadOnly() override { return false; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_running;
- NT_Entry m_name;
+ nt::NetworkTableInstance m_inst;
+ nt::BooleanEntry m_running;
+ nt::StringSubscriber m_name;
DataSource m_runningData;
std::string m_nameValue;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTDifferentialDrive.h b/glass/src/libnt/native/include/glass/networktables/NTDifferentialDrive.h
index 49b3eb0..5e34669 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTDifferentialDrive.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTDifferentialDrive.h
@@ -8,10 +8,12 @@
#include <string_view>
#include <vector>
-#include <ntcore_cpp.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/DoubleTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/Drive.h"
namespace glass {
@@ -20,7 +22,8 @@
static constexpr const char* kType = "DifferentialDrive";
explicit NTDifferentialDriveModel(std::string_view path);
- NTDifferentialDriveModel(NT_Inst instance, std::string_view path);
+ NTDifferentialDriveModel(nt::NetworkTableInstance instance,
+ std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
const std::vector<DriveModel::WheelInfo>& GetWheels() const override {
@@ -35,11 +38,11 @@
bool IsReadOnly() override { return !m_controllableValue; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_name;
- NT_Entry m_controllable;
- NT_Entry m_lPercent;
- NT_Entry m_rPercent;
+ nt::NetworkTableInstance m_inst;
+ nt::StringSubscriber m_name;
+ nt::BooleanSubscriber m_controllable;
+ nt::DoubleEntry m_lPercent;
+ nt::DoubleEntry m_rPercent;
std::string m_nameValue;
bool m_controllableValue = false;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTDigitalInput.h b/glass/src/libnt/native/include/glass/networktables/NTDigitalInput.h
index cd3dfeb..7aa9234 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTDigitalInput.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTDigitalInput.h
@@ -7,11 +7,12 @@
#include <string>
#include <string_view>
-#include <ntcore_cpp.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
#include "glass/hardware/DIO.h"
-#include "glass/networktables/NetworkTablesHelper.h"
namespace glass {
@@ -21,7 +22,7 @@
// path is to the table containing ".type", excluding the trailing /
explicit NTDigitalInputModel(std::string_view path);
- NTDigitalInputModel(NT_Inst inst, std::string_view path);
+ NTDigitalInputModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
@@ -42,9 +43,9 @@
bool IsReadOnly() override { return true; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_value;
- NT_Entry m_name;
+ nt::NetworkTableInstance m_inst;
+ nt::BooleanSubscriber m_value;
+ nt::StringSubscriber m_name;
DataSource m_valueData;
std::string m_nameValue;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTDigitalOutput.h b/glass/src/libnt/native/include/glass/networktables/NTDigitalOutput.h
index 8ed1ee7..fb6a151 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTDigitalOutput.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTDigitalOutput.h
@@ -7,11 +7,12 @@
#include <string>
#include <string_view>
-#include <ntcore_cpp.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
#include "glass/hardware/DIO.h"
-#include "glass/networktables/NetworkTablesHelper.h"
namespace glass {
@@ -21,7 +22,7 @@
// path is to the table containing ".type", excluding the trailing /
explicit NTDigitalOutputModel(std::string_view path);
- NTDigitalOutputModel(NT_Inst inst, std::string_view path);
+ NTDigitalOutputModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
@@ -42,10 +43,10 @@
bool IsReadOnly() override { return !m_controllableValue; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_value;
- NT_Entry m_name;
- NT_Entry m_controllable;
+ nt::NetworkTableInstance m_inst;
+ nt::BooleanEntry m_value;
+ nt::StringSubscriber m_name;
+ nt::BooleanSubscriber m_controllable;
DataSource m_valueData;
std::string m_nameValue;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTFMS.h b/glass/src/libnt/native/include/glass/networktables/NTFMS.h
index b19a9f0..5898080 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTFMS.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTFMS.h
@@ -6,10 +6,12 @@
#include <string_view>
-#include <ntcore_cpp.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/IntegerTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/FMS.h"
namespace glass {
@@ -20,7 +22,7 @@
// path is to the table containing ".type", excluding the trailing /
explicit NTFMSModel(std::string_view path);
- NTFMSModel(NT_Inst inst, std::string_view path);
+ NTFMSModel(nt::NetworkTableInstance inst, std::string_view path);
DataSource* GetFmsAttachedData() override { return &m_fmsAttached; }
DataSource* GetDsAttachedData() override { return &m_dsAttached; }
@@ -45,18 +47,18 @@
void SetEnabled(bool val) override {}
void SetTest(bool val) override {}
void SetAutonomous(bool val) override {}
- void SetGameSpecificMessage(const char* val) override {}
+ void SetGameSpecificMessage(std::string_view val) override {}
void Update() override;
bool Exists() override;
bool IsReadOnly() override { return true; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_gameSpecificMessage;
- NT_Entry m_alliance;
- NT_Entry m_station;
- NT_Entry m_controlWord;
+ nt::NetworkTableInstance m_inst;
+ nt::StringSubscriber m_gameSpecificMessage;
+ nt::BooleanSubscriber m_alliance;
+ nt::IntegerSubscriber m_station;
+ nt::IntegerSubscriber m_controlWord;
DataSource m_fmsAttached;
DataSource m_dsAttached;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTField2D.h b/glass/src/libnt/native/include/glass/networktables/NTField2D.h
index f966e0f..40265ed 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTField2D.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTField2D.h
@@ -10,9 +10,12 @@
#include <utility>
#include <vector>
+#include <networktables/MultiSubscriber.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/NetworkTableListener.h>
+#include <networktables/StringTopic.h>
#include <ntcore_cpp.h>
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/Field2D.h"
namespace glass {
@@ -23,7 +26,7 @@
// path is to the table containing ".type", excluding the trailing /
explicit NTField2DModel(std::string_view path);
- NTField2DModel(NT_Inst inst, std::string_view path);
+ NTField2DModel(nt::NetworkTableInstance inst, std::string_view path);
~NTField2DModel() override;
const char* GetPath() const { return m_path.c_str(); }
@@ -40,9 +43,11 @@
func) override;
private:
- NetworkTablesHelper m_nt;
std::string m_path;
- NT_Entry m_name;
+ nt::NetworkTableInstance m_inst;
+ nt::MultiSubscriber m_tableSub;
+ nt::StringTopic m_nameTopic;
+ nt::NetworkTableListenerPoller m_poller;
std::string m_nameValue;
class ObjectModel;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTGyro.h b/glass/src/libnt/native/include/glass/networktables/NTGyro.h
index db303a5..dc91acc 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTGyro.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTGyro.h
@@ -7,11 +7,12 @@
#include <string>
#include <string_view>
-#include <ntcore_cpp.h>
+#include <networktables/DoubleTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
#include "glass/hardware/Gyro.h"
-#include "glass/networktables/NetworkTablesHelper.h"
namespace glass {
class NTGyroModel : public GyroModel {
@@ -19,7 +20,7 @@
static constexpr const char* kType = "Gyro";
explicit NTGyroModel(std::string_view path);
- NTGyroModel(NT_Inst instance, std::string_view path);
+ NTGyroModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
const char* GetSimDevice() const override { return nullptr; }
@@ -32,9 +33,9 @@
bool IsReadOnly() override { return true; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_angle;
- NT_Entry m_name;
+ nt::NetworkTableInstance m_inst;
+ nt::DoubleSubscriber m_angle;
+ nt::StringSubscriber m_name;
DataSource m_angleData;
std::string m_nameValue;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTMecanumDrive.h b/glass/src/libnt/native/include/glass/networktables/NTMecanumDrive.h
index ce7d234..38895bc 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTMecanumDrive.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTMecanumDrive.h
@@ -8,10 +8,12 @@
#include <string_view>
#include <vector>
-#include <ntcore_cpp.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/DoubleTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/Drive.h"
namespace glass {
@@ -20,7 +22,7 @@
static constexpr const char* kType = "MecanumDrive";
explicit NTMecanumDriveModel(std::string_view path);
- NTMecanumDriveModel(NT_Inst instance, std::string_view path);
+ NTMecanumDriveModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
const std::vector<DriveModel::WheelInfo>& GetWheels() const override {
@@ -35,13 +37,13 @@
bool IsReadOnly() override { return !m_controllableValue; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_name;
- NT_Entry m_controllable;
- NT_Entry m_flPercent;
- NT_Entry m_frPercent;
- NT_Entry m_rlPercent;
- NT_Entry m_rrPercent;
+ nt::NetworkTableInstance m_inst;
+ nt::StringSubscriber m_name;
+ nt::BooleanSubscriber m_controllable;
+ nt::DoubleEntry m_flPercent;
+ nt::DoubleEntry m_frPercent;
+ nt::DoubleEntry m_rlPercent;
+ nt::DoubleEntry m_rrPercent;
std::string m_nameValue;
bool m_controllableValue = false;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTMechanism2D.h b/glass/src/libnt/native/include/glass/networktables/NTMechanism2D.h
index 81f7df1..e8ffa9d 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTMechanism2D.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTMechanism2D.h
@@ -11,9 +11,10 @@
#include <vector>
#include <frc/geometry/Translation2d.h>
-#include <ntcore_cpp.h>
+#include <networktables/MultiSubscriber.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/NetworkTableListener.h>
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/Mechanism2D.h"
namespace glass {
@@ -24,7 +25,7 @@
// path is to the table containing ".type", excluding the trailing /
explicit NTMechanism2DModel(std::string_view path);
- NTMechanism2DModel(NT_Inst inst, std::string_view path);
+ NTMechanism2DModel(nt::NetworkTableInstance inst, std::string_view path);
~NTMechanism2DModel() override;
const char* GetPath() const { return m_path.c_str(); }
@@ -42,12 +43,13 @@
wpi::function_ref<void(MechanismRootModel& model)> func) override;
private:
- NetworkTablesHelper m_nt;
+ nt::NetworkTableInstance m_inst;
std::string m_path;
-
- NT_Entry m_name;
- NT_Entry m_dimensions;
- NT_Entry m_bgColor;
+ nt::MultiSubscriber m_tableSub;
+ nt::Topic m_nameTopic;
+ nt::Topic m_dimensionsTopic;
+ nt::Topic m_bgColorTopic;
+ nt::NetworkTableListenerPoller m_poller;
std::string m_nameValue;
frc::Translation2d m_dimensionsValue;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTSpeedController.h b/glass/src/libnt/native/include/glass/networktables/NTMotorController.h
similarity index 61%
rename from glass/src/libnt/native/include/glass/networktables/NTSpeedController.h
rename to glass/src/libnt/native/include/glass/networktables/NTMotorController.h
index a79c266..574a55b 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTSpeedController.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTMotorController.h
@@ -7,19 +7,21 @@
#include <string>
#include <string_view>
-#include <ntcore_cpp.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/DoubleTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
-#include "glass/hardware/SpeedController.h"
-#include "glass/networktables/NetworkTablesHelper.h"
+#include "glass/hardware/MotorController.h"
namespace glass {
-class NTSpeedControllerModel : public SpeedControllerModel {
+class NTMotorControllerModel : public MotorControllerModel {
public:
static constexpr const char* kType = "Motor Controller";
- explicit NTSpeedControllerModel(std::string_view path);
- NTSpeedControllerModel(NT_Inst instance, std::string_view path);
+ explicit NTMotorControllerModel(std::string_view path);
+ NTMotorControllerModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
const char* GetSimDevice() const override { return nullptr; }
@@ -32,10 +34,10 @@
bool IsReadOnly() override { return !m_controllableValue; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_value;
- NT_Entry m_name;
- NT_Entry m_controllable;
+ nt::NetworkTableInstance m_inst;
+ nt::DoubleEntry m_value;
+ nt::StringSubscriber m_name;
+ nt::BooleanSubscriber m_controllable;
DataSource m_valueData;
std::string m_nameValue;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTPIDController.h b/glass/src/libnt/native/include/glass/networktables/NTPIDController.h
index b975641..f901f72 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTPIDController.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTPIDController.h
@@ -7,10 +7,12 @@
#include <string>
#include <string_view>
-#include <ntcore_cpp.h>
+#include <networktables/BooleanTopic.h>
+#include <networktables/DoubleTopic.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/PIDController.h"
namespace glass {
@@ -19,7 +21,7 @@
static constexpr const char* kType = "PIDController";
explicit NTPIDControllerModel(std::string_view path);
- NTPIDControllerModel(NT_Inst instance, std::string_view path);
+ NTPIDControllerModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
@@ -38,13 +40,13 @@
bool IsReadOnly() override { return !m_controllableValue; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_name;
- NT_Entry m_controllable;
- NT_Entry m_p;
- NT_Entry m_i;
- NT_Entry m_d;
- NT_Entry m_setpoint;
+ nt::NetworkTableInstance m_inst;
+ nt::StringSubscriber m_name;
+ nt::BooleanSubscriber m_controllable;
+ nt::DoubleEntry m_p;
+ nt::DoubleEntry m_i;
+ nt::DoubleEntry m_d;
+ nt::DoubleEntry m_setpoint;
DataSource m_pData;
DataSource m_iData;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTStringChooser.h b/glass/src/libnt/native/include/glass/networktables/NTStringChooser.h
index 2d806c9..d770a74 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTStringChooser.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTStringChooser.h
@@ -7,9 +7,10 @@
#include <string>
#include <vector>
-#include <ntcore_cpp.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringArrayTopic.h>
+#include <networktables/StringTopic.h>
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/StringChooser.h"
namespace glass {
@@ -20,7 +21,7 @@
// path is to the table containing ".type", excluding the trailing /
explicit NTStringChooserModel(std::string_view path);
- NTStringChooserModel(NT_Inst inst, std::string_view path);
+ NTStringChooserModel(nt::NetworkTableInstance inst, std::string_view path);
const std::string& GetDefault() override { return m_defaultValue; }
const std::string& GetSelected() override { return m_selectedValue; }
@@ -29,21 +30,18 @@
return m_optionsValue;
}
- void SetDefault(std::string_view val) override;
void SetSelected(std::string_view val) override;
- void SetActive(std::string_view val) override;
- void SetOptions(wpi::span<const std::string> val) override;
void Update() override;
bool Exists() override;
bool IsReadOnly() override { return false; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_default;
- NT_Entry m_selected;
- NT_Entry m_active;
- NT_Entry m_options;
+ nt::NetworkTableInstance m_inst;
+ nt::StringSubscriber m_default;
+ nt::StringEntry m_selected;
+ nt::StringSubscriber m_active;
+ nt::StringArraySubscriber m_options;
std::string m_defaultValue;
std::string m_selectedValue;
diff --git a/glass/src/libnt/native/include/glass/networktables/NTSubsystem.h b/glass/src/libnt/native/include/glass/networktables/NTSubsystem.h
index c5862cf..6c1ee3a 100644
--- a/glass/src/libnt/native/include/glass/networktables/NTSubsystem.h
+++ b/glass/src/libnt/native/include/glass/networktables/NTSubsystem.h
@@ -7,10 +7,10 @@
#include <string>
#include <string_view>
-#include <ntcore_cpp.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/StringTopic.h>
#include "glass/DataSource.h"
-#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/Subsystem.h"
namespace glass {
@@ -19,7 +19,7 @@
static constexpr const char* kType = "Subsystem";
explicit NTSubsystemModel(std::string_view path);
- NTSubsystemModel(NT_Inst instance, std::string_view path);
+ NTSubsystemModel(nt::NetworkTableInstance inst, std::string_view path);
const char* GetName() const override { return m_nameValue.c_str(); }
const char* GetDefaultCommand() const override {
@@ -34,10 +34,10 @@
bool IsReadOnly() override { return true; }
private:
- NetworkTablesHelper m_nt;
- NT_Entry m_name;
- NT_Entry m_defaultCommand;
- NT_Entry m_currentCommand;
+ nt::NetworkTableInstance m_inst;
+ nt::StringSubscriber m_name;
+ nt::StringSubscriber m_defaultCommand;
+ nt::StringSubscriber m_currentCommand;
std::string m_nameValue;
std::string m_defaultCommandValue;
diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTables.h b/glass/src/libnt/native/include/glass/networktables/NetworkTables.h
index 8538eaa..a7aa514 100644
--- a/glass/src/libnt/native/include/glass/networktables/NetworkTables.h
+++ b/glass/src/libnt/native/include/glass/networktables/NetworkTables.h
@@ -4,13 +4,20 @@
#pragma once
+#include <functional>
+#include <map>
#include <memory>
+#include <span>
#include <string>
#include <string_view>
+#include <utility>
#include <vector>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/NetworkTableListener.h>
#include <ntcore_cpp.h>
#include <wpi/DenseMap.h>
+#include <wpi/json.h>
#include "glass/Model.h"
#include "glass/View.h"
@@ -21,28 +28,64 @@
class NetworkTablesModel : public Model {
public:
- struct Entry {
- explicit Entry(nt::EntryNotification&& event);
+ struct EntryValueTreeNode;
- void UpdateValue();
+ struct ValueSource {
+ void UpdateFromValue(nt::Value&& v, std::string_view name,
+ std::string_view typeStr);
- /** Entry handle. */
- NT_Entry entry;
-
- /** Entry name. */
- std::string name;
-
- /** The value. */
- std::shared_ptr<nt::Value> value;
-
- /** Flags. */
- unsigned int flags = 0;
+ /** The latest value. */
+ nt::Value value;
/** String representation of the value (for arrays / complex values). */
std::string valueStr;
/** Data source (for numeric values). */
std::unique_ptr<DataSource> source;
+
+ /** Children of this node, sorted by name/index */
+ std::vector<EntryValueTreeNode> valueChildren;
+
+ /** Whether or not the children represent a map */
+ bool valueChildrenMap = false;
+ };
+
+ struct EntryValueTreeNode : public ValueSource {
+ /** Short name (e.g. of just this node) */
+ std::string name;
+
+ /** Full path */
+ std::string path;
+ };
+
+ struct Entry : public ValueSource {
+ Entry() = default;
+ Entry(const Entry&) = delete;
+ Entry& operator=(const Entry&) = delete;
+ ~Entry();
+
+ void UpdateTopic(nt::Event&& event) {
+ if (std::holds_alternative<nt::TopicInfo>(event.data)) {
+ UpdateInfo(std::get<nt::TopicInfo>(std::move(event.data)));
+ }
+ }
+ void UpdateInfo(nt::TopicInfo&& info_);
+
+ /** Topic information. */
+ nt::TopicInfo info;
+
+ /** JSON representation of the topic properties. */
+ wpi::json properties;
+
+ /** Specific common property flags. */
+ bool persistent{false};
+ bool retained{false};
+
+ /** Publisher (created when the value changes). */
+ NT_Publisher publisher{0};
+
+ std::vector<nt::meta::TopicPublisher> publishers;
+ std::vector<nt::meta::TopicSubscriber> subscribers;
};
struct TreeNode {
@@ -64,41 +107,89 @@
std::vector<TreeNode> children;
};
+ struct Client : public nt::meta::Client {
+ Client() = default;
+ /*implicit*/ Client(nt::meta::Client&& oth) // NOLINT
+ : nt::meta::Client{std::move(oth)} {}
+
+ struct Subscriber : public nt::meta::ClientSubscriber {
+ /*implicit*/ Subscriber(nt::meta::ClientSubscriber&& oth); // NOLINT
+ std::string topicsStr;
+ };
+
+ std::vector<nt::meta::ClientPublisher> publishers;
+ std::vector<Subscriber> subscribers;
+
+ void UpdatePublishers(std::span<const uint8_t> data);
+ void UpdateSubscribers(std::span<const uint8_t> data);
+ };
+
NetworkTablesModel();
- explicit NetworkTablesModel(NT_Inst inst);
- ~NetworkTablesModel() override;
+ explicit NetworkTablesModel(nt::NetworkTableInstance inst);
void Update() override;
bool Exists() override;
- NT_Inst GetInstance() { return m_inst; }
- const std::vector<Entry*>& GetEntries() { return m_sortedEntries; }
- const std::vector<TreeNode>& GetTreeRoot() { return m_root; }
+ nt::NetworkTableInstance GetInstance() { return m_inst; }
+ const std::vector<Entry*>& GetEntries() const { return m_sortedEntries; }
+ const std::vector<TreeNode>& GetTreeRoot() const { return m_root; }
+ const std::vector<TreeNode>& GetPersistentTreeRoot() const {
+ return m_persistentRoot;
+ }
+ const std::vector<TreeNode>& GetRetainedTreeRoot() const {
+ return m_retainedRoot;
+ }
+ const std::vector<TreeNode>& GetTransitoryTreeRoot() const {
+ return m_transitoryRoot;
+ }
+ const std::map<std::string, Client, std::less<>>& GetClients() const {
+ return m_clients;
+ }
+ const Client& GetServer() const { return m_server; }
+ Entry* GetEntry(std::string_view name);
+ Entry* AddEntry(NT_Topic topic);
private:
- NT_Inst m_inst;
- NT_EntryListenerPoller m_poller;
- wpi::DenseMap<NT_Entry, std::unique_ptr<Entry>> m_entries;
+ void RebuildTree();
+ void RebuildTreeImpl(std::vector<TreeNode>* tree, int category);
+ void UpdateClients(std::span<const uint8_t> data);
+
+ nt::NetworkTableInstance m_inst;
+ nt::NetworkTableListenerPoller m_poller;
+ wpi::DenseMap<NT_Topic, std::unique_ptr<Entry>> m_entries;
// sorted by name
std::vector<Entry*> m_sortedEntries;
std::vector<TreeNode> m_root;
+ std::vector<TreeNode> m_persistentRoot;
+ std::vector<TreeNode> m_retainedRoot;
+ std::vector<TreeNode> m_transitoryRoot;
+
+ std::map<std::string, Client, std::less<>> m_clients;
+ Client m_server;
};
using NetworkTablesFlags = int;
+static constexpr const int kNetworkTablesFlags_PrecisionBitShift = 9;
+
enum NetworkTablesFlags_ {
NetworkTablesFlags_TreeView = 1 << 0,
- NetworkTablesFlags_ReadOnly = 1 << 1,
- NetworkTablesFlags_ShowConnections = 1 << 2,
- NetworkTablesFlags_ShowFlags = 1 << 3,
- NetworkTablesFlags_ShowTimestamp = 1 << 4,
- NetworkTablesFlags_CreateNoncanonicalKeys = 1 << 5,
- NetworkTablesFlags_Default = 1 & ~NetworkTablesFlags_ReadOnly &
- ~NetworkTablesFlags_CreateNoncanonicalKeys
+ NetworkTablesFlags_CombinedView = 1 << 1,
+ NetworkTablesFlags_ReadOnly = 1 << 2,
+ NetworkTablesFlags_ShowSpecial = 1 << 3,
+ NetworkTablesFlags_ShowProperties = 1 << 4,
+ NetworkTablesFlags_ShowTimestamp = 1 << 5,
+ NetworkTablesFlags_ShowServerTimestamp = 1 << 6,
+ NetworkTablesFlags_CreateNoncanonicalKeys = 1 << 7,
+ NetworkTablesFlags_Precision = 0xff << kNetworkTablesFlags_PrecisionBitShift,
+ NetworkTablesFlags_Default = NetworkTablesFlags_TreeView |
+ (6 << kNetworkTablesFlags_PrecisionBitShift),
};
+void DisplayNetworkTablesInfo(NetworkTablesModel* model);
+
void DisplayNetworkTables(
NetworkTablesModel* model,
NetworkTablesFlags flags = NetworkTablesFlags_Default);
@@ -116,10 +207,13 @@
private:
bool* m_pTreeView = nullptr;
- bool* m_pShowConnections = nullptr;
- bool* m_pShowFlags = nullptr;
+ bool* m_pCombinedView = nullptr;
+ bool* m_pShowSpecial = nullptr;
+ bool* m_pShowProperties = nullptr;
bool* m_pShowTimestamp = nullptr;
+ bool* m_pShowServerTimestamp = nullptr;
bool* m_pCreateNoncanonicalKeys = nullptr;
+ int* m_pPrecision = nullptr;
NetworkTablesFlags m_defaultFlags; // NOLINT
NetworkTablesFlags m_flags; // NOLINT
};
@@ -132,6 +226,8 @@
: m_model{model}, m_flags{defaultFlags} {}
void Display() override;
+ void Settings() override;
+ bool HasSettings() override;
private:
NetworkTablesModel* m_model;
diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTablesHelper.h b/glass/src/libnt/native/include/glass/networktables/NetworkTablesHelper.h
deleted file mode 100644
index aba3252..0000000
--- a/glass/src/libnt/native/include/glass/networktables/NetworkTablesHelper.h
+++ /dev/null
@@ -1,55 +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_view>
-#include <vector>
-
-#include <ntcore_cpp.h>
-
-namespace glass {
-
-class NetworkTablesHelper {
- public:
- explicit NetworkTablesHelper(NT_Inst inst);
- ~NetworkTablesHelper();
-
- NetworkTablesHelper(const NetworkTablesHelper&) = delete;
- NetworkTablesHelper& operator=(const NetworkTablesHelper&) = delete;
-
- NT_Inst GetInstance() const { return m_inst; }
- NT_EntryListenerPoller GetPoller() const { return m_poller; }
-
- NT_Entry GetEntry(std::string_view name) const {
- return nt::GetEntry(m_inst, name);
- }
-
- static constexpr int kDefaultListenerFlags =
- NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE | NT_NOTIFY_DELETE |
- NT_NOTIFY_IMMEDIATE;
-
- NT_EntryListener AddListener(NT_Entry entry,
- unsigned int flags = kDefaultListenerFlags) {
- return nt::AddPolledEntryListener(m_poller, entry, flags);
- }
-
- NT_EntryListener AddListener(std::string_view prefix,
- unsigned int flags = kDefaultListenerFlags) {
- return nt::AddPolledEntryListener(m_poller, prefix, flags);
- }
-
- std::vector<nt::EntryNotification> PollListener() {
- bool timedOut = false;
- return nt::PollEntryListener(m_poller, 0, &timedOut);
- }
-
- bool IsConnected() const;
-
- private:
- NT_Inst m_inst;
- NT_EntryListenerPoller m_poller;
-};
-
-} // namespace glass
diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h b/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h
index a8f0f9b..cba34cc 100644
--- a/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h
+++ b/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h
@@ -9,13 +9,15 @@
#include <string_view>
#include <vector>
-#include <ntcore_c.h>
-#include <ntcore_cpp.h>
+#include <networktables/NetworkTableInstance.h>
+#include <networktables/NetworkTableListener.h>
+#include <networktables/StringTopic.h>
+#include <networktables/Topic.h>
+#include <wpi/DenseMap.h>
#include <wpi/StringMap.h>
#include "glass/Model.h"
#include "glass/Provider.h"
-#include "glass/networktables/NetworkTablesHelper.h"
namespace glass {
@@ -23,9 +25,10 @@
namespace detail {
struct NTProviderFunctions {
- using Exists = std::function<bool(NT_Inst inst, const char* path)>;
- using CreateModel =
- std::function<std::unique_ptr<Model>(NT_Inst inst, const char* path)>;
+ using Exists =
+ std::function<bool(nt::NetworkTableInstance inst, const char* path)>;
+ using CreateModel = std::function<std::unique_ptr<Model>(
+ nt::NetworkTableInstance inst, const char* path)>;
using ViewExists = std::function<bool(Model*, const char* path)>;
using CreateView =
std::function<std::unique_ptr<View>(Window*, Model*, const char* path)>;
@@ -41,14 +44,14 @@
using Provider::CreateViewFunc;
explicit NetworkTablesProvider(Storage& storage);
- NetworkTablesProvider(Storage& storage, NT_Inst inst);
+ NetworkTablesProvider(Storage& storage, nt::NetworkTableInstance inst);
/**
* Get the NetworkTables instance being used for this provider.
*
* @return NetworkTables instance
*/
- NT_Inst GetInstance() const { return m_nt.GetInstance(); }
+ nt::NetworkTableInstance GetInstance() const { return m_inst; }
/**
* Perform global initialization. This should be called prior to
@@ -74,8 +77,9 @@
private:
void Update() override;
- NetworkTablesHelper m_nt;
- NT_EntryListener m_listener{0};
+ nt::NetworkTableInstance m_inst;
+ nt::NetworkTableListenerPoller m_poller;
+ NT_Listener m_listener{0};
// cached mapping from table name to type string
Storage& m_typeCache;
@@ -88,17 +92,25 @@
// mapping from .type string to model/view creators
wpi::StringMap<Builder> m_typeMap;
+ struct SubListener {
+ nt::StringSubscriber subscriber;
+ NT_Listener listener;
+ };
+
+ // mapping from .type topic to subscriber/listener
+ wpi::DenseMap<NT_Topic, SubListener> m_topicMap;
+
struct Entry : public ModelEntry {
- Entry(NT_Entry typeEntry, std::string_view name, const Builder& builder)
- : ModelEntry{name, [](NT_Inst, const char*) { return true; },
+ Entry(nt::Topic typeTopic, std::string_view name, const Builder& builder)
+ : ModelEntry{name, [](auto, const char*) { return true; },
builder.createModel},
- typeEntry{typeEntry} {}
- NT_Entry typeEntry;
+ typeTopic{typeTopic} {}
+ nt::Topic typeTopic;
};
void Show(ViewEntry* entry, Window* window) override;
- ViewEntry* GetOrCreateView(const Builder& builder, NT_Entry typeEntry,
+ ViewEntry* GetOrCreateView(const Builder& builder, nt::Topic typeTopic,
std::string_view name);
};
diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTablesSettings.h b/glass/src/libnt/native/include/glass/networktables/NetworkTablesSettings.h
index a1a1c93..5f2ea19 100644
--- a/glass/src/libnt/native/include/glass/networktables/NetworkTablesSettings.h
+++ b/glass/src/libnt/native/include/glass/networktables/NetworkTablesSettings.h
@@ -22,7 +22,7 @@
class NetworkTablesSettings {
public:
- explicit NetworkTablesSettings(Storage& storage,
+ explicit NetworkTablesSettings(std::string_view clientName, Storage& storage,
NT_Inst inst = nt::GetDefaultInstance());
/**
@@ -37,9 +37,12 @@
bool m_restart = true;
bool m_serverOption = true;
EnumSetting m_mode;
- std::string& m_iniName;
+ std::string& m_persistentFilename;
std::string& m_serverTeam;
std::string& m_listenAddress;
+ std::string& m_clientName;
+ int& m_port3;
+ int& m_port4;
bool& m_dsClient;
class Thread : public wpi::SafeThread {
@@ -54,6 +57,9 @@
std::string m_iniName;
std::string m_serverTeam;
std::string m_listenAddress;
+ std::string m_clientName;
+ int m_port3;
+ int m_port4;
bool m_dsClient;
};
wpi::SafeThreadOwner<Thread> m_thread;