mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-22 01:11:42 +00:00
Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa34aacf6e | ||
|
|
63512bbbb8 | ||
|
|
9227b2166e | ||
|
|
fbf92e9190 | ||
|
|
2108a61362 | ||
|
|
0a66479693 | ||
|
|
b510c17ef6 | ||
|
|
e7a7eb2e93 | ||
|
|
a465f2d8f0 | ||
|
|
a3364422fa | ||
|
|
df3242a40a | ||
|
|
00abb8c1e0 | ||
|
|
c886273fd7 | ||
|
|
53b5fd2ace | ||
|
|
56b758320f | ||
|
|
08f298e4cd | ||
|
|
6d0c5b19db | ||
|
|
0d22cf5ff7 | ||
|
|
32ec5b3f75 | ||
|
|
e5c4c6b1a7 | ||
|
|
099d048d9e | ||
|
|
4af84a1c12 | ||
|
|
ce3686b80d | ||
|
|
4b0eecaee0 | ||
|
|
edf4ded412 | ||
|
|
4c46b6aff9 | ||
|
|
490ca4a68a | ||
|
|
cbb5b0b802 | ||
|
|
bb7053d9ee | ||
|
|
9efed9a533 | ||
|
|
dbbfe1aed2 | ||
|
|
de65a135c3 | ||
|
|
3e9788cdff | ||
|
|
ecb072724d | ||
|
|
0d462a4561 | ||
|
|
ba37986561 | ||
|
|
25ab9cda92 | ||
|
|
2f6251d4a6 | ||
|
|
e9a7bed988 | ||
|
|
9cc14bbb43 | ||
|
|
8068369542 | ||
|
|
805c837a42 | ||
|
|
fd18577ba0 | ||
|
|
74dea9f05e | ||
|
|
9eef79d638 | ||
|
|
843574a810 | ||
|
|
226ef35212 | ||
|
|
b30664d630 | ||
|
|
804e5ce236 | ||
|
|
49af88f2bb | ||
|
|
d56314f866 | ||
|
|
43975ac7cc | ||
|
|
5483464158 | ||
|
|
785e7dd85c | ||
|
|
e57ded8c39 | ||
|
|
01f0394419 | ||
|
|
59be120982 | ||
|
|
37f065032f | ||
|
|
22a170bee7 | ||
|
|
2f310a748c | ||
|
|
b43ec87f57 | ||
|
|
19267bef0c | ||
|
|
84cbd48d84 | ||
|
|
1f35750865 | ||
|
|
8230fc631d | ||
|
|
b879a6f8c6 | ||
|
|
49459d3e45 | ||
|
|
4079eabe9b | ||
|
|
fe5d226a19 | ||
|
|
b7535252c2 | ||
|
|
b61ac6db33 | ||
|
|
7b828ce84f | ||
|
|
08a536291b | ||
|
|
193a10d020 | ||
|
|
7867bbde0e | ||
|
|
fa7c01b598 | ||
|
|
2b81610248 | ||
|
|
a4a369b8da | ||
|
|
d991f6e435 | ||
|
|
a27a047ae8 | ||
|
|
2f96cae31a | ||
|
|
83ef8f9658 | ||
|
|
4054893669 | ||
|
|
f75acd11ce | ||
|
|
8bf67b1b33 | ||
|
|
49bb1358d8 | ||
|
|
9c4c07c0f9 | ||
|
|
1a47cc2e86 | ||
|
|
7cd30cffbc | ||
|
|
92aecab2ef | ||
|
|
8785bba080 | ||
|
|
9e5b7b8040 | ||
|
|
917906530a | ||
|
|
00aa66e4fd | ||
|
|
893320544a | ||
|
|
b95d0e060d | ||
|
|
008232b43c | ||
|
|
522be348f4 | ||
|
|
d48a83dee2 | ||
|
|
504fa22143 | ||
|
|
b2b25bf09f | ||
|
|
ce3dc4eb3b | ||
|
|
1ea48caa7d | ||
|
|
fb101925a7 | ||
|
|
657951f6dd | ||
|
|
a60ca9d71c | ||
|
|
f8a45f1558 | ||
|
|
ecba8b99a8 | ||
|
|
e95e88fdf9 | ||
|
|
371d15dec3 | ||
|
|
cb9b8938af | ||
|
|
3b084ecbe0 | ||
|
|
27ba096ea1 | ||
|
|
42c997a3c4 | ||
|
|
5f1a025f27 | ||
|
|
0ebf79b54c | ||
|
|
a8c465f3fb | ||
|
|
a7b1ab683d | ||
|
|
bd6479dc29 | ||
|
|
5cb0340a8c | ||
|
|
ab0e8c37a7 | ||
|
|
b74ac1c645 |
@@ -156,7 +156,7 @@ SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: c++17
|
||||
Standard: c++20
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,6 +9,8 @@ simgui-ds.json
|
||||
simgui-window.json
|
||||
simgui.json
|
||||
|
||||
networktables.json
|
||||
|
||||
# Created by the jenkins test script
|
||||
test-reports
|
||||
|
||||
|
||||
@@ -40,8 +40,39 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system.
|
||||
While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome.
|
||||
|
||||
### Math documentation
|
||||
|
||||
When writing math expressions in documentation, use https://www.unicodeit.net/ to convert LaTeX to a Unicode equivalent that's easier to read. Not all expressions will translate (e.g., superscripts of superscripts) so focus on making it readable by someone who isn't familiar with LaTeX. If content on multiple lines needs to be aligned in Doxygen/Javadoc comments (e.g., integration/summation limits, matrices packed with square brackets and superscripts for them), put them in @verbatim/@endverbatim blocks in Doxygen or `<pre>` tags in Javadoc so they render with monospace font.
|
||||
|
||||
The LaTeX to Unicode conversions can also be done locally via the unicodeit Python package. To install it, execute:
|
||||
```bash
|
||||
pip install --user unicodeit
|
||||
```
|
||||
|
||||
Here's example usage:
|
||||
```bash
|
||||
$ python -m unicodeit.cli 'x_{k+1} = Ax_k + Bu_k'
|
||||
xₖ₊₁ = Axₖ + Buₖ
|
||||
```
|
||||
|
||||
On Linux, this process can be streamlined further by adding the following Bash function to your .bashrc (requires `wl-clipboard` on Wayland or `xclip` on X11):
|
||||
```bash
|
||||
# Converts LaTeX to Unicode, prints the result, and copies it to the clipboard
|
||||
uc() {
|
||||
if [ $WAYLAND_DISPLAY ]; then
|
||||
python -m unicodeit.cli $@ | tee >(wl-copy -n)
|
||||
else
|
||||
python -m unicodeit.cli $@ | tee >(xclip -sel)
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
Here's example usage:
|
||||
```bash
|
||||
$ uc 'x_{k+1} = Ax_k + Bu_k'
|
||||
xₖ₊₁ = Axₖ + Buₖ
|
||||
```
|
||||
|
||||
## Submitting Changes
|
||||
|
||||
### Pull Request Format
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2022 FIRST and other WPILib contributors
|
||||
Copyright (c) 2009-2023 FIRST and other WPILib contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -16,7 +16,7 @@ We provide two base types of artifacts.
|
||||
|
||||
The first types are Java artifacts. These are usually published as `jar` files. Usually, the actual jar file is published with no classifier. The sources are published with the `-sources` classifier, and the javadocs are published with the `-javadoc` classifier.
|
||||
|
||||
The second types are native artifacts. These are usually published as `zip` files (except for the `JNI` artifact types, which are `jar` files. See below for information on this). The `-sources` and `-headers` classifiers contain the sources and headers respecively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The platform artifact only contains the binaries for a specific platform. In addition, we provide a `-all` classifier. This classifer combines all of the platform artifacts into a single artifact. This is useful for tools that cannot determine what version to use during builds. However, we recommend using the platform specific classifier when possible. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
The second types are native artifacts. These are usually published as `zip` files (except for the `JNI` artifact types, which are `jar` files. See below for information on this). The `-sources` and `-headers` classifiers contain the sources and headers respectively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The platform artifact only contains the binaries for a specific platform. In addition, we provide a `-all` classifier. This classifier combines all of the platform artifacts into a single artifact. This is useful for tools that cannot determine what version to use during builds. However, we recommend using the platform specific classifier when possible. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
|
||||
## Artifact Names
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
|
||||
## Requirements
|
||||
|
||||
- [JDK 11](https://adoptopenjdk.net/)
|
||||
- [JDK 11](https://adoptium.net/temurin/releases/?version=11)
|
||||
- Note that the JRE is insufficient; the full JDK is required
|
||||
- On Ubuntu, run `sudo apt install openjdk-11-jdk`
|
||||
- On Windows, install the JDK 11 .msi from the link above
|
||||
@@ -48,7 +48,7 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
- C++ compiler
|
||||
- On Linux, install GCC 11 or greater
|
||||
- On Windows, install [Visual Studio Community 2022](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
|
||||
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
|
||||
- On macOS, install the Xcode command-line build tools via `xcode-select --install`. Xcode 13 or later is required.
|
||||
- ARM compiler toolchain
|
||||
- Run `./gradlew installRoboRioToolchain` after cloning this repository
|
||||
- If the WPILib installer was used, this toolchain is already installed
|
||||
|
||||
@@ -189,7 +189,10 @@ public class AprilTagFieldLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a field layout from a resource within a jar file.
|
||||
* Deserializes a field layout from a resource within a internal jar file.
|
||||
*
|
||||
* <p>Users should use {@link AprilTagFields#loadAprilTagLayoutField()} to load official layouts
|
||||
* and {@link #AprilTagFieldLayout(String)} for custom layouts.
|
||||
*
|
||||
* @param resourcePath The absolute path of the resource
|
||||
* @return The deserialized layout
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
package edu.wpi.first.apriltag;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public enum AprilTagFields {
|
||||
k2022RapidReact("2022-rapidreact.json"),
|
||||
k2023ChargedUp("2023-chargedup.json");
|
||||
@@ -18,4 +20,14 @@ public enum AprilTagFields {
|
||||
AprilTagFields(String resourceFile) {
|
||||
m_resourceFile = kBaseResourceDir + resourceFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link AprilTagFieldLayout} from the resource JSON.
|
||||
*
|
||||
* @return AprilTagFieldLayout of the field
|
||||
* @throws IOException If the layout does not exist
|
||||
*/
|
||||
public AprilTagFieldLayout loadAprilTagLayoutField() throws IOException {
|
||||
return AprilTagFieldLayout.loadFromResource(m_resourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class WPILIB_DLLEXPORT AprilTagFieldLayout {
|
||||
/**
|
||||
* Sets the origin for tag pose transformation.
|
||||
*
|
||||
* This tranforms the Pose3ds returned by GetTagPose(int) to return the
|
||||
* This transforms the Pose3ds returned by GetTagPose(int) to return the
|
||||
* correct pose relative to the provided origin.
|
||||
*
|
||||
* @param origin The new origin for tag transformations
|
||||
|
||||
@@ -1,415 +1,440 @@
|
||||
{
|
||||
"tags" : [ {
|
||||
"ID" : 0,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : -0.0035306,
|
||||
"y" : 7.578928199999999,
|
||||
"z" : 0.8858503999999999
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 1.0,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.0
|
||||
"tags": [
|
||||
{
|
||||
"ID": 0,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": -0.0035306,
|
||||
"y": 7.578928199999999,
|
||||
"z": 0.8858503999999999
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 1,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 3.2327088,
|
||||
"y": 5.486654,
|
||||
"z": 1.7254728
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 2,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 3.067812,
|
||||
"y": 5.3305202,
|
||||
"z": 1.3762228
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.7071067811865476,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": -0.7071067811865475
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 3,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 0.0039878,
|
||||
"y": 5.058536999999999,
|
||||
"z": 0.80645
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 4,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 0.0039878,
|
||||
"y": 3.5124898,
|
||||
"z": 0.80645
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 5,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 0.12110719999999998,
|
||||
"y": 1.7178274,
|
||||
"z": 0.8906002000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.9196502204050923,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 6,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 0.8733027999999999,
|
||||
"y": 0.9412985999999999,
|
||||
"z": 0.8906002000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.9196502204050923,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 7,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 1.6150844,
|
||||
"y": 0.15725139999999999,
|
||||
"z": 0.8906002000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.9196502204050923,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 10,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 16.4627306,
|
||||
"y": 0.6506718,
|
||||
"z": 0.8858503999999999
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 6.123233995736766E-17,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 11,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 13.2350002,
|
||||
"y": 2.743454,
|
||||
"z": 1.7254728
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 6.123233995736766E-17,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 12,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 13.391388000000001,
|
||||
"y": 2.8998418,
|
||||
"z": 1.3762228
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.7071067811865476,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.7071067811865475
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 13,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 16.4552122,
|
||||
"y": 3.1755079999999998,
|
||||
"z": 0.80645
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 6.123233995736766E-17,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 14,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 16.4552122,
|
||||
"y": 4.7171356,
|
||||
"z": 0.80645
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 6.123233995736766E-17,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 15,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 16.3350194,
|
||||
"y": 6.5149729999999995,
|
||||
"z": 0.8937752
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.37298778257580906,
|
||||
"X": -0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 16,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 15.5904946,
|
||||
"y": 7.292695599999999,
|
||||
"z": 0.8906002000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.37298778257580906,
|
||||
"X": -0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 17,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 14.847188999999998,
|
||||
"y": 8.0691228,
|
||||
"z": 0.8906002000000001
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.37298778257580906,
|
||||
"X": -0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 40,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 7.874127,
|
||||
"y": 4.9131728,
|
||||
"z": 0.7032752
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.5446390350150271,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.838670567945424
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 41,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 7.4312271999999995,
|
||||
"y": 3.759327,
|
||||
"z": 0.7032752
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.20791169081775934,
|
||||
"X": -0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.9781476007338057
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 42,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 8.585073,
|
||||
"y": 3.3164272,
|
||||
"z": 0.7032752
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.838670567945424,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": -0.5446390350150271
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 43,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 9.0279728,
|
||||
"y": 4.470273,
|
||||
"z": 0.7032752
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.9781476007338057,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.20791169081775934
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 50,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 7.6790296,
|
||||
"y": 4.3261534,
|
||||
"z": 2.4177244
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.17729273396782605,
|
||||
"X": -0.22744989571511945,
|
||||
"Y": 0.04215534644161733,
|
||||
"Z": 0.9565859910053995
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 51,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 8.0182466,
|
||||
"y": 3.5642296,
|
||||
"z": 2.4177244
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.5510435465842192,
|
||||
"X": -0.19063969497246985,
|
||||
"Y": -0.13102303230819815,
|
||||
"Z": 0.8017733354717242
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 52,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 8.7801704,
|
||||
"y": 3.9034466,
|
||||
"z": 2.4177244
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": -0.9565859910053994,
|
||||
"X": -0.04215534644161739,
|
||||
"Y": -0.22744989571511942,
|
||||
"Z": 0.17729273396782633
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 53,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 8.4409534,
|
||||
"y": 4.6653704,
|
||||
"z": 2.4177244
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.8017733354717241,
|
||||
"X": -0.1310230323081982,
|
||||
"Y": 0.19063969497246983,
|
||||
"Z": 0.5510435465842194
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 1,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 3.2327088,
|
||||
"y" : 5.486654,
|
||||
"z" : 1.7254728
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 1.0,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 2,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 3.067812,
|
||||
"y" : 5.3305202,
|
||||
"z" : 1.3762228
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.7071067811865476,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : -0.7071067811865475
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 3,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 0.0039878,
|
||||
"y" : 5.058536999999999,
|
||||
"z" : 0.80645
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 1.0,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 4,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 0.0039878,
|
||||
"y" : 3.5124898,
|
||||
"z" : 0.80645
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 1.0,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 5,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 0.12110719999999998,
|
||||
"y" : 1.7178274,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.9196502204050923,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 6,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 0.8733027999999999,
|
||||
"y" : 0.9412985999999999,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.9196502204050923,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 7,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 1.6150844,
|
||||
"y" : 0.15725139999999999,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.9196502204050923,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.39273842708457407
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 10,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 16.4627306,
|
||||
"y" : 0.6506718,
|
||||
"z" : 0.8858503999999999
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 6.123233995736766E-17,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 11,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 13.2350002,
|
||||
"y" : 2.743454,
|
||||
"z" : 1.7254728
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 6.123233995736766E-17,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 12,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 13.391388000000001,
|
||||
"y" : 2.8998418,
|
||||
"z" : 1.3762228
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.7071067811865476,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.7071067811865475
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 13,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 16.4552122,
|
||||
"y" : 3.1755079999999998,
|
||||
"z" : 0.80645
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 6.123233995736766E-17,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 14,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 16.4552122,
|
||||
"y" : 4.7171356,
|
||||
"z" : 0.80645
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 6.123233995736766E-17,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 15,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 16.3350194,
|
||||
"y" : 6.5149729999999995,
|
||||
"z" : 0.8937752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.37298778257580906,
|
||||
"X" : -0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 16,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 15.5904946,
|
||||
"y" : 7.292695599999999,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.37298778257580906,
|
||||
"X" : -0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 17,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 14.847188999999998,
|
||||
"y" : 8.0691228,
|
||||
"z" : 0.8906002000000001
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.37298778257580906,
|
||||
"X" : -0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.9278362538989199
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 40,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 7.874127,
|
||||
"y" : 4.9131728,
|
||||
"z" : 0.7032752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.5446390350150271,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.838670567945424
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 41,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 7.4312271999999995,
|
||||
"y" : 3.759327,
|
||||
"z" : 0.7032752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.20791169081775934,
|
||||
"X" : -0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.9781476007338057
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 42,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 8.585073,
|
||||
"y" : 3.3164272,
|
||||
"z" : 0.7032752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.838670567945424,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : -0.5446390350150271
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 43,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 9.0279728,
|
||||
"y" : 4.470273,
|
||||
"z" : 0.7032752
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.9781476007338057,
|
||||
"X" : 0.0,
|
||||
"Y" : 0.0,
|
||||
"Z" : 0.20791169081775934
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 50,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 7.6790296,
|
||||
"y" : 4.3261534,
|
||||
"z" : 2.4177244
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.17729273396782605,
|
||||
"X" : -0.22744989571511945,
|
||||
"Y" : 0.04215534644161733,
|
||||
"Z" : 0.9565859910053995
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 51,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 8.0182466,
|
||||
"y" : 3.5642296,
|
||||
"z" : 2.4177244
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.5510435465842192,
|
||||
"X" : -0.19063969497246985,
|
||||
"Y" : -0.13102303230819815,
|
||||
"Z" : 0.8017733354717242
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 52,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 8.7801704,
|
||||
"y" : 3.9034466,
|
||||
"z" : 2.4177244
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : -0.9565859910053994,
|
||||
"X" : -0.04215534644161739,
|
||||
"Y" : -0.22744989571511942,
|
||||
"Z" : 0.17729273396782633
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"ID" : 53,
|
||||
"pose" : {
|
||||
"translation" : {
|
||||
"x" : 8.4409534,
|
||||
"y" : 4.6653704,
|
||||
"z" : 2.4177244
|
||||
},
|
||||
"rotation" : {
|
||||
"quaternion" : {
|
||||
"W" : 0.8017733354717241,
|
||||
"X" : -0.1310230323081982,
|
||||
"Y" : 0.19063969497246983,
|
||||
"Z" : 0.5510435465842194
|
||||
}
|
||||
}
|
||||
}
|
||||
} ],
|
||||
"field" : {
|
||||
"length" : 16.4592,
|
||||
"width" : 8.2296
|
||||
],
|
||||
"field": {
|
||||
"length": 16.4592,
|
||||
"width": 8.2296
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,16 +23,13 @@ class LoadConfigTest {
|
||||
@ParameterizedTest
|
||||
@EnumSource(AprilTagFields.class)
|
||||
void testLoad(AprilTagFields field) {
|
||||
AprilTagFieldLayout layout =
|
||||
Assertions.assertDoesNotThrow(
|
||||
() -> AprilTagFieldLayout.loadFromResource(field.m_resourceFile));
|
||||
AprilTagFieldLayout layout = Assertions.assertDoesNotThrow(field::loadAprilTagLayoutField);
|
||||
assertNotNull(layout);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2022RapidReact() throws IOException {
|
||||
AprilTagFieldLayout layout =
|
||||
AprilTagFieldLayout.loadFromResource(AprilTagFields.k2022RapidReact.m_resourceFile);
|
||||
AprilTagFieldLayout layout = AprilTagFields.k2022RapidReact.loadAprilTagLayoutField();
|
||||
|
||||
// Blue Hangar Truss - Hub
|
||||
Pose3d expectedPose =
|
||||
|
||||
@@ -474,6 +474,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture() {
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
|
||||
::GetInstance();
|
||||
cs::UsbCamera camera{fmt::format("USB Camera {}", dev), dev};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -483,6 +484,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
|
||||
int dev) {
|
||||
::GetInstance();
|
||||
cs::UsbCamera camera{name, dev};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -492,6 +494,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
|
||||
std::string_view path) {
|
||||
::GetInstance();
|
||||
cs::UsbCamera camera{name, path};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -517,6 +520,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::span<const std::string> hosts) {
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
std::string_view host) {
|
||||
::GetInstance();
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -526,6 +530,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
const char* host) {
|
||||
::GetInstance();
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -535,6 +540,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
const std::string& host) {
|
||||
::GetInstance();
|
||||
cs::AxisCamera camera{name, host};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -544,6 +550,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
std::span<const std::string> hosts) {
|
||||
::GetInstance();
|
||||
cs::AxisCamera camera{name, hosts};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
@@ -552,10 +559,11 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
}
|
||||
|
||||
cs::MjpegServer CameraServer::AddSwitchedCamera(std::string_view name) {
|
||||
auto& inst = ::GetInstance();
|
||||
// create a dummy CvSource
|
||||
cs::CvSource source{name, cs::VideoMode::PixelFormat::kMJPEG, 160, 120, 30};
|
||||
cs::MjpegServer server = StartAutomaticCapture(source);
|
||||
::GetInstance().m_fixedSources[server.GetHandle()] = source.GetHandle();
|
||||
inst.m_fixedSources[server.GetHandle()] = source.GetHandle();
|
||||
|
||||
return server;
|
||||
}
|
||||
@@ -632,6 +640,7 @@ cs::CvSink CameraServer::GetVideo(std::string_view name) {
|
||||
|
||||
cs::CvSource CameraServer::PutVideo(std::string_view name, int width,
|
||||
int height) {
|
||||
::GetInstance();
|
||||
cs::CvSource source{name, cs::VideoMode::kMJPEG, width, height, 30};
|
||||
StartAutomaticCapture(source);
|
||||
return source;
|
||||
@@ -648,6 +657,7 @@ cs::MjpegServer CameraServer::AddServer(std::string_view name) {
|
||||
}
|
||||
|
||||
cs::MjpegServer CameraServer::AddServer(std::string_view name, int port) {
|
||||
::GetInstance();
|
||||
cs::MjpegServer server{name, port};
|
||||
AddServer(server);
|
||||
return server;
|
||||
|
||||
@@ -143,7 +143,7 @@ public class CameraServerJNI {
|
||||
public static native void releaseSource(int source);
|
||||
|
||||
//
|
||||
// Camera Source Common Property Fuctions
|
||||
// Camera Source Common Property Functions
|
||||
//
|
||||
public static native void setCameraBrightness(int source, int brightness);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
namespace cs {
|
||||
|
||||
// The UnlimitedHandleResource class is a way to track handles. This version
|
||||
// allows an unlimted number of handles that are allocated sequentially. When
|
||||
// allows an unlimited number of handles that are allocated sequentially. When
|
||||
// possible, indices are reused to save memory usage and keep the array length
|
||||
// down.
|
||||
// However, automatic array management has not been implemented, but might be in
|
||||
|
||||
@@ -439,7 +439,7 @@ void ReleaseSource(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
//
|
||||
// Camera Source Common Property Fuctions
|
||||
// Camera Source Common Property Functions
|
||||
//
|
||||
|
||||
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
|
||||
|
||||
@@ -323,7 +323,7 @@ void CS_ReleaseSource(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_source_prop_cfunc Camera Source Common Property Fuctions
|
||||
* @defgroup cscore_source_prop_cfunc Camera Source Common Property Functions
|
||||
* @{
|
||||
*/
|
||||
void CS_SetCameraBrightness(CS_Source source, int brightness,
|
||||
|
||||
@@ -262,7 +262,7 @@ void ReleaseSource(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_camera_property_func Camera Source Common Property Fuctions
|
||||
* @defgroup cscore_camera_property_func Camera Source Common Property Functions
|
||||
* @{
|
||||
*/
|
||||
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status);
|
||||
|
||||
@@ -79,7 +79,7 @@ using namespace cs;
|
||||
default:
|
||||
OBJCERROR(
|
||||
"Camera access explicitly blocked for application. No cameras are "
|
||||
"accessable");
|
||||
"accessible");
|
||||
self.isAuthorized = false;
|
||||
// TODO log
|
||||
break;
|
||||
@@ -524,7 +524,7 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
|
||||
if (!self.isAuthorized) {
|
||||
OBJCERROR(
|
||||
"Camera access not authorized for application. No cameras are "
|
||||
"accessable");
|
||||
"accessible");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ STDMETHODIMP SourceReaderCB::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
|
||||
return S_OK;
|
||||
if (SUCCEEDED(hrStatus)) {
|
||||
if (pSample) {
|
||||
// Prcoess sample
|
||||
// Process sample
|
||||
source->ProcessFrame(pSample, m_mode);
|
||||
// DO NOT release the frame
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class UsbCameraTest {
|
||||
static class ConnectVerbose {
|
||||
@Test
|
||||
void setConnectVerboseEnabledTest() {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistant Camera", getNonexistentCameraDev())) {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistent Camera", getNonexistentCameraDev())) {
|
||||
camera.setConnectVerbose(1);
|
||||
|
||||
CompletableFuture<String> result = new CompletableFuture<>();
|
||||
@@ -38,7 +38,7 @@ class UsbCameraTest {
|
||||
|
||||
@Test
|
||||
void setConnectVerboseDisabledTest() {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistant Camera", getNonexistentCameraDev())) {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistent Camera", getNonexistentCameraDev())) {
|
||||
camera.setConnectVerbose(0);
|
||||
|
||||
CompletableFuture<String> result = new CompletableFuture<>();
|
||||
|
||||
@@ -104,6 +104,8 @@ static void DisplayMainMenu() {
|
||||
ImGui::Text("v%s", GetWPILibVersion());
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
|
||||
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate,
|
||||
ImGui::GetIO().Framerate);
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
@@ -466,7 +466,8 @@ static void ValueToCsv(wpi::raw_ostream& os, const Entry& entry,
|
||||
fmt::print(os, "{}", val);
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "int64") {
|
||||
} else if (entry.type == "int64" || entry.type == "int") {
|
||||
// support "int" for compatibility with old NT4 datalogs
|
||||
int64_t val;
|
||||
if (record.GetInteger(&val)) {
|
||||
fmt::print(os, "{}", val);
|
||||
|
||||
@@ -64,6 +64,7 @@ doxygen {
|
||||
cppIncludeRoots.add(it.absolutePath)
|
||||
}
|
||||
}
|
||||
cppIncludeRoots << '../ntcore/build/generated/main/native/include/'
|
||||
|
||||
if (project.hasProperty('docWarningsAsErrors')) {
|
||||
// C++20 shims
|
||||
@@ -238,7 +239,7 @@ task generateJavaDocs(type: Javadoc) {
|
||||
if (JavaVersion.current().isJava8Compatible() && project.hasProperty('docWarningsAsErrors')) {
|
||||
// Treat javadoc warnings as errors.
|
||||
//
|
||||
// The second argument '-quiet' is a hack. The one paramater
|
||||
// The second argument '-quiet' is a hack. The one parameter
|
||||
// addStringOption() doesn't work, so we add '-quiet', which is added
|
||||
// anyway by gradle. See https://github.com/gradle/gradle/issues/2354.
|
||||
//
|
||||
|
||||
@@ -151,7 +151,7 @@ html {
|
||||
--side-nav-arrow-opacity: 0;
|
||||
--side-nav-arrow-hover-opacity: 0.9;
|
||||
|
||||
/* height of an item in any tree / collapsable table */
|
||||
/* height of an item in any tree / collapsible table */
|
||||
--tree-item-height: 30px;
|
||||
|
||||
--darkmode-toggle-button-icon: "☀️";
|
||||
@@ -1615,7 +1615,7 @@ SOFTWARE.
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enought to contain the page title (logo + title + version)
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 340px;
|
||||
--menu-display: none;
|
||||
|
||||
@@ -79,7 +79,7 @@ public class FieldConfig {
|
||||
*
|
||||
* @param resourcePath The path to the resource file
|
||||
* @return The field configuration
|
||||
* @throws IOException Throws if the resoure could not be loaded
|
||||
* @throws IOException Throws if the resource could not be loaded
|
||||
*/
|
||||
public static FieldConfig loadFromResource(String resourcePath) throws IOException {
|
||||
try (InputStream stream = FieldConfig.class.getResourceAsStream(resourcePath);
|
||||
|
||||
@@ -2,9 +2,18 @@
|
||||
"game": "FIRST Power Up",
|
||||
"field-image": "2018-field.jpg",
|
||||
"field-corners": {
|
||||
"top-left": [125, 20],
|
||||
"bottom-right": [827, 370]
|
||||
"top-left": [
|
||||
125,
|
||||
20
|
||||
],
|
||||
"bottom-right": [
|
||||
827,
|
||||
370
|
||||
]
|
||||
},
|
||||
"field-size": [54, 27],
|
||||
"field-size": [
|
||||
54,
|
||||
27
|
||||
],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game" : "Destination: Deep Space",
|
||||
"field-image" : "2019-field.jpg",
|
||||
"field-corners": {
|
||||
"top-left" : [217, 40],
|
||||
"bottom-right" : [1372, 615]
|
||||
},
|
||||
"field-size" : [54, 27],
|
||||
"field-unit" : "foot"
|
||||
"game": "Destination: Deep Space",
|
||||
"field-image": "2019-field.jpg",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
217,
|
||||
40
|
||||
],
|
||||
"bottom-right": [
|
||||
1372,
|
||||
615
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
54,
|
||||
27
|
||||
],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game" : "Infinite Recharge",
|
||||
"field-image" : "2020-field.png",
|
||||
"field-corners": {
|
||||
"top-left" : [96, 25],
|
||||
"bottom-right" : [1040, 514]
|
||||
},
|
||||
"field-size" : [52.4375, 26.9375],
|
||||
"field-unit" : "foot"
|
||||
}
|
||||
"game": "Infinite Recharge",
|
||||
"field-image": "2020-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
96,
|
||||
25
|
||||
],
|
||||
"bottom-right": [
|
||||
1040,
|
||||
514
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
52.4375,
|
||||
26.9375
|
||||
],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game": "Barrel Racing Path",
|
||||
"field-image": "2021-barrel.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
"game": "Barrel Racing Path",
|
||||
"field-image": "2021-barrel.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
20,
|
||||
20
|
||||
],
|
||||
"bottom-right": [
|
||||
780,
|
||||
400
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
30,
|
||||
15
|
||||
],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game": "Bounce Path",
|
||||
"field-image": "2021-bounce.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
"game": "Bounce Path",
|
||||
"field-image": "2021-bounce.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
20,
|
||||
20
|
||||
],
|
||||
"bottom-right": [
|
||||
780,
|
||||
400
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
30,
|
||||
15
|
||||
],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game": "Galactic Search A",
|
||||
"field-image": "2021-galacticsearcha.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
"game": "Galactic Search A",
|
||||
"field-image": "2021-galacticsearcha.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
20,
|
||||
20
|
||||
],
|
||||
"bottom-right": [
|
||||
780,
|
||||
400
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
30,
|
||||
15
|
||||
],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game": "Galactic Search B",
|
||||
"field-image": "2021-galacticsearchb.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
"game": "Galactic Search B",
|
||||
"field-image": "2021-galacticsearchb.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
20,
|
||||
20
|
||||
],
|
||||
"bottom-right": [
|
||||
780,
|
||||
400
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
30,
|
||||
15
|
||||
],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game": "Infinite Recharge 2021",
|
||||
"field-image": "2021-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [127, 34],
|
||||
"bottom-right": [1323, 649]
|
||||
},
|
||||
"field-size": [52.4375, 26.9375],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
"game": "Infinite Recharge 2021",
|
||||
"field-image": "2021-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
127,
|
||||
34
|
||||
],
|
||||
"bottom-right": [
|
||||
1323,
|
||||
649
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
52.4375,
|
||||
26.9375
|
||||
],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game": "Slalom Path",
|
||||
"field-image": "2021-slalom.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
"game": "Slalom Path",
|
||||
"field-image": "2021-slalom.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
20,
|
||||
20
|
||||
],
|
||||
"bottom-right": [
|
||||
780,
|
||||
400
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
30,
|
||||
15
|
||||
],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game": "Rapid React",
|
||||
"field-image": "2022-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [74, 50],
|
||||
"bottom-right": [1774, 900]
|
||||
},
|
||||
"field-size": [54, 27],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
"game": "Rapid React",
|
||||
"field-image": "2022-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
74,
|
||||
50
|
||||
],
|
||||
"bottom-right": [
|
||||
1774,
|
||||
900
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
54,
|
||||
27
|
||||
],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"game": "Charged Up",
|
||||
"field-image": "2023-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [46, 36],
|
||||
"bottom-right": [1088, 544]
|
||||
},
|
||||
"field-size": [54.27083, 26.2916],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
"game": "Charged Up",
|
||||
"field-image": "2023-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
46,
|
||||
36
|
||||
],
|
||||
"bottom-right": [
|
||||
1088,
|
||||
544
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
54.27083,
|
||||
26.2916
|
||||
],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ static bool gSetEnterKey = false;
|
||||
static bool gKeyEdit = false;
|
||||
static int* gEnterKey;
|
||||
static void (*gPrevKeyCallback)(GLFWwindow*, int, int, int, int);
|
||||
static bool gNetworkTablesDebugLog = false;
|
||||
|
||||
static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
|
||||
int action, int mods) {
|
||||
@@ -72,9 +73,8 @@ static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
|
||||
static void NtInitialize() {
|
||||
auto inst = nt::GetDefaultInstance();
|
||||
auto poller = nt::CreateListenerPoller(inst);
|
||||
nt::AddPolledListener(
|
||||
poller, inst,
|
||||
NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE);
|
||||
nt::AddPolledListener(poller, inst, NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE);
|
||||
nt::AddPolledLogger(poller, 0, 100);
|
||||
gui::AddEarlyExecute([poller] {
|
||||
auto win = gui::GetSystemWindow();
|
||||
if (!win) {
|
||||
@@ -98,6 +98,8 @@ static void NtInitialize() {
|
||||
level = "ERROR: ";
|
||||
} else if (msg->level >= NT_LOG_WARNING) {
|
||||
level = "WARNING: ";
|
||||
} else if (msg->level < NT_LOG_INFO && !gNetworkTablesDebugLog) {
|
||||
continue;
|
||||
}
|
||||
gNetworkTablesLog.Append(fmt::format(
|
||||
"{}{} ({}:{})\n", level, msg->message, msg->filename, msg->line));
|
||||
@@ -232,6 +234,8 @@ int main(int argc, char** argv) {
|
||||
if (gNetworkTablesLogWindow) {
|
||||
gNetworkTablesLogWindow->DisplayMenuItem("NetworkTables Log");
|
||||
}
|
||||
ImGui::MenuItem("NetworkTables Debug Logging", nullptr,
|
||||
&gNetworkTablesDebugLog);
|
||||
ImGui::Separator();
|
||||
gNtProvider->DisplayMenu();
|
||||
ImGui::EndMenu();
|
||||
@@ -265,6 +269,8 @@ int main(int argc, char** argv) {
|
||||
ImGui::Text("v%s", GetWPILibVersion());
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
|
||||
ImGui::Text("%.3f ms/frame (%.1f FPS)",
|
||||
1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
@@ -400,6 +400,11 @@ void NetworkTablesModel::ValueSource::UpdateFromValue(
|
||||
}
|
||||
} else {
|
||||
valueChildren.clear();
|
||||
valueStr.clear();
|
||||
wpi::raw_string_ostream os{valueStr};
|
||||
os << '"';
|
||||
os.write_escaped(value.GetString());
|
||||
os << '"';
|
||||
}
|
||||
break;
|
||||
case NT_RAW:
|
||||
@@ -769,25 +774,32 @@ static bool StringToStringArray(std::string_view in,
|
||||
}
|
||||
in = wpi::trim(in);
|
||||
|
||||
wpi::SmallVector<std::string_view, 16> inSplit;
|
||||
wpi::SmallString<32> buf;
|
||||
|
||||
wpi::split(in, inSplit, ',', -1, false);
|
||||
for (auto val : inSplit) {
|
||||
val = wpi::trim(val);
|
||||
if (val.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (val.front() != '"' || val.back() != '"') {
|
||||
fmt::print(stderr,
|
||||
"GUI: NetworkTables: Could not understand value '{}'\n", val);
|
||||
while (!in.empty()) {
|
||||
if (in.front() != '"') {
|
||||
fmt::print(stderr, "GUI: NetworkTables: Expected '\"'");
|
||||
return false;
|
||||
}
|
||||
val.remove_prefix(1);
|
||||
val.remove_suffix(1);
|
||||
out->emplace_back(wpi::UnescapeCString(val, buf).first);
|
||||
in.remove_prefix(1);
|
||||
wpi::SmallString<128> buf;
|
||||
std::string_view val;
|
||||
std::tie(val, in) = wpi::UnescapeCString(in, buf);
|
||||
out->emplace_back(val);
|
||||
if (!in.empty()) {
|
||||
if (in.front() != '"') {
|
||||
fmt::print(stderr, "GUI: NetworkTables: Error escaping string");
|
||||
return false;
|
||||
}
|
||||
in.remove_prefix(1);
|
||||
in = wpi::ltrim(in);
|
||||
}
|
||||
if (!in.empty()) {
|
||||
if (in.front() != ',') {
|
||||
fmt::print(stderr, "GUI: NetworkTables: Expected ','");
|
||||
return false;
|
||||
}
|
||||
in.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -826,9 +838,8 @@ static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
|
||||
break;
|
||||
}
|
||||
case NT_STRING: {
|
||||
// GetString() comes from a std::string, so it's null terminated
|
||||
ImGui::LabelText(typeStr ? typeStr : "string", "%s",
|
||||
val.GetString().data());
|
||||
entry.valueStr.c_str());
|
||||
break;
|
||||
}
|
||||
case NT_BOOLEAN_ARRAY:
|
||||
@@ -938,13 +949,18 @@ static void EmitEntryValueEditable(NetworkTablesModel::Entry& entry,
|
||||
break;
|
||||
}
|
||||
case NT_STRING: {
|
||||
char* v = GetTextBuffer(val.GetString());
|
||||
char* v = GetTextBuffer(entry.valueStr);
|
||||
if (ImGui::InputText(typeStr ? typeStr : "string", v, kTextBufferSize,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (entry.publisher == 0) {
|
||||
entry.publisher = nt::Publish(entry.info.topic, NT_STRING, "string");
|
||||
if (v[0] == '"') {
|
||||
if (entry.publisher == 0) {
|
||||
entry.publisher =
|
||||
nt::Publish(entry.info.topic, NT_STRING, "string");
|
||||
}
|
||||
wpi::SmallString<128> buf;
|
||||
nt::SetString(entry.publisher,
|
||||
wpi::UnescapeCString(v + 1, buf).first);
|
||||
}
|
||||
nt::SetString(entry.publisher, v);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1045,58 +1061,97 @@ static void CreateTopicMenuItem(NetworkTablesModel* model,
|
||||
model->AddEntry(nt::GetTopic(model->GetInstance().GetHandle(), path));
|
||||
if (entry->publisher == 0) {
|
||||
entry->publisher = nt::Publish(entry->info.topic, type, typeStr);
|
||||
// publish a default value so it's editable
|
||||
switch (type) {
|
||||
case NT_BOOLEAN:
|
||||
nt::SetDefaultBoolean(entry->publisher, false);
|
||||
break;
|
||||
case NT_INTEGER:
|
||||
nt::SetDefaultInteger(entry->publisher, 0);
|
||||
break;
|
||||
case NT_FLOAT:
|
||||
nt::SetDefaultFloat(entry->publisher, 0.0);
|
||||
break;
|
||||
case NT_DOUBLE:
|
||||
nt::SetDefaultDouble(entry->publisher, 0.0);
|
||||
break;
|
||||
case NT_STRING:
|
||||
nt::SetDefaultString(entry->publisher, "");
|
||||
break;
|
||||
case NT_BOOLEAN_ARRAY:
|
||||
nt::SetDefaultBooleanArray(entry->publisher, {});
|
||||
break;
|
||||
case NT_INTEGER_ARRAY:
|
||||
nt::SetDefaultIntegerArray(entry->publisher, {});
|
||||
break;
|
||||
case NT_FLOAT_ARRAY:
|
||||
nt::SetDefaultFloatArray(entry->publisher, {});
|
||||
break;
|
||||
case NT_DOUBLE_ARRAY:
|
||||
nt::SetDefaultDoubleArray(entry->publisher, {});
|
||||
break;
|
||||
case NT_STRING_ARRAY:
|
||||
nt::SetDefaultStringArray(entry->publisher, {});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayNetworkTablesAddMenu(NetworkTablesModel* model,
|
||||
std::string_view path,
|
||||
NetworkTablesFlags flags) {
|
||||
static char nameBuffer[kTextBufferSize];
|
||||
|
||||
if (ImGui::BeginMenu("Add new...")) {
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
nameBuffer[0] = '\0';
|
||||
}
|
||||
|
||||
ImGui::InputTextWithHint("New item name", "example", nameBuffer,
|
||||
kTextBufferSize);
|
||||
std::string fullNewPath;
|
||||
if (path == "/") {
|
||||
path = "";
|
||||
}
|
||||
fullNewPath = fmt::format("{}/{}", path, nameBuffer);
|
||||
|
||||
ImGui::Text("Adding: %s", fullNewPath.c_str());
|
||||
ImGui::Separator();
|
||||
auto entry = model->GetEntry(fullNewPath);
|
||||
bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
|
||||
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
|
||||
nameBuffer[0] != '\0') &&
|
||||
!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();
|
||||
}
|
||||
}
|
||||
|
||||
static void EmitParentContextMenu(NetworkTablesModel* model,
|
||||
const std::string& path,
|
||||
NetworkTablesFlags flags) {
|
||||
static char nameBuffer[kTextBufferSize];
|
||||
if (ImGui::BeginPopupContextItem(path.c_str())) {
|
||||
ImGui::Text("%s", path.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::BeginMenu("Add new...")) {
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
nameBuffer[0] = '\0';
|
||||
}
|
||||
|
||||
ImGui::InputTextWithHint("New item name", "example", nameBuffer,
|
||||
kTextBufferSize);
|
||||
std::string fullNewPath;
|
||||
if (path == "/") {
|
||||
fullNewPath = path + nameBuffer;
|
||||
} else {
|
||||
fullNewPath = fmt::format("{}/{}", path, nameBuffer);
|
||||
}
|
||||
|
||||
ImGui::Text("Adding: %s", fullNewPath.c_str());
|
||||
ImGui::Separator();
|
||||
auto entry = model->GetEntry(fullNewPath);
|
||||
bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
|
||||
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
|
||||
nameBuffer[0] != '\0') &&
|
||||
!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();
|
||||
}
|
||||
DisplayNetworkTablesAddMenu(model, path, flags);
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -1280,7 +1335,6 @@ static void DisplayTable(NetworkTablesModel* model,
|
||||
}
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
// EmitParentContextMenu(model, "/", flags);
|
||||
if (flags & NetworkTablesFlags_TreeView) {
|
||||
switch (category) {
|
||||
case ShowPersistent:
|
||||
@@ -1511,6 +1565,7 @@ void NetworkTablesView::Display() {
|
||||
|
||||
void NetworkTablesView::Settings() {
|
||||
m_flags.DisplayMenu();
|
||||
DisplayNetworkTablesAddMenu(m_model, {}, m_flags.GetFlags());
|
||||
}
|
||||
|
||||
bool NetworkTablesView::HasSettings() {
|
||||
|
||||
@@ -194,6 +194,10 @@ void DisplayNetworkTables(
|
||||
NetworkTablesModel* model,
|
||||
NetworkTablesFlags flags = NetworkTablesFlags_Default);
|
||||
|
||||
void DisplayNetworkTablesAddMenu(
|
||||
NetworkTablesModel* model, std::string_view path = {},
|
||||
NetworkTablesFlags flags = NetworkTablesFlags_Default);
|
||||
|
||||
class NetworkTablesFlagsSettings {
|
||||
public:
|
||||
explicit NetworkTablesFlagsSettings(
|
||||
|
||||
@@ -125,7 +125,7 @@ public class DriverStationJNI extends JNIWrapper {
|
||||
|
||||
public static native int sendConsoleLine(String line);
|
||||
|
||||
public static native void refreshDSData();
|
||||
public static native boolean refreshDSData();
|
||||
|
||||
public static native void provideNewDataEventHandle(int handle);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public class SimDevice implements AutoCloseable {
|
||||
* Creates a simulated device.
|
||||
*
|
||||
* <p>The device name must be unique. Returns null if the device name already exists. If multiple
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifer in
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifier in
|
||||
* brackets to the base name, e.g. "device[1]".
|
||||
*
|
||||
* <p>null is returned if not in simulation.
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SimDeviceJNI extends JNIWrapper {
|
||||
* Creates a simulated device.
|
||||
*
|
||||
* <p>The device name must be unique. 0 is returned if the device name already exists. If multiple
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifer in
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifier in
|
||||
* brackets to the base name, e.g. "device[1]".
|
||||
*
|
||||
* <p>0 is returned if not in simulation.
|
||||
|
||||
@@ -69,7 +69,7 @@ void setAnalogNumChannelsToActivate(int32_t channels);
|
||||
* number of active channels and the sample rate.
|
||||
*
|
||||
* When the number of channels changes, use the new value. Otherwise,
|
||||
* return the curent value.
|
||||
* return the current value.
|
||||
*
|
||||
* @return Value to write to the active channels field.
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,7 @@ int32_t HAL_GetFPGAEncoder(HAL_FPGAEncoderHandle fpgaEncoderHandle,
|
||||
/**
|
||||
* Returns the period of the most recent pulse.
|
||||
* Returns the period of the most recent Encoder pulse in seconds.
|
||||
* This method compenstates for the decoding type.
|
||||
* This method compensates for the decoding type.
|
||||
*
|
||||
* @deprecated Use GetRate() in favor of this method. This returns unscaled
|
||||
* periods and GetRate() scales using value from SetDistancePerPulse().
|
||||
|
||||
@@ -43,6 +43,7 @@ struct JoystickDataCache {
|
||||
HAL_JoystickButtons buttons[HAL_kMaxJoysticks];
|
||||
HAL_AllianceStationID allianceStation;
|
||||
float matchTime;
|
||||
HAL_ControlWord controlWord;
|
||||
};
|
||||
static_assert(std::is_standard_layout_v<JoystickDataCache>);
|
||||
// static_assert(std::is_trivial_v<JoystickDataCache>);
|
||||
@@ -104,6 +105,8 @@ void JoystickDataCache::Update() {
|
||||
FRC_NetworkCommunication_getAllianceStation(
|
||||
reinterpret_cast<AllianceStationID_t*>(&allianceStation));
|
||||
FRC_NetworkCommunication_getMatchTime(&matchTime);
|
||||
FRC_NetworkCommunication_getControlWord(
|
||||
reinterpret_cast<ControlWord_t*>(&controlWord));
|
||||
}
|
||||
|
||||
#define CHECK_JOYSTICK_NUMBER(stickNum) \
|
||||
@@ -114,7 +117,7 @@ static HAL_ControlWord newestControlWord;
|
||||
static JoystickDataCache caches[3];
|
||||
static JoystickDataCache* currentRead = &caches[0];
|
||||
static JoystickDataCache* currentReadLocal = &caches[0];
|
||||
static std::atomic<JoystickDataCache*> currentCache{&caches[1]};
|
||||
static std::atomic<JoystickDataCache*> currentCache{nullptr};
|
||||
static JoystickDataCache* lastGiven = &caches[1];
|
||||
static JoystickDataCache* cacheToUpdate = &caches[2];
|
||||
|
||||
@@ -508,17 +511,27 @@ static void newDataOccur(uint32_t refNum) {
|
||||
}
|
||||
}
|
||||
|
||||
void HAL_RefreshDSData(void) {
|
||||
HAL_Bool HAL_RefreshDSData(void) {
|
||||
HAL_ControlWord controlWord;
|
||||
std::memset(&controlWord, 0, sizeof(controlWord));
|
||||
FRC_NetworkCommunication_getControlWord(
|
||||
reinterpret_cast<ControlWord_t*>(&controlWord));
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
JoystickDataCache* prev = currentCache.exchange(nullptr);
|
||||
if (prev != nullptr) {
|
||||
currentRead = prev;
|
||||
JoystickDataCache* prev;
|
||||
{
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
prev = currentCache.exchange(nullptr);
|
||||
if (prev != nullptr) {
|
||||
currentRead = prev;
|
||||
}
|
||||
// If newest state shows we have a DS attached, just use the
|
||||
// control word out of the cache, As it will be the one in sync
|
||||
// with the data. Otherwise use the state that shows disconnected.
|
||||
if (controlWord.dsAttached) {
|
||||
newestControlWord = currentRead->controlWord;
|
||||
} else {
|
||||
newestControlWord = controlWord;
|
||||
}
|
||||
}
|
||||
newestControlWord = controlWord;
|
||||
|
||||
uint32_t mask = tcpMask.exchange(0);
|
||||
if (mask != 0) {
|
||||
@@ -526,6 +539,7 @@ void HAL_RefreshDSData(void) {
|
||||
std::scoped_lock tcpLock(tcpCacheMutex);
|
||||
tcpCache.CloneTo(&tcpCurrent);
|
||||
}
|
||||
return prev != nullptr;
|
||||
}
|
||||
|
||||
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
|
||||
@@ -551,4 +565,13 @@ void InitializeDriverStation() {
|
||||
setNewDataOccurRef(refNumber);
|
||||
FRC_NetworkCommunication_setNewTcpDataOccurRef(tcpRefNumber);
|
||||
}
|
||||
|
||||
void WaitForInitialPacket() {
|
||||
wpi::Event waitForInitEvent;
|
||||
driverStation->newDataEvents.Add(waitForInitEvent.GetHandle());
|
||||
bool timed_out = false;
|
||||
wpi::WaitForObject(waitForInitEvent.GetHandle(), 0.1, &timed_out);
|
||||
// Don't care what the result is, just want to give it a chance.
|
||||
driverStation->newDataEvents.Remove(waitForInitEvent.GetHandle());
|
||||
}
|
||||
} // namespace hal
|
||||
|
||||
@@ -50,6 +50,7 @@ using namespace hal;
|
||||
|
||||
namespace hal {
|
||||
void InitializeDriverStation();
|
||||
void WaitForInitialPacket();
|
||||
namespace init {
|
||||
void InitializeHAL() {
|
||||
InitializeCTREPCM();
|
||||
@@ -252,12 +253,10 @@ const char* HAL_GetErrorMessage(int32_t code) {
|
||||
}
|
||||
}
|
||||
|
||||
static HAL_RuntimeType runtimeType = HAL_Runtime_RoboRIO;
|
||||
|
||||
HAL_RuntimeType HAL_GetRuntimeType(void) {
|
||||
nLoadOut::tTargetClass targetClass = nLoadOut::getTargetClass();
|
||||
if (targetClass == nLoadOut::kTargetClass_RoboRIO2) {
|
||||
return HAL_Runtime_RoboRIO2;
|
||||
}
|
||||
return HAL_Runtime_RoboRIO;
|
||||
return runtimeType;
|
||||
}
|
||||
|
||||
int32_t HAL_GetFPGAVersion(int32_t* status) {
|
||||
@@ -317,11 +316,8 @@ void InitializeRoboRioComments(void) {
|
||||
return;
|
||||
}
|
||||
start += searchString.size();
|
||||
size_t end = fileContents.find("\"", start);
|
||||
if (end == std::string_view::npos) {
|
||||
end = fileContents.size();
|
||||
}
|
||||
std::string_view escapedComments = wpi::slice(fileContents, start, end);
|
||||
std::string_view escapedComments =
|
||||
wpi::slice(fileContents, start, fileContents.size());
|
||||
wpi::SmallString<64> buf;
|
||||
auto [unescapedComments, rem] = wpi::UnescapeCString(escapedComments, buf);
|
||||
unescapedComments.copy(roboRioCommentsString,
|
||||
@@ -522,6 +518,13 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nLoadOut::tTargetClass targetClass = nLoadOut::getTargetClass();
|
||||
if (targetClass == nLoadOut::kTargetClass_RoboRIO2) {
|
||||
runtimeType = HAL_Runtime_RoboRIO2;
|
||||
} else {
|
||||
runtimeType = HAL_Runtime_RoboRIO;
|
||||
}
|
||||
|
||||
InterruptManager::Initialize(global->getSystemInterface());
|
||||
|
||||
hal::InitializeDriverStation();
|
||||
@@ -546,6 +549,8 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
|
||||
return rv;
|
||||
});
|
||||
|
||||
hal::WaitForInitialPacket();
|
||||
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ struct DeviceDescriptor
|
||||
// Bootloader version. Will not change for the life of the product, but additional
|
||||
// field upgrade features could be added in newer hardware.
|
||||
char bootloaderRev[MAX_STRING_LEN];
|
||||
// Manufacture Date. Could be a calender date or just the FRC season year.
|
||||
// Manufacture Date. Could be a calendar date or just the FRC season year.
|
||||
// Also helps troubleshooting "old ones" vs "new ones".
|
||||
char manufactureDate[MAX_STRING_LEN];
|
||||
// General status of the hardware. For example if the device is in bootloader
|
||||
|
||||
@@ -79,7 +79,7 @@ extern "C" {
|
||||
/**
|
||||
* Signals in message Compressor_Config.
|
||||
*
|
||||
* Configures compressor to use digitial/analog sensors
|
||||
* Configures compressor to use digital/analog sensors
|
||||
*
|
||||
* All signal values are as on the CAN bus.
|
||||
*/
|
||||
|
||||
@@ -403,13 +403,13 @@ Java_edu_wpi_first_hal_DriverStationJNI_sendConsoleLine
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: refreshDSData
|
||||
* Signature: ()V
|
||||
* Signature: ()Z
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_refreshDSData
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_RefreshDSData();
|
||||
return HAL_RefreshDSData();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -468,7 +468,11 @@ JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_SimDeviceDataJNI_getSimDeviceName
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
return MakeJString(env, HALSIM_GetSimDeviceName(handle));
|
||||
const char* name = HALSIM_GetSimDeviceName(handle);
|
||||
if (!name) {
|
||||
return nullptr;
|
||||
}
|
||||
return MakeJString(env, name);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -40,7 +40,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes an object for peforming DMA transfers.
|
||||
* Initializes an object for performing DMA transfers.
|
||||
*
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the created dma handle
|
||||
@@ -310,7 +310,7 @@ enum HAL_DMAReadStatus HAL_ReadDMADirect(void* dmaPointer,
|
||||
* timing out
|
||||
* @param[in] remainingOut the number of samples remaining in the queue
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the succes result of the sample read
|
||||
* @return the success result of the sample read
|
||||
*/
|
||||
enum HAL_DMAReadStatus HAL_ReadDMA(HAL_DMAHandle handle,
|
||||
HAL_DMASample* dmaSample,
|
||||
|
||||
@@ -28,7 +28,7 @@ extern "C" {
|
||||
* @param errorCode the error code
|
||||
* @param isLVCode true for a LV error code, false for a standard error code
|
||||
* @param details the details of the error
|
||||
* @param location the file location of the errror
|
||||
* @param location the file location of the error
|
||||
* @param callStack the callstack of the error
|
||||
* @param printMsg true to print the error message to stdout as well as to the
|
||||
* DS
|
||||
@@ -209,7 +209,7 @@ HAL_Bool HAL_GetOutputsEnabled(void);
|
||||
*/
|
||||
int32_t HAL_GetMatchInfo(HAL_MatchInfo* info);
|
||||
|
||||
void HAL_RefreshDSData(void);
|
||||
HAL_Bool HAL_RefreshDSData(void);
|
||||
|
||||
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle);
|
||||
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle);
|
||||
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
* Expected to be called internally, not by users.
|
||||
*
|
||||
* @param library the library path
|
||||
* @return the succes state of the initialization
|
||||
* @return the success state of the initialization
|
||||
*/
|
||||
int HAL_LoadOneExtension(const char* library);
|
||||
|
||||
@@ -39,7 +39,7 @@ int HAL_LoadOneExtension(const char* library);
|
||||
* Loads any extra halsim libraries provided in the HALSIM_EXTENSIONS
|
||||
* environment variable.
|
||||
*
|
||||
* @return the succes state of the initialization
|
||||
* @return the success state of the initialization
|
||||
*/
|
||||
int HAL_LoadExtensions(void);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ extern "C" {
|
||||
* Initializes a Power Distribution Panel.
|
||||
*
|
||||
* @param[in] moduleNumber the module number to initialize
|
||||
* @param[in] type the type of module to intialize
|
||||
* @param[in] type the type of module to initialize
|
||||
* @param[in] allocationLocation the location where the allocation is occurring
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the created PowerDistribution
|
||||
|
||||
@@ -47,7 +47,7 @@ extern "C" {
|
||||
*
|
||||
* The device name must be unique. 0 is returned if the device name already
|
||||
* exists. If multiple instances of the same device are desired, recommend
|
||||
* appending the instance/unique identifer in brackets to the base name,
|
||||
* appending the instance/unique identifier in brackets to the base name,
|
||||
* e.g. "device[1]".
|
||||
*
|
||||
* 0 is returned if not in simulation.
|
||||
@@ -663,7 +663,7 @@ class SimDevice {
|
||||
*
|
||||
* The device name must be unique. Returns null if the device name
|
||||
* already exists. If multiple instances of the same device are desired,
|
||||
* recommend appending the instance/unique identifer in brackets to the base
|
||||
* recommend appending the instance/unique identifier in brackets to the base
|
||||
* name, e.g. "device[1]".
|
||||
*
|
||||
* If not in simulation, results in an "empty" object that evaluates to false
|
||||
|
||||
@@ -63,8 +63,8 @@ int32_t ComputeDigitalMask(HAL_DigitalHandle handle, int32_t* status);
|
||||
|
||||
/**
|
||||
* Unsafe digital output set function
|
||||
* This function can be used to perform fast and determinstically set digital
|
||||
* outputs. This function holds the DIO lock, so calling anyting other then
|
||||
* This function can be used to perform fast and deterministically set digital
|
||||
* outputs. This function holds the DIO lock, so calling anything other then
|
||||
* functions on the Proxy object passed as a parameter can deadlock your
|
||||
* program.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace hal {
|
||||
|
||||
/**
|
||||
* The UnlimitedHandleResource class is a way to track handles. This version
|
||||
* allows an unlimted number of handles that are allocated sequentially. When
|
||||
* allows an unlimited number of handles that are allocated sequentially. When
|
||||
* possible, indices are reused to save memory usage and keep the array length
|
||||
* down.
|
||||
* However, automatic array management has not been implemented, but might be in
|
||||
|
||||
@@ -75,7 +75,7 @@ static HAL_ControlWord newestControlWord;
|
||||
static JoystickDataCache caches[3];
|
||||
static JoystickDataCache* currentRead = &caches[0];
|
||||
static JoystickDataCache* currentReadLocal = &caches[0];
|
||||
static std::atomic<JoystickDataCache*> currentCache{&caches[1]};
|
||||
static std::atomic<JoystickDataCache*> currentCache{nullptr};
|
||||
static JoystickDataCache* lastGiven = &caches[1];
|
||||
static JoystickDataCache* cacheToUpdate = &caches[2];
|
||||
|
||||
@@ -318,9 +318,9 @@ void HAL_ObserveUserProgramTest(void) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void HAL_RefreshDSData(void) {
|
||||
HAL_Bool HAL_RefreshDSData(void) {
|
||||
if (gShutdown) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
HAL_ControlWord controlWord;
|
||||
std::memset(&controlWord, 0, sizeof(controlWord));
|
||||
@@ -336,6 +336,7 @@ void HAL_RefreshDSData(void) {
|
||||
currentRead = prev;
|
||||
}
|
||||
newestControlWord = controlWord;
|
||||
return prev != nullptr;
|
||||
}
|
||||
|
||||
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
|
||||
|
||||
@@ -201,7 +201,7 @@ int32_t HAL_GetREVPHSolenoids(HAL_REVPHHandle handle, int32_t* status) {
|
||||
|
||||
std::scoped_lock lock{pcm->lock};
|
||||
auto& data = SimREVPHData[pcm->module].solenoidOutput;
|
||||
uint8_t ret = 0;
|
||||
int32_t ret = 0;
|
||||
for (int i = 0; i < kNumREVPHChannels; i++) {
|
||||
ret |= (data[i] << i);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
@@ -17,12 +19,23 @@
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
void bench();
|
||||
void bench2();
|
||||
void stress();
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc == 2 && std::string_view{argv[1]} == "bench") {
|
||||
bench();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (argc == 2 && std::string_view{argv[1]} == "bench2") {
|
||||
bench2();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (argc == 2 && std::string_view{argv[1]} == "stress") {
|
||||
stress();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
auto myValue = nt::GetEntry(nt::GetDefaultInstance(), "MyValue");
|
||||
|
||||
nt::SetEntryValue(myValue, nt::Value::MakeString("Hello World"));
|
||||
@@ -106,3 +119,145 @@ void bench() {
|
||||
fmt::print("-- Flush --\n");
|
||||
PrintTimes(flushTimes);
|
||||
}
|
||||
|
||||
void bench2() {
|
||||
// set up instances
|
||||
auto client1 = nt::CreateInstance();
|
||||
auto client2 = nt::CreateInstance();
|
||||
auto server = nt::CreateInstance();
|
||||
|
||||
// connect client and server
|
||||
nt::StartServer(server, "bench2.json", "127.0.0.1", 10001, 10000);
|
||||
nt::StartClient4(client1, "client1");
|
||||
nt::StartClient3(client2, "client2");
|
||||
nt::SetServer(client1, "127.0.0.1", 10000);
|
||||
nt::SetServer(client2, "127.0.0.1", 10001);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(1s);
|
||||
|
||||
// add "typical" set of subscribers on client and server
|
||||
nt::SubscribeMultiple(client1, {{std::string_view{}}});
|
||||
nt::SubscribeMultiple(client2, {{std::string_view{}}});
|
||||
nt::SubscribeMultiple(server, {{std::string_view{}}});
|
||||
|
||||
// create 1000 entries
|
||||
std::array<NT_Entry, 1000> pubs;
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
pubs[i] = nt::GetEntry(
|
||||
nt::GetTopic(server,
|
||||
fmt::format("/some/long/name/with/lots/of/slashes/{}", i)),
|
||||
NT_DOUBLE_ARRAY, "double[]");
|
||||
}
|
||||
|
||||
// warm up
|
||||
for (int i = 1; i <= 100; ++i) {
|
||||
for (auto pub : pubs) {
|
||||
double vals[3] = {i * 0.01, i * 0.02, i * 0.03};
|
||||
nt::SetDoubleArray(pub, vals);
|
||||
}
|
||||
nt::FlushLocal(server);
|
||||
std::this_thread::sleep_for(0.02s);
|
||||
}
|
||||
|
||||
std::vector<int64_t> flushTimes;
|
||||
flushTimes.reserve(1001);
|
||||
|
||||
std::vector<int64_t> times;
|
||||
times.reserve(1001);
|
||||
|
||||
// benchmark
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
int64_t now = nt::Now();
|
||||
for (int i = 1; i <= 1000; ++i) {
|
||||
for (auto pub : pubs) {
|
||||
double vals[3] = {i * 0.01, i * 0.02, i * 0.03};
|
||||
nt::SetDoubleArray(pub, vals);
|
||||
}
|
||||
int64_t prev = now;
|
||||
now = nt::Now();
|
||||
times.emplace_back(now - prev);
|
||||
nt::FlushLocal(server);
|
||||
nt::Flush(server);
|
||||
flushTimes.emplace_back(nt::Now() - now);
|
||||
std::this_thread::sleep_for(0.02s);
|
||||
now = nt::Now();
|
||||
}
|
||||
auto stop = std::chrono::high_resolution_clock::now();
|
||||
|
||||
fmt::print("total time: {}us\n",
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
|
||||
.count());
|
||||
PrintTimes(times);
|
||||
fmt::print("-- Flush --\n");
|
||||
PrintTimes(flushTimes);
|
||||
}
|
||||
|
||||
static std::random_device r;
|
||||
static std::mt19937 gen(r());
|
||||
static std::uniform_real_distribution<double> dist;
|
||||
|
||||
void stress() {
|
||||
auto server = nt::CreateInstance();
|
||||
nt::StartServer(server, "stress.json", "127.0.0.1", 0, 10000);
|
||||
nt::SubscribeMultiple(server, {{std::string_view{}}});
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
for (int count = 0; count < 10; ++count) {
|
||||
std::thread{[] {
|
||||
auto client = nt::CreateInstance();
|
||||
nt::SubscribeMultiple(client, {{std::string_view{}}});
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
// sleep a random amount of time
|
||||
std::this_thread::sleep_for(0.1s * dist(gen));
|
||||
|
||||
// connect
|
||||
nt::StartClient4(client, "client");
|
||||
nt::SetServer(client, "127.0.0.1", 10000);
|
||||
|
||||
// sleep a random amount of time
|
||||
std::this_thread::sleep_for(0.1s * dist(gen));
|
||||
|
||||
// disconnect
|
||||
nt::StopClient(client);
|
||||
}
|
||||
nt::DestroyInstance(client);
|
||||
}}.detach();
|
||||
|
||||
std::thread{[server, count] {
|
||||
for (int n = 0; n < 300; ++n) {
|
||||
// sleep a random amount of time
|
||||
std::this_thread::sleep_for(0.01s * dist(gen));
|
||||
|
||||
// create publishers
|
||||
NT_Publisher pub[30];
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
pub[i] =
|
||||
nt::Publish(nt::GetTopic(server, fmt::format("{}_{}", count, i)),
|
||||
NT_DOUBLE, "double", {});
|
||||
}
|
||||
|
||||
// publish values
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
// sleep a random amount of time between each value set
|
||||
std::this_thread::sleep_for(0.001s * dist(gen));
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
nt::SetDouble(pub[i], dist(gen));
|
||||
}
|
||||
nt::FlushLocal(server);
|
||||
}
|
||||
|
||||
// sleep a random amount of time
|
||||
std::this_thread::sleep_for(0.1s * dist(gen));
|
||||
|
||||
// remove publishers
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
nt::Unpublish(pub[i]);
|
||||
}
|
||||
}
|
||||
}}.detach();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(100s);
|
||||
}
|
||||
|
||||
@@ -911,6 +911,14 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
NetworkTablesJNI.setServerTeam(m_handle, team, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the client if it's running and connected. This will automatically start
|
||||
* reconnection attempts to the current server list.
|
||||
*/
|
||||
public void disconnect() {
|
||||
NetworkTablesJNI.disconnect(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts requesting server address from Driver Station. This connects to the Driver Station
|
||||
* running on localhost to obtain the server IP address, and connects with the default port.
|
||||
|
||||
@@ -251,6 +251,8 @@ public final class NetworkTablesJNI {
|
||||
|
||||
public static native void setServerTeam(int inst, int team, int port);
|
||||
|
||||
public static native void disconnect(int inst);
|
||||
|
||||
public static native void startDSClient(int inst, int port);
|
||||
|
||||
public static native void stopDSClient(int inst);
|
||||
|
||||
@@ -1,366 +1,366 @@
|
||||
[
|
||||
{
|
||||
"TypeName": "Boolean",
|
||||
"TypeString": "\"boolean\"",
|
||||
"c": {
|
||||
"ValueType": "NT_Bool",
|
||||
"ParamType": "NT_Bool"
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "bool",
|
||||
"ParamType": "bool",
|
||||
"TYPE_NAME": "BOOLEAN"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "boolean",
|
||||
"EmptyValue": "false",
|
||||
"ConsumerFunctionPackage": "edu.wpi.first.util.function",
|
||||
"FunctionTypePrefix": "Boolean",
|
||||
"ToWrapObject": "Boolean.valueOf",
|
||||
"FromStorageBegin": "(Boolean) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jboolean",
|
||||
"jtypestr": "Z",
|
||||
"JavaObject": false,
|
||||
"FromJavaBegin": "",
|
||||
"FromJavaEnd": " != JNI_FALSE",
|
||||
"ToJavaBegin": "static_cast<jboolean>(",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJBooleanArray"
|
||||
}
|
||||
{
|
||||
"TypeName": "Boolean",
|
||||
"TypeString": "\"boolean\"",
|
||||
"c": {
|
||||
"ValueType": "NT_Bool",
|
||||
"ParamType": "NT_Bool"
|
||||
},
|
||||
{
|
||||
"TypeName": "Integer",
|
||||
"TypeString": "\"int\"",
|
||||
"c": {
|
||||
"ValueType": "int64_t",
|
||||
"ParamType": "int64_t"
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "int64_t",
|
||||
"ParamType": "int64_t",
|
||||
"TYPE_NAME": "INTEGER"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "long",
|
||||
"EmptyValue": "0",
|
||||
"FunctionTypePrefix": "Long",
|
||||
"ToWrapObject": "Long.valueOf",
|
||||
"FromStorageBegin": "((Number) ",
|
||||
"FromStorageEnd": ").longValue()"
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jlong",
|
||||
"jtypestr": "J",
|
||||
"JavaObject": false,
|
||||
"FromJavaBegin": "",
|
||||
"FromJavaEnd": "",
|
||||
"ToJavaBegin": "static_cast<jlong>(",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJLongArray"
|
||||
}
|
||||
"cpp": {
|
||||
"ValueType": "bool",
|
||||
"ParamType": "bool",
|
||||
"TYPE_NAME": "BOOLEAN"
|
||||
},
|
||||
{
|
||||
"TypeName": "Float",
|
||||
"TypeString": "\"float\"",
|
||||
"c": {
|
||||
"ValueType": "float",
|
||||
"ParamType": "float"
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "float",
|
||||
"ParamType": "float",
|
||||
"TYPE_NAME": "FLOAT"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "float",
|
||||
"EmptyValue": "0",
|
||||
"ConsumerFunctionPackage": "edu.wpi.first.util.function",
|
||||
"SupplierFunctionPackage": "edu.wpi.first.util.function",
|
||||
"FunctionTypePrefix": "Float",
|
||||
"ToWrapObject": "Float.valueOf",
|
||||
"FromStorageBegin": "((Number) ",
|
||||
"FromStorageEnd": ").floatValue()"
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jfloat",
|
||||
"jtypestr": "F",
|
||||
"JavaObject": false,
|
||||
"FromJavaBegin": "",
|
||||
"FromJavaEnd": "",
|
||||
"ToJavaBegin": "static_cast<jfloat>(",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJFloatArray"
|
||||
}
|
||||
"java": {
|
||||
"ValueType": "boolean",
|
||||
"EmptyValue": "false",
|
||||
"ConsumerFunctionPackage": "edu.wpi.first.util.function",
|
||||
"FunctionTypePrefix": "Boolean",
|
||||
"ToWrapObject": "Boolean.valueOf",
|
||||
"FromStorageBegin": "(Boolean) "
|
||||
},
|
||||
{
|
||||
"TypeName": "Double",
|
||||
"TypeString": "\"double\"",
|
||||
"c": {
|
||||
"ValueType": "double",
|
||||
"ParamType": "double"
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "double",
|
||||
"ParamType": "double",
|
||||
"TYPE_NAME": "DOUBLE"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "double",
|
||||
"EmptyValue": "0",
|
||||
"FunctionTypePrefix": "Double",
|
||||
"ToWrapObject": "Double.valueOf",
|
||||
"FromStorageBegin": "((Number) ",
|
||||
"FromStorageEnd": ").doubleValue()"
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jdouble",
|
||||
"jtypestr": "D",
|
||||
"JavaObject": false,
|
||||
"FromJavaBegin": "",
|
||||
"FromJavaEnd": "",
|
||||
"ToJavaBegin": "static_cast<jdouble>(",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJDoubleArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "String",
|
||||
"TypeString": "\"string\"",
|
||||
"c": {
|
||||
"ValueType": "char*",
|
||||
"ParamType": "const char*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::string",
|
||||
"ParamType": "std::string_view",
|
||||
"TYPE_NAME": "STRING",
|
||||
"INCLUDES": "#include <string>\n#include <string_view>\n#include <utility>",
|
||||
"SmallRetType": "std::string_view",
|
||||
"SmallElemType": "char"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "String",
|
||||
"EmptyValue": "\"\"",
|
||||
"FunctionTypeSuffix": "<String>",
|
||||
"FromStorageBegin": "(String) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jstring",
|
||||
"jtypestr": "Ljava/lang/String;",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "JStringRef{env, ",
|
||||
"FromJavaEnd": "}",
|
||||
"ToJavaBegin": "MakeJString(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJStringArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "Raw",
|
||||
"c": {
|
||||
"ValueType": "uint8_t*",
|
||||
"ParamType": "const uint8_t*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<uint8_t>",
|
||||
"ParamType": "std::span<const uint8_t>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "RAW",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<uint8_t>",
|
||||
"SmallElemType": "uint8_t"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "byte[]",
|
||||
"EmptyValue": "new byte[] {}",
|
||||
"FunctionTypeSuffix": "<byte[]>",
|
||||
"FromStorageBegin": "(byte[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jbyteArray",
|
||||
"jtypestr": "[B",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "CriticalJByteArrayRef{env, ",
|
||||
"FromJavaEnd": "}.uarray()",
|
||||
"ToJavaBegin": "MakeJByteArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "BooleanArray",
|
||||
"TypeString": "\"boolean[]\"",
|
||||
"c": {
|
||||
"ValueType": "NT_Bool*",
|
||||
"ParamType": "const NT_Bool*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<int>",
|
||||
"ParamType": "std::span<const int>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "BOOLEAN_ARRAY",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<int>",
|
||||
"SmallElemType": "int"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "boolean[]",
|
||||
"WrapValueType": "Boolean[]",
|
||||
"EmptyValue": "new boolean[] {}",
|
||||
"FunctionTypeSuffix": "<boolean[]>",
|
||||
"FromStorageBegin": "(boolean[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jbooleanArray",
|
||||
"jtypestr": "[Z",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "FromJavaBooleanArray(env, ",
|
||||
"FromJavaEnd": ")",
|
||||
"ToJavaBegin": "MakeJBooleanArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "IntegerArray",
|
||||
"TypeString": "\"int[]\"",
|
||||
"c": {
|
||||
"ValueType": "int64_t*",
|
||||
"ParamType": "const int64_t*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<int64_t>",
|
||||
"ParamType": "std::span<const int64_t>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "INTEGER_ARRAY",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<int64_t>",
|
||||
"SmallElemType": "int64_t"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "long[]",
|
||||
"WrapValueType": "Long[]",
|
||||
"EmptyValue": "new long[] {}",
|
||||
"FunctionTypeSuffix": "<long[]>",
|
||||
"FromStorageBegin": "(long[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jlongArray",
|
||||
"jtypestr": "[J",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "CriticalJLongArrayRef{env, ",
|
||||
"FromJavaEnd": "}",
|
||||
"ToJavaBegin": "MakeJLongArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "FloatArray",
|
||||
"TypeString": "\"float[]\"",
|
||||
"c": {
|
||||
"ValueType": "float*",
|
||||
"ParamType": "const float*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<float>",
|
||||
"ParamType": "std::span<const float>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "FLOAT_ARRAY",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<float>",
|
||||
"SmallElemType": "float"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "float[]",
|
||||
"WrapValueType": "Float[]",
|
||||
"EmptyValue": "new float[] {}",
|
||||
"FunctionTypeSuffix": "<float[]>",
|
||||
"FromStorageBegin": "(float[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jfloatArray",
|
||||
"jtypestr": "[F",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "CriticalJFloatArrayRef{env, ",
|
||||
"FromJavaEnd": "}",
|
||||
"ToJavaBegin": "MakeJFloatArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "DoubleArray",
|
||||
"TypeString": "\"double[]\"",
|
||||
"c": {
|
||||
"ValueType": "double*",
|
||||
"ParamType": "const double*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<double>",
|
||||
"ParamType": "std::span<const double>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "DOUBLE_ARRAY",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<double>",
|
||||
"SmallElemType": "double"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "double[]",
|
||||
"WrapValueType": "Double[]",
|
||||
"EmptyValue": "new double[] {}",
|
||||
"FunctionTypeSuffix": "<double[]>",
|
||||
"FromStorageBegin": "(double[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jdoubleArray",
|
||||
"jtypestr": "[D",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "CriticalJDoubleArrayRef{env, ",
|
||||
"FromJavaEnd": "}",
|
||||
"ToJavaBegin": "MakeJDoubleArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "StringArray",
|
||||
"TypeString": "\"string[]\"",
|
||||
"c": {
|
||||
"ValueType": "struct NT_String*",
|
||||
"ParamType": "const struct NT_String*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<std::string>",
|
||||
"ParamType": "std::span<const std::string>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "STRING_ARRAY",
|
||||
"INCLUDES": "#include <utility>"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "String[]",
|
||||
"EmptyValue": "new String[] {}",
|
||||
"FunctionTypeSuffix": "<String[]>",
|
||||
"FromStorageBegin": "(String[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jobjectArray",
|
||||
"jtypestr": "[Ljava/lang/Object;",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "FromJavaStringArray(env, ",
|
||||
"FromJavaEnd": ")",
|
||||
"ToJavaBegin": "MakeJStringArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
"jni": {
|
||||
"jtype": "jboolean",
|
||||
"jtypestr": "Z",
|
||||
"JavaObject": false,
|
||||
"FromJavaBegin": "",
|
||||
"FromJavaEnd": " != JNI_FALSE",
|
||||
"ToJavaBegin": "static_cast<jboolean>(",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJBooleanArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "Integer",
|
||||
"TypeString": "\"int\"",
|
||||
"c": {
|
||||
"ValueType": "int64_t",
|
||||
"ParamType": "int64_t"
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "int64_t",
|
||||
"ParamType": "int64_t",
|
||||
"TYPE_NAME": "INTEGER"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "long",
|
||||
"EmptyValue": "0",
|
||||
"FunctionTypePrefix": "Long",
|
||||
"ToWrapObject": "Long.valueOf",
|
||||
"FromStorageBegin": "((Number) ",
|
||||
"FromStorageEnd": ").longValue()"
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jlong",
|
||||
"jtypestr": "J",
|
||||
"JavaObject": false,
|
||||
"FromJavaBegin": "",
|
||||
"FromJavaEnd": "",
|
||||
"ToJavaBegin": "static_cast<jlong>(",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJLongArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "Float",
|
||||
"TypeString": "\"float\"",
|
||||
"c": {
|
||||
"ValueType": "float",
|
||||
"ParamType": "float"
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "float",
|
||||
"ParamType": "float",
|
||||
"TYPE_NAME": "FLOAT"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "float",
|
||||
"EmptyValue": "0",
|
||||
"ConsumerFunctionPackage": "edu.wpi.first.util.function",
|
||||
"SupplierFunctionPackage": "edu.wpi.first.util.function",
|
||||
"FunctionTypePrefix": "Float",
|
||||
"ToWrapObject": "Float.valueOf",
|
||||
"FromStorageBegin": "((Number) ",
|
||||
"FromStorageEnd": ").floatValue()"
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jfloat",
|
||||
"jtypestr": "F",
|
||||
"JavaObject": false,
|
||||
"FromJavaBegin": "",
|
||||
"FromJavaEnd": "",
|
||||
"ToJavaBegin": "static_cast<jfloat>(",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJFloatArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "Double",
|
||||
"TypeString": "\"double\"",
|
||||
"c": {
|
||||
"ValueType": "double",
|
||||
"ParamType": "double"
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "double",
|
||||
"ParamType": "double",
|
||||
"TYPE_NAME": "DOUBLE"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "double",
|
||||
"EmptyValue": "0",
|
||||
"FunctionTypePrefix": "Double",
|
||||
"ToWrapObject": "Double.valueOf",
|
||||
"FromStorageBegin": "((Number) ",
|
||||
"FromStorageEnd": ").doubleValue()"
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jdouble",
|
||||
"jtypestr": "D",
|
||||
"JavaObject": false,
|
||||
"FromJavaBegin": "",
|
||||
"FromJavaEnd": "",
|
||||
"ToJavaBegin": "static_cast<jdouble>(",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJDoubleArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "String",
|
||||
"TypeString": "\"string\"",
|
||||
"c": {
|
||||
"ValueType": "char*",
|
||||
"ParamType": "const char*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::string",
|
||||
"ParamType": "std::string_view",
|
||||
"TYPE_NAME": "STRING",
|
||||
"INCLUDES": "#include <string>\n#include <string_view>\n#include <utility>",
|
||||
"SmallRetType": "std::string_view",
|
||||
"SmallElemType": "char"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "String",
|
||||
"EmptyValue": "\"\"",
|
||||
"FunctionTypeSuffix": "<String>",
|
||||
"FromStorageBegin": "(String) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jstring",
|
||||
"jtypestr": "Ljava/lang/String;",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "JStringRef{env, ",
|
||||
"FromJavaEnd": "}",
|
||||
"ToJavaBegin": "MakeJString(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJStringArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "Raw",
|
||||
"c": {
|
||||
"ValueType": "uint8_t*",
|
||||
"ParamType": "const uint8_t*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<uint8_t>",
|
||||
"ParamType": "std::span<const uint8_t>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "RAW",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<uint8_t>",
|
||||
"SmallElemType": "uint8_t"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "byte[]",
|
||||
"EmptyValue": "new byte[] {}",
|
||||
"FunctionTypeSuffix": "<byte[]>",
|
||||
"FromStorageBegin": "(byte[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jbyteArray",
|
||||
"jtypestr": "[B",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "CriticalJByteArrayRef{env, ",
|
||||
"FromJavaEnd": "}.uarray()",
|
||||
"ToJavaBegin": "MakeJByteArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "BooleanArray",
|
||||
"TypeString": "\"boolean[]\"",
|
||||
"c": {
|
||||
"ValueType": "NT_Bool*",
|
||||
"ParamType": "const NT_Bool*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<int>",
|
||||
"ParamType": "std::span<const int>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "BOOLEAN_ARRAY",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<int>",
|
||||
"SmallElemType": "int"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "boolean[]",
|
||||
"WrapValueType": "Boolean[]",
|
||||
"EmptyValue": "new boolean[] {}",
|
||||
"FunctionTypeSuffix": "<boolean[]>",
|
||||
"FromStorageBegin": "(boolean[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jbooleanArray",
|
||||
"jtypestr": "[Z",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "FromJavaBooleanArray(env, ",
|
||||
"FromJavaEnd": ")",
|
||||
"ToJavaBegin": "MakeJBooleanArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "IntegerArray",
|
||||
"TypeString": "\"int[]\"",
|
||||
"c": {
|
||||
"ValueType": "int64_t*",
|
||||
"ParamType": "const int64_t*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<int64_t>",
|
||||
"ParamType": "std::span<const int64_t>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "INTEGER_ARRAY",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<int64_t>",
|
||||
"SmallElemType": "int64_t"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "long[]",
|
||||
"WrapValueType": "Long[]",
|
||||
"EmptyValue": "new long[] {}",
|
||||
"FunctionTypeSuffix": "<long[]>",
|
||||
"FromStorageBegin": "(long[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jlongArray",
|
||||
"jtypestr": "[J",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "CriticalJLongArrayRef{env, ",
|
||||
"FromJavaEnd": "}",
|
||||
"ToJavaBegin": "MakeJLongArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "FloatArray",
|
||||
"TypeString": "\"float[]\"",
|
||||
"c": {
|
||||
"ValueType": "float*",
|
||||
"ParamType": "const float*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<float>",
|
||||
"ParamType": "std::span<const float>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "FLOAT_ARRAY",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<float>",
|
||||
"SmallElemType": "float"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "float[]",
|
||||
"WrapValueType": "Float[]",
|
||||
"EmptyValue": "new float[] {}",
|
||||
"FunctionTypeSuffix": "<float[]>",
|
||||
"FromStorageBegin": "(float[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jfloatArray",
|
||||
"jtypestr": "[F",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "CriticalJFloatArrayRef{env, ",
|
||||
"FromJavaEnd": "}",
|
||||
"ToJavaBegin": "MakeJFloatArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "DoubleArray",
|
||||
"TypeString": "\"double[]\"",
|
||||
"c": {
|
||||
"ValueType": "double*",
|
||||
"ParamType": "const double*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<double>",
|
||||
"ParamType": "std::span<const double>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "DOUBLE_ARRAY",
|
||||
"INCLUDES": "#include <utility>",
|
||||
"SmallRetType": "std::span<double>",
|
||||
"SmallElemType": "double"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "double[]",
|
||||
"WrapValueType": "Double[]",
|
||||
"EmptyValue": "new double[] {}",
|
||||
"FunctionTypeSuffix": "<double[]>",
|
||||
"FromStorageBegin": "(double[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jdoubleArray",
|
||||
"jtypestr": "[D",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "CriticalJDoubleArrayRef{env, ",
|
||||
"FromJavaEnd": "}",
|
||||
"ToJavaBegin": "MakeJDoubleArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"TypeName": "StringArray",
|
||||
"TypeString": "\"string[]\"",
|
||||
"c": {
|
||||
"ValueType": "struct NT_String*",
|
||||
"ParamType": "const struct NT_String*",
|
||||
"IsArray": true
|
||||
},
|
||||
"cpp": {
|
||||
"ValueType": "std::vector<std::string>",
|
||||
"ParamType": "std::span<const std::string>",
|
||||
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
|
||||
"TYPE_NAME": "STRING_ARRAY",
|
||||
"INCLUDES": "#include <utility>"
|
||||
},
|
||||
"java": {
|
||||
"ValueType": "String[]",
|
||||
"EmptyValue": "new String[] {}",
|
||||
"FunctionTypeSuffix": "<String[]>",
|
||||
"FromStorageBegin": "(String[]) "
|
||||
},
|
||||
"jni": {
|
||||
"jtype": "jobjectArray",
|
||||
"jtypestr": "[Ljava/lang/Object;",
|
||||
"JavaObject": true,
|
||||
"FromJavaBegin": "FromJavaStringArray(env, ",
|
||||
"FromJavaEnd": ")",
|
||||
"ToJavaBegin": "MakeJStringArray(env, ",
|
||||
"ToJavaEnd": ")",
|
||||
"ToJavaArray": "MakeJObjectArray"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -113,7 +113,7 @@ public final class NetworkTableListener implements AutoCloseable {
|
||||
* Create a time synchronization listener.
|
||||
*
|
||||
* @param inst instance
|
||||
* @param immediateNotify notify listener of current time synchonization value
|
||||
* @param immediateNotify notify listener of current time synchronization value
|
||||
* @param listener listener function
|
||||
* @return Listener
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@ class INetworkClient {
|
||||
|
||||
virtual void SetServers(
|
||||
std::span<const std::pair<std::string, unsigned int>> servers) = 0;
|
||||
virtual void Disconnect() = 0;
|
||||
|
||||
virtual void StartDSClient(unsigned int port) = 0;
|
||||
virtual void StopDSClient() = 0;
|
||||
|
||||
@@ -243,7 +243,7 @@ struct DataLoggerData {
|
||||
int Start(TopicData* topic, int64_t time) {
|
||||
return log.Start(fmt::format("{}{}", logPrefix,
|
||||
wpi::drop_front(topic->name, prefix.size())),
|
||||
topic->typeStr,
|
||||
topic->typeStr == "int" ? "int64" : topic->typeStr,
|
||||
DataLoggerEntry::MakeMetadata(topic->propertiesStr), time);
|
||||
}
|
||||
|
||||
@@ -501,7 +501,8 @@ bool LSImpl::SetValue(TopicData* topic, const Value& value,
|
||||
if (topic->type != NT_UNASSIGNED && topic->type != value.type()) {
|
||||
return false;
|
||||
}
|
||||
if (!topic->lastValue || value.time() >= topic->lastValue.time()) {
|
||||
if (!topic->lastValue || topic->lastValue.time() == 0 ||
|
||||
value.time() >= topic->lastValue.time()) {
|
||||
// TODO: notify option even if older value
|
||||
topic->type = value.type();
|
||||
topic->lastValue = value;
|
||||
@@ -909,6 +910,7 @@ std::unique_ptr<EntryData> LSImpl::RemoveEntry(NT_Entry entryHandle) {
|
||||
|
||||
MultiSubscriberData* LSImpl::AddMultiSubscriber(
|
||||
std::span<const std::string_view> prefixes, const PubSubOptions& options) {
|
||||
DEBUG4("AddMultiSubscriber({})", fmt::join(prefixes, ","));
|
||||
auto subscriber = m_multiSubscribers.Add(m_inst, prefixes, options);
|
||||
// subscribe to any already existing topics
|
||||
for (auto&& topic : m_topics) {
|
||||
@@ -920,6 +922,7 @@ MultiSubscriberData* LSImpl::AddMultiSubscriber(
|
||||
}
|
||||
}
|
||||
if (m_network) {
|
||||
DEBUG4("-> NetworkSubscribe");
|
||||
m_network->Subscribe(subscriber->handle, subscriber->prefixes,
|
||||
subscriber->options);
|
||||
}
|
||||
@@ -1227,6 +1230,10 @@ PublisherData* LSImpl::PublishEntry(EntryData* entry, NT_Type type) {
|
||||
// create publisher
|
||||
entry->publisher = AddLocalPublisher(entry->topic, wpi::json::object(),
|
||||
entry->subscriber->config);
|
||||
// exclude publisher if requested
|
||||
if (entry->subscriber->config.excludeSelf) {
|
||||
entry->subscriber->config.excludePublisher = entry->publisher->handle;
|
||||
}
|
||||
return entry->publisher;
|
||||
}
|
||||
|
||||
@@ -1279,9 +1286,6 @@ bool LSImpl::SetEntryValue(NT_Handle pubentryHandle, const Value& value) {
|
||||
if (!publisher) {
|
||||
if (auto entry = m_entries.Get(pubentryHandle)) {
|
||||
publisher = PublishEntry(entry, value.type());
|
||||
if (entry->subscriber->config.excludeSelf) {
|
||||
entry->subscriber->config.excludePublisher = publisher->handle;
|
||||
}
|
||||
}
|
||||
if (!publisher) {
|
||||
return false;
|
||||
|
||||
@@ -56,6 +56,7 @@ class NCImpl {
|
||||
void StopDSClient();
|
||||
|
||||
virtual void TcpConnected(uv::Tcp& tcp) = 0;
|
||||
virtual void ForceDisconnect(std::string_view reason) = 0;
|
||||
virtual void Disconnect(std::string_view reason);
|
||||
|
||||
// invariants
|
||||
@@ -99,6 +100,7 @@ class NCImpl3 : public NCImpl {
|
||||
|
||||
void HandleLocal();
|
||||
void TcpConnected(uv::Tcp& tcp) final;
|
||||
void ForceDisconnect(std::string_view reason) override;
|
||||
void Disconnect(std::string_view reason) override;
|
||||
|
||||
std::shared_ptr<net3::UvStreamConnection3> m_wire;
|
||||
@@ -117,6 +119,7 @@ class NCImpl4 : public NCImpl {
|
||||
void HandleLocal();
|
||||
void TcpConnected(uv::Tcp& tcp) final;
|
||||
void WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp);
|
||||
void ForceDisconnect(std::string_view reason) override;
|
||||
void Disconnect(std::string_view reason) override;
|
||||
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
@@ -155,7 +158,9 @@ void NCImpl::SetServers(
|
||||
[this, servers = std::move(serversCopy)](uv::Loop&) mutable {
|
||||
m_servers = std::move(servers);
|
||||
if (m_dsClientServer.first.empty()) {
|
||||
m_parallelConnect->SetServers(m_servers);
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->SetServers(m_servers);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -167,14 +172,20 @@ void NCImpl::StartDSClient(unsigned int port) {
|
||||
}
|
||||
m_dsClientServer.second = port == 0 ? NT_DEFAULT_PORT4 : port;
|
||||
m_dsClient = wpi::DsClient::Create(m_loop, m_logger);
|
||||
m_dsClient->setIp.connect([this](std::string_view ip) {
|
||||
m_dsClientServer.first = ip;
|
||||
m_parallelConnect->SetServers({{m_dsClientServer}});
|
||||
});
|
||||
m_dsClient->clearIp.connect([this] {
|
||||
m_dsClientServer.first.clear();
|
||||
m_parallelConnect->SetServers(m_servers);
|
||||
});
|
||||
if (m_dsClient) {
|
||||
m_dsClient->setIp.connect([this](std::string_view ip) {
|
||||
m_dsClientServer.first = ip;
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->SetServers({{m_dsClientServer}});
|
||||
}
|
||||
});
|
||||
m_dsClient->clearIp.connect([this] {
|
||||
m_dsClientServer.first.clear();
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->SetServers(m_servers);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,15 +202,20 @@ void NCImpl::Disconnect(std::string_view reason) {
|
||||
if (m_readLocalTimer) {
|
||||
m_readLocalTimer->Stop();
|
||||
}
|
||||
m_sendValuesTimer->Stop();
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->Stop();
|
||||
}
|
||||
m_localStorage.ClearNetwork();
|
||||
m_localQueue.ClearQueue();
|
||||
m_connList.RemoveConnection(m_connHandle);
|
||||
m_connHandle = 0;
|
||||
|
||||
// start trying to connect again
|
||||
uv::Timer::SingleShot(m_loop, kReconnectRate,
|
||||
[this] { m_parallelConnect->Disconnected(); });
|
||||
uv::Timer::SingleShot(m_loop, kReconnectRate, [this] {
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->Disconnected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NCImpl3::NCImpl3(int inst, std::string_view id,
|
||||
@@ -212,25 +228,31 @@ NCImpl3::NCImpl3(int inst, std::string_view id,
|
||||
[this](uv::Tcp& tcp) { TcpConnected(tcp); });
|
||||
|
||||
m_sendValuesTimer = uv::Timer::Create(loop);
|
||||
m_sendValuesTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count(), false);
|
||||
}
|
||||
});
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count(), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// set up flush async
|
||||
m_flush = uv::Async<>::Create(m_loop);
|
||||
m_flush->wakeup.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count(), true);
|
||||
}
|
||||
});
|
||||
if (m_flush) {
|
||||
m_flush->wakeup.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
m_flushAtomic = m_flush.get();
|
||||
|
||||
m_flushLocal = uv::Async<>::Create(m_loop);
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
if (m_flushLocal) {
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
}
|
||||
m_flushLocalAtomic = m_flushLocal.get();
|
||||
});
|
||||
}
|
||||
@@ -261,8 +283,10 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
auto clientImpl = std::make_shared<net3::ClientImpl3>(
|
||||
m_loop.Now().count(), m_inst, *wire, m_logger, [this](uint32_t repeatMs) {
|
||||
DEBUG4("Setting periodic timer to {}", repeatMs);
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
uv::Timer::Time{repeatMs});
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
uv::Timer::Time{repeatMs});
|
||||
}
|
||||
});
|
||||
clientImpl->Start(
|
||||
m_id, [this, wire,
|
||||
@@ -276,7 +300,9 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_parallelConnect->Succeeded(tcp);
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->Succeeded(tcp);
|
||||
}
|
||||
|
||||
m_wire = std::move(wire);
|
||||
m_clientImpl = std::move(clientImpl);
|
||||
@@ -305,7 +331,7 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
tcp.closed.connect([this, &tcp] {
|
||||
DEBUG3("NT3 TCP connection closed");
|
||||
if (!tcp.IsLoopClosing()) {
|
||||
Disconnect(m_wire->GetDisconnectReason());
|
||||
Disconnect(m_wire ? m_wire->GetDisconnectReason() : "unknown");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -323,6 +349,12 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
tcp.StartRead();
|
||||
}
|
||||
|
||||
void NCImpl3::ForceDisconnect(std::string_view reason) {
|
||||
if (m_wire) {
|
||||
m_wire->Disconnect(reason);
|
||||
}
|
||||
}
|
||||
|
||||
void NCImpl3::Disconnect(std::string_view reason) {
|
||||
INFO("DISCONNECTED NT3 connection: {}", reason);
|
||||
m_clientImpl.reset();
|
||||
@@ -343,34 +375,42 @@ NCImpl4::NCImpl4(
|
||||
[this](uv::Tcp& tcp) { TcpConnected(tcp); });
|
||||
|
||||
m_readLocalTimer = uv::Timer::Create(loop);
|
||||
m_readLocalTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendControl(m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
|
||||
if (m_readLocalTimer) {
|
||||
m_readLocalTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendControl(m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
|
||||
}
|
||||
|
||||
m_sendValuesTimer = uv::Timer::Create(loop);
|
||||
m_sendValuesTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count(), false);
|
||||
}
|
||||
});
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count(), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// set up flush async
|
||||
m_flush = uv::Async<>::Create(m_loop);
|
||||
m_flush->wakeup.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count(), true);
|
||||
}
|
||||
});
|
||||
if (m_flush) {
|
||||
m_flush->wakeup.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
m_flushAtomic = m_flush.get();
|
||||
|
||||
m_flushLocal = uv::Async<>::Create(m_loop);
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
if (m_flushLocal) {
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
}
|
||||
m_flushLocalAtomic = m_flushLocal.get();
|
||||
});
|
||||
}
|
||||
@@ -418,7 +458,9 @@ void NCImpl4::TcpConnected(uv::Tcp& tcp) {
|
||||
}
|
||||
|
||||
void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
m_parallelConnect->Succeeded(tcp);
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->Succeeded(tcp);
|
||||
}
|
||||
|
||||
ConnectionInfo connInfo;
|
||||
uv::AddrToName(tcp.GetPeer(), &connInfo.remote_ip, &connInfo.remote_port);
|
||||
@@ -432,8 +474,10 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
m_loop.Now().count(), m_inst, *m_wire, m_logger, m_timeSyncUpdated,
|
||||
[this](uint32_t repeatMs) {
|
||||
DEBUG4("Setting periodic timer to {}", repeatMs);
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
uv::Timer::Time{repeatMs});
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
uv::Timer::Time{repeatMs});
|
||||
}
|
||||
});
|
||||
m_clientImpl->SetLocal(&m_localStorage);
|
||||
m_localStorage.StartNetwork(&m_localQueue);
|
||||
@@ -451,13 +495,24 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
});
|
||||
ws.binary.connect([this](std::span<const uint8_t> data, bool) {
|
||||
if (m_clientImpl) {
|
||||
m_clientImpl->ProcessIncomingBinary(data);
|
||||
m_clientImpl->ProcessIncomingBinary(m_loop.Now().count(), data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NCImpl4::ForceDisconnect(std::string_view reason) {
|
||||
if (m_wire) {
|
||||
m_wire->Disconnect(reason);
|
||||
}
|
||||
}
|
||||
|
||||
void NCImpl4::Disconnect(std::string_view reason) {
|
||||
INFO("DISCONNECTED NT4 connection: {}", reason);
|
||||
std::string realReason;
|
||||
if (m_wire) {
|
||||
realReason = m_wire->GetDisconnectReason();
|
||||
}
|
||||
INFO("DISCONNECTED NT4 connection: {}",
|
||||
realReason.empty() ? reason : realReason);
|
||||
m_clientImpl.reset();
|
||||
m_wire.reset();
|
||||
NCImpl::Disconnect(reason);
|
||||
@@ -492,6 +547,11 @@ void NetworkClient::SetServers(
|
||||
m_impl->SetServers(servers, NT_DEFAULT_PORT4);
|
||||
}
|
||||
|
||||
void NetworkClient::Disconnect() {
|
||||
m_impl->m_loopRunner.ExecAsync(
|
||||
[this](auto&) { m_impl->ForceDisconnect("requested by application"); });
|
||||
}
|
||||
|
||||
void NetworkClient::StartDSClient(unsigned int port) {
|
||||
m_impl->StartDSClient(port);
|
||||
}
|
||||
@@ -535,6 +595,11 @@ void NetworkClient3::SetServers(
|
||||
m_impl->SetServers(servers, NT_DEFAULT_PORT3);
|
||||
}
|
||||
|
||||
void NetworkClient3::Disconnect() {
|
||||
m_impl->m_loopRunner.ExecAsync(
|
||||
[this](auto&) { m_impl->ForceDisconnect("requested by application"); });
|
||||
}
|
||||
|
||||
void NetworkClient3::StartDSClient(unsigned int port) {
|
||||
m_impl->StartDSClient(port);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ class NetworkClient final : public INetworkClient {
|
||||
|
||||
void SetServers(
|
||||
std::span<const std::pair<std::string, unsigned int>> servers) final;
|
||||
void Disconnect() final;
|
||||
|
||||
void StartDSClient(unsigned int port) final;
|
||||
void StopDSClient() final;
|
||||
@@ -59,6 +60,7 @@ class NetworkClient3 final : public INetworkClient {
|
||||
|
||||
void SetServers(
|
||||
std::span<const std::pair<std::string, unsigned int>> servers) final;
|
||||
void Disconnect() final;
|
||||
|
||||
void StartDSClient(unsigned int port) final;
|
||||
void StopDSClient() final;
|
||||
|
||||
@@ -255,8 +255,9 @@ void ServerConnection4::ProcessWsUpgrade() {
|
||||
m_info.remote_id = dedupName;
|
||||
m_server.AddConnection(this, m_info);
|
||||
m_websocket->closed.connect([this](uint16_t, std::string_view reason) {
|
||||
auto realReason = m_wire->GetDisconnectReason();
|
||||
INFO("DISCONNECTED NT4 client '{}' (from {}): {}", m_info.remote_id,
|
||||
m_connInfo, reason);
|
||||
m_connInfo, realReason.empty() ? reason : realReason);
|
||||
ConnectionClosed();
|
||||
});
|
||||
m_websocket->text.connect([this](std::string_view data, bool) {
|
||||
@@ -360,8 +361,16 @@ void NSImpl::LoadPersistent() {
|
||||
auto size = fs::file_size(m_persistentFilename, ec);
|
||||
wpi::raw_fd_istream is{m_persistentFilename, ec};
|
||||
if (ec.value() != 0) {
|
||||
INFO("could not open persistent file '{}': {}", m_persistentFilename,
|
||||
ec.message());
|
||||
INFO(
|
||||
"could not open persistent file '{}': {} "
|
||||
"(this can be ignored if you aren't expecting persistent values)",
|
||||
m_persistentFilename, ec.message());
|
||||
// try to write an empty file so it doesn't happen again
|
||||
wpi::raw_fd_ostream os{m_persistentFilename, ec, fs::F_Text};
|
||||
if (ec.value() == 0) {
|
||||
os << "[]\n";
|
||||
os.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
is.readinto(m_persistentData, size);
|
||||
@@ -411,36 +420,46 @@ void NSImpl::Init() {
|
||||
|
||||
// set up timers
|
||||
m_readLocalTimer = uv::Timer::Create(m_loop);
|
||||
m_readLocalTimer->timeout.connect([this] {
|
||||
HandleLocal();
|
||||
m_serverImpl.SendControl(m_loop.Now().count());
|
||||
});
|
||||
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
|
||||
if (m_readLocalTimer) {
|
||||
m_readLocalTimer->timeout.connect([this] {
|
||||
HandleLocal();
|
||||
m_serverImpl.SendControl(m_loop.Now().count());
|
||||
});
|
||||
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
|
||||
}
|
||||
|
||||
m_savePersistentTimer = uv::Timer::Create(m_loop);
|
||||
m_savePersistentTimer->timeout.connect([this] {
|
||||
if (m_serverImpl.PersistentChanged()) {
|
||||
uv::QueueWork(
|
||||
m_loop,
|
||||
[this, fn = m_persistentFilename,
|
||||
data = m_serverImpl.DumpPersistent()] { SavePersistent(fn, data); },
|
||||
nullptr);
|
||||
}
|
||||
});
|
||||
m_savePersistentTimer->Start(uv::Timer::Time{1000}, uv::Timer::Time{1000});
|
||||
if (m_savePersistentTimer) {
|
||||
m_savePersistentTimer->timeout.connect([this] {
|
||||
if (m_serverImpl.PersistentChanged()) {
|
||||
uv::QueueWork(
|
||||
m_loop,
|
||||
[this, fn = m_persistentFilename,
|
||||
data = m_serverImpl.DumpPersistent()] {
|
||||
SavePersistent(fn, data);
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
});
|
||||
m_savePersistentTimer->Start(uv::Timer::Time{1000}, uv::Timer::Time{1000});
|
||||
}
|
||||
|
||||
// set up flush async
|
||||
m_flush = uv::Async<>::Create(m_loop);
|
||||
m_flush->wakeup.connect([this] {
|
||||
HandleLocal();
|
||||
for (auto&& conn : m_connections) {
|
||||
m_serverImpl.SendValues(conn.conn->GetClientId(), m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
if (m_flush) {
|
||||
m_flush->wakeup.connect([this] {
|
||||
HandleLocal();
|
||||
for (auto&& conn : m_connections) {
|
||||
m_serverImpl.SendValues(conn.conn->GetClientId(), m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
}
|
||||
m_flushAtomic = m_flush.get();
|
||||
|
||||
m_flushLocal = uv::Async<>::Create(m_loop);
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
if (m_flushLocal) {
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
}
|
||||
m_flushLocalAtomic = m_flushLocal.get();
|
||||
|
||||
INFO("Listening on NT3 port {}, NT4 port {}", m_port3, m_port4);
|
||||
|
||||
@@ -1298,6 +1298,18 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_setServerTeam
|
||||
nt::SetServerTeam(inst, team, port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: disconnect
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_disconnect
|
||||
(JNIEnv* env, jclass, jint inst)
|
||||
{
|
||||
nt::Disconnect(inst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: startDSClient
|
||||
|
||||
@@ -30,9 +30,9 @@ using namespace nt::net;
|
||||
|
||||
static constexpr uint32_t kMinPeriodMs = 5;
|
||||
|
||||
// maximum number of times the wire can be not ready to send another
|
||||
// maximum amount of time the wire can be not ready to send another
|
||||
// transmission before we close the connection
|
||||
static constexpr int kWireMaxNotReady = 10;
|
||||
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -53,12 +53,12 @@ class CImpl : public ServerMessageHandler {
|
||||
timeSyncUpdated,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic);
|
||||
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data);
|
||||
void ProcessIncomingBinary(uint64_t curTimeMs, std::span<const uint8_t> data);
|
||||
void HandleLocal(std::vector<ClientMessage>&& msgs);
|
||||
bool SendControl(uint64_t curTimeMs);
|
||||
void SendValues(uint64_t curTimeMs, bool flush);
|
||||
void SendInitialValues();
|
||||
bool CheckNetworkReady();
|
||||
bool CheckNetworkReady(uint64_t curTimeMs);
|
||||
|
||||
// ServerMessageHandler interface
|
||||
void ServerAnnounce(std::string_view name, int64_t id,
|
||||
@@ -91,6 +91,7 @@ class CImpl : public ServerMessageHandler {
|
||||
// timestamp handling
|
||||
static constexpr uint32_t kPingIntervalMs = 3000;
|
||||
uint64_t m_nextPingTimeMs{0};
|
||||
uint64_t m_pongTimeMs{0};
|
||||
uint32_t m_rtt2Us{UINT32_MAX};
|
||||
bool m_haveTimeOffset{false};
|
||||
int64_t m_serverTimeOffsetUs{0};
|
||||
@@ -98,7 +99,6 @@ class CImpl : public ServerMessageHandler {
|
||||
// periodic sweep handling
|
||||
uint32_t m_periodMs{kPingIntervalMs + 10};
|
||||
uint64_t m_lastSendMs{0};
|
||||
int m_notReadyCount{0};
|
||||
|
||||
// outgoing queue
|
||||
std::vector<ClientMessage> m_outgoing;
|
||||
@@ -126,7 +126,8 @@ CImpl::CImpl(
|
||||
m_setPeriodic(m_periodMs);
|
||||
}
|
||||
|
||||
void CImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
void CImpl::ProcessIncomingBinary(uint64_t curTimeMs,
|
||||
std::span<const uint8_t> data) {
|
||||
for (;;) {
|
||||
if (data.empty()) {
|
||||
break;
|
||||
@@ -151,6 +152,7 @@ void CImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
}
|
||||
DEBUG4("RTT ping response time {} value {}", value.time(),
|
||||
value.GetInteger());
|
||||
m_pongTimeMs = curTimeMs;
|
||||
int64_t now = wpi::Now();
|
||||
int64_t rtt2 = (now - value.GetInteger()) / 2;
|
||||
if (rtt2 < m_rtt2Us) {
|
||||
@@ -208,7 +210,13 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
|
||||
|
||||
// start a timestamp RTT ping if it's time to do one
|
||||
if (curTimeMs >= m_nextPingTimeMs) {
|
||||
if (!CheckNetworkReady()) {
|
||||
// if we didn't receive a response to our last ping, disconnect
|
||||
if (m_nextPingTimeMs != 0 && m_pongTimeMs == 0) {
|
||||
m_wire.Disconnect("timed out");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return false;
|
||||
}
|
||||
auto now = wpi::Now();
|
||||
@@ -216,10 +224,11 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
|
||||
WireEncodeBinary(m_wire.SendBinary().Add(), -1, 0, Value::MakeInteger(now));
|
||||
// drift isn't critical here, so just go from current time
|
||||
m_nextPingTimeMs = curTimeMs + kPingIntervalMs;
|
||||
m_pongTimeMs = 0;
|
||||
}
|
||||
|
||||
if (!m_outgoing.empty()) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return false;
|
||||
}
|
||||
auto writer = m_wire.SendText();
|
||||
@@ -258,7 +267,7 @@ void CImpl::SendValues(uint64_t curTimeMs, bool flush) {
|
||||
(flush || curTimeMs >= pub->nextSendMs)) {
|
||||
for (auto&& val : pub->outValues) {
|
||||
if (!checkedNetwork) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return;
|
||||
}
|
||||
checkedNetwork = true;
|
||||
@@ -268,6 +277,10 @@ void CImpl::SendValues(uint64_t curTimeMs, bool flush) {
|
||||
int64_t time = val.time();
|
||||
if (time != 0) {
|
||||
time += m_serverTimeOffsetUs;
|
||||
// make sure resultant time isn't exactly 0
|
||||
if (time == 0) {
|
||||
time = 1;
|
||||
}
|
||||
}
|
||||
WireEncodeBinary(writer.Add(), Handle{pub->handle}.GetIndex(), time,
|
||||
val);
|
||||
@@ -308,15 +321,15 @@ void CImpl::SendInitialValues() {
|
||||
}
|
||||
}
|
||||
|
||||
bool CImpl::CheckNetworkReady() {
|
||||
bool CImpl::CheckNetworkReady(uint64_t curTimeMs) {
|
||||
if (!m_wire.Ready()) {
|
||||
++m_notReadyCount;
|
||||
if (m_notReadyCount > kWireMaxNotReady) {
|
||||
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
|
||||
uint64_t now = wpi::Now();
|
||||
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
|
||||
m_wire.Disconnect("transmit stalled");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
m_notReadyCount = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -462,8 +475,9 @@ void ClientImpl::ProcessIncomingText(std::string_view data) {
|
||||
WireDecodeText(data, *m_impl, m_impl->m_logger);
|
||||
}
|
||||
|
||||
void ClientImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
m_impl->ProcessIncomingBinary(data);
|
||||
void ClientImpl::ProcessIncomingBinary(uint64_t curTimeMs,
|
||||
std::span<const uint8_t> data) {
|
||||
m_impl->ProcessIncomingBinary(curTimeMs, data);
|
||||
}
|
||||
|
||||
void ClientImpl::HandleLocal(std::vector<ClientMessage>&& msgs) {
|
||||
|
||||
@@ -40,7 +40,7 @@ class ClientImpl {
|
||||
~ClientImpl();
|
||||
|
||||
void ProcessIncomingText(std::string_view data);
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data);
|
||||
void ProcessIncomingBinary(uint64_t curTimeMs, std::span<const uint8_t> data);
|
||||
void HandleLocal(std::vector<ClientMessage>&& msgs);
|
||||
|
||||
void SendControl(uint64_t curTimeMs);
|
||||
|
||||
@@ -48,9 +48,9 @@ using namespace mpack;
|
||||
|
||||
static constexpr uint32_t kMinPeriodMs = 5;
|
||||
|
||||
// maximum number of times the wire can be not ready to send another
|
||||
// maximum amount of time the wire can be not ready to send another
|
||||
// transmission before we close the connection
|
||||
static constexpr int kWireMaxNotReady = 10;
|
||||
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -78,12 +78,10 @@ class SImpl;
|
||||
|
||||
class ClientData {
|
||||
public:
|
||||
ClientData(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local,
|
||||
ClientData(std::string_view name, std::string_view connInfo, bool local,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: m_originalName{originalName},
|
||||
m_name{name},
|
||||
: m_name{name},
|
||||
m_connInfo{connInfo},
|
||||
m_local{local},
|
||||
m_setPeriodic{std::move(setPeriodic)},
|
||||
@@ -114,12 +112,10 @@ class ClientData {
|
||||
std::string_view name, bool special,
|
||||
wpi::SmallVectorImpl<SubscriberData*>& buf);
|
||||
|
||||
std::string_view GetOriginalName() const { return m_originalName; }
|
||||
std::string_view GetName() const { return m_name; }
|
||||
int GetId() const { return m_id; }
|
||||
|
||||
protected:
|
||||
std::string m_originalName;
|
||||
std::string m_name;
|
||||
std::string m_connInfo;
|
||||
bool m_local; // local to machine
|
||||
@@ -143,12 +139,10 @@ class ClientData {
|
||||
|
||||
class ClientData4Base : public ClientData, protected ClientMessageHandler {
|
||||
public:
|
||||
ClientData4Base(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local,
|
||||
ClientData4Base(std::string_view name, std::string_view connInfo, bool local,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server,
|
||||
int id, wpi::Logger& logger)
|
||||
: ClientData{originalName, name, connInfo, local,
|
||||
setPeriodic, server, id, logger} {}
|
||||
: ClientData{name, connInfo, local, setPeriodic, server, id, logger} {}
|
||||
|
||||
protected:
|
||||
// ClientMessageHandler interface
|
||||
@@ -170,8 +164,7 @@ class ClientData4Base : public ClientData, protected ClientMessageHandler {
|
||||
class ClientDataLocal final : public ClientData4Base {
|
||||
public:
|
||||
ClientDataLocal(SImpl& server, int id, wpi::Logger& logger)
|
||||
: ClientData4Base{"", "", "", true, [](uint32_t) {}, server, id, logger} {
|
||||
}
|
||||
: ClientData4Base{"", "", true, [](uint32_t) {}, server, id, logger} {}
|
||||
|
||||
void ProcessIncomingText(std::string_view data) final {}
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data) final {}
|
||||
@@ -189,12 +182,10 @@ class ClientDataLocal final : public ClientData4Base {
|
||||
|
||||
class ClientData4 final : public ClientData4Base {
|
||||
public:
|
||||
ClientData4(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local, WireConnection& wire,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: ClientData4Base{originalName, name, connInfo, local,
|
||||
setPeriodic, server, id, logger},
|
||||
ClientData4(std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic,
|
||||
SImpl& server, int id, wpi::Logger& logger)
|
||||
: ClientData4Base{name, connInfo, local, setPeriodic, server, id, logger},
|
||||
m_wire{wire} {}
|
||||
|
||||
void ProcessIncomingText(std::string_view data) final;
|
||||
@@ -214,7 +205,7 @@ class ClientData4 final : public ClientData4Base {
|
||||
|
||||
private:
|
||||
std::vector<ServerMessage> m_outgoing;
|
||||
int m_notReadyCount{0};
|
||||
wpi::DenseMap<NT_Topic, size_t> m_outgoingValueMap;
|
||||
|
||||
bool WriteBinary(int64_t id, int64_t time, const Value& value) {
|
||||
return WireEncodeBinary(SendBinary().Add(), id, time, value);
|
||||
@@ -247,7 +238,7 @@ class ClientData3 final : public ClientData, private net3::MessageHandler3 {
|
||||
net3::WireConnection3& wire, ServerImpl::Connected3Func connected,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: ClientData{"", "", connInfo, local, setPeriodic, server, id, logger},
|
||||
: ClientData{"", connInfo, local, setPeriodic, server, id, logger},
|
||||
m_connected{std::move(connected)},
|
||||
m_wire{wire},
|
||||
m_decoder{*this} {}
|
||||
@@ -292,8 +283,8 @@ class ClientData3 final : public ClientData, private net3::MessageHandler3 {
|
||||
net3::WireDecoder3 m_decoder;
|
||||
|
||||
std::vector<net3::Message3> m_outgoing;
|
||||
wpi::DenseMap<NT_Topic, size_t> m_outgoingValueMap;
|
||||
int64_t m_nextPubUid{1};
|
||||
int m_notReadyCount{0};
|
||||
|
||||
struct TopicData3 {
|
||||
explicit TopicData3(TopicData* topic) { UpdateFlags(topic); }
|
||||
@@ -601,13 +592,17 @@ void ClientData4Base::ClientSetProperties(std::string_view name,
|
||||
auto topicIt = m_server.m_nameTopics.find(name);
|
||||
if (topicIt == m_server.m_nameTopics.end() ||
|
||||
!topicIt->second->IsPublished()) {
|
||||
DEBUG3("ignored SetProperties from {} on non-existent topic '{}'", m_id,
|
||||
name);
|
||||
WARNING(
|
||||
"server ignoring SetProperties({}) from client {} on unpublished topic "
|
||||
"'{}'; publish or set a value first",
|
||||
update.dump(), m_id, name);
|
||||
return; // nothing to do
|
||||
}
|
||||
auto topic = topicIt->second;
|
||||
if (topic->special) {
|
||||
DEBUG3("ignored SetProperties from {} on meta topic '{}'", m_id, name);
|
||||
WARNING(
|
||||
"server ignoring SetProperties({}) from client {} on meta topic '{}'",
|
||||
update.dump(), m_id, name);
|
||||
return; // nothing to do
|
||||
}
|
||||
m_server.SetProperties(nullptr, topic, update);
|
||||
@@ -648,6 +643,11 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
|
||||
}
|
||||
|
||||
// see if this immediately subscribes to any topics
|
||||
// for transmit efficiency, we want to batch announcements and values, so
|
||||
// send announcements in first loop and remember what we want to send in
|
||||
// second loop.
|
||||
std::vector<TopicData*> dataToSend;
|
||||
dataToSend.reserve(m_server.m_topics.size());
|
||||
for (auto&& topic : m_server.m_topics) {
|
||||
bool removed = false;
|
||||
if (replace) {
|
||||
@@ -656,10 +656,13 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
|
||||
|
||||
// is client already subscribed?
|
||||
bool wasSubscribed = false;
|
||||
bool wasSubscribedValue = false;
|
||||
for (auto subscriber : topic->subscribers) {
|
||||
if (subscriber->client == this) {
|
||||
wasSubscribed = true;
|
||||
break;
|
||||
if (!subscriber->options.topicsOnly) {
|
||||
wasSubscribedValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,17 +676,22 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
|
||||
m_server.UpdateMetaTopicSub(topic.get());
|
||||
}
|
||||
|
||||
if (!wasSubscribed && added && !removed) {
|
||||
// announce topic to client
|
||||
// announce topic to client if not previously announced
|
||||
if (added && !removed && !wasSubscribed) {
|
||||
DEBUG4("client {}: announce {}", m_id, topic->name);
|
||||
SendAnnounce(topic.get(), std::nullopt);
|
||||
|
||||
// send last value
|
||||
if (!sub->options.topicsOnly && topic->lastValue) {
|
||||
DEBUG4("send last value for {} to client {}", topic->name, m_id);
|
||||
SendValue(topic.get(), topic->lastValue, kSendAll);
|
||||
}
|
||||
}
|
||||
|
||||
// send last value
|
||||
if (added && !sub->options.topicsOnly && !wasSubscribedValue &&
|
||||
topic->lastValue) {
|
||||
dataToSend.emplace_back(topic.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto topic : dataToSend) {
|
||||
DEBUG4("send last value for {} to client {}", topic->name, m_id);
|
||||
SendValue(topic, topic->lastValue, kSendAll);
|
||||
}
|
||||
|
||||
// update meta data
|
||||
@@ -851,24 +859,23 @@ void ClientData4::SendValue(TopicData* topic, const Value& value,
|
||||
}
|
||||
break;
|
||||
case ClientData::kSendAll: // append to outgoing
|
||||
m_outgoingValueMap[topic->id] = m_outgoing.size();
|
||||
m_outgoing.emplace_back(ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
break;
|
||||
case ClientData::kSendNormal: {
|
||||
// scan outgoing and replace, or append if not present
|
||||
bool found = false;
|
||||
for (auto&& msg : m_outgoing) {
|
||||
if (auto m = std::get_if<ServerValueMsg>(&msg.contents)) {
|
||||
if (m->topic == topic->id) {
|
||||
// replace, or append if not present
|
||||
auto [it, added] =
|
||||
m_outgoingValueMap.try_emplace(topic->id, m_outgoing.size());
|
||||
if (!added && it->second < m_outgoing.size()) {
|
||||
if (auto m =
|
||||
std::get_if<ServerValueMsg>(&m_outgoing[it->second].contents)) {
|
||||
if (m->topic == topic->id) { // should always be true
|
||||
m->value = value;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
m_outgoing.emplace_back(
|
||||
ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
}
|
||||
m_outgoing.emplace_back(ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -937,13 +944,13 @@ void ClientData4::SendOutgoing(uint64_t curTimeMs) {
|
||||
}
|
||||
|
||||
if (!m_wire.Ready()) {
|
||||
++m_notReadyCount;
|
||||
if (m_notReadyCount > kWireMaxNotReady) {
|
||||
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
|
||||
uint64_t now = wpi::Now();
|
||||
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
|
||||
m_wire.Disconnect("transmit stalled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_notReadyCount = 0;
|
||||
|
||||
for (auto&& msg : m_outgoing) {
|
||||
if (auto m = std::get_if<ServerValueMsg>(&msg.contents)) {
|
||||
@@ -953,6 +960,7 @@ void ClientData4::SendOutgoing(uint64_t curTimeMs) {
|
||||
}
|
||||
}
|
||||
m_outgoing.resize(0);
|
||||
m_outgoingValueMap.clear();
|
||||
m_lastSendMs = curTimeMs;
|
||||
}
|
||||
|
||||
@@ -985,6 +993,7 @@ void ClientData3::SendValue(TopicData* topic, const Value& value,
|
||||
mode = ClientData::kSendImmNoFlush; // always send local immediately
|
||||
}
|
||||
TopicData3* topic3 = GetTopic3(topic);
|
||||
bool added = false;
|
||||
|
||||
switch (mode) {
|
||||
case ClientData::kSendDisabled: // do nothing
|
||||
@@ -1005,24 +1014,26 @@ void ClientData3::SendValue(TopicData* topic, const Value& value,
|
||||
}
|
||||
break;
|
||||
case ClientData::kSendNormal: {
|
||||
// scan outgoing and replace, or append if not present
|
||||
bool found = false;
|
||||
for (auto&& msg : m_outgoing) {
|
||||
// replace, or append if not present
|
||||
wpi::DenseMap<NT_Topic, size_t>::iterator it;
|
||||
std::tie(it, added) =
|
||||
m_outgoingValueMap.try_emplace(topic->id, m_outgoing.size());
|
||||
if (!added && it->second < m_outgoing.size()) {
|
||||
auto& msg = m_outgoing[it->second];
|
||||
if (msg.Is(net3::Message3::kEntryUpdate) ||
|
||||
msg.Is(net3::Message3::kEntryAssign)) {
|
||||
if (msg.id() == topic->id) {
|
||||
if (msg.id() == topic->id) { // should always be true
|
||||
msg.SetValue(value);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fallthrough
|
||||
case ClientData::kSendAll: // append to outgoing
|
||||
if (!added) {
|
||||
m_outgoingValueMap[topic->id] = m_outgoing.size();
|
||||
}
|
||||
++topic3->seqNum;
|
||||
if (topic3->sentAssign) {
|
||||
m_outgoing.emplace_back(net3::Message3::EntryUpdate(
|
||||
@@ -1110,19 +1121,20 @@ void ClientData3::SendOutgoing(uint64_t curTimeMs) {
|
||||
}
|
||||
|
||||
if (!m_wire.Ready()) {
|
||||
++m_notReadyCount;
|
||||
if (m_notReadyCount > kWireMaxNotReady) {
|
||||
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
|
||||
uint64_t now = wpi::Now();
|
||||
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
|
||||
m_wire.Disconnect("transmit stalled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_notReadyCount = 0;
|
||||
|
||||
auto out = m_wire.Send();
|
||||
for (auto&& msg : m_outgoing) {
|
||||
net3::WireEncode(out.stream(), msg);
|
||||
}
|
||||
m_outgoing.resize(0);
|
||||
m_outgoingValueMap.clear();
|
||||
m_lastSendMs = curTimeMs;
|
||||
}
|
||||
|
||||
@@ -1281,7 +1293,7 @@ void ClientData3::EntryAssign(std::string_view name, unsigned int id,
|
||||
auto topic = m_server.CreateTopic(this, name, typeStr, properties);
|
||||
TopicData3* topic3 = GetTopic3(topic);
|
||||
if (topic3->published || topic3->sentAssign) {
|
||||
WARNING("ignorning client {} duplicate publish of '{}'", m_id, name);
|
||||
WARNING("ignoring client {} duplicate publish of '{}'", m_id, name);
|
||||
return;
|
||||
}
|
||||
++topic3->seqNum;
|
||||
@@ -1501,39 +1513,29 @@ SImpl::SImpl(wpi::Logger& logger) : m_logger{logger} {
|
||||
std::pair<std::string, int> SImpl::AddClient(
|
||||
std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic) {
|
||||
// strip anything after @ in the name
|
||||
name = wpi::split(name, '@').first;
|
||||
if (name.empty()) {
|
||||
name = "NT4";
|
||||
}
|
||||
size_t index = m_clients.size();
|
||||
// find an empty slot and check for duplicates
|
||||
// find an empty slot
|
||||
// just do a linear search as number of clients is typically small (<10)
|
||||
int duplicateName = 0;
|
||||
for (size_t i = 0, end = index; i < end; ++i) {
|
||||
auto& clientData = m_clients[i];
|
||||
if (clientData && clientData->GetOriginalName() == name) {
|
||||
++duplicateName;
|
||||
} else if (!clientData && index == end) {
|
||||
if (!m_clients[i]) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index == m_clients.size()) {
|
||||
m_clients.emplace_back();
|
||||
}
|
||||
|
||||
// if duplicate name, de-duplicate
|
||||
std::string dedupName;
|
||||
if (duplicateName > 0) {
|
||||
dedupName = fmt::format("{}@{}", name, duplicateName);
|
||||
} else {
|
||||
dedupName = name;
|
||||
}
|
||||
// ensure name is unique by suffixing index
|
||||
std::string dedupName = fmt::format("{}@{}", name, index);
|
||||
|
||||
auto& clientData = m_clients[index];
|
||||
clientData = std::make_unique<ClientData4>(name, dedupName, connInfo, local,
|
||||
wire, std::move(setPeriodic),
|
||||
*this, index, m_logger);
|
||||
clientData = std::make_unique<ClientData4>(dedupName, connInfo, local, wire,
|
||||
std::move(setPeriodic), *this,
|
||||
index, m_logger);
|
||||
|
||||
// create client meta topics
|
||||
clientData->m_metaPub =
|
||||
@@ -2125,7 +2127,7 @@ void SImpl::SetFlags(ClientData* client, TopicData* topic, unsigned int flags) {
|
||||
void SImpl::SetValue(ClientData* client, TopicData* topic, const Value& value) {
|
||||
// update retained value if from same client or timestamp newer
|
||||
if (!topic->lastValue || topic->lastValueClient == client ||
|
||||
value.time() >= topic->lastValue.time()) {
|
||||
topic->lastValue.time() == 0 || value.time() >= topic->lastValue.time()) {
|
||||
DEBUG4("updating '{}' last value (time was {} is {})", topic->name,
|
||||
topic->lastValue.time(), value.time());
|
||||
topic->lastValue = value;
|
||||
@@ -2287,9 +2289,10 @@ void ServerImpl::SendControl(uint64_t curTimeMs) {
|
||||
}
|
||||
|
||||
void ServerImpl::SendValues(int clientId, uint64_t curTimeMs) {
|
||||
auto client = m_impl->m_clients[clientId].get();
|
||||
client->SendOutgoing(curTimeMs);
|
||||
client->Flush();
|
||||
if (auto client = m_impl->m_clients[clientId].get()) {
|
||||
client->SendOutgoing(curTimeMs);
|
||||
client->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void ServerImpl::HandleLocal(std::span<const ClientMessage> msgs) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <span>
|
||||
|
||||
#include <wpi/SpanExtras.h>
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpinet/WebSocket.h>
|
||||
|
||||
using namespace nt;
|
||||
@@ -25,6 +26,12 @@ WebSocketConnection::~WebSocketConnection() {
|
||||
for (auto&& buf : m_buf_pool) {
|
||||
buf.Deallocate();
|
||||
}
|
||||
for (auto&& buf : m_text_buffers) {
|
||||
buf.Deallocate();
|
||||
}
|
||||
for (auto&& buf : m_binary_buffers) {
|
||||
buf.Deallocate();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketConnection::Flush() {
|
||||
@@ -50,6 +57,10 @@ void WebSocketConnection::Flush() {
|
||||
if (self->m_sendsActive > 0) {
|
||||
--self->m_sendsActive;
|
||||
}
|
||||
} else {
|
||||
for (auto&& buf : bufs) {
|
||||
buf.Deallocate();
|
||||
}
|
||||
}
|
||||
});
|
||||
m_frames.clear();
|
||||
@@ -57,9 +68,11 @@ void WebSocketConnection::Flush() {
|
||||
m_binary_buffers.clear();
|
||||
m_text_pos = 0;
|
||||
m_binary_pos = 0;
|
||||
m_lastFlushTime = wpi::Now();
|
||||
}
|
||||
|
||||
void WebSocketConnection::Disconnect(std::string_view reason) {
|
||||
m_reason = reason;
|
||||
m_ws.Close(1005, reason);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
@@ -32,8 +34,12 @@ class WebSocketConnection final
|
||||
|
||||
void Flush() final;
|
||||
|
||||
uint64_t GetLastFlushTime() const final { return m_lastFlushTime; }
|
||||
|
||||
void Disconnect(std::string_view reason) final;
|
||||
|
||||
std::string_view GetDisconnectReason() const { return m_reason; }
|
||||
|
||||
private:
|
||||
void StartSendText() final;
|
||||
void FinishSendText() final;
|
||||
@@ -64,6 +70,8 @@ class WebSocketConnection final
|
||||
size_t m_binary_pos = 0;
|
||||
bool m_in_text = false;
|
||||
int m_sendsActive = 0;
|
||||
std::string m_reason;
|
||||
uint64_t m_lastFlushTime = 0;
|
||||
};
|
||||
|
||||
} // namespace nt::net
|
||||
|
||||
@@ -30,6 +30,8 @@ class WireConnection {
|
||||
|
||||
virtual void Flush() = 0;
|
||||
|
||||
virtual uint64_t GetLastFlushTime() const = 0; // in microseconds
|
||||
|
||||
virtual void Disconnect(std::string_view reason) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -142,10 +142,9 @@ bool nt::net::WireEncodeText(wpi::raw_ostream& os, const ClientMessage& msg) {
|
||||
} else if (auto m = std::get_if<SetPropertiesMsg>(&msg.contents)) {
|
||||
WireEncodeSetProperties(os, m->name, m->update);
|
||||
} else if (auto m = std::get_if<SubscribeMsg>(&msg.contents)) {
|
||||
WireEncodeSubscribe(os, Handle{m->subHandle}.GetIndex(), m->topicNames,
|
||||
m->options);
|
||||
WireEncodeSubscribe(os, m->subHandle, m->topicNames, m->options);
|
||||
} else if (auto m = std::get_if<UnsubscribeMsg>(&msg.contents)) {
|
||||
WireEncodeUnsubscribe(os, Handle{m->subHandle}.GetIndex());
|
||||
WireEncodeUnsubscribe(os, m->subHandle);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Log.h"
|
||||
@@ -31,9 +32,9 @@ using namespace nt::net3;
|
||||
|
||||
static constexpr uint32_t kMinPeriodMs = 5;
|
||||
|
||||
// maximum number of times the wire can be not ready to send another
|
||||
// maximum amount of time the wire can be not ready to send another
|
||||
// transmission before we close the connection
|
||||
static constexpr int kWireMaxNotReady = 10;
|
||||
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -93,7 +94,7 @@ class CImpl : public MessageHandler3 {
|
||||
void HandleLocal(std::span<const net::ClientMessage> msgs);
|
||||
void SendPeriodic(uint64_t curTimeMs, bool initial, bool flush);
|
||||
void SendValue(Writer& out, Entry* entry, const Value& value);
|
||||
bool CheckNetworkReady();
|
||||
bool CheckNetworkReady(uint64_t curTimeMs);
|
||||
|
||||
// Outgoing handlers
|
||||
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
|
||||
@@ -142,7 +143,6 @@ class CImpl : public MessageHandler3 {
|
||||
uint32_t m_periodMs{kKeepAliveIntervalMs + 10};
|
||||
uint64_t m_lastSendMs{0};
|
||||
uint64_t m_nextKeepAliveTimeMs;
|
||||
int m_notReadyCount{0};
|
||||
|
||||
// indexed by publisher index
|
||||
std::vector<std::unique_ptr<PublisherData>> m_publishers;
|
||||
@@ -233,9 +233,9 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
|
||||
|
||||
auto out = m_wire.Send();
|
||||
|
||||
// send keep-alives
|
||||
// send keep-alive
|
||||
if (curTimeMs >= m_nextKeepAliveTimeMs) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return;
|
||||
}
|
||||
DEBUG4("Sending keep alive");
|
||||
@@ -246,7 +246,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
|
||||
|
||||
// send any stored-up flags updates
|
||||
if (!m_outgoingFlags.empty()) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return;
|
||||
}
|
||||
for (auto&& p : m_outgoingFlags) {
|
||||
@@ -261,7 +261,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
|
||||
if (pub && !pub->outValues.empty() &&
|
||||
(flush || curTimeMs >= pub->nextSendMs)) {
|
||||
if (!checkedNetwork) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return;
|
||||
}
|
||||
checkedNetwork = true;
|
||||
@@ -302,15 +302,15 @@ void CImpl::SendValue(Writer& out, Entry* entry, const Value& value) {
|
||||
}
|
||||
}
|
||||
|
||||
bool CImpl::CheckNetworkReady() {
|
||||
bool CImpl::CheckNetworkReady(uint64_t curTimeMs) {
|
||||
if (!m_wire.Ready()) {
|
||||
++m_notReadyCount;
|
||||
if (m_notReadyCount > kWireMaxNotReady) {
|
||||
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
|
||||
uint64_t now = wpi::Now();
|
||||
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
|
||||
m_wire.Disconnect("transmit stalled");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
m_notReadyCount = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "UvStreamConnection3.h"
|
||||
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpinet/uv/Stream.h>
|
||||
|
||||
using namespace nt;
|
||||
@@ -33,6 +34,7 @@ void UvStreamConnection3::Flush() {
|
||||
});
|
||||
m_buffers.clear();
|
||||
m_os.reset();
|
||||
m_lastFlushTime = wpi::Now();
|
||||
}
|
||||
|
||||
void UvStreamConnection3::Disconnect(std::string_view reason) {
|
||||
|
||||
@@ -38,6 +38,8 @@ class UvStreamConnection3 final
|
||||
|
||||
void Flush() final;
|
||||
|
||||
uint64_t GetLastFlushTime() const final { return m_lastFlushTime; }
|
||||
|
||||
void Disconnect(std::string_view reason) final;
|
||||
|
||||
std::string_view GetDisconnectReason() const { return m_reason; }
|
||||
@@ -54,6 +56,7 @@ class UvStreamConnection3 final
|
||||
std::vector<wpi::uv::Buffer> m_buf_pool;
|
||||
wpi::raw_uv_ostream m_os;
|
||||
std::string m_reason;
|
||||
uint64_t m_lastFlushTime = 0;
|
||||
int m_sendsActive = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ class WireConnection3 {
|
||||
|
||||
virtual void Flush() = 0;
|
||||
|
||||
virtual uint64_t GetLastFlushTime() const = 0; // in microseconds
|
||||
|
||||
virtual void Disconnect(std::string_view reason) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -537,6 +537,10 @@ void NT_SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port) {
|
||||
nt::SetServerTeam(inst, team, port);
|
||||
}
|
||||
|
||||
void NT_Disconnect(NT_Inst inst) {
|
||||
nt::Disconnect(inst);
|
||||
}
|
||||
|
||||
void NT_StartDSClient(NT_Inst inst, unsigned int port) {
|
||||
nt::StartDSClient(inst, port);
|
||||
}
|
||||
|
||||
@@ -685,6 +685,14 @@ void SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port) {
|
||||
}
|
||||
}
|
||||
|
||||
void Disconnect(NT_Inst inst) {
|
||||
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
|
||||
if (auto client = ii->GetClient()) {
|
||||
client->Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StartDSClient(NT_Inst inst, unsigned int port) {
|
||||
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
|
||||
if (auto client = ii->GetClient()) {
|
||||
|
||||
@@ -586,6 +586,12 @@ class NetworkTableInstance final {
|
||||
*/
|
||||
void SetServerTeam(unsigned int team, unsigned int port = 0);
|
||||
|
||||
/**
|
||||
* Disconnects the client if it's running and connected. This will
|
||||
* automatically start reconnection attempts to the current server list.
|
||||
*/
|
||||
void Disconnect();
|
||||
|
||||
/**
|
||||
* Starts requesting server address from Driver Station.
|
||||
* This connects to the Driver Station running on localhost to obtain the
|
||||
|
||||
@@ -163,6 +163,10 @@ inline void NetworkTableInstance::SetServerTeam(unsigned int team,
|
||||
::nt::SetServerTeam(m_handle, team, port);
|
||||
}
|
||||
|
||||
inline void NetworkTableInstance::Disconnect() {
|
||||
::nt::Disconnect(m_handle);
|
||||
}
|
||||
|
||||
inline void NetworkTableInstance::StartDSClient(unsigned int port) {
|
||||
::nt::StartDSClient(m_handle, port);
|
||||
}
|
||||
|
||||
@@ -1147,6 +1147,14 @@ void NT_SetServerMulti(NT_Inst inst, size_t count, const char** server_names,
|
||||
*/
|
||||
void NT_SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port);
|
||||
|
||||
/**
|
||||
* Disconnects the client if it's running and connected. This will automatically
|
||||
* start reconnection attempts to the current server list.
|
||||
*
|
||||
* @param inst instance handle
|
||||
*/
|
||||
void NT_Disconnect(NT_Inst inst);
|
||||
|
||||
/**
|
||||
* Starts requesting server address from Driver Station.
|
||||
* This connects to the Driver Station running on localhost to obtain the
|
||||
|
||||
@@ -1087,6 +1087,14 @@ void SetServer(
|
||||
*/
|
||||
void SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port);
|
||||
|
||||
/**
|
||||
* Disconnects the client if it's running and connected. This will automatically
|
||||
* start reconnection attempts to the current server list.
|
||||
*
|
||||
* @param inst instance handle
|
||||
*/
|
||||
void Disconnect(NT_Inst inst);
|
||||
|
||||
/**
|
||||
* Starts requesting server address from Driver Station.
|
||||
* This connects to the Driver Station running on localhost to obtain the
|
||||
|
||||
@@ -33,8 +33,10 @@ class SpanMatcher : public ::testing::MatcherInterface<std::span<T>> {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline ::testing::Matcher<std::span<const T>> SpanEq(std::span<const T> good) {
|
||||
return ::testing::MakeMatcher(new SpanMatcher(good));
|
||||
inline ::testing::Matcher<std::span<const typename T::value_type>> SpanEq(
|
||||
const T& good) {
|
||||
return ::testing::MakeMatcher(
|
||||
new SpanMatcher(std::span<const typename T::value_type>(good)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -32,6 +32,8 @@ class MockWireConnection : public WireConnection {
|
||||
|
||||
MOCK_METHOD(void, Flush, (), (override));
|
||||
|
||||
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
|
||||
|
||||
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
|
||||
|
||||
protected:
|
||||
|
||||
333
ntcore/src/test/native/cpp/net/ServerImplTest.cpp
Normal file
333
ntcore/src/test/native/cpp/net/ServerImplTest.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "../MockLogger.h"
|
||||
#include "../PubSubOptionsMatcher.h"
|
||||
#include "../SpanMatcher.h"
|
||||
#include "../TestPrinters.h"
|
||||
#include "../ValueMatcher.h"
|
||||
#include "Handle.h"
|
||||
#include "MockNetworkInterface.h"
|
||||
#include "MockWireConnection.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "net/Message.h"
|
||||
#include "net/ServerImpl.h"
|
||||
#include "net/WireEncoder.h"
|
||||
#include "ntcore_c.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Field;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Property;
|
||||
using ::testing::Return;
|
||||
|
||||
using MockSetPeriodicFunc = ::testing::MockFunction<void(uint32_t repeatMs)>;
|
||||
using MockConnected3Func =
|
||||
::testing::MockFunction<void(std::string_view name, uint16_t proto)>;
|
||||
|
||||
namespace nt {
|
||||
|
||||
class ServerImplTest : public ::testing::Test {
|
||||
public:
|
||||
::testing::StrictMock<net::MockLocalInterface> local;
|
||||
wpi::MockLogger logger;
|
||||
net::ServerImpl server{logger};
|
||||
};
|
||||
|
||||
TEST_F(ServerImplTest, AddClient) {
|
||||
::testing::StrictMock<net::MockWireConnection> wire;
|
||||
EXPECT_CALL(wire, Flush());
|
||||
MockSetPeriodicFunc setPeriodic;
|
||||
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
|
||||
setPeriodic.AsStdFunction());
|
||||
EXPECT_EQ(name, "test@1");
|
||||
EXPECT_NE(id, -1);
|
||||
}
|
||||
|
||||
TEST_F(ServerImplTest, AddDuplicateClient) {
|
||||
::testing::StrictMock<net::MockWireConnection> wire1;
|
||||
::testing::StrictMock<net::MockWireConnection> wire2;
|
||||
MockSetPeriodicFunc setPeriodic1;
|
||||
MockSetPeriodicFunc setPeriodic2;
|
||||
EXPECT_CALL(wire1, Flush());
|
||||
EXPECT_CALL(wire2, Flush());
|
||||
|
||||
auto [name1, id1] = server.AddClient("test", "connInfo", false, wire1,
|
||||
setPeriodic1.AsStdFunction());
|
||||
auto [name2, id2] = server.AddClient("test", "connInfo2", false, wire2,
|
||||
setPeriodic2.AsStdFunction());
|
||||
EXPECT_EQ(name1, "test@1");
|
||||
EXPECT_NE(id1, -1);
|
||||
EXPECT_EQ(name2, "test@2");
|
||||
EXPECT_NE(id1, id2);
|
||||
EXPECT_NE(id2, -1);
|
||||
}
|
||||
|
||||
TEST_F(ServerImplTest, AddClient3) {}
|
||||
|
||||
template <typename T>
|
||||
static std::string EncodeText(const T& msgs) {
|
||||
std::string data;
|
||||
wpi::raw_string_ostream os{data};
|
||||
bool first = true;
|
||||
for (auto&& msg : msgs) {
|
||||
if (first) {
|
||||
os << '[';
|
||||
first = false;
|
||||
} else {
|
||||
os << ',';
|
||||
}
|
||||
net::WireEncodeText(os, msg);
|
||||
}
|
||||
os << ']';
|
||||
return data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::vector<uint8_t> EncodeServerBinary(const T& msgs) {
|
||||
std::vector<uint8_t> data;
|
||||
wpi::raw_uvector_ostream os{data};
|
||||
for (auto&& msg : msgs) {
|
||||
if constexpr (std::is_same_v<typename T::value_type, net::ServerMessage>) {
|
||||
if (auto m = std::get_if<net::ServerValueMsg>(&msg.contents)) {
|
||||
net::WireEncodeBinary(os, m->topic, m->value.time(), m->value);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<typename T::value_type,
|
||||
net::ClientMessage>) {
|
||||
if (auto m = std::get_if<net::ClientValueMsg>(&msg.contents)) {
|
||||
net::WireEncodeBinary(os, Handle{m->pubHandle}.GetIndex(),
|
||||
m->value.time(), m->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST_F(ServerImplTest, PublishLocal) {
|
||||
// publish before client connect
|
||||
server.SetLocal(&local);
|
||||
NT_Publisher pubHandle = nt::Handle{0, 1, nt::Handle::kPublisher};
|
||||
NT_Topic topicHandle = nt::Handle{0, 1, nt::Handle::kTopic};
|
||||
NT_Publisher pubHandle2 = nt::Handle{0, 2, nt::Handle::kPublisher};
|
||||
NT_Topic topicHandle2 = nt::Handle{0, 2, nt::Handle::kTopic};
|
||||
NT_Publisher pubHandle3 = nt::Handle{0, 3, nt::Handle::kPublisher};
|
||||
NT_Topic topicHandle3 = nt::Handle{0, 3, nt::Handle::kTopic};
|
||||
{
|
||||
::testing::InSequence seq;
|
||||
EXPECT_CALL(local, NetworkAnnounce("test", "double", wpi::json::object(),
|
||||
pubHandle));
|
||||
EXPECT_CALL(local, NetworkAnnounce("test2", "double", wpi::json::object(),
|
||||
pubHandle2));
|
||||
EXPECT_CALL(local, NetworkAnnounce("test3", "double", wpi::json::object(),
|
||||
pubHandle3));
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
|
||||
pubHandle, topicHandle, "test", "double", wpi::json::object(), {}}});
|
||||
server.HandleLocal(msgs);
|
||||
}
|
||||
|
||||
// client connect; it should get already-published topic as soon as it
|
||||
// subscribes
|
||||
::testing::StrictMock<net::MockWireConnection> wire;
|
||||
MockSetPeriodicFunc setPeriodic;
|
||||
{
|
||||
::testing::InSequence seq;
|
||||
EXPECT_CALL(wire, Flush()); // AddClient()
|
||||
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
|
||||
EXPECT_CALL(wire, Flush()); // ClientSubscribe()
|
||||
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendControl()
|
||||
{
|
||||
std::vector<net::ServerMessage> smsgs;
|
||||
smsgs.emplace_back(net::ServerMessage{net::AnnounceMsg{
|
||||
"test", 3, "double", std::nullopt, wpi::json::object()}});
|
||||
smsgs.emplace_back(net::ServerMessage{net::AnnounceMsg{
|
||||
"test2", 8, "double", std::nullopt, wpi::json::object()}});
|
||||
EXPECT_CALL(wire, Text(EncodeText(smsgs))); // SendControl()
|
||||
}
|
||||
EXPECT_CALL(wire, Flush()); // SendControl()
|
||||
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendControl()
|
||||
{
|
||||
std::vector<net::ServerMessage> smsgs;
|
||||
smsgs.emplace_back(net::ServerMessage{net::AnnounceMsg{
|
||||
"test3", 11, "double", std::nullopt, wpi::json::object()}});
|
||||
EXPECT_CALL(wire, Text(EncodeText(smsgs))); // SendControl()
|
||||
}
|
||||
EXPECT_CALL(wire, Flush()); // SendControl()
|
||||
}
|
||||
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
|
||||
setPeriodic.AsStdFunction());
|
||||
|
||||
{
|
||||
NT_Subscriber subHandle = nt::Handle{0, 1, nt::Handle::kSubscriber};
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{net::SubscribeMsg{
|
||||
subHandle, {{""}}, PubSubOptions{.prefixMatch = true}}});
|
||||
server.ProcessIncomingText(id, EncodeText(msgs));
|
||||
}
|
||||
|
||||
// publish before send control
|
||||
{
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
|
||||
pubHandle2, topicHandle2, "test2", "double", wpi::json::object(), {}}});
|
||||
server.HandleLocal(msgs);
|
||||
}
|
||||
|
||||
server.SendControl(100);
|
||||
|
||||
// publish after send control
|
||||
{
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
|
||||
pubHandle3, topicHandle3, "test3", "double", wpi::json::object(), {}}});
|
||||
server.HandleLocal(msgs);
|
||||
}
|
||||
|
||||
server.SendControl(200);
|
||||
}
|
||||
|
||||
TEST_F(ServerImplTest, ClientSubTopicOnlyThenValue) {
|
||||
// publish before client connect
|
||||
server.SetLocal(&local);
|
||||
NT_Publisher pubHandle = nt::Handle{0, 1, nt::Handle::kPublisher};
|
||||
NT_Topic topicHandle = nt::Handle{0, 1, nt::Handle::kTopic};
|
||||
EXPECT_CALL(
|
||||
local, NetworkAnnounce("test", "double", wpi::json::object(), pubHandle));
|
||||
|
||||
{
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
|
||||
pubHandle, topicHandle, "test", "double", wpi::json::object(), {}}});
|
||||
msgs.emplace_back(net::ClientMessage{
|
||||
net::ClientValueMsg{pubHandle, Value::MakeDouble(1.0, 10)}});
|
||||
server.HandleLocal(msgs);
|
||||
}
|
||||
|
||||
::testing::StrictMock<net::MockWireConnection> wire;
|
||||
MockSetPeriodicFunc setPeriodic;
|
||||
{
|
||||
::testing::InSequence seq;
|
||||
EXPECT_CALL(wire, Flush()); // AddClient()
|
||||
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
|
||||
EXPECT_CALL(wire, Flush()); // ClientSubscribe()
|
||||
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendValues()
|
||||
{
|
||||
std::vector<net::ServerMessage> smsgs;
|
||||
smsgs.emplace_back(net::ServerMessage{net::AnnounceMsg{
|
||||
"test", 3, "double", std::nullopt, wpi::json::object()}});
|
||||
EXPECT_CALL(wire, Text(EncodeText(smsgs))); // SendValues()
|
||||
}
|
||||
EXPECT_CALL(wire, Flush()); // SendValues()
|
||||
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
|
||||
EXPECT_CALL(wire, Flush()); // ClientSubscribe()
|
||||
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendValues()
|
||||
{
|
||||
std::vector<net::ServerMessage> smsgs;
|
||||
smsgs.emplace_back(net::ServerMessage{
|
||||
net::ServerValueMsg{3, Value::MakeDouble(1.0, 10)}});
|
||||
EXPECT_CALL(
|
||||
wire,
|
||||
Binary(wpi::SpanEq(EncodeServerBinary(smsgs)))); // SendValues()
|
||||
}
|
||||
EXPECT_CALL(wire, Flush()); // SendValues()
|
||||
}
|
||||
|
||||
// connect client
|
||||
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
|
||||
setPeriodic.AsStdFunction());
|
||||
|
||||
// subscribe topics only; will not send value
|
||||
{
|
||||
NT_Subscriber subHandle = nt::Handle{0, 1, nt::Handle::kSubscriber};
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{net::SubscribeMsg{
|
||||
subHandle,
|
||||
{{""}},
|
||||
PubSubOptions{.topicsOnly = true, .prefixMatch = true}}});
|
||||
server.ProcessIncomingText(id, EncodeText(msgs));
|
||||
}
|
||||
|
||||
server.SendValues(id, 100);
|
||||
|
||||
// subscribe normal; will not resend announcement, but will send value
|
||||
{
|
||||
NT_Subscriber subHandle = nt::Handle{0, 2, nt::Handle::kSubscriber};
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{
|
||||
net::SubscribeMsg{subHandle, {{"test"}}, PubSubOptions{}}});
|
||||
server.ProcessIncomingText(id, EncodeText(msgs));
|
||||
}
|
||||
|
||||
server.SendValues(id, 200);
|
||||
}
|
||||
|
||||
TEST_F(ServerImplTest, ZeroTimestampNegativeTime) {
|
||||
// publish before client connect
|
||||
server.SetLocal(&local);
|
||||
NT_Publisher pubHandle = nt::Handle{0, 1, nt::Handle::kPublisher};
|
||||
NT_Topic topicHandle = nt::Handle{0, 1, nt::Handle::kTopic};
|
||||
NT_Subscriber subHandle = nt::Handle{0, 1, nt::Handle::kSubscriber};
|
||||
Value defaultValue = Value::MakeDouble(1.0, 10);
|
||||
defaultValue.SetTime(0);
|
||||
defaultValue.SetServerTime(0);
|
||||
Value value = Value::MakeDouble(5, -10);
|
||||
{
|
||||
::testing::InSequence seq;
|
||||
EXPECT_CALL(local, NetworkAnnounce("test", "double", wpi::json::object(),
|
||||
pubHandle))
|
||||
.WillOnce(Return(topicHandle));
|
||||
EXPECT_CALL(local, NetworkSetValue(topicHandle, defaultValue));
|
||||
EXPECT_CALL(local, NetworkSetValue(topicHandle, value));
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
|
||||
pubHandle, topicHandle, "test", "double", wpi::json::object(), {}}});
|
||||
msgs.emplace_back(
|
||||
net::ClientMessage{net::ClientValueMsg{pubHandle, defaultValue}});
|
||||
msgs.emplace_back(
|
||||
net::ClientMessage{net::SubscribeMsg{subHandle, {"test"}, {}}});
|
||||
server.HandleLocal(msgs);
|
||||
}
|
||||
|
||||
// client connect; it should get already-published topic as soon as it
|
||||
// subscribes
|
||||
::testing::StrictMock<net::MockWireConnection> wire;
|
||||
MockSetPeriodicFunc setPeriodic;
|
||||
{
|
||||
::testing::InSequence seq;
|
||||
EXPECT_CALL(wire, Flush()); // AddClient()
|
||||
}
|
||||
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
|
||||
setPeriodic.AsStdFunction());
|
||||
|
||||
// publish and send non-default value with negative time offset
|
||||
{
|
||||
NT_Subscriber pubHandle2 = nt::Handle{0, 2, nt::Handle::kPublisher};
|
||||
std::vector<net::ClientMessage> msgs;
|
||||
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
|
||||
pubHandle2, topicHandle, "test", "double", wpi::json::object(), {}}});
|
||||
server.ProcessIncomingText(id, EncodeText(msgs));
|
||||
msgs.clear();
|
||||
msgs.emplace_back(
|
||||
net::ClientMessage{net::ClientValueMsg{pubHandle2, value}});
|
||||
server.ProcessIncomingBinary(id, EncodeServerBinary(msgs));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
@@ -172,14 +172,15 @@ TEST_F(WireEncoderTextTest, MessageSubscribe) {
|
||||
ASSERT_TRUE(net::WireEncodeText(os, msg));
|
||||
ASSERT_EQ(os.str(),
|
||||
"{\"method\":\"subscribe\",\"params\":{"
|
||||
"\"options\":{},\"topics\":[\"a\",\"b\"],\"subuid\":5}}");
|
||||
"\"options\":{},\"topics\":[\"a\",\"b\"],\"subuid\":402653189}}");
|
||||
}
|
||||
|
||||
TEST_F(WireEncoderTextTest, MessageUnsubscribe) {
|
||||
net::ClientMessage msg{
|
||||
net::UnsubscribeMsg{Handle{0, 5, Handle::kSubscriber}}};
|
||||
ASSERT_TRUE(net::WireEncodeText(os, msg));
|
||||
ASSERT_EQ(os.str(), "{\"method\":\"unsubscribe\",\"params\":{\"subuid\":5}}");
|
||||
ASSERT_EQ(os.str(),
|
||||
"{\"method\":\"unsubscribe\",\"params\":{\"subuid\":402653189}}");
|
||||
}
|
||||
|
||||
TEST_F(WireEncoderTextTest, MessageAnnounce) {
|
||||
|
||||
@@ -28,6 +28,8 @@ class MockWireConnection3 : public WireConnection3 {
|
||||
|
||||
MOCK_METHOD(void, Flush, (), (override));
|
||||
|
||||
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
|
||||
|
||||
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
|
||||
|
||||
protected:
|
||||
|
||||
@@ -119,6 +119,7 @@ static void DisplayGui() {
|
||||
gui::EmitViewMenu();
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
gFlagsSettings.DisplayMenu();
|
||||
glass::DisplayNetworkTablesAddMenu(gModel.get());
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
@@ -183,6 +184,8 @@ static void DisplayGui() {
|
||||
ImGui::Text("v%s", GetWPILibVersion());
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
|
||||
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate,
|
||||
ImGui::GetIO().Framerate);
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
84
processstarter/build.gradle
Normal file
84
processstarter/build.gradle
Normal file
@@ -0,0 +1,84 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
|
||||
description = "Process Starter"
|
||||
|
||||
apply plugin: 'cpp'
|
||||
apply plugin: 'objective-cpp'
|
||||
apply plugin: 'visual-studio'
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
ext {
|
||||
nativeName = 'processstarter'
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
// Replace shared crt with static crt.
|
||||
// Note this means no wpilib binaries can be dependencies
|
||||
nativeUtils.platformConfigs.named(nativeUtils.wpi.platforms.windowsx64).configure {
|
||||
cppCompiler.debugArgs.remove('/MDd')
|
||||
cppCompiler.debugArgs.add('/MTd')
|
||||
cppCompiler.releaseArgs.remove('/MD')
|
||||
cppCompiler.releaseArgs.add('/MT')
|
||||
}
|
||||
|
||||
project(':').libraryBuild.dependsOn build
|
||||
|
||||
model {
|
||||
components {
|
||||
"${nativeName}"(NativeExecutableSpec) {
|
||||
baseName = 'processstarter'
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.sources {
|
||||
macObjCpp(ObjectiveCppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/osx'
|
||||
include '**/*.mm'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (it.targetPlatform.operatingSystem.isLinux()) {
|
||||
it.sources {
|
||||
linuxCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/linux'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.sources {
|
||||
windowsCpp(CppSourceSet) {
|
||||
source {
|
||||
srcDirs 'src/main/native/windows'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
include '**/*.h'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
67
processstarter/publish.gradle
Normal file
67
processstarter/publish.gradle
Normal file
@@ -0,0 +1,67 @@
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
def baseArtifactId = 'processstarter'
|
||||
def artifactGroupId = 'edu.wpi.first.tools'
|
||||
def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_processstarter_CLS'
|
||||
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
|
||||
model {
|
||||
tasks {
|
||||
// Create the run task.
|
||||
$.components.processstarter.binaries.each { bin ->
|
||||
if (bin.buildable && bin.name.toLowerCase().contains("debug") && nativeUtils.isNativeDesktopPlatform(bin.targetPlatform)) {
|
||||
Task run = project.tasks.create("run", Exec) {
|
||||
commandLine bin.tasks.install.runScriptFile.get().asFile.toString()
|
||||
}
|
||||
run.dependsOn bin.tasks.install
|
||||
}
|
||||
}
|
||||
}
|
||||
publishing {
|
||||
def processstarterTaskList = []
|
||||
$.components.each { component ->
|
||||
component.binaries.each { binary ->
|
||||
if (binary in NativeExecutableBinarySpec && binary.component.name.contains("processstarter")) {
|
||||
if (binary.buildable && (binary.name.contains('Release') || binary.name.contains('release'))) {
|
||||
// We are now in the binary that we want.
|
||||
// This is the default application path for the ZIP task.
|
||||
def applicationPath = binary.executable.file
|
||||
|
||||
// Create the ZIP.
|
||||
def task = project.tasks.create("copyprocessstarterExecutable" + binary.targetPlatform.architecture.name, Zip) {
|
||||
description("Copies the processstarter executable to the outputs directory.")
|
||||
destinationDirectory = outputsFolder
|
||||
|
||||
archiveBaseName = '_M_' + zipBaseName
|
||||
duplicatesStrategy = 'exclude'
|
||||
classifier = nativeUtils.getPublishClassifier(binary)
|
||||
|
||||
from(licenseFile) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from(applicationPath)
|
||||
}
|
||||
|
||||
task.dependsOn binary.tasks.link
|
||||
processstarterTaskList.add(task)
|
||||
project.build.dependsOn task
|
||||
project.artifacts { task }
|
||||
addTaskToCopyAllOutputs(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publications {
|
||||
processstarter(MavenPublication) {
|
||||
processstarterTaskList.each { artifact it }
|
||||
|
||||
artifactId = baseArtifactId
|
||||
groupId = artifactGroupId
|
||||
version wpilibVersioning.version.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
processstarter/src/main/native/linux/main.cpp
Normal file
86
processstarter/src/main/native/linux/main.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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 <poll.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
char path[PATH_MAX];
|
||||
char dest[PATH_MAX];
|
||||
std::memset(dest, 0, sizeof(dest)); // readlink does not null terminate!
|
||||
pid_t pid = getpid();
|
||||
std::snprintf(path, PATH_MAX, "/proc/%d/exe", pid);
|
||||
int readlink_len = readlink(path, dest, PATH_MAX);
|
||||
if (readlink_len < 0) {
|
||||
std::perror("readlink");
|
||||
return 1;
|
||||
} else if (readlink_len == PATH_MAX) {
|
||||
std::printf("Truncation occurred\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::filesystem::path exePath{dest};
|
||||
if (exePath.empty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!exePath.has_stem()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!exePath.has_parent_path()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::filesystem::path jarPath{exePath};
|
||||
jarPath.replace_extension("jar");
|
||||
std::filesystem::path parentPath{exePath.parent_path()};
|
||||
|
||||
if (!parentPath.has_parent_path()) {
|
||||
return 1;
|
||||
}
|
||||
std::filesystem::path toolsFolder{parentPath.parent_path()};
|
||||
|
||||
std::filesystem::path Java = toolsFolder / "jdk" / "bin" / "java";
|
||||
|
||||
pid = 0;
|
||||
std::string data = jarPath;
|
||||
std::string jarArg = "-jar";
|
||||
char* const arguments[] = {jarArg.data(), data.data(), nullptr};
|
||||
|
||||
int status =
|
||||
posix_spawn(&pid, Java.c_str(), nullptr, nullptr, arguments, environ);
|
||||
if (status != 0) {
|
||||
char* home = std::getenv("JAVA_HOME");
|
||||
std::string javaLocal = "java";
|
||||
if (home != nullptr) {
|
||||
std::filesystem::path javaHomePath{home};
|
||||
javaHomePath /= "bin";
|
||||
javaHomePath /= "java";
|
||||
javaLocal = javaHomePath;
|
||||
}
|
||||
|
||||
status = posix_spawn(&pid, javaLocal.c_str(), nullptr, nullptr, arguments,
|
||||
environ);
|
||||
if (status != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int childPid = syscall(SYS_pidfd_open, pid, 0);
|
||||
if (childPid <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct pollfd pfd = {childPid, POLLIN, 0};
|
||||
return poll(&pfd, 1, 3000);
|
||||
}
|
||||
80
processstarter/src/main/native/osx/main.mm
Normal file
80
processstarter/src/main/native/osx/main.mm
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
NSString* exePathPlat = [[NSBundle mainBundle] bundlePath];
|
||||
NSString* identifier = [[NSBundle mainBundle] bundleIdentifier];
|
||||
if (identifier == nil) {
|
||||
exePathPlat = [[NSBundle mainBundle] executablePath];
|
||||
}
|
||||
|
||||
std::filesystem::path exePath{[exePathPlat UTF8String]};
|
||||
if (exePath.empty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!exePath.has_stem()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!exePath.has_parent_path()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::filesystem::path jarPath{exePath};
|
||||
jarPath.replace_extension("jar");
|
||||
std::filesystem::path parentPath{exePath.parent_path()};
|
||||
|
||||
if (!parentPath.has_parent_path()) {
|
||||
return 1;
|
||||
}
|
||||
std::filesystem::path toolsFolder{parentPath.parent_path()};
|
||||
|
||||
std::filesystem::path java = toolsFolder / "jdk" / "bin" / "java";
|
||||
|
||||
NSArray<NSString*>* Arguments =
|
||||
@[ @"-jar", [NSString stringWithFormat:@"%s", jarPath.c_str()] ];
|
||||
|
||||
NSTask* task = [[NSTask alloc] init];
|
||||
task.launchPath = [NSString stringWithFormat:@"%s", java.c_str()];
|
||||
task.arguments = Arguments;
|
||||
task.terminationHandler = ^(NSTask* t) {
|
||||
(void)t;
|
||||
CFRunLoopStop(CFRunLoopGetMain());
|
||||
};
|
||||
|
||||
if (![task launchAndReturnError:nil]) {
|
||||
task.terminationHandler = nil;
|
||||
|
||||
NSString* javaHome =
|
||||
[[[NSProcessInfo processInfo] environment] objectForKey:@"JAVA_HOME"];
|
||||
task = [[NSTask alloc] init];
|
||||
task.launchPath = @"java";
|
||||
if (javaHome != nil) {
|
||||
std::filesystem::path javaHomePath{[javaHome UTF8String]};
|
||||
javaHomePath /= "bin";
|
||||
javaHomePath /= "java";
|
||||
task.launchPath = [NSString stringWithFormat:@"%s", javaHomePath.c_str()];
|
||||
}
|
||||
task.arguments = Arguments;
|
||||
task.terminationHandler = ^(NSTask* t) {
|
||||
(void)t;
|
||||
CFRunLoopStop(CFRunLoopGetMain());
|
||||
};
|
||||
|
||||
if (![task launchAndReturnError:nil]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3, false);
|
||||
|
||||
return task.running ? 0 : 1;
|
||||
}
|
||||
85
processstarter/src/main/native/windows/main.cpp
Normal file
85
processstarter/src/main/native/windows/main.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
// 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 <filesystem>
|
||||
|
||||
#include "Windows.h"
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
||||
LPTSTR pCmdLine, int nCmdShow) {
|
||||
DWORD Status;
|
||||
WCHAR ExePathRaw[1024];
|
||||
Status = GetModuleFileNameW(NULL, ExePathRaw, 1024);
|
||||
if (Status == 0) {
|
||||
DWORD LastError = GetLastError();
|
||||
return LastError;
|
||||
}
|
||||
|
||||
std::filesystem::path ExePath{ExePathRaw};
|
||||
if (ExePath.empty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ExePath.has_stem()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ExePath.has_parent_path()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::filesystem::path JarPath{ExePath};
|
||||
JarPath.replace_extension(L"jar");
|
||||
std::filesystem::path ParentPath{ExePath.parent_path()};
|
||||
|
||||
if (!ParentPath.has_parent_path()) {
|
||||
return 1;
|
||||
}
|
||||
std::filesystem::path ToolsFolder{ParentPath.parent_path()};
|
||||
|
||||
std::filesystem::path Javaw = ToolsFolder / L"jdk" / L"bin" / L"javaw.exe";
|
||||
|
||||
std::wstring ToRun = L" -jar \"";
|
||||
ToRun += JarPath;
|
||||
ToRun += L"\"";
|
||||
|
||||
STARTUPINFOW StartupInfo;
|
||||
PROCESS_INFORMATION ProcessInfo;
|
||||
|
||||
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
|
||||
StartupInfo.cb = sizeof(StartupInfo);
|
||||
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
|
||||
|
||||
if (!CreateProcessW(Javaw.c_str(), ToRun.data(), NULL, NULL, FALSE, 0, NULL,
|
||||
NULL, &StartupInfo, &ProcessInfo)) {
|
||||
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
|
||||
StartupInfo.cb = sizeof(StartupInfo);
|
||||
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
|
||||
|
||||
ToRun = L" -jar \"";
|
||||
ToRun += JarPath;
|
||||
ToRun += L"\"";
|
||||
|
||||
Status = GetEnvironmentVariableW(L"JAVA_HOME", ExePathRaw, 1024);
|
||||
std::wstring JavawLocal = L"javaw";
|
||||
if (Status != 0 && Status < 1024) {
|
||||
std::filesystem::path JavaHomePath{ExePathRaw};
|
||||
JavaHomePath /= "bin";
|
||||
JavaHomePath /= "javaw.exe";
|
||||
JavawLocal = JavaHomePath;
|
||||
}
|
||||
|
||||
if (!CreateProcessW(JavawLocal.c_str(), ToRun.data(), NULL, NULL, FALSE, 0,
|
||||
NULL, NULL, &StartupInfo, &ProcessInfo)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Status =
|
||||
WaitForSingleObject(ProcessInfo.hProcess, 3000); // Wait for 3 seconds
|
||||
CloseHandle(ProcessInfo.hProcess);
|
||||
CloseHandle(ProcessInfo.hThread);
|
||||
|
||||
return Status == WAIT_TIMEOUT ? 0 : 1;
|
||||
}
|
||||
@@ -127,6 +127,8 @@ static void DisplayGui() {
|
||||
static_cast<int>(multicastResolver->HasImplementation()));
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
|
||||
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate,
|
||||
ImGui::GetIO().Framerate);
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ include 'docs'
|
||||
include 'msvcruntime'
|
||||
include 'ntcoreffi'
|
||||
include 'apriltag'
|
||||
include 'processstarter'
|
||||
|
||||
buildCache {
|
||||
def cred = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user