mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-03 03:01:44 +00:00
Compare commits
53 Commits
v2023.1.1-
...
v2023.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b114006543 | ||
|
|
32fbfb7da6 | ||
|
|
02465920fb | ||
|
|
3a5a376465 | ||
|
|
1c3c86e9f1 | ||
|
|
dcda09f90a | ||
|
|
66157397c1 | ||
|
|
9e22ffbebf | ||
|
|
648ab6115c | ||
|
|
8bc3b04f5b | ||
|
|
cfb84a6083 | ||
|
|
02c47726e1 | ||
|
|
b2a0093294 | ||
|
|
2a98d6b5d7 | ||
|
|
9f36301dc8 | ||
|
|
901fc555f4 | ||
|
|
4170ec6107 | ||
|
|
fe400f68c5 | ||
|
|
794669b346 | ||
|
|
dcfa85a5d5 | ||
|
|
15ad855f1d | ||
|
|
11244a49d9 | ||
|
|
1d2e8eb153 | ||
|
|
ad53fb19b4 | ||
|
|
ba850bac3b | ||
|
|
023a5989f8 | ||
|
|
c970011ccc | ||
|
|
07a43c3d9a | ||
|
|
a05b212b04 | ||
|
|
09faf31b67 | ||
|
|
9e1f9c1133 | ||
|
|
f19d2b9b84 | ||
|
|
a28f93863c | ||
|
|
c9f61669b8 | ||
|
|
dcce5ad3b3 | ||
|
|
6836e5923d | ||
|
|
335188c652 | ||
|
|
60a29dcb99 | ||
|
|
b55d5b3034 | ||
|
|
10ed4b3969 | ||
|
|
4a401b89d7 | ||
|
|
c195b4fc46 | ||
|
|
8f2e34c6a3 | ||
|
|
150d692df7 | ||
|
|
3e5bfff1b5 | ||
|
|
9c7e66a27d | ||
|
|
0ca274866b | ||
|
|
dc037f8d41 | ||
|
|
16cdc741cf | ||
|
|
9d5055176d | ||
|
|
d1e66e1296 | ||
|
|
1fc098e696 | ||
|
|
878cc8defb |
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
- name: Set up Python 3.8 (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
|
||||
5
.github/workflows/documentation.yml
vendored
5
.github/workflows/documentation.yml
vendored
@@ -20,8 +20,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
@@ -42,7 +43,7 @@ jobs:
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:generateJavaDocs docs:doxygen -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- name: Install SSH Client 🔑
|
||||
uses: webfactory/ssh-agent@v0.4.1
|
||||
uses: webfactory/ssh-agent@v0.7.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.GH_DEPLOY_KEY }}
|
||||
- name: Deploy Java 🚀
|
||||
|
||||
19
.github/workflows/gradle.yml
vendored
19
.github/workflows/gradle.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
@@ -80,8 +80,9 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
architecture: ${{ matrix.architecture }}
|
||||
- name: Import Developer ID Certificate
|
||||
@@ -115,7 +116,7 @@ jobs:
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: ${{ matrix.outputs }}
|
||||
@@ -127,8 +128,9 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
@@ -140,7 +142,7 @@ jobs:
|
||||
env:
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Documentation
|
||||
path: docs/build/outputs
|
||||
@@ -153,7 +155,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: wpilibsuite/build-tools
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: combiner/products/build/allOutputs
|
||||
- name: Flatten Artifacts
|
||||
@@ -162,8 +164,9 @@ jobs:
|
||||
run: |
|
||||
cat combiner/products/build/allOutputs/version.txt
|
||||
test -s combiner/products/build/allOutputs/version.txt
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
- name: Combine
|
||||
if: |
|
||||
@@ -188,7 +191,7 @@ jobs:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Maven
|
||||
path: ~/releases
|
||||
|
||||
17
.github/workflows/lint-format.yml
vendored
17
.github/workflows/lint-format.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-format
|
||||
@@ -41,11 +41,19 @@ jobs:
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wpiformat fixes
|
||||
path: wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- name: Write to job summary
|
||||
run: |
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
cat wpiformat-fixes.patch >> $GITHUB_STEP_SUMMARY
|
||||
echo '' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
if: ${{ failure() }}
|
||||
|
||||
tidy:
|
||||
name: "clang-tidy"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -59,7 +67,7 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-tidy
|
||||
@@ -102,8 +110,9 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
|
||||
4
.github/workflows/sanitizers.yml
vendored
4
.github/workflows/sanitizers.yml
vendored
@@ -31,13 +31,13 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 clang-14
|
||||
|
||||
- name: Install jinja
|
||||
run: python -m pip install jinja2
|
||||
|
||||
- name: configure
|
||||
run: mkdir build && cd build && cmake ${{ matrix.cmake-flags }} ..
|
||||
run: mkdir build && cd build && cmake -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-14 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-14 ${{ matrix.cmake-flags }} ..
|
||||
|
||||
- name: build
|
||||
working-directory: build
|
||||
|
||||
2
.github/workflows/upstream-utils.yml
vendored
2
.github/workflows/upstream-utils.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Configure committer identity
|
||||
|
||||
@@ -36,22 +36,23 @@ SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
# (but later on when installing)
|
||||
SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
# add the automatically determined parts of the RPATH
|
||||
# which point to directories outside the build tree to the install RPATH
|
||||
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
|
||||
# the RPATH to be used when installing, but only if it's not a system directory
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/wpilib/lib" isSystemDir)
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
|
||||
IF("${isSystemDir}" STREQUAL "-1")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
ENDIF("${isSystemDir}" STREQUAL "-1")
|
||||
|
||||
# Options for building certain parts of the repo. Everything is built by default.
|
||||
option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" ON)
|
||||
option(WITH_JAVA "Include java and JNI in the build" ON)
|
||||
option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
|
||||
option(WITH_NTCORE "Build ntcore" ON)
|
||||
option(WITH_WPIMATH "Build wpimath" ON)
|
||||
option(WITH_WPILIB "Build hal, wpilibc/j, and myRobot (needs OpenCV)" ON)
|
||||
option(WITH_EXAMPLES "Build examples" OFF)
|
||||
@@ -63,10 +64,10 @@ option(WITH_SIMULATION_MODULES "Build simulation modules" ON)
|
||||
option(WITH_EXTERNAL_HAL "Use a separately built HAL" OFF)
|
||||
set(EXTERNAL_HAL_FILE "" CACHE FILEPATH "Location to look for an external HAL CMake File")
|
||||
|
||||
# Options for using a package manager (vcpkg) for certain dependencies.
|
||||
option(USE_VCPKG_FMTLIB "Use vcpkg fmtlib" OFF)
|
||||
option(USE_VCPKG_LIBUV "Use vcpkg libuv" OFF)
|
||||
option(USE_VCPKG_EIGEN "Use vcpkg eigen" OFF)
|
||||
# Options for using a package manager (e.g., vcpkg) for certain dependencies.
|
||||
option(USE_SYSTEM_FMTLIB "Use system fmtlib" OFF)
|
||||
option(USE_SYSTEM_LIBUV "Use system libuv" OFF)
|
||||
option(USE_SYSTEM_EIGEN "Use system eigen" OFF)
|
||||
|
||||
# Options for installation.
|
||||
option(WITH_FLAT_INSTALL "Use a flat install directory" OFF)
|
||||
@@ -117,6 +118,34 @@ FATAL: Cannot build simulation modules with wpilib disabled.
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_CSCORE)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build cameraserver without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_GUI)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build GUI modules without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_SIMULATION_MODULES)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build simulation modules without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_WPIMATH AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without wpimath.
|
||||
@@ -124,11 +153,11 @@ FATAL: Cannot build wpilib without wpimath.
|
||||
")
|
||||
endif()
|
||||
|
||||
set( wpilib_dest wpilib)
|
||||
set( include_dest wpilib/include )
|
||||
set( main_lib_dest wpilib/lib )
|
||||
set( java_lib_dest wpilib/java )
|
||||
set( jni_lib_dest wpilib/jni )
|
||||
set( wpilib_dest "")
|
||||
set( include_dest include )
|
||||
set( main_lib_dest lib )
|
||||
set( java_lib_dest java )
|
||||
set( jni_lib_dest jni )
|
||||
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (wpilib_config_dir ${wpilib_dest})
|
||||
@@ -136,12 +165,15 @@ else()
|
||||
set (wpilib_config_dir share/wpilib)
|
||||
endif()
|
||||
|
||||
if (USE_VCPKG_LIBUV)
|
||||
set (LIBUV_VCPKG_REPLACE "find_package(unofficial-libuv CONFIG)")
|
||||
if (USE_SYSTEM_LIBUV)
|
||||
set (LIBUV_SYSTEM_REPLACE "
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(libuv REQUIRED IMPORTED_TARGET libuv)
|
||||
")
|
||||
endif()
|
||||
|
||||
if (USE_VCPKG_EIGEN)
|
||||
set (EIGEN_VCPKG_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
if (USE_SYSTEM_EIGEN)
|
||||
set (EIGEN_SYSTEM_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
endif()
|
||||
|
||||
find_package(LIBSSH 0.7.1)
|
||||
@@ -247,8 +279,11 @@ if (WITH_TESTS)
|
||||
endif()
|
||||
|
||||
add_subdirectory(wpiutil)
|
||||
add_subdirectory(wpinet)
|
||||
add_subdirectory(ntcore)
|
||||
|
||||
if (WITH_NTCORE)
|
||||
add_subdirectory(wpinet)
|
||||
add_subdirectory(ntcore)
|
||||
endif()
|
||||
|
||||
if (WITH_WPIMATH)
|
||||
add_subdirectory(wpimath)
|
||||
|
||||
@@ -37,8 +37,7 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
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. We currently use clang-format 14.0 with wpiformat.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -90,4 +90,4 @@ The following 3 tasks can be used for deployment:
|
||||
|
||||
Deploying any of these to the roboRIO will disable the current startup project until it is redeployed.
|
||||
|
||||
From here, ssh into the roboRIO using the `admin` account (`lvuser` will fail to run in many cases). In the admin home directory, a file for each deploy type will exist (`myRobotCpp`, `myRobotCppStatic` and `myRobotJavaRun`). These can be run to start up the corresponding project.
|
||||
From here, ssh into the roboRIO using the `lvuser` account and run `frcRunRobot.sh` (It's in path).
|
||||
|
||||
@@ -31,14 +31,20 @@ The following build options are available:
|
||||
* This option will enable Java and JNI builds. If this is on, `WITH_SHARED_LIBS` must be on. Otherwise CMake will error.
|
||||
* `WITH_SHARED_LIBS` (ON Default)
|
||||
* This option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITH_JAVA` must be off. Otherwise CMake will error.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_CSCORE` (ON Default)
|
||||
* This option will cause cscore to be built. Turning this off will implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is off, the OpenCV build requirement is removed.
|
||||
* `WITH_NTCORE` (ON Default)
|
||||
* This option will cause ntcore to be built. Turning this off will implicitly disable wpinet and wpilib as well, irrespective of their specific options.
|
||||
* `WITH_WPIMATH` (ON Default)
|
||||
* This option will build the wpimath library. This option must be on to build wpilib.
|
||||
* `WITH_WPILIB` (ON Default)
|
||||
* This option will build the hal and wpilibc/j during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
|
||||
* `WITH_EXAMPLES` (ON Default)
|
||||
* This option will build C++ examples.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_GUI` (ON Default)
|
||||
* This option will build GUI items.
|
||||
* `WITH_SIMULATION_MODULES` (ON Default)
|
||||
* This option will build simulation modules, including wpigui and the HALSim plugins.
|
||||
* `WITH_EXTERNAL_HAL` (OFF Default)
|
||||
|
||||
@@ -62,7 +62,7 @@ On macOS ARM, run `softwareupdate --install-rosetta`. This is necessary to be ab
|
||||
|
||||
Clone the WPILib repository and follow the instructions above for installing any required tooling.
|
||||
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions.
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions. We use clang-format 14.
|
||||
|
||||
## Building
|
||||
|
||||
@@ -137,7 +137,9 @@ make
|
||||
|
||||
#### wpiformat
|
||||
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat -clang 14` on Windows or `python3 -m wpiformat -clang 14` on other platforms.
|
||||
|
||||
Once a PR has been submitted, formatting can be run in CI by commenting `/wpiformat` on the PR. A new commit will be pushed with the formatting changes.
|
||||
|
||||
#### Java Code Quality Tools
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ Drake wpimath/src/main/native/thirdparty/drake/
|
||||
wpimath/src/test/native/cpp/drake/
|
||||
wpimath/src/test/native/include/drake/
|
||||
V8 export-template wpiutil/src/main/native/include/wpi/SymbolExports.h
|
||||
GCEM wpimath/src/main/native/thirdparty/gcem/include/
|
||||
|
||||
==============================================================================
|
||||
Google Test License
|
||||
@@ -1224,3 +1225,20 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
============
|
||||
GCEM License
|
||||
============
|
||||
Copyright 2022 - ktholer (https://github.com/kthohr/gcem)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -124,6 +124,10 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add '-XDstringConcat=inline'
|
||||
}
|
||||
|
||||
// Enables UTF-8 support in Javadoc
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption("charset", "utf-8")
|
||||
|
||||
@@ -5,5 +5,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2023.2.7"
|
||||
implementation "edu.wpi.first:native-utils:2023.4.0"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import groovy.transform.CompileStatic
|
||||
import javax.inject.Inject
|
||||
import edu.wpi.first.deployutils.deploy.artifact.MavenArtifact
|
||||
import edu.wpi.first.deployutils.deploy.context.DeployContext
|
||||
import org.gradle.api.Project
|
||||
import edu.wpi.first.deployutils.ActionWrapper
|
||||
import edu.wpi.first.deployutils.deploy.target.RemoteTarget
|
||||
import edu.wpi.first.deployutils.PredicateWrapper
|
||||
import groovy.transform.CompileStatic;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.function.Function
|
||||
import org.gradle.api.Project;
|
||||
|
||||
import edu.wpi.first.deployutils.deploy.CommandDeployResult;
|
||||
import edu.wpi.first.deployutils.deploy.artifact.MavenArtifact;
|
||||
import edu.wpi.first.deployutils.deploy.context.DeployContext;
|
||||
import edu.wpi.first.deployutils.deploy.target.RemoteTarget;
|
||||
import edu.wpi.first.deployutils.PredicateWrapper;
|
||||
import edu.wpi.first.deployutils.ActionWrapper;
|
||||
|
||||
@CompileStatic
|
||||
public class WPIJREArtifact extends MavenArtifact {
|
||||
@@ -17,6 +18,18 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
return configName;
|
||||
}
|
||||
|
||||
public boolean isCheckJreVersion() {
|
||||
return checkJreVersion;
|
||||
}
|
||||
|
||||
public void setCheckJreVersion(boolean checkJreVersion) {
|
||||
this.checkJreVersion = checkJreVersion;
|
||||
}
|
||||
|
||||
private boolean checkJreVersion = true;
|
||||
|
||||
private final String artifactLocation = "edu.wpi.first.jdk:roborio-2023:17.0.5u7-1"
|
||||
|
||||
@Inject
|
||||
public WPIJREArtifact(String name, RemoteTarget target) {
|
||||
super(name, target);
|
||||
@@ -24,10 +37,10 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
this.configName = configName;
|
||||
Project project = target.getProject();
|
||||
getConfiguration().set(project.getConfigurations().create(configName));
|
||||
getDependency().set(project.getDependencies().add(configName, "edu.wpi.first.jdk:roborio-2022:11.0.12u5-1"));
|
||||
getDependency().set(project.getDependencies().add(configName, artifactLocation));
|
||||
|
||||
setOnlyIf(new PredicateWrapper({ DeployContext ctx ->
|
||||
return jreMissing(ctx) || project.hasProperty("force-redeploy-jre");
|
||||
return jreMissing(ctx) || jreOutOfDate(ctx) || project.hasProperty("force-redeploy-jre");
|
||||
}));
|
||||
|
||||
getDirectory().set("/tmp");
|
||||
@@ -35,7 +48,7 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
|
||||
getPostdeploy().add(new ActionWrapper({ DeployContext ctx ->
|
||||
ctx.getLogger().log("Installing JRE...");
|
||||
ctx.execute("opkg remove frc2022-openjdk*; opkg install /tmp/frcjre.ipk; rm /tmp/frcjre.ipk");
|
||||
ctx.execute("opkg remove frc*-openjdk*; opkg install /tmp/frcjre.ipk; rm /tmp/frcjre.ipk");
|
||||
ctx.getLogger().log("JRE Deployed!");
|
||||
}));
|
||||
}
|
||||
@@ -44,5 +57,21 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
return ctx.execute("if [[ -f \"/usr/local/frc/JRE/bin/java\" ]]; then echo OK; else echo MISSING; fi").getResult().contains("MISSING");
|
||||
}
|
||||
|
||||
|
||||
private boolean jreOutOfDate(DeployContext ctx) {
|
||||
if (!checkJreVersion) {
|
||||
return false;
|
||||
}
|
||||
String version = getDependency().get().getVersion();
|
||||
CommandDeployResult cmdResult = ctx.execute("opkg list-installed | grep openjdk");
|
||||
if (cmdResult.getExitCode() != 0) {
|
||||
ctx.getLogger().log("JRE not found");
|
||||
return false;
|
||||
}
|
||||
String result = cmdResult.getResult().trim();
|
||||
ctx.getLogger().log("Searching for JRE " + version);
|
||||
ctx.getLogger().log("Found JRE " + result);
|
||||
boolean matches = result.contains(version);
|
||||
ctx.getLogger().log(matches ? "JRE Is Correct Version" : "JRE is mismatched. Reinstalling");
|
||||
return !matches;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public final class Main {
|
||||
} else {
|
||||
System.out.println("Setting up NetworkTables client for team " + team);
|
||||
ntinst.setServerTeam(team);
|
||||
ntinst.startClient4();
|
||||
ntinst.startClient4("multicameraserver");
|
||||
}
|
||||
|
||||
// start cameras
|
||||
|
||||
@@ -183,7 +183,7 @@ int main(int argc, char* argv[]) {
|
||||
ntinst.StartServer();
|
||||
} else {
|
||||
fmt::print("Setting up NetworkTables client for team {}\n", team);
|
||||
ntinst.StartClient4();
|
||||
ntinst.StartClient4("multicameraserver");
|
||||
ntinst.SetServerTeam(team);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ class TestEnvironment : public testing::Environment {
|
||||
HAL_GetControlWord(&controlWord);
|
||||
return controlWord.enabled && controlWord.dsAttached;
|
||||
};
|
||||
HAL_RefreshDSData();
|
||||
while (!checkEnabled()) {
|
||||
if (enableCounter > 50) {
|
||||
// Robot did not enable properly after 5 seconds.
|
||||
@@ -60,6 +61,7 @@ class TestEnvironment : public testing::Environment {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
fmt::print("Waiting for enable: {}\n", enableCounter++);
|
||||
HAL_RefreshDSData();
|
||||
}
|
||||
std::this_thread::sleep_for(500ms);
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ model {
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
if (key == "usbviewer") {
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
@@ -189,7 +189,7 @@ model {
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -199,6 +199,9 @@ model {
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
if (it.targetPlatform.name.startsWith('linuxarm')) {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
}
|
||||
sources {
|
||||
|
||||
@@ -110,7 +110,7 @@ void CvSinkImpl::ThreadMain() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
|
||||
@@ -85,7 +85,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
// (this will result in an error at the read point, and ultimately
|
||||
// a reconnect attempt)
|
||||
if (m_streamConn && m_frameCount == 0) {
|
||||
SWARNING("{}", "Monitor detected stream hung, disconnecting");
|
||||
SWARNING("Monitor detected stream hung, disconnecting");
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
m_frameCount = 0;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Monitor Thread exiting");
|
||||
SDEBUG("Monitor Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::StreamThreadMain() {
|
||||
@@ -141,7 +141,7 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
}
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Camera Thread exiting");
|
||||
SDEBUG("Camera Thread exiting");
|
||||
SetConnected(false);
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
if (m_locations.empty()) {
|
||||
SERROR("{}", "locations array is empty!?");
|
||||
SERROR("locations array is empty!?");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return nullptr;
|
||||
}
|
||||
@@ -273,7 +273,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
wpi::SmallString<64> contentTypeBuf;
|
||||
wpi::SmallString<64> contentLengthBuf;
|
||||
if (!ParseHttpHeaders(is, &contentTypeBuf, &contentLengthBuf)) {
|
||||
SWARNING("{}", "disconnected during headers");
|
||||
SWARNING("disconnected during headers");
|
||||
PutError("disconnected during headers", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -295,7 +295,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
// Ugh, no Content-Length? Read the blocks of the JPEG file.
|
||||
int width, height;
|
||||
if (!ReadJpeg(is, imageBuf, &width, &height)) {
|
||||
SWARNING("{}", "did not receive a JPEG image");
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -314,7 +314,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
}
|
||||
int width, height;
|
||||
if (!GetJpegSize(image->str(), &width, &height)) {
|
||||
SWARNING("{}", "did not receive a JPEG image");
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -344,7 +344,7 @@ void HttpCameraImpl::SettingsThreadMain() {
|
||||
DeviceSendSettings(req);
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Settings Thread exiting");
|
||||
SDEBUG("Settings Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
|
||||
|
||||
@@ -27,26 +27,37 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#define LOG(level, format, ...) WPI_LOG(m_logger, level, format, __VA_ARGS__)
|
||||
#define LOG(level, format, ...) \
|
||||
WPI_LOG(m_logger, level, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(format, ...) WPI_ERROR(m_logger, format, __VA_ARGS__)
|
||||
#define WARNING(format, ...) WPI_WARNING(m_logger, format, __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format, __VA_ARGS__)
|
||||
#define ERROR(format, ...) \
|
||||
WPI_ERROR(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define WARNING(format, ...) \
|
||||
WPI_WARNING(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define DEBUG0(format, ...) WPI_DEBUG(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) WPI_DEBUG1(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) WPI_DEBUG2(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) WPI_DEBUG3(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) WPI_DEBUG4(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG0(format, ...) \
|
||||
WPI_DEBUG(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) \
|
||||
WPI_DEBUG1(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) \
|
||||
WPI_DEBUG2(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) \
|
||||
WPI_DEBUG3(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) \
|
||||
WPI_DEBUG4(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define SLOG(level, format, ...) \
|
||||
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), FMT_STRING(format), \
|
||||
__VA_ARGS__)
|
||||
#define SLOG(level, format, ...) \
|
||||
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), \
|
||||
FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define SERROR(format, ...) SLOG(::wpi::WPI_LOG_ERROR, format, __VA_ARGS__)
|
||||
#define SWARNING(format, ...) SLOG(::wpi::WPI_LOG_WARNING, format, __VA_ARGS__)
|
||||
#define SINFO(format, ...) SLOG(::wpi::WPI_LOG_INFO, format, __VA_ARGS__)
|
||||
#define SERROR(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_ERROR, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SWARNING(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_WARNING, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SINFO(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_INFO, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define SDEBUG(format, ...) \
|
||||
@@ -65,11 +76,16 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
|
||||
do { \
|
||||
} while (0)
|
||||
#else
|
||||
#define SDEBUG(format, ...) SLOG(::wpi::WPI_LOG_DEBUG, format, __VA_ARGS__)
|
||||
#define SDEBUG1(format, ...) SLOG(::wpi::WPI_LOG_DEBUG1, format, __VA_ARGS__)
|
||||
#define SDEBUG2(format, ...) SLOG(::wpi::WPI_LOG_DEBUG2, format, __VA_ARGS__)
|
||||
#define SDEBUG3(format, ...) SLOG(::wpi::WPI_LOG_DEBUG3, format, __VA_ARGS__)
|
||||
#define SDEBUG4(format, ...) SLOG(::wpi::WPI_LOG_DEBUG4, format, __VA_ARGS__)
|
||||
#define SDEBUG(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG1(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG1, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG2(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG2, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG3(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG3, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG4(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG4, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif // CSCORE_LOG_H_
|
||||
|
||||
@@ -650,7 +650,7 @@ void MjpegServerImpl::Stop() {
|
||||
// Send HTTP response and a stream of JPG-frames
|
||||
void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
if (m_noStreaming) {
|
||||
SERROR("{}", "Too many simultaneous client streams");
|
||||
SERROR("Too many simultaneous client streams");
|
||||
SendError(os, 503, "Too many simultaneous streams");
|
||||
return;
|
||||
}
|
||||
@@ -663,7 +663,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
SendHeader(oss, 200, "OK", "multipart/x-mixed-replace;boundary=" BOUNDARY);
|
||||
os << oss.str();
|
||||
|
||||
SDEBUG("{}", "Headers send, sending stream now");
|
||||
SDEBUG("Headers send, sending stream now");
|
||||
|
||||
Frame::Time lastFrameTime = 0;
|
||||
Frame::Time timePerFrame = 0;
|
||||
@@ -685,7 +685,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(0.225); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
@@ -783,7 +783,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
wpi::SmallString<128> reqBuf;
|
||||
std::string_view req = is.getline(reqBuf, 4096);
|
||||
if (is.has_error()) {
|
||||
SDEBUG("{}", "error getting request string");
|
||||
SDEBUG("error getting request string");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -824,7 +824,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
} else if (req.find("GET / ") != std::string_view::npos || req == "GET /\n") {
|
||||
kind = kRootPage;
|
||||
} else {
|
||||
SDEBUG("{}", "HTTP request resource not found");
|
||||
SDEBUG("HTTP request resource not found");
|
||||
SendError(os, 404, "Resource not found");
|
||||
return;
|
||||
}
|
||||
@@ -866,11 +866,11 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
SendHeader(os, 200, "OK", "text/plain");
|
||||
os << "Ignored due to no connected source."
|
||||
<< "\r\n";
|
||||
SDEBUG("{}", "Ignored due to no connected source.");
|
||||
SDEBUG("Ignored due to no connected source.");
|
||||
}
|
||||
break;
|
||||
case kGetSettings:
|
||||
SDEBUG("{}", "request for JSON file");
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendJSON(os, *source, true);
|
||||
} else {
|
||||
@@ -878,7 +878,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
}
|
||||
break;
|
||||
case kGetSourceConfig:
|
||||
SDEBUG("{}", "request for JSON file");
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendHeader(os, 200, "OK", "application/json");
|
||||
CS_Status status = CS_OK;
|
||||
@@ -889,7 +889,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
}
|
||||
break;
|
||||
case kRootPage:
|
||||
SDEBUG("{}", "request for root page");
|
||||
SDEBUG("request for root page");
|
||||
SendHeader(os, 200, "OK", "text/html");
|
||||
if (auto source = GetSource()) {
|
||||
SendHTML(os, *source, false);
|
||||
@@ -900,7 +900,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
break;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "leaving HTTP client thread");
|
||||
SDEBUG("leaving HTTP client thread");
|
||||
}
|
||||
|
||||
// worker thread for clients that connected to this server
|
||||
@@ -927,7 +927,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
return;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "waiting for clients to connect");
|
||||
SDEBUG("waiting for clients to connect");
|
||||
while (m_active) {
|
||||
auto stream = m_acceptor->accept();
|
||||
if (!stream) {
|
||||
@@ -977,7 +977,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
SDEBUG("{}", "leaving server thread");
|
||||
SDEBUG("leaving server thread");
|
||||
}
|
||||
|
||||
void MjpegServerImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {
|
||||
|
||||
@@ -127,7 +127,7 @@ void RawSinkImpl::ThreadMain() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
|
||||
@@ -454,7 +454,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle notify events
|
||||
if (notify_fd >= 0 && FD_ISSET(notify_fd, &readfds)) {
|
||||
SDEBUG4("{}", "notify event");
|
||||
SDEBUG4("notify event");
|
||||
struct inotify_event event;
|
||||
do {
|
||||
// Read the event structure
|
||||
@@ -483,7 +483,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle commands
|
||||
if (command_fd >= 0 && FD_ISSET(command_fd, &readfds)) {
|
||||
SDEBUG4("{}", "got command");
|
||||
SDEBUG4("got command");
|
||||
// Read it to clear
|
||||
eventfd_t val;
|
||||
eventfd_read(command_fd, &val);
|
||||
@@ -493,7 +493,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle frames
|
||||
if (m_streaming && fd >= 0 && FD_ISSET(fd, &readfds)) {
|
||||
SDEBUG4("{}", "grabbing image");
|
||||
SDEBUG4("grabbing image");
|
||||
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf;
|
||||
@@ -501,7 +501,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
if (DoIoctl(fd, VIDIOC_DQBUF, &buf) != 0) {
|
||||
SWARNING("{}", "could not dequeue buffer");
|
||||
SWARNING("could not dequeue buffer");
|
||||
wasStreaming = m_streaming;
|
||||
DeviceStreamOff();
|
||||
DeviceDisconnect();
|
||||
@@ -525,7 +525,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
bool good = true;
|
||||
if (m_mode.pixelFormat == VideoMode::kMJPEG &&
|
||||
!GetJpegSize(image, &width, &height)) {
|
||||
SWARNING("{}", "invalid JPEG image received from camera");
|
||||
SWARNING("invalid JPEG image received from camera");
|
||||
good = false;
|
||||
}
|
||||
if (good) {
|
||||
@@ -536,7 +536,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Requeue buffer
|
||||
if (DoIoctl(fd, VIDIOC_QBUF, &buf) != 0) {
|
||||
SWARNING("{}", "could not requeue buffer");
|
||||
SWARNING("could not requeue buffer");
|
||||
wasStreaming = m_streaming;
|
||||
DeviceStreamOff();
|
||||
DeviceDisconnect();
|
||||
@@ -579,7 +579,7 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
// Try to open the device
|
||||
SDEBUG3("{}", "opening device");
|
||||
SDEBUG3("opening device");
|
||||
int fd = open(m_path.c_str(), O_RDWR);
|
||||
if (fd < 0) {
|
||||
return;
|
||||
@@ -587,7 +587,7 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
m_fd = fd;
|
||||
|
||||
// Get capabilities
|
||||
SDEBUG3("{}", "getting capabilities");
|
||||
SDEBUG3("getting capabilities");
|
||||
struct v4l2_capability vcap;
|
||||
std::memset(&vcap, 0, sizeof(vcap));
|
||||
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) >= 0) {
|
||||
@@ -599,18 +599,18 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
|
||||
// Get or restore video mode
|
||||
if (!m_properties_cached) {
|
||||
SDEBUG3("{}", "caching properties");
|
||||
SDEBUG3("caching properties");
|
||||
DeviceCacheProperties();
|
||||
DeviceCacheVideoModes();
|
||||
DeviceCacheMode();
|
||||
m_properties_cached = true;
|
||||
} else {
|
||||
SDEBUG3("{}", "restoring video mode");
|
||||
SDEBUG3("restoring video mode");
|
||||
DeviceSetMode();
|
||||
DeviceSetFPS();
|
||||
|
||||
// Restore settings
|
||||
SDEBUG3("{}", "restoring settings");
|
||||
SDEBUG3("restoring settings");
|
||||
std::unique_lock lock2(m_mutex);
|
||||
for (size_t i = 0; i < m_propertyData.size(); ++i) {
|
||||
const auto prop =
|
||||
@@ -625,21 +625,21 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
// Request buffers
|
||||
SDEBUG3("{}", "allocating buffers");
|
||||
SDEBUG3("allocating buffers");
|
||||
struct v4l2_requestbuffers rb;
|
||||
std::memset(&rb, 0, sizeof(rb));
|
||||
rb.count = kNumBuffers;
|
||||
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
rb.memory = V4L2_MEMORY_MMAP;
|
||||
if (DoIoctl(fd, VIDIOC_REQBUFS, &rb) != 0) {
|
||||
SWARNING("{}", "could not allocate buffers");
|
||||
SWARNING("could not allocate buffers");
|
||||
close(fd);
|
||||
m_fd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Map buffers
|
||||
SDEBUG3("{}", "mapping buffers");
|
||||
SDEBUG3("mapping buffers");
|
||||
for (int i = 0; i < kNumBuffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
@@ -689,7 +689,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
}
|
||||
|
||||
// Queue buffers
|
||||
SDEBUG3("{}", "queuing buffers");
|
||||
SDEBUG3("queuing buffers");
|
||||
for (int i = 0; i < kNumBuffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
@@ -708,7 +708,6 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
if (errno == ENOSPC) {
|
||||
// indicates too much USB bandwidth requested
|
||||
SERROR(
|
||||
"{}",
|
||||
"could not start streaming due to USB bandwidth limitations; try a "
|
||||
"lower resolution or a different pixel format (VIDIOC_STREAMON: "
|
||||
"No space left on device)");
|
||||
@@ -718,7 +717,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SDEBUG4("{}", "enabled streaming");
|
||||
SDEBUG4("enabled streaming");
|
||||
m_streaming = true;
|
||||
return true;
|
||||
}
|
||||
@@ -735,7 +734,7 @@ bool UsbCameraImpl::DeviceStreamOff() {
|
||||
if (DoIoctl(fd, VIDIOC_STREAMOFF, &type) != 0) {
|
||||
return false;
|
||||
}
|
||||
SDEBUG4("{}", "disabled streaming");
|
||||
SDEBUG4("disabled streaming");
|
||||
m_streaming = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1000,7 +999,7 @@ void UsbCameraImpl::DeviceCacheMode() {
|
||||
#endif
|
||||
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (DoIoctl(fd, VIDIOC_G_FMT, &vfmt) != 0) {
|
||||
SERROR("{}", "could not read current video mode");
|
||||
SERROR("could not read current video mode");
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_mode = VideoMode{VideoMode::kMJPEG, 320, 240, 30};
|
||||
return;
|
||||
@@ -1668,7 +1667,7 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
::closedir(dp);
|
||||
} else {
|
||||
// *status = ;
|
||||
WPI_ERROR(Instance::GetInstance().logger, "{}", "Could not open /dev");
|
||||
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
@@ -486,7 +486,7 @@ bool UsbCameraImpl::DeviceConnect() {
|
||||
SINFO("Connecting to USB camera on {}", m_path);
|
||||
}
|
||||
|
||||
SDEBUG3("{}", "opening device");
|
||||
SDEBUG3("opening device");
|
||||
|
||||
const wchar_t* path = m_widePath.c_str();
|
||||
m_mediaSource = CreateVideoCaptureDevice(path);
|
||||
@@ -520,13 +520,13 @@ bool UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
if (!m_properties_cached) {
|
||||
SDEBUG3("{}", "caching properties");
|
||||
SDEBUG3("caching properties");
|
||||
DeviceCacheProperties();
|
||||
DeviceCacheVideoModes();
|
||||
DeviceCacheMode();
|
||||
m_properties_cached = true;
|
||||
} else {
|
||||
SDEBUG3("{}", "restoring video mode");
|
||||
SDEBUG3("restoring video mode");
|
||||
DeviceSetMode();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
|
||||
description = "roboRIO Team Number Setter"
|
||||
|
||||
@@ -24,19 +24,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
def wpilibVersionFileInput = file("src/main/generate/WPILibVersion.cpp.in")
|
||||
def wpilibVersionFileOutput = file("$buildDir/generated/main/cpp/WPILibVersion.cpp")
|
||||
|
||||
nativeUtils {
|
||||
nativeDependencyContainer {
|
||||
libssh(getNativeDependencyTypeClass('WPIStaticMavenDependency')) {
|
||||
groupId = "edu.wpi.first.thirdparty.frc2023"
|
||||
artifactId = "libssh"
|
||||
headerClassifier = "headers"
|
||||
sourceClassifier = "sources"
|
||||
ext = "zip"
|
||||
version = '0.95-3'
|
||||
targetPlatforms.addAll(nativeUtils.wpi.platforms.desktopPlatforms)
|
||||
}
|
||||
}
|
||||
}
|
||||
apply from: "${rootDir}/shared/libssh.gradle"
|
||||
|
||||
task generateCppVersion() {
|
||||
description = 'Generates the wpilib version class'
|
||||
@@ -107,7 +95,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -124,6 +112,9 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
it.linker.args << '-framework' << 'Kerberos'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
if (it.targetPlatform.name.startsWith('linuxarm')) {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,20 @@ void Downloader::DisplayRemoteDirSelector() {
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Deselect All")) {
|
||||
for (auto&& download : m_downloadList) {
|
||||
download.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Select All")) {
|
||||
for (auto&& download : m_downloadList) {
|
||||
download.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Remote directory text box
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20);
|
||||
if (ImGui::InputText("Remote Dir", &m_remoteDir,
|
||||
|
||||
@@ -4,6 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
evaluationDependsOn(':wpiutil')
|
||||
evaluationDependsOn(':wpinet')
|
||||
evaluationDependsOn(':ntcore')
|
||||
evaluationDependsOn(':cscore')
|
||||
evaluationDependsOn(':hal')
|
||||
@@ -28,6 +29,7 @@ def cppIncludeRoots = []
|
||||
|
||||
cppProjectZips.add(project(':hal').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpiutil').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpinet').cppHeadersZip)
|
||||
cppProjectZips.add(project(':ntcore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cscore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cameraserver').cppHeadersZip)
|
||||
@@ -109,7 +111,7 @@ doxygen {
|
||||
// libuv
|
||||
exclude 'uv.h'
|
||||
exclude 'uv/**'
|
||||
exclude 'wpi/uv/**'
|
||||
exclude 'wpinet/uv/**'
|
||||
|
||||
// json
|
||||
exclude 'wpi/json.h'
|
||||
@@ -124,6 +126,12 @@ doxygen {
|
||||
exclude 'units/**'
|
||||
}
|
||||
|
||||
aliases 'effects=\\par <i>Effects:</i>^^',
|
||||
'notes=\\par <i>Notes:</i>^^',
|
||||
'requires=\\par <i>Requires:</i>^^',
|
||||
'requiredbe=\\par <i>Required Behavior:</i>^^',
|
||||
'concept{2}=<a href=\"md_doc_concepts.html#\1\">\2</a>',
|
||||
'defaultbe=\\par <i>Default Behavior:</i>^^'
|
||||
case_sense_names false
|
||||
extension_mapping 'inc=C++', 'no_extension=C++'
|
||||
extract_all true
|
||||
@@ -200,6 +208,7 @@ task generateJavaDocs(type: Javadoc) {
|
||||
dependsOn project(':ntcore').ntcoreGenerateJavaTypes
|
||||
source project(':hal').sourceSets.main.java
|
||||
source project(':wpiutil').sourceSets.main.java
|
||||
source project(':wpinet').sourceSets.main.java
|
||||
source project(':cscore').sourceSets.main.java
|
||||
source project(':ntcore').sourceSets.main.java
|
||||
source project(':wpimath').sourceSets.main.java
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
|
||||
apply plugin: 'cpp'
|
||||
apply plugin: 'c'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
|
||||
if (!project.hasProperty('onlylinuxathena')) {
|
||||
|
||||
description = "A different kind of dashboard"
|
||||
|
||||
@@ -100,7 +100,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -128,7 +128,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -169,7 +169,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
@@ -190,6 +190,9 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
if (it.targetPlatform.name.startsWith('linuxarm')) {
|
||||
it.linker.args << '-lGL'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,44 +70,41 @@ static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
|
||||
}
|
||||
|
||||
static void NtInitialize() {
|
||||
// update window title when connection status changes
|
||||
auto inst = nt::GetDefaultInstance();
|
||||
auto poller = nt::CreateConnectionListenerPoller(inst);
|
||||
nt::AddPolledConnectionListener(poller, true);
|
||||
auto poller = nt::CreateListenerPoller(inst);
|
||||
nt::AddPolledListener(
|
||||
poller, inst,
|
||||
NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE);
|
||||
gui::AddEarlyExecute([poller] {
|
||||
auto win = gui::GetSystemWindow();
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
for (auto&& event : nt::ReadConnectionListenerQueue(poller)) {
|
||||
if (event.connected) {
|
||||
glfwSetWindowTitle(
|
||||
win, fmt::format("Glass - Connected ({})", event.conn.remote_ip)
|
||||
.c_str());
|
||||
} else {
|
||||
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
|
||||
for (auto&& event : nt::ReadListenerQueue(poller)) {
|
||||
if (auto connInfo = event.GetConnectionInfo()) {
|
||||
// update window title when connection status changes
|
||||
if ((event.flags & NT_EVENT_CONNECTED) != 0) {
|
||||
glfwSetWindowTitle(
|
||||
win, fmt::format("Glass - Connected ({})", connInfo->remote_ip)
|
||||
.c_str());
|
||||
} else {
|
||||
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
|
||||
}
|
||||
} else if (auto msg = event.GetLogMessage()) {
|
||||
const char* level = "";
|
||||
if (msg->level >= NT_LOG_CRITICAL) {
|
||||
level = "CRITICAL: ";
|
||||
} else if (msg->level >= NT_LOG_ERROR) {
|
||||
level = "ERROR: ";
|
||||
} else if (msg->level >= NT_LOG_WARNING) {
|
||||
level = "WARNING: ";
|
||||
}
|
||||
gNetworkTablesLog.Append(fmt::format(
|
||||
"{}{} ({}:{})\n", level, msg->message, msg->filename, msg->line));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// handle NetworkTables log messages
|
||||
auto logPoller = nt::CreateLoggerPoller(inst);
|
||||
nt::AddPolledLogger(logPoller, NT_LOG_INFO, 100);
|
||||
gui::AddEarlyExecute([logPoller] {
|
||||
for (auto&& msg : nt::ReadLoggerQueue(logPoller)) {
|
||||
const char* level = "";
|
||||
if (msg.level >= NT_LOG_CRITICAL) {
|
||||
level = "CRITICAL: ";
|
||||
} else if (msg.level >= NT_LOG_ERROR) {
|
||||
level = "ERROR: ";
|
||||
} else if (msg.level >= NT_LOG_WARNING) {
|
||||
level = "WARNING: ";
|
||||
}
|
||||
gNetworkTablesLog.Append(fmt::format("{}{} ({}:{})\n", level, msg.message,
|
||||
msg.filename, msg.line));
|
||||
}
|
||||
});
|
||||
|
||||
gNetworkTablesLogWindow = std::make_unique<glass::Window>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Log"),
|
||||
"NetworkTables Log", glass::Window::kHide);
|
||||
|
||||
@@ -113,7 +113,7 @@ class PopupState {
|
||||
struct DisplayOptions {
|
||||
explicit DisplayOptions(const gui::Texture& texture) : texture{texture} {}
|
||||
|
||||
enum Style { kBoxImage = 0, kLine, kLineClosed, kTrack };
|
||||
enum Style { kBoxImage = 0, kLine, kLineClosed, kTrack, kHidden };
|
||||
|
||||
static constexpr Style kDefaultStyle = kBoxImage;
|
||||
static constexpr float kDefaultWeight = 4.0f;
|
||||
@@ -547,7 +547,7 @@ ObjectInfo::ObjectInfo(Storage& storage)
|
||||
DisplayOptions::kDefaultLength.to<float>())},
|
||||
m_style{storage.GetString("style"),
|
||||
DisplayOptions::kDefaultStyle,
|
||||
{"Box/Image", "Line", "Line (Closed)", "Track"}},
|
||||
{"Box/Image", "Line", "Line (Closed)", "Track", "Hidden"}},
|
||||
m_weight{storage.GetFloat("weight", DisplayOptions::kDefaultWeight)},
|
||||
m_color{
|
||||
storage.GetFloatArray("color", DisplayOptions::kDefaultColorFloat)},
|
||||
@@ -840,6 +840,8 @@ void PoseFrameData::Draw(ImDrawList* drawList, std::vector<ImVec2>* center,
|
||||
left->emplace_back(m_corners[4]);
|
||||
right->emplace_back(m_corners[5]);
|
||||
break;
|
||||
case DisplayOptions::kHidden:
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_displayOptions.arrows) {
|
||||
|
||||
@@ -117,58 +117,55 @@ NTField2DModel::NTField2DModel(nt::NetworkTableInstance inst,
|
||||
{{nt::PubSubOption::SendAll(true),
|
||||
nt::PubSubOption::Periodic(0.05)}}},
|
||||
m_nameTopic{inst.GetTopic(fmt::format("{}/.name", path))},
|
||||
m_topicListener{inst},
|
||||
m_valueListener{inst} {
|
||||
m_topicListener.Add(m_tableSub, NT_TOPIC_NOTIFY_PUBLISH |
|
||||
NT_TOPIC_NOTIFY_UNPUBLISH |
|
||||
NT_TOPIC_NOTIFY_IMMEDIATE);
|
||||
m_valueListener.Add(m_tableSub,
|
||||
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
|
||||
m_poller{inst} {
|
||||
m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic |
|
||||
nt::EventFlags::kValueAll |
|
||||
nt::EventFlags::kImmediate);
|
||||
}
|
||||
|
||||
NTField2DModel::~NTField2DModel() = default;
|
||||
|
||||
void NTField2DModel::Update() {
|
||||
// handle publish/unpublish
|
||||
for (auto&& event : m_topicListener.ReadQueue()) {
|
||||
auto name = wpi::drop_front(event.info.name, m_path.size());
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
auto [it, match] = Find(event.info.name);
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
if (match) {
|
||||
m_objects.erase(it);
|
||||
for (auto&& event : m_poller.ReadQueue()) {
|
||||
if (auto info = event.GetTopicInfo()) {
|
||||
// handle publish/unpublish
|
||||
auto name = wpi::drop_front(info->name, m_path.size());
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
} else if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<ObjectModel>(
|
||||
event.info.name, nt::DoubleArrayTopic{event.info.topic}));
|
||||
auto [it, match] = Find(info->name);
|
||||
if (event.flags & nt::EventFlags::kUnpublish) {
|
||||
if (match) {
|
||||
m_objects.erase(it);
|
||||
}
|
||||
continue;
|
||||
} else if (event.flags & nt::EventFlags::kPublish) {
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<ObjectModel>(
|
||||
info->name, nt::DoubleArrayTopic{info->topic}));
|
||||
}
|
||||
} else if (!match) {
|
||||
continue;
|
||||
}
|
||||
} else if (auto valueData = event.GetValueEventData()) {
|
||||
// update values
|
||||
// .name
|
||||
if (valueData->topic == m_nameTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsString()) {
|
||||
m_nameValue = valueData->value.GetString();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else if (!match) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// update values
|
||||
for (auto&& event : m_valueListener.ReadQueue()) {
|
||||
// .name
|
||||
if (event.topic == m_nameTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
m_nameValue = event.value.GetString();
|
||||
auto it =
|
||||
std::find_if(m_objects.begin(), m_objects.end(), [&](const auto& e) {
|
||||
return e->GetTopic().GetHandle() == valueData->topic;
|
||||
});
|
||||
if (it != m_objects.end()) {
|
||||
(*it)->NTUpdate(valueData->value);
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it =
|
||||
std::find_if(m_objects.begin(), m_objects.end(), [&](const auto& e) {
|
||||
return e->GetTopic().GetHandle() == event.topic;
|
||||
});
|
||||
if (it != m_objects.end()) {
|
||||
(*it)->NTUpdate(event.value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ class NTMechanismGroupImpl final {
|
||||
const char* GetName() const { return m_name.c_str(); }
|
||||
void ForEachObject(wpi::function_ref<void(MechanismObjectModel& model)> func);
|
||||
|
||||
void NTUpdate(const nt::TopicNotification& event, std::string_view name);
|
||||
void NTUpdate(const nt::ValueNotification& event, std::string_view name);
|
||||
void NTUpdate(const nt::Event& event, std::string_view name);
|
||||
|
||||
protected:
|
||||
nt::NetworkTableInstance m_inst;
|
||||
@@ -74,8 +73,7 @@ class NTMechanismObjectModel final : public MechanismObjectModel {
|
||||
frc::Rotation2d GetAngle() const final { return m_angleValue; }
|
||||
units::meter_t GetLength() const final { return m_lengthValue; }
|
||||
|
||||
bool NTUpdate(const nt::TopicNotification& event, std::string_view name);
|
||||
void NTUpdate(const nt::ValueNotification& event, std::string_view name);
|
||||
bool NTUpdate(const nt::Event& event, std::string_view name);
|
||||
|
||||
private:
|
||||
NTMechanismGroupImpl m_group;
|
||||
@@ -102,7 +100,7 @@ void NTMechanismGroupImpl::ForEachObject(
|
||||
}
|
||||
}
|
||||
|
||||
void NTMechanismGroupImpl::NTUpdate(const nt::TopicNotification& event,
|
||||
void NTMechanismGroupImpl::NTUpdate(const nt::Event& event,
|
||||
std::string_view name) {
|
||||
if (name.empty()) {
|
||||
return;
|
||||
@@ -118,83 +116,69 @@ void NTMechanismGroupImpl::NTUpdate(const nt::TopicNotification& event,
|
||||
[](const auto& e, std::string_view name) { return e->GetName() < name; });
|
||||
bool match = it != m_objects.end() && (*it)->GetName() == name;
|
||||
|
||||
if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<NTMechanismObjectModel>(
|
||||
m_inst, fmt::format("{}/{}", m_path, name), name));
|
||||
match = true;
|
||||
if (event.GetTopicInfo()) {
|
||||
if (event.flags & nt::EventFlags::kPublish) {
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<NTMechanismObjectModel>(
|
||||
m_inst, fmt::format("{}/{}", m_path, name), name));
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if ((*it)->NTUpdate(event, childName)) {
|
||||
m_objects.erase(it);
|
||||
if (match) {
|
||||
if ((*it)->NTUpdate(event, childName)) {
|
||||
m_objects.erase(it);
|
||||
}
|
||||
}
|
||||
} else if (event.GetValueEventData()) {
|
||||
if (match) {
|
||||
(*it)->NTUpdate(event, childName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NTMechanismGroupImpl::NTUpdate(const nt::ValueNotification& event,
|
||||
std::string_view name) {
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string_view childName;
|
||||
std::tie(name, childName) = wpi::split(name, '/');
|
||||
if (childName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(
|
||||
m_objects.begin(), m_objects.end(), name,
|
||||
[](const auto& e, std::string_view name) { return e->GetName() < name; });
|
||||
if (it != m_objects.end() && (*it)->GetName() == name) {
|
||||
(*it)->NTUpdate(event, childName);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTMechanismObjectModel::NTUpdate(const nt::TopicNotification& event,
|
||||
bool NTMechanismObjectModel::NTUpdate(const nt::Event& event,
|
||||
std::string_view childName) {
|
||||
if (event.info.topic == m_typeTopic.GetHandle()) {
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
return true;
|
||||
if (auto info = event.GetTopicInfo()) {
|
||||
if (info->topic == m_typeTopic.GetHandle()) {
|
||||
if (event.flags & nt::EventFlags::kUnpublish) {
|
||||
return true;
|
||||
}
|
||||
} else if (info->topic != m_colorTopic.GetHandle() &&
|
||||
info->topic != m_weightTopic.GetHandle() &&
|
||||
info->topic != m_angleTopic.GetHandle() &&
|
||||
info->topic != m_lengthTopic.GetHandle()) {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
} else if (auto valueData = event.GetValueEventData()) {
|
||||
if (valueData->topic == m_typeTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsString()) {
|
||||
m_typeValue = valueData->value.GetString();
|
||||
}
|
||||
} else if (valueData->topic == m_colorTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsString()) {
|
||||
ConvertColor(valueData->value.GetString(), &m_colorValue);
|
||||
}
|
||||
} else if (valueData->topic == m_weightTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsDouble()) {
|
||||
m_weightValue = valueData->value.GetDouble();
|
||||
}
|
||||
} else if (valueData->topic == m_angleTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsDouble()) {
|
||||
m_angleValue = units::degree_t{valueData->value.GetDouble()};
|
||||
}
|
||||
} else if (valueData->topic == m_lengthTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsDouble()) {
|
||||
m_lengthValue = units::meter_t{valueData->value.GetDouble()};
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
} else if (event.info.topic != m_colorTopic.GetHandle() &&
|
||||
event.info.topic != m_weightTopic.GetHandle() &&
|
||||
event.info.topic != m_angleTopic.GetHandle() &&
|
||||
event.info.topic != m_lengthTopic.GetHandle()) {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NTMechanismObjectModel::NTUpdate(const nt::ValueNotification& event,
|
||||
std::string_view childName) {
|
||||
if (event.topic == m_typeTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
m_typeValue = event.value.GetString();
|
||||
}
|
||||
} else if (event.topic == m_colorTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
ConvertColor(event.value.GetString(), &m_colorValue);
|
||||
}
|
||||
} else if (event.topic == m_weightTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_weightValue = event.value.GetDouble();
|
||||
}
|
||||
} else if (event.topic == m_angleTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_angleValue = units::degree_t{event.value.GetDouble()};
|
||||
}
|
||||
} else if (event.topic == m_lengthTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_lengthValue = units::meter_t{event.value.GetDouble()};
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
}
|
||||
|
||||
class NTMechanism2DModel::RootModel final : public MechanismRootModel {
|
||||
public:
|
||||
RootModel(nt::NetworkTableInstance inst, std::string_view path,
|
||||
@@ -209,8 +193,7 @@ class NTMechanism2DModel::RootModel final : public MechanismRootModel {
|
||||
m_group.ForEachObject(func);
|
||||
}
|
||||
|
||||
bool NTUpdate(const nt::TopicNotification& event, std::string_view childName);
|
||||
void NTUpdate(const nt::ValueNotification& event, std::string_view childName);
|
||||
bool NTUpdate(const nt::Event& event, std::string_view childName);
|
||||
|
||||
frc::Translation2d GetPosition() const override { return m_pos; };
|
||||
|
||||
@@ -221,36 +204,35 @@ class NTMechanism2DModel::RootModel final : public MechanismRootModel {
|
||||
frc::Translation2d m_pos;
|
||||
};
|
||||
|
||||
bool NTMechanism2DModel::RootModel::NTUpdate(const nt::TopicNotification& event,
|
||||
bool NTMechanism2DModel::RootModel::NTUpdate(const nt::Event& event,
|
||||
std::string_view childName) {
|
||||
if (event.info.topic == m_xTopic.GetHandle() ||
|
||||
event.info.topic == m_yTopic.GetHandle()) {
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
return true;
|
||||
if (auto info = event.GetTopicInfo()) {
|
||||
if (info->topic == m_xTopic.GetHandle() ||
|
||||
info->topic == m_yTopic.GetHandle()) {
|
||||
if (event.flags & nt::EventFlags::kUnpublish) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
} else if (auto valueData = event.GetValueEventData()) {
|
||||
if (valueData->topic == m_xTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsDouble()) {
|
||||
m_pos = frc::Translation2d{units::meter_t{valueData->value.GetDouble()},
|
||||
m_pos.Y()};
|
||||
}
|
||||
} else if (valueData->topic == m_yTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsDouble()) {
|
||||
m_pos = frc::Translation2d{
|
||||
m_pos.X(), units::meter_t{valueData->value.GetDouble()}};
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NTMechanism2DModel::RootModel::NTUpdate(const nt::ValueNotification& event,
|
||||
std::string_view childName) {
|
||||
if (event.topic == m_xTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_pos = frc::Translation2d{units::meter_t{event.value.GetDouble()},
|
||||
m_pos.Y()};
|
||||
}
|
||||
} else if (event.topic == m_yTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_pos = frc::Translation2d{m_pos.X(),
|
||||
units::meter_t{event.value.GetDouble()}};
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
}
|
||||
|
||||
NTMechanism2DModel::NTMechanism2DModel(std::string_view path)
|
||||
: NTMechanism2DModel{nt::NetworkTableInstance::GetDefault(), path} {}
|
||||
|
||||
@@ -265,75 +247,72 @@ NTMechanism2DModel::NTMechanism2DModel(nt::NetworkTableInstance inst,
|
||||
m_nameTopic{m_inst.GetTopic(fmt::format("{}/.name", path))},
|
||||
m_dimensionsTopic{m_inst.GetTopic(fmt::format("{}/dims", path))},
|
||||
m_bgColorTopic{m_inst.GetTopic(fmt::format("{}/backgroundColor", path))},
|
||||
m_topicListener{m_inst},
|
||||
m_valueListener{m_inst},
|
||||
m_poller{m_inst},
|
||||
m_dimensionsValue{1_m, 1_m} {
|
||||
m_topicListener.Add(m_tableSub, NT_TOPIC_NOTIFY_PUBLISH |
|
||||
NT_TOPIC_NOTIFY_UNPUBLISH |
|
||||
NT_TOPIC_NOTIFY_IMMEDIATE);
|
||||
m_valueListener.Add(m_tableSub,
|
||||
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
|
||||
m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic |
|
||||
nt::EventFlags::kValueAll |
|
||||
nt::EventFlags::kImmediate);
|
||||
}
|
||||
|
||||
NTMechanism2DModel::~NTMechanism2DModel() = default;
|
||||
|
||||
void NTMechanism2DModel::Update() {
|
||||
for (auto&& event : m_topicListener.ReadQueue()) {
|
||||
auto name = wpi::drop_front(event.info.name, m_path.size());
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
std::string_view childName;
|
||||
std::tie(name, childName) = wpi::split(name, '/');
|
||||
if (childName.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
|
||||
[](const auto& e, std::string_view name) {
|
||||
return e->GetName() < name;
|
||||
});
|
||||
bool match = it != m_roots.end() && (*it)->GetName() == name;
|
||||
|
||||
if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
if (!match) {
|
||||
it = m_roots.emplace(
|
||||
it, std::make_unique<RootModel>(
|
||||
m_inst, fmt::format("{}{}", m_path, name), name));
|
||||
match = true;
|
||||
for (auto&& event : m_poller.ReadQueue()) {
|
||||
if (auto info = event.GetTopicInfo()) {
|
||||
auto name = wpi::drop_front(info->name, m_path.size());
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
if ((*it)->NTUpdate(event, childName)) {
|
||||
m_roots.erase(it);
|
||||
std::string_view childName;
|
||||
std::tie(name, childName) = wpi::split(name, '/');
|
||||
if (childName.empty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto&& event : m_valueListener.ReadQueue()) {
|
||||
// .name
|
||||
if (event.topic == m_nameTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
m_nameValue = event.value.GetString();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
|
||||
[](const auto& e, std::string_view name) {
|
||||
return e->GetName() < name;
|
||||
});
|
||||
bool match = it != m_roots.end() && (*it)->GetName() == name;
|
||||
|
||||
// dims
|
||||
if (event.topic == m_dimensionsTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDoubleArray()) {
|
||||
auto arr = event.value.GetDoubleArray();
|
||||
if (arr.size() == 2) {
|
||||
m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
|
||||
units::meter_t{arr[1]}};
|
||||
if (event.flags & nt::EventFlags::kPublish) {
|
||||
if (!match) {
|
||||
it = m_roots.emplace(
|
||||
it, std::make_unique<RootModel>(
|
||||
m_inst, fmt::format("{}{}", m_path, name), name));
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
if ((*it)->NTUpdate(event, childName)) {
|
||||
m_roots.erase(it);
|
||||
}
|
||||
}
|
||||
} else if (auto valueData = event.GetValueEventData()) {
|
||||
// .name
|
||||
if (valueData->topic == m_nameTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsString()) {
|
||||
m_nameValue = valueData->value.GetString();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// backgroundColor
|
||||
if (event.topic == m_bgColorTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
ConvertColor(event.value.GetString(), &m_bgColorValue);
|
||||
// dims
|
||||
if (valueData->topic == m_dimensionsTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsDoubleArray()) {
|
||||
auto arr = valueData->value.GetDoubleArray();
|
||||
if (arr.size() == 2) {
|
||||
m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
|
||||
units::meter_t{arr[1]}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// backgroundColor
|
||||
if (valueData->topic == m_bgColorTopic.GetHandle()) {
|
||||
if (valueData->value && valueData->value.IsString()) {
|
||||
ConvertColor(valueData->value.GetString(), &m_bgColorValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,15 +110,10 @@ NetworkTablesModel::NetworkTablesModel()
|
||||
: NetworkTablesModel{nt::NetworkTableInstance::GetDefault()} {}
|
||||
|
||||
NetworkTablesModel::NetworkTablesModel(nt::NetworkTableInstance inst)
|
||||
: m_inst{inst},
|
||||
m_subscriber{nt::SubscribeMultiple(inst.GetHandle(), {{"", "$"}})},
|
||||
m_topicPoller{inst},
|
||||
m_valuePoller{inst} {
|
||||
m_topicPoller.Add({{""}},
|
||||
NT_TOPIC_NOTIFY_IMMEDIATE | NT_TOPIC_NOTIFY_PROPERTIES |
|
||||
NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_UNPUBLISH);
|
||||
m_valuePoller.Add(m_subscriber,
|
||||
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
|
||||
: m_inst{inst}, m_poller{inst} {
|
||||
m_poller.AddListener({{"", "$"}}, nt::EventFlags::kTopic |
|
||||
nt::EventFlags::kValueAll |
|
||||
nt::EventFlags::kImmediate);
|
||||
}
|
||||
|
||||
NetworkTablesModel::Entry::~Entry() {
|
||||
@@ -176,7 +171,6 @@ static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
|
||||
case mpack::mpack_type_str: {
|
||||
std::string str;
|
||||
mpack_read_str(&r, &tag, &str);
|
||||
mpack_done_str(&r);
|
||||
out->UpdateFromValue(nt::Value::MakeString(std::move(str), time), name,
|
||||
"");
|
||||
break;
|
||||
@@ -427,57 +421,57 @@ void NetworkTablesModel::ValueSource::UpdateFromValue(
|
||||
|
||||
void NetworkTablesModel::Update() {
|
||||
bool updateTree = false;
|
||||
for (auto&& event : m_topicPoller.ReadQueue()) {
|
||||
auto& entry = m_entries[event.info.topic];
|
||||
if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
if (!entry) {
|
||||
entry = std::make_unique<Entry>();
|
||||
m_sortedEntries.emplace_back(entry.get());
|
||||
for (auto&& event : m_poller.ReadQueue()) {
|
||||
if (auto info = event.GetTopicInfo()) {
|
||||
auto& entry = m_entries[info->topic];
|
||||
if (event.flags & nt::EventFlags::kPublish) {
|
||||
if (!entry) {
|
||||
entry = std::make_unique<Entry>();
|
||||
m_sortedEntries.emplace_back(entry.get());
|
||||
updateTree = true;
|
||||
}
|
||||
}
|
||||
if (event.flags & nt::EventFlags::kUnpublish) {
|
||||
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
|
||||
entry.get());
|
||||
// will be removed completely below
|
||||
if (it != m_sortedEntries.end()) {
|
||||
*it = nullptr;
|
||||
}
|
||||
m_entries.erase(info->topic);
|
||||
updateTree = true;
|
||||
continue;
|
||||
}
|
||||
if (event.flags & nt::EventFlags::kProperties) {
|
||||
updateTree = true;
|
||||
}
|
||||
}
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
|
||||
entry.get());
|
||||
// will be removed completely below
|
||||
if (it != m_sortedEntries.end()) {
|
||||
*it = nullptr;
|
||||
if (entry) {
|
||||
entry->UpdateTopic(std::move(event));
|
||||
}
|
||||
m_entries.erase(event.info.topic);
|
||||
updateTree = true;
|
||||
continue;
|
||||
}
|
||||
if (event.flags & NT_TOPIC_NOTIFY_PROPERTIES) {
|
||||
updateTree = true;
|
||||
}
|
||||
if (entry) {
|
||||
entry->UpdateTopic(std::move(event));
|
||||
}
|
||||
}
|
||||
for (auto&& event : m_valuePoller.ReadQueue()) {
|
||||
auto& entry = m_entries[event.topic];
|
||||
if (entry) {
|
||||
entry->UpdateFromValue(std::move(event.value), entry->info.name,
|
||||
entry->info.type_str);
|
||||
if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() &&
|
||||
entry->info.type_str == "msgpack") {
|
||||
fmt::print(stderr, "Updating meta-topic {}\n", entry->info.name);
|
||||
// meta topic handling
|
||||
if (entry->info.name == "$clients") {
|
||||
UpdateClients(entry->value.GetRaw());
|
||||
} else if (entry->info.name == "$serverpub") {
|
||||
m_server.UpdatePublishers(entry->value.GetRaw());
|
||||
} else if (entry->info.name == "$serversub") {
|
||||
m_server.UpdateSubscribers(entry->value.GetRaw());
|
||||
} else if (wpi::starts_with(entry->info.name, "$clientpub$")) {
|
||||
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
|
||||
if (it != m_clients.end()) {
|
||||
it->second.UpdatePublishers(entry->value.GetRaw());
|
||||
}
|
||||
} else if (wpi::starts_with(entry->info.name, "$clientsub$")) {
|
||||
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
|
||||
if (it != m_clients.end()) {
|
||||
it->second.UpdateSubscribers(entry->value.GetRaw());
|
||||
} else if (auto valueData = event.GetValueEventData()) {
|
||||
auto& entry = m_entries[valueData->topic];
|
||||
if (entry) {
|
||||
entry->UpdateFromValue(std::move(valueData->value), entry->info.name,
|
||||
entry->info.type_str);
|
||||
if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() &&
|
||||
entry->info.type_str == "msgpack") {
|
||||
// meta topic handling
|
||||
if (entry->info.name == "$clients") {
|
||||
UpdateClients(entry->value.GetRaw());
|
||||
} else if (entry->info.name == "$serverpub") {
|
||||
m_server.UpdatePublishers(entry->value.GetRaw());
|
||||
} else if (entry->info.name == "$serversub") {
|
||||
m_server.UpdateSubscribers(entry->value.GetRaw());
|
||||
} else if (wpi::starts_with(entry->info.name, "$clientpub$")) {
|
||||
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
|
||||
if (it != m_clients.end()) {
|
||||
it->second.UpdatePublishers(entry->value.GetRaw());
|
||||
}
|
||||
} else if (wpi::starts_with(entry->info.name, "$clientsub$")) {
|
||||
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
|
||||
if (it != m_clients.end()) {
|
||||
it->second.UpdateSubscribers(entry->value.GetRaw());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,10 @@ NetworkTablesProvider::NetworkTablesProvider(Storage& storage,
|
||||
nt::NetworkTableInstance inst)
|
||||
: Provider{storage.GetChild("windows")},
|
||||
m_inst{inst},
|
||||
m_topicPoller{inst},
|
||||
m_valuePoller{inst},
|
||||
m_poller{inst},
|
||||
m_typeCache{storage.GetChild("types")} {
|
||||
storage.SetCustomApply([this] {
|
||||
m_topicListener = m_topicPoller.Add({{""}}, NT_TOPIC_NOTIFY_PUBLISH |
|
||||
NT_TOPIC_NOTIFY_UNPUBLISH |
|
||||
NT_TOPIC_NOTIFY_IMMEDIATE);
|
||||
m_listener = m_poller.AddListener({{""}}, nt::EventFlags::kTopic);
|
||||
for (auto&& childIt : m_storage.GetChildren()) {
|
||||
auto id = childIt.key();
|
||||
auto typePtr = m_typeCache.FindValue(id);
|
||||
@@ -51,8 +48,8 @@ NetworkTablesProvider::NetworkTablesProvider(Storage& storage,
|
||||
}
|
||||
});
|
||||
storage.SetCustomClear([this, &storage] {
|
||||
m_topicPoller.Remove(m_topicListener);
|
||||
m_topicListener = 0;
|
||||
m_poller.RemoveListener(m_listener);
|
||||
m_listener = 0;
|
||||
for (auto&& modelEntry : m_modelEntries) {
|
||||
modelEntry->model.reset();
|
||||
}
|
||||
@@ -102,59 +99,60 @@ void NetworkTablesProvider::DisplayMenu() {
|
||||
void NetworkTablesProvider::Update() {
|
||||
Provider::Update();
|
||||
|
||||
// add/remove entries from NT changes
|
||||
for (auto&& event : m_topicPoller.ReadQueue()) {
|
||||
// look for .type fields
|
||||
if (!wpi::ends_with(event.info.name, "/.type") ||
|
||||
event.info.type != NT_STRING || event.info.type_str != "string") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
auto it = m_topicMap.find(event.info.topic);
|
||||
if (it != m_topicMap.end()) {
|
||||
m_valuePoller.Remove(it->second.listener);
|
||||
m_topicMap.erase(it);
|
||||
for (auto&& event : m_poller.ReadQueue()) {
|
||||
if (auto info = event.GetTopicInfo()) {
|
||||
// add/remove entries from NT changes
|
||||
// look for .type fields
|
||||
if (!wpi::ends_with(info->name, "/.type") || info->type != NT_STRING ||
|
||||
info->type_str != "string") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it2 = std::find_if(
|
||||
m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
|
||||
return static_cast<Entry*>(elem->modelEntry)
|
||||
->typeTopic.GetHandle() == event.info.topic;
|
||||
});
|
||||
if (it2 != m_viewEntries.end()) {
|
||||
m_viewEntries.erase(it2);
|
||||
if (event.flags & nt::EventFlags::kUnpublish) {
|
||||
auto it = m_topicMap.find(info->topic);
|
||||
if (it != m_topicMap.end()) {
|
||||
m_poller.RemoveListener(it->second.listener);
|
||||
m_topicMap.erase(it);
|
||||
}
|
||||
|
||||
auto it2 = std::find_if(
|
||||
m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
|
||||
return static_cast<Entry*>(elem->modelEntry)
|
||||
->typeTopic.GetHandle() == info->topic;
|
||||
});
|
||||
if (it2 != m_viewEntries.end()) {
|
||||
m_viewEntries.erase(it2);
|
||||
}
|
||||
} else if (event.flags & nt::EventFlags::kPublish) {
|
||||
// subscribe to it; use a subscriber so we only get string values
|
||||
SubListener sublistener;
|
||||
sublistener.subscriber = nt::StringTopic{info->topic}.Subscribe("");
|
||||
sublistener.listener = m_poller.AddListener(
|
||||
sublistener.subscriber,
|
||||
nt::EventFlags::kValueAll | nt::EventFlags::kImmediate);
|
||||
m_topicMap.try_emplace(info->topic, std::move(sublistener));
|
||||
}
|
||||
} else if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
// subscribe to it
|
||||
SubListener sublistener;
|
||||
sublistener.subscriber = nt::StringTopic{event.info.topic}.Subscribe("");
|
||||
sublistener.listener =
|
||||
m_valuePoller.Add(sublistener.subscriber,
|
||||
NT_VALUE_NOTIFY_LOCAL | NT_VALUE_NOTIFY_IMMEDIATE);
|
||||
m_topicMap.try_emplace(event.info.topic, std::move(sublistener));
|
||||
} else if (auto valueData = event.GetValueEventData()) {
|
||||
// handle actual .type strings
|
||||
if (!valueData->value.IsString()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// only handle ones where we have a builder
|
||||
auto builderIt = m_typeMap.find(valueData->value.GetString());
|
||||
if (builderIt == m_typeMap.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto topicName = nt::GetTopicName(valueData->topic);
|
||||
auto tableName = wpi::drop_back(topicName, 6);
|
||||
|
||||
GetOrCreateView(builderIt->second, nt::Topic{valueData->topic},
|
||||
tableName);
|
||||
// cache the type
|
||||
m_typeCache.SetString(tableName, valueData->value.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
// handle actual .type strings
|
||||
for (auto&& event : m_valuePoller.ReadQueue()) {
|
||||
if (!event.value.IsString()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// only handle ones where we have a builder
|
||||
auto builderIt = m_typeMap.find(event.value.GetString());
|
||||
if (builderIt == m_typeMap.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto topicName = nt::GetTopicName(event.topic);
|
||||
auto tableName = wpi::drop_back(topicName, 6);
|
||||
|
||||
GetOrCreateView(builderIt->second, nt::Topic{event.topic}, tableName);
|
||||
// cache the type
|
||||
m_typeCache.SetString(tableName, event.value.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTablesProvider::Register(std::string_view typeName,
|
||||
|
||||
@@ -61,11 +61,10 @@ void NetworkTablesSettings::Thread::Main() {
|
||||
if (m_mode == 1 || m_mode == 2) {
|
||||
std::string_view serverTeam{m_serverTeam};
|
||||
std::optional<unsigned int> team;
|
||||
nt::SetNetworkIdentity(m_inst, m_clientName);
|
||||
if (m_mode == 1) {
|
||||
nt::StartClient4(m_inst);
|
||||
nt::StartClient4(m_inst, m_clientName);
|
||||
} else if (m_mode == 2) {
|
||||
nt::StartClient3(m_inst);
|
||||
nt::StartClient3(m_inst, m_clientName);
|
||||
}
|
||||
if (!wpi::contains(serverTeam, '.') &&
|
||||
(team = wpi::parse_integer<unsigned int>(serverTeam, 10))) {
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
|
||||
#include <networktables/MultiSubscriber.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/NetworkTableListener.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
#include <networktables/TopicListener.h>
|
||||
#include <networktables/ValueListener.h>
|
||||
#include <ntcore_cpp.h>
|
||||
|
||||
#include "glass/other/Field2D.h"
|
||||
@@ -48,8 +47,7 @@ class NTField2DModel : public Field2DModel {
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::MultiSubscriber m_tableSub;
|
||||
nt::StringTopic m_nameTopic;
|
||||
nt::TopicListenerPoller m_topicListener;
|
||||
nt::ValueListenerPoller m_valueListener;
|
||||
nt::NetworkTableListenerPoller m_poller;
|
||||
std::string m_nameValue;
|
||||
|
||||
class ObjectModel;
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
#include <frc/geometry/Translation2d.h>
|
||||
#include <networktables/MultiSubscriber.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/TopicListener.h>
|
||||
#include <networktables/ValueListener.h>
|
||||
#include <networktables/NetworkTableListener.h>
|
||||
|
||||
#include "glass/other/Mechanism2D.h"
|
||||
|
||||
@@ -50,8 +49,7 @@ class NTMechanism2DModel : public Mechanism2DModel {
|
||||
nt::Topic m_nameTopic;
|
||||
nt::Topic m_dimensionsTopic;
|
||||
nt::Topic m_bgColorTopic;
|
||||
nt::TopicListenerPoller m_topicListener;
|
||||
nt::ValueListenerPoller m_valueListener;
|
||||
nt::NetworkTableListenerPoller m_poller;
|
||||
|
||||
std::string m_nameValue;
|
||||
frc::Translation2d m_dimensionsValue;
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/TopicListener.h>
|
||||
#include <networktables/ValueListener.h>
|
||||
#include <networktables/NetworkTableListener.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/json.h>
|
||||
@@ -84,8 +83,10 @@ class NetworkTablesModel : public Model {
|
||||
Entry& operator=(const Entry&) = delete;
|
||||
~Entry();
|
||||
|
||||
void UpdateTopic(nt::TopicNotification&& event) {
|
||||
UpdateInfo(std::move(event.info));
|
||||
void UpdateTopic(nt::Event&& event) {
|
||||
if (std::holds_alternative<nt::TopicInfo>(event.data)) {
|
||||
UpdateInfo(std::get<nt::TopicInfo>(std::move(event.data)));
|
||||
}
|
||||
}
|
||||
void UpdateInfo(nt::TopicInfo&& info_);
|
||||
|
||||
@@ -179,9 +180,7 @@ class NetworkTablesModel : public Model {
|
||||
void UpdateClients(std::span<const uint8_t> data);
|
||||
|
||||
nt::NetworkTableInstance m_inst;
|
||||
NT_MultiSubscriber m_subscriber;
|
||||
nt::TopicListenerPoller m_topicPoller;
|
||||
nt::ValueListenerPoller m_valuePoller;
|
||||
nt::NetworkTableListenerPoller m_poller;
|
||||
wpi::DenseMap<NT_Topic, std::unique_ptr<Entry>> m_entries;
|
||||
|
||||
// sorted by name
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/NetworkTableListener.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
#include <networktables/Topic.h>
|
||||
#include <networktables/TopicListener.h>
|
||||
#include <networktables/ValueListener.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/StringMap.h>
|
||||
|
||||
@@ -79,9 +78,8 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
|
||||
void Update() override;
|
||||
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::TopicListenerPoller m_topicPoller;
|
||||
NT_TopicListener m_topicListener{0};
|
||||
nt::ValueListenerPoller m_valuePoller;
|
||||
nt::NetworkTableListenerPoller m_poller;
|
||||
NT_Listener m_listener{0};
|
||||
|
||||
// cached mapping from table name to type string
|
||||
Storage& m_typeCache;
|
||||
@@ -96,7 +94,7 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
|
||||
|
||||
struct SubListener {
|
||||
nt::StringSubscriber subscriber;
|
||||
NT_ValueListener listener;
|
||||
NT_Listener listener;
|
||||
};
|
||||
|
||||
// mapping from .type topic to subscriber/listener
|
||||
|
||||
137
hal/src/main/java/edu/wpi/first/hal/DriverStationJNI.java
Normal file
137
hal/src/main/java/edu/wpi/first/hal/DriverStationJNI.java
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.hal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class DriverStationJNI extends JNIWrapper {
|
||||
public static native void observeUserProgramStarting();
|
||||
|
||||
public static native void observeUserProgramDisabled();
|
||||
|
||||
public static native void observeUserProgramAutonomous();
|
||||
|
||||
public static native void observeUserProgramTeleop();
|
||||
|
||||
public static native void observeUserProgramTest();
|
||||
|
||||
public static void report(int resource, int instanceNumber) {
|
||||
report(resource, instanceNumber, 0, "");
|
||||
}
|
||||
|
||||
public static void report(int resource, int instanceNumber, int context) {
|
||||
report(resource, instanceNumber, context, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the usage of a resource of interest.
|
||||
*
|
||||
* <p>Original signature: <code>uint32_t report(tResourceType, uint8_t, uint8_t, const
|
||||
* char*)</code>
|
||||
*
|
||||
* @param resource one of the values in the tResourceType above (max value 51).
|
||||
* @param instanceNumber an index that identifies the resource instance.
|
||||
* @param context an optional additional context number for some cases (such as module number).
|
||||
* Set to 0 to omit.
|
||||
* @param feature a string to be included describing features in use on a specific resource.
|
||||
* Setting the same resource more than once allows you to change the feature string.
|
||||
* @return TODO
|
||||
*/
|
||||
public static native int report(int resource, int instanceNumber, int context, String feature);
|
||||
|
||||
public static native int nativeGetControlWord();
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
public static void getControlWord(ControlWord controlWord) {
|
||||
int word = nativeGetControlWord();
|
||||
controlWord.update(
|
||||
(word & 1) != 0,
|
||||
((word >> 1) & 1) != 0,
|
||||
((word >> 2) & 1) != 0,
|
||||
((word >> 3) & 1) != 0,
|
||||
((word >> 4) & 1) != 0,
|
||||
((word >> 5) & 1) != 0);
|
||||
}
|
||||
|
||||
private static native int nativeGetAllianceStation();
|
||||
|
||||
public static final int kRed1AllianceStation = 0;
|
||||
public static final int kRed2AllianceStation = 1;
|
||||
public static final int kRed3AllianceStation = 2;
|
||||
public static final int kBlue1AllianceStation = 3;
|
||||
public static final int kBlue2AllianceStation = 4;
|
||||
public static final int kBlue3AllianceStation = 5;
|
||||
|
||||
@SuppressWarnings("MissingJavadocMethod")
|
||||
public static AllianceStationID getAllianceStation() {
|
||||
switch (nativeGetAllianceStation()) {
|
||||
case kRed1AllianceStation:
|
||||
return AllianceStationID.Red1;
|
||||
case kRed2AllianceStation:
|
||||
return AllianceStationID.Red2;
|
||||
case kRed3AllianceStation:
|
||||
return AllianceStationID.Red3;
|
||||
case kBlue1AllianceStation:
|
||||
return AllianceStationID.Blue1;
|
||||
case kBlue2AllianceStation:
|
||||
return AllianceStationID.Blue2;
|
||||
case kBlue3AllianceStation:
|
||||
return AllianceStationID.Blue3;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final int kMaxJoystickAxes = 12;
|
||||
public static final int kMaxJoystickPOVs = 12;
|
||||
public static final int kMaxJoysticks = 6;
|
||||
|
||||
public static native int getJoystickAxes(byte joystickNum, float[] axesArray);
|
||||
|
||||
public static native int getJoystickAxesRaw(byte joystickNum, int[] rawAxesArray);
|
||||
|
||||
public static native int getJoystickPOVs(byte joystickNum, short[] povsArray);
|
||||
|
||||
public static native int getJoystickButtons(byte joystickNum, ByteBuffer count);
|
||||
|
||||
public static native void getAllJoystickData(
|
||||
float[] axesArray, byte[] rawAxesArray, short[] povsArray, long[] buttonsAndMetadata);
|
||||
|
||||
public static native int setJoystickOutputs(
|
||||
byte joystickNum, int outputs, short leftRumble, short rightRumble);
|
||||
|
||||
public static native int getJoystickIsXbox(byte joystickNum);
|
||||
|
||||
public static native int getJoystickType(byte joystickNum);
|
||||
|
||||
public static native String getJoystickName(byte joystickNum);
|
||||
|
||||
public static native int getJoystickAxisType(byte joystickNum, byte axis);
|
||||
|
||||
public static native double getMatchTime();
|
||||
|
||||
public static native int getMatchInfo(MatchInfoData info);
|
||||
|
||||
public static native int sendError(
|
||||
boolean isError,
|
||||
int errorCode,
|
||||
boolean isLVCode,
|
||||
String details,
|
||||
String location,
|
||||
String callStack,
|
||||
boolean printMsg);
|
||||
|
||||
public static native int sendConsoleLine(String line);
|
||||
|
||||
public static native void refreshDSData();
|
||||
|
||||
public static native void provideNewDataEventHandle(int handle);
|
||||
|
||||
public static native void removeNewDataEventHandle(int handle);
|
||||
|
||||
public static native boolean getOutputsActive();
|
||||
|
||||
private DriverStationJNI() {}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
package edu.wpi.first.hal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -13,8 +12,6 @@ import java.util.List;
|
||||
* .
|
||||
*/
|
||||
public final class HAL extends JNIWrapper {
|
||||
public static native void waitForDSData();
|
||||
|
||||
public static native boolean initialize(int timeout, int mode);
|
||||
|
||||
public static native void shutdown();
|
||||
@@ -116,15 +113,13 @@ public final class HAL extends JNIWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
public static native void observeUserProgramStarting();
|
||||
public static native boolean getBrownedOut();
|
||||
|
||||
public static native void observeUserProgramDisabled();
|
||||
public static native boolean getSystemActive();
|
||||
|
||||
public static native void observeUserProgramAutonomous();
|
||||
public static native int getPortWithModule(byte module, byte channel);
|
||||
|
||||
public static native void observeUserProgramTeleop();
|
||||
|
||||
public static native void observeUserProgramTest();
|
||||
public static native int getPort(byte channel);
|
||||
|
||||
public static void report(int resource, int instanceNumber) {
|
||||
report(resource, instanceNumber, 0, "");
|
||||
@@ -134,114 +129,9 @@ public final class HAL extends JNIWrapper {
|
||||
report(resource, instanceNumber, context, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the usage of a resource of interest.
|
||||
*
|
||||
* <p>Original signature: <code>uint32_t report(tResourceType, uint8_t, uint8_t, const
|
||||
* char*)</code>
|
||||
*
|
||||
* @param resource one of the values in the tResourceType above (max value 51).
|
||||
* @param instanceNumber an index that identifies the resource instance.
|
||||
* @param context an optional additional context number for some cases (such as module number).
|
||||
* Set to 0 to omit.
|
||||
* @param feature a string to be included describing features in use on a specific resource.
|
||||
* Setting the same resource more than once allows you to change the feature string.
|
||||
* @return TODO
|
||||
*/
|
||||
public static native int report(int resource, int instanceNumber, int context, String feature);
|
||||
|
||||
public static native int nativeGetControlWord();
|
||||
|
||||
/**
|
||||
* Get the current DriverStation control word.
|
||||
*
|
||||
* @param controlWord Storage for control word.
|
||||
*/
|
||||
public static void getControlWord(ControlWord controlWord) {
|
||||
int word = nativeGetControlWord();
|
||||
controlWord.update(
|
||||
(word & 1) != 0,
|
||||
((word >> 1) & 1) != 0,
|
||||
((word >> 2) & 1) != 0,
|
||||
((word >> 3) & 1) != 0,
|
||||
((word >> 4) & 1) != 0,
|
||||
((word >> 5) & 1) != 0);
|
||||
public static int report(int resource, int instanceNumber, int context, String feature) {
|
||||
return DriverStationJNI.report(resource, instanceNumber, context, feature);
|
||||
}
|
||||
|
||||
private static native int nativeGetAllianceStation();
|
||||
|
||||
/**
|
||||
* Get the alliance station.
|
||||
*
|
||||
* @return The alliance station.
|
||||
*/
|
||||
public static AllianceStationID getAllianceStation() {
|
||||
switch (nativeGetAllianceStation()) {
|
||||
case 0:
|
||||
return AllianceStationID.Red1;
|
||||
case 1:
|
||||
return AllianceStationID.Red2;
|
||||
case 2:
|
||||
return AllianceStationID.Red3;
|
||||
case 3:
|
||||
return AllianceStationID.Blue1;
|
||||
case 4:
|
||||
return AllianceStationID.Blue2;
|
||||
case 5:
|
||||
return AllianceStationID.Blue3;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static native boolean isNewControlData();
|
||||
|
||||
public static native void releaseDSMutex();
|
||||
|
||||
public static native boolean waitForDSDataTimeout(double timeout);
|
||||
|
||||
public static final int kMaxJoystickAxes = 12;
|
||||
public static final int kMaxJoystickPOVs = 12;
|
||||
|
||||
public static native short getJoystickAxes(byte joystickNum, float[] axesArray);
|
||||
|
||||
public static native short getJoystickPOVs(byte joystickNum, short[] povsArray);
|
||||
|
||||
public static native int getJoystickButtons(byte joystickNum, ByteBuffer count);
|
||||
|
||||
public static native int setJoystickOutputs(
|
||||
byte joystickNum, int outputs, short leftRumble, short rightRumble);
|
||||
|
||||
public static native int getJoystickIsXbox(byte joystickNum);
|
||||
|
||||
public static native int getJoystickType(byte joystickNum);
|
||||
|
||||
public static native String getJoystickName(byte joystickNum);
|
||||
|
||||
public static native int getJoystickAxisType(byte joystickNum, byte axis);
|
||||
|
||||
public static native double getMatchTime();
|
||||
|
||||
public static native boolean getSystemActive();
|
||||
|
||||
public static native boolean getBrownedOut();
|
||||
|
||||
public static native int getMatchInfo(MatchInfoData info);
|
||||
|
||||
public static native int sendError(
|
||||
boolean isError,
|
||||
int errorCode,
|
||||
boolean isLVCode,
|
||||
String details,
|
||||
String location,
|
||||
String callStack,
|
||||
boolean printMsg);
|
||||
|
||||
public static native int sendConsoleLine(String line);
|
||||
|
||||
public static native int getPortWithModule(byte module, byte channel);
|
||||
|
||||
public static native int getPort(byte channel);
|
||||
|
||||
private HAL() {}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <FRC_FPGA_ChipObject/fpgainterfacecapi/NiFpga_HMB.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "ConstantsInternal.h"
|
||||
#include "DigitalInternal.h"
|
||||
#include "FPGACalls.h"
|
||||
#include "HALInitializer.h"
|
||||
#include "HALInternal.h"
|
||||
#include "PortsInternal.h"
|
||||
@@ -21,51 +21,6 @@
|
||||
|
||||
using namespace hal;
|
||||
|
||||
extern "C" {
|
||||
NiFpga_Status NiFpga_ClientFunctionCall(NiFpga_Session session, uint32_t group,
|
||||
uint32_t functionId,
|
||||
const void* inBuffer,
|
||||
size_t inBufferSize, void* outBuffer,
|
||||
size_t outBufferSize);
|
||||
} // extern "C"
|
||||
|
||||
// Shim for broken ChipObject function
|
||||
static const uint32_t clientFeature_hostMemoryBuffer = 0;
|
||||
static const uint32_t hostMemoryBufferFunction_open = 2;
|
||||
|
||||
// Input arguments for HMB open
|
||||
struct AtomicHMBOpenInputs {
|
||||
const char* memoryName;
|
||||
};
|
||||
|
||||
// Output arguments for HMB open
|
||||
struct AtomicHMBOpenOutputs {
|
||||
size_t size;
|
||||
void* virtualAddress;
|
||||
};
|
||||
|
||||
static NiFpga_Status OpenHostMemoryBuffer(NiFpga_Session session,
|
||||
const char* memoryName,
|
||||
void** virtualAddress, size_t* size) {
|
||||
struct AtomicHMBOpenOutputs outputs;
|
||||
|
||||
struct AtomicHMBOpenInputs inputs;
|
||||
inputs.memoryName = memoryName;
|
||||
|
||||
NiFpga_Status retval = NiFpga_ClientFunctionCall(
|
||||
session, clientFeature_hostMemoryBuffer, hostMemoryBufferFunction_open,
|
||||
&inputs, sizeof(struct AtomicHMBOpenInputs), &outputs,
|
||||
sizeof(struct AtomicHMBOpenOutputs));
|
||||
if (NiFpga_IsError(retval)) {
|
||||
return retval;
|
||||
}
|
||||
*virtualAddress = outputs.virtualAddress;
|
||||
if (size) {
|
||||
*size = outputs.size;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct AddressableLED {
|
||||
std::unique_ptr<tLED> led;
|
||||
@@ -89,6 +44,8 @@ void InitializeAddressableLED() {
|
||||
}
|
||||
} // namespace hal::init
|
||||
|
||||
static constexpr const char* HmbName = "HMB_0_LED";
|
||||
|
||||
extern "C" {
|
||||
|
||||
HAL_AddressableLEDHandle HAL_InitializeAddressableLED(
|
||||
@@ -146,8 +103,8 @@ HAL_AddressableLEDHandle HAL_InitializeAddressableLED(
|
||||
|
||||
uint32_t session = led->led->getSystemInterface()->getHandle();
|
||||
|
||||
*status = OpenHostMemoryBuffer(session, "HMB_0_LED", &led->ledBuffer,
|
||||
&led->ledBufferSize);
|
||||
*status = hal::HAL_NiFpga_OpenHmb(session, HmbName, &led->ledBufferSize,
|
||||
&led->ledBuffer);
|
||||
|
||||
if (*status != 0) {
|
||||
addressableLEDHandles->Free(handle);
|
||||
@@ -158,6 +115,12 @@ HAL_AddressableLEDHandle HAL_InitializeAddressableLED(
|
||||
}
|
||||
|
||||
void HAL_FreeAddressableLED(HAL_AddressableLEDHandle handle) {
|
||||
auto led = addressableLEDHandles->Get(handle);
|
||||
if (!led) {
|
||||
return;
|
||||
}
|
||||
uint32_t session = led->led->getSystemInterface()->getHandle();
|
||||
hal::HAL_NiFpga_CloseHmb(session, HmbName);
|
||||
addressableLEDHandles->Free(handle);
|
||||
}
|
||||
|
||||
|
||||
66
hal/src/main/native/athena/FPGACalls.cpp
Normal file
66
hal/src/main/native/athena/FPGACalls.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// 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 "FPGACalls.h"
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
#include "dlfcn.h"
|
||||
#include "hal/Errors.h"
|
||||
|
||||
static void* NiFpgaLibrary = nullptr;
|
||||
|
||||
namespace hal {
|
||||
HAL_NiFpga_ReserveIrqContextFunc HAL_NiFpga_ReserveIrqContext;
|
||||
HAL_NiFpga_UnreserveIrqContextFunc HAL_NiFpga_UnreserveIrqContext;
|
||||
HAL_NiFpga_WaitOnIrqsFunc HAL_NiFpga_WaitOnIrqs;
|
||||
HAL_NiFpga_AcknowledgeIrqsFunc HAL_NiFpga_AcknowledgeIrqs;
|
||||
HAL_NiFpga_OpenHmbFunc HAL_NiFpga_OpenHmb;
|
||||
HAL_NiFpga_CloseHmbFunc HAL_NiFpga_CloseHmb;
|
||||
|
||||
namespace init {
|
||||
int InitializeFPGA() {
|
||||
NiFpgaLibrary = dlopen("libNiFpga.so", RTLD_LAZY);
|
||||
if (!NiFpgaLibrary) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
HAL_NiFpga_ReserveIrqContext =
|
||||
reinterpret_cast<HAL_NiFpga_ReserveIrqContextFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_ReserveIrqContext"));
|
||||
HAL_NiFpga_UnreserveIrqContext =
|
||||
reinterpret_cast<HAL_NiFpga_UnreserveIrqContextFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_UnreserveIrqContext"));
|
||||
HAL_NiFpga_WaitOnIrqs = reinterpret_cast<HAL_NiFpga_WaitOnIrqsFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_WaitOnIrqs"));
|
||||
HAL_NiFpga_AcknowledgeIrqs = reinterpret_cast<HAL_NiFpga_AcknowledgeIrqsFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_AcknowledgeIrqs"));
|
||||
HAL_NiFpga_OpenHmb = reinterpret_cast<HAL_NiFpga_OpenHmbFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_OpenHmb"));
|
||||
HAL_NiFpga_CloseHmb = reinterpret_cast<HAL_NiFpga_CloseHmbFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_CloseHmb"));
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
if (HAL_NiFpga_ReserveIrqContext == nullptr ||
|
||||
HAL_NiFpga_UnreserveIrqContext == nullptr ||
|
||||
HAL_NiFpga_WaitOnIrqs == nullptr ||
|
||||
HAL_NiFpga_AcknowledgeIrqs == nullptr || HAL_NiFpga_OpenHmb == nullptr ||
|
||||
HAL_NiFpga_CloseHmb == nullptr) {
|
||||
HAL_NiFpga_ReserveIrqContext = nullptr;
|
||||
HAL_NiFpga_UnreserveIrqContext = nullptr;
|
||||
HAL_NiFpga_WaitOnIrqs = nullptr;
|
||||
HAL_NiFpga_AcknowledgeIrqs = nullptr;
|
||||
HAL_NiFpga_OpenHmb = nullptr;
|
||||
HAL_NiFpga_CloseHmb = nullptr;
|
||||
dlclose(NiFpgaLibrary);
|
||||
NiFpgaLibrary = nullptr;
|
||||
return NO_AVAILABLE_RESOURCES;
|
||||
}
|
||||
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
} // namespace init
|
||||
} // namespace hal
|
||||
46
hal/src/main/native/athena/FPGACalls.h
Normal file
46
hal/src/main/native/athena/FPGACalls.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <FRC_FPGA_ChipObject/fpgainterfacecapi/NiFpga.h>
|
||||
|
||||
namespace hal {
|
||||
namespace init {
|
||||
[[nodiscard]] int InitializeFPGA();
|
||||
} // namespace init
|
||||
|
||||
using HAL_NiFpga_ReserveIrqContextFunc =
|
||||
NiFpga_Status (*)(NiFpga_Session session, NiFpga_IrqContext* context);
|
||||
|
||||
extern HAL_NiFpga_ReserveIrqContextFunc HAL_NiFpga_ReserveIrqContext;
|
||||
|
||||
using HAL_NiFpga_UnreserveIrqContextFunc =
|
||||
NiFpga_Status (*)(NiFpga_Session session, NiFpga_IrqContext context);
|
||||
|
||||
extern HAL_NiFpga_UnreserveIrqContextFunc HAL_NiFpga_UnreserveIrqContext;
|
||||
|
||||
using HAL_NiFpga_WaitOnIrqsFunc = NiFpga_Status (*)(
|
||||
NiFpga_Session session, NiFpga_IrqContext context, uint32_t irqs,
|
||||
uint32_t timeout, uint32_t* irqsAsserted, NiFpga_Bool* timedOut);
|
||||
|
||||
extern HAL_NiFpga_WaitOnIrqsFunc HAL_NiFpga_WaitOnIrqs;
|
||||
|
||||
using HAL_NiFpga_AcknowledgeIrqsFunc = NiFpga_Status (*)(NiFpga_Session session,
|
||||
uint32_t irqs);
|
||||
|
||||
extern HAL_NiFpga_AcknowledgeIrqsFunc HAL_NiFpga_AcknowledgeIrqs;
|
||||
|
||||
using HAL_NiFpga_OpenHmbFunc = NiFpga_Status (*)(const NiFpga_Session session,
|
||||
const char* memoryName,
|
||||
size_t* memorySize,
|
||||
void** virtualAddress);
|
||||
|
||||
extern HAL_NiFpga_OpenHmbFunc HAL_NiFpga_OpenHmb;
|
||||
|
||||
using HAL_NiFpga_CloseHmbFunc = NiFpga_Status (*)(const NiFpga_Session session,
|
||||
const char* memoryName);
|
||||
|
||||
extern HAL_NiFpga_CloseHmbFunc HAL_NiFpga_CloseHmb;
|
||||
} // namespace hal
|
||||
@@ -13,11 +13,14 @@
|
||||
#include <FRC_NetworkCommunication/FRCComm.h>
|
||||
#include <FRC_NetworkCommunication/NetCommRPCProxy_Occur.h>
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/EventVector.h>
|
||||
#include <wpi/SafeThread.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "hal/DriverStation.h"
|
||||
#include "hal/Errors.h"
|
||||
|
||||
static_assert(sizeof(int32_t) >= sizeof(int),
|
||||
"FRC_NetworkComm status variable is larger than 32 bits");
|
||||
@@ -27,25 +30,45 @@ struct HAL_JoystickAxesInt {
|
||||
int16_t axes[HAL_kMaxJoystickAxes];
|
||||
};
|
||||
|
||||
static constexpr int kJoystickPorts = 6;
|
||||
namespace {
|
||||
struct JoystickDataCache {
|
||||
JoystickDataCache() { std::memset(this, 0, sizeof(*this)); }
|
||||
void Update();
|
||||
|
||||
HAL_JoystickAxes axes[HAL_kMaxJoysticks];
|
||||
HAL_JoystickPOVs povs[HAL_kMaxJoysticks];
|
||||
HAL_JoystickButtons buttons[HAL_kMaxJoysticks];
|
||||
HAL_AllianceStationID allianceStation;
|
||||
float matchTime;
|
||||
bool updated;
|
||||
};
|
||||
static_assert(std::is_standard_layout_v<JoystickDataCache>);
|
||||
// static_assert(std::is_trivial_v<JoystickDataCache>);
|
||||
|
||||
struct FRCDriverStation {
|
||||
wpi::EventVector newDataEvents;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static ::FRCDriverStation* driverStation;
|
||||
|
||||
// Message and Data variables
|
||||
static wpi::mutex msgMutex;
|
||||
|
||||
static int32_t HAL_GetJoystickAxesInternal(int32_t joystickNum,
|
||||
HAL_JoystickAxes* axes) {
|
||||
HAL_JoystickAxesInt axesInt;
|
||||
JoystickAxes_t netcommAxes;
|
||||
|
||||
int retVal = FRC_NetworkCommunication_getJoystickAxes(
|
||||
joystickNum, reinterpret_cast<JoystickAxes_t*>(&axesInt),
|
||||
HAL_kMaxJoystickAxes);
|
||||
joystickNum, &netcommAxes, HAL_kMaxJoystickAxes);
|
||||
|
||||
// copy integer values to double values
|
||||
axes->count = axesInt.count;
|
||||
axes->count = netcommAxes.count;
|
||||
// current scaling is -128 to 127, can easily be patched in the future by
|
||||
// changing this function.
|
||||
for (int32_t i = 0; i < axesInt.count; i++) {
|
||||
int8_t value = axesInt.axes[i];
|
||||
for (int32_t i = 0; i < netcommAxes.count; i++) {
|
||||
int8_t value = netcommAxes.axes[i];
|
||||
axes->raw[i] = value;
|
||||
if (value < 0) {
|
||||
axes->axes[i] = value / 128.0;
|
||||
} else {
|
||||
@@ -68,6 +91,30 @@ static int32_t HAL_GetJoystickButtonsInternal(int32_t joystickNum,
|
||||
return FRC_NetworkCommunication_getJoystickButtons(
|
||||
joystickNum, &buttons->buttons, &buttons->count);
|
||||
}
|
||||
|
||||
void JoystickDataCache::Update() {
|
||||
for (int i = 0; i < HAL_kMaxJoysticks; i++) {
|
||||
HAL_GetJoystickAxesInternal(i, &axes[i]);
|
||||
HAL_GetJoystickPOVsInternal(i, &povs[i]);
|
||||
HAL_GetJoystickButtonsInternal(i, &buttons[i]);
|
||||
}
|
||||
FRC_NetworkCommunication_getAllianceStation(
|
||||
reinterpret_cast<AllianceStationID_t*>(&allianceStation));
|
||||
FRC_NetworkCommunication_getMatchTime(&matchTime);
|
||||
}
|
||||
|
||||
#define CHECK_JOYSTICK_NUMBER(stickNum) \
|
||||
if ((stickNum) < 0 || (stickNum) >= HAL_kMaxJoysticks) \
|
||||
return PARAMETER_OUT_OF_RANGE
|
||||
|
||||
static HAL_ControlWord newestControlWord;
|
||||
static JoystickDataCache caches[3];
|
||||
static JoystickDataCache* currentRead = &caches[0];
|
||||
static JoystickDataCache* currentCache = &caches[1];
|
||||
static JoystickDataCache* cacheToUpdate = &caches[2];
|
||||
|
||||
static wpi::mutex cacheMutex;
|
||||
|
||||
/**
|
||||
* Retrieve the Joystick Descriptor for particular slot.
|
||||
*
|
||||
@@ -102,12 +149,6 @@ static int32_t HAL_GetJoystickDescriptorInternal(int32_t joystickNum,
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int32_t HAL_GetControlWordInternal(HAL_ControlWord* controlWord) {
|
||||
std::memset(controlWord, 0, sizeof(HAL_ControlWord));
|
||||
return FRC_NetworkCommunication_getControlWord(
|
||||
reinterpret_cast<ControlWord_t*>(controlWord));
|
||||
}
|
||||
|
||||
static int32_t HAL_GetMatchInfoInternal(HAL_MatchInfo* info) {
|
||||
MatchType_t matchType = MatchType_t::kMatchType_none;
|
||||
info->gameSpecificMessageSize = sizeof(info->gameSpecificMessage);
|
||||
@@ -126,16 +167,11 @@ static int32_t HAL_GetMatchInfoInternal(HAL_MatchInfo* info) {
|
||||
return status;
|
||||
}
|
||||
|
||||
static wpi::mutex* newDSDataAvailableMutex;
|
||||
static wpi::condition_variable* newDSDataAvailableCond;
|
||||
static int newDSDataAvailableCounter{0};
|
||||
|
||||
namespace hal::init {
|
||||
void InitializeFRCDriverStation() {
|
||||
static wpi::mutex newMutex;
|
||||
newDSDataAvailableMutex = &newMutex;
|
||||
static wpi::condition_variable newCond;
|
||||
newDSDataAvailableCond = &newCond;
|
||||
std::memset(&newestControlWord, 0, sizeof(newestControlWord));
|
||||
static FRCDriverStation ds;
|
||||
driverStation = &ds;
|
||||
}
|
||||
} // namespace hal::init
|
||||
|
||||
@@ -248,20 +284,39 @@ int32_t HAL_SendConsoleLine(const char* line) {
|
||||
}
|
||||
|
||||
int32_t HAL_GetControlWord(HAL_ControlWord* controlWord) {
|
||||
return HAL_GetControlWordInternal(controlWord);
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
*controlWord = newestControlWord;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickAxes(int32_t joystickNum, HAL_JoystickAxes* axes) {
|
||||
return HAL_GetJoystickAxesInternal(joystickNum, axes);
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
*axes = currentRead->axes[joystickNum];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs) {
|
||||
return HAL_GetJoystickPOVsInternal(joystickNum, povs);
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
*povs = currentRead->povs[joystickNum];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickButtons(int32_t joystickNum,
|
||||
HAL_JoystickButtons* buttons) {
|
||||
return HAL_GetJoystickButtonsInternal(joystickNum, buttons);
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
*buttons = currentRead->buttons[joystickNum];
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HAL_GetAllJoystickData(HAL_JoystickAxes* axes, HAL_JoystickPOVs* povs,
|
||||
HAL_JoystickButtons* buttons) {
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
std::memcpy(axes, currentRead->axes, sizeof(currentRead->axes));
|
||||
std::memcpy(povs, currentRead->povs, sizeof(currentRead->povs));
|
||||
std::memcpy(buttons, currentRead->buttons, sizeof(currentRead->buttons));
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickDescriptor(int32_t joystickNum,
|
||||
@@ -274,10 +329,8 @@ int32_t HAL_GetMatchInfo(HAL_MatchInfo* info) {
|
||||
}
|
||||
|
||||
HAL_AllianceStationID HAL_GetAllianceStation(int32_t* status) {
|
||||
HAL_AllianceStationID allianceStation;
|
||||
*status = FRC_NetworkCommunication_getAllianceStation(
|
||||
reinterpret_cast<AllianceStationID_t*>(&allianceStation));
|
||||
return allianceStation;
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
return currentRead->allianceStation;
|
||||
}
|
||||
|
||||
HAL_Bool HAL_GetJoystickIsXbox(int32_t joystickNum) {
|
||||
@@ -317,6 +370,7 @@ void HAL_FreeJoystickName(char* name) {
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickAxisType(int32_t joystickNum, int32_t axis) {
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
HAL_JoystickDescriptor joystickDesc;
|
||||
if (HAL_GetJoystickDescriptor(joystickNum, &joystickDesc) < 0) {
|
||||
return -1;
|
||||
@@ -327,14 +381,14 @@ int32_t HAL_GetJoystickAxisType(int32_t joystickNum, int32_t axis) {
|
||||
|
||||
int32_t HAL_SetJoystickOutputs(int32_t joystickNum, int64_t outputs,
|
||||
int32_t leftRumble, int32_t rightRumble) {
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
return FRC_NetworkCommunication_setJoystickOutputs(joystickNum, outputs,
|
||||
leftRumble, rightRumble);
|
||||
}
|
||||
|
||||
double HAL_GetMatchTime(int32_t* status) {
|
||||
float matchTime;
|
||||
*status = FRC_NetworkCommunication_getMatchTime(&matchTime);
|
||||
return matchTime;
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
return currentRead->matchTime;
|
||||
}
|
||||
|
||||
void HAL_ObserveUserProgramStarting(void) {
|
||||
@@ -357,55 +411,6 @@ void HAL_ObserveUserProgramTest(void) {
|
||||
FRC_NetworkCommunication_observeUserProgramTest();
|
||||
}
|
||||
|
||||
static int& GetThreadLocalLastCount() {
|
||||
// There is a rollover error condition here. At Packet# = n * (uintmax), this
|
||||
// will return false when instead it should return true. However, this at a
|
||||
// 20ms rate occurs once every 2.7 years of DS connected runtime, so not
|
||||
// worth the cycles to check.
|
||||
thread_local int lastCount{0};
|
||||
return lastCount;
|
||||
}
|
||||
|
||||
HAL_Bool HAL_IsNewControlData(void) {
|
||||
std::scoped_lock lock{*newDSDataAvailableMutex};
|
||||
int& lastCount = GetThreadLocalLastCount();
|
||||
int currentCount = newDSDataAvailableCounter;
|
||||
if (lastCount == currentCount) {
|
||||
return false;
|
||||
}
|
||||
lastCount = currentCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HAL_WaitForDSData(void) {
|
||||
HAL_WaitForDSDataTimeout(0);
|
||||
}
|
||||
|
||||
HAL_Bool HAL_WaitForDSDataTimeout(double timeout) {
|
||||
std::unique_lock lock{*newDSDataAvailableMutex};
|
||||
int& lastCount = GetThreadLocalLastCount();
|
||||
int currentCount = newDSDataAvailableCounter;
|
||||
if (lastCount != currentCount) {
|
||||
lastCount = currentCount;
|
||||
return true;
|
||||
}
|
||||
auto timeoutTime =
|
||||
std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
|
||||
|
||||
while (newDSDataAvailableCounter == currentCount) {
|
||||
if (timeout > 0) {
|
||||
auto timedOut = newDSDataAvailableCond->wait_until(lock, timeoutTime);
|
||||
if (timedOut == std::cv_status::timeout) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
newDSDataAvailableCond->wait(lock);
|
||||
}
|
||||
}
|
||||
lastCount = newDSDataAvailableCounter;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Constant number to be used for our occur handle
|
||||
constexpr int32_t refNumber = 42;
|
||||
|
||||
@@ -415,45 +420,47 @@ static void newDataOccur(uint32_t refNum) {
|
||||
if (refNum != refNumber) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{*newDSDataAvailableMutex};
|
||||
// Notify all threads
|
||||
++newDSDataAvailableCounter;
|
||||
newDSDataAvailableCond->notify_all();
|
||||
cacheToUpdate->Update();
|
||||
{
|
||||
std::scoped_lock lock{cacheMutex};
|
||||
std::swap(currentCache, cacheToUpdate);
|
||||
currentCache->updated = true;
|
||||
}
|
||||
driverStation->newDataEvents.Wakeup();
|
||||
}
|
||||
|
||||
/*
|
||||
* Call this to initialize the driver station communication. This will properly
|
||||
* handle multiple calls. However note that this CANNOT be called from a library
|
||||
* that interfaces with LabVIEW.
|
||||
*/
|
||||
void HAL_InitializeDriverStation(void) {
|
||||
static std::atomic_bool initialized{false};
|
||||
static wpi::mutex initializeMutex;
|
||||
// Initial check, as if it's true initialization has finished
|
||||
if (initialized) {
|
||||
return;
|
||||
void 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};
|
||||
if (currentCache->updated) {
|
||||
std::swap(currentCache, currentRead);
|
||||
currentCache->updated = false;
|
||||
}
|
||||
newestControlWord = controlWord;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(initializeMutex);
|
||||
// Second check in case another thread was waiting
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
|
||||
driverStation->newDataEvents.Add(handle);
|
||||
}
|
||||
|
||||
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle) {
|
||||
driverStation->newDataEvents.Remove(handle);
|
||||
}
|
||||
|
||||
HAL_Bool HAL_GetOutputsEnabled(void) {
|
||||
return FRC_NetworkCommunication_getWatchdogActive();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
namespace hal {
|
||||
void InitializeDriverStation() {
|
||||
// Set up the occur function internally with NetComm
|
||||
NetCommRPCProxy_SetOccurFuncPointer(newDataOccur);
|
||||
// Set up our occur reference number
|
||||
setNewDataOccurRef(refNumber);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Releases the DS Mutex to allow proper shutdown of any threads that are
|
||||
* waiting on it.
|
||||
*/
|
||||
void HAL_ReleaseDSMutex(void) {
|
||||
newDataOccur(refNumber);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
} // namespace hal
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "FPGACalls.h"
|
||||
#include "HALInitializer.h"
|
||||
#include "HALInternal.h"
|
||||
#include "hal/ChipObject.h"
|
||||
@@ -40,6 +41,7 @@ static uint64_t dsStartTime;
|
||||
using namespace hal;
|
||||
|
||||
namespace hal {
|
||||
void InitializeDriverStation();
|
||||
namespace init {
|
||||
void InitializeHAL() {
|
||||
InitializeCTREPCM();
|
||||
@@ -393,6 +395,11 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int fpgaInit = hal::init::InitializeFPGA();
|
||||
if (fpgaInit != HAL_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hal::init::InitializeHAL();
|
||||
|
||||
hal::init::HAL_IsInitialized.store(true);
|
||||
@@ -425,13 +432,9 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
status = InterruptManager::Initialize(global->getSystemInterface());
|
||||
InterruptManager::Initialize(global->getSystemInterface());
|
||||
|
||||
if (status != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HAL_InitializeDriverStation();
|
||||
hal::InitializeDriverStation();
|
||||
|
||||
dsStartTime = HAL_GetFPGATime(&status);
|
||||
if (status != 0) {
|
||||
|
||||
@@ -6,34 +6,11 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "FPGACalls.h"
|
||||
#include "HALInternal.h"
|
||||
#include "dlfcn.h"
|
||||
#include "hal/Errors.h"
|
||||
|
||||
// Low level FPGA calls
|
||||
using HAL_NiFpga_ReserveIrqContextFunc =
|
||||
NiFpga_Status (*)(NiFpga_Session session, NiFpga_IrqContext* context);
|
||||
|
||||
static HAL_NiFpga_ReserveIrqContextFunc HAL_NiFpga_ReserveIrqContext;
|
||||
|
||||
using HAL_NiFpga_UnreserveIrqContextFunc =
|
||||
NiFpga_Status (*)(NiFpga_Session session, NiFpga_IrqContext context);
|
||||
|
||||
static HAL_NiFpga_UnreserveIrqContextFunc HAL_NiFpga_UnreserveIrqContext;
|
||||
|
||||
using HAL_NiFpga_WaitOnIrqsFunc = NiFpga_Status (*)(
|
||||
NiFpga_Session session, NiFpga_IrqContext context, uint32_t irqs,
|
||||
uint32_t timeout, uint32_t* irqsAsserted, NiFpga_Bool* timedOut);
|
||||
|
||||
static HAL_NiFpga_WaitOnIrqsFunc HAL_NiFpga_WaitOnIrqs;
|
||||
|
||||
using HAL_NiFpga_AcknowledgeIrqsFunc = NiFpga_Status (*)(NiFpga_Session session,
|
||||
uint32_t irqs);
|
||||
|
||||
static HAL_NiFpga_AcknowledgeIrqsFunc HAL_NiFpga_AcknowledgeIrqs;
|
||||
|
||||
static void* NiFpgaLibrary = nullptr;
|
||||
|
||||
using namespace hal;
|
||||
|
||||
InterruptManager& InterruptManager::GetInstance() {
|
||||
@@ -41,37 +18,9 @@ InterruptManager& InterruptManager::GetInstance() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
int32_t InterruptManager::Initialize(tSystemInterface* baseSystem) {
|
||||
void InterruptManager::Initialize(tSystemInterface* baseSystem) {
|
||||
auto& manager = GetInstance();
|
||||
manager.fpgaSession = baseSystem->getHandle();
|
||||
|
||||
NiFpgaLibrary = dlopen("libNiFpga.so", RTLD_LAZY);
|
||||
if (!NiFpgaLibrary) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
HAL_NiFpga_ReserveIrqContext =
|
||||
reinterpret_cast<HAL_NiFpga_ReserveIrqContextFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_ReserveIrqContext"));
|
||||
HAL_NiFpga_UnreserveIrqContext =
|
||||
reinterpret_cast<HAL_NiFpga_UnreserveIrqContextFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_UnreserveIrqContext"));
|
||||
HAL_NiFpga_WaitOnIrqs = reinterpret_cast<HAL_NiFpga_WaitOnIrqsFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_WaitOnIrqs"));
|
||||
HAL_NiFpga_AcknowledgeIrqs = reinterpret_cast<HAL_NiFpga_AcknowledgeIrqsFunc>(
|
||||
dlsym(NiFpgaLibrary, "NiFpgaDll_AcknowledgeIrqs"));
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
if (HAL_NiFpga_ReserveIrqContext == nullptr ||
|
||||
HAL_NiFpga_UnreserveIrqContext == nullptr ||
|
||||
HAL_NiFpga_WaitOnIrqs == nullptr ||
|
||||
HAL_NiFpga_AcknowledgeIrqs == nullptr) {
|
||||
return NO_AVAILABLE_RESOURCES;
|
||||
}
|
||||
|
||||
return HAL_SUCCESS;
|
||||
}
|
||||
|
||||
NiFpga_IrqContext InterruptManager::GetContext() noexcept {
|
||||
|
||||
450
hal/src/main/native/cpp/jni/DriverStationJNI.cpp
Normal file
450
hal/src/main/native/cpp/jni/DriverStationJNI.cpp
Normal file
@@ -0,0 +1,450 @@
|
||||
// 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 <jni.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/jni_util.h>
|
||||
|
||||
#include "HALUtil.h"
|
||||
#include "edu_wpi_first_hal_DriverStationJNI.h"
|
||||
#include "hal/DriverStation.h"
|
||||
#include "hal/FRCUsageReporting.h"
|
||||
#include "hal/HALBase.h"
|
||||
|
||||
// TODO Static asserts
|
||||
|
||||
using namespace hal;
|
||||
using namespace wpi::java;
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: observeUserProgramStarting
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramStarting
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramStarting();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: observeUserProgramDisabled
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramDisabled
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramDisabled();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: observeUserProgramAutonomous
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramAutonomous
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramAutonomous();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: observeUserProgramTeleop
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramTeleop
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramTeleop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: observeUserProgramTest
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramTest
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramTest();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: report
|
||||
* Signature: (IIILjava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_report
|
||||
(JNIEnv* paramEnv, jclass, jint paramResource, jint paramInstanceNumber,
|
||||
jint paramContext, jstring paramFeature)
|
||||
{
|
||||
JStringRef featureStr{paramEnv, paramFeature};
|
||||
jint returnValue = HAL_Report(paramResource, paramInstanceNumber,
|
||||
paramContext, featureStr.c_str());
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: nativeGetControlWord
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_nativeGetControlWord
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
static_assert(sizeof(HAL_ControlWord) == sizeof(jint),
|
||||
"Java int must match the size of control word");
|
||||
HAL_ControlWord controlWord;
|
||||
HAL_GetControlWord(&controlWord);
|
||||
jint retVal = 0;
|
||||
std::memcpy(&retVal, &controlWord, sizeof(HAL_ControlWord));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: nativeGetAllianceStation
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_nativeGetAllianceStation
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
int32_t status = 0;
|
||||
auto allianceStation = HAL_GetAllianceStation(&status);
|
||||
return static_cast<jint>(allianceStation);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getJoystickAxesRaw
|
||||
* Signature: (B[I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickAxesRaw
|
||||
(JNIEnv* env, jclass, jbyte joystickNum, jintArray axesRawArray)
|
||||
{
|
||||
HAL_JoystickAxes axes;
|
||||
HAL_GetJoystickAxes(joystickNum, &axes);
|
||||
|
||||
jsize javaSize = env->GetArrayLength(axesRawArray);
|
||||
if (axes.count > javaSize) {
|
||||
ThrowIllegalArgumentException(
|
||||
env,
|
||||
fmt::format("Native array size larger then passed in java array "
|
||||
"size\nNative Size: {} Java Size: {}",
|
||||
static_cast<int>(axes.count), static_cast<int>(javaSize)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
jint raw[HAL_kMaxJoystickAxes];
|
||||
for (int16_t i = 0; i < axes.count; i++) {
|
||||
raw[i] = axes.raw[i];
|
||||
}
|
||||
env->SetIntArrayRegion(axesRawArray, 0, axes.count, raw);
|
||||
|
||||
return axes.count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getJoystickAxes
|
||||
* Signature: (B[F)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickAxes
|
||||
(JNIEnv* env, jclass, jbyte joystickNum, jfloatArray axesArray)
|
||||
{
|
||||
HAL_JoystickAxes axes;
|
||||
HAL_GetJoystickAxes(joystickNum, &axes);
|
||||
|
||||
jsize javaSize = env->GetArrayLength(axesArray);
|
||||
if (axes.count > javaSize) {
|
||||
ThrowIllegalArgumentException(
|
||||
env,
|
||||
fmt::format("Native array size larger then passed in java array "
|
||||
"size\nNative Size: {} Java Size: {}",
|
||||
static_cast<int>(axes.count), static_cast<int>(javaSize)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
env->SetFloatArrayRegion(axesArray, 0, axes.count, axes.axes);
|
||||
|
||||
return axes.count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getJoystickPOVs
|
||||
* Signature: (B[S)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickPOVs
|
||||
(JNIEnv* env, jclass, jbyte joystickNum, jshortArray povsArray)
|
||||
{
|
||||
HAL_JoystickPOVs povs;
|
||||
HAL_GetJoystickPOVs(joystickNum, &povs);
|
||||
|
||||
jsize javaSize = env->GetArrayLength(povsArray);
|
||||
if (povs.count > javaSize) {
|
||||
ThrowIllegalArgumentException(
|
||||
env,
|
||||
fmt::format("Native array size larger then passed in java array "
|
||||
"size\nNative Size: {} Java Size: {}",
|
||||
static_cast<int>(povs.count), static_cast<int>(javaSize)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
env->SetShortArrayRegion(povsArray, 0, povs.count, povs.povs);
|
||||
|
||||
return povs.count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getAllJoystickData
|
||||
* Signature: ([F[B[S[J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getAllJoystickData
|
||||
(JNIEnv* env, jclass cls, jfloatArray axesArray, jbyteArray rawAxesArray,
|
||||
jshortArray povsArray, jlongArray buttonsAndMetadataArray)
|
||||
{
|
||||
HAL_JoystickAxes axes[HAL_kMaxJoysticks];
|
||||
HAL_JoystickPOVs povs[HAL_kMaxJoysticks];
|
||||
HAL_JoystickButtons buttons[HAL_kMaxJoysticks];
|
||||
|
||||
HAL_GetAllJoystickData(axes, povs, buttons);
|
||||
|
||||
CriticalJFloatArrayRef jAxes(env, axesArray);
|
||||
CriticalJByteArrayRef jRawAxes(env, rawAxesArray);
|
||||
CriticalJShortArrayRef jPovs(env, povsArray);
|
||||
CriticalJLongArrayRef jButtons(env, buttonsAndMetadataArray);
|
||||
|
||||
static_assert(sizeof(jAxes[0]) == sizeof(axes[0].axes[0]));
|
||||
static_assert(sizeof(jRawAxes[0]) == sizeof(axes[0].raw[0]));
|
||||
static_assert(sizeof(jPovs[0]) == sizeof(povs[0].povs[0]));
|
||||
|
||||
for (size_t i = 0; i < HAL_kMaxJoysticks; i++) {
|
||||
std::memcpy(&jAxes[i * HAL_kMaxJoystickAxes], axes[i].axes,
|
||||
sizeof(axes[i].axes));
|
||||
std::memcpy(&jRawAxes[i * HAL_kMaxJoystickAxes], axes[i].raw,
|
||||
sizeof(axes[i].raw));
|
||||
std::memcpy(&jPovs[i * HAL_kMaxJoystickPOVs], povs[i].povs,
|
||||
sizeof(povs[i].povs));
|
||||
jButtons[i * 4] = axes[i].count;
|
||||
jButtons[(i * 4) + 1] = povs[i].count;
|
||||
jButtons[(i * 4) + 2] = buttons[i].count;
|
||||
jButtons[(i * 4) + 3] = buttons[i].buttons;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getJoystickButtons
|
||||
* Signature: (BLjava/lang/Object;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickButtons
|
||||
(JNIEnv* env, jclass, jbyte joystickNum, jobject count)
|
||||
{
|
||||
HAL_JoystickButtons joystickButtons;
|
||||
HAL_GetJoystickButtons(joystickNum, &joystickButtons);
|
||||
jbyte* countPtr =
|
||||
reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(count));
|
||||
*countPtr = joystickButtons.count;
|
||||
return joystickButtons.buttons;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: setJoystickOutputs
|
||||
* Signature: (BISS)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_setJoystickOutputs
|
||||
(JNIEnv*, jclass, jbyte port, jint outputs, jshort leftRumble,
|
||||
jshort rightRumble)
|
||||
{
|
||||
return HAL_SetJoystickOutputs(port, outputs, leftRumble, rightRumble);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getJoystickIsXbox
|
||||
* Signature: (B)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickIsXbox
|
||||
(JNIEnv*, jclass, jbyte port)
|
||||
{
|
||||
return HAL_GetJoystickIsXbox(port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getJoystickType
|
||||
* Signature: (B)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickType
|
||||
(JNIEnv*, jclass, jbyte port)
|
||||
{
|
||||
return HAL_GetJoystickType(port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getJoystickName
|
||||
* Signature: (B)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickName
|
||||
(JNIEnv* env, jclass, jbyte port)
|
||||
{
|
||||
char* joystickName = HAL_GetJoystickName(port);
|
||||
jstring str = MakeJString(env, joystickName);
|
||||
HAL_FreeJoystickName(joystickName);
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getJoystickAxisType
|
||||
* Signature: (BB)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickAxisType
|
||||
(JNIEnv*, jclass, jbyte joystickNum, jbyte axis)
|
||||
{
|
||||
return HAL_GetJoystickAxisType(joystickNum, axis);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getMatchTime
|
||||
* Signature: ()D
|
||||
*/
|
||||
JNIEXPORT jdouble JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getMatchTime
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
int32_t status = 0;
|
||||
return HAL_GetMatchTime(&status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getMatchInfo
|
||||
* Signature: (Ljava/lang/Object;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getMatchInfo
|
||||
(JNIEnv* env, jclass, jobject info)
|
||||
{
|
||||
HAL_MatchInfo matchInfo;
|
||||
auto status = HAL_GetMatchInfo(&matchInfo);
|
||||
if (status == 0) {
|
||||
SetMatchInfoObject(env, info, matchInfo);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: sendError
|
||||
* Signature: (ZIZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_sendError
|
||||
(JNIEnv* env, jclass, jboolean isError, jint errorCode, jboolean isLVCode,
|
||||
jstring details, jstring location, jstring callStack, jboolean printMsg)
|
||||
{
|
||||
JStringRef detailsStr{env, details};
|
||||
JStringRef locationStr{env, location};
|
||||
JStringRef callStackStr{env, callStack};
|
||||
|
||||
jint returnValue =
|
||||
HAL_SendError(isError, errorCode, isLVCode, detailsStr.c_str(),
|
||||
locationStr.c_str(), callStackStr.c_str(), printMsg);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: sendConsoleLine
|
||||
* Signature: (Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_sendConsoleLine
|
||||
(JNIEnv* env, jclass, jstring line)
|
||||
{
|
||||
JStringRef lineStr{env, line};
|
||||
|
||||
jint returnValue = HAL_SendConsoleLine(lineStr.c_str());
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: refreshDSData
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_refreshDSData
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_RefreshDSData();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: provideNewDataEventHandle
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_provideNewDataEventHandle
|
||||
(JNIEnv*, jclass, jint handle)
|
||||
{
|
||||
HAL_ProvideNewDataEventHandle(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: removeNewDataEventHandle
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_removeNewDataEventHandle
|
||||
(JNIEnv*, jclass, jint handle)
|
||||
{
|
||||
HAL_RemoveNewDataEventHandle(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_DriverStationJNI
|
||||
* Method: getOutputsActive
|
||||
* Signature: ()Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_first_hal_DriverStationJNI_getOutputsActive
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
return HAL_GetOutputsEnabled();
|
||||
}
|
||||
} // extern "C"
|
||||
@@ -106,310 +106,6 @@ Java_edu_wpi_first_hal_HAL_simPeriodicAfterNative
|
||||
HAL_SimPeriodicAfter();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: observeUserProgramStarting
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_observeUserProgramStarting
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramStarting();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: observeUserProgramDisabled
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_observeUserProgramDisabled
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramDisabled();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: observeUserProgramAutonomous
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_observeUserProgramAutonomous
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramAutonomous();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: observeUserProgramTeleop
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_observeUserProgramTeleop
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramTeleop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: observeUserProgramTest
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_observeUserProgramTest
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HAL_ObserveUserProgramTest();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: report
|
||||
* Signature: (IIILjava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_report
|
||||
(JNIEnv* paramEnv, jclass, jint paramResource, jint paramInstanceNumber,
|
||||
jint paramContext, jstring paramFeature)
|
||||
{
|
||||
JStringRef featureStr{paramEnv, paramFeature};
|
||||
jint returnValue = HAL_Report(paramResource, paramInstanceNumber,
|
||||
paramContext, featureStr.c_str());
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: nativeGetControlWord
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_nativeGetControlWord
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
static_assert(sizeof(HAL_ControlWord) == sizeof(jint),
|
||||
"Java int must match the size of control word");
|
||||
HAL_ControlWord controlWord;
|
||||
HAL_GetControlWord(&controlWord);
|
||||
jint retVal = 0;
|
||||
std::memcpy(&retVal, &controlWord, sizeof(HAL_ControlWord));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: nativeGetAllianceStation
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_nativeGetAllianceStation
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
int32_t status = 0;
|
||||
auto allianceStation = HAL_GetAllianceStation(&status);
|
||||
return static_cast<jint>(allianceStation);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getJoystickAxes
|
||||
* Signature: (B[F)S
|
||||
*/
|
||||
JNIEXPORT jshort JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getJoystickAxes
|
||||
(JNIEnv* env, jclass, jbyte joystickNum, jfloatArray axesArray)
|
||||
{
|
||||
HAL_JoystickAxes axes;
|
||||
HAL_GetJoystickAxes(joystickNum, &axes);
|
||||
|
||||
jsize javaSize = env->GetArrayLength(axesArray);
|
||||
if (axes.count > javaSize) {
|
||||
ThrowIllegalArgumentException(
|
||||
env,
|
||||
fmt::format("Native array size larger then passed in java array "
|
||||
"size\nNative Size: {} Java Size: {}",
|
||||
static_cast<int>(axes.count), static_cast<int>(javaSize)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
env->SetFloatArrayRegion(axesArray, 0, axes.count, axes.axes);
|
||||
|
||||
return axes.count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getJoystickPOVs
|
||||
* Signature: (B[S)S
|
||||
*/
|
||||
JNIEXPORT jshort JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getJoystickPOVs
|
||||
(JNIEnv* env, jclass, jbyte joystickNum, jshortArray povsArray)
|
||||
{
|
||||
HAL_JoystickPOVs povs;
|
||||
HAL_GetJoystickPOVs(joystickNum, &povs);
|
||||
|
||||
jsize javaSize = env->GetArrayLength(povsArray);
|
||||
if (povs.count > javaSize) {
|
||||
ThrowIllegalArgumentException(
|
||||
env,
|
||||
fmt::format("Native array size larger then passed in java array "
|
||||
"size\nNative Size: {} Java Size: {}",
|
||||
static_cast<int>(povs.count), static_cast<int>(javaSize)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
env->SetShortArrayRegion(povsArray, 0, povs.count, povs.povs);
|
||||
|
||||
return povs.count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getJoystickButtons
|
||||
* Signature: (BLjava/lang/Object;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getJoystickButtons
|
||||
(JNIEnv* env, jclass, jbyte joystickNum, jobject count)
|
||||
{
|
||||
HAL_JoystickButtons joystickButtons;
|
||||
HAL_GetJoystickButtons(joystickNum, &joystickButtons);
|
||||
jbyte* countPtr =
|
||||
reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(count));
|
||||
*countPtr = joystickButtons.count;
|
||||
return joystickButtons.buttons;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: setJoystickOutputs
|
||||
* Signature: (BISS)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_setJoystickOutputs
|
||||
(JNIEnv*, jclass, jbyte port, jint outputs, jshort leftRumble,
|
||||
jshort rightRumble)
|
||||
{
|
||||
return HAL_SetJoystickOutputs(port, outputs, leftRumble, rightRumble);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getJoystickIsXbox
|
||||
* Signature: (B)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getJoystickIsXbox
|
||||
(JNIEnv*, jclass, jbyte port)
|
||||
{
|
||||
return HAL_GetJoystickIsXbox(port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getJoystickType
|
||||
* Signature: (B)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getJoystickType
|
||||
(JNIEnv*, jclass, jbyte port)
|
||||
{
|
||||
return HAL_GetJoystickType(port);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getJoystickName
|
||||
* Signature: (B)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getJoystickName
|
||||
(JNIEnv* env, jclass, jbyte port)
|
||||
{
|
||||
char* joystickName = HAL_GetJoystickName(port);
|
||||
jstring str = MakeJString(env, joystickName);
|
||||
HAL_FreeJoystickName(joystickName);
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getJoystickAxisType
|
||||
* Signature: (BB)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getJoystickAxisType
|
||||
(JNIEnv*, jclass, jbyte joystickNum, jbyte axis)
|
||||
{
|
||||
return HAL_GetJoystickAxisType(joystickNum, axis);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: isNewControlData
|
||||
* Signature: ()Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_isNewControlData
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
return static_cast<jboolean>(HAL_IsNewControlData());
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: waitForDSData
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_waitForDSData
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
HAL_WaitForDSData();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: releaseDSMutex
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_releaseDSMutex
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
HAL_ReleaseDSMutex();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: waitForDSDataTimeout
|
||||
* Signature: (D)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_waitForDSDataTimeout
|
||||
(JNIEnv*, jclass, jdouble timeout)
|
||||
{
|
||||
return static_cast<jboolean>(HAL_WaitForDSDataTimeout(timeout));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getMatchTime
|
||||
* Signature: ()D
|
||||
*/
|
||||
JNIEXPORT jdouble JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getMatchTime
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
int32_t status = 0;
|
||||
return HAL_GetMatchTime(&status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getSystemActive
|
||||
@@ -440,58 +136,6 @@ Java_edu_wpi_first_hal_HAL_getBrownedOut
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getMatchInfo
|
||||
* Signature: (Ljava/lang/Object;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_getMatchInfo
|
||||
(JNIEnv* env, jclass, jobject info)
|
||||
{
|
||||
HAL_MatchInfo matchInfo;
|
||||
auto status = HAL_GetMatchInfo(&matchInfo);
|
||||
if (status == 0) {
|
||||
SetMatchInfoObject(env, info, matchInfo);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: sendError
|
||||
* Signature: (ZIZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_sendError
|
||||
(JNIEnv* env, jclass, jboolean isError, jint errorCode, jboolean isLVCode,
|
||||
jstring details, jstring location, jstring callStack, jboolean printMsg)
|
||||
{
|
||||
JStringRef detailsStr{env, details};
|
||||
JStringRef locationStr{env, location};
|
||||
JStringRef callStackStr{env, callStack};
|
||||
|
||||
jint returnValue =
|
||||
HAL_SendError(isError, errorCode, isLVCode, detailsStr.c_str(),
|
||||
locationStr.c_str(), callStackStr.c_str(), printMsg);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: sendConsoleLine
|
||||
* Signature: (Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_HAL_sendConsoleLine
|
||||
(JNIEnv* env, jclass, jstring line)
|
||||
{
|
||||
JStringRef lineStr{env, line};
|
||||
|
||||
jint returnValue = HAL_SendConsoleLine(lineStr.c_str());
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HAL
|
||||
* Method: getPortWithModule
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <wpi/Synchronization.h>
|
||||
|
||||
#include "hal/DriverStationTypes.h"
|
||||
#include "hal/Types.h"
|
||||
|
||||
@@ -87,6 +89,9 @@ int32_t HAL_GetJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs);
|
||||
int32_t HAL_GetJoystickButtons(int32_t joystickNum,
|
||||
HAL_JoystickButtons* buttons);
|
||||
|
||||
void HAL_GetAllJoystickData(HAL_JoystickAxes* axes, HAL_JoystickPOVs* povs,
|
||||
HAL_JoystickButtons* buttons);
|
||||
|
||||
/**
|
||||
* Retrieves the Joystick Descriptor for particular slot.
|
||||
*
|
||||
@@ -183,6 +188,11 @@ int32_t HAL_SetJoystickOutputs(int32_t joystickNum, int64_t outputs,
|
||||
*/
|
||||
double HAL_GetMatchTime(int32_t* status);
|
||||
|
||||
/**
|
||||
* Gets if outputs are enabled by the control system.
|
||||
*/
|
||||
HAL_Bool HAL_GetOutputsEnabled(void);
|
||||
|
||||
/**
|
||||
* Gets info about a specific match.
|
||||
*
|
||||
@@ -191,44 +201,10 @@ double HAL_GetMatchTime(int32_t* status);
|
||||
*/
|
||||
int32_t HAL_GetMatchInfo(HAL_MatchInfo* info);
|
||||
|
||||
/**
|
||||
* Releases the DS Mutex to allow proper shutdown of any threads that are
|
||||
* waiting on it.
|
||||
*/
|
||||
void HAL_ReleaseDSMutex(void);
|
||||
void HAL_RefreshDSData(void);
|
||||
|
||||
/**
|
||||
* Has a new control packet from the driver station arrived since the last
|
||||
* time this function was called?
|
||||
*
|
||||
* @return true if the control data has been updated since the last call
|
||||
*/
|
||||
HAL_Bool HAL_IsNewControlData(void);
|
||||
|
||||
/**
|
||||
* Waits for the newest DS packet to arrive. Note that this is a blocking call.
|
||||
* Checks if new control data has arrived since the last HAL_WaitForDSData or
|
||||
* HAL_IsNewControlData call. If new data has not arrived, waits for new data
|
||||
* to arrive. Otherwise, returns immediately.
|
||||
*/
|
||||
void HAL_WaitForDSData(void);
|
||||
|
||||
/**
|
||||
* Waits for the newest DS packet to arrive. If timeout is <= 0, this will wait
|
||||
* forever. Otherwise, it will wait until either a new packet, or the timeout
|
||||
* time has passed.
|
||||
*
|
||||
* @param[in] timeout timeout in seconds
|
||||
* @return true for new data, false for timeout
|
||||
*/
|
||||
HAL_Bool HAL_WaitForDSDataTimeout(double timeout);
|
||||
|
||||
/**
|
||||
* Initializes the driver station communication. This will properly
|
||||
* handle multiple calls. However note that this CANNOT be called from a library
|
||||
* that interfaces with LabVIEW.
|
||||
*/
|
||||
void HAL_InitializeDriverStation(void);
|
||||
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle);
|
||||
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle);
|
||||
|
||||
/**
|
||||
* Sets the program starting flag in the DS.
|
||||
|
||||
@@ -69,6 +69,7 @@ HAL_ENUM(HAL_MatchType) {
|
||||
struct HAL_JoystickAxes {
|
||||
int16_t count;
|
||||
float axes[HAL_kMaxJoystickAxes];
|
||||
uint8_t raw[HAL_kMaxJoystickAxes];
|
||||
};
|
||||
typedef struct HAL_JoystickAxes HAL_JoystickAxes;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace hal {
|
||||
class InterruptManager {
|
||||
public:
|
||||
static InterruptManager& GetInstance();
|
||||
static int32_t Initialize(tSystemInterface* baseSystem);
|
||||
static void Initialize(tSystemInterface* baseSystem);
|
||||
|
||||
NiFpga_IrqContext GetContext() noexcept;
|
||||
void ReleaseContext(NiFpga_IrqContext context) noexcept;
|
||||
|
||||
@@ -14,32 +14,78 @@
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/EventVector.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "HALInitializer.h"
|
||||
#include "hal/Errors.h"
|
||||
#include "hal/cpp/fpga_clock.h"
|
||||
#include "hal/simulation/MockHooks.h"
|
||||
#include "mockdata/DriverStationDataInternal.h"
|
||||
|
||||
static wpi::mutex msgMutex;
|
||||
static wpi::condition_variable* newDSDataAvailableCond;
|
||||
static wpi::mutex newDSDataAvailableMutex;
|
||||
static int newDSDataAvailableCounter{0};
|
||||
static std::atomic_bool isFinalized{false};
|
||||
static std::atomic<HALSIM_SendErrorHandler> sendErrorHandler{nullptr};
|
||||
static std::atomic<HALSIM_SendConsoleLineHandler> sendConsoleLineHandler{
|
||||
nullptr};
|
||||
|
||||
using namespace hal;
|
||||
|
||||
static constexpr int kJoystickPorts = 6;
|
||||
|
||||
namespace {
|
||||
struct JoystickDataCache {
|
||||
JoystickDataCache() { std::memset(this, 0, sizeof(*this)); }
|
||||
void Update();
|
||||
|
||||
HAL_JoystickAxes axes[kJoystickPorts];
|
||||
HAL_JoystickPOVs povs[kJoystickPorts];
|
||||
HAL_JoystickButtons buttons[kJoystickPorts];
|
||||
HAL_AllianceStationID allianceStation;
|
||||
double matchTime;
|
||||
bool updated;
|
||||
};
|
||||
static_assert(std::is_standard_layout_v<JoystickDataCache>);
|
||||
// static_assert(std::is_trivial_v<JoystickDataCache>);
|
||||
|
||||
static std::atomic_bool gShutdown{false};
|
||||
|
||||
struct FRCDriverStation {
|
||||
~FRCDriverStation() { gShutdown = true; }
|
||||
wpi::EventVector newDataEvents;
|
||||
wpi::mutex cacheMutex;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void JoystickDataCache::Update() {
|
||||
for (int i = 0; i < kJoystickPorts; i++) {
|
||||
SimDriverStationData->GetJoystickAxes(i, &axes[i]);
|
||||
SimDriverStationData->GetJoystickPOVs(i, &povs[i]);
|
||||
SimDriverStationData->GetJoystickButtons(i, &buttons[i]);
|
||||
}
|
||||
allianceStation = SimDriverStationData->allianceStationId;
|
||||
matchTime = SimDriverStationData->matchTime;
|
||||
}
|
||||
|
||||
#define CHECK_JOYSTICK_NUMBER(stickNum) \
|
||||
if ((stickNum) < 0 || (stickNum) >= HAL_kMaxJoysticks) \
|
||||
return PARAMETER_OUT_OF_RANGE
|
||||
|
||||
static HAL_ControlWord newestControlWord;
|
||||
static JoystickDataCache caches[3];
|
||||
static JoystickDataCache* currentRead = &caches[0];
|
||||
static JoystickDataCache* currentCache = &caches[1];
|
||||
static JoystickDataCache* cacheToUpdate = &caches[2];
|
||||
|
||||
static ::FRCDriverStation* driverStation;
|
||||
|
||||
namespace hal::init {
|
||||
void InitializeDriverStation() {
|
||||
static wpi::condition_variable nddaC;
|
||||
newDSDataAvailableCond = &nddaC;
|
||||
static FRCDriverStation ds;
|
||||
driverStation = &ds;
|
||||
}
|
||||
} // namespace hal::init
|
||||
|
||||
using namespace hal;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void HALSIM_SetSendError(HALSIM_SendErrorHandler handler) {
|
||||
@@ -122,39 +168,67 @@ int32_t HAL_SendConsoleLine(const char* line) {
|
||||
}
|
||||
|
||||
int32_t HAL_GetControlWord(HAL_ControlWord* controlWord) {
|
||||
std::memset(controlWord, 0, sizeof(HAL_ControlWord));
|
||||
controlWord->enabled = SimDriverStationData->enabled;
|
||||
controlWord->autonomous = SimDriverStationData->autonomous;
|
||||
controlWord->test = SimDriverStationData->test;
|
||||
controlWord->eStop = SimDriverStationData->eStop;
|
||||
controlWord->fmsAttached = SimDriverStationData->fmsAttached;
|
||||
controlWord->dsAttached = SimDriverStationData->dsAttached;
|
||||
if (gShutdown) {
|
||||
return INCOMPATIBLE_STATE;
|
||||
}
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
*controlWord = newestControlWord;
|
||||
return 0;
|
||||
}
|
||||
|
||||
HAL_AllianceStationID HAL_GetAllianceStation(int32_t* status) {
|
||||
*status = 0;
|
||||
return SimDriverStationData->allianceStationId;
|
||||
if (gShutdown) {
|
||||
return HAL_AllianceStationID_kRed1;
|
||||
}
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
return currentRead->allianceStation;
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickAxes(int32_t joystickNum, HAL_JoystickAxes* axes) {
|
||||
SimDriverStationData->GetJoystickAxes(joystickNum, axes);
|
||||
if (gShutdown) {
|
||||
return INCOMPATIBLE_STATE;
|
||||
}
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
*axes = currentRead->axes[joystickNum];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs) {
|
||||
SimDriverStationData->GetJoystickPOVs(joystickNum, povs);
|
||||
if (gShutdown) {
|
||||
return INCOMPATIBLE_STATE;
|
||||
}
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
*povs = currentRead->povs[joystickNum];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickButtons(int32_t joystickNum,
|
||||
HAL_JoystickButtons* buttons) {
|
||||
SimDriverStationData->GetJoystickButtons(joystickNum, buttons);
|
||||
if (gShutdown) {
|
||||
return INCOMPATIBLE_STATE;
|
||||
}
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
*buttons = currentRead->buttons[joystickNum];
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HAL_GetAllJoystickData(HAL_JoystickAxes* axes, HAL_JoystickPOVs* povs,
|
||||
HAL_JoystickButtons* buttons) {
|
||||
if (gShutdown) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
std::memcpy(axes, currentRead->axes, sizeof(currentRead->axes));
|
||||
std::memcpy(povs, currentRead->povs, sizeof(currentRead->povs));
|
||||
std::memcpy(buttons, currentRead->buttons, sizeof(currentRead->buttons));
|
||||
}
|
||||
|
||||
int32_t HAL_GetJoystickDescriptor(int32_t joystickNum,
|
||||
HAL_JoystickDescriptor* desc) {
|
||||
CHECK_JOYSTICK_NUMBER(joystickNum);
|
||||
SimDriverStationData->GetJoystickDescriptor(joystickNum, desc);
|
||||
return 0;
|
||||
}
|
||||
@@ -196,7 +270,11 @@ int32_t HAL_SetJoystickOutputs(int32_t joystickNum, int64_t outputs,
|
||||
}
|
||||
|
||||
double HAL_GetMatchTime(int32_t* status) {
|
||||
return SimDriverStationData->matchTime;
|
||||
if (gShutdown) {
|
||||
return 0;
|
||||
}
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
return currentRead->matchTime;
|
||||
}
|
||||
|
||||
int32_t HAL_GetMatchInfo(HAL_MatchInfo* info) {
|
||||
@@ -224,103 +302,66 @@ void HAL_ObserveUserProgramTest(void) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
static int& GetThreadLocalLastCount() {
|
||||
// There is a rollover error condition here. At Packet# = n * (uintmax), this
|
||||
// will return false when instead it should return true. However, this at a
|
||||
// 20ms rate occurs once every 2.7 years of DS connected runtime, so not
|
||||
// worth the cycles to check.
|
||||
thread_local int lastCount{0};
|
||||
return lastCount;
|
||||
}
|
||||
|
||||
HAL_Bool HAL_IsNewControlData(void) {
|
||||
std::scoped_lock lock(newDSDataAvailableMutex);
|
||||
int& lastCount = GetThreadLocalLastCount();
|
||||
int currentCount = newDSDataAvailableCounter;
|
||||
if (lastCount == currentCount) {
|
||||
return false;
|
||||
}
|
||||
lastCount = currentCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HAL_WaitForDSData(void) {
|
||||
HAL_WaitForDSDataTimeout(0);
|
||||
}
|
||||
|
||||
HAL_Bool HAL_WaitForDSDataTimeout(double timeout) {
|
||||
std::unique_lock lock(newDSDataAvailableMutex);
|
||||
int& lastCount = GetThreadLocalLastCount();
|
||||
int currentCount = newDSDataAvailableCounter;
|
||||
if (lastCount != currentCount) {
|
||||
lastCount = currentCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isFinalized.load()) {
|
||||
return false;
|
||||
}
|
||||
auto timeoutTime =
|
||||
std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
|
||||
|
||||
while (newDSDataAvailableCounter == currentCount) {
|
||||
if (timeout > 0) {
|
||||
auto timedOut = newDSDataAvailableCond->wait_until(lock, timeoutTime);
|
||||
if (timedOut == std::cv_status::timeout) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
newDSDataAvailableCond->wait(lock);
|
||||
}
|
||||
}
|
||||
lastCount = newDSDataAvailableCounter;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Constant number to be used for our occur handle
|
||||
constexpr int32_t refNumber = 42;
|
||||
|
||||
static int32_t newDataOccur(uint32_t refNum) {
|
||||
// Since we could get other values, require our specific handle
|
||||
// to signal our threads
|
||||
if (refNum != refNumber) {
|
||||
return 0;
|
||||
}
|
||||
SimDriverStationData->CallNewDataCallbacks();
|
||||
std::scoped_lock lock(newDSDataAvailableMutex);
|
||||
// Nofify all threads
|
||||
newDSDataAvailableCounter++;
|
||||
newDSDataAvailableCond->notify_all();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HAL_InitializeDriverStation(void) {
|
||||
hal::init::CheckInit();
|
||||
static std::atomic_bool initialized{false};
|
||||
static wpi::mutex initializeMutex;
|
||||
// Initial check, as if it's true initialization has finished
|
||||
if (initialized) {
|
||||
void HAL_RefreshDSData(void) {
|
||||
if (gShutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(initializeMutex);
|
||||
// Second check in case another thread was waiting
|
||||
if (initialized) {
|
||||
return;
|
||||
HAL_ControlWord controlWord;
|
||||
std::memset(&controlWord, 0, sizeof(controlWord));
|
||||
controlWord.enabled = SimDriverStationData->enabled;
|
||||
controlWord.autonomous = SimDriverStationData->autonomous;
|
||||
controlWord.test = SimDriverStationData->test;
|
||||
controlWord.eStop = SimDriverStationData->eStop;
|
||||
controlWord.fmsAttached = SimDriverStationData->fmsAttached;
|
||||
controlWord.dsAttached = SimDriverStationData->dsAttached;
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
if (currentCache->updated) {
|
||||
std::swap(currentCache, currentRead);
|
||||
currentCache->updated = false;
|
||||
}
|
||||
|
||||
SimDriverStationData->ResetData();
|
||||
|
||||
std::atexit([]() {
|
||||
isFinalized.store(true);
|
||||
HAL_ReleaseDSMutex();
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
newestControlWord = controlWord;
|
||||
}
|
||||
|
||||
void HAL_ReleaseDSMutex(void) {
|
||||
newDataOccur(refNumber);
|
||||
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
|
||||
if (gShutdown) {
|
||||
return;
|
||||
}
|
||||
driverStation->newDataEvents.Add(handle);
|
||||
}
|
||||
|
||||
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle) {
|
||||
if (gShutdown) {
|
||||
return;
|
||||
}
|
||||
driverStation->newDataEvents.Remove(handle);
|
||||
}
|
||||
|
||||
HAL_Bool HAL_GetOutputsEnabled(void) {
|
||||
if (gShutdown) {
|
||||
return false;
|
||||
}
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
return newestControlWord.enabled && newestControlWord.dsAttached;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
namespace hal {
|
||||
void NewDriverStationData() {
|
||||
if (gShutdown) {
|
||||
return;
|
||||
}
|
||||
cacheToUpdate->Update();
|
||||
{
|
||||
std::scoped_lock lock{driverStation->cacheMutex};
|
||||
std::swap(currentCache, cacheToUpdate);
|
||||
currentCache->updated = true;
|
||||
}
|
||||
driverStation->newDataEvents.Wakeup();
|
||||
SimDriverStationData->CallNewDataCallbacks();
|
||||
}
|
||||
|
||||
void InitializeDriverStation() {
|
||||
SimDriverStationData->ResetData();
|
||||
}
|
||||
} // namespace hal
|
||||
|
||||
@@ -57,6 +57,10 @@ static std::vector<std::pair<void*, void (*)(void*)>> gOnShutdown;
|
||||
static SimPeriodicCallbackRegistry gSimPeriodicBefore;
|
||||
static SimPeriodicCallbackRegistry gSimPeriodicAfter;
|
||||
|
||||
namespace hal {
|
||||
void InitializeDriverStation();
|
||||
} // namespace hal
|
||||
|
||||
namespace hal::init {
|
||||
void InitializeHAL() {
|
||||
InitializeAccelerometerData();
|
||||
@@ -335,7 +339,7 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
|
||||
hal::init::HAL_IsInitialized.store(true);
|
||||
|
||||
hal::RestartTiming();
|
||||
HAL_InitializeDriverStation();
|
||||
hal::InitializeDriverStation();
|
||||
|
||||
initialized = true;
|
||||
|
||||
|
||||
@@ -216,8 +216,12 @@ void DriverStationData::CallNewDataCallbacks() {
|
||||
m_newDataCallbacks(&empty);
|
||||
}
|
||||
|
||||
namespace hal {
|
||||
void NewDriverStationData();
|
||||
} // namespace hal
|
||||
|
||||
void DriverStationData::NotifyNewData() {
|
||||
HAL_ReleaseDSMutex();
|
||||
hal::NewDriverStationData();
|
||||
}
|
||||
|
||||
void DriverStationData::SetJoystickButton(int32_t stick, int32_t button,
|
||||
|
||||
@@ -61,6 +61,9 @@ TEST(DriverStationTest, Joystick) {
|
||||
HALSIM_SetJoystickPOVs(joystickUnderTest, &set_povs);
|
||||
HALSIM_SetJoystickButtons(joystickUnderTest, &set_buttons);
|
||||
|
||||
HALSIM_NotifyDriverStationNewData();
|
||||
HAL_RefreshDSData();
|
||||
|
||||
// Check the set values
|
||||
HAL_GetJoystickAxes(joystickUnderTest, &axes);
|
||||
HAL_GetJoystickPOVs(joystickUnderTest, &povs);
|
||||
@@ -89,6 +92,9 @@ TEST(DriverStationTest, Joystick) {
|
||||
|
||||
// Reset
|
||||
HALSIM_ResetDriverStationData();
|
||||
HALSIM_NotifyDriverStationNewData();
|
||||
HAL_RefreshDSData();
|
||||
|
||||
for (int joystickNum = 0; joystickNum < 6; ++joystickNum) {
|
||||
HAL_GetJoystickAxes(joystickNum, &axes);
|
||||
HAL_GetJoystickPOVs(joystickNum, &povs);
|
||||
|
||||
@@ -58,7 +58,7 @@ def simProjects = ['halsim_gui']
|
||||
deploy {
|
||||
targets {
|
||||
roborio(RemoteTarget) {
|
||||
directory = '/home/admin'
|
||||
directory = '/home/lvuser'
|
||||
maxChannels = 4
|
||||
locations {
|
||||
ssh(SshDeployLocation) {
|
||||
@@ -78,7 +78,8 @@ deploy {
|
||||
artifacts {
|
||||
all {
|
||||
predeploy << { ctx ->
|
||||
ctx.execute('/usr/local/frc/bin/frcKillRobot.sh -t')
|
||||
ctx.execute('. /etc/profile.d/natinst-path.sh; /usr/local/frc/bin/frcKillRobot.sh -t 2> /dev/null')
|
||||
ctx.execute("sed -i -e 's/\"exec /\"/' /usr/local/frc/bin/frcRunRobot.sh")
|
||||
}
|
||||
postdeploy << { ctx ->
|
||||
ctx.execute("sync")
|
||||
@@ -92,6 +93,9 @@ deploy {
|
||||
excludes.add('**/*.so.debug')
|
||||
excludes.add('**/*.so.*.debug')
|
||||
postdeploy << { ctx ->
|
||||
ctx.execute("echo '/home/lvuser/myRobotCpp' > /home/lvuser/robotCommand")
|
||||
ctx.execute("chmod +x /home/lvuser/robotCommand; chown lvuser /home/lvuser/robotCommand")
|
||||
ctx.execute("setcap cap_sys_nice+eip \"/home/lvuser/myRobotCpp\"")
|
||||
ctx.execute('chmod +x myRobotCpp')
|
||||
}
|
||||
}
|
||||
@@ -99,18 +103,28 @@ deploy {
|
||||
myRobotCppStatic(NativeExecutableArtifact) {
|
||||
libraryDirectory = '/usr/local/frc/third-party/lib'
|
||||
postdeploy << { ctx ->
|
||||
ctx.execute("echo '/home/lvuser/myRobotCppStatic' > /home/lvuser/robotCommand")
|
||||
ctx.execute("chmod +x /home/lvuser/robotCommand; chown lvuser /home/lvuser/robotCommand")
|
||||
ctx.execute("setcap cap_sys_nice+eip \"/home/lvuser/myRobotCppStatic\"")
|
||||
ctx.execute('chmod +x myRobotCppStatic')
|
||||
}
|
||||
}
|
||||
|
||||
myRobotCppJava(NativeExecutableArtifact) {
|
||||
libraryDirectory = '/usr/local/frc/third-party/lib'
|
||||
def excludes = getLibraryFilter().getExcludes()
|
||||
excludes.add('**/*.so.debug')
|
||||
excludes.add('**/*.so.*.debug')
|
||||
}
|
||||
|
||||
jre(WPIJREArtifact) {
|
||||
}
|
||||
|
||||
myRobotJava(JavaArtifact) {
|
||||
jarTask = shadowJar
|
||||
postdeploy << { ctx ->
|
||||
ctx.execute("echo '/usr/local/frc/JRE/bin/java -XX:+UseConcMarkSweepGC -Djava.library.path=/usr/local/frc/third-party/lib -Djava.lang.invoke.stringConcat=BC_SB -jar /home/admin/myRobot-all.jar' > /home/admin/myRobotJavaRun")
|
||||
ctx.execute("chmod +x /home/admin/myRobotJavaRun; chown lvuser /home/admin/myRobotJavaRun")
|
||||
ctx.execute("echo '/usr/local/frc/JRE/bin/java -XX:+UseG1GC -XX:MaxGCPauseMillis=1 -XX:GCTimeRatio=1 -Djava.library.path=/usr/local/frc/third-party/lib -Djava.lang.invoke.stringConcat=BC_SB -jar /home/lvuser/myRobot-all.jar' > /home/lvuser/robotCommand")
|
||||
ctx.execute("chmod +x /home/lvuser/robotCommand; chown lvuser /home/lvuser/robotCommand")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +136,7 @@ tasks.register('deployJava') {
|
||||
try {
|
||||
dependsOn tasks.named('deployjreroborio')
|
||||
dependsOn tasks.named('deploymyRobotJavaroborio')
|
||||
dependsOn tasks.named('deploymyRobotCpproborio') // Deploying shared C++ is how to get the Java shared libraries.
|
||||
dependsOn tasks.named('deploymyRobotCppJavaroborio') // Deploying shared C++ is how to get the Java shared libraries.
|
||||
} catch (ignored) {
|
||||
}
|
||||
}
|
||||
@@ -161,6 +175,7 @@ model {
|
||||
if (binary.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
if (binary.buildType.name == 'debug') {
|
||||
deploy.targets.roborio.artifacts.myRobotCpp.binary = binary
|
||||
deploy.targets.roborio.artifacts.myRobotCppJava.binary = binary
|
||||
}
|
||||
}
|
||||
lib project: ':wpilibNewCommands', library: 'wpilibNewCommands', linkage: 'shared'
|
||||
|
||||
@@ -35,6 +35,7 @@ set_property(TARGET ntcore PROPERTY FOLDER "libraries")
|
||||
|
||||
install(TARGETS ntcore EXPORT ntcore DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/ntcore")
|
||||
install(DIRECTORY ${WPILIB_BINARY_DIR}/ntcore/generated/main/native/include/ DESTINATION "${include_dest}/ntcore")
|
||||
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (ntcore_config_dir ${wpilib_dest})
|
||||
|
||||
@@ -51,12 +51,10 @@ void bench() {
|
||||
// set up instances
|
||||
auto client = nt::CreateInstance();
|
||||
auto server = nt::CreateInstance();
|
||||
nt::SetNetworkIdentity(server, "server");
|
||||
nt::SetNetworkIdentity(client, "client");
|
||||
|
||||
// connect client and server
|
||||
nt::StartServer(server, "bench.json", "127.0.0.1", 0, 10000);
|
||||
nt::StartClient4(client);
|
||||
nt::StartClient4(client, "client");
|
||||
nt::SetServer(client, "127.0.0.1", 10000);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
import edu.wpi.first.util.concurrent.Event;
|
||||
import edu.wpi.first.util.datalog.DataLog;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -30,6 +33,7 @@ import java.util.function.Consumer;
|
||||
* kept to the NetworkTableInstance returned by this function to keep it from being garbage
|
||||
* collected.
|
||||
*/
|
||||
@SuppressWarnings("PMD.CouplingBetweenObjects")
|
||||
public final class NetworkTableInstance implements AutoCloseable {
|
||||
/**
|
||||
* Client/server mode flag values (as returned by {@link #getNetworkMode()}). This is a bitmask.
|
||||
@@ -62,6 +66,7 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_owned && m_handle != 0) {
|
||||
m_listeners.close();
|
||||
NetworkTablesJNI.destroyInstance(m_handle);
|
||||
}
|
||||
}
|
||||
@@ -350,116 +355,320 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
* Callback Creation Functions
|
||||
*/
|
||||
|
||||
private final ReentrantLock m_connectionListenerLock = new ReentrantLock();
|
||||
private final Map<Integer, Consumer<ConnectionNotification>> m_connectionListeners =
|
||||
new HashMap<>();
|
||||
private int m_connectionListenerPoller;
|
||||
private static class ListenerStorage implements AutoCloseable {
|
||||
private final ReentrantLock m_lock = new ReentrantLock();
|
||||
private final Map<Integer, Consumer<NetworkTableEvent>> m_listeners = new HashMap<>();
|
||||
private Thread m_thread;
|
||||
private int m_poller;
|
||||
private boolean m_waitQueue;
|
||||
private final Event m_waitQueueEvent = new Event();
|
||||
private final Condition m_waitQueueCond = m_lock.newCondition();
|
||||
private final NetworkTableInstance m_inst;
|
||||
|
||||
@SuppressWarnings("PMD.AvoidCatchingThrowable")
|
||||
private void startConnectionListenerThread() {
|
||||
var connectionListenerThread =
|
||||
new Thread(
|
||||
() -> {
|
||||
boolean wasInterrupted = false;
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
WPIUtilJNI.waitForObject(m_connectionListenerPoller);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
// don't try to destroy poller, as its handle is likely no longer valid
|
||||
wasInterrupted = true;
|
||||
break;
|
||||
}
|
||||
ConnectionNotification[] events =
|
||||
NetworkTablesJNI.readConnectionListenerQueue(this, m_connectionListenerPoller);
|
||||
for (ConnectionNotification event : events) {
|
||||
Consumer<ConnectionNotification> listener;
|
||||
m_connectionListenerLock.lock();
|
||||
ListenerStorage(NetworkTableInstance inst) {
|
||||
m_inst = inst;
|
||||
}
|
||||
|
||||
int add(String[] prefixes, int mask, Consumer<NetworkTableEvent> listener) {
|
||||
m_lock.lock();
|
||||
try {
|
||||
if (m_poller == 0) {
|
||||
m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
int h = NetworkTablesJNI.addListener(m_poller, prefixes, mask);
|
||||
m_listeners.put(h, listener);
|
||||
return h;
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
int add(int handle, int mask, Consumer<NetworkTableEvent> listener) {
|
||||
m_lock.lock();
|
||||
try {
|
||||
if (m_poller == 0) {
|
||||
m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
int h = NetworkTablesJNI.addListener(m_poller, handle, mask);
|
||||
m_listeners.put(h, listener);
|
||||
return h;
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> listener) {
|
||||
m_lock.lock();
|
||||
try {
|
||||
if (m_poller == 0) {
|
||||
m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
int h = NetworkTablesJNI.addLogger(m_poller, minLevel, maxLevel);
|
||||
m_listeners.put(h, listener);
|
||||
return h;
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void remove(int listener) {
|
||||
m_lock.lock();
|
||||
try {
|
||||
m_listeners.remove(listener);
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
NetworkTablesJNI.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (m_poller != 0) {
|
||||
NetworkTablesJNI.destroyListenerPoller(m_poller);
|
||||
}
|
||||
m_poller = 0;
|
||||
}
|
||||
|
||||
private void startThread() {
|
||||
m_thread =
|
||||
new Thread(
|
||||
() -> {
|
||||
boolean wasInterrupted = false;
|
||||
int[] handles = new int[] { m_poller, m_waitQueueEvent.getHandle() };
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
listener = m_connectionListeners.get(event.listener);
|
||||
} finally {
|
||||
m_connectionListenerLock.unlock();
|
||||
}
|
||||
if (listener != null) {
|
||||
WPIUtilJNI.waitForObjects(handles);
|
||||
} catch (InterruptedException ex) {
|
||||
m_lock.lock();
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable throwable) {
|
||||
System.err.println(
|
||||
"Unhandled exception during connection listener callback: "
|
||||
+ throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
if (m_waitQueue) {
|
||||
m_waitQueue = false;
|
||||
m_waitQueueCond.signalAll();
|
||||
}
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
// don't try to destroy poller, as its handle is likely no longer valid
|
||||
wasInterrupted = true;
|
||||
break;
|
||||
}
|
||||
for (NetworkTableEvent event :
|
||||
NetworkTablesJNI.readListenerQueue(m_inst, m_poller)) {
|
||||
Consumer<NetworkTableEvent> listener;
|
||||
m_lock.lock();
|
||||
try {
|
||||
listener = m_listeners.get(event.listener);
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable throwable) {
|
||||
System.err.println(
|
||||
"Unhandled exception during listener callback: "
|
||||
+ throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
m_lock.lock();
|
||||
try {
|
||||
if (m_waitQueue) {
|
||||
m_waitQueue = false;
|
||||
m_waitQueueCond.signalAll();
|
||||
}
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
m_connectionListenerLock.lock();
|
||||
try {
|
||||
if (!wasInterrupted) {
|
||||
NetworkTablesJNI.destroyConnectionListenerPoller(m_connectionListenerPoller);
|
||||
m_lock.lock();
|
||||
try {
|
||||
if (!wasInterrupted) {
|
||||
NetworkTablesJNI.destroyListenerPoller(m_poller);
|
||||
}
|
||||
m_poller = 0;
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
m_connectionListenerPoller = 0;
|
||||
} finally {
|
||||
m_connectionListenerLock.unlock();
|
||||
}
|
||||
},
|
||||
"NTConnectionListener");
|
||||
connectionListenerThread.setDaemon(true);
|
||||
connectionListenerThread.start();
|
||||
}
|
||||
},
|
||||
"NTListener");
|
||||
m_thread.setDaemon(true);
|
||||
m_thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a connection listener.
|
||||
*
|
||||
* @param listener Listener to add
|
||||
* @param immediateNotify Notify listener of all existing connections
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addConnectionListener(
|
||||
Consumer<ConnectionNotification> listener, boolean immediateNotify) {
|
||||
m_connectionListenerLock.lock();
|
||||
try {
|
||||
if (m_connectionListenerPoller == 0) {
|
||||
m_connectionListenerPoller = NetworkTablesJNI.createConnectionListenerPoller(m_handle);
|
||||
startConnectionListenerThread();
|
||||
boolean waitForQueue(double timeout) {
|
||||
m_lock.lock();
|
||||
try {
|
||||
if (m_poller != 0) {
|
||||
m_waitQueue = true;
|
||||
m_waitQueueEvent.set();
|
||||
while (m_waitQueue) {
|
||||
try {
|
||||
if (timeout < 0) {
|
||||
m_waitQueueCond.await();
|
||||
} else {
|
||||
return m_waitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
m_lock.unlock();
|
||||
}
|
||||
int handle =
|
||||
NetworkTablesJNI.addPolledConnectionListener(m_connectionListenerPoller, immediateNotify);
|
||||
m_connectionListeners.put(handle, listener);
|
||||
return handle;
|
||||
} finally {
|
||||
m_connectionListenerLock.unlock();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private ListenerStorage m_listeners = new ListenerStorage(this);
|
||||
|
||||
/**
|
||||
* Remove a connection listener.
|
||||
*
|
||||
* @param listener Listener handle to remove
|
||||
*/
|
||||
public void removeConnectionListener(int listener) {
|
||||
m_connectionListenerLock.lock();
|
||||
try {
|
||||
m_connectionListeners.remove(listener);
|
||||
} finally {
|
||||
m_connectionListenerLock.unlock();
|
||||
public void removeListener(int listener) {
|
||||
m_listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the listener queue to be empty. This is primarily useful for deterministic
|
||||
* testing. This blocks until either the listener queue is empty (e.g. there are no
|
||||
* more events that need to be passed along to callbacks or poll queues) or the timeout expires.
|
||||
*
|
||||
* @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to
|
||||
* block indefinitely
|
||||
* @return False if timed out, otherwise true.
|
||||
*/
|
||||
public boolean waitForListenerQueue(double timeout) {
|
||||
return m_listeners.waitForQueue(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a connection listener. The callback function is called asynchronously on a separate
|
||||
* thread, so it's important to use synchronization or atomics when accessing any shared state
|
||||
* from the callback function.
|
||||
*
|
||||
* @param immediateNotify Notify listener of all existing connections
|
||||
* @param listener Listener to add
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addConnectionListener(
|
||||
boolean immediateNotify, Consumer<NetworkTableEvent> listener) {
|
||||
return m_listeners.add(m_handle,
|
||||
NetworkTableEvent.kConnection | (immediateNotify ? NetworkTableEvent.kImmediate : 0),
|
||||
listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for changes on a particular topic. The callback function is called
|
||||
* asynchronously on a separate thread, so it's important to use synchronization or atomics when
|
||||
* accessing any shared state from the callback function.
|
||||
*
|
||||
* <p>This creates a corresponding internal subscriber with the lifetime of the
|
||||
* listener.
|
||||
*
|
||||
* @param topic Topic
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(
|
||||
Topic topic, int eventMask, Consumer<NetworkTableEvent> listener) {
|
||||
if (topic.getInstance().getHandle() != m_handle) {
|
||||
throw new IllegalArgumentException("topic is not from this instance");
|
||||
}
|
||||
NetworkTablesJNI.removeConnectionListener(listener);
|
||||
return m_listeners.add(topic.getHandle(), eventMask, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for changes on a subscriber. The callback function is called
|
||||
* asynchronously on a separate thread, so it's important to use synchronization or atomics when
|
||||
* accessing any shared state from the callback function. This does NOT keep the subscriber
|
||||
* active.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(
|
||||
Subscriber subscriber, int eventMask, Consumer<NetworkTableEvent> listener) {
|
||||
if (subscriber.getTopic().getInstance().getHandle() != m_handle) {
|
||||
throw new IllegalArgumentException("subscriber is not from this instance");
|
||||
}
|
||||
return m_listeners.add(subscriber.getHandle(), eventMask, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for changes on a subscriber. The callback function is called
|
||||
* asynchronously on a separate thread, so it's important to use synchronization or atomics when
|
||||
* accessing any shared state from the callback function. This does NOT keep the subscriber
|
||||
* active.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(
|
||||
MultiSubscriber subscriber, int eventMask, Consumer<NetworkTableEvent> listener) {
|
||||
if (subscriber.getInstance().getHandle() != m_handle) {
|
||||
throw new IllegalArgumentException("subscriber is not from this instance");
|
||||
}
|
||||
return m_listeners.add(subscriber.getHandle(), eventMask, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for changes on an entry. The callback function is called
|
||||
* asynchronously on a separate thread, so it's important to use synchronization or atomics when
|
||||
* accessing any shared state from the callback function.
|
||||
*
|
||||
* @param entry Entry
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(
|
||||
NetworkTableEntry entry, int eventMask, Consumer<NetworkTableEvent> listener) {
|
||||
if (entry.getTopic().getInstance().getHandle() != m_handle) {
|
||||
throw new IllegalArgumentException("entry is not from this instance");
|
||||
}
|
||||
return m_listeners.add(entry.getHandle(), eventMask, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener for changes to topics with names that start with any of the given
|
||||
* prefixes. The callback function is called asynchronously on a separate thread, so it's
|
||||
* important to use synchronization or atomics when accessing any shared state from the callback
|
||||
* function.
|
||||
*
|
||||
* <p>This creates a corresponding internal subscriber with the lifetime of the
|
||||
* listener.
|
||||
*
|
||||
* @param prefixes Topic name string prefixes
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(
|
||||
String[] prefixes,
|
||||
int eventMask,
|
||||
Consumer<NetworkTableEvent> listener) {
|
||||
return m_listeners.add(prefixes, eventMask, listener);
|
||||
}
|
||||
|
||||
/*
|
||||
* Client/Server Functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set the network identity of this node. This is the name used during the initial connection
|
||||
* handshake, and is visible through ConnectionInfo on the remote node.
|
||||
*
|
||||
* @param name identity to advertise
|
||||
*/
|
||||
public void setNetworkIdentity(String name) {
|
||||
NetworkTablesJNI.setNetworkIdentity(m_handle, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current network mode.
|
||||
*
|
||||
@@ -478,8 +687,8 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops local-only operation. startServer or startClient can be called after this call to start a
|
||||
* server or client.
|
||||
* Stops local-only operation. startServer or startClient can be called after this call to start
|
||||
* a server or client.
|
||||
*/
|
||||
public void stopLocal() {
|
||||
NetworkTablesJNI.stopLocal(m_handle);
|
||||
@@ -541,14 +750,22 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
NetworkTablesJNI.stopServer(m_handle);
|
||||
}
|
||||
|
||||
/** Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port. */
|
||||
public void startClient3() {
|
||||
NetworkTablesJNI.startClient3(m_handle);
|
||||
/**
|
||||
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port.
|
||||
*
|
||||
* @param identity network identity to advertise (cannot be empty string)
|
||||
*/
|
||||
public void startClient3(String identity) {
|
||||
NetworkTablesJNI.startClient3(m_handle, identity);
|
||||
}
|
||||
|
||||
/** Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port. */
|
||||
public void startClient4() {
|
||||
NetworkTablesJNI.startClient4(m_handle);
|
||||
/**
|
||||
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port.
|
||||
*
|
||||
* @param identity network identity to advertise (cannot be empty string)
|
||||
*/
|
||||
public void startClient4(String identity) {
|
||||
NetworkTablesJNI.startClient4(m_handle, identity);
|
||||
}
|
||||
|
||||
/** Stops the client if it is running. */
|
||||
@@ -602,8 +819,8 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets server addresses and ports for client (without restarting client). The client will attempt
|
||||
* to connect to each server in round robin fashion.
|
||||
* Sets server addresses and ports for client (without restarting client). The client will
|
||||
* attempt to connect to each server in round robin fashion.
|
||||
*
|
||||
* @param serverNames array of server names
|
||||
* @param ports array of port numbers (0=default)
|
||||
@@ -666,8 +883,8 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
|
||||
/**
|
||||
* Flushes all updated values immediately to the network. Note: This is rate-limited to protect
|
||||
* the network from flooding. This is primarily useful for synchronizing network updates with user
|
||||
* code.
|
||||
* the network from flooding. This is primarily useful for synchronizing network updates with
|
||||
* user code.
|
||||
*/
|
||||
public void flush() {
|
||||
NetworkTablesJNI.flush(m_handle);
|
||||
@@ -736,98 +953,19 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
NetworkTablesJNI.stopConnectionDataLog(logger);
|
||||
}
|
||||
|
||||
private final ReentrantLock m_loggerLock = new ReentrantLock();
|
||||
private final Map<Integer, Consumer<LogMessage>> m_loggers = new HashMap<>();
|
||||
private int m_loggerPoller;
|
||||
|
||||
@SuppressWarnings("PMD.AvoidCatchingThrowable")
|
||||
private void startLogThread() {
|
||||
var loggerThread =
|
||||
new Thread(
|
||||
() -> {
|
||||
boolean wasInterrupted = false;
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
WPIUtilJNI.waitForObject(m_loggerPoller);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
// don't try to destroy poller, as its handle is likely no longer valid
|
||||
wasInterrupted = true;
|
||||
break;
|
||||
}
|
||||
LogMessage[] events = NetworkTablesJNI.readLoggerQueue(this, m_loggerPoller);
|
||||
for (LogMessage event : events) {
|
||||
Consumer<LogMessage> logger;
|
||||
m_loggerLock.lock();
|
||||
try {
|
||||
logger = m_loggers.get(event.logger);
|
||||
} finally {
|
||||
m_loggerLock.unlock();
|
||||
}
|
||||
if (logger != null) {
|
||||
try {
|
||||
logger.accept(event);
|
||||
} catch (Throwable throwable) {
|
||||
System.err.println(
|
||||
"Unhandled exception during logger callback: " + throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_loggerLock.lock();
|
||||
try {
|
||||
if (!wasInterrupted) {
|
||||
NetworkTablesJNI.destroyLoggerPoller(m_loggerPoller);
|
||||
}
|
||||
} finally {
|
||||
m_loggerLock.unlock();
|
||||
}
|
||||
},
|
||||
"NTLogger");
|
||||
loggerThread.setDaemon(true);
|
||||
loggerThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add logger callback function. By default, log messages are sent to stderr; this function sends
|
||||
* log messages with the specified levels to the provided callback function instead. The callback
|
||||
* function will only be called for log messages with level greater than or equal to minLevel and
|
||||
* less than or equal to maxLevel; messages outside this range will be silently ignored.
|
||||
*
|
||||
* @param func log callback function
|
||||
* @param minLevel minimum log level
|
||||
* @param maxLevel maximum log level
|
||||
* @return Logger handle
|
||||
* @param func callback function
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addLogger(Consumer<LogMessage> func, int minLevel, int maxLevel) {
|
||||
m_loggerLock.lock();
|
||||
try {
|
||||
if (m_loggerPoller == 0) {
|
||||
m_loggerPoller = NetworkTablesJNI.createLoggerPoller(m_handle);
|
||||
startLogThread();
|
||||
}
|
||||
int handle = NetworkTablesJNI.addPolledLogger(m_loggerPoller, minLevel, maxLevel);
|
||||
m_loggers.put(handle, func);
|
||||
return handle;
|
||||
} finally {
|
||||
m_loggerLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a logger.
|
||||
*
|
||||
* @param logger Logger handle to remove
|
||||
*/
|
||||
public void removeLogger(int logger) {
|
||||
m_loggerLock.lock();
|
||||
try {
|
||||
m_loggers.remove(logger);
|
||||
} finally {
|
||||
m_loggerLock.unlock();
|
||||
}
|
||||
NetworkTablesJNI.removeLogger(logger);
|
||||
public int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> func) {
|
||||
return m_listeners.addLogger(minLevel, maxLevel, func);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -199,42 +199,18 @@ public final class NetworkTablesJNI {
|
||||
|
||||
public static native TopicInfo getTopicInfo(NetworkTableInstance inst, int topic);
|
||||
|
||||
public static native int createTopicListenerPoller(int inst);
|
||||
public static native int createListenerPoller(int inst);
|
||||
|
||||
public static native void destroyTopicListenerPoller(int poller);
|
||||
public static native void destroyListenerPoller(int poller);
|
||||
|
||||
public static native int addPolledTopicListener(int poller, String[] prefixes, int flags);
|
||||
public static native int addListener(int poller, String[] prefixes, int mask);
|
||||
|
||||
public static native int addPolledTopicListener(int poller, int handle, int flags);
|
||||
public static native int addListener(int poller, int handle, int mask);
|
||||
|
||||
public static native TopicNotification[] readTopicListenerQueue(
|
||||
public static native NetworkTableEvent[] readListenerQueue(
|
||||
NetworkTableInstance inst, int poller);
|
||||
|
||||
public static native void removeTopicListener(int topicListener);
|
||||
|
||||
public static native int createValueListenerPoller(int inst);
|
||||
|
||||
public static native void destroyValueListenerPoller(int poller);
|
||||
|
||||
public static native int addPolledValueListener(int poller, int subentry, int flags);
|
||||
|
||||
public static native ValueNotification[] readValueListenerQueue(
|
||||
NetworkTableInstance inst, int poller);
|
||||
|
||||
public static native void removeValueListener(int valueListener);
|
||||
|
||||
public static native int createConnectionListenerPoller(int inst);
|
||||
|
||||
public static native void destroyConnectionListenerPoller(int poller);
|
||||
|
||||
public static native int addPolledConnectionListener(int poller, boolean immediateNotify);
|
||||
|
||||
public static native ConnectionNotification[] readConnectionListenerQueue(
|
||||
NetworkTableInstance inst, int poller);
|
||||
|
||||
public static native void removeConnectionListener(int connListener);
|
||||
|
||||
public static native void setNetworkIdentity(int inst, String name);
|
||||
public static native void removeListener(int listener);
|
||||
|
||||
public static native int getNetworkMode(int inst);
|
||||
|
||||
@@ -247,9 +223,9 @@ public final class NetworkTablesJNI {
|
||||
|
||||
public static native void stopServer(int inst);
|
||||
|
||||
public static native void startClient3(int inst);
|
||||
public static native void startClient3(int inst, String identity);
|
||||
|
||||
public static native void startClient4(int inst);
|
||||
public static native void startClient4(int inst, String identity);
|
||||
|
||||
public static native void stopClient(int inst);
|
||||
|
||||
@@ -289,13 +265,5 @@ public final class NetworkTablesJNI {
|
||||
|
||||
public static native void stopConnectionDataLog(int logger);
|
||||
|
||||
public static native int createLoggerPoller(int inst);
|
||||
|
||||
public static native void destroyLoggerPoller(int poller);
|
||||
|
||||
public static native int addPolledLogger(int poller, int minLevel, int maxLevel);
|
||||
|
||||
public static native LogMessage[] readLoggerQueue(NetworkTableInstance inst, int poller);
|
||||
|
||||
public static native void removeLogger(int logger);
|
||||
public static native int addLogger(int poller, int minLevel, int maxLevel);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ package edu.wpi.first.networktables;
|
||||
public final class ConnectionInfo {
|
||||
/**
|
||||
* The remote identifier (as set on the remote node by {@link
|
||||
* NetworkTableInstance#setNetworkIdentity(String)}).
|
||||
* NetworkTableInstance#startClient4(String)}).
|
||||
*/
|
||||
public final String remote_id;
|
||||
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** Connection listener. This calls back to a callback function when a connection change occurs. */
|
||||
public final class ConnectionListener implements AutoCloseable {
|
||||
/**
|
||||
* Create a listener for connection changes.
|
||||
*
|
||||
* @param inst Instance
|
||||
* @param immediateNotify if notification should be immediately created for existing connections
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public ConnectionListener(
|
||||
NetworkTableInstance inst,
|
||||
boolean immediateNotify,
|
||||
Consumer<ConnectionNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = inst;
|
||||
s_poller = NetworkTablesJNI.createConnectionListenerPoller(inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle = NetworkTablesJNI.addPolledConnectionListener(s_poller, immediateNotify);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
s_listeners.remove(m_handle);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
NetworkTablesJNI.removeConnectionListener(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
*
|
||||
* @return True if the native handle is valid, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native handle.
|
||||
*
|
||||
* @return Native handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
private int m_handle;
|
||||
|
||||
private static final ReentrantLock s_lock = new ReentrantLock();
|
||||
private static final Map<Integer, Consumer<ConnectionNotification>> s_listeners = new HashMap<>();
|
||||
private static Thread s_thread;
|
||||
private static NetworkTableInstance s_inst;
|
||||
private static int s_poller;
|
||||
private static boolean s_waitQueue;
|
||||
private static final Condition s_waitQueueCond = s_lock.newCondition();
|
||||
|
||||
private static void startThread() {
|
||||
s_thread =
|
||||
new Thread(
|
||||
() -> {
|
||||
boolean wasInterrupted = false;
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
WPIUtilJNI.waitForObject(s_poller);
|
||||
} catch (InterruptedException ex) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_waitQueue) {
|
||||
s_waitQueue = false;
|
||||
s_waitQueueCond.signalAll();
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
// don't try to destroy poller, as its handle is likely no longer valid
|
||||
wasInterrupted = true;
|
||||
break;
|
||||
}
|
||||
for (ConnectionNotification event :
|
||||
NetworkTablesJNI.readConnectionListenerQueue(s_inst, s_poller)) {
|
||||
Consumer<ConnectionNotification> listener;
|
||||
s_lock.lock();
|
||||
try {
|
||||
listener = s_listeners.get(event.listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable throwable) {
|
||||
System.err.println(
|
||||
"Unhandled exception during listener callback: " + throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (!wasInterrupted) {
|
||||
NetworkTablesJNI.destroyConnectionListenerPoller(s_poller);
|
||||
}
|
||||
s_poller = 0;
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
},
|
||||
"ConnectionListener");
|
||||
s_thread.setDaemon(true);
|
||||
s_thread.start();
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/**
|
||||
* A connection listener. This queues connection notifications. Code using the listener must
|
||||
* periodically call readQueue() to read the notifications.
|
||||
*/
|
||||
public final class ConnectionListenerPoller implements AutoCloseable {
|
||||
/**
|
||||
* Construct a connection listener poller.
|
||||
*
|
||||
* @param inst Instance
|
||||
*/
|
||||
public ConnectionListenerPoller(NetworkTableInstance inst) {
|
||||
m_inst = inst;
|
||||
m_handle = NetworkTablesJNI.createConnectionListenerPoller(inst.getHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connection listener.
|
||||
*
|
||||
* @param immediateNotify if notification should be immediately created for existing connections
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(boolean immediateNotify) {
|
||||
return NetworkTablesJNI.addPolledConnectionListener(m_handle, immediateNotify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a connection listener.
|
||||
*
|
||||
* @param listener Listener handle
|
||||
*/
|
||||
public void remove(int listener) {
|
||||
NetworkTablesJNI.removeConnectionListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read connection notifications.
|
||||
*
|
||||
* @return Connection notifications since the previous call to readQueue()
|
||||
*/
|
||||
public ConnectionNotification[] readQueue() {
|
||||
return NetworkTablesJNI.readConnectionListenerQueue(m_inst, m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
NetworkTablesJNI.destroyConnectionListenerPoller(m_handle);
|
||||
}
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
*
|
||||
* @return True if the native handle is valid, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native handle.
|
||||
*
|
||||
* @return Handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
private final NetworkTableInstance m_inst;
|
||||
private int m_handle;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/** NetworkTables Connection notification. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final class ConnectionNotification {
|
||||
/**
|
||||
* Handle of listener that was triggered. ConnectionListener.getHandle() or the return value of
|
||||
* ConnectionListenerPoller.add() can be used to map this to a specific added listener.
|
||||
*/
|
||||
public final int listener;
|
||||
|
||||
/** True if event is due to connection being established. */
|
||||
public final boolean connected;
|
||||
|
||||
/** Connection information. */
|
||||
public final ConnectionInfo conn;
|
||||
|
||||
/**
|
||||
* Constructor. This should generally only be used internally to NetworkTables.
|
||||
*
|
||||
* @param inst Instance
|
||||
* @param listener Listener that was triggered
|
||||
* @param connected Connected if true
|
||||
* @param conn Connection information
|
||||
*/
|
||||
public ConnectionNotification(
|
||||
NetworkTableInstance inst, int listener, boolean connected, ConnectionInfo conn) {
|
||||
this.m_inst = inst;
|
||||
this.listener = listener;
|
||||
this.connected = connected;
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
private final NetworkTableInstance m_inst;
|
||||
|
||||
public NetworkTableInstance getInstance() {
|
||||
return m_inst;
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,6 @@ public final class LogMessage {
|
||||
public static final int kDebug3 = 7;
|
||||
public static final int kDebug4 = 6;
|
||||
|
||||
/** The logger that generated the message. */
|
||||
public final int logger;
|
||||
|
||||
/** Log level of the message. */
|
||||
public final int level;
|
||||
|
||||
@@ -37,26 +34,15 @@ public final class LogMessage {
|
||||
/**
|
||||
* Constructor. This should generally only be used internally to NetworkTables.
|
||||
*
|
||||
* @param inst Instance
|
||||
* @param logger Logger
|
||||
* @param level Log level
|
||||
* @param filename Filename
|
||||
* @param line Line number
|
||||
* @param message Message
|
||||
*/
|
||||
public LogMessage(
|
||||
NetworkTableInstance inst, int logger, int level, String filename, int line, String message) {
|
||||
this.m_inst = inst;
|
||||
this.logger = logger;
|
||||
public LogMessage(int level, String filename, int line, String message) {
|
||||
this.level = level;
|
||||
this.filename = filename;
|
||||
this.line = line;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
private final NetworkTableInstance m_inst;
|
||||
|
||||
NetworkTableInstance getInstance() {
|
||||
return m_inst;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/**
|
||||
* NetworkTables event.
|
||||
*
|
||||
* <p>Events have flags. The flags are a bitmask and must be OR'ed together when listening to an
|
||||
* event to indicate the combination of events desired to be received.
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public final class NetworkTableEvent {
|
||||
/** No flags. */
|
||||
public static final int kNone = 0;
|
||||
|
||||
/**
|
||||
* Initial listener addition. Set this flag to receive immediate notification of matches to the
|
||||
* flag criteria.
|
||||
*/
|
||||
public static final int kImmediate = 0x01;
|
||||
|
||||
/** Client connected (on server, any client connected). */
|
||||
public static final int kConnected = 0x02;
|
||||
|
||||
/** Client disconnected (on server, any client disconnected). */
|
||||
public static final int kDisconnected = 0x04;
|
||||
|
||||
/** Any connection event (connect or disconnect). */
|
||||
public static final int kConnection = kConnected | kDisconnected;
|
||||
|
||||
/** New topic published. */
|
||||
public static final int kPublish = 0x08;
|
||||
|
||||
/** Topic unpublished. */
|
||||
public static final int kUnpublish = 0x10;
|
||||
|
||||
/** Topic properties changed. */
|
||||
public static final int kProperties = 0x20;
|
||||
|
||||
/** Any topic event (publish, unpublish, or properties changed). */
|
||||
public static final int kTopic = kPublish | kUnpublish | kProperties;
|
||||
|
||||
/** Topic value updated (via network). */
|
||||
public static final int kValueRemote = 0x40;
|
||||
|
||||
/** Topic value updated (local). */
|
||||
public static final int kValueLocal = 0x80;
|
||||
|
||||
/** Topic value updated (network or local). */
|
||||
public static final int kValueAll = kValueRemote | kValueLocal;
|
||||
|
||||
/** Log message. */
|
||||
public static final int kLogMessage = 0x100;
|
||||
|
||||
/**
|
||||
* Handle of listener that was triggered. The value returned when adding the listener can be used
|
||||
* to map this to a specific added listener.
|
||||
*/
|
||||
public final int listener;
|
||||
|
||||
/**
|
||||
* Event flags. For example, kPublish if the topic was not previously published. Also indicates
|
||||
* the data included with the event:
|
||||
*
|
||||
* <ul>
|
||||
* <li>kConnected or kDisconnected: connInfo
|
||||
* <li>kPublish, kUnpublish, or kProperties: topicInfo
|
||||
* <li>kValueRemote, kValueLocal: valueData
|
||||
* <li>kLogMessage: logMessage
|
||||
* </ul>
|
||||
*/
|
||||
public final int flags;
|
||||
|
||||
/** Connection information (for connection events). */
|
||||
public final ConnectionInfo connInfo;
|
||||
|
||||
/** Topic information (for topic events). */
|
||||
public final TopicInfo topicInfo;
|
||||
|
||||
/** Value data (for value events). */
|
||||
public final ValueEventData valueData;
|
||||
|
||||
/** Log message (for log message events). */
|
||||
public final LogMessage logMessage;
|
||||
|
||||
/**
|
||||
* Constructor. This should generally only be used internally to NetworkTables.
|
||||
*
|
||||
* @param inst Instance
|
||||
* @param listener Listener that was triggered
|
||||
* @param flags Event flags
|
||||
* @param connInfo Connection information
|
||||
* @param topicInfo Topic information
|
||||
* @param valueData Value data
|
||||
* @param logMessage Log message
|
||||
*/
|
||||
public NetworkTableEvent(
|
||||
NetworkTableInstance inst,
|
||||
int listener,
|
||||
int flags,
|
||||
ConnectionInfo connInfo,
|
||||
TopicInfo topicInfo,
|
||||
ValueEventData valueData,
|
||||
LogMessage logMessage) {
|
||||
this.m_inst = inst;
|
||||
this.listener = listener;
|
||||
this.flags = flags;
|
||||
this.connInfo = connInfo;
|
||||
this.topicInfo = topicInfo;
|
||||
this.valueData = valueData;
|
||||
this.logMessage = logMessage;
|
||||
}
|
||||
|
||||
/* Network table instance. */
|
||||
private final NetworkTableInstance m_inst;
|
||||
|
||||
/**
|
||||
* Gets the instance associated with this event.
|
||||
*
|
||||
* @return Instance
|
||||
*/
|
||||
public NetworkTableInstance getInstance() {
|
||||
return m_inst;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Event listener. This calls back to a callback function when an event matching the specified mask
|
||||
* occurs. The callback function is called asynchronously on a separate thread, so it's important to
|
||||
* use synchronization or atomics when accessing any shared state from the callback function.
|
||||
*/
|
||||
public final class NetworkTableListener implements AutoCloseable {
|
||||
/**
|
||||
* Create a listener for changes to topics with names that start with any of the given prefixes.
|
||||
* This creates a corresponding internal subscriber with the lifetime of the listener.
|
||||
*
|
||||
* @param inst Instance
|
||||
* @param prefixes Topic name string prefixes
|
||||
* @param eventMask Bitmask of NetworkTableEvent flags values
|
||||
* @param listener Listener function
|
||||
* @return Listener
|
||||
*/
|
||||
public static NetworkTableListener createListener(
|
||||
NetworkTableInstance inst,
|
||||
String[] prefixes,
|
||||
int eventMask,
|
||||
Consumer<NetworkTableEvent> listener) {
|
||||
return new NetworkTableListener(inst, inst.addListener(prefixes, eventMask, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for changes on a particular topic. This creates a corresponding internal
|
||||
* subscriber with the lifetime of the listener.
|
||||
*
|
||||
* @param topic Topic
|
||||
* @param eventMask Bitmask of NetworkTableEvent flags values
|
||||
* @param listener Listener function
|
||||
* @return Listener
|
||||
*/
|
||||
public static NetworkTableListener createListener(
|
||||
Topic topic, int eventMask, Consumer<NetworkTableEvent> listener) {
|
||||
NetworkTableInstance inst = topic.getInstance();
|
||||
return new NetworkTableListener(inst, inst.addListener(topic, eventMask, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of NetworkTableEvent flags values
|
||||
* @param listener Listener function
|
||||
* @return Listener
|
||||
*/
|
||||
public static NetworkTableListener createListener(
|
||||
Subscriber subscriber, int eventMask, Consumer<NetworkTableEvent> listener) {
|
||||
NetworkTableInstance inst = subscriber.getTopic().getInstance();
|
||||
return new NetworkTableListener(inst, inst.addListener(subscriber, eventMask, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of NetworkTableEvent flags values
|
||||
* @param listener Listener function
|
||||
* @return Listener
|
||||
*/
|
||||
public static NetworkTableListener createListener(
|
||||
MultiSubscriber subscriber, int eventMask, Consumer<NetworkTableEvent> listener) {
|
||||
NetworkTableInstance inst = subscriber.getInstance();
|
||||
return new NetworkTableListener(inst, inst.addListener(subscriber, eventMask, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for topic changes on an entry.
|
||||
*
|
||||
* @param entry Entry
|
||||
* @param eventMask Bitmask of NetworkTableEvent flags values
|
||||
* @param listener Listener function
|
||||
* @return Listener
|
||||
*/
|
||||
public static NetworkTableListener createListener(
|
||||
NetworkTableEntry entry, int eventMask, Consumer<NetworkTableEvent> listener) {
|
||||
NetworkTableInstance inst = entry.getInstance();
|
||||
return new NetworkTableListener(inst, inst.addListener(entry, eventMask, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connection listener.
|
||||
*
|
||||
* @param inst instance
|
||||
* @param immediateNotify notify listener of all existing connections
|
||||
* @param listener listener function
|
||||
* @return Listener
|
||||
*/
|
||||
public static NetworkTableListener createConnectionListener(
|
||||
NetworkTableInstance inst, boolean immediateNotify, Consumer<NetworkTableEvent> listener) {
|
||||
return new NetworkTableListener(inst, inst.addConnectionListener(immediateNotify, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for log messages. By default, log messages are sent to stderr; this function
|
||||
* sends log messages with the specified levels to the provided callback function instead. The
|
||||
* callback function will only be called for log messages with level greater than or equal to
|
||||
* minLevel and less than or equal to maxLevel; messages outside this range will be silently
|
||||
* ignored.
|
||||
*
|
||||
* @param inst instance
|
||||
* @param minLevel minimum log level
|
||||
* @param maxLevel maximum log level
|
||||
* @param listener listener function
|
||||
* @return Listener
|
||||
*/
|
||||
public static NetworkTableListener createLogger(
|
||||
NetworkTableInstance inst, int minLevel, int maxLevel, Consumer<NetworkTableEvent> listener) {
|
||||
return new NetworkTableListener(inst, inst.addLogger(minLevel, maxLevel, listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
m_inst.removeListener(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
*
|
||||
* @return True if the native handle is valid, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native handle.
|
||||
*
|
||||
* @return Native handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the topic listener queue to be empty. This is primarily useful for deterministic
|
||||
* testing. This blocks until either the topic listener queue is empty (e.g. there are no more
|
||||
* events that need to be passed along to callbacks or poll queues) or the timeout expires.
|
||||
*
|
||||
* @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to
|
||||
* block indefinitely
|
||||
* @return False if timed out, otherwise true.
|
||||
*/
|
||||
public boolean waitForQueue(double timeout) {
|
||||
return m_inst.waitForListenerQueue(timeout);
|
||||
}
|
||||
|
||||
private NetworkTableListener(NetworkTableInstance inst, int handle) {
|
||||
m_inst = inst;
|
||||
m_handle = handle;
|
||||
}
|
||||
|
||||
private final NetworkTableInstance m_inst;
|
||||
private int m_handle;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/**
|
||||
* Topic change listener. This queues topic change events matching the specified mask. Code using
|
||||
* the listener must periodically call readQueue() to read the events.
|
||||
*/
|
||||
public final class NetworkTableListenerPoller implements AutoCloseable {
|
||||
/**
|
||||
* Construct a topic listener poller.
|
||||
*
|
||||
* @param inst Instance
|
||||
*/
|
||||
public NetworkTableListenerPoller(NetworkTableInstance inst) {
|
||||
m_inst = inst;
|
||||
m_handle = NetworkTablesJNI.createListenerPoller(inst.getHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to topic changes for topics with names that start with any of the given
|
||||
* prefixes. This creates a corresponding internal subscriber with the lifetime of the listener.
|
||||
*
|
||||
* @param prefixes Topic name string prefixes
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(String[] prefixes, int eventMask) {
|
||||
return NetworkTablesJNI.addListener(m_handle, prefixes, eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to changes to a particular topic. This creates a corresponding internal
|
||||
* subscriber with the lifetime of the listener.
|
||||
*
|
||||
* @param topic Topic
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(Topic topic, int eventMask) {
|
||||
return NetworkTablesJNI.addListener(m_handle, topic.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to topic changes on a subscriber. This does NOT keep the subscriber active.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(Subscriber subscriber, int eventMask) {
|
||||
return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to topic changes on a subscriber. This does NOT keep the subscriber active.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(MultiSubscriber subscriber, int eventMask) {
|
||||
return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to topic changes on an entry.
|
||||
*
|
||||
* @param entry Entry
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(NetworkTableEntry entry, int eventMask) {
|
||||
return NetworkTablesJNI.addListener(m_handle, entry.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a connection listener. The callback function is called asynchronously on a separate thread,
|
||||
* so it's important to use synchronization or atomics when accessing any shared state from the
|
||||
* callback function.
|
||||
*
|
||||
* @param immediateNotify notify listener of all existing connections
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addConnectionListener(boolean immediateNotify) {
|
||||
return NetworkTablesJNI.addListener(
|
||||
m_handle,
|
||||
m_inst.getHandle(),
|
||||
NetworkTableEvent.kConnection | (immediateNotify ? NetworkTableEvent.kImmediate : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add logger callback function. By default, log messages are sent to stderr; this function sends
|
||||
* log messages with the specified levels to the provided callback function instead. The callback
|
||||
* function will only be called for log messages with level greater than or equal to minLevel and
|
||||
* less than or equal to maxLevel; messages outside this range will be silently ignored.
|
||||
*
|
||||
* @param minLevel minimum log level
|
||||
* @param maxLevel maximum log level
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addLogger(int minLevel, int maxLevel) {
|
||||
return NetworkTablesJNI.addLogger(m_handle, minLevel, maxLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener.
|
||||
*
|
||||
* @param listener Listener handle
|
||||
*/
|
||||
public void removeListener(int listener) {
|
||||
NetworkTablesJNI.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read topic notifications.
|
||||
*
|
||||
* @return Topic notifications since the previous call to readQueue()
|
||||
*/
|
||||
public NetworkTableEvent[] readQueue() {
|
||||
return NetworkTablesJNI.readListenerQueue(m_inst, m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
NetworkTablesJNI.destroyListenerPoller(m_handle);
|
||||
}
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
*
|
||||
* @return True if the native handle is valid, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native handle.
|
||||
*
|
||||
* @return Handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
private final NetworkTableInstance m_inst;
|
||||
private int m_handle;
|
||||
}
|
||||
@@ -64,7 +64,8 @@ public class PubSubOption {
|
||||
|
||||
/**
|
||||
* Polling storage for subscription. Specifies the maximum number of updates NetworkTables should
|
||||
* store between calls to the subscriber's poll() function. Defaults to 1.
|
||||
* store between calls to the subscriber's poll() function. Defaults to 1 if sendAll is false, 20
|
||||
* if sendAll is true.
|
||||
*
|
||||
* @param depth number of entries to save for polling.
|
||||
* @return option
|
||||
|
||||
@@ -42,6 +42,15 @@ public final class TopicInfo {
|
||||
/* Cached topic object. */
|
||||
private Topic m_topicObject;
|
||||
|
||||
/**
|
||||
* Get the instance.
|
||||
*
|
||||
* @return Instance
|
||||
*/
|
||||
public NetworkTableInstance getInstance() {
|
||||
return m_inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the topic as an object.
|
||||
*
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Topic change listener. This calls back to a callback function when a topic change matching the
|
||||
* specified mask occurs.
|
||||
*/
|
||||
public final class TopicListener implements AutoCloseable {
|
||||
/**
|
||||
* Create a listener for changes on a particular topic.
|
||||
*
|
||||
* @param topic Topic
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public TopicListener(Topic topic, int eventMask, Consumer<TopicNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = topic.getInstance();
|
||||
s_poller = NetworkTablesJNI.createTopicListenerPoller(s_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle = NetworkTablesJNI.addPolledTopicListener(s_poller, topic.getHandle(), eventMask);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for topic changes on a subscriber.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public TopicListener(Subscriber subscriber, int eventMask, Consumer<TopicNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = subscriber.getTopic().getInstance();
|
||||
s_poller = NetworkTablesJNI.createTopicListenerPoller(s_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle =
|
||||
NetworkTablesJNI.addPolledTopicListener(s_poller, subscriber.getHandle(), eventMask);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for topic changes on a subscriber.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public TopicListener(
|
||||
MultiSubscriber subscriber, int eventMask, Consumer<TopicNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = subscriber.getInstance();
|
||||
s_poller = NetworkTablesJNI.createTopicListenerPoller(s_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle =
|
||||
NetworkTablesJNI.addPolledTopicListener(s_poller, subscriber.getHandle(), eventMask);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for topic changes on an entry.
|
||||
*
|
||||
* @param entry Entry
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public TopicListener(
|
||||
NetworkTableEntry entry, int eventMask, Consumer<TopicNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = entry.getInstance();
|
||||
s_poller = NetworkTablesJNI.createTopicListenerPoller(s_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle = NetworkTablesJNI.addPolledTopicListener(s_poller, entry.getHandle(), eventMask);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for changes to topics with names that start with any of the given prefixes.
|
||||
*
|
||||
* @param inst Instance
|
||||
* @param prefixes Topic name string prefixes
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public TopicListener(
|
||||
NetworkTableInstance inst,
|
||||
String[] prefixes,
|
||||
int eventMask,
|
||||
Consumer<TopicNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = inst;
|
||||
s_poller = NetworkTablesJNI.createTopicListenerPoller(inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle = NetworkTablesJNI.addPolledTopicListener(s_poller, prefixes, eventMask);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
s_listeners.remove(m_handle);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
NetworkTablesJNI.removeTopicListener(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
*
|
||||
* @return True if the native handle is valid, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native handle.
|
||||
*
|
||||
* @return Native handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
private int m_handle;
|
||||
|
||||
private static final ReentrantLock s_lock = new ReentrantLock();
|
||||
private static final Map<Integer, Consumer<TopicNotification>> s_listeners = new HashMap<>();
|
||||
private static Thread s_thread;
|
||||
private static NetworkTableInstance s_inst;
|
||||
private static int s_poller;
|
||||
private static boolean s_waitQueue;
|
||||
private static final Condition s_waitQueueCond = s_lock.newCondition();
|
||||
|
||||
private static void startThread() {
|
||||
s_thread =
|
||||
new Thread(
|
||||
() -> {
|
||||
boolean wasInterrupted = false;
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
WPIUtilJNI.waitForObject(s_poller);
|
||||
} catch (InterruptedException ex) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_waitQueue) {
|
||||
s_waitQueue = false;
|
||||
s_waitQueueCond.signalAll();
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
// don't try to destroy poller, as its handle is likely no longer valid
|
||||
wasInterrupted = true;
|
||||
break;
|
||||
}
|
||||
for (TopicNotification event :
|
||||
NetworkTablesJNI.readTopicListenerQueue(s_inst, s_poller)) {
|
||||
Consumer<TopicNotification> listener;
|
||||
s_lock.lock();
|
||||
try {
|
||||
listener = s_listeners.get(event.listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable throwable) {
|
||||
System.err.println(
|
||||
"Unhandled exception during listener callback: " + throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (!wasInterrupted) {
|
||||
NetworkTablesJNI.destroyTopicListenerPoller(s_poller);
|
||||
}
|
||||
s_poller = 0;
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
},
|
||||
"TopicListener");
|
||||
s_thread.setDaemon(true);
|
||||
s_thread.start();
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/**
|
||||
* Flag values for use with topic listeners.
|
||||
*
|
||||
* <p>The flags are a bitmask and must be OR'ed together to indicate the combination of events
|
||||
* desired to be received.
|
||||
*
|
||||
* <p>The constants kPublish, kUnpublish, and kProperties represent different events that can occur
|
||||
* to topics.
|
||||
*/
|
||||
public enum TopicListenerFlags {
|
||||
; // no enum values
|
||||
|
||||
/**
|
||||
* Initial listener addition.
|
||||
*
|
||||
* <p>Set this flag to receive immediate notification of topics matching the flag criteria
|
||||
* (generally only useful when combined with kPublish).
|
||||
*/
|
||||
public static final int kImmediate = 0x01;
|
||||
|
||||
/**
|
||||
* Newly published topic.
|
||||
*
|
||||
* <p>Set this flag to receive a notification when a topic is initially published.
|
||||
*/
|
||||
public static final int kPublish = 0x02;
|
||||
|
||||
/**
|
||||
* Topic has no more publishers.
|
||||
*
|
||||
* <p>Set this flag to receive a notification when a topic has no more publishers.
|
||||
*/
|
||||
public static final int kUnpublish = 0x04;
|
||||
|
||||
/**
|
||||
* Topic's properties changed.
|
||||
*
|
||||
* <p>Set this flag to receive a notification when an topic's properties change.
|
||||
*/
|
||||
public static final int kProperties = 0x08;
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/**
|
||||
* Topic change listener. This queues topic change events matching the specified mask. Code using
|
||||
* the listener must periodically call readQueue() to read the events.
|
||||
*/
|
||||
public final class TopicListenerPoller implements AutoCloseable {
|
||||
/**
|
||||
* Construct a topic listener poller.
|
||||
*
|
||||
* @param inst Instance
|
||||
*/
|
||||
public TopicListenerPoller(NetworkTableInstance inst) {
|
||||
m_inst = inst;
|
||||
m_handle = NetworkTablesJNI.createTopicListenerPoller(inst.getHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to changes to a particular topic.
|
||||
*
|
||||
* @param topic Topic
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(Topic topic, int eventMask) {
|
||||
return NetworkTablesJNI.addPolledTopicListener(m_handle, topic.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to topic changes on a subscriber.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(Subscriber subscriber, int eventMask) {
|
||||
return NetworkTablesJNI.addPolledTopicListener(m_handle, subscriber.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to topic changes on a subscriber.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(MultiSubscriber subscriber, int eventMask) {
|
||||
return NetworkTablesJNI.addPolledTopicListener(m_handle, subscriber.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to topic changes on an entry.
|
||||
*
|
||||
* @param entry Entry
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(NetworkTableEntry entry, int eventMask) {
|
||||
return NetworkTablesJNI.addPolledTopicListener(m_handle, entry.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to topic changes for topics with names that start with any of the given
|
||||
* prefixes.
|
||||
*
|
||||
* @param prefixes Topic name string prefixes
|
||||
* @param eventMask Bitmask of TopicListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(String[] prefixes, int eventMask) {
|
||||
return NetworkTablesJNI.addPolledTopicListener(m_handle, prefixes, eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener.
|
||||
*
|
||||
* @param listener Listener handle
|
||||
*/
|
||||
public void remove(int listener) {
|
||||
NetworkTablesJNI.removeTopicListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read topic notifications.
|
||||
*
|
||||
* @return Topic notifications since the previous call to readQueue()
|
||||
*/
|
||||
public TopicNotification[] readQueue() {
|
||||
return NetworkTablesJNI.readTopicListenerQueue(m_inst, m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
NetworkTablesJNI.destroyTopicListenerPoller(m_handle);
|
||||
}
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
*
|
||||
* @return True if the native handle is valid, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native handle.
|
||||
*
|
||||
* @return Handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
private final NetworkTableInstance m_inst;
|
||||
private int m_handle;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/** NetworkTables topic notification. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final class TopicNotification {
|
||||
/**
|
||||
* Handle of listener that was triggered. TopicListener.getHandle() or the return value of
|
||||
* TopicListenerPoller.add() can be used to map this to a specific added listener.
|
||||
*/
|
||||
public final int listener;
|
||||
|
||||
/** Topic information. */
|
||||
public final TopicInfo info;
|
||||
|
||||
/**
|
||||
* Update flags. For example, {@link TopicListenerFlags#kPublish} if the topic was not previously
|
||||
* published.
|
||||
*/
|
||||
public final int flags;
|
||||
|
||||
/**
|
||||
* Constructor. This should generally only be used internally to NetworkTables.
|
||||
*
|
||||
* @param listener Listener that was triggered
|
||||
* @param info Topic information
|
||||
* @param flags Update flags
|
||||
*/
|
||||
public TopicNotification(int listener, TopicInfo info, int flags) {
|
||||
this.listener = listener;
|
||||
this.info = info;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,9 @@
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/** NetworkTables value notification. */
|
||||
/** NetworkTables value event data. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final class ValueNotification {
|
||||
/**
|
||||
* Handle of listener that was triggered. ValueListener.getHandle() or the return value of
|
||||
* ValueListenerPoller.add() can be used to map this to a specific added listener.
|
||||
*/
|
||||
public final int listener;
|
||||
|
||||
public final class ValueEventData {
|
||||
/** Topic handle. Topic.getHandle() can be used to map this to the corresponding Topic object. */
|
||||
public final int topic;
|
||||
|
||||
@@ -25,39 +19,26 @@ public final class ValueNotification {
|
||||
/** The new value. */
|
||||
public final NetworkTableValue value;
|
||||
|
||||
/** Update flags. */
|
||||
public final int flags;
|
||||
|
||||
/**
|
||||
* Constructor. This should generally only be used internally to NetworkTables.
|
||||
*
|
||||
* @param inst Instance
|
||||
* @param listener Listener that was triggered
|
||||
* @param topic Topic handle
|
||||
* @param subentry Subscriber/entry handle
|
||||
* @param value The new value
|
||||
* @param flags Update flags
|
||||
*/
|
||||
public ValueNotification(
|
||||
NetworkTableInstance inst,
|
||||
int listener,
|
||||
int topic,
|
||||
int subentry,
|
||||
NetworkTableValue value,
|
||||
int flags) {
|
||||
public ValueEventData(
|
||||
NetworkTableInstance inst, int topic, int subentry, NetworkTableValue value) {
|
||||
this.m_inst = inst;
|
||||
this.listener = listener;
|
||||
this.topic = topic;
|
||||
this.subentry = subentry;
|
||||
this.value = value;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
/* Network table instance. */
|
||||
private final NetworkTableInstance m_inst;
|
||||
|
||||
/* Cached topic object. */
|
||||
Topic m_topicObject;
|
||||
private Topic m_topicObject;
|
||||
|
||||
private final NetworkTableInstance m_inst;
|
||||
|
||||
/**
|
||||
* Get the topic as an object.
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Value change listener. This calls back to a callback function when a value change matching the
|
||||
* specified mask occurs.
|
||||
*/
|
||||
public final class ValueListener implements AutoCloseable {
|
||||
/**
|
||||
* Create a listener for value changes on a subscriber.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of ValueListenerFlags values
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public ValueListener(Subscriber subscriber, int eventMask, Consumer<ValueNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = subscriber.getTopic().getInstance();
|
||||
s_poller = NetworkTablesJNI.createValueListenerPoller(s_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle =
|
||||
NetworkTablesJNI.addPolledValueListener(s_poller, subscriber.getHandle(), eventMask);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for value changes on a subscriber.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of ValueListenerFlags values
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public ValueListener(
|
||||
MultiSubscriber subscriber, int eventMask, Consumer<ValueNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = subscriber.getInstance();
|
||||
s_poller = NetworkTablesJNI.createValueListenerPoller(s_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle =
|
||||
NetworkTablesJNI.addPolledValueListener(s_poller, subscriber.getHandle(), eventMask);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a listener for value changes on an entry.
|
||||
*
|
||||
* @param entry Entry
|
||||
* @param eventMask Bitmask of ValueListenerFlags values
|
||||
* @param listener Listener function
|
||||
*/
|
||||
public ValueListener(
|
||||
NetworkTableEntry entry, int eventMask, Consumer<ValueNotification> listener) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_inst = entry.getInstance();
|
||||
s_poller = NetworkTablesJNI.createValueListenerPoller(s_inst.getHandle());
|
||||
startThread();
|
||||
}
|
||||
m_handle = NetworkTablesJNI.addPolledValueListener(s_poller, entry.getHandle(), eventMask);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
s_listeners.remove(m_handle);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
NetworkTablesJNI.removeValueListener(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
*
|
||||
* @return True if the native handle is valid, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native handle.
|
||||
*
|
||||
* @return Native handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
private int m_handle;
|
||||
|
||||
private static final ReentrantLock s_lock = new ReentrantLock();
|
||||
private static final Map<Integer, Consumer<ValueNotification>> s_listeners = new HashMap<>();
|
||||
private static Thread s_thread;
|
||||
private static NetworkTableInstance s_inst;
|
||||
private static int s_poller;
|
||||
private static boolean s_waitQueue;
|
||||
private static final Condition s_waitQueueCond = s_lock.newCondition();
|
||||
|
||||
private static void startThread() {
|
||||
s_thread =
|
||||
new Thread(
|
||||
() -> {
|
||||
boolean wasInterrupted = false;
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
WPIUtilJNI.waitForObject(s_poller);
|
||||
} catch (InterruptedException ex) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_waitQueue) {
|
||||
s_waitQueue = false;
|
||||
s_waitQueueCond.signalAll();
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
// don't try to destroy poller, as its handle is likely no longer valid
|
||||
wasInterrupted = true;
|
||||
break;
|
||||
}
|
||||
for (ValueNotification event :
|
||||
NetworkTablesJNI.readValueListenerQueue(s_inst, s_poller)) {
|
||||
Consumer<ValueNotification> listener;
|
||||
s_lock.lock();
|
||||
try {
|
||||
listener = s_listeners.get(event.listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable throwable) {
|
||||
System.err.println(
|
||||
"Unhandled exception during listener callback: " + throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (!wasInterrupted) {
|
||||
NetworkTablesJNI.destroyValueListenerPoller(s_poller);
|
||||
}
|
||||
s_poller = 0;
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
},
|
||||
"ValueListener");
|
||||
s_thread.setDaemon(true);
|
||||
s_thread.start();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/**
|
||||
* Flag values for use with value listeners.
|
||||
*
|
||||
* <p>The flags are a bitmask and must be OR'ed together to indicate the combination of events
|
||||
* desired to be received.
|
||||
*
|
||||
* <p>By default, notifications are only generated for remote changes occurring after the listener
|
||||
* is created. The constants kImmediate and kLocal are modifiers that cause notifications to be
|
||||
* generated at other times.
|
||||
*/
|
||||
public enum ValueListenerFlags {
|
||||
; // no enum values
|
||||
|
||||
/**
|
||||
* Initial listener addition.
|
||||
*
|
||||
* <p>Set this flag to receive immediate notification of the current value.
|
||||
*/
|
||||
public static final int kImmediate = 0x01;
|
||||
|
||||
/**
|
||||
* Changed locally.
|
||||
*
|
||||
* <p>Set this flag to receive notification of both local changes and changes coming from remote
|
||||
* nodes. By default, notifications are only generated for remote changes.
|
||||
*/
|
||||
public static final int kLocal = 0x02;
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
/**
|
||||
* Value change listener. This queues value change events matching the specified mask. Code using
|
||||
* the listener must periodically call readQueue() to read the events.
|
||||
*/
|
||||
public final class ValueListenerPoller implements AutoCloseable {
|
||||
/**
|
||||
* Construct a value listener poller.
|
||||
*
|
||||
* @param inst Instance
|
||||
*/
|
||||
public ValueListenerPoller(NetworkTableInstance inst) {
|
||||
m_inst = inst;
|
||||
m_handle = NetworkTablesJNI.createValueListenerPoller(inst.getHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to value changes on a subscriber.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of ValueListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(Subscriber subscriber, int eventMask) {
|
||||
return NetworkTablesJNI.addPolledValueListener(m_handle, subscriber.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to value changes on a subscriber.
|
||||
*
|
||||
* @param subscriber Subscriber
|
||||
* @param eventMask Bitmask of ValueListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(MultiSubscriber subscriber, int eventMask) {
|
||||
return NetworkTablesJNI.addPolledValueListener(m_handle, subscriber.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to value changes on an entry.
|
||||
*
|
||||
* @param entry Entry
|
||||
* @param eventMask Bitmask of ValueListenerFlags values
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int add(NetworkTableEntry entry, int eventMask) {
|
||||
return NetworkTablesJNI.addPolledValueListener(m_handle, entry.getHandle(), eventMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener.
|
||||
*
|
||||
* @param listener Listener handle
|
||||
*/
|
||||
public void remove(int listener) {
|
||||
NetworkTablesJNI.removeValueListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read value notifications.
|
||||
*
|
||||
* @return Value notifications since the previous call to readQueue()
|
||||
*/
|
||||
public ValueNotification[] readQueue() {
|
||||
return NetworkTablesJNI.readValueListenerQueue(m_inst, m_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
NetworkTablesJNI.destroyValueListenerPoller(m_handle);
|
||||
}
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the native handle is valid.
|
||||
*
|
||||
* @return True if the native handle is valid, false otherwise.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native handle.
|
||||
*
|
||||
* @return Handle
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
private final NetworkTableInstance m_inst;
|
||||
private int m_handle;
|
||||
}
|
||||
@@ -4,184 +4,16 @@
|
||||
|
||||
#include "ConnectionList.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
|
||||
#include <wpi/DataLog.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/SafeThread.h>
|
||||
#include <wpi/Synchronization.h>
|
||||
#include <wpi/UidVector.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/json_serializer.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "HandleMap.h"
|
||||
#include "IListenerStorage.h"
|
||||
#include "ntcore_c.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
namespace {
|
||||
|
||||
struct PollerData {
|
||||
static constexpr auto kType = Handle::kConnectionListenerPoller;
|
||||
|
||||
explicit PollerData(NT_ConnectionListenerPoller handle) : handle{handle} {}
|
||||
|
||||
wpi::SignalObject<NT_ConnectionListenerPoller> handle;
|
||||
std::vector<ConnectionNotification> queue;
|
||||
};
|
||||
|
||||
struct ListenerData {
|
||||
static constexpr auto kType = Handle::kConnectionListener;
|
||||
|
||||
ListenerData(NT_ConnectionListener handle, PollerData* poller)
|
||||
: handle{handle}, poller{poller} {}
|
||||
|
||||
wpi::SignalObject<NT_ConnectionListener> handle;
|
||||
PollerData* poller;
|
||||
};
|
||||
|
||||
struct DataLoggerData {
|
||||
static constexpr auto kType = Handle::kConnectionDataLogger;
|
||||
|
||||
DataLoggerData(NT_ConnectionDataLogger handle, wpi::log::DataLog& log,
|
||||
std::string_view name, int64_t time)
|
||||
: handle{handle},
|
||||
entry{log, name, "{\"schema\":\"NTConnectionInfo\",\"source\":\"NT\"}",
|
||||
"json", time} {}
|
||||
|
||||
NT_ConnectionDataLogger handle;
|
||||
wpi::log::StringLogEntry entry;
|
||||
};
|
||||
|
||||
class ListenerThread final : public wpi::SafeThreadEvent {
|
||||
public:
|
||||
explicit ListenerThread(NT_ConnectionListenerPoller poller)
|
||||
: m_poller{poller} {}
|
||||
|
||||
void Main() final;
|
||||
|
||||
NT_ConnectionListenerPoller m_poller;
|
||||
wpi::DenseMap<NT_ConnectionListener,
|
||||
std::function<void(const ConnectionNotification& event)>>
|
||||
m_callbacks;
|
||||
};
|
||||
|
||||
class CLImpl {
|
||||
public:
|
||||
explicit CLImpl(int inst) : m_inst{inst} {}
|
||||
|
||||
int m_inst;
|
||||
|
||||
// shared with user (must be atomic or mutex-protected)
|
||||
std::atomic_bool m_connected{false};
|
||||
wpi::UidVector<std::optional<ConnectionInfo>, 8> m_connections;
|
||||
|
||||
HandleMap<PollerData, 8> m_pollers;
|
||||
HandleMap<ListenerData, 8> m_listeners;
|
||||
HandleMap<DataLoggerData, 8> m_dataloggers;
|
||||
|
||||
wpi::SafeThreadOwner<ListenerThread> m_listenerThread;
|
||||
|
||||
NT_ConnectionListener AddListener(
|
||||
std::function<void(const ConnectionNotification& event)> callback,
|
||||
bool immediateNotify);
|
||||
PollerData* CreateListenerPoller() { return m_pollers.Add(m_inst); }
|
||||
void DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle);
|
||||
NT_ConnectionListener AddPolledListener(
|
||||
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify);
|
||||
void RemoveListener(NT_ConnectionListener listenerHandle);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void ListenerThread::Main() {
|
||||
while (m_active) {
|
||||
WPI_Handle signaledBuf[2];
|
||||
auto signaled =
|
||||
wpi::WaitForObjects({m_poller, m_stopEvent.GetHandle()}, signaledBuf);
|
||||
if (signaled.empty() || !m_active) {
|
||||
return;
|
||||
}
|
||||
// call all the way back out to the C++ API to ensure valid handle
|
||||
auto events = nt::ReadConnectionListenerQueue(m_poller);
|
||||
if (events.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::unique_lock lock{m_mutex};
|
||||
for (auto&& event : events) {
|
||||
auto callbackIt = m_callbacks.find(event.listener);
|
||||
if (callbackIt != m_callbacks.end()) {
|
||||
auto callback = callbackIt->second;
|
||||
lock.unlock();
|
||||
callback(event);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NT_ConnectionListener CLImpl::AddListener(
|
||||
std::function<void(const ConnectionNotification& event)> callback,
|
||||
bool immediateNotify) {
|
||||
if (!m_listenerThread) {
|
||||
m_listenerThread.Start(CreateListenerPoller()->handle);
|
||||
}
|
||||
if (auto thr = m_listenerThread.GetThread()) {
|
||||
auto listener = AddPolledListener(thr->m_poller, immediateNotify);
|
||||
if (listener) {
|
||||
thr->m_callbacks.try_emplace(listener, std::move(callback));
|
||||
}
|
||||
return listener;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void CLImpl::DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle) {
|
||||
if (auto poller = m_pollers.Remove(pollerHandle)) {
|
||||
// ensure all listeners that use this poller are removed
|
||||
wpi::SmallVector<NT_ConnectionListener, 16> toRemove;
|
||||
for (auto&& listener : m_listeners) {
|
||||
if (listener->poller == poller.get()) {
|
||||
toRemove.emplace_back(listener->handle);
|
||||
}
|
||||
}
|
||||
for (auto handle : toRemove) {
|
||||
RemoveListener(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NT_ConnectionListener CLImpl::AddPolledListener(
|
||||
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify) {
|
||||
auto poller = m_pollers.Get(pollerHandle);
|
||||
if (!poller) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto listener = m_listeners.Add(m_inst, poller);
|
||||
if (immediateNotify && !m_connections.empty()) {
|
||||
for (auto&& conn : m_connections) {
|
||||
listener->poller->queue.emplace_back(listener->handle.GetHandle(), true,
|
||||
*conn);
|
||||
}
|
||||
listener->poller->handle.Set();
|
||||
listener->handle.Set();
|
||||
}
|
||||
return listener->handle;
|
||||
}
|
||||
|
||||
void CLImpl::RemoveListener(NT_ConnectionListener listenerHandle) {
|
||||
if (auto listener = m_listeners.Remove(listenerHandle)) {
|
||||
if (auto thr = m_listenerThread.GetThread()) {
|
||||
if (thr->m_poller == listener->poller->handle) {
|
||||
thr->m_callbacks.erase(listenerHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string ConnInfoToJson(bool connected, const ConnectionInfo& info) {
|
||||
std::string str;
|
||||
wpi::raw_string_ostream os{str};
|
||||
@@ -200,50 +32,35 @@ static std::string ConnInfoToJson(bool connected, const ConnectionInfo& info) {
|
||||
return str;
|
||||
}
|
||||
|
||||
class ConnectionList::Impl : public CLImpl {
|
||||
public:
|
||||
explicit Impl(int inst) : CLImpl{inst} {}
|
||||
};
|
||||
|
||||
ConnectionList::ConnectionList(int inst)
|
||||
: m_impl{std::make_unique<Impl>(inst)} {}
|
||||
ConnectionList::ConnectionList(int inst, IListenerStorage& listenerStorage)
|
||||
: m_inst{inst}, m_listenerStorage{listenerStorage} {}
|
||||
|
||||
ConnectionList::~ConnectionList() = default;
|
||||
|
||||
int ConnectionList::AddConnection(const ConnectionInfo& info) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_impl->m_connected = true;
|
||||
for (auto&& listener : m_impl->m_listeners) {
|
||||
listener->poller->queue.emplace_back(listener->handle.GetHandle(), true,
|
||||
info);
|
||||
listener->poller->handle.Set();
|
||||
listener->handle.Set();
|
||||
}
|
||||
if (!m_impl->m_dataloggers.empty()) {
|
||||
m_connected = true;
|
||||
m_listenerStorage.Notify({}, NT_EVENT_CONNECTED, &info);
|
||||
if (!m_dataloggers.empty()) {
|
||||
auto now = Now();
|
||||
for (auto&& datalogger : m_impl->m_dataloggers) {
|
||||
for (auto&& datalogger : m_dataloggers) {
|
||||
datalogger->entry.Append(ConnInfoToJson(true, info), now);
|
||||
}
|
||||
}
|
||||
return m_impl->m_connections.emplace_back(info);
|
||||
return m_connections.emplace_back(info);
|
||||
}
|
||||
|
||||
void ConnectionList::RemoveConnection(int handle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto val = m_impl->m_connections.erase(handle);
|
||||
if (m_impl->m_connections.empty()) {
|
||||
m_impl->m_connected = false;
|
||||
auto val = m_connections.erase(handle);
|
||||
if (m_connections.empty()) {
|
||||
m_connected = false;
|
||||
}
|
||||
if (val) {
|
||||
for (auto&& listener : m_impl->m_listeners) {
|
||||
listener->poller->queue.emplace_back(listener->handle.GetHandle(), false,
|
||||
*val);
|
||||
listener->poller->handle.Set();
|
||||
listener->handle.Set();
|
||||
}
|
||||
if (!m_impl->m_dataloggers.empty()) {
|
||||
m_listenerStorage.Notify({}, NT_EVENT_DISCONNECTED, &(*val));
|
||||
if (!m_dataloggers.empty()) {
|
||||
auto now = Now();
|
||||
for (auto&& datalogger : m_impl->m_dataloggers) {
|
||||
for (auto&& datalogger : m_dataloggers) {
|
||||
datalogger->entry.Append(ConnInfoToJson(false, *val), now);
|
||||
}
|
||||
}
|
||||
@@ -252,79 +69,50 @@ void ConnectionList::RemoveConnection(int handle) {
|
||||
|
||||
void ConnectionList::ClearConnections() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_impl->m_connected = false;
|
||||
for (auto&& conn : m_impl->m_connections) {
|
||||
for (auto&& listener : m_impl->m_listeners) {
|
||||
listener->poller->queue.emplace_back(listener->handle.GetHandle(), false,
|
||||
*conn);
|
||||
listener->poller->handle.Set();
|
||||
listener->handle.Set();
|
||||
}
|
||||
m_connected = false;
|
||||
for (auto&& conn : m_connections) {
|
||||
m_listenerStorage.Notify({}, NT_EVENT_DISCONNECTED, &(*conn));
|
||||
}
|
||||
m_impl->m_connections.clear();
|
||||
m_connections.clear();
|
||||
}
|
||||
|
||||
std::vector<ConnectionInfo> ConnectionList::GetConnections() const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::vector<ConnectionInfo> info;
|
||||
info.reserve(m_impl->m_connections.size());
|
||||
for (auto&& conn : m_impl->m_connections) {
|
||||
info.reserve(m_connections.size());
|
||||
for (auto&& conn : m_connections) {
|
||||
info.emplace_back(*conn);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
bool ConnectionList::IsConnected() const {
|
||||
return m_impl->m_connected;
|
||||
return m_connected;
|
||||
}
|
||||
|
||||
NT_ConnectionListener ConnectionList::AddListener(
|
||||
std::function<void(const ConnectionNotification& event)> callback,
|
||||
bool immediateNotify) {
|
||||
void ConnectionList::AddListener(NT_Listener listener, unsigned int eventMask) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_impl->AddListener(std::move(callback), immediateNotify);
|
||||
}
|
||||
|
||||
NT_ConnectionListenerPoller ConnectionList::CreateListenerPoller() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_impl->CreateListenerPoller()->handle;
|
||||
}
|
||||
|
||||
void ConnectionList::DestroyListenerPoller(
|
||||
NT_ConnectionListenerPoller pollerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_impl->DestroyListenerPoller(pollerHandle);
|
||||
}
|
||||
|
||||
NT_ConnectionListener ConnectionList::AddPolledListener(
|
||||
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_impl->AddPolledListener(pollerHandle, immediateNotify);
|
||||
}
|
||||
|
||||
std::vector<ConnectionNotification> ConnectionList::ReadListenerQueue(
|
||||
NT_ConnectionListenerPoller pollerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (auto poller = m_impl->m_pollers.Get(pollerHandle)) {
|
||||
std::vector<ConnectionNotification> rv;
|
||||
rv.swap(poller->queue);
|
||||
return rv;
|
||||
} else {
|
||||
return {};
|
||||
eventMask &= (NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE);
|
||||
m_listenerStorage.Activate(listener, eventMask);
|
||||
if ((eventMask & (NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE)) ==
|
||||
(NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE) &&
|
||||
!m_connections.empty()) {
|
||||
wpi::SmallVector<const ConnectionInfo*, 16> infos;
|
||||
infos.reserve(m_connections.size());
|
||||
for (auto&& conn : m_connections) {
|
||||
infos.emplace_back(&(*conn));
|
||||
}
|
||||
m_listenerStorage.Notify({&listener, 1},
|
||||
NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE, infos);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionList::RemoveListener(NT_ConnectionListener listenerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_impl->RemoveListener(listenerHandle);
|
||||
}
|
||||
|
||||
NT_ConnectionDataLogger ConnectionList::StartDataLog(wpi::log::DataLog& log,
|
||||
std::string_view name) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto now = Now();
|
||||
auto datalogger = m_impl->m_dataloggers.Add(m_impl->m_inst, log, name, now);
|
||||
for (auto&& conn : m_impl->m_connections) {
|
||||
auto datalogger = m_dataloggers.Add(m_inst, log, name, now);
|
||||
for (auto&& conn : m_connections) {
|
||||
datalogger->entry.Append(ConnInfoToJson(true, *conn), now);
|
||||
}
|
||||
return datalogger->handle;
|
||||
@@ -332,7 +120,7 @@ NT_ConnectionDataLogger ConnectionList::StartDataLog(wpi::log::DataLog& log,
|
||||
|
||||
void ConnectionList::StopDataLog(NT_ConnectionDataLogger logger) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (auto datalogger = m_impl->m_dataloggers.Remove(logger)) {
|
||||
if (auto datalogger = m_dataloggers.Remove(logger)) {
|
||||
datalogger->entry.Finish(Now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,21 +4,31 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/DataLog.h>
|
||||
#include <wpi/UidVector.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "HandleMap.h"
|
||||
#include "IConnectionList.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class IListenerStorage;
|
||||
|
||||
class ConnectionList final : public IConnectionList {
|
||||
public:
|
||||
explicit ConnectionList(int inst);
|
||||
ConnectionList(int inst, IListenerStorage& listenerStorage);
|
||||
~ConnectionList() final;
|
||||
|
||||
// IConnectionList interface
|
||||
@@ -30,27 +40,35 @@ class ConnectionList final : public IConnectionList {
|
||||
std::vector<ConnectionInfo> GetConnections() const final;
|
||||
bool IsConnected() const final;
|
||||
|
||||
NT_ConnectionListener AddListener(
|
||||
std::function<void(const ConnectionNotification& event)> callback,
|
||||
bool immediateNotify);
|
||||
|
||||
NT_ConnectionListenerPoller CreateListenerPoller();
|
||||
void DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle);
|
||||
NT_ConnectionListener AddPolledListener(
|
||||
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify);
|
||||
std::vector<ConnectionNotification> ReadListenerQueue(
|
||||
NT_ConnectionListenerPoller pollerHandle);
|
||||
void RemoveListener(NT_ConnectionListener listenerHandle);
|
||||
void AddListener(NT_Listener listener, unsigned int eventMask);
|
||||
|
||||
NT_ConnectionDataLogger StartDataLog(wpi::log::DataLog& log,
|
||||
std::string_view name);
|
||||
void StopDataLog(NT_ConnectionDataLogger logger);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
|
||||
int m_inst;
|
||||
IListenerStorage& m_listenerStorage;
|
||||
mutable wpi::mutex m_mutex;
|
||||
|
||||
// shared with user (must be atomic or mutex-protected)
|
||||
std::atomic_bool m_connected{false};
|
||||
wpi::UidVector<std::optional<ConnectionInfo>, 8> m_connections;
|
||||
|
||||
struct DataLoggerData {
|
||||
static constexpr auto kType = Handle::kConnectionDataLogger;
|
||||
|
||||
DataLoggerData(NT_ConnectionDataLogger handle, wpi::log::DataLog& log,
|
||||
std::string_view name, int64_t time)
|
||||
: handle{handle},
|
||||
entry{log, name,
|
||||
"{\"schema\":\"NTConnectionInfo\",\"source\":\"NT\"}", "json",
|
||||
time} {}
|
||||
|
||||
NT_ConnectionDataLogger handle;
|
||||
wpi::log::StringLogEntry entry;
|
||||
};
|
||||
HandleMap<DataLoggerData, 8> m_dataloggers;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -18,24 +18,16 @@ namespace nt {
|
||||
class Handle {
|
||||
public:
|
||||
enum Type {
|
||||
kConnectionListener = wpi::kHandleTypeNTBase,
|
||||
kConnectionListenerPoller,
|
||||
kListener = wpi::kHandleTypeNTBase,
|
||||
kListenerPoller,
|
||||
kEntry,
|
||||
kEntryListener,
|
||||
kEntryListenerPoller,
|
||||
kInstance,
|
||||
kLogger,
|
||||
kLoggerPoller,
|
||||
kDataLogger,
|
||||
kConnectionDataLogger,
|
||||
kMultiSubscriber,
|
||||
kTopic,
|
||||
kTopicListener,
|
||||
kTopicListenerPoller,
|
||||
kSubscriber,
|
||||
kPublisher,
|
||||
kValueListener,
|
||||
kValueListenerPoller,
|
||||
kTypeMax
|
||||
};
|
||||
static_assert(kTypeMax <= wpi::kHandleTypeHALBase);
|
||||
|
||||
49
ntcore/src/main/native/cpp/IListenerStorage.h
Normal file
49
ntcore/src/main/native/cpp/IListenerStorage.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class IListenerStorage {
|
||||
public:
|
||||
// Return false if event should not be issued (final check).
|
||||
// This is called only during Notify() processing.
|
||||
using FinishEventFunc = std::function<bool(unsigned int mask, Event* event)>;
|
||||
|
||||
virtual ~IListenerStorage() = default;
|
||||
|
||||
virtual void Activate(NT_Listener listener, unsigned int mask,
|
||||
FinishEventFunc finishEvent = {}) = 0;
|
||||
|
||||
// If handles is not empty, notifies ONLY those listeners
|
||||
virtual void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
std::span<ConnectionInfo const* const> infos) = 0;
|
||||
virtual void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
std::span<const TopicInfo> infos) = 0;
|
||||
virtual void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
NT_Topic topic, NT_Handle subentry,
|
||||
const Value& value) = 0;
|
||||
virtual void Notify(unsigned int flags, unsigned int level,
|
||||
std::string_view filename, unsigned int line,
|
||||
std::string_view message) = 0;
|
||||
|
||||
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
const ConnectionInfo* info) {
|
||||
Notify(handles, flags, {&info, 1});
|
||||
}
|
||||
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
const TopicInfo& info) {
|
||||
Notify(handles, flags, {&info, 1});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
@@ -13,11 +13,12 @@ wpi::mutex InstanceImpl::s_mutex;
|
||||
using namespace std::placeholders;
|
||||
|
||||
InstanceImpl::InstanceImpl(int inst)
|
||||
: logger_impl(inst),
|
||||
logger(
|
||||
std::bind(&LoggerImpl::Log, &logger_impl, _1, _2, _3, _4)), // NOLINT
|
||||
connectionList(inst),
|
||||
localStorage(inst, logger),
|
||||
: listenerStorage{inst},
|
||||
logger_impl{listenerStorage},
|
||||
logger{
|
||||
std::bind(&LoggerImpl::Log, &logger_impl, _1, _2, _3, _4)}, // NOLINT
|
||||
connectionList{inst, listenerStorage},
|
||||
localStorage{inst, listenerStorage, logger},
|
||||
m_inst{inst} {
|
||||
logger.set_min_level(logger_impl.GetMinLevel());
|
||||
}
|
||||
@@ -120,23 +121,29 @@ void InstanceImpl::StopServer() {
|
||||
networkMode = NT_NET_MODE_NONE;
|
||||
}
|
||||
|
||||
void InstanceImpl::StartClient3() {
|
||||
void InstanceImpl::StartClient3(std::string_view identity) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (networkMode != NT_NET_MODE_NONE) {
|
||||
return;
|
||||
}
|
||||
m_networkClient = std::make_shared<NetworkClient3>(
|
||||
m_inst, m_identity, localStorage, connectionList, logger);
|
||||
m_inst, identity, localStorage, connectionList, logger);
|
||||
if (!m_servers.empty()) {
|
||||
m_networkClient->SetServers(m_servers);
|
||||
}
|
||||
networkMode = NT_NET_MODE_CLIENT3;
|
||||
}
|
||||
|
||||
void InstanceImpl::StartClient4() {
|
||||
void InstanceImpl::StartClient4(std::string_view identity) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (networkMode != NT_NET_MODE_NONE) {
|
||||
return;
|
||||
}
|
||||
m_networkClient = std::make_shared<NetworkClient>(
|
||||
m_inst, m_identity, localStorage, connectionList, logger);
|
||||
m_inst, identity, localStorage, connectionList, logger);
|
||||
if (!m_servers.empty()) {
|
||||
m_networkClient->SetServers(m_servers);
|
||||
}
|
||||
networkMode = NT_NET_MODE_CLIENT4;
|
||||
}
|
||||
|
||||
@@ -149,9 +156,13 @@ void InstanceImpl::StopClient() {
|
||||
networkMode = NT_NET_MODE_NONE;
|
||||
}
|
||||
|
||||
void InstanceImpl::SetIdentity(std::string_view identity) {
|
||||
void InstanceImpl::SetServers(
|
||||
std::span<const std::pair<std::string, unsigned int>> servers) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_identity = identity;
|
||||
m_servers = {servers.begin(), servers.end()};
|
||||
if (m_networkClient) {
|
||||
m_networkClient->SetServers(servers);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<NetworkServer> InstanceImpl::GetServer() {
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "ConnectionList.h"
|
||||
#include "Handle.h"
|
||||
#include "ListenerStorage.h"
|
||||
#include "LocalStorage.h"
|
||||
#include "Log.h"
|
||||
#include "LoggerImpl.h"
|
||||
@@ -45,14 +47,16 @@ class InstanceImpl {
|
||||
std::string_view listenAddress, unsigned int port3,
|
||||
unsigned int port4);
|
||||
void StopServer();
|
||||
void StartClient3();
|
||||
void StartClient4();
|
||||
void StartClient3(std::string_view identity);
|
||||
void StartClient4(std::string_view identity);
|
||||
void StopClient();
|
||||
void SetIdentity(std::string_view identity);
|
||||
void SetServers(
|
||||
std::span<const std::pair<std::string, unsigned int>> servers);
|
||||
|
||||
std::shared_ptr<NetworkServer> GetServer();
|
||||
std::shared_ptr<INetworkClient> GetClient();
|
||||
|
||||
ListenerStorage listenerStorage;
|
||||
LoggerImpl logger_impl;
|
||||
wpi::Logger logger;
|
||||
ConnectionList connectionList;
|
||||
@@ -68,9 +72,9 @@ class InstanceImpl {
|
||||
static wpi::mutex s_mutex;
|
||||
|
||||
wpi::mutex m_mutex;
|
||||
std::string m_identity;
|
||||
std::shared_ptr<NetworkServer> m_networkServer;
|
||||
std::shared_ptr<INetworkClient> m_networkClient;
|
||||
std::vector<std::pair<std::string, unsigned int>> m_servers;
|
||||
int m_inst;
|
||||
};
|
||||
|
||||
|
||||
351
ntcore/src/main/native/cpp/ListenerStorage.cpp
Normal file
351
ntcore/src/main/native/cpp/ListenerStorage.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
// 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 "ListenerStorage.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "ntcore_c.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
class ListenerStorage::Thread final : public wpi::SafeThreadEvent {
|
||||
public:
|
||||
explicit Thread(NT_ListenerPoller poller) : m_poller{poller} {}
|
||||
|
||||
void Main() final;
|
||||
|
||||
NT_ListenerPoller m_poller;
|
||||
wpi::DenseMap<NT_Listener, ListenerCallback> m_callbacks;
|
||||
wpi::Event m_waitQueueWakeup;
|
||||
wpi::Event m_waitQueueWaiter;
|
||||
};
|
||||
|
||||
void ListenerStorage::Thread::Main() {
|
||||
while (m_active) {
|
||||
WPI_Handle signaledBuf[3];
|
||||
auto signaled = wpi::WaitForObjects(
|
||||
{m_poller, m_stopEvent.GetHandle(), m_waitQueueWakeup.GetHandle()},
|
||||
signaledBuf);
|
||||
if (signaled.empty() || !m_active) {
|
||||
return;
|
||||
}
|
||||
// call all the way back out to the C++ API to ensure valid handle
|
||||
auto events = nt::ReadListenerQueue(m_poller);
|
||||
if (events.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::unique_lock lock{m_mutex};
|
||||
for (auto&& event : events) {
|
||||
auto callbackIt = m_callbacks.find(event.listener);
|
||||
if (callbackIt != m_callbacks.end()) {
|
||||
auto callback = callbackIt->second;
|
||||
lock.unlock();
|
||||
callback(event);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
if (std::find(signaled.begin(), signaled.end(),
|
||||
m_waitQueueWakeup.GetHandle()) != signaled.end()) {
|
||||
m_waitQueueWaiter.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListenerStorage::ListenerStorage(int inst) : m_inst{inst} {}
|
||||
|
||||
ListenerStorage::~ListenerStorage() = default;
|
||||
|
||||
void ListenerStorage::Activate(NT_Listener listenerHandle, unsigned int mask,
|
||||
FinishEventFunc finishEvent) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (auto listener = m_listeners.Get(listenerHandle)) {
|
||||
listener->sources.emplace_back(std::move(finishEvent), mask);
|
||||
unsigned int deltaMask = mask & (~listener->eventMask);
|
||||
listener->eventMask |= mask;
|
||||
|
||||
if ((deltaMask & NT_EVENT_CONNECTION) != 0) {
|
||||
m_connListeners.Add(listener);
|
||||
}
|
||||
if ((deltaMask & NT_EVENT_TOPIC) != 0) {
|
||||
m_topicListeners.Add(listener);
|
||||
}
|
||||
if ((deltaMask & NT_EVENT_VALUE_ALL) != 0) {
|
||||
m_valueListeners.Add(listener);
|
||||
}
|
||||
// detect the higher log bits too; see LoggerImpl
|
||||
if ((deltaMask & NT_EVENT_LOGMESSAGE) != 0 ||
|
||||
(deltaMask & 0x1ff0000) != 0) {
|
||||
m_logListeners.Add(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListenerStorage::Notify(std::span<const NT_Listener> handles,
|
||||
unsigned int flags,
|
||||
std::span<ConnectionInfo const* const> infos) {
|
||||
if (flags == 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
auto doSignal = [&](ListenerData& listener) {
|
||||
if ((flags & listener.eventMask) != 0) {
|
||||
for (auto&& [finishEvent, mask] : listener.sources) {
|
||||
if ((flags & mask) != 0) {
|
||||
for (auto&& info : infos) {
|
||||
listener.poller->queue.emplace_back(listener.handle, flags, *info);
|
||||
// finishEvent is never set (see ConnectionList)
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.handle.Set();
|
||||
listener.poller->handle.Set();
|
||||
}
|
||||
};
|
||||
|
||||
if (!handles.empty()) {
|
||||
for (auto handle : handles) {
|
||||
if (auto listener = m_listeners.Get(handle)) {
|
||||
doSignal(*listener);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto&& listener : m_connListeners) {
|
||||
doSignal(*listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListenerStorage::Notify(std::span<const NT_Listener> handles,
|
||||
unsigned int flags,
|
||||
std::span<const TopicInfo> infos) {
|
||||
if (flags == 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
auto doSignal = [&](ListenerData& listener) {
|
||||
if ((flags & listener.eventMask) != 0) {
|
||||
int count = 0;
|
||||
for (auto&& [finishEvent, mask] : listener.sources) {
|
||||
if ((flags & mask) != 0) {
|
||||
for (auto&& info : infos) {
|
||||
listener.poller->queue.emplace_back(listener.handle, flags, info);
|
||||
if (finishEvent &&
|
||||
!finishEvent(mask, &listener.poller->queue.back())) {
|
||||
listener.poller->queue.pop_back();
|
||||
} else {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
listener.handle.Set();
|
||||
listener.poller->handle.Set();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!handles.empty()) {
|
||||
for (auto handle : handles) {
|
||||
if (auto listener = m_listeners.Get(handle)) {
|
||||
doSignal(*listener);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto&& listener : m_topicListeners) {
|
||||
doSignal(*listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListenerStorage::Notify(std::span<const NT_Listener> handles,
|
||||
unsigned int flags, NT_Topic topic,
|
||||
NT_Handle subentry, const Value& value) {
|
||||
if (flags == 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
auto doSignal = [&](ListenerData& listener) {
|
||||
if ((flags & listener.eventMask) != 0) {
|
||||
int count = 0;
|
||||
for (auto&& [finishEvent, mask] : listener.sources) {
|
||||
if ((flags & mask) != 0) {
|
||||
listener.poller->queue.emplace_back(listener.handle, flags, topic,
|
||||
subentry, value);
|
||||
if (finishEvent &&
|
||||
!finishEvent(mask, &listener.poller->queue.back())) {
|
||||
listener.poller->queue.pop_back();
|
||||
} else {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
listener.handle.Set();
|
||||
listener.poller->handle.Set();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!handles.empty()) {
|
||||
for (auto handle : handles) {
|
||||
if (auto listener = m_listeners.Get(handle)) {
|
||||
doSignal(*listener);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto&& listener : m_valueListeners) {
|
||||
doSignal(*listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListenerStorage::Notify(unsigned int flags, unsigned int level,
|
||||
std::string_view filename, unsigned int line,
|
||||
std::string_view message) {
|
||||
if (flags == 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
for (auto&& listener : m_logListeners) {
|
||||
if ((flags & listener->eventMask) != 0) {
|
||||
int count = 0;
|
||||
for (auto&& [finishEvent, mask] : listener->sources) {
|
||||
if ((flags & mask) != 0) {
|
||||
listener->poller->queue.emplace_back(listener->handle, flags, level,
|
||||
filename, line, message);
|
||||
if (finishEvent &&
|
||||
!finishEvent(mask, &listener->poller->queue.back())) {
|
||||
listener->poller->queue.pop_back();
|
||||
} else {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
listener->handle.Set();
|
||||
listener->poller->handle.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NT_Listener ListenerStorage::AddListener(ListenerCallback callback) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (!m_thread) {
|
||||
m_thread.Start(m_pollers.Add(m_inst)->handle);
|
||||
}
|
||||
if (auto thr = m_thread.GetThread()) {
|
||||
auto listener = DoAddListener(thr->m_poller);
|
||||
if (listener) {
|
||||
thr->m_callbacks.try_emplace(listener, std::move(callback));
|
||||
}
|
||||
return listener;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
NT_Listener ListenerStorage::AddListener(NT_ListenerPoller pollerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return DoAddListener(pollerHandle);
|
||||
}
|
||||
|
||||
NT_Listener ListenerStorage::DoAddListener(NT_ListenerPoller pollerHandle) {
|
||||
if (auto poller = m_pollers.Get(pollerHandle)) {
|
||||
return m_listeners.Add(m_inst, poller)->handle;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
NT_ListenerPoller ListenerStorage::CreateListenerPoller() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_pollers.Add(m_inst)->handle;
|
||||
}
|
||||
|
||||
std::vector<std::pair<NT_Listener, unsigned int>>
|
||||
ListenerStorage::DestroyListenerPoller(NT_ListenerPoller pollerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (auto poller = m_pollers.Remove(pollerHandle)) {
|
||||
// ensure all listeners that use this poller are removed
|
||||
wpi::SmallVector<NT_Listener, 16> toRemove;
|
||||
for (auto&& listener : m_listeners) {
|
||||
if (listener->poller == poller.get()) {
|
||||
toRemove.emplace_back(listener->handle);
|
||||
}
|
||||
}
|
||||
return DoRemoveListeners(toRemove);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Event> ListenerStorage::ReadListenerQueue(
|
||||
NT_ListenerPoller pollerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (auto poller = m_pollers.Get(pollerHandle)) {
|
||||
std::vector<Event> rv;
|
||||
rv.swap(poller->queue);
|
||||
return rv;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<NT_Listener, unsigned int>>
|
||||
ListenerStorage::RemoveListener(NT_Listener listenerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return DoRemoveListeners({&listenerHandle, 1});
|
||||
}
|
||||
|
||||
bool ListenerStorage::WaitForListenerQueue(double timeout) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
WPI_EventHandle h;
|
||||
if (auto thr = m_thread.GetThread()) {
|
||||
h = thr->m_waitQueueWaiter.GetHandle();
|
||||
thr->m_waitQueueWakeup.Set();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
bool timedOut;
|
||||
return wpi::WaitForObject(h, timeout, &timedOut);
|
||||
}
|
||||
|
||||
std::vector<std::pair<NT_Listener, unsigned int>>
|
||||
ListenerStorage::DoRemoveListeners(std::span<const NT_Listener> handles) {
|
||||
std::vector<std::pair<NT_Listener, unsigned int>> rv;
|
||||
auto thr = m_thread.GetThread();
|
||||
for (auto handle : handles) {
|
||||
if (auto listener = m_listeners.Remove(handle)) {
|
||||
rv.emplace_back(handle, listener->eventMask);
|
||||
if (thr) {
|
||||
if (thr->m_poller == listener->poller->handle) {
|
||||
thr->m_callbacks.erase(handle);
|
||||
}
|
||||
}
|
||||
if ((listener->eventMask & NT_EVENT_CONNECTION) != 0) {
|
||||
m_connListeners.Remove(listener.get());
|
||||
}
|
||||
if ((listener->eventMask & NT_EVENT_TOPIC) != 0) {
|
||||
m_topicListeners.Remove(listener.get());
|
||||
}
|
||||
if ((listener->eventMask & NT_EVENT_VALUE_ALL) != 0) {
|
||||
m_valueListeners.Remove(listener.get());
|
||||
}
|
||||
if ((listener->eventMask & NT_EVENT_LOGMESSAGE) != 0 ||
|
||||
(listener->eventMask & 0x1ff0000) != 0) {
|
||||
m_logListeners.Remove(listener.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
111
ntcore/src/main/native/cpp/ListenerStorage.h
Normal file
111
ntcore/src/main/native/cpp/ListenerStorage.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SafeThread.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/Synchronization.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "HandleMap.h"
|
||||
#include "IListenerStorage.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class ListenerStorage final : public IListenerStorage {
|
||||
public:
|
||||
explicit ListenerStorage(int inst);
|
||||
ListenerStorage(const ListenerStorage&) = delete;
|
||||
ListenerStorage& operator=(const ListenerStorage&) = delete;
|
||||
~ListenerStorage() final;
|
||||
|
||||
// IListenerStorage interface
|
||||
void Activate(NT_Listener listenerHandle, unsigned int mask,
|
||||
FinishEventFunc finishEvent = {}) final;
|
||||
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
std::span<ConnectionInfo const* const> infos) final;
|
||||
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
std::span<const TopicInfo> infos) final;
|
||||
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
|
||||
NT_Topic topic, NT_Handle subentry, const Value& value) final;
|
||||
void Notify(unsigned int flags, unsigned int level, std::string_view filename,
|
||||
unsigned int line, std::string_view message) final;
|
||||
|
||||
// user-facing functions
|
||||
NT_Listener AddListener(ListenerCallback callback);
|
||||
NT_Listener AddListener(NT_ListenerPoller pollerHandle);
|
||||
NT_ListenerPoller CreateListenerPoller();
|
||||
|
||||
// returns listener handle and mask for each listener that was destroyed
|
||||
[[nodiscard]] std::vector<std::pair<NT_Listener, unsigned int>>
|
||||
DestroyListenerPoller(NT_ListenerPoller pollerHandle);
|
||||
|
||||
std::vector<Event> ReadListenerQueue(NT_ListenerPoller pollerHandle);
|
||||
|
||||
// returns listener handle and mask for each listener that was destroyed
|
||||
[[nodiscard]] std::vector<std::pair<NT_Listener, unsigned int>>
|
||||
RemoveListener(NT_Listener listenerHandle);
|
||||
|
||||
bool WaitForListenerQueue(double timeout);
|
||||
|
||||
private:
|
||||
// these assume the mutex is already held
|
||||
NT_Listener DoAddListener(NT_ListenerPoller pollerHandle);
|
||||
std::vector<std::pair<NT_Listener, unsigned int>> DoRemoveListeners(
|
||||
std::span<const NT_Listener> handles);
|
||||
|
||||
int m_inst;
|
||||
mutable wpi::mutex m_mutex;
|
||||
|
||||
struct PollerData {
|
||||
static constexpr auto kType = Handle::kListenerPoller;
|
||||
|
||||
explicit PollerData(NT_ListenerPoller handle) : handle{handle} {}
|
||||
|
||||
wpi::SignalObject<NT_ListenerPoller> handle;
|
||||
std::vector<Event> queue;
|
||||
};
|
||||
HandleMap<PollerData, 8> m_pollers;
|
||||
|
||||
struct ListenerData {
|
||||
static constexpr auto kType = Handle::kListener;
|
||||
|
||||
ListenerData(NT_Listener handle, PollerData* poller)
|
||||
: handle{handle}, poller{poller} {}
|
||||
|
||||
wpi::SignalObject<NT_Listener> handle;
|
||||
PollerData* poller;
|
||||
wpi::SmallVector<std::pair<FinishEventFunc, unsigned int>, 2> sources;
|
||||
unsigned int eventMask{0};
|
||||
};
|
||||
HandleMap<ListenerData, 8> m_listeners;
|
||||
|
||||
// Utility wrapper for making a set-like vector
|
||||
template <typename T>
|
||||
class VectorSet : public std::vector<T> {
|
||||
public:
|
||||
void Add(T value) { this->push_back(value); }
|
||||
void Remove(T value) { std::erase(*this, value); }
|
||||
};
|
||||
|
||||
VectorSet<ListenerData*> m_connListeners;
|
||||
VectorSet<ListenerData*> m_topicListeners;
|
||||
VectorSet<ListenerData*> m_valueListeners;
|
||||
VectorSet<ListenerData*> m_logListeners;
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_thread;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,9 +25,12 @@ class Logger;
|
||||
|
||||
namespace nt {
|
||||
|
||||
class IListenerStorage;
|
||||
|
||||
class LocalStorage final : public net::ILocalStorage {
|
||||
public:
|
||||
LocalStorage(int inst, wpi::Logger& logger);
|
||||
LocalStorage(int inst, IListenerStorage& listenerStorage,
|
||||
wpi::Logger& logger);
|
||||
LocalStorage(const LocalStorage&) = delete;
|
||||
LocalStorage& operator=(const LocalStorage&) = delete;
|
||||
~LocalStorage() final;
|
||||
@@ -41,8 +44,8 @@ class LocalStorage final : public net::ILocalStorage {
|
||||
bool ack) final;
|
||||
void NetworkSetValue(NT_Topic topicHandle, const Value& value) final;
|
||||
|
||||
void StartNetwork(net::NetworkStartupInterface& startup) final;
|
||||
void SetNetwork(net::NetworkInterface* network) final;
|
||||
void StartNetwork(net::NetworkStartupInterface& startup,
|
||||
net::NetworkInterface* network) final;
|
||||
void ClearNetwork() final;
|
||||
|
||||
// User functions. These are the actual implementations of the corresponding
|
||||
@@ -189,49 +192,15 @@ class LocalStorage final : public net::ILocalStorage {
|
||||
int64_t GetEntryLastChange(NT_Entry entry);
|
||||
|
||||
//
|
||||
// Topic listener functions
|
||||
// Listener functions
|
||||
//
|
||||
|
||||
NT_TopicListener AddTopicListener(
|
||||
std::span<const std::string_view> prefixes, unsigned int mask,
|
||||
std::function<void(const TopicNotification&)> callback);
|
||||
NT_TopicListener AddTopicListener(
|
||||
NT_Handle handle, unsigned int mask,
|
||||
std::function<void(const TopicNotification&)> callback);
|
||||
void AddListener(NT_Listener listener,
|
||||
std::span<const std::string_view> prefixes,
|
||||
unsigned int mask);
|
||||
void AddListener(NT_Listener listener, NT_Handle handle, unsigned int mask);
|
||||
|
||||
NT_TopicListenerPoller CreateTopicListenerPoller();
|
||||
void DestroyTopicListenerPoller(NT_TopicListenerPoller poller);
|
||||
|
||||
NT_TopicListener AddPolledTopicListener(
|
||||
NT_TopicListenerPoller poller, std::span<const std::string_view> prefixes,
|
||||
unsigned int mask);
|
||||
NT_TopicListener AddPolledTopicListener(NT_TopicListenerPoller poller,
|
||||
NT_Handle handle, unsigned int mask);
|
||||
|
||||
std::vector<TopicNotification> ReadTopicListenerQueue(
|
||||
NT_TopicListenerPoller poller);
|
||||
|
||||
void RemoveTopicListener(NT_TopicListener listener);
|
||||
|
||||
//
|
||||
// Value listener functions
|
||||
//
|
||||
|
||||
NT_ValueListener AddValueListener(
|
||||
NT_Handle subentry, unsigned int mask,
|
||||
std::function<void(const ValueNotification&)> callback);
|
||||
|
||||
NT_ValueListenerPoller CreateValueListenerPoller();
|
||||
void DestroyValueListenerPoller(NT_ValueListenerPoller poller);
|
||||
|
||||
NT_ValueListener AddPolledValueListener(NT_ValueListenerPoller poller,
|
||||
NT_Handle subentry,
|
||||
unsigned int mask);
|
||||
|
||||
std::vector<ValueNotification> ReadValueListenerQueue(
|
||||
NT_ValueListenerPoller poller);
|
||||
|
||||
void RemoveValueListener(NT_ValueListener listener);
|
||||
void RemoveListener(NT_Listener listener, unsigned int mask);
|
||||
|
||||
//
|
||||
// Data log functions
|
||||
|
||||
@@ -6,15 +6,23 @@
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
|
||||
#define LOG(level, format, ...) WPI_LOG(m_logger, level, format, __VA_ARGS__)
|
||||
#define LOG(level, format, ...) \
|
||||
WPI_LOG(m_logger, level, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(format, ...) WPI_ERROR(m_logger, format, __VA_ARGS__)
|
||||
#define WARNING(format, ...) WPI_WARNING(m_logger, format, __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format, __VA_ARGS__)
|
||||
#define ERROR(format, ...) \
|
||||
WPI_ERROR(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define WARNING(format, ...) \
|
||||
WPI_WARNING(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define DEBUG0(format, ...) WPI_DEBUG(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) WPI_DEBUG1(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) WPI_DEBUG2(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) WPI_DEBUG3(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) WPI_DEBUG4(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG0(format, ...) \
|
||||
WPI_DEBUG(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) \
|
||||
WPI_DEBUG1(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) \
|
||||
WPI_DEBUG2(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) \
|
||||
WPI_DEBUG3(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) \
|
||||
WPI_DEBUG4(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
@@ -5,24 +5,27 @@
|
||||
#include "LoggerImpl.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/fs.h>
|
||||
|
||||
#include "IListenerStorage.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
static void DefaultLogger(unsigned int level, const char* file,
|
||||
unsigned int line, const char* msg) {
|
||||
if (level == 20) {
|
||||
if (level == wpi::WPI_LOG_INFO) {
|
||||
fmt::print(stderr, "NT: {}\n", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view levelmsg;
|
||||
if (level >= 50) {
|
||||
if (level >= wpi::WPI_LOG_CRITICAL) {
|
||||
levelmsg = "CRITICAL";
|
||||
} else if (level >= 40) {
|
||||
} else if (level >= wpi::WPI_LOG_ERROR) {
|
||||
levelmsg = "ERROR";
|
||||
} else if (level >= 30) {
|
||||
} else if (level >= wpi::WPI_LOG_WARNING) {
|
||||
levelmsg = "WARNING";
|
||||
} else {
|
||||
return;
|
||||
@@ -30,108 +33,107 @@ static void DefaultLogger(unsigned int level, const char* file,
|
||||
fmt::print(stderr, "NT: {}: {} ({}:{})\n", levelmsg, msg, file, line);
|
||||
}
|
||||
|
||||
class LoggerImpl::Thread final : public wpi::SafeThreadEvent {
|
||||
public:
|
||||
explicit Thread(NT_LoggerPoller poller) : m_poller{poller} {}
|
||||
static constexpr unsigned int kFlagCritical = 1u << 16;
|
||||
static constexpr unsigned int kFlagError = 1u << 17;
|
||||
static constexpr unsigned int kFlagWarning = 1u << 18;
|
||||
static constexpr unsigned int kFlagInfo = 1u << 19;
|
||||
static constexpr unsigned int kFlagDebug = 1u << 20;
|
||||
static constexpr unsigned int kFlagDebug1 = 1u << 21;
|
||||
static constexpr unsigned int kFlagDebug2 = 1u << 22;
|
||||
static constexpr unsigned int kFlagDebug3 = 1u << 23;
|
||||
static constexpr unsigned int kFlagDebug4 = 1u << 24;
|
||||
|
||||
void Main() final;
|
||||
|
||||
NT_LoggerPoller m_poller;
|
||||
wpi::DenseMap<NT_Logger, std::function<void(const LogMessage& msg)>>
|
||||
m_callbacks;
|
||||
};
|
||||
|
||||
void LoggerImpl::Thread::Main() {
|
||||
while (m_active) {
|
||||
WPI_Handle signaledBuf[2];
|
||||
auto signaled =
|
||||
wpi::WaitForObjects({m_poller, m_stopEvent.GetHandle()}, signaledBuf);
|
||||
if (signaled.empty() || !m_active) {
|
||||
return;
|
||||
}
|
||||
// call all the way back out to the C++ API to ensure valid handle
|
||||
auto events = nt::ReadLoggerQueue(m_poller);
|
||||
if (events.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::unique_lock lock{m_mutex};
|
||||
for (auto&& event : events) {
|
||||
auto callbackIt = m_callbacks.find(event.logger);
|
||||
if (callbackIt != m_callbacks.end()) {
|
||||
auto callback = callbackIt->second;
|
||||
lock.unlock();
|
||||
callback(event);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
static unsigned int LevelToFlag(unsigned int level) {
|
||||
if (level >= wpi::WPI_LOG_CRITICAL) {
|
||||
return EventFlags::kLogMessage | kFlagCritical;
|
||||
} else if (level >= wpi::WPI_LOG_ERROR) {
|
||||
return EventFlags::kLogMessage | kFlagError;
|
||||
} else if (level >= wpi::WPI_LOG_WARNING) {
|
||||
return EventFlags::kLogMessage | kFlagWarning;
|
||||
} else if (level >= wpi::WPI_LOG_INFO) {
|
||||
return EventFlags::kLogMessage | kFlagInfo;
|
||||
} else if (level >= wpi::WPI_LOG_DEBUG) {
|
||||
return EventFlags::kLogMessage | kFlagDebug;
|
||||
} else if (level >= wpi::WPI_LOG_DEBUG1) {
|
||||
return EventFlags::kLogMessage | kFlagDebug1;
|
||||
} else if (level >= wpi::WPI_LOG_DEBUG2) {
|
||||
return EventFlags::kLogMessage | kFlagDebug2;
|
||||
} else if (level >= wpi::WPI_LOG_DEBUG3) {
|
||||
return EventFlags::kLogMessage | kFlagDebug3;
|
||||
} else if (level >= wpi::WPI_LOG_DEBUG4) {
|
||||
return EventFlags::kLogMessage | kFlagDebug4;
|
||||
} else {
|
||||
return EventFlags::kLogMessage;
|
||||
}
|
||||
}
|
||||
|
||||
LoggerImpl::LoggerImpl(int inst) : m_inst{inst} {}
|
||||
static unsigned int LevelsToEventMask(unsigned int minLevel,
|
||||
unsigned int maxLevel) {
|
||||
unsigned int mask = 0;
|
||||
if (minLevel <= wpi::WPI_LOG_CRITICAL && maxLevel >= wpi::WPI_LOG_CRITICAL) {
|
||||
mask |= kFlagCritical;
|
||||
}
|
||||
if (minLevel <= wpi::WPI_LOG_ERROR && maxLevel >= wpi::WPI_LOG_ERROR) {
|
||||
mask |= kFlagError;
|
||||
}
|
||||
if (minLevel <= wpi::WPI_LOG_WARNING && maxLevel >= wpi::WPI_LOG_WARNING) {
|
||||
mask |= kFlagWarning;
|
||||
}
|
||||
if (minLevel <= wpi::WPI_LOG_INFO && maxLevel >= wpi::WPI_LOG_INFO) {
|
||||
mask |= kFlagInfo;
|
||||
}
|
||||
if (minLevel <= wpi::WPI_LOG_DEBUG && maxLevel >= wpi::WPI_LOG_DEBUG) {
|
||||
mask |= kFlagDebug;
|
||||
}
|
||||
if (minLevel <= wpi::WPI_LOG_DEBUG1 && maxLevel >= wpi::WPI_LOG_DEBUG1) {
|
||||
mask |= kFlagDebug1;
|
||||
}
|
||||
if (minLevel <= wpi::WPI_LOG_DEBUG2 && maxLevel >= wpi::WPI_LOG_DEBUG2) {
|
||||
mask |= kFlagDebug2;
|
||||
}
|
||||
if (minLevel <= wpi::WPI_LOG_DEBUG3 && maxLevel >= wpi::WPI_LOG_DEBUG3) {
|
||||
mask |= kFlagDebug3;
|
||||
}
|
||||
if (minLevel <= wpi::WPI_LOG_DEBUG4 && maxLevel >= wpi::WPI_LOG_DEBUG4) {
|
||||
mask |= kFlagDebug4;
|
||||
}
|
||||
if (mask == 0) {
|
||||
mask = EventFlags::kLogMessage;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
LoggerImpl::LoggerImpl(IListenerStorage& listenerStorage)
|
||||
: m_listenerStorage{listenerStorage} {}
|
||||
|
||||
LoggerImpl::~LoggerImpl() = default;
|
||||
|
||||
NT_Logger LoggerImpl::Add(std::function<void(const LogMessage& msg)> callback,
|
||||
unsigned int minLevel, unsigned int maxLevel) {
|
||||
if (!m_thread) {
|
||||
m_thread.Start(CreatePoller());
|
||||
}
|
||||
if (auto thr = m_thread.GetThread()) {
|
||||
auto listener = AddPolled(thr->m_poller, minLevel, maxLevel);
|
||||
if (listener) {
|
||||
thr->m_callbacks.try_emplace(listener, std::move(callback));
|
||||
}
|
||||
return listener;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
void LoggerImpl::AddListener(NT_Listener listener, unsigned int minLevel,
|
||||
unsigned int maxLevel) {
|
||||
++m_listenerCount;
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_listenerLevels.emplace_back(listener, minLevel, maxLevel);
|
||||
m_listenerStorage.Activate(listener, LevelsToEventMask(minLevel, maxLevel),
|
||||
[](unsigned int mask, Event* event) {
|
||||
event->flags = NT_EVENT_LOGMESSAGE;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
NT_LoggerPoller LoggerImpl::CreatePoller() {
|
||||
void LoggerImpl::RemoveListener(NT_Listener listener) {
|
||||
--m_listenerCount;
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_pollers.Add(m_inst)->handle;
|
||||
}
|
||||
|
||||
void LoggerImpl::DestroyPoller(NT_LoggerPoller pollerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_pollers.Remove(pollerHandle);
|
||||
}
|
||||
|
||||
NT_Logger LoggerImpl::AddPolled(NT_LoggerPoller pollerHandle,
|
||||
unsigned int minLevel, unsigned int maxLevel) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (auto poller = m_pollers.Get(pollerHandle)) {
|
||||
return m_listeners.Add(m_inst, poller, minLevel, maxLevel)->handle;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<LogMessage> LoggerImpl::ReadQueue(NT_LoggerPoller pollerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (auto poller = m_pollers.Get(pollerHandle)) {
|
||||
std::vector<LogMessage> rv;
|
||||
rv.swap(poller->queue);
|
||||
return rv;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void LoggerImpl::Remove(NT_Logger listenerHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_listeners.Remove(listenerHandle);
|
||||
if (auto thr = m_thread.GetThread()) {
|
||||
thr->m_callbacks.erase(listenerHandle);
|
||||
}
|
||||
std::erase_if(m_listenerLevels,
|
||||
[&](auto& v) { return v.listener == listener; });
|
||||
}
|
||||
|
||||
unsigned int LoggerImpl::GetMinLevel() {
|
||||
// return 0;
|
||||
std::scoped_lock lock{m_mutex};
|
||||
unsigned int level = NT_LOG_INFO;
|
||||
for (auto&& listener : m_listeners) {
|
||||
if (listener && listener->minLevel < level) {
|
||||
level = listener->minLevel;
|
||||
for (auto&& listenerLevel : m_listenerLevels) {
|
||||
if (listenerLevel.minLevel < level) {
|
||||
level = listenerLevel.minLevel;
|
||||
}
|
||||
}
|
||||
return level;
|
||||
@@ -140,19 +142,10 @@ unsigned int LoggerImpl::GetMinLevel() {
|
||||
void LoggerImpl::Log(unsigned int level, const char* file, unsigned int line,
|
||||
const char* msg) {
|
||||
auto filename = fs::path{file}.filename();
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_listeners.empty()) {
|
||||
DefaultLogger(level, filename.string().c_str(), line, msg);
|
||||
} else {
|
||||
for (auto&& listener : m_listeners) {
|
||||
if (level >= listener->minLevel && level <= listener->maxLevel) {
|
||||
listener->poller->queue.emplace_back(listener->handle.GetHandle(),
|
||||
level, file, line, msg);
|
||||
listener->poller->handle.Set();
|
||||
listener->handle.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_listenerCount == 0) {
|
||||
DefaultLogger(level, filename.string().c_str(), line, msg);
|
||||
} else {
|
||||
m_listenerStorage.Notify(LevelToFlag(level), level, filename.string(), line,
|
||||
msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,29 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SafeThread.h>
|
||||
#include <wpi/Synchronization.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "HandleMap.h"
|
||||
#include "IListenerStorage.h"
|
||||
#include "ntcore_c.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class IListenerStorage;
|
||||
|
||||
class LoggerImpl {
|
||||
public:
|
||||
explicit LoggerImpl(int inst);
|
||||
explicit LoggerImpl(IListenerStorage& listenerStorage);
|
||||
LoggerImpl(const LoggerImpl&) = delete;
|
||||
LoggerImpl& operator=(const LoggerImpl&) = delete;
|
||||
~LoggerImpl();
|
||||
|
||||
NT_Logger Add(std::function<void(const LogMessage& msg)> callback,
|
||||
unsigned int minLevel, unsigned int maxLevel);
|
||||
|
||||
NT_LoggerPoller CreatePoller();
|
||||
void DestroyPoller(NT_LoggerPoller pollerHandle);
|
||||
NT_Logger AddPolled(NT_LoggerPoller pollerHandle, unsigned int minLevel,
|
||||
unsigned int maxLevel);
|
||||
std::vector<LogMessage> ReadQueue(NT_LoggerPoller pollerHandle);
|
||||
void Remove(NT_Logger listenerHandle);
|
||||
void AddListener(NT_Listener listener, unsigned int minLevel,
|
||||
unsigned int maxLevel);
|
||||
void RemoveListener(NT_Listener listener);
|
||||
|
||||
unsigned int GetMinLevel();
|
||||
|
||||
@@ -39,38 +34,20 @@ class LoggerImpl {
|
||||
const char* msg);
|
||||
|
||||
private:
|
||||
int m_inst;
|
||||
mutable wpi::mutex m_mutex;
|
||||
IListenerStorage& m_listenerStorage;
|
||||
std::atomic_int m_listenerCount{0};
|
||||
wpi::mutex m_mutex;
|
||||
|
||||
struct PollerData {
|
||||
static constexpr auto kType = Handle::kLoggerPoller;
|
||||
struct ListenerLevels {
|
||||
ListenerLevels(NT_Listener listener, unsigned int minLevel,
|
||||
unsigned int maxLevel)
|
||||
: listener{listener}, minLevel{minLevel}, maxLevel{maxLevel} {}
|
||||
|
||||
explicit PollerData(NT_LoggerPoller handle) : handle{handle} {}
|
||||
|
||||
wpi::SignalObject<NT_LoggerPoller> handle;
|
||||
std::vector<LogMessage> queue;
|
||||
};
|
||||
HandleMap<PollerData, 8> m_pollers;
|
||||
|
||||
struct ListenerData {
|
||||
static constexpr auto kType = Handle::kLogger;
|
||||
|
||||
ListenerData(NT_Logger handle, PollerData* poller, unsigned int minLevel,
|
||||
unsigned int maxLevel)
|
||||
: handle{handle},
|
||||
poller{poller},
|
||||
minLevel{minLevel},
|
||||
maxLevel{maxLevel} {}
|
||||
|
||||
wpi::SignalObject<NT_Logger> handle;
|
||||
PollerData* poller;
|
||||
NT_Listener listener;
|
||||
unsigned int minLevel;
|
||||
unsigned int maxLevel;
|
||||
};
|
||||
HandleMap<ListenerData, 8> m_listeners;
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_thread;
|
||||
std::vector<ListenerLevels> m_listenerLevels;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpinet/DsClient.h>
|
||||
#include <wpinet/EventLoopRunner.h>
|
||||
#include <wpinet/HttpUtil.h>
|
||||
#include <wpinet/ParallelTcpConnector.h>
|
||||
#include <wpinet/WebSocket.h>
|
||||
#include <wpinet/uv/Async.h>
|
||||
@@ -114,7 +116,7 @@ class NCImpl4 : public NCImpl {
|
||||
void WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp);
|
||||
void Disconnect(std::string_view reason) override;
|
||||
|
||||
std::unique_ptr<net::WebSocketConnection> m_wire;
|
||||
std::shared_ptr<net::WebSocketConnection> m_wire;
|
||||
std::unique_ptr<net::ClientImpl> m_clientImpl;
|
||||
};
|
||||
|
||||
@@ -131,7 +133,7 @@ NCImpl::NCImpl(int inst, std::string_view id, net::ILocalStorage& localStorage,
|
||||
m_loop{*m_loopRunner.GetLoop()} {
|
||||
m_localMsgs.reserve(net::NetworkLoopQueue::kInitialQueueSize);
|
||||
|
||||
INFO("{}", "starting network client");
|
||||
INFO("starting network client");
|
||||
}
|
||||
|
||||
void NCImpl::SetServers(
|
||||
@@ -191,7 +193,8 @@ void NCImpl::Disconnect(std::string_view reason) {
|
||||
m_connHandle = 0;
|
||||
|
||||
// start trying to connect again
|
||||
m_parallelConnect->Disconnected();
|
||||
uv::Timer::SingleShot(m_loop, kReconnectRate,
|
||||
[this] { m_parallelConnect->Disconnected(); });
|
||||
}
|
||||
|
||||
NCImpl3::NCImpl3(int inst, std::string_view id,
|
||||
@@ -214,8 +217,10 @@ NCImpl3::NCImpl3(int inst, std::string_view id,
|
||||
// set up flush async
|
||||
m_flush = uv::Async<>::Create(m_loop);
|
||||
m_flush->wakeup.connect([this] {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count());
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
m_flushAtomic = m_flush.get();
|
||||
|
||||
@@ -237,7 +242,9 @@ NCImpl3::~NCImpl3() {
|
||||
|
||||
void NCImpl3::HandleLocal() {
|
||||
m_localQueue.ReadQueue(&m_localMsgs);
|
||||
m_clientImpl->HandleLocal(m_localMsgs);
|
||||
if (m_clientImpl) {
|
||||
m_clientImpl->HandleLocal(m_localMsgs);
|
||||
}
|
||||
}
|
||||
|
||||
void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
@@ -285,13 +292,13 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
}
|
||||
});
|
||||
tcp.end.connect([this, &tcp] {
|
||||
DEBUG3("{}", "NT3 TCP read ended");
|
||||
DEBUG3("NT3 TCP read ended");
|
||||
if (!tcp.IsLoopClosing()) {
|
||||
Disconnect("remote end closed connection");
|
||||
}
|
||||
});
|
||||
tcp.closed.connect([this, &tcp] {
|
||||
DEBUG3("{}", "NT3 TCP connection closed");
|
||||
DEBUG3("NT3 TCP connection closed");
|
||||
if (!tcp.IsLoopClosing()) {
|
||||
Disconnect(m_wire->GetDisconnectReason());
|
||||
}
|
||||
@@ -299,9 +306,8 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
|
||||
{
|
||||
net3::ClientStartup3 startup{*m_clientImpl};
|
||||
m_localStorage.StartNetwork(startup);
|
||||
m_localStorage.StartNetwork(startup, &m_localQueue);
|
||||
}
|
||||
m_localStorage.SetNetwork(&m_localQueue);
|
||||
m_clientImpl->SetLocal(&m_localStorage);
|
||||
});
|
||||
|
||||
@@ -350,8 +356,10 @@ NCImpl4::NCImpl4(int inst, std::string_view id,
|
||||
// set up flush async
|
||||
m_flush = uv::Async<>::Create(m_loop);
|
||||
m_flush->wakeup.connect([this] {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count());
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
m_flushAtomic = m_flush.get();
|
||||
|
||||
@@ -373,7 +381,9 @@ NCImpl4::~NCImpl4() {
|
||||
|
||||
void NCImpl4::HandleLocal() {
|
||||
m_localQueue.ReadQueue(&m_localMsgs);
|
||||
m_clientImpl->HandleLocal(std::move(m_localMsgs));
|
||||
if (m_clientImpl) {
|
||||
m_clientImpl->HandleLocal(std::move(m_localMsgs));
|
||||
}
|
||||
}
|
||||
|
||||
void NCImpl4::TcpConnected(uv::Tcp& tcp) {
|
||||
@@ -387,9 +397,10 @@ void NCImpl4::TcpConnected(uv::Tcp& tcp) {
|
||||
}
|
||||
wpi::WebSocket::ClientOptions options;
|
||||
options.handshakeTimeout = kWebsocketHandshakeTimeout;
|
||||
auto ws =
|
||||
wpi::WebSocket::CreateClient(tcp, fmt::format("/nt/{}", m_id), "",
|
||||
{{"networktables.first.wpi.edu"}}, options);
|
||||
wpi::SmallString<128> idBuf;
|
||||
auto ws = wpi::WebSocket::CreateClient(
|
||||
tcp, fmt::format("/nt/{}", wpi::EscapeURI(m_id, idBuf)), "",
|
||||
{{"networktables.first.wpi.edu"}}, options);
|
||||
ws->SetMaxMessageSize(kMaxMessageSize);
|
||||
ws->open.connect([this, &tcp, ws = ws.get()](std::string_view) {
|
||||
if (m_connList.IsConnected()) {
|
||||
@@ -410,7 +421,7 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
INFO("CONNECTED NT4 to {} port {}", connInfo.remote_ip, connInfo.remote_port);
|
||||
m_connHandle = m_connList.AddConnection(connInfo);
|
||||
|
||||
m_wire = std::make_unique<net::WebSocketConnection>(ws);
|
||||
m_wire = std::make_shared<net::WebSocketConnection>(ws);
|
||||
m_clientImpl = std::make_unique<net::ClientImpl>(
|
||||
m_loop.Now().count(), m_inst, *m_wire, m_logger,
|
||||
[this](uint32_t repeatMs) {
|
||||
@@ -420,9 +431,8 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
});
|
||||
{
|
||||
net::ClientStartup startup{*m_clientImpl};
|
||||
m_localStorage.StartNetwork(startup);
|
||||
m_localStorage.StartNetwork(startup, &m_localQueue);
|
||||
}
|
||||
m_localStorage.SetNetwork(&m_localQueue);
|
||||
m_clientImpl->SetLocal(&m_localStorage);
|
||||
ws.closed.connect([this, &ws](uint16_t, std::string_view reason) {
|
||||
if (!ws.GetStream().IsLoopClosing()) {
|
||||
@@ -430,10 +440,14 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
}
|
||||
});
|
||||
ws.text.connect([this](std::string_view data, bool) {
|
||||
m_clientImpl->ProcessIncomingText(data);
|
||||
if (m_clientImpl) {
|
||||
m_clientImpl->ProcessIncomingText(data);
|
||||
}
|
||||
});
|
||||
ws.binary.connect([this](std::span<const uint8_t> data, bool) {
|
||||
m_clientImpl->ProcessIncomingBinary(data);
|
||||
if (m_clientImpl) {
|
||||
m_clientImpl->ProcessIncomingBinary(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -482,7 +496,9 @@ void NetworkClient::FlushLocal() {
|
||||
void NetworkClient::Flush() {
|
||||
m_impl->m_loopRunner.ExecAsync([this](uv::Loop&) {
|
||||
m_impl->HandleLocal();
|
||||
m_impl->m_clientImpl->SendValues(m_impl->m_loop.Now().count());
|
||||
if (m_impl->m_clientImpl) {
|
||||
m_impl->m_clientImpl->SendValues(m_impl->m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/fs.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpinet/EventLoopRunner.h>
|
||||
#include <wpinet/HttpUtil.h>
|
||||
#include <wpinet/HttpWebSocketServerConnection.h>
|
||||
#include <wpinet/UrlParser.h>
|
||||
#include <wpinet/uv/Async.h>
|
||||
@@ -87,7 +89,7 @@ class ServerConnection4 final
|
||||
void ProcessRequest() final;
|
||||
void ProcessWsUpgrade() final;
|
||||
|
||||
std::unique_ptr<net::WebSocketConnection> m_wire;
|
||||
std::shared_ptr<net::WebSocketConnection> m_wire;
|
||||
};
|
||||
|
||||
class ServerConnection3 : public ServerConnection {
|
||||
@@ -97,7 +99,7 @@ class ServerConnection3 : public ServerConnection {
|
||||
wpi::Logger& logger);
|
||||
|
||||
private:
|
||||
std::unique_ptr<net3::UvStreamConnection3> m_wire;
|
||||
std::shared_ptr<net3::UvStreamConnection3> m_wire;
|
||||
};
|
||||
|
||||
class NSImpl {
|
||||
@@ -223,20 +225,24 @@ void ServerConnection4::ProcessWsUpgrade() {
|
||||
}
|
||||
DEBUG4("path: '{}'", path);
|
||||
|
||||
wpi::SmallString<128> nameBuf;
|
||||
std::string_view name;
|
||||
bool err = false;
|
||||
if (wpi::starts_with(path, "/nt/")) {
|
||||
name = wpi::drop_front(path, 4);
|
||||
name = wpi::UnescapeURI(wpi::drop_front(path, 4), nameBuf, &err);
|
||||
}
|
||||
if (name.empty()) {
|
||||
INFO("invalid path '{}' (from {}), closing", path, m_connInfo);
|
||||
m_websocket->Fail(404, fmt::format("invalid path '{}'", path));
|
||||
if (err || name.empty()) {
|
||||
INFO("invalid path '{}' (from {}), must match /nt/[clientId], closing",
|
||||
path, m_connInfo);
|
||||
m_websocket->Fail(
|
||||
404, fmt::format("invalid path '{}', must match /nt/[clientId]", path));
|
||||
return;
|
||||
}
|
||||
|
||||
m_websocket->SetMaxMessageSize(kMaxMessageSize);
|
||||
|
||||
m_websocket->open.connect([this, name = std::string{name}](std::string_view) {
|
||||
m_wire = std::make_unique<net::WebSocketConnection>(*m_websocket);
|
||||
m_wire = std::make_shared<net::WebSocketConnection>(*m_websocket);
|
||||
// TODO: set local flag appropriately
|
||||
m_clientId = m_server.m_serverImpl.AddClient(
|
||||
name, m_connInfo, false, *m_wire,
|
||||
@@ -247,11 +253,12 @@ void ServerConnection4::ProcessWsUpgrade() {
|
||||
m_websocket->Fail(409, fmt::format("duplicate name '{}'", name));
|
||||
return;
|
||||
}
|
||||
INFO("CONNECTED NT4 client '{}' (from {})", name, m_connInfo);
|
||||
m_info.remote_id = name;
|
||||
m_server.AddConnection(this, m_info);
|
||||
m_websocket->closed.connect([this](uint16_t, std::string_view reason) {
|
||||
INFO("NT4 connection '{}' closed (from {})", m_info.remote_id,
|
||||
m_connInfo);
|
||||
INFO("DISCONNECTED NT4 client '{}' (from {}): {}", m_info.remote_id,
|
||||
m_connInfo, reason);
|
||||
ConnectionClosed();
|
||||
});
|
||||
m_websocket->text.connect([this](std::string_view data, bool) {
|
||||
@@ -269,7 +276,7 @@ ServerConnection3::ServerConnection3(std::shared_ptr<uv::Stream> stream,
|
||||
NSImpl& server, std::string_view addr,
|
||||
unsigned int port, wpi::Logger& logger)
|
||||
: ServerConnection{server, addr, port, logger},
|
||||
m_wire{std::make_unique<net3::UvStreamConnection3>(*stream)} {
|
||||
m_wire{std::make_shared<net3::UvStreamConnection3>(*stream)} {
|
||||
m_info.remote_ip = addr;
|
||||
m_info.remote_port = port;
|
||||
|
||||
@@ -280,6 +287,7 @@ ServerConnection3::ServerConnection3(std::shared_ptr<uv::Stream> stream,
|
||||
m_info.remote_id = name;
|
||||
m_info.protocol_version = proto;
|
||||
m_server.AddConnection(this, m_info);
|
||||
INFO("CONNECTED NT3 client '{}' (from {})", name, m_connInfo);
|
||||
},
|
||||
[this](uint32_t repeatMs) { UpdatePeriodicTimer(repeatMs); });
|
||||
|
||||
@@ -298,7 +306,7 @@ ServerConnection3::ServerConnection3(std::shared_ptr<uv::Stream> stream,
|
||||
m_wire->GetStream().Shutdown([this] { m_wire->GetStream().Close(); });
|
||||
});
|
||||
stream->closed.connect([this] {
|
||||
INFO("NT3 connection '{}' closed (from {}): {}", m_info.remote_id,
|
||||
INFO("DISCONNECTED NT3 client '{}' (from {}): {}", m_info.remote_id,
|
||||
m_connInfo, m_wire->GetDisconnectReason());
|
||||
ConnectionClosed();
|
||||
});
|
||||
@@ -332,9 +340,8 @@ NSImpl::NSImpl(std::string_view persistentFilename,
|
||||
// connect local storage to server
|
||||
{
|
||||
net::ServerStartup startup{m_serverImpl};
|
||||
m_localStorage.StartNetwork(startup);
|
||||
m_localStorage.StartNetwork(startup, &m_localQueue);
|
||||
}
|
||||
m_localStorage.SetNetwork(&m_localQueue);
|
||||
m_serverImpl.SetLocal(&m_localStorage);
|
||||
|
||||
// load persistent file first, then initialize
|
||||
@@ -360,7 +367,7 @@ void NSImpl::LoadPersistent() {
|
||||
is.readinto(m_persistentData, size);
|
||||
DEBUG4("read data: {}", m_persistentData);
|
||||
if (is.has_error()) {
|
||||
WARNING("{}", "error reading persistent file");
|
||||
WARNING("error reading persistent file");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -453,7 +460,7 @@ void NSImpl::Init() {
|
||||
if (uv::AddrToName(tcp->GetPeer(), &peerAddr, &peerPort) == 0) {
|
||||
INFO("Got a NT3 connection from {} port {}", peerAddr, peerPort);
|
||||
} else {
|
||||
INFO("{}", "Got a NT3 connection from unknown");
|
||||
INFO("Got a NT3 connection from unknown");
|
||||
}
|
||||
auto conn = std::make_shared<ServerConnection3>(tcp, *this, peerAddr,
|
||||
peerPort, m_logger);
|
||||
@@ -485,7 +492,7 @@ void NSImpl::Init() {
|
||||
if (uv::AddrToName(tcp->GetPeer(), &peerAddr, &peerPort) == 0) {
|
||||
INFO("Got a NT4 connection from {} port {}", peerAddr, peerPort);
|
||||
} else {
|
||||
INFO("{}", "Got a NT4 connection from unknown");
|
||||
INFO("Got a NT4 connection from unknown");
|
||||
}
|
||||
auto conn = std::make_shared<ServerConnection4>(tcp, *this, peerAddr,
|
||||
peerPort, m_logger);
|
||||
@@ -496,7 +503,7 @@ void NSImpl::Init() {
|
||||
}
|
||||
|
||||
if (m_initDone) {
|
||||
DEBUG4("{}", "NetworkServer initDone()");
|
||||
DEBUG4("NetworkServer initDone()");
|
||||
m_initDone();
|
||||
m_initDone = nullptr;
|
||||
}
|
||||
|
||||
@@ -34,15 +34,14 @@ void JNI_UnloadTypes(JNIEnv* env);
|
||||
static JavaVM* jvm = nullptr;
|
||||
static JClass booleanCls;
|
||||
static JClass connectionInfoCls;
|
||||
static JClass connectionNotificationCls;
|
||||
static JClass doubleCls;
|
||||
static JClass eventCls;
|
||||
static JClass floatCls;
|
||||
static JClass logMessageCls;
|
||||
static JClass longCls;
|
||||
static JClass topicInfoCls;
|
||||
static JClass topicNotificationCls;
|
||||
static JClass valueCls;
|
||||
static JClass valueNotificationCls;
|
||||
static JClass valueEventDataCls;
|
||||
static JException illegalArgEx;
|
||||
static JException interruptedEx;
|
||||
static JException nullPointerEx;
|
||||
@@ -50,16 +49,14 @@ static JException nullPointerEx;
|
||||
static const JClassInit classes[] = {
|
||||
{"java/lang/Boolean", &booleanCls},
|
||||
{"edu/wpi/first/networktables/ConnectionInfo", &connectionInfoCls},
|
||||
{"edu/wpi/first/networktables/ConnectionNotification",
|
||||
&connectionNotificationCls},
|
||||
{"java/lang/Double", &doubleCls},
|
||||
{"edu/wpi/first/networktables/NetworkTableEvent", &eventCls},
|
||||
{"java/lang/Float", &floatCls},
|
||||
{"edu/wpi/first/networktables/LogMessage", &logMessageCls},
|
||||
{"java/lang/Long", &longCls},
|
||||
{"edu/wpi/first/networktables/TopicInfo", &topicInfoCls},
|
||||
{"edu/wpi/first/networktables/TopicNotification", &topicNotificationCls},
|
||||
{"edu/wpi/first/networktables/NetworkTableValue", &valueCls},
|
||||
{"edu/wpi/first/networktables/ValueNotification", &valueNotificationCls}};
|
||||
{"edu/wpi/first/networktables/ValueEventData", &valueEventDataCls}};
|
||||
|
||||
static const JExceptionInit exceptions[] = {
|
||||
{"java/lang/IllegalArgumentException", &illegalArgEx},
|
||||
@@ -216,29 +213,12 @@ static jobject MakeJObject(JNIEnv* env, const nt::ConnectionInfo& info) {
|
||||
static_cast<jint>(info.protocol_version));
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, jobject inst,
|
||||
const nt::ConnectionNotification& notification) {
|
||||
static jobject MakeJObject(JNIEnv* env, const nt::LogMessage& msg) {
|
||||
static jmethodID constructor = env->GetMethodID(
|
||||
connectionNotificationCls, "<init>",
|
||||
"(Ledu/wpi/first/networktables/NetworkTableInstance;IZLedu/wpi/first/"
|
||||
"networktables/ConnectionInfo;)V");
|
||||
JLocal<jobject> conn{env, MakeJObject(env, notification.conn)};
|
||||
return env->NewObject(connectionNotificationCls, constructor, inst,
|
||||
static_cast<jint>(notification.listener),
|
||||
static_cast<jboolean>(notification.connected),
|
||||
conn.obj());
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, jobject inst,
|
||||
const nt::LogMessage& msg) {
|
||||
static jmethodID constructor = env->GetMethodID(
|
||||
logMessageCls, "<init>",
|
||||
"(Ledu/wpi/first/networktables/NetworkTableInstance;IILjava/lang/"
|
||||
"String;ILjava/lang/String;)V");
|
||||
logMessageCls, "<init>", "(ILjava/lang/String;ILjava/lang/String;)V");
|
||||
JLocal<jstring> filename{env, MakeJString(env, msg.filename)};
|
||||
JLocal<jstring> message{env, MakeJString(env, msg.message)};
|
||||
return env->NewObject(logMessageCls, constructor, inst,
|
||||
static_cast<jint>(msg.logger),
|
||||
return env->NewObject(logMessageCls, constructor,
|
||||
static_cast<jint>(msg.level), filename.obj(),
|
||||
static_cast<jint>(msg.line), message.obj());
|
||||
}
|
||||
@@ -257,28 +237,42 @@ static jobject MakeJObject(JNIEnv* env, jobject inst,
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, jobject inst,
|
||||
const nt::TopicNotification& notification) {
|
||||
const nt::ValueEventData& data) {
|
||||
static jmethodID constructor =
|
||||
env->GetMethodID(topicNotificationCls, "<init>",
|
||||
"(ILedu/wpi/first/networktables/TopicInfo;I)V");
|
||||
JLocal<jobject> info{env, MakeJObject(env, inst, notification.info)};
|
||||
return env->NewObject(topicNotificationCls, constructor,
|
||||
static_cast<jint>(notification.listener), info.obj(),
|
||||
static_cast<jint>(notification.flags));
|
||||
env->GetMethodID(valueEventDataCls, "<init>",
|
||||
"(Ledu/wpi/first/networktables/NetworkTableInstance;II"
|
||||
"Ledu/wpi/first/networktables/NetworkTableValue;)V");
|
||||
JLocal<jobject> value{env, MakeJValue(env, data.value)};
|
||||
return env->NewObject(valueEventDataCls, constructor, inst,
|
||||
static_cast<jint>(data.topic),
|
||||
static_cast<jint>(data.subentry), value.obj());
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, jobject inst,
|
||||
const nt::ValueNotification& notification) {
|
||||
static jobject MakeJObject(JNIEnv* env, jobject inst, const nt::Event& event) {
|
||||
static jmethodID constructor =
|
||||
env->GetMethodID(valueNotificationCls, "<init>",
|
||||
"(Ledu/wpi/first/networktables/NetworkTableInstance;III"
|
||||
"Ledu/wpi/first/networktables/NetworkTableValue;I)V");
|
||||
JLocal<jobject> value{env, MakeJValue(env, notification.value)};
|
||||
return env->NewObject(valueNotificationCls, constructor, inst,
|
||||
static_cast<jint>(notification.listener),
|
||||
static_cast<jint>(notification.topic),
|
||||
static_cast<jint>(notification.subentry), value.obj(),
|
||||
static_cast<jint>(notification.flags));
|
||||
env->GetMethodID(eventCls, "<init>",
|
||||
"(Ledu/wpi/first/networktables/NetworkTableInstance;II"
|
||||
"Ledu/wpi/first/networktables/ConnectionInfo;"
|
||||
"Ledu/wpi/first/networktables/TopicInfo;"
|
||||
"Ledu/wpi/first/networktables/ValueEventData;"
|
||||
"Ledu/wpi/first/networktables/LogMessage;)V");
|
||||
JLocal<jobject> connInfo{env, nullptr};
|
||||
JLocal<jobject> topicInfo{env, nullptr};
|
||||
JLocal<jobject> valueData{env, nullptr};
|
||||
JLocal<jobject> logMessage{env, nullptr};
|
||||
if (auto v = event.GetConnectionInfo()) {
|
||||
connInfo = JLocal<jobject>{env, MakeJObject(env, *v)};
|
||||
} else if (auto v = event.GetTopicInfo()) {
|
||||
topicInfo = JLocal<jobject>{env, MakeJObject(env, inst, *v)};
|
||||
} else if (auto v = event.GetValueEventData()) {
|
||||
valueData = JLocal<jobject>{env, MakeJObject(env, inst, *v)};
|
||||
} else if (auto v = event.GetLogMessage()) {
|
||||
logMessage = JLocal<jobject>{env, MakeJObject(env, *v)};
|
||||
}
|
||||
return env->NewObject(eventCls, constructor, inst,
|
||||
static_cast<jint>(event.listener),
|
||||
static_cast<jint>(event.flags), connInfo.obj(),
|
||||
topicInfo.obj(), valueData.obj(), logMessage.obj());
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env, std::span<const nt::Value> arr) {
|
||||
@@ -293,52 +287,9 @@ static jobjectArray MakeJObject(JNIEnv* env, std::span<const nt::Value> arr) {
|
||||
return jarr;
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(
|
||||
JNIEnv* env, jobject inst,
|
||||
std::span<const nt::ConnectionNotification> arr) {
|
||||
jobjectArray jarr =
|
||||
env->NewObjectArray(arr.size(), connectionNotificationCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < arr.size(); ++i) {
|
||||
JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
|
||||
env->SetObjectArrayElement(jarr, i, elem.obj());
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
|
||||
std::span<const nt::LogMessage> arr) {
|
||||
jobjectArray jarr = env->NewObjectArray(arr.size(), logMessageCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < arr.size(); ++i) {
|
||||
JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
|
||||
env->SetObjectArrayElement(jarr, i, elem.obj());
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
|
||||
std::span<const nt::TopicNotification> arr) {
|
||||
jobjectArray jarr =
|
||||
env->NewObjectArray(arr.size(), topicNotificationCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < arr.size(); ++i) {
|
||||
JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
|
||||
env->SetObjectArrayElement(jarr, i, elem.obj());
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
|
||||
std::span<const nt::ValueNotification> arr) {
|
||||
jobjectArray jarr =
|
||||
env->NewObjectArray(arr.size(), valueNotificationCls, nullptr);
|
||||
std::span<const nt::Event> arr) {
|
||||
jobjectArray jarr = env->NewObjectArray(arr.size(), eventCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1013,35 +964,35 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_getTopicInfo
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: createTopicListenerPoller
|
||||
* Method: createListenerPoller
|
||||
* Signature: (I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_createTopicListenerPoller
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_createListenerPoller
|
||||
(JNIEnv*, jclass, jint inst)
|
||||
{
|
||||
return nt::CreateTopicListenerPoller(inst);
|
||||
return nt::CreateListenerPoller(inst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: destroyTopicListenerPoller
|
||||
* Method: destroyListenerPoller
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyTopicListenerPoller
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyListenerPoller
|
||||
(JNIEnv*, jclass, jint poller)
|
||||
{
|
||||
nt::DestroyTopicListenerPoller(poller);
|
||||
nt::DestroyListenerPoller(poller);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: addPolledTopicListener
|
||||
* Method: addListener
|
||||
* Signature: (I[Ljava/lang/Object;I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledTopicListener__I_3Ljava_lang_String_2I
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_addListener__I_3Ljava_lang_String_2I
|
||||
(JNIEnv* env, jclass, jint poller, jobjectArray prefixes, jint flags)
|
||||
{
|
||||
if (!prefixes) {
|
||||
@@ -1066,179 +1017,43 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledTopicListener__I_3Lja
|
||||
arrview.emplace_back(arr.back());
|
||||
}
|
||||
|
||||
return nt::AddPolledTopicListener(poller, arrview, flags);
|
||||
return nt::AddPolledListener(poller, arrview, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: addPolledTopicListener
|
||||
* Method: addListener
|
||||
* Signature: (III)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledTopicListener__III
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_addListener__III
|
||||
(JNIEnv* env, jclass, jint poller, jint handle, jint flags)
|
||||
{
|
||||
return nt::AddPolledTopicListener(poller, handle, flags);
|
||||
return nt::AddPolledListener(poller, handle, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: readTopicListenerQueue
|
||||
* Method: readListenerQueue
|
||||
* Signature: (Ljava/lang/Object;I)[Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_readTopicListenerQueue
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_readListenerQueue
|
||||
(JNIEnv* env, jclass, jobject inst, jint poller)
|
||||
{
|
||||
return MakeJObject(env, inst, nt::ReadTopicListenerQueue(poller));
|
||||
return MakeJObject(env, inst, nt::ReadListenerQueue(poller));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: removeTopicListener
|
||||
* Method: removeListener
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_removeTopicListener
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_removeListener
|
||||
(JNIEnv*, jclass, jint topicListener)
|
||||
{
|
||||
nt::RemoveTopicListener(topicListener);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: createValueListenerPoller
|
||||
* Signature: (I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_createValueListenerPoller
|
||||
(JNIEnv*, jclass, jint inst)
|
||||
{
|
||||
return nt::CreateValueListenerPoller(inst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: destroyValueListenerPoller
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyValueListenerPoller
|
||||
(JNIEnv*, jclass, jint poller)
|
||||
{
|
||||
nt::DestroyValueListenerPoller(poller);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: addPolledValueListener
|
||||
* Signature: (III)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledValueListener
|
||||
(JNIEnv* env, jclass, jint poller, jint topic, jint flags)
|
||||
{
|
||||
return nt::AddPolledValueListener(poller, topic, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: readValueListenerQueue
|
||||
* Signature: (Ljava/lang/Object;I)[Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_readValueListenerQueue
|
||||
(JNIEnv* env, jclass, jobject inst, jint poller)
|
||||
{
|
||||
return MakeJObject(env, inst, nt::ReadValueListenerQueue(poller));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: removeValueListener
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_removeValueListener
|
||||
(JNIEnv*, jclass, jint topicListener)
|
||||
{
|
||||
nt::RemoveValueListener(topicListener);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: createConnectionListenerPoller
|
||||
* Signature: (I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_createConnectionListenerPoller
|
||||
(JNIEnv*, jclass, jint inst)
|
||||
{
|
||||
return nt::CreateConnectionListenerPoller(inst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: destroyConnectionListenerPoller
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyConnectionListenerPoller
|
||||
(JNIEnv*, jclass, jint poller)
|
||||
{
|
||||
nt::DestroyConnectionListenerPoller(poller);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: addPolledConnectionListener
|
||||
* Signature: (IZ)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledConnectionListener
|
||||
(JNIEnv* env, jclass, jint poller, jboolean immediateNotify)
|
||||
{
|
||||
return nt::AddPolledConnectionListener(poller, immediateNotify);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: readConnectionListenerQueue
|
||||
* Signature: (Ljava/lang/Object;I)[Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_readConnectionListenerQueue
|
||||
(JNIEnv* env, jclass, jobject inst, jint poller)
|
||||
{
|
||||
return MakeJObject(env, inst, nt::ReadConnectionListenerQueue(poller));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: removeConnectionListener
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_removeConnectionListener
|
||||
(JNIEnv*, jclass, jint connListenerUid)
|
||||
{
|
||||
nt::RemoveConnectionListener(connListenerUid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: setNetworkIdentity
|
||||
* Signature: (ILjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_setNetworkIdentity
|
||||
(JNIEnv* env, jclass, jint inst, jstring name)
|
||||
{
|
||||
if (!name) {
|
||||
nullPointerEx.Throw(env, "name cannot be null");
|
||||
return;
|
||||
}
|
||||
nt::SetNetworkIdentity(inst, JStringRef{env, name}.str());
|
||||
nt::RemoveListener(topicListener);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1314,25 +1129,33 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_stopServer
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: startClient3
|
||||
* Signature: (I)V
|
||||
* Signature: (ILjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient3
|
||||
(JNIEnv*, jclass, jint inst)
|
||||
(JNIEnv* env, jclass, jint inst, jstring identity)
|
||||
{
|
||||
nt::StartClient3(inst);
|
||||
if (!identity) {
|
||||
nullPointerEx.Throw(env, "identity cannot be null");
|
||||
return;
|
||||
}
|
||||
nt::StartClient3(inst, JStringRef{env, identity}.str());
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: startClient4
|
||||
* Signature: (I)V
|
||||
* Signature: (ILjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient4
|
||||
(JNIEnv*, jclass, jint inst)
|
||||
(JNIEnv* env, jclass, jint inst, jstring identity)
|
||||
{
|
||||
nt::StartClient4(inst);
|
||||
if (!identity) {
|
||||
nullPointerEx.Throw(env, "identity cannot be null");
|
||||
return;
|
||||
}
|
||||
nt::StartClient4(inst, JStringRef{env, identity}.str());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1569,62 +1392,14 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_stopConnectionDataLog
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: createLoggerPoller
|
||||
* Signature: (I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_createLoggerPoller
|
||||
(JNIEnv*, jclass, jint inst)
|
||||
{
|
||||
return nt::CreateLoggerPoller(inst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: destroyLoggerPoller
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyLoggerPoller
|
||||
(JNIEnv*, jclass, jint poller)
|
||||
{
|
||||
nt::DestroyLoggerPoller(poller);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: addPolledLogger
|
||||
* Method: addLogger
|
||||
* Signature: (III)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledLogger
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_addLogger
|
||||
(JNIEnv*, jclass, jint poller, jint minLevel, jint maxLevel)
|
||||
{
|
||||
return nt::AddPolledLogger(poller, minLevel, maxLevel);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: readLoggerQueue
|
||||
* Signature: (Ljava/lang/Object;I)[Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_readLoggerQueue
|
||||
(JNIEnv* env, jclass, jobject inst, jint poller)
|
||||
{
|
||||
return MakeJObject(env, inst, nt::ReadLoggerQueue(poller));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: removeLogger
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_removeLogger
|
||||
(JNIEnv*, jclass, jint logger)
|
||||
{
|
||||
nt::RemoveLogger(logger);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -54,7 +54,6 @@ class CImpl : public ServerMessageHandler {
|
||||
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data);
|
||||
void HandleLocal(std::vector<ClientMessage>&& msgs);
|
||||
void SendOutgoing(std::span<const ClientMessage> msgs);
|
||||
bool SendControl(uint64_t curTimeMs);
|
||||
void SendValues(uint64_t curTimeMs);
|
||||
bool CheckNetworkReady();
|
||||
@@ -171,7 +170,7 @@ void CImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
}
|
||||
|
||||
void CImpl::HandleLocal(std::vector<ClientMessage>&& msgs) {
|
||||
DEBUG4("{}", "HandleLocal()");
|
||||
DEBUG4("HandleLocal()");
|
||||
for (auto&& elem : msgs) {
|
||||
// common case is value
|
||||
if (auto msg = std::get_if<ClientValueMsg>(&elem.contents)) {
|
||||
|
||||
@@ -59,8 +59,8 @@ class NetworkInterface : public NetworkStartupInterface {
|
||||
|
||||
class ILocalStorage : public LocalInterface {
|
||||
public:
|
||||
virtual void StartNetwork(NetworkStartupInterface& startup) = 0;
|
||||
virtual void SetNetwork(NetworkInterface* network) = 0;
|
||||
virtual void StartNetwork(NetworkStartupInterface& startup,
|
||||
NetworkInterface* network) = 0;
|
||||
virtual void ClearNetwork() = 0;
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user