Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da1ca774e7 | |||
| abc11bc7fc | |||
| 1b29f0e521 | |||
| f40225681c | |||
| e9e273c012 | |||
| 942f9d8515 | |||
| 1e22656299 | |||
| 80004ab1e7 | |||
| 55fe2675df | |||
| af19125ac2 | |||
| eb7fe11de4 | |||
| 7ddbde1a2f | |||
| ac3d97b3ec | |||
| 274143e41d | |||
| d75430ef82 | |||
| 06f3f2eebe | |||
| 07609c6d6c | |||
| 1bad358921 | |||
| 77e123ff4a | |||
| 9c3a8ea695 | |||
| 0ff86a0a5e | |||
| 8ad5179a61 | |||
| e600b0f96e | |||
| b0d61c3e38 | |||
| 296e517389 | |||
| 758586bb3c | |||
| ccf0ab77bf | |||
| 774661e1f0 | |||
| 33b61f429a | |||
| 7ded9d287d | |||
| a55bcc2a03 | |||
| f15c07bc83 | |||
| e3ce533fe4 | |||
| 2075c0b797 | |||
| 37b8cb0c7f | |||
| 4ed36eb88a | |||
| e53b0be7e2 | |||
| e299e956e5 | |||
| ca9eae197f | |||
| 08122c086c | |||
| cb8b13d0cd | |||
| 46686db07e | |||
| 97d93c670e | |||
| 9d77e8b345 | |||
| 4bf19fda90 | |||
| ed9635582f | |||
| b7ff100499 | |||
| d06ba8957a | |||
| 1aec779351 | |||
| 6baa0bb008 | |||
| 274edc1013 | |||
| c0eecf76d4 | |||
| 2390042892 | |||
| d32cb1ddac | |||
| 0cf86cf18a | |||
| 5c7a92b3a4 | |||
| 21da27649e | |||
| 71564b4d83 | |||
| 8123ec1227 | |||
| 2a60fdd9df | |||
| 462247d743 | |||
| 6b4fe05fc0 | |||
| 0090aa6e3a | |||
| 7e514a1fa3 | |||
| f1ce1ab19c | |||
| 542e3081ad | |||
| 65b9460a3a | |||
| cea65dcd00 | |||
| 6cebf6856e | |||
| 20d568a063 | |||
| bd52e49ba3 | |||
| 93103aa8d4 | |||
| 2be78401b5 | |||
| eca3f47884 | |||
| f3909fc000 | |||
| c0798d1bdb | |||
| e45a9ee5d1 | |||
| b43ffcb677 | |||
| 2c60248127 | |||
| 993bf568fc | |||
| 27e707a22d | |||
| 7ab6e7b2c3 | |||
| 5b2354bfe0 | |||
| 42ab935da6 | |||
| 572a8612ed | |||
| 51b70b179c | |||
| 1a56e2a107 | |||
| e6b8d3e85d | |||
| 52567406ca | |||
| 2f18ade4ab | |||
| f8bf8083af | |||
| 782bcd4567 | |||
| 2212aec080 | |||
| d0303becd7 | |||
| 27bebeb702 | |||
| 09a0041f20 | |||
| c6c3d6c9e8 | |||
| 092a0954a0 | |||
| d2ed525106 | |||
| dbc9569775 | |||
| 1e2cc5ef16 | |||
| 31cadb2ee4 | |||
| 329d57a16d | |||
| 32179eee5e | |||
| f05c465d61 | |||
| 79c50ff191 | |||
| d10217f3f5 | |||
| 4b0e832e27 | |||
| dd9ce63cb8 | |||
| b9322c5e89 | |||
| 2dfa615eb7 | |||
| 596bc1fbd2 | |||
| ec50526804 | |||
| eb3366cfd2 | |||
| b49e281010 | |||
| 9a23dbbe95 | |||
| 5a3c0699f7 | |||
| 816a047920 | |||
| 33c006b178 | |||
| 0733bb9a68 | |||
| eeaa4ed2df | |||
| 0788bbd799 | |||
| a6eccede4a | |||
| 437f7ea10f | |||
| 58f7df49ae | |||
| 03da91f5e5 | |||
| 02c071394b | |||
| c52c447a78 | |||
| ddd6f6d6c6 | |||
| f5c359a6a9 | |||
| af33b7f097 | |||
| 92e55641a0 | |||
| 9e00cd1530 | |||
| 8fd8826f8d | |||
| 5d30941aab | |||
| 429bd2a349 | |||
| 5c79a89cd4 | |||
| a931f9f01a | |||
| 7f3bfec835 | |||
| 19b39d391f | |||
| 674d74cfb9 | |||
| b768739b96 | |||
| 77acbdd8de | |||
| 7b699d5d36 | |||
| db8f047322 | |||
| 472184bbbc | |||
| dd3d5fea66 | |||
| 6573a1b14d | |||
| 0759461c69 | |||
| 62cada2547 | |||
| e755383e72 | |||
| d1e4c1a2ea | |||
| 29b192b2ee | |||
| 7c48abbcca | |||
| 0ec227cf9e | |||
| baad2a9890 | |||
| 91ccd16b33 | |||
| 16865dc36f | |||
| da0ef64f62 | |||
| 83af74f4be | |||
| 7cb6c8521e | |||
| 1d3d929ddd | |||
| 25a9721f92 | |||
| b99b147959 | |||
| 96bf653167 | |||
| 4429135539 | |||
| 89947dfc71 | |||
| fb17c51ef6 | |||
| b8c931397d | |||
| f5195450e4 | |||
| b4102e6ee1 | |||
| 20034513ad | |||
| 3a852bfb9d | |||
| 4f74e1cd31 | |||
| b7cf4c9135 | |||
| e08dc0678b | |||
| 81842e4571 | |||
| 428a32a950 | |||
| 01da06b051 | |||
| a989256f22 | |||
| 21d2df4d34 | |||
| 8d18765a3a | |||
| 0c43c88554 | |||
| 07937a4846 | |||
| 067c928e47 | |||
| 680977b211 | |||
| 4143541adf | |||
| 1b6b12256d | |||
| 5b5a701c69 | |||
| 3457efcbf8 | |||
| bb1c7e4be1 | |||
| cc33b333d2 | |||
| 0449e557b0 | |||
| 73b2d981f9 | |||
| 20cdf64afb | |||
| 48121ec84c | |||
| 725f8772b1 | |||
| 8413277847 | |||
| 576d3ed7a5 | |||
| 381b29c12d | |||
| 0dcb4ce65f | |||
| c880f5b73e | |||
| 6ba6cb9cf0 | |||
| f587b45b38 | |||
| 1ededb85b9 | |||
| 9a500e39ab | |||
| 8f0e945f0c | |||
| 5d55157ffd | |||
| 7f367fd6e3 | |||
| b0596d12f9 | |||
| 3b07a15e11 | |||
| 38b29ddfc0 | |||
| d2bf5aceee | |||
| a0d577bf81 | |||
| 25234c4229 | |||
| 71c448a1d7 | |||
| 93fd2ac0ab | |||
| 3e9eecc279 | |||
| 5e4597b8fd | |||
| 6eb6fa1eb0 | |||
| f00e1c7cf1 | |||
| 26be261cff | |||
| e5a3c41c20 | |||
| 1d9b8a2cf6 | |||
| 4ce4c9f9f8 | |||
| fe5a7d480d | |||
| 83273ae806 | |||
| 04a3631881 |
@@ -0,0 +1,3 @@
|
||||
[submodule "third_party/googletest"]
|
||||
path = third_party/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
@@ -3,12 +3,11 @@
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/include",
|
||||
"${workspaceFolder}/smocore/include",
|
||||
"${workspaceFolder}/b/include",
|
||||
"/usr/include",
|
||||
"/usr/local/include",
|
||||
"${workspaceFolder}/b/include"
|
||||
"/usr/local/include"
|
||||
],
|
||||
"defines": [],
|
||||
"compilerPath": "/usr/bin/g++",
|
||||
@@ -24,7 +23,8 @@
|
||||
},
|
||||
"forcedInclude": [
|
||||
"${workspaceFolder}/b/include/config.h"
|
||||
]
|
||||
],
|
||||
"configurationProvider": "ms-vscode.cmake-tools"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
||||
@@ -74,10 +74,27 @@
|
||||
"future": "cpp",
|
||||
"shared_mutex": "cpp",
|
||||
"typeindex": "cpp",
|
||||
"bitset": "cpp"
|
||||
"bitset": "cpp",
|
||||
"*.ipp": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"barrier": "cpp"
|
||||
},
|
||||
"editor.rulers": [80, 120],
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": false,
|
||||
"editor.detectIndentation": false
|
||||
"editor.detectIndentation": false,
|
||||
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
|
||||
"C_Cpp.default.browse.limitSymbolsToIncludedHeaders": true,
|
||||
"C_Cpp.default.browse.path": [
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/b"
|
||||
],
|
||||
"C_Cpp.default.includePath": [
|
||||
"${workspaceFolder}/include",
|
||||
"${workspaceFolder}/smocore/include",
|
||||
"${workspaceFolder}/b/include",
|
||||
"/usr/include",
|
||||
"/usr/local/include"
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(salmanoff VERSION 0.00.004 LANGUAGES CXX)
|
||||
project(salmanoff VERSION 0.01.000 LANGUAGES CXX)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DAPSS.cmake)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DebugOpts.cmake)
|
||||
|
||||
# Set C++ standard
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
@@ -22,10 +24,39 @@ if(NOT MIND_VOSCILLATOR_PERIOD_MS GREATER 0)
|
||||
endif()
|
||||
math(EXPR MIND_VOSCILLATOR_FREQ_MS "1000 / ${MIND_VOSCILLATOR_PERIOD_MS}")
|
||||
|
||||
# Device manager reattacher configuration
|
||||
set(MRNTT_DEVMGR_REATTACHER_PERIOD_MS 2000
|
||||
CACHE STRING "Device manager reattacher period (ms)")
|
||||
if(NOT MRNTT_DEVMGR_REATTACHER_PERIOD_MS GREATER 0)
|
||||
message(FATAL_ERROR
|
||||
"MRNTT_DEVMGR_REATTACHER_PERIOD_MS must be a positive integer > 0")
|
||||
endif()
|
||||
|
||||
# World thread configuration
|
||||
option(WORLD_USE_BODY_THREAD
|
||||
"Use body thread for world component instead of separate world thread" OFF)
|
||||
|
||||
# Test configuration
|
||||
option(ENABLE_TESTS "Enable building tests" OFF)
|
||||
|
||||
# Set the debug locks variable for config.h
|
||||
if(ENABLE_DEBUG_LOCKS)
|
||||
set(CONFIG_ENABLE_DEBUG_LOCKS TRUE)
|
||||
endif()
|
||||
|
||||
# Set the world thread variable for config.h
|
||||
if(WORLD_USE_BODY_THREAD)
|
||||
set(CONFIG_WORLD_USE_BODY_THREAD TRUE)
|
||||
endif()
|
||||
|
||||
# Set the timeout variable for config.h
|
||||
set(CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS ${DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS})
|
||||
|
||||
# Configure config.h
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/config.h
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Include directories
|
||||
@@ -36,7 +67,10 @@ include_directories(
|
||||
)
|
||||
|
||||
# Find core dependencies
|
||||
find_package(Boost 1.69.0 REQUIRED COMPONENTS system)
|
||||
# Boost 1.72.0 is required to ensure that a certain bug where boost::asio
|
||||
# objects depend on specific copies of symbols, and boost will cause a segfault
|
||||
# if boost::asio objects are used inside of a dlopen()'d library, is fixed.
|
||||
find_package(Boost 1.73.0 REQUIRED COMPONENTS system)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(FLEX REQUIRED)
|
||||
find_package(BISON REQUIRED)
|
||||
@@ -47,21 +81,51 @@ if(NOT DL_LIBRARY)
|
||||
message(FATAL_ERROR "Dynamic linking library (libdl/libldl) not found")
|
||||
endif()
|
||||
|
||||
# Add third-party dependencies
|
||||
if(ENABLE_TESTS)
|
||||
add_subdirectory(third_party)
|
||||
endif()
|
||||
# Add core components
|
||||
add_subdirectory(smocore)
|
||||
add_subdirectory(commonLibs)
|
||||
add_subdirectory(senseApis)
|
||||
add_subdirectory(wilzorApis)
|
||||
add_subdirectory(devices)
|
||||
|
||||
# Main executable
|
||||
add_executable(salmanoff main.cpp)
|
||||
target_link_libraries(salmanoff
|
||||
smocore
|
||||
marionette
|
||||
deviceManager
|
||||
senseApis
|
||||
${Boost_LIBRARIES}
|
||||
${DL_LIBRARY}
|
||||
)
|
||||
|
||||
# Add all registered DAPSS targets as dependencies
|
||||
add_all_daps_dependencies()
|
||||
|
||||
# Add tests if enabled
|
||||
if(ENABLE_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
install(TARGETS salmanoff DESTINATION bin)
|
||||
|
||||
# Install device configuration files (preprocessed .daps files)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/devices/
|
||||
DESTINATION share/salmanoff/devices
|
||||
FILES_MATCHING PATTERN "*.daps"
|
||||
)
|
||||
|
||||
# Install documentation
|
||||
install(FILES README.md DESTINATION share/doc/salmanoff)
|
||||
install(FILES LICENSE DESTINATION share/doc/salmanoff)
|
||||
|
||||
# Install example configurations if they exist
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples")
|
||||
install(DIRECTORY examples/ DESTINATION share/salmanoff/examples)
|
||||
endif()
|
||||
|
||||
# Include CPack configuration
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CPackConfig.cmake)
|
||||
include(CPack)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2024 Salmanoff Project. All rights reserved.
|
||||
|
||||
PROPRIETARY SOFTWARE LICENSE
|
||||
|
||||
This software and associated documentation files (the "Software") are
|
||||
proprietary and confidential. The Software is owned exclusively by the
|
||||
Salmanoff Project and is protected by copyright laws and international
|
||||
treaty provisions.
|
||||
|
||||
NO LICENSE GRANTED. No person or entity is granted any rights or
|
||||
permissions to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
sell, or otherwise transfer the Software or any portion thereof without
|
||||
explicit written permission from the Salmanoff Project.
|
||||
|
||||
UNAUTHORIZED USE PROHIBITED. Any unauthorized use, reproduction, or
|
||||
distribution of the Software is strictly prohibited and may result in
|
||||
severe civil and criminal penalties.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Package Generation
|
||||
|
||||
This project supports generating both Debian (.deb) and RPM (.rpm) packages
|
||||
using CPack.
|
||||
|
||||
## Manual Package Generation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CMake 3.16 or later
|
||||
- Make or Ninja build system
|
||||
- For RPM packages: `rpmbuild` utility
|
||||
|
||||
### Build Process
|
||||
|
||||
1. **Create build directory:**
|
||||
```bash
|
||||
mkdir -p build-package
|
||||
cd build-package
|
||||
```
|
||||
|
||||
2. **Configure with CMake:**
|
||||
```bash
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
```
|
||||
|
||||
3. **Build the project:**
|
||||
```bash
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
4. **Generate packages:**
|
||||
```bash
|
||||
cpack -G DEB # Generate Debian package
|
||||
cpack -G RPM # Generate RPM package (requires rpmbuild)
|
||||
```
|
||||
|
||||
### Requirements for RPM Generation
|
||||
|
||||
To generate RPM packages, you need `rpmbuild` installed:
|
||||
|
||||
- **Ubuntu/Debian**: `sudo apt-get install rpm`
|
||||
- **CentOS/RHEL**: `sudo yum install rpm-build`
|
||||
- **Fedora**: `sudo dnf install rpm-build`
|
||||
|
||||
### Package Contents
|
||||
|
||||
The generated packages include:
|
||||
|
||||
- **Main executable**: `/usr/bin/salmanoff`
|
||||
- **Shared libraries**: `/usr/lib/lib*.so`
|
||||
- **Device configurations**: `/usr/share/salmanoff/devices/` (preprocessed
|
||||
.daps files)
|
||||
- **Documentation**: `/usr/share/doc/salmanoff/`
|
||||
|
||||
### Installing Packages
|
||||
|
||||
**Debian/Ubuntu:**
|
||||
```bash
|
||||
sudo dpkg -i salmanoff-0.00.004-x86_64.deb
|
||||
```
|
||||
|
||||
**CentOS/RHEL/Fedora:**
|
||||
```bash
|
||||
sudo rpm -i salmanoff-0.00.004-x86_64.rpm
|
||||
```
|
||||
|
||||
### Package Configuration
|
||||
|
||||
Package metadata and configuration is defined in
|
||||
`cmake/CPackConfig.cmake`. This includes:
|
||||
|
||||
- Package name, version, and description
|
||||
- Dependencies and recommendations
|
||||
- License information
|
||||
- File naming conventions
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- **RPM generation fails**: Ensure `rpmbuild` is installed
|
||||
- **Missing dependencies**: Check that all build dependencies are
|
||||
installed
|
||||
- **Permission errors**: Ensure you have write permissions in the build
|
||||
directory
|
||||
@@ -1,6 +1,8 @@
|
||||
# The Salmanoff Project:
|
||||
|
||||

|
||||
<p align="center">
|
||||
<img src="docs/img/salmanoff-logo-512.png" alt="Salmanoff project logo" />
|
||||
</p>
|
||||
|
||||
This project, Salmanoff (pronounced: Sal-man-off), is an ROS rewrite of the Harikoff project. The name is more reflective of the people whose ideas sparked the solutions in my mind. These people are:
|
||||
* Gregory `SAL`mieri.
|
||||
@@ -8,3 +10,5 @@ This project, Salmanoff (pronounced: Sal-man-off), is an ROS rewrite of the Hari
|
||||
* Leonard Peik`OFF`.
|
||||
|
||||
Would you like to know what this project is and does? Well, it's a secret! But you can find out by reading the code. Or you could just ask me. Or you could wait until I release it. But that's no fun.
|
||||
|
||||
For package generation instructions, see [PACKAGING.md](PACKAGING.md).
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# CPack configuration for package generation
|
||||
# This file contains all CPack settings for generating deb and rpm packages
|
||||
|
||||
# Set package metadata using project variables
|
||||
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
|
||||
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
|
||||
"Salmanoff - A sensor management and control system")
|
||||
set(CPACK_PACKAGE_VENDOR "Salmanoff Project")
|
||||
set(CPACK_PACKAGE_CONTACT "maintainer@salmanoff.org")
|
||||
|
||||
# Set package description
|
||||
set(CPACK_PACKAGE_DESCRIPTION
|
||||
"Salmanoff is a comprehensive sensor management and control system that\n"
|
||||
"provides unified interfaces for various sensor devices including LiDAR\n"
|
||||
"systems. It features modular architecture with support for multiple\n"
|
||||
"device types, asynchronous processing, and real-time data handling."
|
||||
)
|
||||
|
||||
# License information
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||
|
||||
# Enable deb and rpm generators
|
||||
set(CPACK_GENERATOR "DEB;RPM")
|
||||
|
||||
# DEB package specific settings
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER
|
||||
"Salmanoff Project <maintainer@salmanoff.org>")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "science")
|
||||
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS
|
||||
"libboost-system1.74.0 | libboost-system1.73.0 | libboost-system1.72.0, "
|
||||
"libc6, libstdc++6")
|
||||
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libxcb1, libx11-6")
|
||||
set(CPACK_DEBIAN_PACKAGE_SUGGESTS "livox-sdk")
|
||||
|
||||
# RPM package specific settings
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "Proprietary")
|
||||
set(CPACK_RPM_PACKAGE_GROUP "Applications/Engineering")
|
||||
set(CPACK_RPM_PACKAGE_URL "https://github.com/salmanoff/salmanoff")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "boost-system >= 1.72.0, glibc, libstdc++")
|
||||
set(CPACK_RPM_PACKAGE_SUGGESTS "xcb, libX11, livox-sdk")
|
||||
|
||||
# Package file naming using project variables
|
||||
set(CPACK_PACKAGE_FILE_NAME
|
||||
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_PROCESSOR}")
|
||||
|
||||
# Set compression
|
||||
set(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||
set(CPACK_RPM_COMPONENT_INSTALL ON)
|
||||
@@ -0,0 +1,153 @@
|
||||
# DAPSS (Device Attachment Pipe Specification Source) preprocessing module
|
||||
# This module provides functionality to preprocess .dapss files to .daps files
|
||||
# using the C preprocessor, respecting include directories and target dependencies.
|
||||
#
|
||||
# Usage:
|
||||
# add_daps_target(target_name SOURCES file1.dapss file2.dapss ...)
|
||||
# register_daps_target(target_name) # In subdirectories
|
||||
# add_all_daps_dependencies() # In main CMakeLists.txt
|
||||
#
|
||||
# Examples:
|
||||
# add_daps_target(device_specs SOURCES devices/avia0.dapss devices/win0.dapss)
|
||||
# register_daps_target(device_specs)
|
||||
# add_all_daps_dependencies()
|
||||
#
|
||||
# The preprocessed .daps files will be placed in ${CMAKE_CURRENT_BINARY_DIR}/
|
||||
|
||||
# Function to add a DAPSS preprocessing target
|
||||
# Usage: add_daps_target(target_name SOURCES file1.dapss file2.dapss ...)
|
||||
function(add_daps_target target_name)
|
||||
set(options)
|
||||
set(oneValueArgs)
|
||||
set(multiValueArgs SOURCES)
|
||||
cmake_parse_arguments(DAPS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
if(NOT DAPS_SOURCES)
|
||||
message(FATAL_ERROR "add_daps_target: No SOURCES specified for target ${target_name}")
|
||||
endif()
|
||||
|
||||
# Use binary directory directly for processed files
|
||||
# This ensures files are created in the same directory as the target
|
||||
set(output_dir "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
# List to store all output files
|
||||
set(output_files)
|
||||
|
||||
# Process each source file
|
||||
foreach(source_file ${DAPS_SOURCES})
|
||||
# Get the base name without extension
|
||||
get_filename_component(base_name ${source_file} NAME_WE)
|
||||
get_filename_component(source_dir ${source_file} DIRECTORY)
|
||||
|
||||
# Create output file path
|
||||
set(output_file "${output_dir}/${base_name}.daps")
|
||||
list(APPEND output_files ${output_file})
|
||||
|
||||
# Get include directories from current directory and target
|
||||
get_directory_property(include_dirs INCLUDE_DIRECTORIES)
|
||||
|
||||
# Build include flags
|
||||
set(include_flags)
|
||||
foreach(include_dir ${include_dirs})
|
||||
list(APPEND include_flags "-I${include_dir}")
|
||||
endforeach()
|
||||
|
||||
# Add current source directory to includes if it's not already there
|
||||
if(source_dir)
|
||||
list(APPEND include_flags "-I${source_dir}")
|
||||
endif()
|
||||
|
||||
# Convert list to space-separated string
|
||||
string(REPLACE ";" " " include_flags_str "${include_flags}")
|
||||
|
||||
# Find C compiler if not already set
|
||||
if(NOT CMAKE_C_COMPILER)
|
||||
find_program(CMAKE_C_COMPILER gcc cc clang)
|
||||
if(NOT CMAKE_C_COMPILER)
|
||||
message(FATAL_ERROR "No C compiler found for DAPSS preprocessing")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Create custom command to preprocess the file
|
||||
add_custom_command(
|
||||
OUTPUT ${output_file}
|
||||
COMMAND sh -c "\"${CMAKE_C_COMPILER}\" -E -P -x c ${include_flags_str} \"${CMAKE_CURRENT_SOURCE_DIR}/${source_file}\" > \"${output_file}\""
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${source_file}
|
||||
COMMENT "Preprocessing ${source_file} to ${base_name}.daps"
|
||||
VERBATIM
|
||||
)
|
||||
endforeach()
|
||||
|
||||
# Create custom target that depends on all output files
|
||||
add_custom_target(${target_name} DEPENDS ${output_files})
|
||||
|
||||
# Make the target part of the ALL target so it gets built by default
|
||||
# This ensures it gets built when building just this subdirectory
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
FOLDER "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
EXCLUDE_FROM_ALL FALSE
|
||||
)
|
||||
|
||||
# Set target properties
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
DAPS_OUTPUT_DIR ${output_dir}
|
||||
DAPS_OUTPUT_FILES "${output_files}"
|
||||
)
|
||||
|
||||
# Make the target available globally
|
||||
set(${target_name}_OUTPUT_DIR ${output_dir} PARENT_SCOPE)
|
||||
set(${target_name}_OUTPUT_FILES "${output_files}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Function to register a DAPSS target for later dependency addition
|
||||
# Usage: register_daps_target(target_name)
|
||||
# This stores the target name in a global property for later use
|
||||
function(register_daps_target target_name)
|
||||
# Store the target name in a global property
|
||||
get_property(registered_targets GLOBAL PROPERTY DAPS_REGISTERED_TARGETS)
|
||||
list(APPEND registered_targets ${target_name})
|
||||
set_property(GLOBAL PROPERTY DAPS_REGISTERED_TARGETS ${registered_targets})
|
||||
message(STATUS "Registered DAPSS target ${target_name} for later dependency addition")
|
||||
endfunction()
|
||||
|
||||
# Function to add all registered DAPSS targets as dependencies
|
||||
# Usage: add_all_daps_dependencies([TARGET main_target] [CONDITION condition_expression])
|
||||
# This should be called from the main CMakeLists.txt after all subdirectories are processed
|
||||
function(add_all_daps_dependencies)
|
||||
set(options)
|
||||
set(oneValueArgs TARGET CONDITION)
|
||||
set(multiValueArgs)
|
||||
cmake_parse_arguments(DAPS_ALL "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
# Default target is PROJECT_NAME
|
||||
if(DAPS_ALL_TARGET)
|
||||
set(dep_target ${DAPS_ALL_TARGET})
|
||||
else()
|
||||
set(dep_target ${PROJECT_NAME})
|
||||
endif()
|
||||
|
||||
# Get all registered targets
|
||||
get_property(registered_targets GLOBAL PROPERTY DAPS_REGISTERED_TARGETS)
|
||||
|
||||
if(registered_targets)
|
||||
foreach(target_name ${registered_targets})
|
||||
if(TARGET ${target_name})
|
||||
if(DAPS_ALL_CONDITION)
|
||||
if(${DAPS_ALL_CONDITION})
|
||||
add_dependencies(${dep_target} ${target_name})
|
||||
message(STATUS "Added registered DAPSS target ${target_name} as dependency of ${dep_target} (condition: ${DAPS_ALL_CONDITION})")
|
||||
else()
|
||||
message(STATUS "Skipped registered DAPSS target ${target_name} (condition: ${DAPS_ALL_CONDITION} not met)")
|
||||
endif()
|
||||
else()
|
||||
add_dependencies(${dep_target} ${target_name})
|
||||
message(STATUS "Added registered DAPSS target ${target_name} as dependency of ${dep_target}")
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "Registered DAPSS target ${target_name} does not exist")
|
||||
endif()
|
||||
endforeach()
|
||||
else()
|
||||
message(STATUS "No DAPSS targets registered for dependency addition")
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,22 @@
|
||||
# DebugOpts.cmake - Debug configuration options
|
||||
|
||||
# Enable debug locking features
|
||||
option(ENABLE_DEBUG_LOCKS "Enable debug features for locking system" ON)
|
||||
|
||||
# Qutex deadlock detection configuration
|
||||
# Always define the variable in cache so it appears in ccmake
|
||||
set(DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS 500 CACHE STRING
|
||||
"Timeout in milliseconds for deadlock detection in qutex system")
|
||||
|
||||
if(ENABLE_DEBUG_LOCKS)
|
||||
# Validate the timeout value
|
||||
if(NOT DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS OR DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS STREQUAL "")
|
||||
message(FATAL_ERROR "DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS must be a positive integer > 0")
|
||||
endif()
|
||||
|
||||
# Convert to integer and validate
|
||||
math(EXPR timeout_int "${DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS}")
|
||||
if(timeout_int LESS_EQUAL 0)
|
||||
message(FATAL_ERROR "DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS must be a positive integer > 0")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,91 @@
|
||||
# Clang toolchain file for native builds
|
||||
# This file should be used with cmake -DCMAKE_TOOLCHAIN_FILE=cmake/clang-native.cmake
|
||||
|
||||
# Disable cross-compilation
|
||||
set(CMAKE_CROSSCOMPILING FALSE)
|
||||
|
||||
# Target OS (native)
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
|
||||
# Specify the Clang compilers
|
||||
set(CMAKE_C_COMPILER clang)
|
||||
set(CMAKE_CXX_COMPILER clang++)
|
||||
|
||||
# Set Clang-specific compiler flags
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic")
|
||||
|
||||
# Enable C++20 standard (as specified in main CMakeLists.txt)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Set Clang-specific optimization flags
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -DNDEBUG")
|
||||
|
||||
# Set debug flags
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
|
||||
|
||||
# Enable address sanitizer in debug builds (optional)
|
||||
# Uncomment the following lines if you want to enable address sanitizer
|
||||
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer")
|
||||
# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer")
|
||||
# set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address")
|
||||
# set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=address")
|
||||
|
||||
# Enable undefined behavior sanitizer in debug builds (optional)
|
||||
# Uncomment the following lines if you want to enable UBSan
|
||||
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined -fno-omit-frame-pointer")
|
||||
# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined -fno-omit-frame-pointer")
|
||||
# set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=undefined")
|
||||
# set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=undefined")
|
||||
|
||||
# Set native search paths (use system defaults)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
|
||||
|
||||
# Clang-specific linker flags
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld")
|
||||
|
||||
# Enable link-time optimization in release builds (optional)
|
||||
# Uncomment the following lines if you want to enable LTO
|
||||
# set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
|
||||
# set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
|
||||
# set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -flto")
|
||||
# set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -flto")
|
||||
|
||||
# Set Clang-specific C++ features
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++")
|
||||
|
||||
# Alternative: Use libstdc++ instead of libc++ (uncomment if preferred)
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++")
|
||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libstdc++")
|
||||
# set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libstdc++")
|
||||
|
||||
# Set compiler-specific features
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics")
|
||||
|
||||
# Enable all warnings and treat them as errors in debug builds (optional)
|
||||
# Uncomment the following lines if you want to treat warnings as errors
|
||||
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror")
|
||||
# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Werror")
|
||||
|
||||
# Set Clang-specific optimization flags
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=native -mtune=native")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -march=native -mtune=native")
|
||||
|
||||
# Print configuration information
|
||||
message(STATUS "Clang toolchain configuration:")
|
||||
message(STATUS " C Compiler: ${CMAKE_C_COMPILER}")
|
||||
message(STATUS " CXX Compiler: ${CMAKE_CXX_COMPILER}")
|
||||
message(STATUS " CXX Standard: ${CMAKE_CXX_STANDARD}")
|
||||
message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}")
|
||||
message(STATUS " Cross-compiling: ${CMAKE_CROSSCOMPILING}")
|
||||
@@ -0,0 +1,72 @@
|
||||
# Generic Flex/Yacc Generation Functions
|
||||
# This file provides reusable functions for generating C++ files from Flex/Bison sources
|
||||
|
||||
# Function to generate Flex lexer files
|
||||
# Usage: generate_flex_lexer(OUTPUT_VAR INPUT_FILE [PREFIX] [HEADER_DEPENDENCY])
|
||||
# OUTPUT_VAR: Variable name to store the output file path
|
||||
# INPUT_FILE: Path to the .ll input file
|
||||
# PREFIX: Optional prefix for the generated files (defaults to basename of input file)
|
||||
# HEADER_DEPENDENCY: Optional header file that the lexer depends on (e.g., from Bison)
|
||||
function(generate_flex_lexer OUTPUT_VAR INPUT_FILE)
|
||||
get_filename_component(INPUT_BASENAME ${INPUT_FILE} NAME_WE)
|
||||
|
||||
if(ARGC GREATER 2)
|
||||
set(PREFIX ${ARGV2})
|
||||
else()
|
||||
set(PREFIX ${INPUT_BASENAME})
|
||||
endif()
|
||||
|
||||
set(LEX_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.cc)
|
||||
set(LEX_HEADER ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.hh)
|
||||
|
||||
# Set up dependencies
|
||||
set(DEPENDENCIES ${INPUT_FILE})
|
||||
if(ARGC GREATER 3)
|
||||
list(APPEND DEPENDENCIES ${ARGV3})
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${LEX_OUTPUT}
|
||||
DEPENDS ${DEPENDENCIES}
|
||||
COMMAND ${FLEX_EXECUTABLE} --header-file=${LEX_HEADER} -o ${LEX_OUTPUT} ${INPUT_FILE}
|
||||
COMMENT "Generating ${PREFIX}.cc from ${INPUT_FILE}"
|
||||
)
|
||||
|
||||
set(${OUTPUT_VAR} ${LEX_OUTPUT} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Function to generate Bison parser files
|
||||
# Usage: generate_bison_parser(OUTPUT_VAR HEADER_VAR INPUT_FILE [PREFIX])
|
||||
# OUTPUT_VAR: Variable name to store the output .cc file path
|
||||
# HEADER_VAR: Variable name to store the output .hh file path
|
||||
# INPUT_FILE: Path to the .yy input file
|
||||
# PREFIX: Optional prefix for the generated files (defaults to basename of input file)
|
||||
function(generate_bison_parser OUTPUT_VAR HEADER_VAR INPUT_FILE)
|
||||
get_filename_component(INPUT_BASENAME ${INPUT_FILE} NAME_WE)
|
||||
|
||||
if(ARGC GREATER 3)
|
||||
set(PREFIX ${ARGV3})
|
||||
else()
|
||||
set(PREFIX ${INPUT_BASENAME})
|
||||
endif()
|
||||
|
||||
set(YACC_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.cc)
|
||||
set(YACC_HEADER ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.hh)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${YACC_OUTPUT} ${YACC_HEADER}
|
||||
DEPENDS ${INPUT_FILE}
|
||||
COMMAND ${BISON_EXECUTABLE} -p ${PREFIX} --header=${YACC_HEADER} -o ${YACC_OUTPUT} ${INPUT_FILE}
|
||||
COMMENT "Generating ${PREFIX}.cc and ${PREFIX}.hh from ${INPUT_FILE}"
|
||||
)
|
||||
|
||||
set(${OUTPUT_VAR} ${YACC_OUTPUT} PARENT_SCOPE)
|
||||
set(${HEADER_VAR} ${YACC_HEADER} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Generate device attachment parser files using the generic functions
|
||||
# Generate Bison parser first (creates the header file)
|
||||
generate_bison_parser(YACC_OUTPUT YACC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/deviceManager/deviceAttachmentPipeSpecp.yy deviceAttachmentPipeSpecp)
|
||||
|
||||
# Generate Flex lexer with dependency on Bison header
|
||||
generate_flex_lexer(LEX_OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/deviceManager/deviceAttachmentPipeSpecl.ll deviceAttachmentPipeSpecl ${YACC_HEADER})
|
||||
@@ -1 +1,2 @@
|
||||
add_subdirectory(xcbXorg)
|
||||
add_subdirectory(livoxProto1)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
option(ENABLE_LIB_livoxProto1 "Enable Livox Protocol v1 backend lib" ON)
|
||||
|
||||
if(ENABLE_LIB_livoxProto1)
|
||||
add_library(livoxProto1 SHARED
|
||||
livoxProto1.cpp
|
||||
core.cpp
|
||||
device.cpp
|
||||
protocol.cpp
|
||||
broadcastListener.cpp
|
||||
)
|
||||
|
||||
# Set config define for header generation
|
||||
add_compile_definitions(CONFIG_LIB_LIVOXPROTO1_ENABLED)
|
||||
target_include_directories(livoxProto1 PUBLIC ${Boost_INCLUDE_DIRS})
|
||||
target_link_libraries(livoxProto1 ${Boost_LIBRARIES})
|
||||
|
||||
# Install rules
|
||||
install(TARGETS livoxProto1 DESTINATION lib)
|
||||
endif()
|
||||
@@ -0,0 +1,186 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <opts.h>
|
||||
#include "broadcastListener.h"
|
||||
|
||||
namespace livoxProto1 {
|
||||
namespace comms {
|
||||
|
||||
BroadcastListener::BroadcastListener(
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
uint16_t listeningPort, uint16_t connectPort
|
||||
)
|
||||
: componentThread(componentThread),
|
||||
listeningPort(listeningPort),
|
||||
connectPort(connectPort),
|
||||
deviceGoneAwayCb(nullptr),
|
||||
socket(componentThread->getIoService()),
|
||||
listeningEndpoint(boost::asio::ip::udp::v4(), listeningPort),
|
||||
isListening(false)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<DiscoveredDevice>
|
||||
BroadcastListener::getDevice(const std::string &deviceIdentifier) const
|
||||
{
|
||||
auto it = std::find_if(discoveredDevices.begin(), discoveredDevices.end(),
|
||||
[&deviceIdentifier](const std::shared_ptr<DiscoveredDevice>& device) {
|
||||
return comms::deviceIdentifiersEqual(
|
||||
device->deviceIdentifier, deviceIdentifier);
|
||||
}
|
||||
);
|
||||
|
||||
return it != discoveredDevices.end() ? *it : nullptr;
|
||||
}
|
||||
|
||||
void BroadcastListener::broadcastMsgInd(
|
||||
const boost::system::error_code& ec, std::size_t bytes_received)
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
std::cerr << __func__ << ": Error receiving broadcast message: "
|
||||
<< ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytes_received < sizeof(BroadcastMessage))
|
||||
{
|
||||
std::cerr << __func__
|
||||
<< ": Received packet too small: " << bytes_received
|
||||
<< " bytes (expected at least "
|
||||
<< sizeof(BroadcastMessage) << ")" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Use placement new to construct BroadcastMessage in the buffer
|
||||
BroadcastMessage* msg = new (bcastMsgRecvBuffer) BroadcastMessage;
|
||||
|
||||
// Following the clean receiving flow:
|
||||
// 1. Swap CRC32 to host endianness first
|
||||
msg->footer.swapCrc32ToHostEndianness();
|
||||
// 2. Validate CRC32 (on whole message excluding footer CRC32 field)
|
||||
if (!msg->validateCrc32())
|
||||
{
|
||||
std::cerr << __func__
|
||||
<< ": Broadcast message failed CRC32 validation" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Swap CRC16 to host endianness
|
||||
msg->header.swapCrc16ToHostEndianness();
|
||||
// 4. Validate CRC16 (on header only)
|
||||
if (!msg->header.validateCrc16())
|
||||
{
|
||||
std::cerr << __func__
|
||||
<< ": Broadcast message failed CRC16 validation" << std::endl;
|
||||
return;
|
||||
}
|
||||
// 5. Swap content to host endianness
|
||||
msg->swapContentsToHostEndianness();
|
||||
// 6. Validate message sanity
|
||||
if (!msg->sanityCheck())
|
||||
{
|
||||
std::cerr << __func__
|
||||
<< ": Broadcast message failed sanity check" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract device information
|
||||
std::string senderIP = senderEndpoint.address().to_string();
|
||||
std::string broadcastCode(
|
||||
reinterpret_cast<const char*>(msg->broadcast_code));
|
||||
|
||||
// Early return if device already exists
|
||||
if (deviceExists(broadcastCode))
|
||||
{
|
||||
// Device already exists, just log the update
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__
|
||||
<< ": Received broadcast from known device: "
|
||||
<< broadcastCode << " at " << senderIP << "\n";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new DiscoveredDevice using conversion constructor
|
||||
auto device = std::make_shared<DiscoveredDevice>(*msg, senderIP);
|
||||
discoveredDevices.push_back(device);
|
||||
|
||||
// Output device information using stringify
|
||||
std::cout << __func__ << ": Discovered new Livox device: "
|
||||
<< device->stringify() << "\n";
|
||||
}
|
||||
|
||||
void BroadcastListener::start(void)
|
||||
{
|
||||
if (isListening.load()) { return; }
|
||||
|
||||
try
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Set up a boost::asio udp listening socket on the broadcast listening
|
||||
* port.
|
||||
*
|
||||
* FIXME:
|
||||
* We should also set up a timer to check for devices that have gone
|
||||
* away.
|
||||
*/
|
||||
socket.open(boost::asio::ip::udp::v4());
|
||||
socket.bind(listeningEndpoint);
|
||||
|
||||
isListening.store(true);
|
||||
// Start the first async receive operation
|
||||
startReceive();
|
||||
std::cout << __func__ << ": BroadcastListener started on port "
|
||||
<< listeningPort << std::endl;
|
||||
}
|
||||
catch (const boost::system::system_error& e)
|
||||
{
|
||||
isListening.store(false);
|
||||
std::cerr << __func__ << ": Failed to start BroadcastListener: "
|
||||
<< e.what() << std::endl;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void BroadcastListener::startReceive(void)
|
||||
{
|
||||
if (!isListening.load()) { return; }
|
||||
|
||||
socket.async_receive_from(
|
||||
boost::asio::buffer(bcastMsgRecvBuffer, sizeof(bcastMsgRecvBuffer)),
|
||||
senderEndpoint,
|
||||
[this](const boost::system::error_code& ec, std::size_t bytes_received)
|
||||
{
|
||||
broadcastMsgInd(ec, bytes_received);
|
||||
|
||||
// Continue listening for the next packet
|
||||
if (isListening.load())
|
||||
{ startReceive(); }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void BroadcastListener::stop(void)
|
||||
{
|
||||
if (!isListening.load()) { return; }
|
||||
|
||||
isListening.store(false);
|
||||
|
||||
try
|
||||
{
|
||||
socket.close();
|
||||
std::cout << __func__ << ": BroadcastListener stopped" << std::endl;
|
||||
}
|
||||
catch (const boost::system::system_error& e)
|
||||
{
|
||||
std::cerr << __func__ << ": Error stopping BroadcastListener: " << e.what()
|
||||
<< std::endl;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace comms
|
||||
} // namespace livoxProto1
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef BROADCAST_LISTENER_H
|
||||
#define BROADCAST_LISTENER_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include "device.h"
|
||||
|
||||
namespace livoxProto1 {
|
||||
namespace comms {
|
||||
|
||||
/** EXPLANATION:
|
||||
* This class merely listens for UDP bcast dgrams on the designated listening
|
||||
* port. It then builds a list of client device IP addrs that it has heard from.
|
||||
* It doesn't connect to them or signal any events to the rest of the lib,
|
||||
* except in the case that a device which the lib is using has gone away.
|
||||
*
|
||||
* Other than that, its role is to tell the lib which devices are available
|
||||
* on the network.
|
||||
*/
|
||||
#define UDP_BCAST_MSG_BUFFER_NBYTES (1024)
|
||||
|
||||
class BroadcastListener
|
||||
{
|
||||
public:
|
||||
BroadcastListener(
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
uint16_t listeningPort=55000, uint16_t connectPort=65000);
|
||||
|
||||
~BroadcastListener() = default;
|
||||
|
||||
typedef void (DeviceGoneAwayCbFn)(const DiscoveredDevice &device);
|
||||
void setDeviceGoneAwayCb(DeviceGoneAwayCbFn *cb)
|
||||
{ deviceGoneAwayCb = cb; }
|
||||
|
||||
bool deviceExists(const std::string &deviceIdentifier) const
|
||||
{ return getDevice(deviceIdentifier) != nullptr; }
|
||||
|
||||
std::shared_ptr<DiscoveredDevice>
|
||||
getDevice(const std::string &deviceIdentifier) const;
|
||||
|
||||
void start(void);
|
||||
void stop(void);
|
||||
|
||||
void broadcastMsgInd(
|
||||
const boost::system::error_code& ec, std::size_t bytes_received);
|
||||
|
||||
private:
|
||||
void startReceive(void);
|
||||
|
||||
private:
|
||||
std::shared_ptr<smo::ComponentThread> componentThread;
|
||||
/** EXPLANATION:
|
||||
* The Livox proto says that client devices will spam broadcast UDP
|
||||
* dgrams to us on the listening port. We can then use the source IP from
|
||||
* the bcast dgram to figure out the client device's IP addr. Then we
|
||||
* should send a connect dgram to the connect port. This will tell the
|
||||
* client device our IP addr.
|
||||
*/
|
||||
uint16_t listeningPort, connectPort;
|
||||
DeviceGoneAwayCbFn *deviceGoneAwayCb;
|
||||
std::vector<std::shared_ptr<DiscoveredDevice>> discoveredDevices;
|
||||
|
||||
boost::asio::ip::udp::socket socket;
|
||||
boost::asio::ip::udp::endpoint listeningEndpoint, senderEndpoint;
|
||||
std::atomic<bool> isListening;
|
||||
|
||||
uint8_t bcastMsgRecvBuffer[UDP_BCAST_MSG_BUFFER_NBYTES];
|
||||
};
|
||||
|
||||
} // namespace comms
|
||||
} // namespace livoxProto1
|
||||
|
||||
#endif // BROADCAST_LISTENER_H
|
||||
@@ -0,0 +1,273 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <opts.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <callback.h>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include "protocol.h"
|
||||
#include "core.h"
|
||||
#include "device.h"
|
||||
#include "broadcastListener.h"
|
||||
#include "livoxProto1.h"
|
||||
|
||||
|
||||
namespace livoxProto1 {
|
||||
|
||||
static ProtoState protoState =
|
||||
{
|
||||
.isInitialized = false,
|
||||
.componentThread = nullptr,
|
||||
.deviceManager = nullptr,
|
||||
.smoCallbacks = {}
|
||||
};
|
||||
|
||||
ProtoState& getProtoState()
|
||||
{
|
||||
return protoState;
|
||||
}
|
||||
|
||||
DeviceManager::DeviceManager()
|
||||
: broadcastListener(protoState.componentThread)
|
||||
{
|
||||
broadcastListener.setDeviceGoneAwayCb(deviceGoneAwayInd);
|
||||
}
|
||||
|
||||
void DeviceManager::deviceGoneAwayInd(const comms::DiscoveredDevice &device)
|
||||
{
|
||||
std::cout << "Device gone away: " << device.stringify() << std::endl;
|
||||
|
||||
// Check if device exists in our collection
|
||||
if (!protoState.deviceManager->getDevice(device)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find and remove the device from the collection
|
||||
auto it = std::find_if(
|
||||
protoState.deviceManager->devices.begin(),
|
||||
protoState.deviceManager->devices.end(),
|
||||
[&device](const std::shared_ptr<Device> &d) {
|
||||
return d->discoveredDevice == device;
|
||||
}
|
||||
);
|
||||
if (it != protoState.deviceManager->devices.end()) {
|
||||
protoState.deviceManager->devices.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Device>> DeviceManager::getDevice(
|
||||
const std::string &deviceIdentifier
|
||||
)
|
||||
{
|
||||
for (auto& device : devices)
|
||||
{
|
||||
if (comms::deviceIdentifiersEqual(
|
||||
device->discoveredDevice.deviceIdentifier, deviceIdentifier))
|
||||
{
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// GetOrCreateDeviceReq nested class implementation
|
||||
class DeviceManager::GetOrCreateDeviceReq
|
||||
: public smo::NonPostedAsynchronousContinuation<
|
||||
livoxProto1_getOrCreateDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
DeviceManager& deviceManager;
|
||||
// The device we're trying to connect (holds all connection parameters)
|
||||
std::shared_ptr<Device> pendingDevice;
|
||||
|
||||
public:
|
||||
GetOrCreateDeviceReq(
|
||||
DeviceManager& mgr,
|
||||
std::shared_ptr<Device> device,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> cb)
|
||||
: smo::NonPostedAsynchronousContinuation<
|
||||
livoxProto1_getOrCreateDeviceReqCbFn>(std::move(cb)),
|
||||
deviceManager(mgr), pendingDevice(device)
|
||||
{}
|
||||
|
||||
// Public accessor for the original callback
|
||||
void callOriginalCallback(bool success, std::shared_ptr<Device> device)
|
||||
{ callOriginalCb(success, device); }
|
||||
|
||||
void callOriginalCallbackWithFailure()
|
||||
{ callOriginalCallback(false, nullptr); }
|
||||
|
||||
void getOrCreateDeviceReq1(
|
||||
std::shared_ptr<GetOrCreateDeviceReq> context, bool connectSuccess
|
||||
)
|
||||
{
|
||||
if (!connectSuccess)
|
||||
{
|
||||
std::cerr << __func__ << ": Connection failed for device "
|
||||
<< context->pendingDevice->discoveredDevice.deviceIdentifier
|
||||
<< std::endl;
|
||||
context->callOriginalCallbackWithFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection successful, add device to collection
|
||||
context->deviceManager.devices.push_back(context->pendingDevice);
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": Successfully connected and added device "
|
||||
<< context->pendingDevice->discoveredDevice.deviceIdentifier
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// Return success with the connected device
|
||||
context->callOriginalCallback(true, context->pendingDevice);
|
||||
}
|
||||
};
|
||||
|
||||
void DeviceManager::getOrCreateDeviceReq(
|
||||
const std::string &deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback)
|
||||
{
|
||||
// Validate smoIp format using Boost.Asio IPv4 validation
|
||||
if (!smoIp.empty() && !comms::isValidIPv4(smoIp))
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
std::string(__func__) +
|
||||
": Invalid IPv4 smoIp format: " + smoIp);
|
||||
}
|
||||
|
||||
// Validate subnet nbits
|
||||
if (smoSubnetNbits > 32)
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
std::string(__func__) +
|
||||
": smoSubnetNbits must be between 0 and 32, got: " +
|
||||
std::to_string(smoSubnetNbits));
|
||||
}
|
||||
|
||||
// First try to get existing device
|
||||
auto existingDevice = getDevice(deviceIdentifier);
|
||||
if (existingDevice)
|
||||
{
|
||||
// Device already exists and is connected, return it
|
||||
callback.callbackFn(true, existingDevice.value());
|
||||
return;
|
||||
}
|
||||
|
||||
// Device doesn't exist, create a new one but don't add it to collection yet
|
||||
auto newDevice = std::make_shared<Device>(
|
||||
deviceIdentifier, componentThread,
|
||||
handshakeTimeoutMs, retryDelayMs,
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort);
|
||||
|
||||
// Create the continuation request object to hold state and callbacks
|
||||
auto request = std::make_shared<GetOrCreateDeviceReq>(
|
||||
*this, newDevice, std::move(callback));
|
||||
|
||||
// Start the connection process - only add to collection on success
|
||||
request->pendingDevice->connectReq(
|
||||
{request, std::bind(
|
||||
&DeviceManager::GetOrCreateDeviceReq::getOrCreateDeviceReq1,
|
||||
request.get(), request, std::placeholders::_1)});
|
||||
}
|
||||
|
||||
class DeviceManager::DestroyDeviceReq
|
||||
: public smo::NonPostedAsynchronousContinuation<
|
||||
livoxProto1_destroyDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
DeviceManager& deviceManager;
|
||||
std::shared_ptr<Device> pendingDevice;
|
||||
|
||||
public:
|
||||
DestroyDeviceReq(
|
||||
DeviceManager& mgr,
|
||||
std::shared_ptr<Device> device,
|
||||
smo::Callback<livoxProto1_destroyDeviceReqCbFn> cb)
|
||||
: smo::NonPostedAsynchronousContinuation<
|
||||
livoxProto1_destroyDeviceReqCbFn>(std::move(cb)),
|
||||
deviceManager(mgr), pendingDevice(device)
|
||||
{}
|
||||
|
||||
// Public accessor for the original callback
|
||||
void callOriginalCallback(bool success)
|
||||
{ callOriginalCb(success); }
|
||||
|
||||
void callOriginalCallbackWithFailure()
|
||||
{ callOriginalCallback(false); }
|
||||
|
||||
void destroyDeviceReq1(
|
||||
std::shared_ptr<DestroyDeviceReq> context, bool success
|
||||
)
|
||||
{
|
||||
context->deviceManager.devices.erase(
|
||||
std::remove(
|
||||
context->deviceManager.devices.begin(),
|
||||
context->deviceManager.devices.end(),
|
||||
context->pendingDevice),
|
||||
context->deviceManager.devices.end());
|
||||
|
||||
context->callOriginalCallback(success);
|
||||
}
|
||||
};
|
||||
|
||||
void DeviceManager::destroyDeviceReq(
|
||||
std::shared_ptr<Device> dev,
|
||||
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback
|
||||
)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Check to see if the device is in our collection. If so, call
|
||||
* disconnectReq and then remove it.
|
||||
*/
|
||||
std::shared_ptr<Device> device = getDevice(dev->discoveredDevice).
|
||||
value_or(nullptr);
|
||||
|
||||
if (!device)
|
||||
{
|
||||
callback.callbackFn(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto request = std::make_shared<DestroyDeviceReq>(
|
||||
*this, device, std::move(callback));
|
||||
|
||||
device->disconnectReq(
|
||||
{request, std::bind(
|
||||
&DeviceManager::DestroyDeviceReq::destroyDeviceReq1,
|
||||
request.get(), request, std::placeholders::_1)});
|
||||
}
|
||||
|
||||
void main(const std::shared_ptr<smo::ComponentThread> &componentThread,
|
||||
const smo::sense_api::SmoCallbacks& smoCallbacks)
|
||||
{
|
||||
if (protoState.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
protoState.isInitialized = true;
|
||||
protoState.componentThread = componentThread;
|
||||
protoState.smoCallbacks = smoCallbacks;
|
||||
protoState.deviceManager = std::make_unique<DeviceManager>();
|
||||
protoState.deviceManager->broadcastListener.start();
|
||||
}
|
||||
|
||||
void exit(void)
|
||||
{
|
||||
if (!protoState.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
protoState.deviceManager->broadcastListener.stop();
|
||||
protoState.deviceManager.reset();
|
||||
protoState.componentThread.reset();
|
||||
protoState.isInitialized = false;
|
||||
}
|
||||
|
||||
} // namespace livoxProto1
|
||||
@@ -0,0 +1,78 @@
|
||||
#ifndef LIVOXPROTO1_CORE_H
|
||||
#define LIVOXPROTO1_CORE_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include "device.h"
|
||||
#include "broadcastListener.h"
|
||||
#include "livoxProto1.h"
|
||||
#include <callback.h>
|
||||
|
||||
namespace livoxProto1 {
|
||||
|
||||
class DeviceManager
|
||||
{
|
||||
public:
|
||||
DeviceManager();
|
||||
~DeviceManager() = default;
|
||||
|
||||
static void deviceGoneAwayInd(const comms::DiscoveredDevice &device);
|
||||
|
||||
void getOrCreateDeviceReq(
|
||||
const std::string &deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
|
||||
|
||||
void destroyDeviceReq(
|
||||
std::shared_ptr<Device> device,
|
||||
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback);
|
||||
|
||||
std::optional<std::shared_ptr<Device>> getDevice(
|
||||
const std::string &deviceIdentifier);
|
||||
|
||||
std::optional<std::shared_ptr<Device>> getDevice(
|
||||
const comms::DiscoveredDevice &device)
|
||||
{
|
||||
return getDevice(device.deviceIdentifier);
|
||||
}
|
||||
|
||||
private:
|
||||
// Configuration
|
||||
static constexpr int RETRY_DELAY_SECONDS = 3; // <N> seconds delay
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<Device>> devices;
|
||||
comms::BroadcastListener broadcastListener;
|
||||
|
||||
// Nested continuation class for async device creation
|
||||
class GetOrCreateDeviceReq;
|
||||
class DestroyDeviceReq;
|
||||
};
|
||||
|
||||
void main(
|
||||
const std::shared_ptr<smo::ComponentThread> &componentThread,
|
||||
const smo::sense_api::SmoCallbacks& smoCallbacks);
|
||||
void exit(void);
|
||||
|
||||
// Global state structure
|
||||
struct ProtoState
|
||||
{
|
||||
bool isInitialized = false;
|
||||
std::shared_ptr<smo::ComponentThread> componentThread;
|
||||
std::unique_ptr<DeviceManager> deviceManager;
|
||||
smo::sense_api::SmoCallbacks smoCallbacks;
|
||||
};
|
||||
|
||||
// Access to global state for extern "C" functions
|
||||
ProtoState& getProtoState();
|
||||
|
||||
} // namespace livoxProto1
|
||||
|
||||
#endif // LIVOXPROTO1_CORE_H
|
||||
@@ -0,0 +1,133 @@
|
||||
#ifndef LIVOX_PROTO1_DEVICE_H
|
||||
#define LIVOX_PROTO1_DEVICE_H
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include "protocol.h"
|
||||
#include <callback.h>
|
||||
|
||||
// Forward declaration
|
||||
namespace smo {
|
||||
class ComponentThread;
|
||||
}
|
||||
|
||||
namespace livoxProto1 {
|
||||
namespace comms {
|
||||
|
||||
/** EXPLANATION:
|
||||
* This class represents a discovered device. It is used to store the
|
||||
* device identifier and IP address of a discovered device.
|
||||
*/
|
||||
class DiscoveredDevice
|
||||
{
|
||||
public:
|
||||
DiscoveredDevice(
|
||||
const std::string &deviceIdentifier,
|
||||
DeviceType deviceType,
|
||||
const std::string &ipAddr);
|
||||
|
||||
// "Conversion" constructor from BroadcastMessage
|
||||
DiscoveredDevice(const BroadcastMessage &msg, const std::string &ipAddr);
|
||||
|
||||
~DiscoveredDevice() = default;
|
||||
|
||||
bool operator==(const DiscoveredDevice &other) const
|
||||
{
|
||||
return comms::deviceIdentifiersEqual(
|
||||
deviceIdentifier, other.deviceIdentifier);
|
||||
}
|
||||
|
||||
std::string stringify(void) const;
|
||||
std::string getDeviceTypeName(void) const;
|
||||
|
||||
public:
|
||||
std::string deviceIdentifier;
|
||||
DeviceType deviceType;
|
||||
std::string ipAddr;
|
||||
};
|
||||
|
||||
} // namespace comms
|
||||
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
Device(const std::string &deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
|
||||
~Device();
|
||||
|
||||
public:
|
||||
comms::DiscoveredDevice discoveredDevice;
|
||||
|
||||
// Configuration
|
||||
std::shared_ptr<smo::ComponentThread> componentThread;
|
||||
int handshakeTimeoutMs, retryDelayMs;
|
||||
std::string smoIp;
|
||||
std::string detectedSmoListeningIp;
|
||||
uint8_t smoSubnetNbits;
|
||||
uint16_t dataPort, cmdPort, imuPort;
|
||||
|
||||
private:
|
||||
// Heartbeat mechanism
|
||||
void startHeartbeat();
|
||||
void sendHeartbeat();
|
||||
void onHeartbeatTimer(const boost::system::error_code& error);
|
||||
std::string generateClientDeviceIpFromSerialNumber(
|
||||
const std::string& broadcastCode);
|
||||
|
||||
// IP detection methods
|
||||
std::optional<std::string> detectSmoIp(const std::string& deviceIP);
|
||||
uint32_t getSubnetMaskFor(uint8_t nbits);
|
||||
|
||||
class ConnectReq;
|
||||
class ConnectToKnownDeviceReq;
|
||||
class ConnectByDeviceIdentifierReq;
|
||||
class ExecuteHandshakeReq;
|
||||
class DisconnectReq;
|
||||
|
||||
public:
|
||||
// Utility methods
|
||||
std::optional<std::string> getSmoIp(const std::string& deviceIP);
|
||||
|
||||
// Callback function type definitions for async methods
|
||||
typedef std::function<void(bool success)> connectReqCbFn;
|
||||
typedef std::function<
|
||||
void(bool success, const std::string& ipAddr, int fd)>
|
||||
connectToKnownDeviceReqCbFn;
|
||||
typedef std::function<
|
||||
void(bool success, const std::string& ipAddr, int fd)>
|
||||
connectByDeviceIdentifierReqCbFn;
|
||||
typedef std::function<void(bool success, int fd)> executeHandshakeReqCbFn;
|
||||
typedef std::function<void(bool success)> disconnectReqCbFn;
|
||||
|
||||
// Async connection methods
|
||||
void connectReq(smo::Callback<connectReqCbFn> callback);
|
||||
void connectToKnownDeviceReq(
|
||||
smo::Callback<connectToKnownDeviceReqCbFn> callback);
|
||||
void connectByDeviceIdentifierReq(
|
||||
smo::Callback<connectByDeviceIdentifierReqCbFn> callback);
|
||||
void executeHandshakeReq(
|
||||
const std::string& deviceIP,
|
||||
smo::Callback<executeHandshakeReqCbFn> callback);
|
||||
void disconnectReq(smo::Callback<disconnectReqCbFn> callback);
|
||||
|
||||
// Heartbeat state
|
||||
std::unique_ptr<boost::asio::deadline_timer> heartbeatTimer;
|
||||
int heartbeatFd; // Socket file descriptor used for heartbeat
|
||||
std::atomic<bool> heartbeatActive;
|
||||
};
|
||||
|
||||
} // namespace livoxProto1
|
||||
|
||||
#endif // LIVOX_PROTO1_DEVICE_H
|
||||
@@ -0,0 +1,65 @@
|
||||
#include <stdexcept>
|
||||
#include <callback.h>
|
||||
#include "livoxProto1.h"
|
||||
#include "device.h"
|
||||
#include "core.h"
|
||||
|
||||
|
||||
extern "C" {
|
||||
|
||||
void livoxProto1_getOrCreateDeviceReq(
|
||||
const std::string& deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback
|
||||
)
|
||||
{
|
||||
// Get the global DeviceManager instance
|
||||
auto& protoState = livoxProto1::getProtoState();
|
||||
if (!protoState.deviceManager)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": LivoxProto1 not initialized - call "
|
||||
"livoxProto1_main first");
|
||||
}
|
||||
|
||||
// Delegate to DeviceManager
|
||||
protoState.deviceManager->getOrCreateDeviceReq(
|
||||
deviceIdentifier, componentThread,
|
||||
handshakeTimeoutMs, retryDelayMs,
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort,
|
||||
callback);
|
||||
}
|
||||
|
||||
void livoxProto1_destroyDeviceReq(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback
|
||||
)
|
||||
{
|
||||
auto& protoState = livoxProto1::getProtoState();
|
||||
if (!protoState.deviceManager)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": DeviceManager not initialized");
|
||||
}
|
||||
|
||||
protoState.deviceManager->destroyDeviceReq(
|
||||
device, callback);
|
||||
}
|
||||
|
||||
void livoxProto1_main(
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
const smo::sense_api::SmoCallbacks& smoCallbacks)
|
||||
{
|
||||
livoxProto1::main(componentThread, smoCallbacks);
|
||||
}
|
||||
|
||||
void livoxProto1_exit(void)
|
||||
{
|
||||
livoxProto1::exit();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef LIVOXPROTO1_H
|
||||
#define LIVOXPROTO1_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <callback.h>
|
||||
|
||||
// Forward declarations
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
struct SmoCallbacks;
|
||||
}
|
||||
class ComponentThread;
|
||||
}
|
||||
|
||||
namespace livoxProto1 {
|
||||
class Device;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize the Livox protocol library
|
||||
* @param componentThread Component thread shared pointer
|
||||
* @param smoCallbacks Callbacks provided by SMO
|
||||
*/
|
||||
typedef void livoxProto1_mainFn(
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
const smo::sense_api::SmoCallbacks& smoCallbacks);
|
||||
|
||||
/**
|
||||
* Cleanup the Livox protocol library
|
||||
*/
|
||||
typedef void livoxProto1_exitFn(void);
|
||||
|
||||
/**
|
||||
* Create a new Livox device connection
|
||||
* @param deviceIdentifier The device identifier (broadcast code)
|
||||
* @param componentThread Component thread for async operations
|
||||
* @param handshakeTimeoutMs Handshake timeout in milliseconds (default: 1000)
|
||||
* @param retryDelayMs Retry delay in milliseconds (default: 3000)
|
||||
* @param smoIp SMO IP address (empty string for auto-detection)
|
||||
* @param smoSubnetNbits SMO subnet mask bits (e.g., 24 for /24, 16 for /16)
|
||||
* @param dataPort Data port for point cloud (default: 56000)
|
||||
* @param cmdPort Command port (default: 56001)
|
||||
* @param imuPort IMU port (default: 56002)
|
||||
* @return Device pointer on success, nullptr on failure
|
||||
*/
|
||||
typedef std::function<
|
||||
void(bool success, std::shared_ptr<livoxProto1::Device> device)>
|
||||
livoxProto1_getOrCreateDeviceReqCbFn;
|
||||
|
||||
typedef void livoxProto1_getOrCreateDeviceReqFn(
|
||||
const std::string& deviceIdentifier,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
int handshakeTimeoutMs, int retryDelayMs,
|
||||
const std::string& smoIp, uint8_t smoSubnetNbits,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
|
||||
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
|
||||
|
||||
typedef std::function<void(bool success)> livoxProto1_destroyDeviceReqCbFn;
|
||||
typedef void livoxProto1_destroyDeviceReqFn(
|
||||
std::shared_ptr<livoxProto1::Device> device,
|
||||
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback);
|
||||
|
||||
|
||||
livoxProto1_mainFn livoxProto1_main;
|
||||
livoxProto1_exitFn livoxProto1_exit;
|
||||
livoxProto1_getOrCreateDeviceReqFn livoxProto1_getOrCreateDeviceReq;
|
||||
livoxProto1_destroyDeviceReqFn livoxProto1_destroyDeviceReq;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // LIVOXPROTO1_H
|
||||
@@ -0,0 +1,712 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
#include "protocol.h"
|
||||
|
||||
namespace livoxProto1 {
|
||||
namespace comms {
|
||||
|
||||
// Command methods
|
||||
void Command::swapToHostEndianness()
|
||||
{
|
||||
// No multi-byte fields to swap
|
||||
}
|
||||
|
||||
void Command::swapToProtocolEndianness()
|
||||
{
|
||||
// No multi-byte fields to swap
|
||||
}
|
||||
|
||||
bool Command::sanityCheck() const
|
||||
{
|
||||
// Basic validation - can be extended for specific command sets
|
||||
return true;
|
||||
}
|
||||
|
||||
// Header methods
|
||||
void Header::swapToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
length = __builtin_bswap16(length);
|
||||
seq_num = __builtin_bswap16(seq_num);
|
||||
crc_16 = __builtin_bswap16(crc_16);
|
||||
}
|
||||
|
||||
void Header::swapToProtocolEndianness()
|
||||
{
|
||||
// Protocol is little-endian, so if host is already little-endian, no swap needed
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
// Host is big-endian, need to swap to little-endian
|
||||
length = __builtin_bswap16(length);
|
||||
seq_num = __builtin_bswap16(seq_num);
|
||||
crc_16 = __builtin_bswap16(crc_16);
|
||||
}
|
||||
|
||||
bool Header::sanityCheck() const
|
||||
{
|
||||
return (sof == 0xAA) && (version == 1);
|
||||
}
|
||||
|
||||
uint16_t Header::calculateCrc16() const
|
||||
{
|
||||
// Calculate CRC16 for the header excluding the crc_16 field itself
|
||||
// This matches the Livox SDK approach: calculate over raw bytes excluding CRC16 field
|
||||
const uint8_t* headerData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t headerSize = sizeof(Header) - sizeof(crc_16); // Exclude CRC16 field
|
||||
|
||||
return comms::calculateCrc16(headerData, headerSize);
|
||||
}
|
||||
|
||||
bool Header::validateCrc16() const
|
||||
{
|
||||
// Calculate CRC16 for the header excluding the crc_16 field itself
|
||||
uint16_t calculatedCrc = calculateCrc16();
|
||||
|
||||
// Compare with the CRC in the header
|
||||
bool isValid = (calculatedCrc == crc_16);
|
||||
|
||||
// Debug output only if validation fails
|
||||
if (!isValid) {
|
||||
std::cout << "CRC16 Debug: calculated=0x" << std::hex << calculatedCrc
|
||||
<< ", received=0x" << crc_16 << std::dec << std::endl;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
void Header::setCrc16FromRawBytes()
|
||||
{
|
||||
// Calculate CRC16 on raw bytes and set it (after endianness swap)
|
||||
crc_16 = calculateCrc16();
|
||||
}
|
||||
|
||||
void Header::swapCrc16ToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
crc_16 = __builtin_bswap16(crc_16);
|
||||
}
|
||||
|
||||
void Header::swapCrc16ToProtocolEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
crc_16 = __builtin_bswap16(crc_16);
|
||||
}
|
||||
|
||||
// Footer methods
|
||||
void Footer::swapToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
crc_32 = __builtin_bswap32(crc_32);
|
||||
}
|
||||
|
||||
void Footer::swapToProtocolEndianness()
|
||||
{
|
||||
// Protocol is little-endian, so if host is already little-endian, no swap needed
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
// Host is big-endian, need to swap to little-endian
|
||||
crc_32 = __builtin_bswap32(crc_32);
|
||||
}
|
||||
|
||||
void Footer::swapCrc32ToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
crc_32 = __builtin_bswap32(crc_32);
|
||||
}
|
||||
|
||||
void Footer::swapCrc32ToProtocolEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
crc_32 = __builtin_bswap32(crc_32);
|
||||
}
|
||||
|
||||
bool Footer::validateCrc32() const
|
||||
{
|
||||
// This method should validate the CRC32 against the message content
|
||||
// For now, we'll return true since the validation is done on raw bytes
|
||||
// before struct construction in the receiving flow
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Footer::sanityCheck() const
|
||||
{
|
||||
/** FIXME:
|
||||
* Add CRC validation here.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
// BroadcastMessage methods
|
||||
void BroadcastMessage::swapContentsToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
// Only swap content fields, not CRC fields
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
reserved = __builtin_bswap16(reserved);
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
|
||||
bool BroadcastMessage::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) &&
|
||||
(command.cmd_id == 0x00) &&
|
||||
(header.cmd_type == 0x02) &&
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool BroadcastMessage::validateCrc32() const
|
||||
{
|
||||
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
|
||||
// Try calculating on the raw bytes of the entire message (excluding CRC field)
|
||||
uint32_t calculatedCrc = 0xFFFFFFFF;
|
||||
|
||||
// Calculate CRC32 over the entire message except the CRC field itself
|
||||
// The message structure is: header + command + broadcast_code + dev_type + reserved + footer(without crc_32)
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(BroadcastMessage) - sizeof(footer.crc_32);
|
||||
|
||||
calculatedCrc = comms::calculateCrc32(messageData, messageSize);
|
||||
|
||||
// Compare with the CRC in the footer
|
||||
bool isValid = (calculatedCrc == footer.crc_32);
|
||||
|
||||
// Debug output only if validation fails
|
||||
if (!isValid) {
|
||||
std::cout << "BroadcastMessage CRC32 Debug: calculated=0x" << std::hex << calculatedCrc
|
||||
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// HandshakeRequest methods
|
||||
HandshakeRequest::HandshakeRequest(
|
||||
const std::string& hostIP,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort
|
||||
)
|
||||
{
|
||||
// Initialize header
|
||||
header.sof = 0xAA;
|
||||
header.version = 1;
|
||||
header.length = sizeof(HandshakeRequest);
|
||||
header.cmd_type = 0x00; // CMD (request)
|
||||
header.seq_num = 1; // Sequence number
|
||||
header.crc_16 = 0; // Will be calculated later
|
||||
|
||||
// Initialize command
|
||||
command.cmd_set = 0x00; // General Command Set
|
||||
command.cmd_id = 0x01; // Handshake Command
|
||||
|
||||
// Parse host IP address
|
||||
std::istringstream iss(hostIP);
|
||||
std::string token;
|
||||
int i = 0;
|
||||
while (std::getline(iss, token, '.') && i < 4)
|
||||
{
|
||||
user_ip[i] = static_cast<uint8_t>(std::stoi(token));
|
||||
i++;
|
||||
}
|
||||
|
||||
// Set ports
|
||||
this->data_port = dataPort;
|
||||
this->cmd_port = cmdPort;
|
||||
this->imu_port = imuPort;
|
||||
|
||||
// Initialize footer
|
||||
footer.crc_32 = 0; // Will be calculated later
|
||||
// Note: CRC16 will be calculated before sending (in swapToProtocolEndianness)
|
||||
}
|
||||
|
||||
uint32_t HandshakeRequest::calculateCrc32() const
|
||||
{
|
||||
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(HandshakeRequest) - sizeof(footer.crc_32);
|
||||
|
||||
return comms::calculateCrc32(messageData, messageSize);
|
||||
}
|
||||
|
||||
|
||||
void HandshakeRequest::swapContentsToProtocolEndianness()
|
||||
{
|
||||
// Protocol uses little-endian, so on little-endian machines, no swap needed
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
|
||||
// On big-endian machines, swap to little-endian for wire transmission
|
||||
// Only swap content fields, not CRC fields
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
data_port = __builtin_bswap16(data_port);
|
||||
cmd_port = __builtin_bswap16(cmd_port);
|
||||
imu_port = __builtin_bswap16(imu_port);
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
void HandshakeRequest::swapContentsToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
data_port = __builtin_bswap16(data_port);
|
||||
cmd_port = __builtin_bswap16(cmd_port);
|
||||
imu_port = __builtin_bswap16(imu_port);
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
|
||||
bool HandshakeRequest::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) && (command.cmd_id == 0x01) &&
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
// HandshakeResponse methods
|
||||
void HandshakeResponse::swapContentsToHostEndianness()
|
||||
{
|
||||
if (endian::isLittleEndian()) { return; }
|
||||
// Only swap content fields, not CRC fields
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
|
||||
bool HandshakeResponse::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) && (command.cmd_id == 0x01) &&
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool HandshakeResponse::validateCrc32() const
|
||||
{
|
||||
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(HandshakeResponse) - sizeof(footer.crc_32);
|
||||
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
|
||||
|
||||
// Compare with the CRC in the footer
|
||||
bool isValid = (calculatedCrc == footer.crc_32);
|
||||
|
||||
// Debug output only if validation fails
|
||||
if (!isValid) {
|
||||
std::cout << "HandshakeResponse CRC32 Debug: calculated=0x" << std::hex << calculatedCrc
|
||||
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Standalone CRC16 calculation utility
|
||||
uint16_t calculateCrc16(const uint8_t* data, size_t length)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Livox SDK CRC16 implementation (exact copy from FastCRC library)
|
||||
* This matches the exact implementation used by Livox devices
|
||||
*/
|
||||
static const uint16_t crc_table_mcrf4xx[1024] = {
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
|
||||
};
|
||||
|
||||
// Livox SDK seed
|
||||
uint16_t crc = LIVOX_CRC16_SEED;
|
||||
|
||||
// Simple implementation for now - can be optimized later
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
crc = (crc >> 8) ^ crc_table_mcrf4xx[(crc & 0xff) ^ data[i]];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
// Standalone CRC32 calculation utility
|
||||
uint32_t calculateCrc32(const uint8_t* data, size_t length)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Livox SDK CRC32 implementation (exact copy from FastCRC library)
|
||||
* This matches the exact implementation used by Livox devices
|
||||
*/
|
||||
static const uint32_t crc_table_crc32[256] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
||||
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
||||
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
|
||||
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
|
||||
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
|
||||
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
|
||||
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
|
||||
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
||||
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
|
||||
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
|
||||
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
|
||||
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
|
||||
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
|
||||
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
|
||||
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
|
||||
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
||||
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
|
||||
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
|
||||
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
};
|
||||
|
||||
// Livox SDK seed XORed with 0xffffffff
|
||||
uint32_t crc = LIVOX_CRC32_SEED ^ 0xffffffff;
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
crc = (crc >> 8) ^ crc_table_crc32[(crc & 0xff) ^ data[i]];
|
||||
}
|
||||
|
||||
return crc ^ 0xffffffff;
|
||||
}
|
||||
|
||||
// IP address parsing utility
|
||||
std::optional<IPOctets> parseIPv4Address(const std::string& ipAddress)
|
||||
{
|
||||
IPOctets result;
|
||||
|
||||
std::istringstream iss(ipAddress);
|
||||
if (std::getline(iss, result.octet1, '.') &&
|
||||
std::getline(iss, result.octet2, '.') &&
|
||||
std::getline(iss, result.octet3, '.') &&
|
||||
std::getline(iss, result.octet4, '.'))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// HeartbeatMessage methods
|
||||
HeartbeatMessage::HeartbeatMessage()
|
||||
{
|
||||
// Initialize header
|
||||
header.sof = 0xAA;
|
||||
header.version = 0x01;
|
||||
header.length = sizeof(Header) + sizeof(Command) + sizeof(Footer);
|
||||
header.cmd_type = 0x00; // kCommandTypeCmd
|
||||
header.seq_num = 0x0001; // Simple sequence number
|
||||
header.crc_16 = 0; // Will be calculated
|
||||
|
||||
// Initialize command
|
||||
command.cmd_set = 0x00; // kCommandSetGeneral
|
||||
command.cmd_id = 0x03; // kCommandIDGeneralHeartbeat
|
||||
|
||||
// Initialize footer
|
||||
footer.crc_32 = 0; // Will be calculated
|
||||
// Note: CRC16 will be calculated before sending (in swapToProtocolEndianness)
|
||||
}
|
||||
|
||||
uint32_t HeartbeatMessage::calculateCrc32() const
|
||||
{
|
||||
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(HeartbeatMessage) - sizeof(footer.crc_32);
|
||||
|
||||
return comms::calculateCrc32(messageData, messageSize);
|
||||
}
|
||||
|
||||
|
||||
void HeartbeatMessage::swapContentsToProtocolEndianness()
|
||||
{
|
||||
// Protocol is little-endian, so if host is already little-endian, no swap needed
|
||||
if (endian::isLittleEndian()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Host is big-endian, need to swap to little-endian
|
||||
// Only swap content fields, not CRC fields
|
||||
header.swapToProtocolEndianness();
|
||||
command.swapToProtocolEndianness();
|
||||
// Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
void HeartbeatMessage::swapContentsToHostEndianness()
|
||||
{
|
||||
// If host is already little-endian, no swap needed
|
||||
if (endian::isLittleEndian()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Host is big-endian, need to swap from little-endian protocol to big-endian host
|
||||
// Only swap content fields, not CRC fields
|
||||
header.swapToHostEndianness();
|
||||
command.swapToHostEndianness();
|
||||
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
|
||||
bool HeartbeatMessage::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) && (command.cmd_id == 0x03) &&
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool HeartbeatMessage::validateCrc32() const
|
||||
{
|
||||
// Use the calculateCrc32 method to avoid code duplication
|
||||
uint32_t calculatedCrc = calculateCrc32();
|
||||
|
||||
// Compare with the CRC in the footer
|
||||
bool isValid = (calculatedCrc == footer.crc_32);
|
||||
|
||||
// Debug output only if validation fails
|
||||
if (!isValid) {
|
||||
std::cout << "HeartbeatMessage CRC32 Debug: calculated=0x" << std::hex << calculatedCrc
|
||||
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// DisconnectMessage methods
|
||||
DisconnectMessage::DisconnectMessage()
|
||||
{
|
||||
// Initialize header
|
||||
header.sof = 0xAA;
|
||||
header.version = 0x01;
|
||||
header.length = sizeof(Header) + sizeof(Command) + sizeof(Footer);
|
||||
header.cmd_type = 0x00; // kCommandTypeCmd
|
||||
header.seq_num = 0x0001; // Simple sequence number
|
||||
header.crc_16 = 0; // Will be calculated
|
||||
|
||||
// Initialize command
|
||||
command.cmd_set = 0x00; // kCommandSetGeneral
|
||||
command.cmd_id = 0x06; // kCommandIDGeneralDisconnect
|
||||
|
||||
// Initialize footer
|
||||
footer.crc_32 = 0; // Will be calculated
|
||||
// Note: CRC16 will be calculated before sending (in swapToProtocolEndianness)
|
||||
}
|
||||
|
||||
uint32_t DisconnectMessage::calculateCrc32() const
|
||||
{
|
||||
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
|
||||
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
|
||||
size_t messageSize = sizeof(DisconnectMessage) - sizeof(footer.crc_32);
|
||||
|
||||
return comms::calculateCrc32(messageData, messageSize);
|
||||
}
|
||||
|
||||
void DisconnectMessage::swapContentsToProtocolEndianness()
|
||||
{
|
||||
// Protocol is little-endian, so if host is already little-endian, no swap needed
|
||||
if (endian::isLittleEndian()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Host is big-endian, need to swap to little-endian
|
||||
// Only swap content fields, not CRC fields
|
||||
header.swapToProtocolEndianness();
|
||||
command.swapToProtocolEndianness();
|
||||
// Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here
|
||||
}
|
||||
|
||||
bool DisconnectMessage::sanityCheck() const
|
||||
{
|
||||
return header.sanityCheck() &&
|
||||
command.sanityCheck() &&
|
||||
(command.cmd_set == 0x00) && (command.cmd_id == 0x06) &&
|
||||
footer.sanityCheck();
|
||||
}
|
||||
|
||||
bool DisconnectMessage::validateCrc32() const
|
||||
{
|
||||
// Use the calculateCrc32 method to avoid code duplication
|
||||
uint32_t calculatedCrc = calculateCrc32();
|
||||
|
||||
// Compare with the CRC in the footer
|
||||
bool isValid = (calculatedCrc == footer.crc_32);
|
||||
|
||||
// Debug output only if validation fails
|
||||
if (!isValid)
|
||||
{
|
||||
std::cout << "DisconnectMessage CRC32 Debug: calculated=0x"
|
||||
<< std::hex << calculatedCrc
|
||||
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
} // namespace comms
|
||||
} // namespace livoxProto1
|
||||
@@ -0,0 +1,252 @@
|
||||
#ifndef LIVOXPROTO1_PROTOCOL_H
|
||||
#define LIVOXPROTO1_PROTOCOL_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <user/senseApiDesc.h>
|
||||
|
||||
namespace livoxProto1 {
|
||||
namespace comms {
|
||||
|
||||
/** EXPLANATION:
|
||||
* Undocumented Livox SDK CRC seed constants. These were found in the Livox SDK
|
||||
* source code.
|
||||
*/
|
||||
constexpr uint16_t LIVOX_CRC16_SEED = 0x4c49;
|
||||
constexpr uint32_t LIVOX_CRC32_SEED = 0x564f580a;
|
||||
|
||||
// Endianness detection
|
||||
namespace endian {
|
||||
inline bool isLittleEndian() {
|
||||
union {
|
||||
uint32_t i;
|
||||
char c[4];
|
||||
} test = {0x01020304};
|
||||
return test.c[0] == 4;
|
||||
}
|
||||
}
|
||||
|
||||
// IPv4 address validation
|
||||
inline bool isValidIPv4(const std::string& ipAddress) {
|
||||
boost::system::error_code ec;
|
||||
boost::asio::ip::address_v4::from_string(ipAddress, ec);
|
||||
return !ec;
|
||||
}
|
||||
|
||||
// CRC calculation utilities
|
||||
uint16_t calculateCrc16(
|
||||
const uint8_t* data, size_t length);
|
||||
uint32_t calculateCrc32(
|
||||
const uint8_t* data, size_t length);
|
||||
|
||||
// IP address parsing utility
|
||||
struct IPOctets {
|
||||
std::string octet1, octet2, octet3, octet4;
|
||||
};
|
||||
|
||||
std::optional<IPOctets> parseIPv4Address(const std::string& ipAddress);
|
||||
|
||||
// Device identifier comparison
|
||||
inline bool deviceIdentifiersEqual(
|
||||
const std::string& id1, const std::string& id2
|
||||
)
|
||||
{
|
||||
// Use pointers to avoid unnecessary string copies
|
||||
const std::string* serial1_ptr;
|
||||
const std::string* serial2_ptr;
|
||||
|
||||
// Local copies only needed for 15-character broadcast codes
|
||||
std::string serial1_copy, serial2_copy;
|
||||
|
||||
// Determine if id1 is serial (14 chars) or broadcast code (15 chars)
|
||||
if (id1.length() == 14) {
|
||||
serial1_ptr = &id1; // No copy needed, use original string
|
||||
} else if (id1.length() == 15) {
|
||||
serial1_copy = id1.substr(0, 14); // Copy only when necessary
|
||||
serial1_ptr = &serial1_copy;
|
||||
} else {
|
||||
return false; // Invalid length
|
||||
}
|
||||
|
||||
// Determine if id2 is serial (14 chars) or broadcast code (15 chars)
|
||||
if (id2.length() == 14) {
|
||||
serial2_ptr = &id2; // No copy needed, use original string
|
||||
} else if (id2.length() == 15) {
|
||||
serial2_copy = id2.substr(0, 14); // Copy only when necessary
|
||||
serial2_ptr = &serial2_copy;
|
||||
} else {
|
||||
return false; // Invalid length
|
||||
}
|
||||
|
||||
// Compare the serial numbers using pointers
|
||||
return *serial1_ptr == *serial2_ptr;
|
||||
}
|
||||
|
||||
/** EXPLANATION:
|
||||
* Device types as defined in the Livox protocol specification
|
||||
*/
|
||||
enum class DeviceType : uint8_t {
|
||||
Hub = 0,
|
||||
Mid40 = 1,
|
||||
Tele15 = 2,
|
||||
Horizon = 3,
|
||||
Mid70 = 6,
|
||||
Avia = 7
|
||||
};
|
||||
|
||||
/** EXPLANATION:
|
||||
* Protocol frame header structure.
|
||||
* All multi-byte fields are in little-endian format as per protocol spec.
|
||||
*/
|
||||
struct Header
|
||||
{
|
||||
uint8_t sof; // 0: Start of Frame (0xAA)
|
||||
uint8_t version; // 1: Protocol Version (1)
|
||||
uint16_t length; // 2-3: Frame Length (little-endian)
|
||||
uint8_t cmd_type; // 4: Command Type (0x02 = MSG for broadcast)
|
||||
uint16_t seq_num; // 5-6: Sequence Number (little-endian)
|
||||
uint16_t crc_16; // 7-8: Header Checksum (little-endian)
|
||||
|
||||
void swapToHostEndianness();
|
||||
void swapToProtocolEndianness();
|
||||
void swapCrc16ToHostEndianness();
|
||||
void swapCrc16ToProtocolEndianness();
|
||||
bool sanityCheck() const;
|
||||
uint16_t calculateCrc16() const;
|
||||
bool validateCrc16() const;
|
||||
void setCrc16FromRawBytes();
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Protocol frame footer structure.
|
||||
* All multi-byte fields are in little-endian format as per protocol spec.
|
||||
*/
|
||||
struct Footer
|
||||
{
|
||||
uint32_t crc_32; // 0-3: Whole Frame Checksum (little-endian)
|
||||
|
||||
void swapToHostEndianness();
|
||||
void swapToProtocolEndianness();
|
||||
void swapCrc32ToHostEndianness();
|
||||
void swapCrc32ToProtocolEndianness();
|
||||
bool validateCrc32() const;
|
||||
bool sanityCheck() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Command identification structure used in all Livox protocol messages.
|
||||
* Contains the command set and command ID fields.
|
||||
*/
|
||||
struct Command
|
||||
{
|
||||
uint8_t cmd_set; // 0: Command Set (0x00 = General, etc.)
|
||||
uint8_t cmd_id; // 1: Command ID (0x00 = Broadcast, 0x01 = Handshake, etc.)
|
||||
|
||||
void swapToHostEndianness();
|
||||
void swapToProtocolEndianness();
|
||||
bool sanityCheck() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete wire format for Livox broadcast messages.
|
||||
* All multi-byte fields are in little-endian format as per protocol spec.
|
||||
*/
|
||||
struct BroadcastMessage
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t broadcast_code[16]; // 11-26: Device Broadcast Code (null-terminated string)
|
||||
uint8_t dev_type; // 27: Device Type
|
||||
uint16_t reserved; // 28-29: Reserved (little-endian)
|
||||
Footer footer; // 30-33: Protocol frame footer
|
||||
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete handshake request frame for connecting to Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct HandshakeRequest
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t user_ip[4]; // 11-14: Host IP Address (little-endian)
|
||||
uint16_t data_port; // 15-16: Host Point Cloud Data UDP Destination Port (little-endian)
|
||||
uint16_t cmd_port; // 17-18: Host Control Command UDP Destination Port (little-endian)
|
||||
uint16_t imu_port; // 19-20: Host IMU UDP Destination Port (little-endian)
|
||||
Footer footer; // 21-24: Protocol frame footer
|
||||
|
||||
HandshakeRequest(
|
||||
const std::string& hostIP,
|
||||
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
|
||||
|
||||
// Calculate CRC32 for the entire message
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete handshake response frame from Livox devices.
|
||||
* This is the complete wire format including header, command fields, data, and footer.
|
||||
*/
|
||||
struct HandshakeResponse
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
|
||||
Footer footer; // 12-15: Protocol frame footer
|
||||
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete heartbeat command frame for maintaining connection with Livox devices.
|
||||
* This is the complete wire format including header, command fields, and footer.
|
||||
*/
|
||||
struct HeartbeatMessage
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
Footer footer; // 11-14: Protocol frame footer
|
||||
|
||||
HeartbeatMessage();
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
void swapContentsToHostEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/** EXPLANATION:
|
||||
* Complete disconnect command frame for disconnecting from Livox devices.
|
||||
* This is the complete wire format including header, command fields, and footer.
|
||||
*/
|
||||
struct DisconnectMessage
|
||||
{
|
||||
Header header; // 0-8: Protocol frame header
|
||||
Command command; // 9-10: Command identification
|
||||
Footer footer; // 11-14: Protocol frame footer
|
||||
|
||||
DisconnectMessage();
|
||||
uint32_t calculateCrc32() const;
|
||||
void swapContentsToProtocolEndianness();
|
||||
bool sanityCheck() const;
|
||||
bool validateCrc32() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
} // namespace comms
|
||||
} // namespace livoxProto1
|
||||
|
||||
#endif // LIVOXPROTO1_PROTOCOL_H
|
||||
@@ -0,0 +1,13 @@
|
||||
add_subdirectory(bodies)
|
||||
|
||||
add_daps_target(all_device_specs
|
||||
SOURCES
|
||||
avia0.dapss
|
||||
win0.dapss
|
||||
)
|
||||
|
||||
# Register this target for later dependency addition from main CMakeLists.txt
|
||||
register_daps_target(all_device_specs)
|
||||
# Make the DAPSS target part of the ALL target for this subdirectory
|
||||
# This ensures DAPSS targets are built when building just this subdirectory
|
||||
set_property(TARGET all_device_specs PROPERTY FOLDER "devices")
|
||||
@@ -0,0 +1,3 @@
|
||||
+edev|avia0|
|
||||
structural-implexor|livoxGen1()|livoxProto1()
|
||||
|3JEDK380010Z39
|
||||
@@ -0,0 +1,17 @@
|
||||
add_daps_target(body_rpi5_persys
|
||||
SOURCES
|
||||
rpi5-persys.dapss
|
||||
)
|
||||
|
||||
add_daps_target(body_dell_laptop
|
||||
SOURCES
|
||||
dell-laptop.dapss
|
||||
)
|
||||
|
||||
# Register this target for later dependency addition from main CMakeLists.txt
|
||||
register_daps_target(body_rpi5_persys)
|
||||
register_daps_target(body_dell_laptop)
|
||||
# Make the DAPSS target part of the ALL target for this subdirectory
|
||||
# This ensures DAPSS targets are built when building just this subdirectory
|
||||
set_property(TARGET body_rpi5_persys PROPERTY FOLDER "devices/bodies")
|
||||
set_property(TARGET body_dell_laptop PROPERTY FOLDER "devices/bodies")
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "../win0.dapss"
|
||||
||
|
||||
#include "../avia0.dapss"
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "../win0.dapss"
|
||||
||
|
||||
#include "../avia0.dapss"
|
||||
@@ -0,0 +1,3 @@
|
||||
+edev|win0|
|
||||
visual-implexor|xcb(dev-substring)|xorg(display=1|screen=0)
|
||||
|mut
|
||||
@@ -0,0 +1,67 @@
|
||||
Ok: I realized I may be able to bridge async sequences without needing needing to make a bunch of functions async. The basic thing is:
|
||||
|
||||
```
|
||||
funcThatCallsAsyncFuncsButWhoseSignatureIsItselfSync(ComponentThread &ct)
|
||||
{
|
||||
std::atomic continuationCondition(false);
|
||||
|
||||
async_call(ct, [] {
|
||||
// Do stuff that will enqueue events on ct.
|
||||
continuationCondition.store(true);
|
||||
ComponentThread::getSelf()->getIoService()
|
||||
.post([]{});
|
||||
});
|
||||
for (;;)
|
||||
{
|
||||
/* We, the thread actually executing this sequence
|
||||
* here, may not actually be the thread that owns
|
||||
* ct, in which case, ct's owner will dequeue the stuff
|
||||
* that async_call() sent it to do, then conclude its
|
||||
* processing. It may or may not send a message back
|
||||
* to the thread executing this sync sequence here.
|
||||
*
|
||||
* If we are the same thread as ct, then wonderful:
|
||||
* we will dequeue the messsage ourself and process
|
||||
* it, then conclude the processing. We may or may
|
||||
* not send a message back to ourself at the end of
|
||||
* the processing but it doesn't matter since we'll
|
||||
* have to check the condvar in the loop post-
|
||||
* conditions before the next iteration.
|
||||
*
|
||||
* The problem is the first case where ct is a foreign
|
||||
* thread which may not send us a wakeup message
|
||||
* when condvar has been modified, and we may
|
||||
* hypothetically never get another signal from any
|
||||
* other thread.
|
||||
* This can be solved by just ensuring that this thread
|
||||
* always gives a callback to async_call() which first
|
||||
* modifies condvar, and then sends an empty message
|
||||
* to itself.
|
||||
*/
|
||||
ct.run_one();
|
||||
if (continuationCondition.load()==true
|
||||
||!ct.isRunnable())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* We've now bridged the async calls into this
|
||||
* sync function's body without losing any
|
||||
* event responsiveness for the main loop.
|
||||
*/
|
||||
// Continue executing normally...
|
||||
}
|
||||
```
|
||||
|
||||
This should be a complete solution for async bridging.
|
||||
|
||||
Now let's try it first in the sendHandshake sequence. We'll try to use non-blocking socket api calls to send the heartbeat and wait for a response asynchronously. Don't use boost:asio:socket functions because they cause a segfault due to a bug (see /CMakelists.txt). To wait for events on the socket, setup boost to wait on the socket or bind FD.
|
||||
|
||||
Async_call is OBVIOUSLY NOT a function you're expected to implement. It's merely a placeholder for any async sequence we call inside of the sync function.
|
||||
|
||||
You're expected to get rid of the io_context auto-scope object inside of executeHandshake, and use the stored Device.componentThread's io_context instead. You are expected to refrain from associating Device.componentThread.io_service with any boost::asio::socket stuff. You will have to write manual posix socket api code, and use a boost::file_descriptor to catch wakeup events on the socket from the UDP events like recv-data-ready.
|
||||
|
||||
You're basically going to break up executeHandshake into a series of lambdas, but use the pattern I described above to keep it as one synchronous function by bridging it. executeHandshake is the synchronous function that must remain synchronous to its caller. Internally, you must split up executeHandshake into several lambdas, and then at the end you set the condvar and post a message back to CompnentThread::getSelf()->io_service. Then, since executeHandshake() has a bridging sequence that loops and calls run_one() until the condvar is set, executeHandshake will resume executing after that loop exits.
|
||||
|
||||
Makes sense?
|
||||
@@ -1,282 +0,0 @@
|
||||
# Adaptive Resource Acquisition with Re-queuing
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes a novel synchronization pattern that combines the benefits of spinlocks, mutexes, and queuing systems while avoiding their respective drawbacks. The pattern is designed for high-throughput async systems where multiple threads need to coordinate access to shared resources without blocking or wasting CPU cycles.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Traditional synchronization mechanisms have significant trade-offs that limit system performance:
|
||||
|
||||
- **Mutexes**: Block threads, causing context switches and reduced throughput
|
||||
- **Spinlocks**: Waste CPU cycles while waiting, preventing other work from proceeding
|
||||
- **Pure Queuing**: Serializes all operations, reducing parallelism unnecessarily
|
||||
|
||||
The challenge is to maintain data consistency across multi-segment async operations while maximizing system throughput. In high-performance systems, the overhead of context switching can be substantial, and CPU cycles are precious resources that should not be wasted on busy-waiting.
|
||||
|
||||
## Core Concept
|
||||
|
||||
The Adaptive Resource Acquisition pattern uses **atomic flags on shared objects** combined with **immediate re-queuing** to achieve optimal performance characteristics:
|
||||
|
||||
1. **No thread blocking** - Threads never sleep or context switch, maintaining maximum responsiveness
|
||||
2. **No CPU waste** - No busy-waiting when other work could proceed, ensuring efficient resource utilization
|
||||
3. **Maximum throughput** - Threads always process available work, maximizing system productivity
|
||||
4. **Data consistency** - Atomic resource acquisition preserves integrity without traditional locking overhead
|
||||
|
||||
This approach fundamentally changes how we think about resource coordination, treating it as a flow management problem rather than a blocking synchronization problem.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Resource Objects
|
||||
|
||||
Each shared object that requires synchronization carries an atomic flag that indicates its availability. This flag serves as the primary coordination mechanism, allowing threads to atomically claim ownership without the overhead of traditional locks.
|
||||
|
||||
The resource object structure is intentionally simple, containing only the essential coordination mechanism and the resource-specific data. This minimalism reduces memory overhead and improves cache locality.
|
||||
|
||||
### Request Structure
|
||||
|
||||
Async operations are encapsulated as requests that specify their resource requirements and the operation to be performed. This encapsulation allows the system to reason about resource dependencies before attempting execution, enabling intelligent scheduling decisions.
|
||||
|
||||
The request structure includes metadata such as priority levels, which can be used for advanced scheduling policies. This flexibility allows the system to adapt to different workload characteristics and business requirements.
|
||||
|
||||
### Resource Manager
|
||||
|
||||
The core component orchestrates resource acquisition and request processing through a sophisticated coordination mechanism. It maintains a registry of all available resources and manages the flow of requests through the system.
|
||||
|
||||
The resource manager operates on a simple principle: attempt to acquire all required resources atomically, and if successful, execute the operation immediately. If any resource is unavailable, the request is immediately re-queued for later processing without any blocking or waiting.
|
||||
|
||||
## Algorithm
|
||||
|
||||
### Resource Acquisition Process
|
||||
|
||||
The resource acquisition process follows a simple but effective strategy. For each request, the system attempts to atomically acquire all required resources in a single pass. This atomicity is crucial for maintaining data consistency and preventing race conditions.
|
||||
|
||||
If all resources can be acquired atomically, the operation proceeds immediately. This represents the optimal case where no coordination overhead is incurred beyond the atomic operations themselves. The system achieves maximum throughput in this scenario.
|
||||
|
||||
If any resource cannot be acquired, the system immediately releases any resources that were successfully acquired and re-queues the request. This approach ensures that resources are never held unnecessarily and that the system can continue processing other requests without delay.
|
||||
|
||||
The key insight is that failed acquisition attempts are not failures in the traditional sense, but rather normal flow control mechanisms. The system treats resource contention as a scheduling opportunity rather than a blocking condition.
|
||||
|
||||
#### Atomic Resource Acquisition Pseudocode
|
||||
|
||||
```
|
||||
TRY_ACQUIRE_RESOURCES(resource_names):
|
||||
acquired_resources = []
|
||||
|
||||
FOR EACH resource_name IN resource_names:
|
||||
resource = GET_RESOURCE(resource_name)
|
||||
expected_value = false
|
||||
desired_value = true
|
||||
|
||||
// Atomic compare-and-swap operation
|
||||
IF ATOMIC_COMPARE_EXCHANGE_STRONG(resource.flag, expected_value, desired_value):
|
||||
// Successfully acquired this resource
|
||||
acquired_resources.ADD(resource)
|
||||
ELSE:
|
||||
// Failed to acquire this resource
|
||||
// Release all previously acquired resources
|
||||
FOR EACH acquired_resource IN acquired_resources:
|
||||
ATOMIC_STORE(acquired_resource.flag, false)
|
||||
RETURN false
|
||||
|
||||
// Successfully acquired all resources
|
||||
RETURN true
|
||||
```
|
||||
|
||||
### Request Processing Workflow
|
||||
|
||||
The request processing workflow is designed for maximum efficiency. Each request is processed exactly once per cycle, either by successful execution or by re-queuing for later processing.
|
||||
|
||||
When a request is successfully processed, the system immediately releases all acquired resources, making them available for other requests. This rapid resource turnover maximizes system throughput and minimizes resource contention.
|
||||
|
||||
The re-queuing mechanism ensures that no request is lost, while the immediate nature of the re-queuing prevents any blocking or waiting. Requests that cannot be processed immediately simply wait their turn in the queue, allowing other requests to proceed without interference.
|
||||
|
||||
#### Basic Processing Algorithm
|
||||
|
||||
```
|
||||
PROCESS_REQUEST(request):
|
||||
// Step 1: Dequeue the request
|
||||
request = DEQUEUE_FROM_QUEUE()
|
||||
|
||||
// Step 2: Attempt atomic resource acquisition
|
||||
resources_acquired = []
|
||||
acquisition_successful = true
|
||||
|
||||
FOR EACH resource_name IN request.required_resources:
|
||||
resource = GET_RESOURCE(resource_name)
|
||||
IF ATOMIC_COMPARE_EXCHANGE(resource.flag, false, true):
|
||||
resources_acquired.ADD(resource)
|
||||
ELSE:
|
||||
acquisition_successful = false
|
||||
BREAK
|
||||
|
||||
// Step 3: Handle acquisition result
|
||||
IF acquisition_successful:
|
||||
// Execute the operation
|
||||
EXECUTE_OPERATION(request.operation)
|
||||
|
||||
// Release all acquired resources
|
||||
FOR EACH resource IN resources_acquired:
|
||||
ATOMIC_STORE(resource.flag, false)
|
||||
ELSE:
|
||||
// Release any partially acquired resources
|
||||
FOR EACH resource IN resources_acquired:
|
||||
ATOMIC_STORE(resource.flag, false)
|
||||
|
||||
// Re-queue the request for later processing
|
||||
ENQUEUE_REQUEST(request)
|
||||
```
|
||||
|
||||
### Event Loop Management
|
||||
|
||||
The event loop continuously processes requests from the queue until no more requests are available. This simple loop structure ensures that the system is always making progress on available work.
|
||||
|
||||
The loop processes requests in the order they were queued, providing a natural fairness mechanism. However, the system can be extended with priority queuing or other scheduling policies to meet specific requirements.
|
||||
|
||||
The event loop is designed to be efficient and non-blocking, ensuring that the system remains responsive even under high load conditions.
|
||||
|
||||
#### Main Event Loop Pseudocode
|
||||
|
||||
```
|
||||
MAIN_EVENT_LOOP():
|
||||
WHILE true:
|
||||
// Check if there are requests to process
|
||||
IF QUEUE_IS_EMPTY():
|
||||
BREAK
|
||||
|
||||
// Dequeue the next request
|
||||
request = DEQUEUE_FROM_QUEUE()
|
||||
|
||||
// Process the request (this includes re-queuing if needed)
|
||||
PROCESS_REQUEST(request)
|
||||
|
||||
// Continue with next request
|
||||
CONTINUE
|
||||
```
|
||||
|
||||
#### Multi-threaded Worker Loop
|
||||
|
||||
```
|
||||
WORKER_THREAD():
|
||||
WHILE true:
|
||||
// Wait for work to become available
|
||||
request = WAIT_FOR_REQUEST()
|
||||
|
||||
// Process the request
|
||||
PROCESS_REQUEST(request)
|
||||
|
||||
// Return to waiting state
|
||||
CONTINUE
|
||||
```
|
||||
|
||||
## Multi-Threaded Implementation
|
||||
|
||||
### Thread-Safe Coordination
|
||||
|
||||
In a multi-threaded environment, the resource manager must coordinate access to its internal data structures while maintaining the non-blocking characteristics of the pattern. This coordination is achieved through careful use of atomic operations and minimal locking.
|
||||
|
||||
The queue management uses traditional mutex-based synchronization, but only for the queue operations themselves. The critical resource acquisition path remains lock-free, ensuring that the performance benefits of the pattern are preserved.
|
||||
|
||||
Worker threads continuously process requests from the shared queue, attempting to acquire resources and execute operations. The coordination between threads is handled implicitly through the atomic resource flags, eliminating the need for explicit thread synchronization in the critical path.
|
||||
|
||||
### Worker Thread Behavior
|
||||
|
||||
Worker threads operate in a continuous loop, processing requests as they become available. Each thread independently attempts to acquire resources and execute operations, creating natural parallelism without explicit coordination.
|
||||
|
||||
The worker threads are designed to be lightweight and efficient, with minimal overhead beyond the actual resource acquisition and operation execution. This design allows the system to scale effectively with the number of available CPU cores.
|
||||
|
||||
The thread coordination is handled through the shared queue and atomic resource flags, creating a self-balancing system that naturally distributes work across available threads.
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Device Management Systems
|
||||
|
||||
In device management systems, multiple operations may need to coordinate access to physical or logical devices. The adaptive resource acquisition pattern provides an elegant solution for managing these complex coordination requirements.
|
||||
|
||||
For example, when attaching a device, the system may need to coordinate access to the device itself, the device registry, and various system resources. The pattern allows these operations to proceed atomically when resources are available, while gracefully handling contention through re-queuing.
|
||||
|
||||
The device management system can handle complex multi-step operations that require coordination across multiple resources, all while maintaining high throughput and responsiveness.
|
||||
|
||||
### Database Connection Pools
|
||||
|
||||
Database connection pools are a natural fit for the adaptive resource acquisition pattern. Each database operation requires access to a connection from the pool, and the pattern provides efficient coordination without the overhead of traditional locking.
|
||||
|
||||
The pattern allows the system to process multiple database operations concurrently when connections are available, while gracefully handling periods of high contention. The re-queuing mechanism ensures that no operations are lost, even during peak load periods.
|
||||
|
||||
The connection pool can implement sophisticated scheduling policies, such as priority queuing for different types of operations, while maintaining the performance benefits of the pattern.
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
### Throughput Analysis
|
||||
|
||||
The performance characteristics of the adaptive resource acquisition pattern are determined by the resource contention patterns in the system. In the best case, when resources are readily available, the system achieves maximum throughput with minimal overhead.
|
||||
|
||||
In the worst case, when resources are heavily contended, the system gracefully degrades to a queuing behavior, ensuring that all operations eventually complete. The system maintains fairness and prevents starvation through the natural ordering of the queue.
|
||||
|
||||
The average case performance represents the typical operating conditions, where the system achieves optimal parallelism while handling occasional resource contention through re-queuing.
|
||||
|
||||
### Comparison with Traditional Methods
|
||||
|
||||
The adaptive resource acquisition pattern provides a unique combination of performance characteristics that are not achievable with traditional synchronization mechanisms:
|
||||
|
||||
- **Mutexes** provide data consistency but at the cost of thread blocking and context switching overhead
|
||||
- **Spinlocks** avoid context switching but waste CPU cycles during contention
|
||||
- **Pure queuing** avoids both blocking and CPU waste but serializes operations unnecessarily
|
||||
|
||||
The adaptive pattern combines the best aspects of these approaches while avoiding their drawbacks, creating a solution that is both efficient and practical.
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Priority Queuing
|
||||
|
||||
The system can be extended with priority queuing to handle different types of operations with varying importance. High-priority operations can be processed before lower-priority operations, ensuring that critical operations receive timely attention.
|
||||
|
||||
The priority queuing mechanism integrates seamlessly with the existing re-queuing behavior, allowing the system to maintain its performance characteristics while providing sophisticated scheduling capabilities.
|
||||
|
||||
### Resource Groups
|
||||
|
||||
Complex operations may require coordination across multiple related resources. Resource groups allow the system to treat related resources as a single unit for acquisition purposes, simplifying the coordination logic for complex operations.
|
||||
|
||||
Resource groups can be used to implement sophisticated resource management policies, such as ensuring that related resources are always acquired together or implementing resource reservation mechanisms.
|
||||
|
||||
### Fairness Mechanisms
|
||||
|
||||
The system can implement various fairness mechanisms to ensure that all requests receive fair treatment over time. Round-robin processing, aging mechanisms, and other fairness policies can be implemented while maintaining the performance benefits of the pattern.
|
||||
|
||||
Fairness mechanisms are particularly important in systems where different types of operations have different resource requirements, ensuring that no operation type dominates the system resources.
|
||||
|
||||
## Implementation Considerations
|
||||
|
||||
### Memory Management
|
||||
|
||||
The pattern requires careful attention to memory management, particularly for the request objects and resource metadata. Smart pointers and object pooling can be used to minimize memory allocation overhead and improve performance.
|
||||
|
||||
The system should implement proper cleanup mechanisms for failed operations and ensure that resources are always released, even in error conditions.
|
||||
|
||||
### Error Handling
|
||||
|
||||
Robust error handling is essential for maintaining system reliability. The system should gracefully handle operation failures, resource unavailability, and other error conditions without affecting the overall system performance.
|
||||
|
||||
Retry mechanisms with exponential backoff can be implemented for transient failures, while deadlock detection and resolution mechanisms can handle more complex failure scenarios.
|
||||
|
||||
### Monitoring and Debugging
|
||||
|
||||
The system should provide comprehensive monitoring capabilities to track performance metrics, resource utilization, and queue behavior. These metrics are essential for tuning the system and identifying performance bottlenecks.
|
||||
|
||||
Debugging support should include detailed logging of resource acquisition attempts, queue operations, and operation execution, allowing developers to understand and optimize system behavior.
|
||||
|
||||
## Conclusion
|
||||
|
||||
The Adaptive Resource Acquisition pattern provides a novel solution to the classic synchronization dilemma. By combining atomic operations with intelligent re-queuing, it achieves maximum throughput while maintaining data consistency and avoiding the overhead of traditional synchronization mechanisms.
|
||||
|
||||
This pattern is particularly well-suited for high-performance async systems where traditional synchronization mechanisms would create unacceptable overhead. The pattern's simplicity and effectiveness make it a valuable addition to the toolkit of concurrent programming patterns.
|
||||
|
||||
The pattern represents a fundamental shift in how we think about resource coordination, treating it as a flow management problem rather than a blocking synchronization problem. This shift enables new levels of performance and scalability in concurrent systems.
|
||||
|
||||
The adaptive resource acquisition pattern is particularly valuable in:
|
||||
- High-performance async systems where throughput is critical
|
||||
- Resource-constrained environments where CPU cycles are precious
|
||||
- Systems requiring predictable latency and responsiveness
|
||||
- Multi-threaded applications with complex shared state requirements
|
||||
|
||||
By providing a practical solution to the synchronization dilemma, this pattern enables developers to build high-performance concurrent systems without sacrificing simplicity or reliability.
|
||||
@@ -0,0 +1,603 @@
|
||||
I just realized that my spinqueueing mechanism is highly power inefficient if a
|
||||
lock needs to be held across a "true async wait"—where the async sequence
|
||||
actually waits on a hardware bottleneck. In this case, the thread acquires the
|
||||
spinlock, then goes to sleep in the kernel schedq until some hardware event
|
||||
occurs, and is then awakened—all while still holding the spinlock.
|
||||
|
||||
Meanwhile, other sequences running on other threads and contending for that lock
|
||||
will be Qspinning. This is acceptable if all I care about is maximum throughput:
|
||||
the Qspinning just re-posts the sequences back into the Q, and eventually
|
||||
they'll acquire the LockSet and proceed.
|
||||
|
||||
Importantly, since the thread itself isn't slept in the kschedQ, it will be
|
||||
deQing and processing other sequences that aren't bottlenecked on the lock held
|
||||
by the sequence waiting for the hardware response. Throughput is indeed
|
||||
maximized.
|
||||
|
||||
However, I just realized that if kernel mutexes expose FD events, I can apply
|
||||
this same logic to sleeplocks: I can wait on the sleeplocks asynchronously
|
||||
instead of synchronously. If I can make my asio::io_service wait on all the
|
||||
mutex FDs requested by sequences on the current thread, then in theory I can put
|
||||
the thread to sleep and know that when the mutex becomes available, I'll be
|
||||
awakened again.
|
||||
|
||||
Hence, I can get the best of both worlds: maximum throughput and power saving.
|
||||
Instead of spinqueueing, we just add the lock FDs to an FD set to be waited on
|
||||
by asio. If any of those locks become available, the kernel scheduler will
|
||||
awaken our asioQ thread, and we can then awaken and retry the lock.
|
||||
|
||||
## Boost asio queue-based sleep locking:
|
||||
|
||||
Instead of using FDs, we can also try to use a fifo Q based mechanism: each lock
|
||||
is a spinlock and a fifo queue.
|
||||
|
||||
Acquire:
|
||||
```
|
||||
lock(spinlock);
|
||||
q.push_back(self);
|
||||
head = q.peek_front();
|
||||
if (head == self) {
|
||||
// We acquired the lock.
|
||||
unlock(spinlock);
|
||||
return;
|
||||
}
|
||||
|
||||
unlock(spinlock);
|
||||
```
|
||||
|
||||
release:
|
||||
```
|
||||
lock(spinlock);
|
||||
// Should get back ourself.
|
||||
q.pop_front();
|
||||
// Wake up the next request in the q.
|
||||
head = q.peek_front();
|
||||
if (head == NULL) {
|
||||
// Nobody was waiting.
|
||||
unlock(spinlock);
|
||||
return;
|
||||
}
|
||||
|
||||
head.thread_to_wake.getIoService().post([]{
|
||||
// This lambda causes thread_to_wake to check this lock's
|
||||
// Q and then proceed to execute since it now owns the lock.
|
||||
});
|
||||
unlock(spinlock);
|
||||
```
|
||||
|
||||
Something like this: it causes the entire thing to be, at least ostensibly,
|
||||
in userspace -- though idk how Boost handles its queues internally.
|
||||
|
||||
## Priortizing LockSets:
|
||||
|
||||
One problem we have with a FIFO-based sleeping system is that it makes it very
|
||||
unlikely that LockSets will ever acquire all of their locks, if there are
|
||||
contenders for those same locks who only need to acquire one of the locks in
|
||||
that LockSet.
|
||||
|
||||
We could theoretically give locksets an advantage by not making them backout
|
||||
if they fail to acquire all locks in their set. I.e: if they get 2/3, then they
|
||||
hold those 2 and then wait for the 3rd. This is problematic because it leaves
|
||||
room open for deadlocks in the form of T1 and T2 needing both LockA and LockB,
|
||||
but they acquire them in reverse order. I.e: T1 takes LockA and now waits for
|
||||
LockB; and T2 takes LockB and now waits for LockA. This will now happen among
|
||||
the LockSets if we don't impose backing out. It may be possible to avoid this
|
||||
using very careful lock ordering and dependency analysis but this project is
|
||||
asynchronous the locking is done in the async sequences and not in the sync
|
||||
accessor functions. So this kind of analysis is almost impossible to do.
|
||||
|
||||
|
||||
We need to think of a way to make the FIFOs biased toward LockSets so that they
|
||||
have an advantage over single-lock acquirers. Or else LockSet sequences will be
|
||||
starved.
|
||||
|
||||
### Timed backoff:
|
||||
|
||||
We could have Locksets be greedy and try to hold on to the locks they've
|
||||
acquired (say, 2/3 and then wait for the 3rd) but then be forced to backoff
|
||||
after a timeout.
|
||||
|
||||
This introduces async event complexity and also the timeout we choose is almost
|
||||
guaranteed to be arbitrary.
|
||||
|
||||
### Fractionally inserted FIFOs:
|
||||
|
||||
We insert sequences with a LockSet.size() of 1, at the back.
|
||||
We insert all other sequences (>1) into first 1/LockSet.size()th position in the
|
||||
Queue.
|
||||
So a Lockset of size 2 will be inserted at the end of the first half of the
|
||||
items in the queue.
|
||||
A Lockset of size 3 will be inserted at the end of the first 33% of items.
|
||||
A lockset of size 4 will be inserted at the end of the first 25% of items.
|
||||
And so on.
|
||||
|
||||
This ensures that higher LockSet.size()s will be prioritized ever higher, and
|
||||
at the same time they don't completely hog everything. Those single-lock
|
||||
sequences that have already naturally progressed past the fraction-mark of a
|
||||
given LockSet size will continue making progress toward the front.
|
||||
|
||||
For queueing sequences with Locksets>1, we can enQ them on the FIFO of the first
|
||||
lock in their set. They'll back off each time anyway, so they'll always be
|
||||
re-trying from the first lock in their set each time.
|
||||
|
||||
#### Impl details:
|
||||
|
||||
We'd like to use std::unordered_set because insertion will require lots of
|
||||
moving items around, but we'll have to use std::vector because we need direct
|
||||
access to insert at arbitrary fractional indexes. It's unlikely the number of
|
||||
items in any lock's Q will ever be large enough to require lots of displacement,
|
||||
but welp there's no reason not to plan for scaling. Although if we end up
|
||||
needing scaling that's a symptom of a bigger problem...with scaling itself lol.
|
||||
There shouldn't be enough items blocked on a lock that we have to design the
|
||||
lock's queue to be scalable.
|
||||
|
||||
### Inverted Fractionally acquired locksets:
|
||||
|
||||
The previous ideas of fractionally inserted lockQs was okay, but the acquisition
|
||||
algo required that the async seq be at the front of a locks queue to
|
||||
successfully acquire that lock. That makes it almost impossible for Locksets>1
|
||||
to ever acquire all of their locks. If we add backoff to that, it basically
|
||||
means no lockset will ever acquire all of its locks.
|
||||
|
||||
Instead what we now do is always insert at the rear (push_back()) and then when
|
||||
acquiring, we check to see if the sequence is in the first
|
||||
1/(1/(LockSet.size())), and if so, it successfully acquires the lock. I.e: if
|
||||
the sequence item isn't in the LAST 1/(LockSet.size()) items, then it succeeds.
|
||||
* For a lockset of size=1: It must be at the front of the queue.
|
||||
* Lockset.size=2: it must be in the first 50% of items.
|
||||
* Lockset.size=3: it must be in the first 66% of items.
|
||||
* Lockset.size=4: It must be in the first 75% of items.
|
||||
|
||||
So this way larger LockSets are favoured, but 1-size locksets make progress.
|
||||
|
||||
For performance:
|
||||
* We obv can just scan the smaller tail percentage for the item instead of
|
||||
scanning the larger front percentage.
|
||||
* If we use a doubly-linked list, we can prolly keep the insertion iterator
|
||||
and this way we won't have to actually find the item in the lockQ when we wish
|
||||
to eventually remove it from the lockQ when releasing the lock.
|
||||
|
||||
## Total overall design:
|
||||
|
||||
### Asio queues and Lockvokers:
|
||||
|
||||
Lockvokers are initially enqueued on a CompThread's queue. When the lockvoker
|
||||
first runs, it checks a flag to see if it has been "registered" into the queues
|
||||
for all locks in its set. If not, then it "registers" itself in each lock's
|
||||
ticketQ and then attempts to acquire each lock. Registration and acquisition
|
||||
are logically separate operations; and locks will often attempt acquisition
|
||||
many times after first registering, without needing to register again. Ideally
|
||||
we can implement a LockSet::registerAndTryAcquireAll() method, but that's for
|
||||
us to think about later.
|
||||
|
||||
```
|
||||
/* We'll need to rename current class LockSpec to LockSet. */
|
||||
class LockSet
|
||||
{
|
||||
/* Add this either inside of LockSet or outside of it -- depends on whether
|
||||
* it's we can get it to compile because I'm seeing some potential circular
|
||||
* definition dependencies.
|
||||
*/
|
||||
typedef std::pair<Qutex, LockerAndInvokerList::iterator>
|
||||
LockUsageDesc;
|
||||
|
||||
/* Find a LockUsageDesc -- useful below */
|
||||
LockUsageDesc &getLockUsageDesc(Qutex &criterionLock)
|
||||
{
|
||||
for (auto &reqLock: requiredLocks) {
|
||||
if (reqLock.first == &criterionLock) { return reqLock; }
|
||||
}
|
||||
|
||||
// Should never happen.
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
LockSet::register(LockerAndInvoker &lockvoker)
|
||||
{
|
||||
for (auto &lock: lockset.locks) {
|
||||
// Register the Lockvoker object in each lock's ticketQ.
|
||||
lock.second = lock.first.register(lockvoker);
|
||||
}
|
||||
registered = true;
|
||||
}
|
||||
|
||||
bool LockSet::tryAcquire(LockerAndInvoker &lockvoker)
|
||||
{
|
||||
if (!registered) {
|
||||
// Should never happen.
|
||||
throw ...;
|
||||
}
|
||||
int nLocksAcquired=0,
|
||||
nLocksInSet = lockset.size();
|
||||
for (auto &lock: lockset.locks) {
|
||||
if (!lock.first.tryAcquire(nLocksInSet)) {
|
||||
break;
|
||||
}
|
||||
|
||||
nLocksAcquired++;
|
||||
}
|
||||
|
||||
if (nLocksAcquired == nLocksInSet) {
|
||||
// Success
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i=0; i<nLocksAcquired; i++) {
|
||||
// Backoff does different stuff from release();
|
||||
locks[i].first.backoff(lockvoker);
|
||||
}
|
||||
}
|
||||
|
||||
LockSet::release()
|
||||
{
|
||||
for (auto &lock: requiredLocks) {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, the Qutex class is what we'll use for synchronization. It's just a
|
||||
combination of a SpinLock, a sh_ptr<LockerAndInvoker> and a std::list.
|
||||
|
||||
```
|
||||
class SpinLock
|
||||
{
|
||||
/* Modify to add methods acquire() and release() which busy-wait.
|
||||
*/
|
||||
void acquire();
|
||||
void release();
|
||||
};
|
||||
|
||||
class LockSet
|
||||
{
|
||||
/* Modify the std::vector of SpinLock to instead be:
|
||||
* std::vector<LockUsageDesc> locks;
|
||||
*/
|
||||
std::vector<LockUsageDesc> locks;
|
||||
}
|
||||
|
||||
bool LockerAndInvoker::operator==(const LockerAndInvoker &other)
|
||||
{
|
||||
/* Compare by the address of the continuation objects. Why?
|
||||
* Because there's no guarantee that the lockvoker object that was
|
||||
* passed in by the io_service invocation is the same object as that
|
||||
* which is in the qutexQs. Especially because we make_shared() a
|
||||
* copy when registerInQutexQueues()ing.
|
||||
*
|
||||
* Generally when we "wake" a lockvoker by enqueuing it, boost's
|
||||
* io_service::post will copy the lockvoker object.
|
||||
*/
|
||||
return &this->serializedContinuation == &other.serializedContinuation;
|
||||
}
|
||||
|
||||
bool LockerAndInvoker::operator !=(const LockerAndInvoker &other)
|
||||
{
|
||||
return &this->serializedContinuation != &other.serializedContinuation;
|
||||
}
|
||||
|
||||
class Qutex
|
||||
{
|
||||
public:
|
||||
typedef std::list<LockerAndInvoker> LockerAndInvokerList;
|
||||
|
||||
LockerAndInvokerList::iterator register(const LockerAndInvoker &lockvoker)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Just insert the lockvoker into the rear of the list.
|
||||
*
|
||||
* Then, since we want to store the
|
||||
*/
|
||||
LockerAndInvokerList::iterator it;
|
||||
|
||||
lock.acquire();
|
||||
queue.push_back(lockvoker);
|
||||
it = queue.end();
|
||||
--it;
|
||||
lock.release();
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
void unregister(LockerAndInvokerList::iterator it, bool shouldLock=1)
|
||||
{
|
||||
if (shouldLock)
|
||||
{
|
||||
lock.acquire();
|
||||
queue.erase(it);
|
||||
lock.release();
|
||||
}
|
||||
else{
|
||||
queue.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool tryAcquire(LockerAndInvoker &tryingLockvoker)
|
||||
{
|
||||
const nRequiredLocks = tryingLockvoker.serializedContinuation
|
||||
.requiredLocks.size();
|
||||
|
||||
lock.acquire();
|
||||
|
||||
const qNItems = queue.size();
|
||||
|
||||
if (qNItems < 1) {
|
||||
lock.release();
|
||||
|
||||
/** EXPLANATION:
|
||||
* requiredLocks before ever trying to tryAcquire() them, so if
|
||||
* tryAcquire is being called, that must mean that queue.size() > 0.
|
||||
*
|
||||
* Ergo this should never happen.
|
||||
*/
|
||||
throw;
|
||||
}
|
||||
|
||||
if (!!currentOwner) {
|
||||
lock.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
/** EXPLANATION:
|
||||
* From here:
|
||||
* if qNItems == 1 the we are the only one in the ticketQ and we have
|
||||
* successfully acquired the lock.
|
||||
* If qNitems / nRequiredLocks == 0, then we acquire by default since
|
||||
* the number of items in the ticketQ guarantees that we are in the top
|
||||
* X% for that nRequiredLocks.
|
||||
* If qNItems / nRequiredLocks >= 1, then we must do the normal algo:
|
||||
* Check the last (qNItems/nRequiredLocks) items, and if the item isn't
|
||||
* in those items, then it must be in the earlier ones (obviously).
|
||||
* Hence this Lockvoker acquisition should be considered successful.
|
||||
*
|
||||
* EXPLANATION 2:
|
||||
* You'll notice that we don't do actual percentages but rather we just
|
||||
* do discrete fractions -- this makes the algo more deterministic
|
||||
* and much easier to reason about. I.e:
|
||||
* If nRequiredLocks is 6 and qNItems==3:
|
||||
* we don't actually calculate that the Lockvoker item must be in
|
||||
* the top (100-17%), and then try to calculate whether we ought to
|
||||
* consider the 3rd item to be in the last 17-percentile. We just
|
||||
* do a fractional count and assume complete discreteness.
|
||||
*/
|
||||
const int nRearItemsToScan = qNItems / nRequiredLocks;
|
||||
|
||||
if (qNItems == 1 || nRearItemsToScan < 1) {
|
||||
currOwner = tryingLockvoker;
|
||||
lock.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** EXPLANATION:
|
||||
* For lockvokers that only have 1 requiredLock, they must be at the
|
||||
* front of the queue to successfully acquire.
|
||||
*/
|
||||
if (nRequiredLocks == 1)
|
||||
{
|
||||
bool ret=false;
|
||||
|
||||
if (tryingLockvoker == &queue.front())
|
||||
{
|
||||
currOwner = tryingLockvoker;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ret = false;
|
||||
lock.release();
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto rIt = queue.rbegin();
|
||||
auto rEndIt = queue.rend();
|
||||
bool foundInRear = false;
|
||||
for (int i=0; i<nRearItemsToScan && rIt != rEndIt; rIt++, i++)
|
||||
{
|
||||
if (*rIt != tryingLockvoker) { continue; }
|
||||
|
||||
foundInRear = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundInRear) {
|
||||
lock.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Not found in rear: this means the item is in the top X%. That means
|
||||
* it should be allowed to claim the lock.
|
||||
*/
|
||||
currOwner = tryingLockvoker;
|
||||
lock.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
backoff(LockerAndInvoker &failedAcquirer)
|
||||
{
|
||||
lock.acquire();
|
||||
|
||||
const int nQItems = queue.size();
|
||||
// Rotate queue members if failedAcquirer is at front of queue.
|
||||
LockerAndInvoker &currFront = queue.front();
|
||||
if (currFront == failedAcquirer && nQItems > 1)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Rotate the top LockSet.size() items in the queue by moving
|
||||
* the failedAcquirer to the last position in the top
|
||||
* LockSet.size() items within the queue.
|
||||
*
|
||||
* I.e: if queue.size()==20, and lockSet.size()==5, then move
|
||||
* failedAcquirer from the front the 5th position in the queue,
|
||||
* which should push the other 4 items forward.
|
||||
* If queue.size==3 and LockSet.size()==5, then just
|
||||
* push_back(failedAcquirer).
|
||||
*
|
||||
* It is impossible for a Qutex queue to have only one
|
||||
* item in it, yet for that Lockvoker item to have failed to
|
||||
* acquire the Qutex. Being the only item in the ticketQ
|
||||
* means that you must succeed at acquiring the Qutex.
|
||||
*/
|
||||
int indexOfItemToInsertCurrFrontBehind = min(
|
||||
nQItems - 1,
|
||||
failedAcquirer.serializedContinuation.requiredLocks.size() - 1);
|
||||
|
||||
/* EXPLANATION:
|
||||
* Rotate them here.
|
||||
*
|
||||
* The reason why we do this rotation is to avoid a particular kind
|
||||
* of deadlock wherein a grid of async requests is perfectly
|
||||
* configured so as to guarantee that none of them can make any
|
||||
* forward progress unless they get reordered.
|
||||
*
|
||||
* Consider 2 different locks with 2 different items in them
|
||||
* each, both of which come from 2 particular requests:
|
||||
* Qutex1: Lockvoker1, Lv2
|
||||
* Qutex2: Lv2, Lv1
|
||||
*
|
||||
* Moreover, both of these lockvokers have requiredLocks.size()==2,
|
||||
* and the particular 2 locks that each one requires are indeed
|
||||
* Qutex1 and Qutex2.
|
||||
*
|
||||
* This particular setup basically means that in TL1's queue, Lv1
|
||||
* will wakeup since it's at the front of TL1. It'll successfully
|
||||
* acquire TL1 (since it's at the front), and then it'll try to
|
||||
* acquire TL2. But since Lv1 isn't in the top 50% of items in TL2's
|
||||
* queue, Lv1 will fail to acquire TL2.
|
||||
*
|
||||
* Then similarly, in TL2's queue, Lv2 will wakeup since it's at
|
||||
* the front. Again, it'll successfully acquire TL2 since it's at
|
||||
* the front of TL2's queue. But then it'll try to acquire TL1.
|
||||
* Since it's not in the top 50% of TL1's enqueued items, it'll fail
|
||||
* to acquire TL1.
|
||||
*
|
||||
* N.B: This type of perfectly ordered deadlock can occur in any
|
||||
* kind of NxN situation where ticketQ.size()==requiredLocks.size().
|
||||
* That could be 4x4, 5x5, 6x6, etc. It doesn't happen in 1x1
|
||||
* because a Lockvoker that only requires one lock will always just
|
||||
* succeed if it's at the front of its queue.
|
||||
*
|
||||
* This state of affairs is stable and will persist unless these
|
||||
* queues are reordered in some way. Hence: that's why we rotate the
|
||||
* items in a QutexQ after backing off of it. Backing off means
|
||||
* Not necessarily that the calling LockVoker failed to acquire
|
||||
* THIS PARTICULAR Qutex, but rather than it failed to acquire
|
||||
* ALL of its required locks.
|
||||
*
|
||||
* Hence, if we are backing out, we should also rotate the items
|
||||
* in the queue if the current front item is the failed acquirer.
|
||||
* So that's why we do this rotation here.
|
||||
*/
|
||||
// The first arg (the iterator) is a ref in case it must be updated.
|
||||
rotate(
|
||||
currFront.serializedContinuation.requiredLocks.getLockDesc(
|
||||
*this).second,
|
||||
indexOfItemToInsertCurrFrontBehind);
|
||||
}
|
||||
|
||||
currOwner.release();
|
||||
|
||||
LockerAndInvoker &newFront = queue.front();
|
||||
|
||||
lock.release();
|
||||
|
||||
/** EXPLANATION:
|
||||
* Why should this never happen? Well, if we were at the front of the queue
|
||||
* and we failed to acquire the lock, we should have been rotated away from
|
||||
* the front. On the other hand, if we were not at the front of the queue
|
||||
* and we failed to acquire the lock, then we weren't at the front of the
|
||||
* queue to begin with.
|
||||
* The exception is if the queue has only one item in it.
|
||||
*
|
||||
* Hence there ought to be no way for the failedAcquirer to be at the front
|
||||
* of the queue at this point UNLESS the queue has only one item in it.
|
||||
*/
|
||||
if (newFront == failedAcquirer && nQItems > 1)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
/** EXPLANATION:
|
||||
* We should always awaken whoever is at the front of the queue, even if
|
||||
* we didn't rotate. Why? Consider this scenario:
|
||||
*
|
||||
* Lv1 has LockSet.size==1. Lv2 has LockSet.size==3.
|
||||
* Lv1's required lock overlaps with Lv2's set of 3 required locks.
|
||||
* Lv1 registers itself in its 1 qutex's queue.
|
||||
* Lv2 registers itself in all 3 of its qutexes' queues.
|
||||
* Lv2 acquires the lock that it needs in common with Lv1.
|
||||
* (Assume that Lv2 was not at the front of the common qutex's
|
||||
* internal queue -- it only needed to be in the top 66%.)
|
||||
* Lv1 tries to acquire the common lock and fails. It gets taken off of
|
||||
* its io_service. It's now asleep until it gets
|
||||
* re-added into an io_service.
|
||||
* Lv2 fails to acquire the other 2 locks it needs and backoff()s from
|
||||
* the common lock it shares with Lv1.
|
||||
*
|
||||
* If Lv2 does NOT awaken the item at the front of the common lock's
|
||||
* queue (aka: Lv1), then Lv1 is doomed to never wake up again.
|
||||
*
|
||||
* Hence: backout() callers should always wake up the lockvoker at the
|
||||
* front of their queue before leaving.
|
||||
*
|
||||
* The exception is if the item at the front is the backout() caller
|
||||
* itself. This can happen if, for example a multi-locking lockvoker
|
||||
* is backing off of a qutex within which it's the only waiter.
|
||||
*/
|
||||
if (nQItems > 1) {
|
||||
wakeUp(newFront);
|
||||
}
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
lock.acquire();
|
||||
|
||||
/* Get the saved iterator and use it to unregister.
|
||||
* Don't acquire lock because we already acquired it in this function.
|
||||
*/
|
||||
unregister(currOwner->serializedContinuation.requiredLocks
|
||||
.getLockUsageDesc(*this).second, false);
|
||||
|
||||
currOwner.release();
|
||||
|
||||
/** EXPLANATION:
|
||||
* It would be nice to be able to optimize by only awakening if the
|
||||
* release()ing lockvoker was at the front of the qutexQ, but if we
|
||||
* don't unconditionally wakeup() the front item, we could get lost
|
||||
* wakeups. Consider:
|
||||
*
|
||||
* Lv1 only has 1 requiredLock.
|
||||
* Lv2 has 3 requiredLocks. One of its requiredLocks overlaps with
|
||||
* Lv1's single requiredLock. So they both share a common lock.
|
||||
* Lv3's currently owns Lv1 & Lv2's common requiredLock.
|
||||
* Lv3 release()s that common lock.
|
||||
* Lv1 happens to be next in queue after Lv3 unregisters itself.
|
||||
* Lv3 wakes up Lv1.
|
||||
* Just before Lv1 can acquire the common lock, Lv2 acquires it now,
|
||||
* because it only needs to be in the top 66% to succeed.
|
||||
* Lv1 checks the currOwner and sees that it's owned. Lv1 is now
|
||||
* dequeued from its io_service. It won't be awakened until someone
|
||||
* awakens it.
|
||||
* Lv2 finishes its critical section and releas()es the common lock.
|
||||
* Lv2 was not at the front of the qutexQ, so it does NOT awaken the
|
||||
* current item at the front.
|
||||
*
|
||||
* Thus, Lv1 never gets awakened again. The end.
|
||||
* This also means that no LockSet.size()==1 lockvoker will ever be able
|
||||
* to run again since they can only run if they are at the front of the
|
||||
* qutexQ.
|
||||
*
|
||||
* Therefore we must always awaken the front item when releas()ing.
|
||||
*/
|
||||
LockerAndInvoker &front = queue.front();
|
||||
|
||||
lock.release();
|
||||
|
||||
wakeUp(front);
|
||||
}
|
||||
|
||||
public:
|
||||
SpinLock lock;
|
||||
std::shared_ptr<LockerAndInvoker> currOwner;
|
||||
LockerAndInvokerList queue;
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,59 @@
|
||||
# Spinqueueing: A new locking method that only blocks requests and not threads.
|
||||
|
||||
The idea is that instead of using sleeplocks like mutexes, we instead only spin
|
||||
particular request objects by re-posting them to the queue.
|
||||
|
||||
Particular requests may need a given shared resource. Instead of sleeping a
|
||||
whole thread while that particular request waits for the resource, we instead
|
||||
sleep the request itself by re-posting it into the thread's queue. This
|
||||
basically implements a kind of spinlock without busy-waiting. The underlying
|
||||
thread is never blocked unless it has no requests that can make forward
|
||||
progress.
|
||||
|
||||
Forward progress through requests is only halted when an external resource is
|
||||
actually being waited on. Generally this will be an actual hardware event that
|
||||
is being waited on. No software bottlenecks will be slept on.
|
||||
|
||||
All locks in the program are simple spinlocks, but the algorithm to spin on them
|
||||
is:
|
||||
|
||||
## Each async call has a "locker and invoker":
|
||||
|
||||
int funcThatCallsAnAsyncFunc(...)
|
||||
{
|
||||
// Do preparatory stuff ...
|
||||
|
||||
|
||||
// Post the lockvoker to the target thread.
|
||||
targetThread.io_service.post(
|
||||
[targetThread, /* args to asyncOperationReq captured here */]()
|
||||
{
|
||||
int nAcquired;
|
||||
for (nAcquired=0; nAcquired<nLocksRequired; nAcquired++)
|
||||
{
|
||||
if (!requiredLocks->tryAcquire()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nAcquired < nLocksRequired)
|
||||
{
|
||||
for (int i=0; i<nAcquired; i++) {
|
||||
requiredLocks->release();
|
||||
}
|
||||
|
||||
/* Unsure how to recapture the lambda object and re-enqueue it.
|
||||
* Dunno if that's even possible. But this is the essence of the
|
||||
* queue-spin system. We re-enqueue the lockvoker until it
|
||||
* gets all locks required. Then it will invoke the async
|
||||
* frontend.
|
||||
*/
|
||||
targetThead.io_service.post(this?);
|
||||
}
|
||||
|
||||
managerObject.asyncOperationReq(
|
||||
/* args to asyncOperationReq passed here */);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
## Idk how to encapsulate lockvokers into a terse, reusable idiom.
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 710 KiB |
@@ -0,0 +1,214 @@
|
||||
# LivoxGen1Lidar Device Attachment Protocol (DAP) Specification
|
||||
|
||||
## Overview
|
||||
|
||||
The LivoxGen1Lidar DAP specification defines how to attach to Livox Gen1 LiDAR devices and access their various data streams using a unified API with mode-based parameter selection.
|
||||
|
||||
## API Structure
|
||||
|
||||
The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based parameters to specify which data interface to present.
|
||||
|
||||
## Device Attachment Specifications
|
||||
|
||||
### 1. Point Cloud Intensity Data Device (Interoceptor)
|
||||
|
||||
**Purpose**: Provides light intensity/reflectivity data from the LiDAR point cloud.
|
||||
|
||||
**Syntax**:
|
||||
```
|
||||
+idev | avia0 | structural-implexor | livoxGen1(mode=pointCloudIntensity) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Alternative Syntax** (all equivalent):
|
||||
```
|
||||
+idev | avia0 | structural-implexor | livoxGen1(stim=pcloudIntensity) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
+idev | avia0 | structural-implexor | livoxGen1(affordance=pCloudI) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Mode Parameter Values** (synonymous):
|
||||
- `pointCloudIntensity`
|
||||
- `pcloudIntensity`
|
||||
- `pCloudIntensity`
|
||||
- `pCloudI`
|
||||
- `pcloudI`
|
||||
|
||||
### 2. Point Cloud Coordinate Data Device (Extrospector)
|
||||
|
||||
**Purpose**: Provides spatial coordinate data from the LiDAR point cloud.
|
||||
|
||||
**Syntax**:
|
||||
```
|
||||
+edev | avia0 | structural-implexor | livoxGen1(mode=pcloud,format=xyz) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Mode Parameter Values** (synonymous):
|
||||
- `pcloud`
|
||||
- `pCloud`
|
||||
- `pointCloud`
|
||||
|
||||
**Format Parameter** (for point cloud modes):
|
||||
- `xyz`: Standard Cartesian coordinates (X, Y, Z)
|
||||
- `spherical`: Raw spherical coordinates
|
||||
- `spherical-cartesian`: Spherical coordinates converted to Cartesian
|
||||
- `dual-cartesian`: Dual Cartesian coordinate system
|
||||
- `dual-spherical`: Dual spherical coordinate system
|
||||
|
||||
**Alternative Format Parameter Names** (synonymous):
|
||||
- `format` or `fmt`
|
||||
|
||||
### 3. IMU Gyroscope Data Device (Interoceptor)
|
||||
|
||||
**Purpose**: Provides gyroscope data from the LiDAR's internal IMU.
|
||||
|
||||
**Syntax**:
|
||||
```
|
||||
+idev | avia0 | gyro-implexor | livoxGen1(mode=gyro) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Mode Parameter Values**:
|
||||
- `gyro`
|
||||
|
||||
### 4. IMU Accelerometer Data Device (Interoceptor)
|
||||
|
||||
**Purpose**: Provides accelerometer data from the LiDAR's internal IMU.
|
||||
|
||||
**Syntax**:
|
||||
```
|
||||
+idev | avia0 | accel-implexor | livoxGen1(mode=accel) | livoxProto1(handshake-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
|
||||
```
|
||||
|
||||
**Mode Parameter Values**:
|
||||
- `accel`
|
||||
|
||||
## Provider Parameters
|
||||
|
||||
### livoxProto1 Provider
|
||||
|
||||
The `livoxProto1` provider accepts the following parameters:
|
||||
|
||||
**handshake-timeout-ms** (optional):
|
||||
- Specifies the timeout for handshake operations when connecting to devices
|
||||
- Value: Integer number of milliseconds
|
||||
- Example: `handshake-timeout-ms=1000` (1 second timeout)
|
||||
- Default: 1000ms if not specified
|
||||
|
||||
**retry-delay-ms** (optional):
|
||||
- Specifies how long to wait for broadcast messages to arrive after attempting an initial direct connection
|
||||
- Value: Integer number of milliseconds
|
||||
- Example: `retry-delay-ms=3000` (wait 3 seconds)
|
||||
- Default: 3000ms if not specified
|
||||
|
||||
**subnet** (optional):
|
||||
- Specifies the IP subnet for device IP address calculation
|
||||
- Value: IP address in the form X.X.0.0 where non-subnet bits must be 0
|
||||
- Example: `subnet=10.42.0.0` (use 10.42.x.x subnet)
|
||||
- Default: 0.0.0.0 (use default 192.168.1.x subnet)
|
||||
|
||||
**data-port** (optional):
|
||||
- Specifies the UDP port for receiving point cloud data from the device
|
||||
- Value: Integer port number
|
||||
- Example: `data-port=56000`
|
||||
- Default: 56000 if not specified
|
||||
|
||||
**cmd-port** (optional):
|
||||
- Specifies the UDP port for receiving command responses from the device
|
||||
- Value: Integer port number
|
||||
- Example: `cmd-port=56001`
|
||||
- Default: 56001 if not specified
|
||||
|
||||
**imu-port** (optional):
|
||||
- Specifies the UDP port for receiving IMU data from the device
|
||||
- Value: Integer port number
|
||||
- Example: `imu-port=56002`
|
||||
- Default: 56002 if not specified
|
||||
|
||||
## Parameter Summary
|
||||
|
||||
### Mode/Stim/Affordance Parameter Values
|
||||
|
||||
| Data Type | Mode Values | Description |
|
||||
|-----------|-------------|-------------|
|
||||
| Point Cloud Intensity | `pointCloudIntensity`, `pcloudIntensity`, `pCloudIntensity`, `pCloudI`, `pcloudI` | Light intensity/reflectivity data |
|
||||
| Point Cloud Coordinates | `pcloud`, `pCloud`, `pointCloud` | Spatial coordinate data |
|
||||
| Gyroscope | `gyro` | Angular velocity measurements |
|
||||
| Accelerometer | `accel` | Linear acceleration measurements |
|
||||
|
||||
### Format Parameter Values (for point cloud modes)
|
||||
|
||||
| Format | Description |
|
||||
|--------|-------------|
|
||||
| `xyz` | Standard Cartesian coordinates (X, Y, Z) |
|
||||
| `spherical` | Raw spherical coordinates (range, azimuth, elevation) |
|
||||
| `spherical-cartesian` | Spherical coordinates converted to Cartesian |
|
||||
| `dual-cartesian` | Dual Cartesian coordinate system |
|
||||
| `dual-spherical` | Dual spherical coordinate system |
|
||||
|
||||
## Device Discovery and Connection
|
||||
|
||||
The specification uses a retry-based connection strategy with two different approaches:
|
||||
|
||||
### Connection Methods
|
||||
|
||||
**1. Broadcast-Based Connection (connectToKnownDeviceReq)**
|
||||
- Uses device IP addresses discovered from broadcast advertisements
|
||||
- **smo-ip parameter**: Optional - if omitted, driver auto-detects the appropriate interface
|
||||
- **smo-subnet-nbits parameter**: Optional - used for validation if smo-ip is provided
|
||||
- **When to use**: When devices are actively broadcasting their presence
|
||||
|
||||
**2. Heuristic Connection (connectByDeviceIdentifierReq)**
|
||||
- Generates device IP addresses from serial numbers using network prefix
|
||||
- **smo-ip parameter**: **Required** - needed to determine network prefix for IP generation
|
||||
- **smo-subnet-nbits parameter**: **Required** - needed to calculate valid device IP addresses
|
||||
- **When to use**: When devices are not broadcasting or for initial setup
|
||||
|
||||
### Connection Strategy
|
||||
|
||||
1. **Initial Check**: Check if device is already known from broadcasts
|
||||
2. **Direct Connect**: Attempt direct connection based on calculated IP address
|
||||
3. **Retry Wait**: If direct connect fails, wait for `retry-delay-ms` for broadcast messages
|
||||
4. **Final Check**: Check known devices again after retry delay
|
||||
5. **Report Result**: Success or failure based on final check
|
||||
|
||||
## Data Formats
|
||||
|
||||
### Point Cloud Coordinate Formats
|
||||
|
||||
1. **XYZ Format**: Standard 3D Cartesian coordinates
|
||||
- X, Y, Z in meters
|
||||
- Standard coordinate system orientation
|
||||
|
||||
2. **Spherical Format**: Raw spherical coordinates
|
||||
- Range (distance) in meters
|
||||
- Azimuth angle in degrees/radians
|
||||
- Elevation angle in degrees/radians
|
||||
|
||||
3. **Spherical-Cartesian Format**: Spherical coordinates converted to Cartesian
|
||||
- Range, azimuth, elevation converted to X, Y, Z
|
||||
- Maintains spherical measurement precision
|
||||
|
||||
4. **Dual Formats**: Support for dual-coordinate systems
|
||||
- Useful for devices with multiple measurement modes
|
||||
- Provides redundancy and validation capabilities
|
||||
|
||||
### Intensity Data
|
||||
|
||||
- Reflectivity values typically in the range 0-255
|
||||
- Normalized intensity measurements
|
||||
- Calibrated for material reflectivity analysis
|
||||
|
||||
### IMU Data
|
||||
|
||||
- **Gyroscope**: Angular velocity measurements (rad/s)
|
||||
- **Accelerometer**: Linear acceleration measurements (m/s²)
|
||||
- Timestamped data synchronized with point cloud measurements
|
||||
|
||||
## Error Handling
|
||||
|
||||
The specification includes comprehensive error handling for:
|
||||
|
||||
- Network connectivity issues
|
||||
- Device communication timeouts
|
||||
- Invalid coordinate format requests
|
||||
- IMU data stream interruptions
|
||||
- Device discovery failures
|
||||
- Connection retry timeouts
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef ASYNCHRONOUS_BRIDGE_H
|
||||
#define ASYNCHRONOUS_BRIDGE_H
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class AsynchronousBridge
|
||||
{
|
||||
public:
|
||||
AsynchronousBridge(boost::asio::io_service &io_service)
|
||||
: isAsyncOperationComplete(false), io_service(io_service)
|
||||
{}
|
||||
|
||||
void setAsyncOperationComplete(void)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* This empty post()ed message is necessary to ensure that the thread
|
||||
* that's waiting on the io_service is signaled to wake up and check
|
||||
* the io_service's queue.
|
||||
*/
|
||||
isAsyncOperationComplete.store(true);
|
||||
io_service.post([]{});
|
||||
}
|
||||
|
||||
void waitForAsyncOperationCompleteOrIoServiceStopped(void)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
io_service.run_one();
|
||||
if (isAsyncOperationComplete.load() || io_service.stopped())
|
||||
{ break; }
|
||||
}
|
||||
}
|
||||
|
||||
bool exitedBecauseIoServiceStopped(void) const
|
||||
{ return io_service.stopped(); }
|
||||
|
||||
private:
|
||||
std::atomic<bool> isAsyncOperationComplete;
|
||||
boost::asio::io_service &io_service;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // ASYNCHRONOUS_BRIDGE_H
|
||||
@@ -0,0 +1,152 @@
|
||||
#ifndef ASYNCHRONOUS_CONTINUATION_H
|
||||
#define ASYNCHRONOUS_CONTINUATION_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
#include <componentThread.h>
|
||||
#include <lockSet.h>
|
||||
#include <callback.h>
|
||||
#include <asynchronousContinuationChainLink.h>
|
||||
|
||||
|
||||
namespace smo {
|
||||
|
||||
/**
|
||||
* AsynchronousContinuation - Template base class for async sequence management
|
||||
*
|
||||
* This template provides a common pattern for managing asynchronous operations
|
||||
* that need to maintain object lifetime through a sequence of callbacks.
|
||||
*
|
||||
* The template parameter OriginalCbFnT represents the signature of the original
|
||||
* callback that will be invoked when the async sequence completes.
|
||||
*/
|
||||
template <class OriginalCbFnT>
|
||||
class AsynchronousContinuation
|
||||
: public AsynchronousContinuationChainLink
|
||||
{
|
||||
public:
|
||||
explicit AsynchronousContinuation(Callback<OriginalCbFnT> originalCb)
|
||||
: originalCallback(std::move(originalCb))
|
||||
{}
|
||||
|
||||
/** EXPLANATION:
|
||||
* Each numbered segmented sequence persists the lifetime of the
|
||||
* continuation object by taking a copy of its shared_ptr.
|
||||
*/
|
||||
typedef void (SegmentFn)(
|
||||
std::shared_ptr<AsynchronousContinuation<OriginalCbFnT>>
|
||||
lifetimePreservingConveyance);
|
||||
|
||||
/** EXPLANATION:
|
||||
* When an exception is thrown in a an async callee, which pertains to an
|
||||
* error in the data given by the caller, we ought not to throw the
|
||||
* exception within the callee. Instead, we should store the exception
|
||||
* in the continuation object and return it to the caller.
|
||||
*
|
||||
* The caller should then call checkException() to rethrow it on its
|
||||
* own stack.
|
||||
*
|
||||
* This macro should be used by the caller to bubble the exception to the
|
||||
* caller.
|
||||
*/
|
||||
#define CONT_SET_EXC(continuation, type, exc_obj) \
|
||||
(continuation)->exception = std::make_exception_ptr<type>(exc_obj)
|
||||
|
||||
#define CONT_SET_EXC_AND_RET(continuation, type, exc_obj) \
|
||||
do { \
|
||||
(continuation)->exception = std::make_exception_ptr<type>(exc_obj); \
|
||||
return; \
|
||||
} while(0)
|
||||
|
||||
// Call this in the caller to rethrow the exception.
|
||||
void checkException()
|
||||
{
|
||||
if (exception)
|
||||
{ std::rethrow_exception(exception); }
|
||||
}
|
||||
|
||||
// Implement the virtual method from AsynchronousContinuationChainLink
|
||||
virtual std::shared_ptr<AsynchronousContinuationChainLink>
|
||||
getCallersContinuationShPtr() const override
|
||||
{ return originalCallback.callerContinuation; }
|
||||
|
||||
public:
|
||||
Callback<OriginalCbFnT> originalCallback;
|
||||
std::exception_ptr exception;
|
||||
};
|
||||
|
||||
/**
|
||||
* NonPostedAsynchronousContinuation - For continuations that don't post
|
||||
* callbacks
|
||||
*
|
||||
* Note: We intentionally do not create a
|
||||
* LockedNonPostedAsynchronousContinuation because the only way to implement
|
||||
* non-posted locking would be via busy-spinning or sleeplocks. This would
|
||||
* eliminate the throughput advantage from our Qspinning mechanism, which
|
||||
* relies on re-posting to the io_service queue when locks are unavailable.
|
||||
*/
|
||||
template <class OriginalCbFnT>
|
||||
class NonPostedAsynchronousContinuation
|
||||
: public AsynchronousContinuation<OriginalCbFnT>
|
||||
{
|
||||
public:
|
||||
explicit NonPostedAsynchronousContinuation(
|
||||
Callback<OriginalCbFnT> originalCb)
|
||||
: AsynchronousContinuation<OriginalCbFnT>(originalCb)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Call the original callback with perfect forwarding
|
||||
* (immediate execution)
|
||||
*
|
||||
* This implementation calls the original callback immediately without
|
||||
* posting to any thread or queue. Used for non-posted continuations.
|
||||
*
|
||||
* @param args Arguments to forward to the original callback
|
||||
*/
|
||||
template<typename... Args>
|
||||
void callOriginalCb(Args&&... args)
|
||||
{
|
||||
if (AsynchronousContinuation<OriginalCbFnT>::originalCallback
|
||||
.callbackFn)
|
||||
{
|
||||
AsynchronousContinuation<OriginalCbFnT>::originalCallback
|
||||
.callbackFn(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class OriginalCbFnT>
|
||||
class PostedAsynchronousContinuation
|
||||
: public AsynchronousContinuation<OriginalCbFnT>
|
||||
{
|
||||
public:
|
||||
PostedAsynchronousContinuation(
|
||||
const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<OriginalCbFnT> originalCbFn)
|
||||
: AsynchronousContinuation<OriginalCbFnT>(originalCbFn),
|
||||
caller(caller)
|
||||
{}
|
||||
|
||||
template<typename... Args>
|
||||
void callOriginalCb(Args&&... args)
|
||||
{
|
||||
if (AsynchronousContinuation<OriginalCbFnT>::originalCallback
|
||||
.callbackFn)
|
||||
{
|
||||
caller->getIoService().post(
|
||||
std::bind(
|
||||
AsynchronousContinuation<OriginalCbFnT>::originalCallback
|
||||
.callbackFn,
|
||||
std::forward<Args>(args)...));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::shared_ptr<ComponentThread> caller;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // ASYNCHRONOUS_CONTINUATION_H
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H
|
||||
#define ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace smo {
|
||||
|
||||
/**
|
||||
* @brief Base class for all asynchronous continuation chain links
|
||||
*
|
||||
* This non-template base class provides type erasure for the continuation
|
||||
* chain, allowing RTTI and dynamic casting when walking the chain.
|
||||
*
|
||||
* The chain walking logic can use dynamic_cast to determine the most
|
||||
* derived type and perform appropriate operations.
|
||||
*
|
||||
* Inherits from enable_shared_from_this to allow objects to obtain a
|
||||
* shared_ptr to themselves, which is useful for gridlock detection tracking.
|
||||
*/
|
||||
class AsynchronousContinuationChainLink
|
||||
: public std::enable_shared_from_this<AsynchronousContinuationChainLink>
|
||||
{
|
||||
public:
|
||||
virtual ~AsynchronousContinuationChainLink() = default;
|
||||
|
||||
virtual std::shared_ptr<AsynchronousContinuationChainLink>
|
||||
getCallersContinuationShPtr() const = 0;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef ASYNCHRONOUS_LOOP_H
|
||||
#define ASYNCHRONOUS_LOOP_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class AsynchronousLoop
|
||||
{
|
||||
public:
|
||||
AsynchronousLoop(
|
||||
const unsigned int nTotal,
|
||||
unsigned int nSucceeded=0, unsigned int nFailed=0)
|
||||
: nTotal(nTotal), nSucceeded(nSucceeded), nFailed(nFailed)
|
||||
{}
|
||||
|
||||
AsynchronousLoop(const AsynchronousLoop& other)
|
||||
: nTotal(other.nTotal),
|
||||
nSucceeded(other.nSucceeded.load()), nFailed(other.nFailed.load())
|
||||
{}
|
||||
|
||||
AsynchronousLoop& operator=(const AsynchronousLoop& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
nTotal = other.nTotal;
|
||||
nSucceeded.store(other.nSucceeded.load());
|
||||
nFailed.store(other.nFailed.load());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isComplete(void) const
|
||||
{
|
||||
return nSucceeded + nFailed == nTotal;
|
||||
}
|
||||
|
||||
void incrementSuccessOrFailureDueTo(bool success)
|
||||
{
|
||||
if (success)
|
||||
{ ++nSucceeded; }
|
||||
else
|
||||
{ ++nFailed; }
|
||||
}
|
||||
|
||||
bool incrementSuccessOrFailureAndTestForCompletionDueTo(bool success)
|
||||
{
|
||||
incrementSuccessOrFailureDueTo(success);
|
||||
return isComplete();
|
||||
}
|
||||
|
||||
bool nTotalIsZero(void) const
|
||||
{
|
||||
return nTotal == 0;
|
||||
}
|
||||
|
||||
public:
|
||||
unsigned int nTotal;
|
||||
std::atomic<unsigned int> nSucceeded, nFailed;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // ASYNCHRONOUS_LOOP_H
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef CALLBACK_H
|
||||
#define CALLBACK_H
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace smo {
|
||||
|
||||
// Forward declaration
|
||||
class AsynchronousContinuationChainLink;
|
||||
|
||||
/**
|
||||
* @brief Callback class that wraps a function and its caller continuation
|
||||
*
|
||||
* This class provides a way to pass both a callback function and the
|
||||
* caller's continuation in a single object, enabling deadlock detection
|
||||
* by walking the chain of continuations.
|
||||
*
|
||||
* Usage: Callback<CbFnT>{context, std::bind(...)}
|
||||
*/
|
||||
template<typename CbFnT>
|
||||
class Callback
|
||||
{
|
||||
public:
|
||||
// Aggregate initialization allows: Callback<CbFnT>{context, std::bind(...)}
|
||||
std::shared_ptr<AsynchronousContinuationChainLink> callerContinuation;
|
||||
CbFnT callbackFn;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // CALLBACK_H
|
||||
@@ -9,6 +9,16 @@
|
||||
#define CONFIG_MIND_VOSCILLATOR_PERIOD_MS @MIND_VOSCILLATOR_PERIOD_MS@
|
||||
#define CONFIG_MIND_VOSCILLATOR_FREQ_MS @MIND_VOSCILLATOR_FREQ_MS@
|
||||
|
||||
/* Device manager reattacher configuration */
|
||||
#define CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS @MRNTT_DEVMGR_REATTACHER_PERIOD_MS@
|
||||
|
||||
/* World thread configuration */
|
||||
#cmakedefine CONFIG_WORLD_USE_BODY_THREAD
|
||||
|
||||
/* Debug locking configuration */
|
||||
#cmakedefine CONFIG_ENABLE_DEBUG_LOCKS
|
||||
#cmakedefine CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS @DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS@
|
||||
|
||||
/* Cross-compilation configuration */
|
||||
#cmakedefine CMAKE_CROSSCOMPILING
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#ifndef DEPENDENCY_GRAPH_H
|
||||
#define DEPENDENCY_GRAPH_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace smo {
|
||||
|
||||
// Forward declarations
|
||||
class AsynchronousContinuationChainLink;
|
||||
|
||||
/**
|
||||
* @brief DependencyGraph - Represents a directed graph for lock dependency analysis
|
||||
*
|
||||
* This graph represents dependencies between continuations (lockvokers) where
|
||||
* an edge from A to B means that continuation A wants a lock that is held by
|
||||
* continuation B. This is used to detect circular dependencies (gridlocks).
|
||||
*/
|
||||
class DependencyGraph
|
||||
{
|
||||
public:
|
||||
typedef std::shared_ptr<AsynchronousContinuationChainLink> Node;
|
||||
// Each node maps to a set of nodes it depends on
|
||||
typedef std::unordered_map<Node, std::unordered_set<Node>> AdjacencyList;
|
||||
|
||||
public:
|
||||
void addNode(const Node& node);
|
||||
|
||||
/**
|
||||
* @brief Add a directed edge from source to target
|
||||
* @param source The continuation that wants a lock
|
||||
* @param target The continuation that holds the wanted lock
|
||||
*/
|
||||
void addEdge(const Node& source, const Node& target);
|
||||
|
||||
/**
|
||||
* @brief Find all cycles in the graph using DFS
|
||||
* @return Vector of cycles, where each cycle is a vector of nodes
|
||||
*/
|
||||
std::vector<std::vector<Node>> findCycles() const;
|
||||
|
||||
/**
|
||||
* @brief Check if there are any cycles in the graph
|
||||
* @return true if cycles exist, false otherwise
|
||||
*/
|
||||
bool hasCycles() const;
|
||||
|
||||
/**
|
||||
* @brief Get the number of nodes in the graph
|
||||
* @return Number of nodes
|
||||
*/
|
||||
size_t getNodeCount() const;
|
||||
|
||||
/**
|
||||
* @brief Get the adjacency list for debugging
|
||||
* @return Reference to the adjacency list
|
||||
*/
|
||||
const AdjacencyList& getAdjacencyList() const { return adjacencyList; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief DFS helper for cycle detection
|
||||
* @param node Current node being visited
|
||||
* @param visited Set of nodes that have been fully processed
|
||||
* @param recursionStack Set of nodes currently in the recursion stack
|
||||
* @param path Current path being explored
|
||||
* @param cycles Vector to store found cycles
|
||||
*/
|
||||
void dfsCycleDetection(
|
||||
const Node& node,
|
||||
std::unordered_set<Node>& visited,
|
||||
std::unordered_set<Node>& recursionStack,
|
||||
std::vector<Node>& path,
|
||||
std::vector<std::vector<Node>>& cycles)
|
||||
const;
|
||||
|
||||
private:
|
||||
AdjacencyList adjacencyList;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // DEPENDENCY_GRAPH_H
|
||||
@@ -0,0 +1,262 @@
|
||||
#ifndef LOCK_SET_H
|
||||
#define LOCK_SET_H
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <qutex.h>
|
||||
#include <lockerAndInvokerBase.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
// Forward declarations
|
||||
template <class OriginalCbFnT>
|
||||
class SerializedAsynchronousContinuation;
|
||||
class Qutex;
|
||||
|
||||
/**
|
||||
* @brief LockSet - Manages a collection of locks for acquisition/release
|
||||
*/
|
||||
template <class OriginalCbFnT>
|
||||
class LockSet
|
||||
{
|
||||
public:
|
||||
/** EXPLANATION:
|
||||
* Tracks both the Qutex that must be acquired, as well as the parent
|
||||
* LockerAndInvoker that this LockSet has registered into that Qutex's
|
||||
* queue.
|
||||
*/
|
||||
struct LockUsageDesc
|
||||
{
|
||||
std::reference_wrapper<Qutex> qutex;
|
||||
typename LockerAndInvokerBase::List::iterator iterator;
|
||||
bool hasBeenReleased = false;
|
||||
|
||||
LockUsageDesc(std::reference_wrapper<Qutex> qutexRef,
|
||||
typename LockerAndInvokerBase::List::iterator iter)
|
||||
: qutex(qutexRef), iterator(iter), hasBeenReleased(false) {}
|
||||
};
|
||||
|
||||
typedef std::vector<std::reference_wrapper<Qutex>> Set;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param parentContinuation Reference to the parent
|
||||
* SerializedAsynchronousContinuation
|
||||
* @param qutexes Vector of Qutex references that must be acquired
|
||||
*/
|
||||
LockSet(
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT> &parentContinuation,
|
||||
std::vector<std::reference_wrapper<Qutex>> qutexes = {})
|
||||
: parentContinuation(parentContinuation), allLocksAcquired(false),
|
||||
registeredInQutexQueues(false)
|
||||
{
|
||||
/* Convert Qutex references to LockUsageDesc (iterators will be filled
|
||||
* in during registration)
|
||||
*/
|
||||
locks.reserve(qutexes.size());
|
||||
for (auto& qutexRef : qutexes)
|
||||
{
|
||||
locks.emplace_back(
|
||||
qutexRef,
|
||||
typename LockerAndInvokerBase::List::iterator{});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Register the LockSet with all its Qutex locks
|
||||
* @param lockvoker The LockerAndInvoker to register with each Qutex
|
||||
*
|
||||
* EXPLANATION:
|
||||
* I'm not sure an unregisterFromQutexQueues() method is needed.
|
||||
* Why? Because if an async sequence can't acquire all locks, it will
|
||||
* simply never leave the qutexQ until it eventually does. The only other
|
||||
* time it will leave the qutexQ is when the program terminates.
|
||||
*
|
||||
* I'm not sure we'll actually cancal all in-flight async sequences --
|
||||
* and especially not all those that aren't even in any io_service queues.
|
||||
* To whatever extent these objects get cleaned up, they'll probably be
|
||||
* cleaned up in the qutexQ's std::list destructor -- and that won't
|
||||
* execute any fancy cleanup logic. It'll just clear() out the list.
|
||||
*/
|
||||
void registerInQutexQueues(
|
||||
const std::shared_ptr<LockerAndInvokerBase> &lockvoker
|
||||
)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Register the lockvoker with each Qutex and store the returned
|
||||
* iterator to its place within each Qutex's queue. We store the
|
||||
* iterator so that we can quickly move the lockvoker around within
|
||||
* the queue, and eventually, erase() it when we acquire all the
|
||||
* locks.
|
||||
*/
|
||||
for (auto& lockUsageDesc : locks)
|
||||
{
|
||||
lockUsageDesc.iterator = lockUsageDesc.qutex.get().registerInQueue(
|
||||
lockvoker);
|
||||
}
|
||||
|
||||
registeredInQutexQueues = true;
|
||||
}
|
||||
|
||||
void unregisterFromQutexQueues()
|
||||
{
|
||||
if (!registeredInQutexQueues)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": LockSet::unregisterFromQutexQueues() called but not "
|
||||
"registered in Qutex queues");
|
||||
}
|
||||
|
||||
// Unregister from all qutex queues
|
||||
for (auto& lockUsageDesc : locks)
|
||||
{
|
||||
auto it = lockUsageDesc.iterator;
|
||||
lockUsageDesc.qutex.get().unregisterFromQueue(it);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Try to acquire all locks in order; back off if acquisition fails
|
||||
* @param lockvoker The LockerAndInvoker attempting to acquire the locks
|
||||
* @param firstFailedQutex Output parameter to receive the first Qutex that
|
||||
* failed acquisition (can be nullptr)
|
||||
* @return true if all locks were acquired, false otherwise
|
||||
*/
|
||||
bool tryAcquireOrBackOff(
|
||||
LockerAndInvokerBase &lockvoker,
|
||||
std::optional<std::reference_wrapper<Qutex>> &firstFailedQutex
|
||||
= std::nullopt
|
||||
)
|
||||
{
|
||||
if (!registeredInQutexQueues)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": LockSet::tryAcquireOrBackOff() called but not registered in "
|
||||
"Qutex queues");
|
||||
}
|
||||
if (allLocksAcquired)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": LockSet::tryAcquireOrBackOff() called but allLocksAcquired "
|
||||
"is already true");
|
||||
}
|
||||
|
||||
// Try to acquire all required locks
|
||||
int nAcquired = 0;
|
||||
const int nRequiredLocks = static_cast<int>(locks.size());
|
||||
for (auto& lockUsageDesc : locks)
|
||||
{
|
||||
if (!lockUsageDesc.qutex.get().tryAcquire(
|
||||
lockvoker, nRequiredLocks))
|
||||
{
|
||||
// Set the first failed qutex for debugging
|
||||
firstFailedQutex = std::ref(lockUsageDesc.qutex.get());
|
||||
break;
|
||||
}
|
||||
|
||||
nAcquired++;
|
||||
}
|
||||
|
||||
if (nAcquired < nRequiredLocks)
|
||||
{
|
||||
// Release any locks we managed to acquire
|
||||
for (int i = 0; i < nAcquired; i++) {
|
||||
locks[i].qutex.get().backoff(lockvoker, nRequiredLocks);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
allLocksAcquired = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// @brief Release all locks
|
||||
void release()
|
||||
{
|
||||
if (!registeredInQutexQueues)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": LockSet::release() called but not registered in Qutex "
|
||||
"queues");
|
||||
}
|
||||
|
||||
if (!allLocksAcquired)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": LockSet::release() called but allLocksAcquired is false");
|
||||
}
|
||||
|
||||
for (auto& lockUsageDesc : locks)
|
||||
{
|
||||
if (lockUsageDesc.hasBeenReleased) { continue; }
|
||||
|
||||
lockUsageDesc.qutex.get().release();
|
||||
}
|
||||
|
||||
allLocksAcquired = false;
|
||||
}
|
||||
|
||||
const LockUsageDesc &getLockUsageDesc(const Qutex &criterionLock) const
|
||||
{
|
||||
for (auto& lockUsageDesc : locks)
|
||||
{
|
||||
if (&lockUsageDesc.qutex.get() == &criterionLock) {
|
||||
return lockUsageDesc;
|
||||
}
|
||||
}
|
||||
|
||||
// Should never happen if the LockSet is properly constructed
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": Qutex not found in this LockSet");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Release a specific qutex early and mark it as released
|
||||
* @param qutex The qutex to release early
|
||||
*/
|
||||
void releaseQutexEarly(Qutex &qutex)
|
||||
{
|
||||
if (!allLocksAcquired)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": LockSet::releaseQutexEarly() called but allLocksAcquired is false");
|
||||
}
|
||||
|
||||
auto& lockUsageDesc = const_cast<LockUsageDesc&>(
|
||||
getLockUsageDesc(qutex));
|
||||
|
||||
if (!lockUsageDesc.hasBeenReleased)
|
||||
{
|
||||
lockUsageDesc.qutex.get().release();
|
||||
lockUsageDesc.hasBeenReleased = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<LockUsageDesc> locks;
|
||||
|
||||
private:
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT> &parentContinuation;
|
||||
bool allLocksAcquired, registeredInQutexQueues;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // LOCK_SET_H
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef LOCKER_AND_INVOKER_BASE_H
|
||||
#define LOCKER_AND_INVOKER_BASE_H
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
namespace smo {
|
||||
|
||||
// Forward declaration
|
||||
class Qutex;
|
||||
|
||||
/**
|
||||
* @brief LockerAndInvokerBase - Base class for lockvoking mechanism
|
||||
*
|
||||
* This base class contains the common functionality needed by Qutex,
|
||||
* including the serialized continuation reference and comparison operators.
|
||||
*/
|
||||
class LockerAndInvokerBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param serializedContinuationVaddr Raw pointer to the serialized continuation
|
||||
*/
|
||||
explicit LockerAndInvokerBase(const void* serializedContinuationVaddr)
|
||||
: serializedContinuationVaddr(serializedContinuationVaddr)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Typedef for list of LockerAndInvokerBase shared pointers
|
||||
*/
|
||||
typedef std::list<std::shared_ptr<LockerAndInvokerBase>> List;
|
||||
|
||||
/**
|
||||
* @brief Get the iterator for this lockvoker in the specified Qutex's queue
|
||||
* @param qutex The Qutex to get the iterator for
|
||||
* @return Iterator pointing to this lockvoker in the Qutex's queue
|
||||
*/
|
||||
virtual List::iterator getLockvokerIteratorForQutex(Qutex& qutex) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Awaken this lockvoker by posting it to its io_service
|
||||
* @param forceAwaken If true, post even if already awake
|
||||
*/
|
||||
virtual void awaken(bool forceAwaken = false) = 0;
|
||||
|
||||
/* These two are ued to iterate through the lockset of a Lockvoker in a
|
||||
* template-erased manner. We use them in the gridlock detection algorithm.
|
||||
*/
|
||||
virtual size_t getLockSetSize() const = 0;
|
||||
virtual Qutex& getLockAt(size_t index) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Equality operator
|
||||
*
|
||||
* Compare by the address of the continuation objects. Why?
|
||||
* Because there's no guarantee that the lockvoker object that was
|
||||
* passed in by the io_service invocation is the same object as that
|
||||
* which is in the qutexQs. Especially because we make_shared() a
|
||||
* copy when registerInQutexQueues()ing.
|
||||
*
|
||||
* Generally when we "wake" a lockvoker by enqueuing it, boost's
|
||||
* io_service::post will copy the lockvoker object.
|
||||
*/
|
||||
bool operator==(const LockerAndInvokerBase &other) const
|
||||
{
|
||||
return serializedContinuationVaddr == other.serializedContinuationVaddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inequality operator
|
||||
*/
|
||||
bool operator!=(const LockerAndInvokerBase &other) const
|
||||
{
|
||||
return serializedContinuationVaddr != other.serializedContinuationVaddr;
|
||||
}
|
||||
|
||||
protected:
|
||||
/* Never let this monstrosity be seen beyond this class's scope.
|
||||
* Remember what I've taught you, quasi-modo?
|
||||
*/
|
||||
const void* serializedContinuationVaddr;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // LOCKER_AND_INVOKER_BASE_H
|
||||
@@ -0,0 +1,109 @@
|
||||
#ifndef QUTEX_H
|
||||
#define QUTEX_H
|
||||
|
||||
#include <config.h>
|
||||
#include <list>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <spinLock.h>
|
||||
#include <lockerAndInvokerBase.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
/**
|
||||
* @brief Qutex - Queue-based mutex for asynchronous lock management
|
||||
*
|
||||
* A Qutex combines a spinlock, an ownership flag, and a queue of waiting
|
||||
* lockvokers to provide efficient asynchronous lock management with
|
||||
* priority-based acquisition for LockSets.
|
||||
*/
|
||||
class Qutex
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
Qutex([[maybe_unused]] const std::string &_name)
|
||||
:
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
name(_name), currOwner(nullptr),
|
||||
#endif
|
||||
isOwned(false)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Register a lockvoker in the queue
|
||||
* @param lockvoker The lockvoker to register
|
||||
* @return Iterator pointing to the registered lockvoker in the queue
|
||||
*/
|
||||
LockerAndInvokerBase::List::iterator registerInQueue(
|
||||
const std::shared_ptr<LockerAndInvokerBase> &lockvoker
|
||||
)
|
||||
{
|
||||
lock.acquire();
|
||||
auto it = queue.insert(queue.end(), lockvoker);
|
||||
lock.release();
|
||||
return it;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregister a lockvoker from the queue
|
||||
* @param it Iterator pointing to the lockvoker to unregister
|
||||
* @param shouldLock Whether to acquire the spinlock before erasing (default: true)
|
||||
*/
|
||||
void unregisterFromQueue(
|
||||
LockerAndInvokerBase::List::iterator it, bool shouldLock = true
|
||||
)
|
||||
{
|
||||
if (shouldLock)
|
||||
{
|
||||
lock.acquire();
|
||||
queue.erase(it);
|
||||
lock.release();
|
||||
}
|
||||
else {
|
||||
queue.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try to acquire the lock for a lockvoker
|
||||
* @param tryingLockvoker The lockvoker attempting to acquire the lock
|
||||
* @param nRequiredLocks Number of locks required by the lockvoker's LockSet
|
||||
* @return true if the lock was successfully acquired, false otherwise
|
||||
*/
|
||||
bool tryAcquire(
|
||||
const LockerAndInvokerBase &tryingLockvoker, int nRequiredLocks);
|
||||
|
||||
/**
|
||||
* @brief Handle backoff when a lockvoker fails to acquire all required locks
|
||||
* @param failedAcquirer The lockvoker that failed to acquire all locks
|
||||
* @param nRequiredLocks Number of locks required by the lockvoker's LockSet
|
||||
*/
|
||||
void backoff(const LockerAndInvokerBase &failedAcquirer, int nRequiredLocks);
|
||||
|
||||
/**
|
||||
* @brief Release the lock and wake up the next waiting lockvoker
|
||||
*/
|
||||
void release();
|
||||
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
std::shared_ptr<LockerAndInvokerBase> getCurrOwner() const
|
||||
{ return currOwner; }
|
||||
#endif
|
||||
|
||||
public:
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
std::string name;
|
||||
std::shared_ptr<LockerAndInvokerBase> currOwner;
|
||||
#endif
|
||||
SpinLock lock;
|
||||
LockerAndInvokerBase::List queue;
|
||||
bool isOwned;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // QUTEX_H
|
||||
@@ -0,0 +1,164 @@
|
||||
#ifndef QUTEX_ACQUISITION_HISTORY_TRACKER_H
|
||||
#define QUTEX_ACQUISITION_HISTORY_TRACKER_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <forward_list>
|
||||
#include <functional>
|
||||
#include "spinLock.h"
|
||||
|
||||
|
||||
namespace smo {
|
||||
|
||||
// Forward declarations
|
||||
class Qutex;
|
||||
class AsynchronousContinuationChainLink;
|
||||
class DependencyGraph;
|
||||
|
||||
/**
|
||||
* @brief QutexAcquisitionHistoryTracker - Tracks acquisition history for
|
||||
* gridlock detection
|
||||
*
|
||||
* This class maintains a central acquisition history to track all lockvokers
|
||||
* suspected of being gridlocked. It stores information about what locks each
|
||||
* timed-out lockvoker wants and what locks they hold in their continuation
|
||||
* history.
|
||||
*/
|
||||
class QutexAcquisitionHistoryTracker
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Type definition for the acquisition history entry
|
||||
*
|
||||
* pair.first: The firstFailedQutex that this lockvoker WANTS but can't
|
||||
* acquire
|
||||
* pair.second: A unique_ptr to a list of all acquired Qutexes in this
|
||||
* lockvoker's continuation history
|
||||
*/
|
||||
typedef std::pair<
|
||||
std::reference_wrapper<Qutex>,
|
||||
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
|
||||
> AcquisitionHistoryEntry;
|
||||
|
||||
/**
|
||||
* @brief Type definition for the acquisition history map
|
||||
*
|
||||
* Key: std::shared_ptr<AsynchronousContinuationChainLink>
|
||||
* (the continuation that contains the timed-out lockvoker)
|
||||
* Value: AcquisitionHistoryEntry
|
||||
* (its wanted lock (aka: firstFailedQutex/pair.first) + held locks)
|
||||
*/
|
||||
typedef std::unordered_map<
|
||||
std::shared_ptr<AsynchronousContinuationChainLink>,
|
||||
AcquisitionHistoryEntry
|
||||
> AcquisitionHistoryMap;
|
||||
|
||||
public:
|
||||
static QutexAcquisitionHistoryTracker& getInstance()
|
||||
{
|
||||
static QutexAcquisitionHistoryTracker instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add a continuation to the acquisition history if it doesn't
|
||||
* already exist
|
||||
* @param continuation Shared pointer to the
|
||||
* AsynchronousContinuationChainLink
|
||||
* @param wantedLock The lock that this continuation wants but can't
|
||||
* acquire
|
||||
* @param heldLocks Unique pointer to list of locks held in this
|
||||
* continuation's history (will be moved)
|
||||
*/
|
||||
void addIfNotExists(
|
||||
std::shared_ptr<AsynchronousContinuationChainLink> &continuation,
|
||||
Qutex& wantedLock,
|
||||
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
|
||||
heldLocks
|
||||
)
|
||||
{
|
||||
acquisitionHistoryLock.acquire();
|
||||
|
||||
auto it = acquisitionHistory.find(continuation);
|
||||
// If a continuation already exists, don't add it again
|
||||
if (it != acquisitionHistory.end())
|
||||
{
|
||||
acquisitionHistoryLock.release();
|
||||
return;
|
||||
}
|
||||
|
||||
acquisitionHistory.emplace(continuation, std::make_pair(
|
||||
std::ref(wantedLock), std::move(heldLocks)));
|
||||
|
||||
acquisitionHistoryLock.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a continuation from the acquisition history
|
||||
*
|
||||
* @param continuation Shared pointer to the
|
||||
* AsynchronousContinuationChainLink to remove
|
||||
* @return true if the continuation was found and removed, false if not found
|
||||
*/
|
||||
bool remove(
|
||||
std::shared_ptr<AsynchronousContinuationChainLink> &continuation
|
||||
)
|
||||
{
|
||||
acquisitionHistoryLock.acquire();
|
||||
|
||||
auto it = acquisitionHistory.find(continuation);
|
||||
if (it != acquisitionHistory.end())
|
||||
{
|
||||
acquisitionHistory.erase(it);
|
||||
|
||||
acquisitionHistoryLock.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
acquisitionHistoryLock.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool heuristicallyTraceContinuationHistoryForGridlockOn(
|
||||
Qutex &firstFailedQutex,
|
||||
std::shared_ptr<AsynchronousContinuationChainLink>&
|
||||
currentContinuation);
|
||||
bool completelyTraceContinuationHistoryForGridlockOn(
|
||||
Qutex &firstFailedQutex);
|
||||
|
||||
/**
|
||||
* @brief Generates a dependency graph among known continuations, based on
|
||||
* the currently known acquisition history. There may well be a cyclical
|
||||
* dependency which hasn't been reported to the history tracker yet.
|
||||
* @param dontAcquireLock If true, skips acquiring the internal spinlock
|
||||
* (assumes caller already holds it)
|
||||
*/
|
||||
[[nodiscard]] std::unique_ptr<DependencyGraph> generateGraph(
|
||||
bool dontAcquireLock = false);
|
||||
|
||||
// Disable copy constructor and assignment operator
|
||||
QutexAcquisitionHistoryTracker(
|
||||
const QutexAcquisitionHistoryTracker&) = delete;
|
||||
QutexAcquisitionHistoryTracker& operator=(
|
||||
const QutexAcquisitionHistoryTracker&) = delete;
|
||||
|
||||
private:
|
||||
QutexAcquisitionHistoryTracker() = default;
|
||||
~QutexAcquisitionHistoryTracker() = default;
|
||||
|
||||
private:
|
||||
/** EXPLANATION:
|
||||
* We use a SpinLock here instead of a Qutex because this acquisition
|
||||
* history tracker is invoked within the LockerAndInvoker.
|
||||
* Since LockerAndInvoker is too tightly coupled with Qutex workings, using
|
||||
* a Qutex here would create a circular dependency or deadlock situation.
|
||||
* Therefore, it's best to use a SpinLock on the history class to avoid
|
||||
* these coupling issues.
|
||||
*/
|
||||
SpinLock acquisitionHistoryLock;
|
||||
AcquisitionHistoryMap acquisitionHistory;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // QUTEX_ACQUISITION_HISTORY_TRACKER_H
|
||||
@@ -0,0 +1,589 @@
|
||||
#ifndef SERIALIZED_ASYNCHRONOUS_CONTINUATION_H
|
||||
#define SERIALIZED_ASYNCHRONOUS_CONTINUATION_H
|
||||
|
||||
#include <config.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <componentThread.h>
|
||||
#include <lockSet.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <lockerAndInvokerBase.h>
|
||||
#include <callback.h>
|
||||
#include <qutexAcquisitionHistoryTracker.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
template <class OriginalCbFnT>
|
||||
class SerializedAsynchronousContinuation
|
||||
: public PostedAsynchronousContinuation<OriginalCbFnT>
|
||||
{
|
||||
public:
|
||||
SerializedAsynchronousContinuation(
|
||||
const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<OriginalCbFnT> originalCbFn,
|
||||
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
|
||||
: PostedAsynchronousContinuation<OriginalCbFnT>(caller, originalCbFn),
|
||||
requiredLocks(*this, std::move(requiredLocks))
|
||||
{}
|
||||
|
||||
template<typename... Args>
|
||||
void callOriginalCb(Args&&... args)
|
||||
{
|
||||
requiredLocks.release();
|
||||
PostedAsynchronousContinuation<OriginalCbFnT>::callOriginalCb(
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Return list of all qutexes in predecessors' LockSets; excludes self.
|
||||
[[nodiscard]]
|
||||
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
|
||||
getAcquiredQutexHistory() const;
|
||||
|
||||
/**
|
||||
* @brief Release a specific qutex early
|
||||
* @param qutex The qutex to release early
|
||||
*/
|
||||
void releaseQutexEarly(Qutex &qutex)
|
||||
{ requiredLocks.releaseQutexEarly(qutex); }
|
||||
|
||||
public:
|
||||
LockSet<OriginalCbFnT> requiredLocks;
|
||||
std::atomic<bool> isAwakeOrBeingAwakened{false};
|
||||
|
||||
/**
|
||||
* @brief LockerAndInvoker - Template class for lockvoking mechanism
|
||||
*
|
||||
* This class wraps a std::bind result and provides locking functionality.
|
||||
* When locks cannot be acquired, the object re-posts itself to the io_service
|
||||
* queue, implementing the "spinqueueing" pattern.
|
||||
*/
|
||||
template <class InvocationTargetT>
|
||||
class LockerAndInvoker
|
||||
: public LockerAndInvokerBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor that immediately posts to io_service
|
||||
* @param serializedContinuation Reference to the serialized continuation
|
||||
* containing LockSet and target io_service
|
||||
* @param target The ComponentThread whose io_service to post to
|
||||
* @param invocationTarget The std::bind result to invoke when locks are acquired
|
||||
*/
|
||||
LockerAndInvoker(
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT>
|
||||
&serializedContinuation,
|
||||
const std::shared_ptr<ComponentThread>& target,
|
||||
InvocationTargetT invocationTarget)
|
||||
: LockerAndInvokerBase(&serializedContinuation),
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
creationTimestamp(std::chrono::steady_clock::now()),
|
||||
#endif
|
||||
serializedContinuation(serializedContinuation),
|
||||
target(target),
|
||||
invocationTarget(std::move(invocationTarget))
|
||||
{
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
std::optional<std::reference_wrapper<Qutex>> firstDuplicatedQutex =
|
||||
traceContinuationHistoryForDeadlock();
|
||||
|
||||
if (firstDuplicatedQutex.has_value())
|
||||
{
|
||||
handleDeadlock(firstDuplicatedQutex.value().get());
|
||||
throw std::runtime_error(
|
||||
"LockerAndInvoker::LockerAndInvoker(): Deadlock detected");
|
||||
}
|
||||
#endif // CONFIG_ENABLE_DEBUG_LOCKS
|
||||
|
||||
firstWake();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function call operator - tries to acquire locks and either
|
||||
* invokes the target or returns (already registered in qutex queues)
|
||||
*/
|
||||
void operator()();
|
||||
|
||||
/**
|
||||
* @brief Get the iterator for this lockvoker in the specified Qutex's queue
|
||||
* @param qutex The Qutex to get the iterator for
|
||||
* @return Iterator pointing to this lockvoker in the Qutex's queue
|
||||
*/
|
||||
LockerAndInvokerBase::List::iterator
|
||||
getLockvokerIteratorForQutex(Qutex& qutex) const override
|
||||
{
|
||||
return serializedContinuation.requiredLocks.getLockUsageDesc(
|
||||
qutex).iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Awaken this lockvoker by posting it to its io_service
|
||||
* @param forceAwaken If true, post even if already awake
|
||||
*/
|
||||
void awaken(bool forceAwaken = false) override
|
||||
{
|
||||
bool prevVal = serializedContinuation.isAwakeOrBeingAwakened
|
||||
.exchange(true);
|
||||
|
||||
if (prevVal == true && !forceAwaken)
|
||||
{ return; }
|
||||
|
||||
target->getIoService().post(*this);
|
||||
}
|
||||
|
||||
size_t getLockSetSize() const override
|
||||
{ return serializedContinuation.requiredLocks.locks.size(); }
|
||||
|
||||
Qutex& getLockAt(size_t index) const override
|
||||
{
|
||||
return serializedContinuation.requiredLocks.locks[index]
|
||||
.qutex.get();
|
||||
}
|
||||
|
||||
private:
|
||||
// Allow awakening by resetting the awake flag
|
||||
void allowAwakening()
|
||||
{ serializedContinuation.isAwakeOrBeingAwakened.store(false); }
|
||||
|
||||
/** EXPLANATION:
|
||||
* We create a copy of the Lockvoker and then give sh_ptrs to that
|
||||
* *COPY*, to each Qutex's internal queue. This enables us to keep
|
||||
* the AsyncContinuation sh_ptr (which the Lockvoker contains within
|
||||
* itself) alive without wasting too much memory.
|
||||
*
|
||||
* This way the io_service objects can remove the lockvoker from
|
||||
* their queues and there'll be a copy of the lockvoker in each
|
||||
* Qutex's queue.
|
||||
*
|
||||
* For non-serialized, posted continuations, they won't be removed
|
||||
* from the io_service queue until they're executed, so there's no
|
||||
* need to create copies of them. Lockvokers are removed from their
|
||||
* io_service, potentially without being executed if they fail to
|
||||
* acquire all locks.
|
||||
*/
|
||||
void registerInLockSet()
|
||||
{
|
||||
auto sharedLockvoker = std::make_shared<
|
||||
LockerAndInvoker<InvocationTargetT>>(*this);
|
||||
|
||||
serializedContinuation.requiredLocks.registerInQutexQueues(
|
||||
sharedLockvoker);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief First wake - register in queues and awaken
|
||||
*
|
||||
* Sets isAwake=true before calling awaken with forceAwaken to ensure
|
||||
* that none of the locks we just registered with awaken()s a duplicate
|
||||
* copy of this lockvoker on the io_service.
|
||||
*/
|
||||
void firstWake()
|
||||
{
|
||||
serializedContinuation.isAwakeOrBeingAwakened.store(true);
|
||||
registerInLockSet();
|
||||
// Force awaken since we just set the flag above
|
||||
awaken(true);
|
||||
}
|
||||
|
||||
// Has CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS elapsed since creation?
|
||||
bool isDeadlockLikely() const
|
||||
{
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - creationTimestamp);
|
||||
return elapsed.count() >= CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Wrapper around isDeadlockLikely for gridlock detection
|
||||
bool isGridlockLikely() const
|
||||
{ return isDeadlockLikely(); }
|
||||
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
struct obsolete {
|
||||
bool traceContinuationHistoryForGridlockOn(Qutex &firstFailedQutex);
|
||||
};
|
||||
|
||||
bool traceContinuationHistoryForDeadlockOn(Qutex &firstFailedQutex);
|
||||
std::optional<std::reference_wrapper<Qutex>>
|
||||
traceContinuationHistoryForDeadlock(void)
|
||||
{
|
||||
for (auto& lockUsageDesc
|
||||
: serializedContinuation.requiredLocks.locks)
|
||||
{
|
||||
if (traceContinuationHistoryForDeadlockOn(
|
||||
lockUsageDesc.qutex.get()))
|
||||
{
|
||||
return std::ref(lockUsageDesc.qutex.get());
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle a likely deadlock situation by logging debug information
|
||||
* @param firstFailedQutex The first qutex that failed acquisition
|
||||
*/
|
||||
void handleDeadlock(const Qutex &firstFailedQutex)
|
||||
{
|
||||
std::cerr << __func__ << ": Deadlock: "
|
||||
<< "Lockvoker has been waiting for "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - this->creationTimestamp)
|
||||
.count()
|
||||
<< "ms, failed on qutex @" << &firstFailedQutex
|
||||
<< " (" << firstFailedQutex.name << ")" << std::endl;
|
||||
}
|
||||
|
||||
void handleGridlock(const Qutex &firstFailedQutex)
|
||||
{
|
||||
std::cerr << __func__ << ": Gridlock: "
|
||||
<< "Lockvoker has been waiting for "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - this->creationTimestamp)
|
||||
.count()
|
||||
<< "ms, failed on qutex @" << &firstFailedQutex
|
||||
<< " (" << firstFailedQutex.name << ")" << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
std::chrono::steady_clock::time_point creationTimestamp;
|
||||
#endif
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT>
|
||||
&serializedContinuation;
|
||||
std::shared_ptr<ComponentThread> target;
|
||||
InvocationTargetT invocationTarget;
|
||||
};
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
|
||||
template <class OriginalCbFnT>
|
||||
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT>::getAcquiredQutexHistory()
|
||||
const
|
||||
{
|
||||
auto heldLocks = std::make_unique<
|
||||
std::forward_list<std::reference_wrapper<Qutex>>>();
|
||||
|
||||
/** EXPLANATION:
|
||||
* Walk through the continuation chain to collect all acquired locks
|
||||
*
|
||||
* We don't add the current continuation's locks because it's the one
|
||||
* failing to acquire locks and backing off. So we start from the previous
|
||||
* continuation.
|
||||
*/
|
||||
for (std::shared_ptr<AsynchronousContinuationChainLink> currContin =
|
||||
this->getCallersContinuationShPtr();
|
||||
currContin != nullptr;
|
||||
currContin = currContin->getCallersContinuationShPtr())
|
||||
{
|
||||
auto serializedCont = std::dynamic_pointer_cast<
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin);
|
||||
|
||||
if (serializedCont == nullptr) { continue; }
|
||||
|
||||
// Add this continuation's locks to the held locks list
|
||||
for (size_t i = 0; i < serializedCont->requiredLocks.locks.size(); ++i)
|
||||
{
|
||||
heldLocks->push_front(serializedCont->requiredLocks.locks[i].qutex);
|
||||
}
|
||||
}
|
||||
|
||||
return heldLocks;
|
||||
}
|
||||
|
||||
template <class OriginalCbFnT>
|
||||
template <class InvocationTargetT>
|
||||
bool
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT>
|
||||
::LockerAndInvoker<InvocationTargetT>
|
||||
::traceContinuationHistoryForDeadlockOn(Qutex& firstFailedQutex)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* In this function we will trace through the chain of continuations that
|
||||
* led up to this Lockvoker's continuation. For each continuation which is
|
||||
* a SerializedAsynchronousContinuation, we check through its LockSet to see
|
||||
* if it contains the lock that failed acquisition. If it does, we have a
|
||||
* deadlock.
|
||||
*/
|
||||
|
||||
/* We can't start with the continuation directly referenced by this starting
|
||||
* Lockvoker as it would contain the all locks we're currently trying to
|
||||
* acquire...and rightly so because it's the continuation for this current
|
||||
* lockvoker.
|
||||
*/
|
||||
for (std::shared_ptr<AsynchronousContinuationChainLink> currContin =
|
||||
this->serializedContinuation.getCallersContinuationShPtr();
|
||||
currContin != nullptr;
|
||||
currContin = currContin->getCallersContinuationShPtr())
|
||||
{
|
||||
auto serializedCont = std::dynamic_pointer_cast<
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin);
|
||||
|
||||
if (serializedCont == nullptr) { continue; }
|
||||
|
||||
// Check if the firstFailedQutex is in this continuation's LockSet
|
||||
try {
|
||||
serializedCont->requiredLocks.getLockUsageDesc(firstFailedQutex);
|
||||
} catch (const std::runtime_error& e) {
|
||||
std::cerr << __func__ << ": " << e.what() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout << __func__ << ":Deadlock detected: Found "
|
||||
<< "firstFailedQutex @" << &firstFailedQutex
|
||||
<< " (" << firstFailedQutex.name << ") in LockSet of "
|
||||
<< "SerializedAsynchronousContinuation @"
|
||||
<< serializedCont.get() << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class OriginalCbFnT>
|
||||
template <class InvocationTargetT>
|
||||
bool
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT>
|
||||
::LockerAndInvoker<InvocationTargetT>
|
||||
::obsolete::traceContinuationHistoryForGridlockOn(Qutex &firstFailedQutex)
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* In this function we check for gridlocks which are slightly different
|
||||
* from deadlocks. In a gridlock, two requests are waiting for locks that
|
||||
* are held by the other. I.e:
|
||||
*
|
||||
* R1 holds LockA and is waiting for LockB.
|
||||
* R2 holds LockB and is waiting for LockA.
|
||||
*
|
||||
* This differs from deadlocks because it's not a single request which is
|
||||
* attempting to re-acquire a lock that it already holds.
|
||||
*
|
||||
* To detect this condition, we wait until the acquisition timeout has
|
||||
* expired. Then: we extract the current owner of the first lock we're
|
||||
* failing to acquire.
|
||||
*
|
||||
* From there, we go through each of the locks in the foreign owner's
|
||||
* current (i.e: immediate, most recent continuation's) required LockSet.
|
||||
* For each of the locks in the foreign owner's most immediate required
|
||||
* LockSet, we trace backward in our *OWN* history to see if any of *OUR*
|
||||
* continuations (excluding our most immediate continuation) contains that
|
||||
* lock.
|
||||
*
|
||||
* If we find a match, that means that we're holding a lock that the foreign
|
||||
* owner is waiting for. And we already know that the foreign owner is
|
||||
* holding a lock that we're waiting for (when we extracted the current
|
||||
* owner of the first failed lock in our most immediate Lockset).
|
||||
*
|
||||
* Hence, we have a gridlock.
|
||||
*/
|
||||
|
||||
std::shared_ptr<LockerAndInvokerBase> foreignOwnerShPtr =
|
||||
firstFailedQutex.getCurrOwner();
|
||||
// If no current owner, can't be a gridlock
|
||||
if (foreignOwnerShPtr == nullptr)
|
||||
{ return false; }
|
||||
|
||||
// Use reference for the rest of the function for safety.
|
||||
LockerAndInvokerBase &foreignOwner = *foreignOwnerShPtr;
|
||||
|
||||
/* For each lock in the foreign owner's LockSet, check if we hold it
|
||||
* in any of our previous continuations (excluding our most immediate one)
|
||||
*/
|
||||
for (size_t i = 0; i < foreignOwner.getLockSetSize(); ++i)
|
||||
{
|
||||
Qutex& foreignLock = foreignOwner.getLockAt(i);
|
||||
|
||||
/* Skip the firstFailedQutex since we already know the foreign owner
|
||||
* holds it -- hence it's impossible for any of our previous
|
||||
* continuations to hold it.
|
||||
*/
|
||||
if (&foreignLock == &firstFailedQutex)
|
||||
{ continue; }
|
||||
|
||||
/** EXPLANATION:
|
||||
* Trace backward through our continuation history (excluding our most
|
||||
* immediate continuation).
|
||||
*
|
||||
* The reason we exclude our most immediate continuation is because the
|
||||
* LockSet acquisition algorithm backs off if it fails to acquire ALL
|
||||
* locks in the set. So if the lock that the foreign owner is waiting
|
||||
* for is in our most immediate continuation, and NOT in one of our
|
||||
* previous continuations, then we will back off and the foreign owner
|
||||
* should eventually be able to acquire that lock.
|
||||
*/
|
||||
for (std::shared_ptr<AsynchronousContinuationChainLink> currContin =
|
||||
this->serializedContinuation.getCallersContinuationShPtr();
|
||||
currContin != nullptr;
|
||||
currContin = currContin->getCallersContinuationShPtr())
|
||||
{
|
||||
auto serializedCont = std::dynamic_pointer_cast<
|
||||
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin);
|
||||
|
||||
if (serializedCont == nullptr) { continue; }
|
||||
|
||||
// Check if this continuation holds the foreign lock
|
||||
try {
|
||||
const auto& lockUsageDesc = serializedCont->requiredLocks
|
||||
.getLockUsageDesc(foreignLock);
|
||||
|
||||
// Matched! We hold a lock that the foreign owner is waiting for
|
||||
std::cout << __func__ << ": Gridlock detected: We hold lock @"
|
||||
<< &foreignLock << " (" << foreignLock.name << ") in "
|
||||
"continuation @" << serializedCont.get()
|
||||
<< ", while foreign owner @" << &foreignOwner
|
||||
<< " holds lock @" << &firstFailedQutex << " ("
|
||||
<< firstFailedQutex.name << ") that we're waiting for"
|
||||
<< std::endl;
|
||||
|
||||
return true;
|
||||
} catch (const std::runtime_error& e) {
|
||||
// This continuation doesn't hold the foreign lock. Continue.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // CONFIG_ENABLE_DEBUG_LOCKS
|
||||
|
||||
template <class OriginalCbFnT>
|
||||
template <class InvocationTargetT>
|
||||
void SerializedAsynchronousContinuation<OriginalCbFnT>
|
||||
::LockerAndInvoker<InvocationTargetT>::operator()()
|
||||
{
|
||||
if (ComponentThread::getSelf() != target)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"LockerAndInvoker::operator(): Thread safety violation - "
|
||||
"executing on wrong ComponentThread");
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<Qutex>> firstFailedQutexRet;
|
||||
bool deadlockLikely = isDeadlockLikely();
|
||||
bool gridlockLikely = isGridlockLikely();
|
||||
|
||||
if (!serializedContinuation.requiredLocks.tryAcquireOrBackOff(
|
||||
*this, firstFailedQutexRet))
|
||||
{
|
||||
// Just allow this lockvoker to be dropped from its io_service.
|
||||
allowAwakening();
|
||||
if (!deadlockLikely && !gridlockLikely)
|
||||
{ return; }
|
||||
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
Qutex &firstFailedQutex = firstFailedQutexRet.value().get();
|
||||
bool isDeadlock = traceContinuationHistoryForDeadlockOn(
|
||||
firstFailedQutex);
|
||||
|
||||
bool gridlockIsHeuristicallyLikely = false;
|
||||
bool gridlockIsAlgorithmicallyLikely = false;
|
||||
|
||||
if (gridlockLikely)
|
||||
{
|
||||
auto& tracker = QutexAcquisitionHistoryTracker
|
||||
::getInstance();
|
||||
|
||||
auto heldLocks = serializedContinuation
|
||||
.getAcquiredQutexHistory();
|
||||
|
||||
// Add this continuation to the tracker
|
||||
auto currentContinuationShPtr = serializedContinuation
|
||||
.shared_from_this();
|
||||
|
||||
tracker.addIfNotExists(
|
||||
currentContinuationShPtr,
|
||||
firstFailedQutex, std::move(heldLocks));
|
||||
|
||||
gridlockIsHeuristicallyLikely = tracker
|
||||
.heuristicallyTraceContinuationHistoryForGridlockOn(
|
||||
firstFailedQutex, currentContinuationShPtr);
|
||||
|
||||
if (gridlockIsHeuristicallyLikely)
|
||||
{
|
||||
gridlockIsAlgorithmicallyLikely = tracker
|
||||
.completelyTraceContinuationHistoryForGridlockOn(
|
||||
firstFailedQutex);
|
||||
}
|
||||
}
|
||||
|
||||
bool isGridlock = (gridlockIsHeuristicallyLikely
|
||||
|| gridlockIsAlgorithmicallyLikely);
|
||||
|
||||
if (!isDeadlock && !isGridlock)
|
||||
{ return; }
|
||||
|
||||
if (isDeadlock) { handleDeadlock(firstFailedQutex); }
|
||||
if (isGridlock) { handleGridlock(firstFailedQutex); }
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
/** EXPLANATION:
|
||||
* Successfully acquired all locks, so unregister from qutex queues.
|
||||
* We do this here so that we can free up queue slots in the qutex
|
||||
* queues for other lockvokers that may be waiting to acquire the
|
||||
* locks. The size of the qutex queues does matter for other
|
||||
* contending lockvokers; and so also does their position in the
|
||||
* queues.
|
||||
*
|
||||
* The alternative is to leave ourself in the queues until we
|
||||
* eventually release all locks; and given that we may hold locks
|
||||
* even across true async hardware bottlenecks, this could take a
|
||||
* long time.
|
||||
*
|
||||
* Granted, the fact that we own the locks means that even though
|
||||
* we've removed ourselves from the queues, other lockvokers still
|
||||
* can't acquire the locks anyway.
|
||||
*/
|
||||
serializedContinuation.requiredLocks.unregisterFromQutexQueues();
|
||||
|
||||
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
|
||||
/** EXPLANATION:
|
||||
* If we were being tracked for gridlock detection but successfully
|
||||
* acquired all locks, it was a false positive due to timed delay,
|
||||
* long-running operation, or I/O delay
|
||||
*/
|
||||
if (gridlockLikely)
|
||||
{
|
||||
std::shared_ptr<AsynchronousContinuationChainLink>
|
||||
currentContinuationShPtr =
|
||||
serializedContinuation.shared_from_this();
|
||||
|
||||
bool removed = QutexAcquisitionHistoryTracker::getInstance()
|
||||
.remove(currentContinuationShPtr);
|
||||
|
||||
if (removed)
|
||||
{
|
||||
std::cerr
|
||||
<< "LockerAndInvoker::operator(): False positive "
|
||||
"gridlock detection - continuation @"
|
||||
<< &serializedContinuation
|
||||
<< " was being tracked but successfully acquired all "
|
||||
"locks. This was likely due to timed delay, "
|
||||
"long-running operation, or I/O delay."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
invocationTarget();
|
||||
}
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // SERIALIZED_ASYNCHRONOUS_CONTINUATION_H
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef SPIN_LOCK_H
|
||||
#define SPIN_LOCK_H
|
||||
|
||||
#include <atomic>
|
||||
#ifdef __x86_64__
|
||||
#include <immintrin.h>
|
||||
#elif defined(__i386__)
|
||||
#include <xmmintrin.h>
|
||||
#elif defined(__arm__)
|
||||
#include <arm_neon.h>
|
||||
#elif defined(__aarch64__)
|
||||
#include <arm_neon.h>
|
||||
#elif defined(__aarch32__)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
namespace smo {
|
||||
|
||||
/**
|
||||
* @brief Simple spinlock using std::atomic
|
||||
*/
|
||||
class SpinLock
|
||||
{
|
||||
public:
|
||||
SpinLock()
|
||||
: locked(false)
|
||||
{}
|
||||
|
||||
bool tryAcquire()
|
||||
{
|
||||
bool expected = false;
|
||||
return locked.compare_exchange_strong(expected, true);
|
||||
}
|
||||
|
||||
inline void spinPause()
|
||||
{
|
||||
#ifdef __x86_64__
|
||||
_mm_pause();
|
||||
#elif defined(__i386__)
|
||||
_mm_pause();
|
||||
#elif defined(__arm__)
|
||||
__asm__ volatile("yield");
|
||||
#elif defined(__aarch64__)
|
||||
__asm__ volatile("yield");
|
||||
#elif defined(__aarch32__)
|
||||
__asm__ volatile("yield");
|
||||
#else
|
||||
# error "Unsupported architecture"
|
||||
#endif
|
||||
}
|
||||
|
||||
void acquire()
|
||||
{
|
||||
while (!tryAcquire())
|
||||
{
|
||||
/** EXPLANATION:
|
||||
* Busy-wait: keep trying to acquire the lock
|
||||
* The CPU will spin here until the lock becomes available
|
||||
*
|
||||
* The spinPause() function is architecture-specific and is
|
||||
* essential because I once fried an older Intel M-class laptop CPU
|
||||
* when I forgot to include a PAUSE instruction in a for (;;){}
|
||||
* loop. I'm not interested in frying my RPi or my other testbed
|
||||
* robot boards.
|
||||
*/
|
||||
spinPause();
|
||||
}
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
locked.store(false);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> locked;
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // SPIN_LOCK_H
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace smo {
|
||||
namespace device {
|
||||
@@ -65,6 +67,40 @@ public:
|
||||
|
||||
return os.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse a required integer parameter from provider parameters
|
||||
* @param spec The device attachment specification
|
||||
* @param paramName The name of the parameter to parse
|
||||
* @return The parsed integer value
|
||||
* @throws std::runtime_error if parameter is not found or cannot be parsed
|
||||
*/
|
||||
static int parseRequiredParamAsInt(
|
||||
const DeviceAttachmentSpec& spec, const std::string& paramName
|
||||
)
|
||||
{
|
||||
auto it = std::find_if(
|
||||
spec.providerParams.begin(),
|
||||
spec.providerParams.end(),
|
||||
[¶mName](const auto& param) {
|
||||
return param.first == paramName;
|
||||
}
|
||||
);
|
||||
|
||||
if (it == spec.providerParams.end())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"No " + paramName + " specified in provider params");
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stoi(it->second);
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(
|
||||
"Failed to parse '" + paramName + "' param value '"
|
||||
+ it->second + "' as integer: " + e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class InteroceptorDevAttachmentSpec : public DeviceAttachmentSpec
|
||||
|
||||
@@ -6,17 +6,49 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <preprocessor.h>
|
||||
#include <componentThread.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
|
||||
/**
|
||||
* @brief Threading model descriptor for senseApi libraries.
|
||||
*
|
||||
* This structure provides senseApi libraries with access to the information and
|
||||
* resources they need to operate with SMO's threading model.
|
||||
*/
|
||||
struct SmoThreadingModelDesc
|
||||
{
|
||||
/**
|
||||
* @brief sh_ptr to ComponentThread for device-independent state mgt.
|
||||
*
|
||||
* This ComponentThread should be used by senseApis for state management
|
||||
* that's independent of any particular device or attachment spec.
|
||||
* SMO will usually pass in the Marionette thread here.
|
||||
*
|
||||
* State management that's tied to a particular attachment spec should be
|
||||
* done on the ComponentThread for the thread that SMO provided in the
|
||||
* attachDeviceReq call.
|
||||
*/
|
||||
std::shared_ptr<ComponentThread> componentThread;
|
||||
};
|
||||
|
||||
typedef std::function<void(bool, std::shared_ptr<device::DeviceAttachmentSpec>)>
|
||||
sal_mlo_attachDeviceReqCbFn;
|
||||
typedef std::function<void(bool, std::shared_ptr<device::DeviceAttachmentSpec>)>
|
||||
sal_mlo_detachDeviceReqCbFn;
|
||||
|
||||
typedef int (sal_mlo_initializeIndFn)(void);
|
||||
typedef int (sal_mlo_finalizeIndFn)(void);
|
||||
typedef int (sal_mlo_attachDeviceReqFn)(
|
||||
const std::shared_ptr<device::DeviceAttachmentSpec>& desc);
|
||||
typedef int (sal_mlo_detachDeviceReqFn)(
|
||||
const std::shared_ptr<device::DeviceAttachmentSpec>& desc);
|
||||
typedef void (sal_mlo_attachDeviceReqFn)(
|
||||
const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
|
||||
const std::shared_ptr<ComponentThread>& componentThread,
|
||||
Callback<sal_mlo_attachDeviceReqCbFn> cb);
|
||||
typedef void (sal_mlo_detachDeviceReqFn)(
|
||||
const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
|
||||
Callback<sal_mlo_detachDeviceReqCbFn> cb);
|
||||
|
||||
/**
|
||||
* @brief Hooks provided by Salmanoff to senseApi libraries.
|
||||
@@ -24,7 +56,7 @@ typedef int (sal_mlo_detachDeviceReqFn)(
|
||||
* This structure contains function pointers that senseApi libraries can use
|
||||
* to interact with Salmanoff's functionality, such as searching for commonLibs.
|
||||
*/
|
||||
struct SalmanoffCallbacks
|
||||
struct SmoCallbacks
|
||||
{
|
||||
/**
|
||||
* @brief Search for a library in Salmanoff's search paths
|
||||
@@ -37,6 +69,15 @@ struct SalmanoffCallbacks
|
||||
*/
|
||||
std::optional<std::string> (*searchForLibInSmoSearchPaths)(
|
||||
const std::string& libraryPath);
|
||||
|
||||
/**
|
||||
* @brief Get the current ComponentThread instance
|
||||
* @return Shared pointer to the current ComponentThread
|
||||
*
|
||||
* This function provides access to the current ComponentThread instance,
|
||||
* equivalent to calling ComponentThread::getSelf().
|
||||
*/
|
||||
std::shared_ptr<ComponentThread> (*ComponentThread_getSelf)(void);
|
||||
};
|
||||
|
||||
struct Sal_Mgmt_LibOps
|
||||
@@ -132,11 +173,14 @@ public:
|
||||
* The SenseApiDesc struct also gives Smo pointers to API functions
|
||||
* to invoke for communication between Smo and the library.
|
||||
*
|
||||
* The SalmanoffCallbacks parameter provides the library with access to
|
||||
* The SmoCallbacks parameter provides the library with access to
|
||||
* Salmanoff's hooks.
|
||||
* The SmoThreadingModelDesc parameter provides the library with access to
|
||||
* the io_service for network operations and event handling.
|
||||
*/
|
||||
typedef const SenseApiDesc &(SMO_GET_SENSE_API_DESC_FN_TYPEDEF)(
|
||||
const SalmanoffCallbacks& callbacks);
|
||||
const SmoCallbacks& callbacks,
|
||||
const SmoThreadingModelDesc& threadingModel);
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace smo
|
||||
|
||||
@@ -10,17 +10,17 @@ int main(int argc, char *argv[], char *envp[])
|
||||
*/
|
||||
std::cout << "CRT:" << __func__ << ": about to JOLT Mrntt with cmdline args"
|
||||
<< '\n';
|
||||
smo::mrntt::mrntt->getIoService().post(
|
||||
smo::mrntt::thread->getIoService().post(
|
||||
[argc, argv, envp]()
|
||||
{
|
||||
std::cout << "Mrntt:" << __func__ << ":JOLTED: setting cmdline args"
|
||||
<< '\n';
|
||||
smo::CrtCommandLineArgs::set(argc, argv, envp);
|
||||
smo::mrntt::mrntt->getIoService().stop();
|
||||
smo::mrntt::thread->getIoService().stop();
|
||||
}
|
||||
);
|
||||
|
||||
smo::mrntt::mrntt->thread.join();
|
||||
smo::mrntt::thread->thread.join();
|
||||
std::cout << "CRT:" << __func__ << ": Mrntt exited with code '"
|
||||
<< smo::mrntt::exitCode << "'\n";
|
||||
return smo::mrntt::exitCode;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Simple script to show just the C/C++ line count summary
|
||||
|
||||
# Change to project root directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Count lines
|
||||
CPP_LINES=$(find . -name "*.cpp" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}' || echo "0")
|
||||
H_LINES=$(find . -name "*.h" -o -name "*.hpp" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}' || echo "0")
|
||||
C_LINES=$(find . -name "*.c" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}' || echo "0")
|
||||
|
||||
TOTAL=$((CPP_LINES + H_LINES + C_LINES))
|
||||
|
||||
echo "Salmanoff Project C/C++ Lines: $TOTAL"
|
||||
echo " C++ Source: $CPP_LINES"
|
||||
echo " Headers: $H_LINES"
|
||||
echo " C Source: $C_LINES"
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to count C/C++ lines of code in the salmanoff project
|
||||
# Excludes third-party dependencies, build artifacts, and generated files
|
||||
|
||||
# set -e # Commented out to prevent early exit on empty file lists
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}=== Salmanoff Project C/C++ Line Count ===${NC}"
|
||||
echo
|
||||
|
||||
# Change to project root directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo -e "${YELLOW}Project root: $PROJECT_ROOT${NC}"
|
||||
echo
|
||||
|
||||
# Find all C/C++ files, excluding third-party and build artifacts
|
||||
CPP_FILES=$(find . -name "*.cpp" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles)
|
||||
H_FILES=$(find . -name "*.h" -o -name "*.hpp" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles)
|
||||
C_FILES=$(find . -name "*.c" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles)
|
||||
|
||||
# Count lines for each file type
|
||||
echo -e "${BLUE}=== C++ Source Files (.cpp) ===${NC}"
|
||||
CPP_LINES=0
|
||||
if [ -n "$CPP_FILES" ]; then
|
||||
echo "$CPP_FILES" | xargs wc -l
|
||||
CPP_LINES=$(echo "$CPP_FILES" | xargs wc -l | tail -1 | awk '{print $1}')
|
||||
else
|
||||
echo "No C++ source files found"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo -e "${BLUE}=== Header Files (.h, .hpp) ===${NC}"
|
||||
H_LINES=0
|
||||
if [ -n "$H_FILES" ]; then
|
||||
echo "$H_FILES" | xargs wc -l
|
||||
H_LINES=$(echo "$H_FILES" | xargs wc -l | tail -1 | awk '{print $1}')
|
||||
else
|
||||
echo "No header files found"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo -e "${BLUE}=== C Source Files (.c) ===${NC}"
|
||||
C_LINES=0
|
||||
if [ -n "$C_FILES" ]; then
|
||||
echo "$C_FILES" | xargs wc -l
|
||||
C_LINES=$(echo "$C_FILES" | xargs wc -l | tail -1 | awk '{print $1}')
|
||||
else
|
||||
echo "No C source files found"
|
||||
fi
|
||||
echo
|
||||
|
||||
# Calculate total
|
||||
TOTAL_LINES=$((CPP_LINES + H_LINES + C_LINES))
|
||||
|
||||
echo -e "${GREEN}=== SUMMARY ===${NC}"
|
||||
echo -e "C++ Source Files (.cpp): ${YELLOW}$CPP_LINES${NC} lines"
|
||||
echo -e "Header Files (.h/.hpp): ${YELLOW}$H_LINES${NC} lines"
|
||||
echo -e "C Source Files (.c): ${YELLOW}$C_LINES${NC} lines"
|
||||
echo -e "${GREEN}─────────────────────────${NC}"
|
||||
echo -e "${GREEN}Total C/C++ Lines: ${YELLOW}$TOTAL_LINES${NC} lines${NC}"
|
||||
echo
|
||||
|
||||
# Show what's excluded
|
||||
echo -e "${BLUE}=== Excluded from count: ===${NC}"
|
||||
echo "• third_party/ directory (googletest, etc.)"
|
||||
echo "• build*/ directories (build artifacts)"
|
||||
echo "• b/ directories (build artifacts)"
|
||||
echo "• Livox-sdk-git/ directory (third-party SDK)"
|
||||
echo "• CMakeFiles/ directories (generated files)"
|
||||
echo "• Generated files (*.d, *.o, etc.)"
|
||||
echo
|
||||
|
||||
# Optional: Show file count breakdown by directory
|
||||
echo -e "${BLUE}=== Files by directory: ===${NC}"
|
||||
echo "C++ Source Files:"
|
||||
echo "$CPP_FILES" | sed 's|^\./||' | cut -d'/' -f1 | sort | uniq -c | sort -nr | head -10
|
||||
|
||||
echo
|
||||
echo "Header Files:"
|
||||
echo "$H_FILES" | sed 's|^\./||' | cut -d'/' -f1 | sort | uniq -c | sort -nr | head -10
|
||||
@@ -1 +1,2 @@
|
||||
add_subdirectory(xcbWindow)
|
||||
add_subdirectory(livoxGen1)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
cmake_dependent_option(ENABLE_SENSEAPI_livoxGen1
|
||||
"Enable Livox Gen1 LiDAR sense API" ON
|
||||
"ENABLE_LIB_livoxProto1" OFF)
|
||||
|
||||
if(ENABLE_SENSEAPI_livoxGen1)
|
||||
add_library(livoxGen1 SHARED
|
||||
livoxGen1.cpp
|
||||
)
|
||||
|
||||
# Set config define for header generation
|
||||
add_compile_definitions(CONFIG_SENSEAPI_LIVOXGEN1_ENABLED)
|
||||
target_include_directories(livoxGen1 PUBLIC
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${CMAKE_SOURCE_DIR}/commonLibs
|
||||
)
|
||||
target_link_libraries(livoxGen1
|
||||
${Boost_LIBRARIES}
|
||||
)
|
||||
|
||||
# Install rules
|
||||
install(TARGETS livoxGen1 DESTINATION lib)
|
||||
endif()
|
||||
@@ -0,0 +1,416 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <dlfcn.h>
|
||||
#include <opts.h>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
#include <livoxProto1/livoxProto1.h>
|
||||
#include <livoxProto1/device.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
|
||||
// Salmanoff hooks, obtained from SMO_GET_SENSE_API_DESC_FN_NAME().
|
||||
static const SmoCallbacks* smoHooksPtr = nullptr;
|
||||
static SmoThreadingModelDesc smoThreadingModelDesc;
|
||||
|
||||
// LivoxProto1 library state
|
||||
struct LivoxProto1DllState
|
||||
{
|
||||
LivoxProto1DllState()
|
||||
: dlopenHandle(nullptr, DlCloser),
|
||||
livoxProto1_main(nullptr),
|
||||
livoxProto1_exit(nullptr),
|
||||
livoxProto1_getOrCreateDeviceReq(nullptr),
|
||||
livoxProto1_destroyDeviceReq(nullptr)
|
||||
{}
|
||||
|
||||
static void DlCloser(void* handle)
|
||||
{
|
||||
if (handle) {
|
||||
dlclose(handle);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<void, void(*)(void*)> dlopenHandle;
|
||||
livoxProto1_mainFn *livoxProto1_main;
|
||||
livoxProto1_exitFn *livoxProto1_exit;
|
||||
livoxProto1_getOrCreateDeviceReqFn *livoxProto1_getOrCreateDeviceReq;
|
||||
livoxProto1_destroyDeviceReqFn *livoxProto1_destroyDeviceReq;
|
||||
};
|
||||
|
||||
static LivoxProto1DllState livoxProto1;
|
||||
|
||||
// Attached Livox devices
|
||||
static std::vector<std::shared_ptr<livoxProto1::Device>> g_attachedDevices;
|
||||
|
||||
// Continuation classes for async operations
|
||||
class AttachDeviceReq
|
||||
: public smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
AttachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
|
||||
smo::Callback<sal_mlo_attachDeviceReqCbFn> cb)
|
||||
: smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>(
|
||||
std::move(cb)),
|
||||
spec(spec)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
|
||||
|
||||
public:
|
||||
void attachDeviceReq1(
|
||||
std::shared_ptr<AttachDeviceReq> context,
|
||||
bool success, std::shared_ptr<livoxProto1::Device> dev)
|
||||
{
|
||||
if (!dev)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to create Livox device: "
|
||||
<< context->spec->deviceSelector << std::endl;
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
g_attachedDevices.push_back(dev);
|
||||
if (1 || OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": Successfully attached Livox "
|
||||
"device: " << context->spec->deviceSelector << " (ID: "
|
||||
<< context->spec->deviceIdentifier << ")\n";
|
||||
}
|
||||
|
||||
context->callOriginalCb(success, context->spec);
|
||||
}
|
||||
};
|
||||
|
||||
class DetachDeviceReq
|
||||
: public smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
DetachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
|
||||
smo::Callback<sal_mlo_detachDeviceReqCbFn> cb)
|
||||
: smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>(
|
||||
std::move(cb)),
|
||||
spec(spec)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
|
||||
|
||||
public:
|
||||
void detachDeviceReq1(
|
||||
std::shared_ptr<DetachDeviceReq> context,
|
||||
bool success)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to destroy Livox device: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the device in g_attachedDevices and remove it.
|
||||
auto eraseIt = std::find_if(
|
||||
g_attachedDevices.begin(), g_attachedDevices.end(),
|
||||
[context](const std::shared_ptr<livoxProto1::Device>& dev)
|
||||
{
|
||||
const std::string& devId = dev->discoveredDevice.deviceIdentifier;
|
||||
std::string devIdPrefix = devId.substr(
|
||||
0, std::min<size_t>(14, devId.size()));
|
||||
return devIdPrefix == context->spec->deviceSelector.substr(
|
||||
0, std::min<size_t>(14, context->spec->deviceSelector.size()));
|
||||
}
|
||||
);
|
||||
|
||||
if (eraseIt == g_attachedDevices.end())
|
||||
{
|
||||
std::cerr << __func__ << ": Race condition: device not found "
|
||||
"in g_attachedDevices for detachment: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
g_attachedDevices.erase(eraseIt);
|
||||
std::cout << __func__ << ": Successfully detached Livox device: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
|
||||
context->callOriginalCb(success, context->spec);
|
||||
}
|
||||
};
|
||||
|
||||
// Callback function declarations
|
||||
extern "C" sal_mlo_initializeIndFn livoxGen1_initializeInd;
|
||||
extern "C" sal_mlo_finalizeIndFn livoxGen1_finalizeInd;
|
||||
extern "C" sal_mlo_attachDeviceReqFn livoxGen1_attachDeviceReq;
|
||||
extern "C" sal_mlo_detachDeviceReqFn livoxGen1_detachDeviceReq;
|
||||
|
||||
// Sense API descriptor
|
||||
static const SenseApiDesc livoxGen1ApiDesc = {
|
||||
.name = "livoxGen1",
|
||||
.exportedImplexorApis = {
|
||||
{.name = "pointCloudCoords"},
|
||||
{.name = "pointCloudIntensity"},
|
||||
{.name = "gyro"},
|
||||
{.name = "accel"}
|
||||
},
|
||||
.sal_mgmt_libOps = {
|
||||
.initializeInd = livoxGen1_initializeInd,
|
||||
.finalizeInd = livoxGen1_finalizeInd,
|
||||
.attachDeviceReq = livoxGen1_attachDeviceReq,
|
||||
.detachDeviceReq = livoxGen1_detachDeviceReq
|
||||
}
|
||||
};
|
||||
|
||||
// Callback function implementations
|
||||
extern "C" int livoxGen1_initializeInd(void)
|
||||
{
|
||||
if (!smoHooksPtr)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__) + ": SMO hooks "
|
||||
"pointers not filled in.");
|
||||
}
|
||||
|
||||
// Load LivoxProto1 library
|
||||
auto libPath = smoHooksPtr->searchForLibInSmoSearchPaths(
|
||||
"liblivoxProto1.so");
|
||||
|
||||
livoxProto1.dlopenHandle.reset(dlopen(
|
||||
libPath.value_or("liblivoxProto1.so").c_str(), RTLD_LAZY));
|
||||
|
||||
if (!livoxProto1.dlopenHandle)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": Failed to load LivoxProto1 library: " +
|
||||
(dlerror() ? dlerror() : "unknown error"));
|
||||
}
|
||||
|
||||
// Get LivoxProto1 library functions
|
||||
livoxProto1.livoxProto1_main = reinterpret_cast<livoxProto1_mainFn *>(
|
||||
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_main"));
|
||||
livoxProto1.livoxProto1_exit = reinterpret_cast<livoxProto1_exitFn *>(
|
||||
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_exit"));
|
||||
livoxProto1.livoxProto1_getOrCreateDeviceReq = reinterpret_cast<
|
||||
livoxProto1_getOrCreateDeviceReqFn *>(
|
||||
dlsym(
|
||||
livoxProto1.dlopenHandle.get(),
|
||||
"livoxProto1_getOrCreateDeviceReq"));
|
||||
livoxProto1.livoxProto1_destroyDeviceReq = reinterpret_cast<
|
||||
livoxProto1_destroyDeviceReqFn *>(
|
||||
dlsym(
|
||||
livoxProto1.dlopenHandle.get(),
|
||||
"livoxProto1_destroyDeviceReq"));
|
||||
|
||||
if (!livoxProto1.livoxProto1_main || !livoxProto1.livoxProto1_exit
|
||||
|| !livoxProto1.livoxProto1_getOrCreateDeviceReq
|
||||
|| !livoxProto1.livoxProto1_destroyDeviceReq)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": Failed to get LivoxProto1 library functions");
|
||||
}
|
||||
|
||||
// Call LivoxProto1 library main function
|
||||
(*livoxProto1.livoxProto1_main)(
|
||||
smoThreadingModelDesc.componentThread, *smoHooksPtr);
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
extern "C" int livoxGen1_finalizeInd(void)
|
||||
{
|
||||
// Clear all attached devices
|
||||
g_attachedDevices.clear();
|
||||
|
||||
// Call LivoxProto1 library exit function
|
||||
if (livoxProto1.livoxProto1_exit) {
|
||||
(*livoxProto1.livoxProto1_exit)();
|
||||
}
|
||||
|
||||
if (livoxProto1.dlopenHandle)
|
||||
{
|
||||
dlclose(livoxProto1.dlopenHandle.get());
|
||||
livoxProto1.dlopenHandle.reset();
|
||||
}
|
||||
|
||||
livoxProto1 = LivoxProto1DllState();
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
extern "C" void livoxGen1_attachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
Callback<smo::sense_api::sal_mlo_attachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
if (!livoxProto1.livoxProto1_getOrCreateDeviceReq)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": LivoxProto1 getOrCreateDevice function "
|
||||
"not available");
|
||||
}
|
||||
|
||||
for (const auto& dev : g_attachedDevices)
|
||||
{
|
||||
if (dev->discoveredDevice.deviceIdentifier == desc->deviceIdentifier)
|
||||
{
|
||||
cb.callbackFn(true, desc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse integer parameters from provider params with defaults
|
||||
/* The Livox Avia will generally respond to a handshake request within
|
||||
* 50ms. So we set the handshake timeout to 300ms to be safe.
|
||||
*/
|
||||
int handshakeTimeoutMs = 300; // Default: 50ms
|
||||
/* Based on testing on a Livox Avia, the device will generally resume
|
||||
* sending broadcast advertisement dgrams after about 5 seconds at most.
|
||||
* Generally, it will resume sending them within 1-2 seconds.
|
||||
*/
|
||||
int retryDelayMs = 5250; // Default: 500ms
|
||||
uint8_t smoSubnetNbits = 24; // Default: /24 subnet
|
||||
uint16_t dataPort = 56000; // Default data port
|
||||
uint16_t cmdPort = 56001; // Default command port
|
||||
uint16_t imuPort = 56002; // Default IMU port
|
||||
// Default: empty string (will trigger IP auto-detection)
|
||||
std::string smoIp = "";
|
||||
|
||||
// Parse optional integer parameters from provider params
|
||||
for (const auto& param : desc->providerParams)
|
||||
{
|
||||
if (param.first == "handshake-timeout-ms")
|
||||
{
|
||||
handshakeTimeoutMs = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "handshake-timeout-ms");
|
||||
} else if (param.first == "retry-delay-ms")
|
||||
{
|
||||
retryDelayMs = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "retry-delay-ms");
|
||||
} else if (param.first == "smo-subnet-nbits")
|
||||
{
|
||||
smoSubnetNbits = static_cast<uint8_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "smo-subnet-nbits"));
|
||||
} else if (param.first == "data-port")
|
||||
{
|
||||
dataPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "data-port"));
|
||||
} else if (param.first == "cmd-port")
|
||||
{
|
||||
cmdPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "cmd-port"));
|
||||
} else if (param.first == "imu-port")
|
||||
{
|
||||
imuPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "imu-port"));
|
||||
} else if (param.first == "smo-ip")
|
||||
{
|
||||
if (param.second.empty())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": smo-ip parameter is empty");
|
||||
}
|
||||
if (param.second.find('.') == std::string::npos ||
|
||||
std::count(param.second.begin(), param.second.end(), '.') != 3)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": smo-ip parameter is not an "
|
||||
"IPv4 address");
|
||||
}
|
||||
smoIp = param.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Unknown provider parameter: "
|
||||
+ param.first);
|
||||
}
|
||||
}
|
||||
|
||||
auto request = std::make_shared<AttachDeviceReq>(desc, cb);
|
||||
|
||||
(*livoxProto1.livoxProto1_getOrCreateDeviceReq)(
|
||||
desc->deviceSelector, // deviceIdentifier (broadcast code)
|
||||
componentThread,
|
||||
handshakeTimeoutMs, retryDelayMs,
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort,
|
||||
{request, std::bind(
|
||||
&AttachDeviceReq::attachDeviceReq1,
|
||||
request.get(), request,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
|
||||
extern "C" void livoxGen1_detachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
|
||||
Callback<smo::sense_api::sal_mlo_detachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
// Find and remove the device from our collection
|
||||
auto it = std::find_if(g_attachedDevices.begin(), g_attachedDevices.end(),
|
||||
[&desc](const std::shared_ptr<livoxProto1::Device>& dev) {
|
||||
/** EXPLANATION:
|
||||
* Compare the first 14 characters of the deviceIdentifier with
|
||||
* the first 14 characters of the deviceSelector
|
||||
*/
|
||||
const std::string& devId = dev->discoveredDevice.deviceIdentifier;
|
||||
std::string devIdPrefix = devId.substr(
|
||||
0, std::min<size_t>(14, devId.size()));
|
||||
|
||||
return devIdPrefix == desc->deviceSelector.substr(
|
||||
0, std::min<size_t>(14, desc->deviceSelector.size()));
|
||||
}
|
||||
);
|
||||
|
||||
if (it == g_attachedDevices.end())
|
||||
{
|
||||
std::cerr << std::string(__func__)
|
||||
<< ": Device not found for detachment: "
|
||||
<< desc->deviceIdentifier << std::endl;
|
||||
cb.callbackFn(false, desc);
|
||||
return;
|
||||
}
|
||||
|
||||
auto request = std::make_shared<DetachDeviceReq>(desc, cb);
|
||||
|
||||
(*livoxProto1.livoxProto1_destroyDeviceReq)(
|
||||
*it,
|
||||
{request, std::bind(
|
||||
&DetachDeviceReq::detachDeviceReq1,
|
||||
request.get(), request,
|
||||
std::placeholders::_1)});
|
||||
}
|
||||
|
||||
// Exported function
|
||||
extern "C" smo::sense_api::SMO_GET_SENSE_API_DESC_FN_TYPEDEF
|
||||
SMO_GET_SENSE_API_DESC_FN_NAME;
|
||||
|
||||
const smo::sense_api::SenseApiDesc& SMO_GET_SENSE_API_DESC_FN_NAME(
|
||||
const smo::sense_api::SmoCallbacks& callbacks,
|
||||
const smo::sense_api::SmoThreadingModelDesc& threadingModel)
|
||||
{
|
||||
smoHooksPtr = &callbacks;
|
||||
smoThreadingModelDesc = threadingModel;
|
||||
|
||||
return livoxGen1ApiDesc;
|
||||
}
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace smo
|
||||
@@ -1,7 +1,7 @@
|
||||
# XCB/Xorg Window Attaching SenseAPI backend
|
||||
cmake_dependent_option(ENABLE_SENSEAPI_xcbWindow
|
||||
"Enable XCB/Xorg Window Attaching SenseAPI backend" ON
|
||||
"ENABLE_LIB_xcbXorg" ON)
|
||||
"ENABLE_LIB_xcbXorg" OFF)
|
||||
|
||||
if(ENABLE_SENSEAPI_xcbWindow)
|
||||
add_library(xcbWindow SHARED
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xcb/xcb.h>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
#include <xcbXorg/xcbXorg.h>
|
||||
#include "xcbWindow.h"
|
||||
|
||||
@@ -28,7 +29,8 @@ struct XcbXorgDllState
|
||||
static XcbXorgDllState xcbXorg;
|
||||
|
||||
// Salmanoff hooks, obtained from SMO_GET_SENSE_API_DESC_FN_NAME().
|
||||
static const smo::sense_api::SalmanoffCallbacks* smoHooksPtr = nullptr;
|
||||
static const smo::sense_api::SmoCallbacks* smoHooksPtr = nullptr;
|
||||
static smo::sense_api::SmoThreadingModelDesc smoThreadingModelDesc;
|
||||
|
||||
// Attached windows.
|
||||
static std::vector<std::unique_ptr<xcb_window::AttachedWindow>>
|
||||
@@ -68,8 +70,11 @@ AttachedWindow::AttachedWindow(
|
||||
": Required xcbXorg function pointers not available");
|
||||
}
|
||||
|
||||
windowSelector.display = getRequiredParamAsInt(*spec, "display");
|
||||
windowSelector.screen = getRequiredParamAsInt(*spec, "screen");
|
||||
windowSelector.display = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*spec, "display");
|
||||
windowSelector.screen = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*spec, "screen");
|
||||
|
||||
parseWindowSelector(*spec);
|
||||
|
||||
// Get connection from libxcbXorg
|
||||
@@ -148,32 +153,6 @@ void AttachedWindow::parseWindowSelector(
|
||||
}
|
||||
}
|
||||
|
||||
int AttachedWindow::getRequiredParamAsInt(const smo::device::DeviceAttachmentSpec& spec,
|
||||
const std::string& paramName)
|
||||
{
|
||||
auto it = std::find_if(
|
||||
spec.providerParams.begin(),
|
||||
spec.providerParams.end(),
|
||||
[¶mName](const auto& param) {
|
||||
return param.first == paramName;
|
||||
}
|
||||
);
|
||||
|
||||
if (it == spec.providerParams.end())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"No " + paramName + " specified in provider params");
|
||||
}
|
||||
|
||||
try {
|
||||
return std::stoi(it->second);
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(
|
||||
"Failed to parse '" + paramName + "' param value '"
|
||||
+ it->second + "' as integer: " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::string AttachedWindow::stringify() const {
|
||||
std::ostringstream os;
|
||||
|
||||
@@ -295,21 +274,35 @@ static int xcbWindow_finalizeInd(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xcbWindow_attachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc
|
||||
static void xcbWindow_attachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
smo::Callback<smo::sense_api::sal_mlo_attachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
// Not used yet, but may be used later.
|
||||
(void)componentThread;
|
||||
|
||||
try {
|
||||
g_attachedWindows.emplace_back(
|
||||
std::make_unique<xcb_window::AttachedWindow>(desc));
|
||||
} catch (const std::exception& exc) {
|
||||
std::cerr << __func__ << ": Exception while attaching X11 window: "
|
||||
<< exc.what() << "\n";
|
||||
cb.callbackFn(false, desc);
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << __func__ << ": Attached X11 window:\n "
|
||||
<< g_attachedWindows.back()->stringify()
|
||||
<< "\n";
|
||||
return 0;
|
||||
|
||||
cb.callbackFn(true, desc);
|
||||
}
|
||||
|
||||
static int xcbWindow_detachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec
|
||||
static void xcbWindow_detachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
|
||||
smo::Callback<smo::sense_api::sal_mlo_detachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
auto it = std::find_if(g_attachedWindows.begin(), g_attachedWindows.end(),
|
||||
@@ -322,13 +315,16 @@ static int xcbWindow_detachDeviceReq(
|
||||
{
|
||||
std::cerr << __func__ << ": Device not found for detachment:\n"
|
||||
<< spec->stringify() << "\n";
|
||||
return -1;
|
||||
|
||||
cb.callbackFn(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
g_attachedWindows.erase(it);
|
||||
std::cout << __func__ << ": Detached X11 window device:\n"
|
||||
<< spec->stringify() << "\n";
|
||||
return 0;
|
||||
|
||||
cb.callbackFn(true, spec);
|
||||
}
|
||||
|
||||
// SenseApi descriptor
|
||||
@@ -348,8 +344,10 @@ extern "C" smo::sense_api::SMO_GET_SENSE_API_DESC_FN_TYPEDEF
|
||||
SMO_GET_SENSE_API_DESC_FN_NAME;
|
||||
|
||||
const smo::sense_api::SenseApiDesc& SMO_GET_SENSE_API_DESC_FN_NAME(
|
||||
const smo::sense_api::SalmanoffCallbacks& callbacks)
|
||||
const smo::sense_api::SmoCallbacks& callbacks,
|
||||
const smo::sense_api::SmoThreadingModelDesc& threadingModel)
|
||||
{
|
||||
smoHooksPtr = &callbacks;
|
||||
smoThreadingModelDesc = threadingModel;
|
||||
return xcbWindowApiDesc;
|
||||
}
|
||||
|
||||
@@ -44,9 +44,6 @@ public:
|
||||
|
||||
private:
|
||||
void parseWindowSelector(const smo::device::DeviceAttachmentSpec& spec);
|
||||
int getRequiredParamAsInt(
|
||||
const smo::device::DeviceAttachmentSpec& spec,
|
||||
const std::string& paramName);
|
||||
|
||||
std::shared_ptr<smo::device::DeviceAttachmentSpec> deviceAttachmentSpec;
|
||||
WindowSelector windowSelector;
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
# Core library
|
||||
# Include Flex/Bison generation rules
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/flexYacc.cmake)
|
||||
|
||||
# Consolidated smocore library with all source files
|
||||
add_library(smocore STATIC
|
||||
# Core files
|
||||
mind.cpp
|
||||
opts.cpp
|
||||
componentThread.cpp
|
||||
component.cpp
|
||||
painfulQuale.cpp
|
||||
qutex.cpp
|
||||
lockerAndInvokerBase.cpp
|
||||
|
||||
# Body
|
||||
body/body.cpp
|
||||
|
||||
# Marionette
|
||||
marionette/main.cpp
|
||||
marionette/salmanoff.cpp
|
||||
marionette/lifetime.cpp
|
||||
marionette/qualeEvent.cpp
|
||||
|
||||
# DeviceManager
|
||||
deviceManager/deviceManager.cpp
|
||||
deviceManager/deviceReattacher.cpp
|
||||
deviceManager/deviceAttachmentPipeSpecParser.cpp
|
||||
${LEX_OUTPUT}
|
||||
${YACC_OUTPUT}
|
||||
|
||||
# SenseApis
|
||||
senseApis/senseApiManager.cpp
|
||||
|
||||
# MindManager
|
||||
mindManager/mindManager.cpp
|
||||
)
|
||||
|
||||
# Conditionally add qutexAcquisitionHistoryTracker.cpp only when debug locks are enabled
|
||||
if(ENABLE_DEBUG_LOCKS)
|
||||
target_sources(smocore PRIVATE qutexAcquisitionHistoryTracker.cpp)
|
||||
endif()
|
||||
|
||||
target_include_directories(smocore PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Link against pthread for CPU affinity functions
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(smocore PRIVATE Threads::Threads)
|
||||
|
||||
add_subdirectory(marionette)
|
||||
add_subdirectory(deviceManager)
|
||||
add_subdirectory(senseApis)
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
#include <iostream>
|
||||
#include <opts.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <callback.h>
|
||||
#include <body/body.h>
|
||||
#include <componentThread.h>
|
||||
#include <mind.h>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
|
||||
namespace smo {
|
||||
namespace body {
|
||||
|
||||
Body::Body(Mind &parent, const std::shared_ptr<ComponentThread> &thread)
|
||||
: MindComponent(parent, thread)
|
||||
{
|
||||
}
|
||||
|
||||
class Body::InitializeReq
|
||||
: public PostedAsynchronousContinuation<bodyLifetimeMgmtOpCbFn>
|
||||
{
|
||||
public:
|
||||
InitializeReq(
|
||||
Mind &parent, const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<bodyLifetimeMgmtOpCbFn> callback)
|
||||
: PostedAsynchronousContinuation<bodyLifetimeMgmtOpCbFn>(caller, callback),
|
||||
parent(parent)
|
||||
{}
|
||||
|
||||
private:
|
||||
Mind &parent;
|
||||
|
||||
public:
|
||||
void initializeReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<InitializeReq> context
|
||||
)
|
||||
{
|
||||
auto self = ComponentThread::getSelf();
|
||||
if (self->id != ComponentThread::BODY)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Must be executed on Body thread");
|
||||
}
|
||||
|
||||
/** EXPLANATION:
|
||||
* The ComponentThread instance we pass in here is the one that will be
|
||||
* used by Senseapi libs to perform device-independent background
|
||||
* operations.
|
||||
* For example, liblivoxProto1's BroadcastListener will use this thread
|
||||
* to listen for UDP broadcast dgrams from Livox devices.
|
||||
*
|
||||
* Right now we use Marionette, but there's a strong argument for using
|
||||
* Body instead since it's meant to handle device-management operations.
|
||||
*/
|
||||
sense_api::SenseApiManager::getInstance()
|
||||
.loadAllSenseApiLibsFromOptions(caller);
|
||||
|
||||
/** EXPLANATION:
|
||||
* Consider body::initializeReq to have been called if even one of its
|
||||
* operations was executed at all, whether successfully or
|
||||
* unsuccessfully.
|
||||
*/
|
||||
context->parent.bodyComponentInitialized = true;
|
||||
|
||||
std::cout << sense_api::SenseApiManager::getInstance().stringifyLibs()
|
||||
<< std::endl;
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": About to initializeAllSenseApiLibs"
|
||||
<< '\n';
|
||||
}
|
||||
sense_api::SenseApiManager::getInstance().initializeAllSenseApiLibs();
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": About to attachAllSenseDevicesFromSpecs"
|
||||
<< '\n';
|
||||
}
|
||||
device::DeviceManager::getInstance()
|
||||
.attachAllUnattachedDevicesFromCmdlineReq(
|
||||
{context, std::bind(
|
||||
&InitializeReq::initializeReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1)});
|
||||
}
|
||||
|
||||
void initializeReq2(
|
||||
[[maybe_unused]] std::shared_ptr<InitializeReq> context,
|
||||
smo::AsynchronousLoop &results
|
||||
)
|
||||
{
|
||||
std::cout << "Mrntt: attached "
|
||||
<< results.nSucceeded << " of " << results.nTotal
|
||||
<< " sense devices." << "\n";
|
||||
|
||||
callOriginalCb(results.nSucceeded > 0);
|
||||
}
|
||||
};
|
||||
|
||||
class Body::FinalizeReq
|
||||
: public InitializeReq
|
||||
{
|
||||
public:
|
||||
using InitializeReq::InitializeReq;
|
||||
|
||||
public:
|
||||
void finalizeReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<FinalizeReq> context
|
||||
)
|
||||
{
|
||||
auto self = ComponentThread::getSelf();
|
||||
if (self->id != ComponentThread::BODY)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Must be executed on Body thread");
|
||||
}
|
||||
|
||||
std::cout << "Mrntt: About to detach all sense devices." << "\n";
|
||||
device::DeviceManager::getInstance().detachAllAttachedDeviceRoles(
|
||||
{context, std::bind(
|
||||
&FinalizeReq::finalizeReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1)});
|
||||
}
|
||||
|
||||
void finalizeReq2(
|
||||
[[maybe_unused]] std::shared_ptr<FinalizeReq> context,
|
||||
smo::AsynchronousLoop &results
|
||||
)
|
||||
{
|
||||
std::cout << "Mrntt: Successfully detached "
|
||||
<< results.nSucceeded << " of " << results.nTotal
|
||||
<< " sense devices." << "\n";
|
||||
|
||||
std::cout << "Mrntt: About to finalize all sense api libs." << "\n";
|
||||
sense_api::SenseApiManager::getInstance().finalizeAllSenseApiLibs();
|
||||
|
||||
std::cout << "Mrntt: About to unload all sense api libs." << "\n";
|
||||
sense_api::SenseApiManager::getInstance().unloadAllSenseApiLibs();
|
||||
callOriginalCb(results.nSucceeded == results.nTotal);
|
||||
}
|
||||
};
|
||||
|
||||
void Body::initializeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
auto mrntt = ComponentThread::getSelf();
|
||||
|
||||
if (mrntt->id != ComponentThread::MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Must be invoked by Mrntt thread");
|
||||
}
|
||||
|
||||
auto request = std::make_shared<InitializeReq>(
|
||||
parent, mrntt, callback);
|
||||
|
||||
thread->getIoService().post(
|
||||
std::bind(
|
||||
&InitializeReq::initializeReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
auto mrntt = ComponentThread::getSelf();
|
||||
|
||||
if (mrntt->id != ComponentThread::MRNTT)
|
||||
{
|
||||
std::cerr << __func__ << ": Must be invoked by Mrntt thread"
|
||||
<< std::endl;
|
||||
callback.callbackFn(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parent.bodyComponentInitialized)
|
||||
{
|
||||
std::cout << "Mrntt: Body component not initialized. "
|
||||
<< "Skipping finalization." << "\n";
|
||||
callback.callbackFn(true);
|
||||
return;
|
||||
}
|
||||
|
||||
auto request = std::make_shared<FinalizeReq>(
|
||||
parent, mrntt, callback);
|
||||
|
||||
thread->getIoService().post(
|
||||
std::bind(
|
||||
&FinalizeReq::finalizeReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
} // namespace body
|
||||
} // namespace smo
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <component.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
Component::Component(const std::shared_ptr<ComponentThread> &thread)
|
||||
: thread(thread)
|
||||
{
|
||||
}
|
||||
|
||||
MindComponent::MindComponent(
|
||||
Mind &parent, const std::shared_ptr<ComponentThread> &thread)
|
||||
: Component(thread),
|
||||
parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
namespace mrntt {
|
||||
|
||||
MarionetteComponent::MarionetteComponent(
|
||||
const std::shared_ptr<ComponentThread> &thread)
|
||||
: Component(thread)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace mrntt
|
||||
|
||||
} // namespace smo
|
||||
@@ -3,7 +3,11 @@
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <opts.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <callback.h>
|
||||
#include <mind.h>
|
||||
#include <mindManager/mindManager.h>
|
||||
#include <componentThread.h>
|
||||
#include <marionette/marionette.h>
|
||||
|
||||
@@ -11,17 +15,18 @@ namespace smo {
|
||||
|
||||
thread_local std::shared_ptr<ComponentThread> thisComponentThread;
|
||||
|
||||
namespace mrntt {
|
||||
extern std::shared_ptr<ComponentThread> mrntt;
|
||||
}
|
||||
|
||||
// Implementation of static method
|
||||
std::shared_ptr<ComponentThread> ComponentThread::getMrntt()
|
||||
std::shared_ptr<MarionetteThread> ComponentThread::getMrntt()
|
||||
{
|
||||
return mrntt::mrntt;
|
||||
return mrntt::thread;
|
||||
}
|
||||
|
||||
void ComponentThread::initializeTls(void)
|
||||
void MarionetteThread::initializeTls(void)
|
||||
{
|
||||
thisComponentThread = shared_from_this();
|
||||
}
|
||||
|
||||
void MindThread::initializeTls(void)
|
||||
{
|
||||
thisComponentThread = shared_from_this();
|
||||
}
|
||||
@@ -37,9 +42,15 @@ const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void)
|
||||
return thisComponentThread;
|
||||
}
|
||||
|
||||
void ComponentThread::main(ComponentThread& self)
|
||||
void MindThread::main(MindThread& self)
|
||||
{
|
||||
std::cout << self.name << ":" << __func__ << ": Waiting for JOLT" <<"\n";
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << self.name << ":" << __func__ << ": Waiting for JOLT"
|
||||
<<"\n";
|
||||
}
|
||||
|
||||
self.getIoService().run();
|
||||
self.initializeTls();
|
||||
|
||||
@@ -57,6 +68,16 @@ void ComponentThread::main(ComponentThread& self)
|
||||
bool sendExceptionInd = false;
|
||||
|
||||
try {
|
||||
/** EXPLANATION:
|
||||
* This reset() call is crucial for async bridging patterns
|
||||
* to work.
|
||||
* When the outermost thread's io_service is stop()ped (e.g.,
|
||||
* from JOLT sequence), it won't process any new work until
|
||||
* reset() is called, even if nested async operations try to
|
||||
* post work to it. This means async bridges invoked from
|
||||
* the outermost thread main sequence won't work until this
|
||||
* reset() call.
|
||||
*/
|
||||
self.getIoService().reset();
|
||||
self.getIoService().run();
|
||||
}
|
||||
@@ -73,180 +94,218 @@ void ComponentThread::main(ComponentThread& self)
|
||||
<< ": Unknown exception occurred" << "\n";
|
||||
}
|
||||
|
||||
if (sendExceptionInd) { mrntt::mrntt->exceptionInd(self); }
|
||||
if (sendExceptionInd)
|
||||
{
|
||||
mrntt::mrntt.finalizeReq(
|
||||
{nullptr, std::bind(
|
||||
&mrntt::marionetteFinalizeReqCb, std::placeholders::_1)});
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << self.name << ":" << __func__ << ": Exited event loop" << "\n";
|
||||
}
|
||||
|
||||
// Thread management method implementations
|
||||
void ComponentThread::startThreadReq(std::function<void()> callback)
|
||||
class MindThread::ThreadLifetimeMgmtOp
|
||||
: public PostedAsynchronousContinuation<threadLifetimeMgmtOpCbFn>
|
||||
{
|
||||
this->getIoService().post([this, caller = getSelf(), callback]()
|
||||
public:
|
||||
ThreadLifetimeMgmtOp(
|
||||
const std::shared_ptr<ComponentThread> &caller,
|
||||
const std::shared_ptr<MindThread> &target,
|
||||
Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
: PostedAsynchronousContinuation<threadLifetimeMgmtOpCbFn>(
|
||||
caller, callback),
|
||||
target(target)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::shared_ptr<MindThread> target;
|
||||
|
||||
public:
|
||||
void joltThreadReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
|
||||
)
|
||||
{
|
||||
std::cout << "Thread '" << name << "': handling startThread." << "\n";
|
||||
std::cout << __func__ << ": Thread '" << target->name << "': handling "
|
||||
"JOLT request."
|
||||
<< "\n";
|
||||
|
||||
target->io_service.stop();
|
||||
callOriginalCb();
|
||||
}
|
||||
|
||||
void startThreadReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
|
||||
)
|
||||
{
|
||||
std::cout << __func__ << ": Thread '" << target->name << "': handling "
|
||||
"startThread."
|
||||
<< "\n";
|
||||
|
||||
// Execute private setup sequence here
|
||||
// This is where each thread would implement its specific initialization
|
||||
|
||||
if (callback) {
|
||||
caller->getIoService().post(callback);
|
||||
callOriginalCb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void exitThreadReq1_mainQueue_posted(
|
||||
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
|
||||
)
|
||||
{
|
||||
std::cout << __func__ << ": Thread '" << target->name << "': handling "
|
||||
"exitThread (main queue)." << "\n";
|
||||
|
||||
target->cleanup();
|
||||
target->io_service.stop();
|
||||
callOriginalCb();
|
||||
}
|
||||
|
||||
void exitThreadReq1_pauseQueue_posted(
|
||||
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
|
||||
)
|
||||
{
|
||||
std::cout << __func__ << ": Thread '" << target->name << "': handling "
|
||||
"exitThread (pause queue)."<< "\n";
|
||||
|
||||
target->cleanup();
|
||||
target->pause_io_service.stop();
|
||||
target->io_service.stop();
|
||||
callOriginalCb();
|
||||
}
|
||||
|
||||
void pauseThreadReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
|
||||
)
|
||||
{
|
||||
std::cout << __func__ << ": Thread '" << target->name << "': handling "
|
||||
"pauseThread." << "\n";
|
||||
|
||||
/* We have to invoke the callback here before moving on because
|
||||
* our next operation is going to block the thread, so it won't
|
||||
* have a chance to invoke the callback until it's unblocked.
|
||||
*/
|
||||
callOriginalCb();
|
||||
target->pause_io_service.reset();
|
||||
target->pause_io_service.run();
|
||||
}
|
||||
|
||||
void resumeThreadReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
|
||||
)
|
||||
{
|
||||
std::cout << __func__ << ": Thread '" << target->name << "': handling "
|
||||
"resumeThread." << "\n";
|
||||
|
||||
target->pause_io_service.stop();
|
||||
callOriginalCb();
|
||||
}
|
||||
};
|
||||
|
||||
void ComponentThread::cleanup(void)
|
||||
{
|
||||
this->keepLooping = false;
|
||||
}
|
||||
|
||||
void ComponentThread::exitThreadReq(std::function<void()> callback)
|
||||
void MindThread::joltThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
// Post to the main io_service
|
||||
this->getIoService().post([this, caller = getSelf(), callback]()
|
||||
{
|
||||
std::cout << "Thread '" << name << "': handling exitThread "
|
||||
"(main queue)." << std::endl;
|
||||
|
||||
cleanup();
|
||||
|
||||
// Stop the main io_service to exit the thread
|
||||
io_service.stop();
|
||||
if (callback) { caller->getIoService().post(callback); }
|
||||
});
|
||||
|
||||
// Also post to the pause io_service
|
||||
this->pause_io_service.post([this, caller = getSelf(), callback]()
|
||||
{
|
||||
std::cout << "Thread '" << name << "': handling exitThread "
|
||||
"(pause queue)." << std::endl;
|
||||
|
||||
cleanup();
|
||||
|
||||
// Stop both io_services to exit the thread
|
||||
pause_io_service.stop();
|
||||
io_service.stop();
|
||||
if (callback) { caller->getIoService().post(callback); }
|
||||
});
|
||||
}
|
||||
|
||||
void ComponentThread::pauseThreadReq(std::function<void()> callback)
|
||||
{
|
||||
this->getIoService().post([this, caller = getSelf(), callback]()
|
||||
{
|
||||
std::cout << "Thread '" << name << "': handling pauseThread."
|
||||
<< std::endl;
|
||||
|
||||
if (callback) {
|
||||
caller->getIoService().post(callback);
|
||||
}
|
||||
|
||||
// Reset the pause io_service before running to ensure it can run again
|
||||
pause_io_service.reset();
|
||||
// Run the pause io_service to block this thread
|
||||
pause_io_service.run();
|
||||
});
|
||||
}
|
||||
|
||||
void ComponentThread::resumeThreadReq(std::function<void()> callback)
|
||||
{
|
||||
// Post to the pause_io_service to unblock the paused thread
|
||||
pause_io_service.post([this, caller = getSelf(), callback]()
|
||||
{
|
||||
std::cout << "Thread '" << name << "': handling resumeThread."
|
||||
<< std::endl;
|
||||
|
||||
if (callback) {
|
||||
caller->getIoService().post(callback);
|
||||
}
|
||||
|
||||
// Stop the pause_io_service to unblock the thread
|
||||
pause_io_service.stop();
|
||||
});
|
||||
}
|
||||
|
||||
void ComponentThread::joltThreadReq(std::function<void()> callback)
|
||||
{
|
||||
this->getIoService().post([this, caller = getSelf(), callback]()
|
||||
{
|
||||
std::cout << "Thread '" << name << "': handling JOLT request." << "\n";
|
||||
|
||||
// Stop the main io_service to jolt the thread
|
||||
io_service.stop();
|
||||
|
||||
if (callback) {
|
||||
caller->getIoService().post(callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* This shouldn't take a callback because the caller shouldn't expect to
|
||||
* Mrntt to send a reply signal to it. Sending this Indication means that
|
||||
* Mrntt will send the calling thread an exitThreadReq. When the caller
|
||||
* processes that exitThreadReq(), the caller will exit its event loop and then
|
||||
* terminate.
|
||||
/** EXPLANATION:
|
||||
* We can't use shared_from_this() here because JOLTing occurs prior to
|
||||
* TLS being set up.
|
||||
*
|
||||
* Even if Mrntt sent a RDY response, the caller shouldn't actually be executing
|
||||
* any longer to receive it anyway.
|
||||
* We also can't use getSelf() as yet for the same reason: getSelf()
|
||||
* requires TLS to be set up.
|
||||
*
|
||||
* To obtain a sh_ptr to the caller, we just supply the mrntt thread since
|
||||
* JOLT is always invoked by the mrntt thread. The JOLT sequence that the
|
||||
* CRT main() function invokes on the mrntt thread is special since it
|
||||
* supplies cmdline args and envp.
|
||||
*
|
||||
* To obtain a sh_ptr to the target thread, we explicitly look it up in the
|
||||
* Mind object's collection of component threads.
|
||||
*/
|
||||
void ComponentThread::exceptionInd(ComponentThread& thread)
|
||||
{
|
||||
if (this->id != ComponentThread::MRNTT)
|
||||
if (id == ComponentThread::MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": invoked on non-mrntt thread " + thread.name);
|
||||
+ ": invoked on mrntt thread");
|
||||
}
|
||||
|
||||
// Post the exception to the mrntt thread.
|
||||
this->getIoService().post(
|
||||
[&thread]()
|
||||
{
|
||||
std::cerr << "Mrntt: Exception occurred: in thread "
|
||||
<< thread.name << ". Killing Salmanoff." << "\n";
|
||||
std::shared_ptr<MarionetteThread> mrntt = mrntt::thread;
|
||||
std::shared_ptr<MindThread> target = getParent().getComponentThread(id);
|
||||
|
||||
/** EXPLANATION:
|
||||
* An exception has occurred in one of a mind's threads. We need to
|
||||
* shut down all of that particular mind's threads.
|
||||
*/
|
||||
thread.parent.finalizeReq([]() {
|
||||
/** FIXME:
|
||||
* When we eventually support multiple minds, we should remove this
|
||||
* since it causes marionette to exit, even if there are other minds
|
||||
* that are still running.
|
||||
*/
|
||||
smo::mrntt::exitMarionetteLoop();
|
||||
});
|
||||
});
|
||||
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
|
||||
mrntt, target, callback);
|
||||
|
||||
this->getIoService().post(
|
||||
std::bind(
|
||||
&ThreadLifetimeMgmtOp::joltThreadReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void ComponentThread::userShutdownInd()
|
||||
// Thread management method implementations
|
||||
void MindThread::startThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
if (this->id != ComponentThread::MRNTT)
|
||||
std::shared_ptr<ComponentThread> caller = getSelf();
|
||||
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
|
||||
caller, shared_from_this(), callback);
|
||||
|
||||
this->getIoService().post(
|
||||
std::bind(
|
||||
&ThreadLifetimeMgmtOp::startThreadReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
std::shared_ptr<ComponentThread> caller = getSelf();
|
||||
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
|
||||
caller, shared_from_this(), callback);
|
||||
|
||||
this->getIoService().post(
|
||||
std::bind(
|
||||
&ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted,
|
||||
request.get(), request));
|
||||
|
||||
pause_io_service.post(
|
||||
std::bind(
|
||||
&ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
if (id == ComponentThread::MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": invoked on non-mrntt thread " + this->name);
|
||||
+ ": invoked on mrntt thread");
|
||||
}
|
||||
|
||||
// Post the user shutdown to the mrntt thread.
|
||||
this->getIoService().post(
|
||||
[this]()
|
||||
{
|
||||
std::cerr << "Mrntt: User requested shutdown (SIGINT)."
|
||||
<< " Killing Salmanoff." << "\n";
|
||||
std::shared_ptr<ComponentThread> caller = getSelf();
|
||||
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
|
||||
caller, shared_from_this(), callback);
|
||||
|
||||
/** EXPLANATION:
|
||||
* A user has requested a shutdown. We need to shut down all of the
|
||||
* threads in all running Minds.
|
||||
*/
|
||||
parent.finalizeReq([]() {
|
||||
/** FIXME:
|
||||
* When we eventually support multiple minds, we should remove this
|
||||
* since it causes marionette to exit, even if there are other minds
|
||||
* that are still running.
|
||||
*/
|
||||
smo::mrntt::exitMarionetteLoop();
|
||||
});
|
||||
});
|
||||
this->getIoService().post(
|
||||
std::bind(
|
||||
&ThreadLifetimeMgmtOp::pauseThreadReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
if (id == ComponentThread::MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": invoked on mrntt thread");
|
||||
}
|
||||
|
||||
// Post to the pause_io_service to unblock the paused thread
|
||||
std::shared_ptr<ComponentThread> caller = getSelf();
|
||||
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
|
||||
caller, shared_from_this(), callback);
|
||||
|
||||
pause_io_service.post(
|
||||
std::bind(
|
||||
&ThreadLifetimeMgmtOp::resumeThreadReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
// CPU management method implementations
|
||||
@@ -272,7 +331,7 @@ int ComponentThread::getAvailableCpuCount()
|
||||
return cpuCount;
|
||||
}
|
||||
|
||||
void ComponentThread::pinToCpu(int cpuId)
|
||||
void MindThread::pinToCpu(int cpuId)
|
||||
{
|
||||
if (cpuId < 0)
|
||||
{
|
||||
@@ -294,7 +353,10 @@ void ComponentThread::pinToCpu(int cpuId)
|
||||
}
|
||||
|
||||
pinnedCpuId = cpuId;
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << name << ": Pinned to CPU " << cpuId << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace smo
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# Flex/Bison generated files
|
||||
set(LEX_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/deviceAttachmentPipeSpecl.cc)
|
||||
set(YACC_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/deviceAttachmentPipeSpecp.cc)
|
||||
set(YACC_HEADER ${CMAKE_CURRENT_BINARY_DIR}/deviceAttachmentPipeSpecp.hh)
|
||||
|
||||
# Generate Flex/Bison files using custom commands
|
||||
add_custom_command(
|
||||
OUTPUT ${LEX_OUTPUT}
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/deviceAttachmentPipeSpecl.ll ${YACC_HEADER}
|
||||
COMMAND ${FLEX_EXECUTABLE} --header-file=${CMAKE_CURRENT_BINARY_DIR}/deviceAttachmentPipeSpecl.hh -o ${LEX_OUTPUT} ${CMAKE_CURRENT_SOURCE_DIR}/deviceAttachmentPipeSpecl.ll
|
||||
COMMENT "Generating deviceAttachmentPipeSpecl.cc from deviceAttachmentPipeSpecl.ll"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${YACC_OUTPUT} ${YACC_HEADER}
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/deviceAttachmentPipeSpecp.yy
|
||||
COMMAND ${BISON_EXECUTABLE} -p deviceAttachmentPipeSpecp --header=${YACC_HEADER} -o ${YACC_OUTPUT} ${CMAKE_CURRENT_SOURCE_DIR}/deviceAttachmentPipeSpecp.yy
|
||||
COMMENT "Generating deviceAttachmentPipeSpecp.cc and deviceAttachmentPipeSpecp.hh from deviceAttachmentPipeSpecp.yy"
|
||||
)
|
||||
|
||||
# Device manager library
|
||||
add_library(deviceManager STATIC
|
||||
deviceManager.cpp
|
||||
deviceAttachmentPipeSpecParser.cpp
|
||||
${LEX_OUTPUT}
|
||||
${YACC_OUTPUT}
|
||||
)
|
||||
|
||||
target_include_directories(deviceManager PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
@@ -87,8 +87,7 @@ interoceptor_spec:
|
||||
*static_cast<smo::device::InteroceptorDevAttachmentSpec *>($3));
|
||||
|
||||
spec->sensorType = $1;
|
||||
smo::device::DeviceManager::interoceptorDeviceSpecs.push_back(spec);
|
||||
smo::device::DeviceManager::deviceAttachmentSpecs.push_back(spec);
|
||||
smo::device::DeviceManager::commandLineDASpecs.push_back(*spec);
|
||||
|
||||
delete $3;
|
||||
}
|
||||
@@ -100,8 +99,7 @@ extrospector_spec:
|
||||
*static_cast<smo::device::ExtrospectorDevAttachmentSpec *>($3));
|
||||
|
||||
spec->sensorType = $1;
|
||||
smo::device::DeviceManager::extrospectorDeviceSpecs.push_back(spec);
|
||||
smo::device::DeviceManager::deviceAttachmentSpecs.push_back(spec);
|
||||
smo::device::DeviceManager::commandLineDASpecs.push_back(*spec);
|
||||
|
||||
delete $3;
|
||||
}
|
||||
|
||||
@@ -6,108 +6,823 @@
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <opts.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <serializedAsynchronousContinuation.h>
|
||||
#include <callback.h>
|
||||
#include <componentThread.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
#include <deviceManager/deviceReattacher.h>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <marionette/marionette.h>
|
||||
#include <mind.h>
|
||||
|
||||
namespace smo {
|
||||
namespace device {
|
||||
|
||||
std::vector<std::shared_ptr<InteroceptorDevAttachmentSpec>>
|
||||
DeviceManager::interoceptorDeviceSpecs;
|
||||
std::vector<std::shared_ptr<ExtrospectorDevAttachmentSpec>>
|
||||
DeviceManager::extrospectorDeviceSpecs;
|
||||
std::vector<std::shared_ptr<DeviceAttachmentSpec>>
|
||||
DeviceManager::deviceAttachmentSpecs;
|
||||
std::vector<std::shared_ptr<Device>>
|
||||
DeviceManager::devices;
|
||||
std::vector<std::shared_ptr<DeviceRole>>
|
||||
DeviceManager::attachedDeviceRoles;
|
||||
std::vector<DeviceAttachmentSpec>
|
||||
DeviceManager::commandLineDASpecs;
|
||||
|
||||
// Async continuation structure
|
||||
struct DeviceAttachmentContinuation {
|
||||
std::shared_ptr<DeviceAttachmentSpec> spec;
|
||||
std::function<void(
|
||||
bool success, std::shared_ptr<Device> device,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec)
|
||||
> callback;
|
||||
|
||||
DeviceAttachmentContinuation(
|
||||
std::shared_ptr<DeviceAttachmentSpec> s,
|
||||
std::function<void(
|
||||
bool success, std::shared_ptr<Device> device,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec)
|
||||
> cb)
|
||||
: spec(s), callback(cb)
|
||||
{}
|
||||
};
|
||||
DeviceManager::~DeviceManager()
|
||||
{
|
||||
}
|
||||
|
||||
const std::string DeviceManager::stringifyDeviceSpecs(void)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
for (const auto& spec : DeviceManager::interoceptorDeviceSpecs) {
|
||||
oss << "Interoceptor " << spec->stringify();
|
||||
}
|
||||
|
||||
for (const auto& spec : DeviceManager::extrospectorDeviceSpecs) {
|
||||
oss << "Extrospector " << spec->stringify();
|
||||
for (const auto& spec : DeviceManager::deviceAttachmentSpecs) {
|
||||
oss << "Device Attachment Spec: " << spec->stringify();
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void DeviceManager::newDeviceAttachmentSpecInd(
|
||||
std::shared_ptr<DeviceAttachmentSpec> spec,
|
||||
std::function<void(
|
||||
bool success, std::shared_ptr<Device> device,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec)
|
||||
> callback)
|
||||
class DeviceManager::NewDeviceAttachmentSpecInd
|
||||
: public SerializedAsynchronousContinuation<newDeviceAttachmentSpecIndCbFn>
|
||||
{
|
||||
// Create async continuation
|
||||
auto continuation = std::make_shared<DeviceAttachmentContinuation>(
|
||||
spec, callback);
|
||||
public:
|
||||
NewDeviceAttachmentSpecInd(
|
||||
const DeviceAttachmentSpec &spec,
|
||||
const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<newDeviceAttachmentSpecIndCbFn> cb,
|
||||
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
|
||||
: SerializedAsynchronousContinuation<newDeviceAttachmentSpecIndCbFn>(
|
||||
caller, cb, requiredLocks),
|
||||
spec(spec)
|
||||
{}
|
||||
|
||||
// Check if a DeviceAttachmentSpec already matches
|
||||
for (const auto& existingSpec : deviceAttachmentSpecs)
|
||||
public:
|
||||
DeviceAttachmentSpec spec;
|
||||
std::shared_ptr<DeviceAttachmentSpec> specPtr;
|
||||
std::shared_ptr<Device> device;
|
||||
|
||||
public:
|
||||
void newDeviceAttachmentSpecInd1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<NewDeviceAttachmentSpecInd> context
|
||||
)
|
||||
{
|
||||
if (!(*existingSpec == *spec)) { continue; }
|
||||
// Already exists, callback with error
|
||||
callback(false, nullptr, nullptr);
|
||||
return;
|
||||
// First, add the spec to deviceAttachmentSpecs if it's not already there
|
||||
bool specExists = false;
|
||||
for (const auto& existingSpec : DeviceManager::deviceAttachmentSpecs)
|
||||
{
|
||||
if (*existingSpec == spec)
|
||||
{
|
||||
specExists = true;
|
||||
specPtr = existingSpec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to attach the sense device
|
||||
try {
|
||||
sense_api::SenseApiManager::getInstance().attachSenseDevice(spec);
|
||||
|
||||
// Look for existing Device with same identifier
|
||||
std::shared_ptr<Device> device = nullptr;
|
||||
for (const auto& existingDevice : devices)
|
||||
if (!specExists)
|
||||
{
|
||||
if (existingDevice->deviceIdentifier != spec->deviceIdentifier)
|
||||
specPtr = std::make_shared<DeviceAttachmentSpec>(spec);
|
||||
DeviceManager::deviceAttachmentSpecs.push_back(specPtr);
|
||||
}
|
||||
|
||||
bool deviceExists = false;
|
||||
for (const auto& existingDevice : DeviceManager::devices)
|
||||
{
|
||||
if (existingDevice->deviceIdentifier != spec.deviceIdentifier)
|
||||
{ continue; }
|
||||
|
||||
device = existingDevice;
|
||||
deviceExists = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If device doesn't exist, create a new one and add it
|
||||
if (!device)
|
||||
{
|
||||
device = std::make_shared<Device>(spec->deviceIdentifier);
|
||||
devices.push_back(device);
|
||||
device = std::make_shared<Device>(spec.deviceIdentifier);
|
||||
DeviceManager::devices.push_back(device);
|
||||
}
|
||||
|
||||
// Add DeviceAttachmentSpec to device's list
|
||||
device->deviceAttachmentSpecs.push_back(spec);
|
||||
// Check if a DeviceRole w/ this spec already exists in attachedDeviceRoles
|
||||
bool deviceRoleExists = false;
|
||||
std::shared_ptr<DeviceRole> existingDeviceRole = nullptr;
|
||||
for (const auto& role : DeviceManager::attachedDeviceRoles)
|
||||
{
|
||||
if (*role->deviceAttachmentSpec == spec)
|
||||
{
|
||||
deviceRoleExists = true;
|
||||
existingDeviceRole = role;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add DeviceAttachmentSpec to DeviceManager's list
|
||||
deviceAttachmentSpecs.push_back(spec);
|
||||
// If DeviceRole exists, both spec and device must also exist
|
||||
if (deviceRoleExists)
|
||||
{
|
||||
if (!specExists || !deviceExists)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Program error: DeviceRole exists but spec or device doesn't "
|
||||
"pre-exist. specExists=" + std::to_string(specExists) +
|
||||
", deviceExists=" + std::to_string(deviceExists));
|
||||
}
|
||||
|
||||
// Already attached, callback with success and return
|
||||
callOriginalCb(true, existingDeviceRole, specPtr);
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceManager::getInstance().attachSenseDeviceReq(
|
||||
specPtr,
|
||||
{context, std::bind(
|
||||
&NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
|
||||
void newDeviceAttachmentSpecInd2(
|
||||
[[maybe_unused]] std::shared_ptr<NewDeviceAttachmentSpecInd> context,
|
||||
bool success,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
|
||||
)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << __func__ << ": Attach failed for device spec "
|
||||
<< deviceSpec->stringify() << std::endl;
|
||||
callOriginalCb(false, nullptr, deviceSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create DeviceRole and add it to both DeviceManager's and Device's collections
|
||||
auto deviceRole = std::make_shared<DeviceRole>(*device, specPtr);
|
||||
device->deviceRoles.push_back(deviceRole);
|
||||
DeviceManager::attachedDeviceRoles.push_back(deviceRole);
|
||||
|
||||
// Callback with success
|
||||
callback(true, device, spec);
|
||||
callOriginalCb(true, deviceRole, specPtr);
|
||||
} catch (const std::exception& e) {
|
||||
// Attach failed, callback with error
|
||||
callback(false, nullptr, nullptr);
|
||||
callOriginalCb(false, nullptr, specPtr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DeviceManager::RemoveDeviceAttachmentSpecReq
|
||||
: public SerializedAsynchronousContinuation<removeDeviceAttachmentSpecReqCbFn>
|
||||
{
|
||||
public:
|
||||
RemoveDeviceAttachmentSpecReq(
|
||||
const DeviceAttachmentSpec &spec,
|
||||
const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<removeDeviceAttachmentSpecReqCbFn> cb,
|
||||
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
|
||||
: SerializedAsynchronousContinuation<removeDeviceAttachmentSpecReqCbFn>(
|
||||
caller, cb, requiredLocks),
|
||||
spec(spec)
|
||||
{}
|
||||
|
||||
public:
|
||||
DeviceAttachmentSpec spec;
|
||||
std::shared_ptr<DeviceAttachmentSpec> specPtr;
|
||||
|
||||
public:
|
||||
void removeDeviceAttachmentSpecReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<RemoveDeviceAttachmentSpecReq> context
|
||||
)
|
||||
{
|
||||
// Find the shared_ptr to the spec in the collection
|
||||
for (const auto& existingSpec : DeviceManager::deviceAttachmentSpecs)
|
||||
{
|
||||
if (*existingSpec == spec)
|
||||
{
|
||||
specPtr = existingSpec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!specPtr)
|
||||
{
|
||||
// Spec not found, callback with failure and return
|
||||
callOriginalCb(false, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call detachSenseDeviceReq first - only clean up metadata if this succeeds
|
||||
DeviceManager::getInstance().detachSenseDeviceReq(
|
||||
specPtr,
|
||||
{context, std::bind(
|
||||
&RemoveDeviceAttachmentSpecReq::removeDeviceAttachmentSpecReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
|
||||
void removeDeviceAttachmentSpecReq2(
|
||||
[[maybe_unused]] std::shared_ptr<RemoveDeviceAttachmentSpecReq> context,
|
||||
bool success,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
|
||||
)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
// Detach failed, callback with failure (metadata remains intact)
|
||||
callOriginalCb(false, deviceSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Detach succeeded, now find and clean up metadata
|
||||
try {
|
||||
// Find the DeviceRole in attachedDeviceRoles
|
||||
auto deviceRoleIt = std::find_if(
|
||||
DeviceManager::attachedDeviceRoles.begin(),
|
||||
DeviceManager::attachedDeviceRoles.end(),
|
||||
[&specPtr = specPtr](const std::shared_ptr<DeviceRole> &role) {
|
||||
return *role->deviceAttachmentSpec == *specPtr;
|
||||
}
|
||||
);
|
||||
|
||||
if (deviceRoleIt == DeviceManager::attachedDeviceRoles.end())
|
||||
{
|
||||
// DeviceRole not found, callback with failure
|
||||
callOriginalCb(false, deviceSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto deviceRole = *deviceRoleIt;
|
||||
auto& device = deviceRole->parentDevice;
|
||||
|
||||
// Remove DeviceRole from DeviceManager's collection
|
||||
DeviceManager::attachedDeviceRoles.erase(deviceRoleIt);
|
||||
|
||||
// Remove DeviceRole from Device's collection
|
||||
auto deviceRoleIt2 = std::find(
|
||||
device.deviceRoles.begin(),
|
||||
device.deviceRoles.end(),
|
||||
deviceRole);
|
||||
if (deviceRoleIt2 != device.deviceRoles.end())
|
||||
{
|
||||
device.deviceRoles.erase(deviceRoleIt2);
|
||||
}
|
||||
|
||||
// Remove DeviceAttachmentSpec from deviceAttachmentSpecs collection
|
||||
auto specIt = std::find_if(
|
||||
DeviceManager::deviceAttachmentSpecs.begin(),
|
||||
DeviceManager::deviceAttachmentSpecs.end(),
|
||||
[&specPtr = specPtr](
|
||||
const std::shared_ptr<DeviceAttachmentSpec> &existingSpec)
|
||||
{
|
||||
return *existingSpec == *specPtr;
|
||||
}
|
||||
);
|
||||
|
||||
if (specIt != DeviceManager::deviceAttachmentSpecs.end())
|
||||
{
|
||||
DeviceManager::deviceAttachmentSpecs.erase(specIt);
|
||||
}
|
||||
|
||||
// Callback with success
|
||||
callOriginalCb(true, deviceSpec);
|
||||
} catch (const std::exception& e) {
|
||||
// Cleanup failed, callback with error
|
||||
callOriginalCb(false, deviceSpec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void DeviceManager::newDeviceAttachmentSpecInd(
|
||||
const DeviceAttachmentSpec &spec,
|
||||
Callback<newDeviceAttachmentSpecIndCbFn> callback)
|
||||
{
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
|
||||
auto request = std::make_shared<NewDeviceAttachmentSpecInd>(
|
||||
spec, caller, callback,
|
||||
LockSet<newDeviceAttachmentSpecIndCbFn>::Set{
|
||||
std::ref(DeviceManager::getInstance().qutex)
|
||||
});
|
||||
|
||||
NewDeviceAttachmentSpecInd::LockerAndInvoker lockvoker(
|
||||
*request, mrntt::mrntt.thread,
|
||||
std::bind(
|
||||
&NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void DeviceManager::removeDeviceAttachmentSpecReq(
|
||||
const DeviceAttachmentSpec &spec,
|
||||
Callback<removeDeviceAttachmentSpecReqCbFn> callback)
|
||||
{
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
|
||||
auto request = std::make_shared<RemoveDeviceAttachmentSpecReq>(
|
||||
spec, caller, callback,
|
||||
LockSet<removeDeviceAttachmentSpecReqCbFn>::Set{
|
||||
std::ref(DeviceManager::getInstance().qutex)
|
||||
});
|
||||
|
||||
RemoveDeviceAttachmentSpecReq::LockerAndInvoker lockvoker(
|
||||
*request, mrntt::mrntt.thread,
|
||||
std::bind(
|
||||
&RemoveDeviceAttachmentSpecReq
|
||||
::removeDeviceAttachmentSpecReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
class DeviceManager::AttachSenseDeviceReq
|
||||
: public SerializedAsynchronousContinuation<attachSenseDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
AttachSenseDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<attachSenseDeviceReqCbFn> cb,
|
||||
std::shared_ptr<sense_api::SenseApiLib> &senseApiLib,
|
||||
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
|
||||
: SerializedAsynchronousContinuation<attachSenseDeviceReqCbFn>(
|
||||
caller, cb, requiredLocks),
|
||||
spec(spec), senseApiLib(senseApiLib)
|
||||
{}
|
||||
|
||||
public:
|
||||
void attachSenseDeviceReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<AttachSenseDeviceReq> context
|
||||
)
|
||||
{
|
||||
if (caller->id != ComponentThread::MRNTT)
|
||||
{
|
||||
std::cerr << std::string(__func__)
|
||||
<< ": executed on non-mrntt thread: "
|
||||
<< caller->name << std::endl;
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (senseApiLib->isBeingDestroyed.load())
|
||||
{
|
||||
std::cerr << std::string(__func__) + ": Library is being destroyed"
|
||||
<< " for API '" << spec->api << "'. Bailing out." << std::endl;
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!senseApiLib->senseApiDesc.sal_mgmt_libOps.attachDeviceReq)
|
||||
{
|
||||
std::cerr << std::string(__func__) + ": attachDeviceReq() is NULL "
|
||||
"for library '" << senseApiLib->libraryPath << "'" << std::endl;
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
releaseQutexEarly(sense_api::SenseApiManager::getInstance().qutex);
|
||||
|
||||
/** EXPLANATION:
|
||||
* We pass in either the body or world thread here, depending on whether
|
||||
* the device is an introspector (idev) or extrospector (edev).
|
||||
*
|
||||
* Introspectors are attached to the body thread; extrospectors are
|
||||
* attached to the world thread.
|
||||
*/
|
||||
std::shared_ptr<ComponentThread> threadForAttachment;
|
||||
if (spec->sensorType == 'e')
|
||||
{
|
||||
threadForAttachment = mind::globalMind->world.thread;
|
||||
std::cout << __func__ << ": Attaching edev "
|
||||
<< spec->deviceIdentifier << " to world thread" << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
threadForAttachment = mind::globalMind->body.thread;
|
||||
std::cout << __func__ << ": Attaching non-edev "
|
||||
<< spec->deviceIdentifier << " to body thread" << "\n";
|
||||
}
|
||||
|
||||
senseApiLib->senseApiDesc.sal_mgmt_libOps.attachDeviceReq(
|
||||
spec, threadForAttachment,
|
||||
{context, std::bind(
|
||||
&AttachSenseDeviceReq::attachSenseDeviceReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
|
||||
void attachSenseDeviceReq2(
|
||||
[[maybe_unused]] std::shared_ptr<AttachSenseDeviceReq> context,
|
||||
bool success,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
|
||||
)
|
||||
{
|
||||
callOriginalCb(success, deviceSpec);
|
||||
}
|
||||
|
||||
void detachSenseDeviceReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<DetachSenseDeviceReq> context
|
||||
)
|
||||
{
|
||||
if (caller->id != ComponentThread::MRNTT)
|
||||
{
|
||||
std::cerr << std::string(__func__)
|
||||
<< ": executed on non-mrntt thread: "
|
||||
<< caller->name << std::endl;
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (senseApiLib->isBeingDestroyed.load())
|
||||
{
|
||||
std::cerr << std::string(__func__) + ": Library is being destroyed"
|
||||
<< " for API '" << spec->api << "'. Bailing out." << std::endl;
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!senseApiLib->senseApiDesc.sal_mgmt_libOps.detachDeviceReq)
|
||||
{
|
||||
std::cerr << std::string(__func__) + ": detachDeviceReq() is NULL "
|
||||
"for library '" << senseApiLib->libraryPath << "'" << std::endl;
|
||||
callOriginalCb(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
releaseQutexEarly(sense_api::SenseApiManager::getInstance().qutex);
|
||||
|
||||
senseApiLib->senseApiDesc.sal_mgmt_libOps.detachDeviceReq(
|
||||
spec,
|
||||
{context, std::bind(
|
||||
&DetachSenseDeviceReq::detachSenseDeviceReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
|
||||
void detachSenseDeviceReq2(
|
||||
[[maybe_unused]] std::shared_ptr<DetachSenseDeviceReq> context,
|
||||
bool success,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
|
||||
)
|
||||
{
|
||||
callOriginalCb(success, deviceSpec);
|
||||
}
|
||||
|
||||
public:
|
||||
std::shared_ptr<DeviceAttachmentSpec> spec;
|
||||
std::shared_ptr<sense_api::SenseApiLib> senseApiLib;
|
||||
};
|
||||
|
||||
void DeviceManager::attachSenseDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
Callback<attachSenseDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
|
||||
// Get the sense API lib's qutex
|
||||
auto libOpt = sense_api::SenseApiManager::getInstance()
|
||||
.getSenseApiLibByApiName(spec->api);
|
||||
|
||||
if (!libOpt)
|
||||
{
|
||||
std::cerr << "attachSenseDeviceReq: No library found for API '"
|
||||
<< spec->api << "'" << std::endl;
|
||||
cb.callbackFn(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& lib = *libOpt.value();
|
||||
|
||||
auto request = std::make_shared<AttachSenseDeviceReq>(
|
||||
spec, caller, cb, libOpt.value(),
|
||||
LockSet<attachSenseDeviceReqCbFn>::Set{
|
||||
std::ref(sense_api::SenseApiManager::getInstance().qutex),
|
||||
std::ref(lib.qutex)
|
||||
});
|
||||
|
||||
AttachSenseDeviceReq::LockerAndInvoker lockvoker(
|
||||
*request, mrntt::mrntt.thread,
|
||||
std::bind(
|
||||
&AttachSenseDeviceReq::attachSenseDeviceReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void DeviceManager::detachSenseDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
Callback<detachSenseDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
|
||||
// Get the sense API lib's qutex
|
||||
auto libOpt = sense_api::SenseApiManager::getInstance()
|
||||
.getSenseApiLibByApiName(spec->api);
|
||||
|
||||
if (!libOpt)
|
||||
{
|
||||
std::cerr << "detachSenseDeviceReq: No library found for API '"
|
||||
<< spec->api << "'" << std::endl;
|
||||
cb.callbackFn(false, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& lib = *libOpt.value();
|
||||
|
||||
auto request = std::make_shared<DetachSenseDeviceReq>(
|
||||
spec, caller, cb, libOpt.value(),
|
||||
LockSet<detachSenseDeviceReqCbFn>::Set{
|
||||
std::ref(sense_api::SenseApiManager::getInstance().qutex),
|
||||
std::ref(lib.qutex)
|
||||
});
|
||||
|
||||
DetachSenseDeviceReq::LockerAndInvoker lockvoker(
|
||||
*request, mrntt::mrntt.thread,
|
||||
std::bind(
|
||||
&DetachSenseDeviceReq::detachSenseDeviceReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
class DeviceManager::AttachAllUnattachedDevicesFromReq
|
||||
: public PostedAsynchronousContinuation<
|
||||
attachAllUnattachedDevicesFromReqCbFn>
|
||||
{
|
||||
public:
|
||||
AttachAllUnattachedDevicesFromReq(
|
||||
const unsigned int totalNSpecs,
|
||||
const std::shared_ptr<std::vector<DeviceAttachmentSpec>>& specs,
|
||||
const std::shared_ptr<ComponentThread>& caller,
|
||||
Callback<attachAllUnattachedDevicesFromReqCbFn> cb)
|
||||
: PostedAsynchronousContinuation<attachAllUnattachedDevicesFromReqCbFn>(
|
||||
caller, cb),
|
||||
loop(totalNSpecs), specs(specs)
|
||||
{}
|
||||
|
||||
public:
|
||||
void attachAllUnattachedDevicesFromReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<AttachAllUnattachedDevicesFromReq>
|
||||
context
|
||||
)
|
||||
{
|
||||
for (const auto& spec : *specs)
|
||||
{
|
||||
DeviceManager::getInstance().newDeviceAttachmentSpecInd(
|
||||
spec,
|
||||
{context, std::bind(
|
||||
&AttachAllUnattachedDevicesFromReq
|
||||
::attachAllUnattachedDevicesFromReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3)});
|
||||
}
|
||||
}
|
||||
|
||||
// Callback methods for the attachment sequence
|
||||
void attachAllUnattachedDevicesFromReq2(
|
||||
std::shared_ptr<AttachAllUnattachedDevicesFromReq> context,
|
||||
bool success, [[maybe_unused]] std::shared_ptr<DeviceRole> deviceRole,
|
||||
std::shared_ptr<DeviceAttachmentSpec> spec
|
||||
)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to attach device: "
|
||||
<< spec->deviceIdentifier << "\n";
|
||||
// Fallthrough.
|
||||
}
|
||||
|
||||
if (!context->loop.incrementSuccessOrFailureAndTestForCompletionDueTo(
|
||||
success))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": " << context->loop.nSucceeded.load()
|
||||
<< " devices attached, "
|
||||
<< context->loop.nFailed.load() << " devices failed\n";
|
||||
}
|
||||
|
||||
context->callOriginalCb(loop);
|
||||
}
|
||||
|
||||
public:
|
||||
AsynchronousLoop loop;
|
||||
std::shared_ptr<std::vector<DeviceAttachmentSpec>> specs;
|
||||
};
|
||||
|
||||
void DeviceManager::attachAllUnattachedDevicesFromReq(
|
||||
const std::shared_ptr<std::vector<DeviceAttachmentSpec>> &specs,
|
||||
Callback<attachAllUnattachedDevicesFromReqCbFn> cb
|
||||
)
|
||||
{
|
||||
if (specs->size() == 0)
|
||||
{
|
||||
AsynchronousLoop tmp(0);
|
||||
cb.callbackFn(tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
auto request = std::make_shared<AttachAllUnattachedDevicesFromReq>(
|
||||
specs->size(), specs, caller, std::move(cb));
|
||||
|
||||
mrntt::mrntt.thread->getIoService().post(
|
||||
std::bind(
|
||||
&AttachAllUnattachedDevicesFromReq
|
||||
::attachAllUnattachedDevicesFromReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq(
|
||||
Callback<attachAllUnattachedDevicesFromReqCbFn> cb
|
||||
)
|
||||
{
|
||||
auto specs = std::make_shared<std::vector<DeviceAttachmentSpec>>(
|
||||
commandLineDASpecs);
|
||||
attachAllUnattachedDevicesFromReq(specs, std::move(cb));
|
||||
}
|
||||
|
||||
class DeviceManager::AttachAllUnattachedDevicesFromKnownListReq
|
||||
: public SerializedAsynchronousContinuation<
|
||||
attachAllUnattachedDevicesFromReqCbFn>
|
||||
{
|
||||
public:
|
||||
AttachAllUnattachedDevicesFromKnownListReq(
|
||||
const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<attachAllUnattachedDevicesFromReqCbFn> cb,
|
||||
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
|
||||
: SerializedAsynchronousContinuation<
|
||||
attachAllUnattachedDevicesFromReqCbFn>(
|
||||
caller, cb, requiredLocks)
|
||||
{}
|
||||
|
||||
public:
|
||||
void attachAllUnattachedDevicesFromKnownListReq1_posted(
|
||||
[[maybe_unused]]
|
||||
std::shared_ptr<AttachAllUnattachedDevicesFromKnownListReq> context
|
||||
)
|
||||
{
|
||||
// Create a vector to hold unattached device specs
|
||||
auto unattachedSpecs = std::make_shared<
|
||||
std::vector<DeviceAttachmentSpec>>();
|
||||
|
||||
// Cycle through all DA specs in deviceAttachmentSpecs
|
||||
for (const auto& spec : DeviceManager::deviceAttachmentSpecs)
|
||||
{
|
||||
bool isAttached = false;
|
||||
|
||||
// Cross reference with attachedDeviceRoles
|
||||
for (const auto& role : DeviceManager::attachedDeviceRoles)
|
||||
{
|
||||
if (*role->deviceAttachmentSpec == *spec)
|
||||
{
|
||||
isAttached = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If spec doesn't appear in attachedDeviceRoles, add it to vector
|
||||
if (!isAttached) {
|
||||
unattachedSpecs->push_back(*spec);
|
||||
}
|
||||
}
|
||||
|
||||
// Release the DeviceManager qutex early before calling the inner method
|
||||
releaseQutexEarly(DeviceManager::getInstance().qutex);
|
||||
|
||||
// Pass the vector to the existing function
|
||||
DeviceManager::getInstance().attachAllUnattachedDevicesFromReq(
|
||||
unattachedSpecs,
|
||||
{context, std::bind(
|
||||
&AttachAllUnattachedDevicesFromKnownListReq
|
||||
::attachAllUnattachedDevicesFromKnownListReq2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1)});
|
||||
}
|
||||
|
||||
void attachAllUnattachedDevicesFromKnownListReq2(
|
||||
[[maybe_unused]]
|
||||
std::shared_ptr<AttachAllUnattachedDevicesFromKnownListReq> context,
|
||||
AsynchronousLoop loop
|
||||
)
|
||||
{
|
||||
callOriginalCb(loop);
|
||||
}
|
||||
};
|
||||
|
||||
void DeviceManager::attachAllUnattachedDevicesFromKnownListReq(
|
||||
Callback<attachAllUnattachedDevicesFromReqCbFn> cb
|
||||
)
|
||||
{
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
|
||||
auto request = std::make_shared<AttachAllUnattachedDevicesFromKnownListReq>(
|
||||
caller, cb,
|
||||
LockSet<attachAllUnattachedDevicesFromReqCbFn>::Set{
|
||||
std::ref(DeviceManager::getInstance().qutex)
|
||||
});
|
||||
|
||||
AttachAllUnattachedDevicesFromKnownListReq::LockerAndInvoker lockvoker(
|
||||
*request, mrntt::mrntt.thread,
|
||||
std::bind(
|
||||
&AttachAllUnattachedDevicesFromKnownListReq
|
||||
::attachAllUnattachedDevicesFromKnownListReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
class DeviceManager::DetachAllAttachedDeviceRoles
|
||||
: public PostedAsynchronousContinuation<
|
||||
detachAllAttachedDeviceRolesCbFn>
|
||||
{
|
||||
public:
|
||||
DetachAllAttachedDeviceRoles(
|
||||
const unsigned int totalNSpecs,
|
||||
const std::shared_ptr<ComponentThread>& caller,
|
||||
Callback<detachAllAttachedDeviceRolesCbFn> cb)
|
||||
: PostedAsynchronousContinuation<detachAllAttachedDeviceRolesCbFn>(
|
||||
caller, cb),
|
||||
loop(totalNSpecs)
|
||||
{}
|
||||
|
||||
void detachAllAttachedDeviceRoles1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<DetachAllAttachedDeviceRoles> context
|
||||
)
|
||||
{
|
||||
for (const auto& deviceRole : DeviceManager::attachedDeviceRoles)
|
||||
{
|
||||
DeviceManager::getInstance().detachSenseDeviceReq(
|
||||
deviceRole->deviceAttachmentSpec,
|
||||
{context, std::bind(
|
||||
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles2,
|
||||
context.get(), context,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
}
|
||||
|
||||
void detachAllAttachedDeviceRoles2(
|
||||
std::shared_ptr<DetachAllAttachedDeviceRoles> context,
|
||||
bool success, std::shared_ptr<DeviceAttachmentSpec> spec
|
||||
)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to detach device: "
|
||||
<< spec->deviceIdentifier << "\n";
|
||||
// Fallthrough.
|
||||
}
|
||||
|
||||
if (!context->loop.incrementSuccessOrFailureAndTestForCompletionDueTo(
|
||||
success))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": " << context->loop.nSucceeded.load()
|
||||
<< " devices detached, "
|
||||
<< context->loop.nFailed.load() << " devices failed\n";
|
||||
}
|
||||
|
||||
context->callOriginalCb(loop);
|
||||
}
|
||||
|
||||
public:
|
||||
AsynchronousLoop loop;
|
||||
};
|
||||
|
||||
void DeviceManager::detachAllAttachedDeviceRoles(
|
||||
Callback<detachAllAttachedDeviceRolesCbFn> cb
|
||||
)
|
||||
{
|
||||
if (DeviceManager::getInstance().attachedDeviceRoles.size() == 0)
|
||||
{
|
||||
AsynchronousLoop tmp(0);
|
||||
cb.callbackFn(tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& caller = ComponentThread::getSelf();
|
||||
auto request = std::make_shared<DetachAllAttachedDeviceRoles>(
|
||||
DeviceManager::getInstance().attachedDeviceRoles.size(),
|
||||
caller, std::move(cb));
|
||||
|
||||
mrntt::mrntt.thread->getIoService().post(
|
||||
std::bind(
|
||||
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void DeviceManager::initializeDeviceReattacher()
|
||||
{
|
||||
deviceReattacher = std::make_unique<DeviceReattacher>(
|
||||
*this, mrntt::mrntt.thread);
|
||||
|
||||
deviceReattacher->start();
|
||||
}
|
||||
|
||||
void DeviceManager::finalizeDeviceReattacher()
|
||||
{
|
||||
if (!deviceReattacher) { return; }
|
||||
|
||||
deviceReattacher->stop();
|
||||
deviceReattacher.reset();
|
||||
}
|
||||
|
||||
} // namespace device
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#include <config.h>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <componentThread.h>
|
||||
#include <callback.h>
|
||||
#include <deviceManager/deviceReattacher.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
|
||||
namespace smo {
|
||||
namespace device {
|
||||
|
||||
static void reattachmentCb(AsynchronousLoop& results)
|
||||
{
|
||||
if (results.nTotal == 0) { return; }
|
||||
|
||||
std::cout << "DeviceReattacher: Successfully reattached "
|
||||
<< results.nSucceeded << " of " << results.nTotal << " devices"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
DeviceReattacher::DeviceReattacher(
|
||||
DeviceManager& parent, std::shared_ptr<ComponentThread> ioThread)
|
||||
: parent(parent), ioThread(ioThread), shouldContinue(false),
|
||||
timer(ioThread->getIoService())
|
||||
{
|
||||
}
|
||||
|
||||
void DeviceReattacher::start()
|
||||
{
|
||||
shouldContinue.store(true);
|
||||
scheduleNextTimeout();
|
||||
}
|
||||
|
||||
void DeviceReattacher::stop()
|
||||
{
|
||||
shouldContinue.store(false);
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
void DeviceReattacher::scheduleNextTimeout()
|
||||
{
|
||||
if (!shouldContinue.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule the next timeout using the configured period
|
||||
timer.expires_from_now(
|
||||
boost::posix_time::milliseconds(
|
||||
CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS));
|
||||
|
||||
timer.async_wait(
|
||||
std::bind(&DeviceReattacher::onTimeout, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void DeviceReattacher::onTimeout(const boost::system::error_code& error)
|
||||
{
|
||||
// Timer was cancelled, which is expected when stopping
|
||||
if (error == boost::asio::error::operation_aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
std::cerr << "DeviceReattacher: Timer error: " << error.message()
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldContinue.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to reattach all unattached devices from the known list
|
||||
parent.attachAllUnattachedDevicesFromKnownListReq(
|
||||
{ nullptr, reattachmentCb});
|
||||
|
||||
// Schedule the next timeout
|
||||
scheduleNextTimeout();
|
||||
}
|
||||
|
||||
} // namespace device
|
||||
} // namespace smo
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef _BODY_COMPONENT_H
|
||||
#define _BODY_COMPONENT_H
|
||||
|
||||
#include <component.h>
|
||||
#include <functional>
|
||||
#include <callback.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class Mind;
|
||||
class ComponentThread;
|
||||
|
||||
namespace body {
|
||||
|
||||
class Body
|
||||
: public MindComponent
|
||||
{
|
||||
public:
|
||||
Body(Mind &parent, const std::shared_ptr<ComponentThread> &thread);
|
||||
~Body() = default;
|
||||
|
||||
typedef std::function<void(bool)> bodyLifetimeMgmtOpCbFn;
|
||||
void initializeReq(Callback<bodyLifetimeMgmtOpCbFn> callback);
|
||||
void finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback);
|
||||
|
||||
private:
|
||||
class InitializeReq;
|
||||
class FinalizeReq;
|
||||
};
|
||||
|
||||
} // namespace body
|
||||
} // namespace smo
|
||||
|
||||
#endif // _BODY_COMPONENT_H
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef COMPONENT_H
|
||||
#define COMPONENT_H
|
||||
|
||||
#include <config.h>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <callback.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class Mind;
|
||||
class ComponentThread;
|
||||
|
||||
class Component
|
||||
{
|
||||
public:
|
||||
Component(const std::shared_ptr<ComponentThread> &thread);
|
||||
~Component() = default;
|
||||
|
||||
public:
|
||||
std::shared_ptr<ComponentThread> thread;
|
||||
|
||||
public:
|
||||
};
|
||||
|
||||
class MindComponent
|
||||
: public Component
|
||||
{
|
||||
public:
|
||||
MindComponent(Mind &parent, const std::shared_ptr<ComponentThread> &thread);
|
||||
~MindComponent() = default;
|
||||
|
||||
public:
|
||||
Mind &parent;
|
||||
};
|
||||
|
||||
namespace mrntt {
|
||||
|
||||
class MarionetteComponent
|
||||
: public Component
|
||||
{
|
||||
public:
|
||||
MarionetteComponent(const std::shared_ptr<ComponentThread> &thread);
|
||||
~MarionetteComponent() = default;
|
||||
|
||||
public:
|
||||
typedef std::function<void(bool)> mrnttLifetimeMgmtOpCbFn;
|
||||
void initializeReq(Callback<mrnttLifetimeMgmtOpCbFn> callback);
|
||||
void finalizeReq(Callback<mrnttLifetimeMgmtOpCbFn> callback);
|
||||
|
||||
private:
|
||||
class MrnttLifetimeMgmtOp;
|
||||
};
|
||||
|
||||
} // namespace mrntt
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif // COMPONENT_H
|
||||
@@ -12,13 +12,15 @@
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <memory>
|
||||
#include <callback.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class Mind; // Forward declaration
|
||||
class MarionetteThread;
|
||||
class MindThread;
|
||||
|
||||
class ComponentThread
|
||||
: public std::enable_shared_from_this<ComponentThread>
|
||||
{
|
||||
public:
|
||||
enum ThreadId
|
||||
@@ -32,77 +34,39 @@ public:
|
||||
N_ITEMS
|
||||
};
|
||||
|
||||
ComponentThread(ThreadId _id, Mind& parent)
|
||||
: id(_id), name(getThreadName(_id)), parent(parent),
|
||||
work(io_service), pause_work(pause_io_service),
|
||||
pinnedCpuId(-1),
|
||||
thread(
|
||||
((id == MRNTT) ? marionetteMain : main),
|
||||
std::ref(*this))
|
||||
protected:
|
||||
ComponentThread(ThreadId _id)
|
||||
: id(_id), name(getThreadName(_id)),
|
||||
work(io_service)
|
||||
{}
|
||||
|
||||
public:
|
||||
virtual ~ComponentThread() = default;
|
||||
|
||||
void cleanup(void);
|
||||
|
||||
boost::asio::io_service& getIoService(void) { return io_service; }
|
||||
|
||||
void initializeTls(void);
|
||||
static const std::shared_ptr<ComponentThread> getSelf(void);
|
||||
|
||||
static std::shared_ptr<ComponentThread> getMrntt();
|
||||
Mind& getParent() const { return parent; }
|
||||
static std::shared_ptr<MarionetteThread> getMrntt();
|
||||
|
||||
typedef void (mainFn)(ComponentThread &self);
|
||||
static mainFn main, marionetteMain;
|
||||
|
||||
// Thread management methods
|
||||
void startThreadReq(std::function<void()> callback = nullptr);
|
||||
void exitThreadReq(std::function<void()> callback = nullptr);
|
||||
void pauseThreadReq(std::function<void()> callback = nullptr);
|
||||
void resumeThreadReq(std::function<void()> callback = nullptr);
|
||||
/**
|
||||
* JOLTs this thread to begin processing after global initialization.
|
||||
*
|
||||
* JOLTing is the mechanism that allows threads to enter their main
|
||||
* event loops and set up TLS vars after all global constructors have
|
||||
* completed. This prevents race conditions during system startup.
|
||||
*/
|
||||
void joltThreadReq(std::function<void()> callback = nullptr);
|
||||
|
||||
// CPU management methods
|
||||
static int getAvailableCpuCount();
|
||||
void pinToCpu(int cpuId);
|
||||
|
||||
enum class ThreadOp
|
||||
{
|
||||
START,
|
||||
PAUSE,
|
||||
RESUME,
|
||||
EXIT,
|
||||
JOLT,
|
||||
N_ITEMS
|
||||
};
|
||||
|
||||
typedef std::function<void()> mindShutdownIndOpCbFn;
|
||||
// Intentionally doesn't take a callback.
|
||||
void exceptionInd(ComponentThread& thread);
|
||||
void exceptionInd(const std::shared_ptr<ComponentThread> &faultyThread);
|
||||
// Intentionally doesn't take a callback.
|
||||
void userShutdownInd();
|
||||
|
||||
public:
|
||||
ThreadId id;
|
||||
std::string name;
|
||||
Mind &parent;
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::io_service::work work;
|
||||
boost::asio::io_service pause_io_service;
|
||||
boost::asio::io_service::work pause_work;
|
||||
std::atomic<bool> keepLooping;
|
||||
int pinnedCpuId;
|
||||
|
||||
|
||||
/* Always ensure that this is last so that the thread is spawned after
|
||||
* everything else is constructed.
|
||||
*/
|
||||
std::thread thread;
|
||||
|
||||
static const std::string getThreadName(ThreadId id)
|
||||
{
|
||||
@@ -126,9 +90,86 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class MarionetteThread
|
||||
: public std::enable_shared_from_this<MarionetteThread>,
|
||||
public ComponentThread
|
||||
{
|
||||
public:
|
||||
MarionetteThread()
|
||||
: ComponentThread(MRNTT),
|
||||
thread(main, std::ref(*this))
|
||||
{
|
||||
}
|
||||
|
||||
static void main(MarionetteThread& self);
|
||||
void initializeTls(void);
|
||||
|
||||
public:
|
||||
std::thread thread;
|
||||
};
|
||||
|
||||
class MindThread
|
||||
: public std::enable_shared_from_this<MindThread>, public ComponentThread
|
||||
{
|
||||
public:
|
||||
enum class ThreadOp
|
||||
{
|
||||
START,
|
||||
PAUSE,
|
||||
RESUME,
|
||||
EXIT,
|
||||
JOLT,
|
||||
N_ITEMS
|
||||
};
|
||||
|
||||
MindThread(ThreadId _id, Mind& parent)
|
||||
: ComponentThread(_id),
|
||||
pinnedCpuId(-1),
|
||||
pause_work(pause_io_service),
|
||||
parent(parent),
|
||||
thread(main, std::ref(*this))
|
||||
{
|
||||
}
|
||||
|
||||
static void main(MindThread& self);
|
||||
void initializeTls(void);
|
||||
|
||||
Mind& getParent() const { return parent; }
|
||||
|
||||
// Thread management methods
|
||||
typedef std::function<void()> threadLifetimeMgmtOpCbFn;
|
||||
void startThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
|
||||
void exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
|
||||
void pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
|
||||
void resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
|
||||
|
||||
/**
|
||||
* JOLTs this thread to begin processing after global initialization.
|
||||
*
|
||||
* JOLTing is the mechanism that allows threads to enter their main
|
||||
* event loops and set up TLS vars after all global constructors have
|
||||
* completed. This prevents race conditions during system startup.
|
||||
*/
|
||||
void joltThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
|
||||
|
||||
// CPU management methods
|
||||
void pinToCpu(int cpuId);
|
||||
|
||||
public:
|
||||
int pinnedCpuId;
|
||||
boost::asio::io_service pause_io_service;
|
||||
boost::asio::io_service::work pause_work;
|
||||
Mind& parent;
|
||||
std::thread thread;
|
||||
|
||||
public:
|
||||
class ThreadLifetimeMgmtOp;
|
||||
class MindShutdownIndOp;
|
||||
};
|
||||
|
||||
namespace mrntt {
|
||||
extern std::shared_ptr<ComponentThread> mrntt;
|
||||
}
|
||||
extern std::shared_ptr<MarionetteThread> thread;
|
||||
} // namespace mrntt
|
||||
|
||||
} // namespace smo
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <deviceManager/deviceRole.h>
|
||||
#include <qutex.h>
|
||||
|
||||
namespace smo {
|
||||
namespace device {
|
||||
@@ -13,21 +15,25 @@ namespace device {
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
std::string deviceIdentifier;
|
||||
std::vector<std::shared_ptr<DeviceAttachmentSpec>> deviceAttachmentSpecs;
|
||||
|
||||
Device(const std::string& identifier) : deviceIdentifier(identifier) {}
|
||||
Device(const std::string& identifier)
|
||||
: deviceIdentifier(identifier), qutex("Device-" + identifier)
|
||||
{}
|
||||
|
||||
std::string stringify() const
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Device Identifier: " << deviceIdentifier
|
||||
<< ", Attachment Specs: " << deviceAttachmentSpecs.size() << std::endl;
|
||||
for (const auto& spec : deviceAttachmentSpecs) {
|
||||
os << " " << spec->stringify();
|
||||
<< ", Device Roles: " << deviceRoles.size() << std::endl;
|
||||
for (const auto& deviceRole : deviceRoles) {
|
||||
os << " " << deviceRole->deviceAttachmentSpec->stringify();
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
public:
|
||||
std::string deviceIdentifier;
|
||||
std::vector<std::shared_ptr<DeviceRole>> deviceRoles;
|
||||
Qutex qutex;
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
|
||||
@@ -7,13 +7,21 @@
|
||||
#include <opts.h>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <deviceManager/device.h>
|
||||
#include <functional>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <deviceManager/device.h>
|
||||
#include <deviceManager/deviceRole.h>
|
||||
#include <deviceManager/deviceReattacher.h>
|
||||
#include <callback.h>
|
||||
#include <qutex.h>
|
||||
|
||||
namespace smo {
|
||||
namespace device {
|
||||
|
||||
class DeviceReattacher;
|
||||
|
||||
class DeviceManager
|
||||
{
|
||||
public:
|
||||
@@ -23,32 +31,88 @@ public:
|
||||
return instance;
|
||||
}
|
||||
|
||||
void initialize(void)
|
||||
{};
|
||||
void finalize(void)
|
||||
{};
|
||||
|
||||
void initializeDeviceReattacher();
|
||||
void finalizeDeviceReattacher();
|
||||
|
||||
std::string readDapSpecFile(const std::string& filename);
|
||||
void collateAllDapSpecs(void);
|
||||
void parseAllDapSpecs(void);
|
||||
|
||||
static const std::string stringifyDeviceSpecs(void);
|
||||
|
||||
// New async function for device attachment
|
||||
typedef std::function<void(
|
||||
bool success, std::shared_ptr<DeviceRole> deviceRole,
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceSpec)>
|
||||
newDeviceAttachmentSpecIndCbFn;
|
||||
typedef std::function<void(
|
||||
bool success, std::shared_ptr<DeviceAttachmentSpec> deviceSpec)>
|
||||
removeDeviceAttachmentSpecReqCbFn;
|
||||
|
||||
void newDeviceAttachmentSpecInd(
|
||||
std::shared_ptr<DeviceAttachmentSpec> spec,
|
||||
std::function<void(bool success, std::shared_ptr<Device> device, std::shared_ptr<DeviceAttachmentSpec> deviceSpec)> callback);
|
||||
const DeviceAttachmentSpec &spec,
|
||||
Callback<newDeviceAttachmentSpecIndCbFn> callback);
|
||||
void removeDeviceAttachmentSpecReq(
|
||||
const DeviceAttachmentSpec &spec,
|
||||
Callback<removeDeviceAttachmentSpecReqCbFn> callback);
|
||||
|
||||
// Device attachment/detachment methods moved from SenseApiManager
|
||||
typedef sense_api::sal_mlo_attachDeviceReqCbFn attachSenseDeviceReqCbFn;
|
||||
typedef sense_api::sal_mlo_detachDeviceReqCbFn detachSenseDeviceReqCbFn;
|
||||
|
||||
void attachSenseDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
Callback<attachSenseDeviceReqCbFn> cb);
|
||||
void detachSenseDeviceReq(
|
||||
const std::shared_ptr<DeviceAttachmentSpec>& spec,
|
||||
Callback<detachSenseDeviceReqCbFn> cb);
|
||||
|
||||
typedef std::function<void(AsynchronousLoop &results)>
|
||||
attachAllUnattachedDevicesFromReqCbFn;
|
||||
typedef std::function<void(AsynchronousLoop &results)>
|
||||
detachAllAttachedDeviceRolesCbFn;
|
||||
|
||||
void attachAllUnattachedDevicesFromReq(
|
||||
const std::shared_ptr<std::vector<DeviceAttachmentSpec>> &specs,
|
||||
Callback<attachAllUnattachedDevicesFromReqCbFn> cb);
|
||||
void attachAllUnattachedDevicesFromKnownListReq(
|
||||
Callback<attachAllUnattachedDevicesFromReqCbFn> cb);
|
||||
void attachAllUnattachedDevicesFromCmdlineReq(
|
||||
Callback<attachAllUnattachedDevicesFromReqCbFn> cb);
|
||||
void detachAllAttachedDeviceRoles(
|
||||
Callback<detachAllAttachedDeviceRolesCbFn> cb);
|
||||
|
||||
private:
|
||||
DeviceManager() = default;
|
||||
~DeviceManager() = default;
|
||||
DeviceManager()
|
||||
: qutex("DeviceManager"), deviceReattacher(nullptr)
|
||||
{}
|
||||
~DeviceManager();
|
||||
DeviceManager(const DeviceManager&) = delete;
|
||||
DeviceManager& operator=(const DeviceManager&) = delete;
|
||||
|
||||
public:
|
||||
Qutex qutex;
|
||||
std::string allDapSpecs;
|
||||
static std::vector<std::shared_ptr<InteroceptorDevAttachmentSpec>>
|
||||
interoceptorDeviceSpecs;
|
||||
static std::vector<std::shared_ptr<ExtrospectorDevAttachmentSpec>>
|
||||
extrospectorDeviceSpecs;
|
||||
static std::vector<std::shared_ptr<DeviceAttachmentSpec>>
|
||||
deviceAttachmentSpecs;
|
||||
static std::vector<std::shared_ptr<Device>> devices;
|
||||
static std::vector<std::shared_ptr<DeviceRole>> attachedDeviceRoles;
|
||||
static std::vector<DeviceAttachmentSpec> commandLineDASpecs;
|
||||
|
||||
private:
|
||||
std::unique_ptr<DeviceReattacher> deviceReattacher;
|
||||
|
||||
class NewDeviceAttachmentSpecInd;
|
||||
class RemoveDeviceAttachmentSpecReq;
|
||||
class AttachSenseDeviceReq;
|
||||
typedef AttachSenseDeviceReq DetachSenseDeviceReq;
|
||||
class AttachAllUnattachedDevicesFromReq;
|
||||
class AttachAllUnattachedDevicesFromKnownListReq;
|
||||
class DetachAllAttachedDeviceRoles;
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef DEVICEREATTACHER_H
|
||||
#define DEVICEREATTACHER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class ComponentThread;
|
||||
|
||||
namespace device {
|
||||
|
||||
class DeviceManager;
|
||||
|
||||
class DeviceReattacher
|
||||
{
|
||||
public:
|
||||
DeviceReattacher(
|
||||
DeviceManager& parent, std::shared_ptr<ComponentThread> ioThread);
|
||||
~DeviceReattacher() = default;
|
||||
|
||||
// Non-copyable
|
||||
DeviceReattacher(const DeviceReattacher&) = delete;
|
||||
DeviceReattacher& operator=(const DeviceReattacher&) = delete;
|
||||
|
||||
// Control methods
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void scheduleNextTimeout();
|
||||
void onTimeout(const boost::system::error_code& error);
|
||||
|
||||
DeviceManager &parent;
|
||||
std::shared_ptr<ComponentThread> ioThread;
|
||||
std::atomic<bool> shouldContinue;
|
||||
boost::asio::deadline_timer timer;
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
} // namespace smo
|
||||
|
||||
#endif // DEVICEREATTACHER_H
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef DEVICEROLE_H
|
||||
#define DEVICEROLE_H
|
||||
|
||||
#include <memory>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
|
||||
namespace smo {
|
||||
namespace device {
|
||||
|
||||
class Device; // Forward declaration
|
||||
|
||||
class DeviceRole
|
||||
{
|
||||
public:
|
||||
DeviceRole(
|
||||
Device& parentDevice,
|
||||
std::shared_ptr<DeviceAttachmentSpec>& spec)
|
||||
: parentDevice(parentDevice), deviceAttachmentSpec(spec)
|
||||
{}
|
||||
|
||||
Device& parentDevice;
|
||||
std::shared_ptr<DeviceAttachmentSpec> deviceAttachmentSpec;
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
} // namespace smo
|
||||
|
||||
#endif // DEVICEROLE_H
|
||||
@@ -2,15 +2,25 @@
|
||||
#define DIRECTOR_H
|
||||
|
||||
#include <config.h>
|
||||
#include <component.h>
|
||||
#include <goal.h>
|
||||
#include <lruLifo.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class Mind;
|
||||
class ComponentThread;
|
||||
|
||||
namespace director {
|
||||
|
||||
class Director {
|
||||
class Director
|
||||
: public MindComponent
|
||||
{
|
||||
public:
|
||||
Director() = default;
|
||||
Director(Mind &parent, const std::shared_ptr<ComponentThread> &thread)
|
||||
: MindComponent(parent, thread)
|
||||
{}
|
||||
|
||||
~Director() = default;
|
||||
|
||||
/** EXPLANATION:
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef _BODY_H
|
||||
#define _BODY_H
|
||||
|
||||
namespace mrntt {
|
||||
namespace body {
|
||||
|
||||
class Body
|
||||
{
|
||||
public:
|
||||
Body() = default;
|
||||
~Body() = default;
|
||||
};
|
||||
|
||||
} // namespace body
|
||||
} // namespace mrntt
|
||||
|
||||
#endif // _BODY_H
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef _MRNTT_BODYMAP_BODY_H
|
||||
#define _MRNTT_BODYMAP_BODY_H
|
||||
|
||||
namespace mrntt {
|
||||
namespace bodyMap {
|
||||
|
||||
class Body
|
||||
{
|
||||
public:
|
||||
Body() = default;
|
||||
~Body() = default;
|
||||
};
|
||||
|
||||
} // namespace bodyMap
|
||||
} // namespace mrntt
|
||||
|
||||
#endif // _MRNTT_BODYMAP_BODY_H
|
||||
@@ -1,12 +1,12 @@
|
||||
#ifndef MRNTT_BODY_BODYMAP_H
|
||||
#define MRNTT_BODY_BODYMAP_H
|
||||
#ifndef MRNTT_BODYMAP_BODYMAP_H
|
||||
#define MRNTT_BODYMAP_BODYMAP_H
|
||||
|
||||
#include <set>
|
||||
#include <cstdint>
|
||||
#include <body/limb.h>
|
||||
#include <bodyMap/limb.h>
|
||||
|
||||
namespace mrntt {
|
||||
namespace body {
|
||||
namespace bodyMap {
|
||||
|
||||
class BodyMap {
|
||||
public:
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
std::set<uint32_t, Limb> limbs;
|
||||
};
|
||||
|
||||
} // namespace body
|
||||
} // namespace bodyMap
|
||||
} // namespace mrntt
|
||||
|
||||
#endif // MRNTT_BODY_BODYMAP_H
|
||||
#endif // MRNTT_BODYMAP_BODYMAP_H
|
||||
@@ -1,14 +1,14 @@
|
||||
#ifndef MRNTT_BODY_BODYMESSAGE_H
|
||||
#define MRNTT_BODY_BODYMESSAGE_H
|
||||
#ifndef MRNTT_BODYMAP_BODYMESSAGE_H
|
||||
#define MRNTT_BODYMAP_BODYMESSAGE_H
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#include <body/limb.h>
|
||||
#include <body/part.h>
|
||||
#include <bodyMap/limb.h>
|
||||
#include <bodyMap/part.h>
|
||||
|
||||
namespace mrntt {
|
||||
namespace body {
|
||||
namespace bodyMap {
|
||||
|
||||
class BodyMessage
|
||||
{
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
const Part& part;
|
||||
};
|
||||
|
||||
} // namespace body
|
||||
} // namespace bodyMap
|
||||
} // namespace mrntt
|
||||
|
||||
#endif // MRNTT_BODY_BODYMESSAGE_H
|
||||
#endif // MRNTT_BODYMAP_BODYMESSAGE_H
|
||||
@@ -1,14 +1,14 @@
|
||||
#ifndef MRNTT_BODY_LIMB_H
|
||||
#define MRNTT_BODY_LIMB_H
|
||||
#ifndef MRNTT_BODYMAP_LIMB_H
|
||||
#define MRNTT_BODYMAP_LIMB_H
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <cstdint>
|
||||
|
||||
#include <body/part.h>
|
||||
#include <bodyMap/part.h>
|
||||
|
||||
namespace mrntt {
|
||||
namespace body {
|
||||
namespace bodyMap {
|
||||
|
||||
class Limb
|
||||
{
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
std::set<uint32_t, Part> parts;
|
||||
};
|
||||
|
||||
} // namespace body
|
||||
} // namespace bodyMap
|
||||
} // namespace mrntt
|
||||
|
||||
#endif // MRNTT_BODY_LIMB_H
|
||||
#endif // MRNTT_BODYMAP_LIMB_H
|
||||
@@ -1,13 +1,13 @@
|
||||
#ifndef _BODY_MAP_H
|
||||
#define _BODY_MAP_H
|
||||
#ifndef _BODYMAP_MAP_H
|
||||
#define _BODYMAP_MAP_H
|
||||
|
||||
#include <set>
|
||||
#include <cstdint>
|
||||
|
||||
#include <body/limb.h>
|
||||
#include <bodyMap/limb.h>
|
||||
|
||||
namespace mrntt {
|
||||
namespace body {
|
||||
namespace bodyMap {
|
||||
|
||||
class BodyMap {
|
||||
public:
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
std::set<uint32_t, Limb> limbs;
|
||||
};
|
||||
|
||||
} // namespace body
|
||||
} // namespace bodyMap
|
||||
} // namespace mrntt
|
||||
|
||||
#endif // _BODY_MAP_H
|
||||
#endif // _BODYMAP_MAP_H
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef BODYPART_H
|
||||
#define BODYPART_H
|
||||
#ifndef BODYMAP_PART_H
|
||||
#define BODYMAP_PART_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <sensors/interoceptor.h>
|
||||
|
||||
namespace mrntt {
|
||||
namespace body {
|
||||
namespace bodyMap {
|
||||
|
||||
class Spot
|
||||
{
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
std::set<uint32_t, Spot> spots;
|
||||
};
|
||||
|
||||
} // namespace body
|
||||
} // namespace bodyMap
|
||||
} // namespace mrntt
|
||||
|
||||
#endif // BODYPART_H
|
||||
#endif // BODYMAP_PART_H
|
||||
@@ -3,17 +3,20 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <componentThread.h>
|
||||
#include <component.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class MarionetteThread;
|
||||
|
||||
namespace mrntt {
|
||||
|
||||
extern std::atomic<int> exitCode;
|
||||
|
||||
class Marionette
|
||||
{
|
||||
};
|
||||
|
||||
void exitMarionetteLoop();
|
||||
void marionetteFinalizeReqCb(bool success);
|
||||
extern mrntt::MarionetteComponent mrntt;
|
||||
|
||||
} // namespace mrntt
|
||||
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <callback.h>
|
||||
|
||||
#include <component.h>
|
||||
#include <componentThread.h>
|
||||
#include <director/director.h>
|
||||
#include <simulator/simulator.h>
|
||||
#include <componentThread.h>
|
||||
#include <body/body.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
@@ -20,37 +23,45 @@ public:
|
||||
Mind(void);
|
||||
~Mind(void) = default;
|
||||
|
||||
void initialize(void);
|
||||
void execute(void);
|
||||
void finalizeReq(std::function<void()> callback);
|
||||
typedef std::function<void(bool)> mindLifetimeMgmtOpCbFn;
|
||||
void initializeReq(Callback<mindLifetimeMgmtOpCbFn> callback);
|
||||
void finalizeReq(Callback<mindLifetimeMgmtOpCbFn> callback);
|
||||
|
||||
// ComponentThread access methods
|
||||
std::shared_ptr<ComponentThread> getComponentThread(
|
||||
std::shared_ptr<MindThread> getComponentThread(
|
||||
ComponentThread::ThreadId id) const;
|
||||
std::shared_ptr<ComponentThread> getComponentThread(
|
||||
std::shared_ptr<MindThread> getComponentThread(
|
||||
const std::string& name) const;
|
||||
// Get all this Mind's component threads.
|
||||
std::vector<std::shared_ptr<ComponentThread>> getMindThreads() const;
|
||||
std::vector<std::shared_ptr<MindThread>> getMindThreads() const;
|
||||
|
||||
// Thread management methods (moved from ComponentThread)
|
||||
void startAllMindThreadsReq(std::function<void()> callback = nullptr);
|
||||
void pauseAllMindThreadsReq(std::function<void()> callback = nullptr);
|
||||
void resumeAllMindThreadsReq(std::function<void()> callback = nullptr);
|
||||
void exitAllMindThreadsReq(std::function<void()> callback = nullptr);
|
||||
void joltAllMindThreadsReq(std::function<void()> callback = nullptr);
|
||||
typedef std::function<void()> mindThreadLifetimeMgmtOpCbFn;
|
||||
void joltAllMindThreadsReq(Callback<mindThreadLifetimeMgmtOpCbFn> callback);
|
||||
void startAllMindThreadsReq(
|
||||
Callback<mindThreadLifetimeMgmtOpCbFn> callback);
|
||||
void pauseAllMindThreadsReq(
|
||||
Callback<mindThreadLifetimeMgmtOpCbFn> callback);
|
||||
void resumeAllMindThreadsReq(
|
||||
Callback<mindThreadLifetimeMgmtOpCbFn> callback);
|
||||
void exitAllMindThreadsReq(Callback<mindThreadLifetimeMgmtOpCbFn> callback);
|
||||
|
||||
// CPU distribution method
|
||||
void distributeAndPinThreadsAcrossCpus();
|
||||
|
||||
public:
|
||||
std::thread directorThread;
|
||||
std::thread simulatorThread;
|
||||
std::thread subconsciousThread;
|
||||
private:
|
||||
// Collection of ComponentThread instances (excluding marionette)
|
||||
std::vector<std::shared_ptr<MindThread>> componentThreads;
|
||||
|
||||
public:
|
||||
director::Director director;
|
||||
simulator::Simulator canvas;
|
||||
MindComponent subconscious;
|
||||
body::Body body;
|
||||
MindComponent world;
|
||||
|
||||
private:
|
||||
friend class body::Body;
|
||||
/**
|
||||
* Indicates whether all mind threads have been JOLTed at least once.
|
||||
*
|
||||
@@ -70,13 +81,18 @@ private:
|
||||
* This flag ensures that JOLTing happens exactly once and provides
|
||||
* a synchronization point for the entire system initialization.
|
||||
*/
|
||||
bool threadsHaveBeenJolted = false;
|
||||
// Collection of ComponentThread instances (excluding marionette)
|
||||
std::vector<std::shared_ptr<ComponentThread>> componentThreads;
|
||||
bool threadsHaveBeenJolted = false,
|
||||
bodyComponentInitialized = false;
|
||||
|
||||
private:
|
||||
class MindLifetimeMgmtOp;
|
||||
class MindThreadLifetimeMgmtOp;
|
||||
};
|
||||
|
||||
namespace mind {
|
||||
// Global Mind instance will be defined in marionette.cpp
|
||||
extern std::shared_ptr<Mind> globalMind;
|
||||
} // namespace mind
|
||||
|
||||
} // namespace smo
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef _SMO_MIND_MANAGER_H
|
||||
#define _SMO_MIND_MANAGER_H
|
||||
|
||||
#include <config.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <mind.h>
|
||||
|
||||
namespace smo {
|
||||
namespace mind {
|
||||
|
||||
/** EXPLANATION:
|
||||
* MindManager is responsible for managing the lifecycle of all minds.
|
||||
* It is responsible for creating, destroying, and managing the minds.
|
||||
*
|
||||
* For now it does nothing since we haven't yet added support for multiple
|
||||
* minds.
|
||||
*/
|
||||
class MindManager
|
||||
{
|
||||
public:
|
||||
MindManager(void) = default;
|
||||
~MindManager(void) = default;
|
||||
|
||||
static MindManager& getInstance()
|
||||
{
|
||||
static MindManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public:
|
||||
void initialize(void) {};
|
||||
void finalize(void) {};
|
||||
|
||||
std::shared_ptr<Mind> getMind(void) const;
|
||||
void addMind(const std::shared_ptr<Mind>& mind);
|
||||
void removeMind(const std::shared_ptr<Mind>& mind);
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<Mind>> minds;
|
||||
};
|
||||
|
||||
} // namespace mind
|
||||
} // namespace smo
|
||||
|
||||
#endif // _SMO_MIND_MANAGER_H
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <quale.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class PleasurableQuale
|
||||
: public NonNeutralQuale
|
||||
{
|
||||
@@ -17,4 +19,6 @@ public:
|
||||
virtual void eventInd(void);
|
||||
};
|
||||
|
||||
} // namespace smo
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#define _QUALE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <mentalEntity.h>
|
||||
#include <mentenon.h>
|
||||
#include <attentionTrigger.h>
|
||||
#include <implexa.h>
|
||||
#include <implex/implix.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#ifndef _SALMANOFF_H
|
||||
#define _SALMANOFF_H
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <componentThread.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
void initializeSalmanoff(void);
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <dlfcn.h>
|
||||
#include <functional>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include <qutex.h>
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
@@ -29,7 +31,7 @@ public:
|
||||
SenseApiLib(
|
||||
const std::string& path, void *_dlopen_handle,
|
||||
SMO_GET_SENSE_API_DESC_FN_TYPEDEF *descFn)
|
||||
: libraryPath(path),
|
||||
: libraryPath(path), qutex("SenseApiLib-" + path), isBeingDestroyed(false),
|
||||
dlopen_handle(_dlopen_handle, DlCloser()),
|
||||
SMO_GET_SENSE_API_DESC_FN_NAME(descFn)
|
||||
{}
|
||||
@@ -48,6 +50,8 @@ public:
|
||||
|
||||
public:
|
||||
std::string libraryPath;
|
||||
Qutex qutex;
|
||||
std::atomic<bool> isBeingDestroyed;
|
||||
std::unique_ptr<void, DlCloser> dlopen_handle;
|
||||
/* UNIMPLEMENTED: API-specific cmdline options. These affect this specific
|
||||
* sense api lib's behaviour globally.
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <componentThread.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <senseApis/senseApiLib.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
#include <qutex.h>
|
||||
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
@@ -22,7 +26,15 @@ public:
|
||||
return instance;
|
||||
}
|
||||
|
||||
SenseApiLib& loadSenseApiLib(const std::string& libraryPath);
|
||||
void initialize(void)
|
||||
{};
|
||||
void finalize(void)
|
||||
{};
|
||||
|
||||
SenseApiLib& loadSenseApiLib(
|
||||
const std::string& libraryPath,
|
||||
const std::shared_ptr<ComponentThread>& componentThread);
|
||||
|
||||
std::optional<std::shared_ptr<SenseApiLib>> getSenseApiLib(
|
||||
const std::string& libraryPath);
|
||||
std::optional<std::shared_ptr<SenseApiLib>> getSenseApiLibByApiName(
|
||||
@@ -32,22 +44,19 @@ public:
|
||||
void initializeSenseApiLib(SenseApiLib& lib);
|
||||
void finalizeSenseApiLib(SenseApiLib& lib);
|
||||
|
||||
void loadAllSenseApiLibsFromOptions(void);
|
||||
void loadAllSenseApiLibsFromOptions(
|
||||
const std::shared_ptr<ComponentThread>& componentThread);
|
||||
|
||||
void unloadAllSenseApiLibs(void);
|
||||
void initializeAllSenseApiLibs(void);
|
||||
void finalizeAllSenseApiLibs(void);
|
||||
|
||||
void attachAllSenseDevicesFromSpecs(void);
|
||||
void attachSenseDevice(
|
||||
const std::shared_ptr<device::DeviceAttachmentSpec>& spec);
|
||||
void detachSenseDevice(
|
||||
const std::shared_ptr<device::DeviceAttachmentSpec>& spec);
|
||||
void detachAllSenseDevices(void);
|
||||
|
||||
std::string stringifyLibs() const;
|
||||
|
||||
private:
|
||||
SenseApiManager() = default;
|
||||
SenseApiManager()
|
||||
: qutex("SenseApiManager")
|
||||
{}
|
||||
~SenseApiManager() = default;
|
||||
|
||||
SenseApiManager(const SenseApiManager&) = delete;
|
||||
@@ -55,6 +64,9 @@ private:
|
||||
|
||||
std::vector<std::shared_ptr<SenseApiLib>> senseApiLibs;
|
||||
|
||||
public:
|
||||
Qutex qutex;
|
||||
|
||||
public:
|
||||
static std::optional<std::string> searchForLibInSmoSearchPaths(
|
||||
const std::string& libraryPath);
|
||||
|
||||
@@ -2,14 +2,24 @@
|
||||
#define SIMULATOR_H
|
||||
|
||||
#include <config.h>
|
||||
#include <component.h>
|
||||
#include <simulator/scene.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
class Mind;
|
||||
class ComponentThread;
|
||||
|
||||
namespace simulator {
|
||||
|
||||
class Simulator {
|
||||
class Simulator
|
||||
: public MindComponent
|
||||
{
|
||||
public:
|
||||
Simulator() = default;
|
||||
Simulator(Mind &parent, const std::shared_ptr<ComponentThread> &thread)
|
||||
: MindComponent(parent, thread)
|
||||
{}
|
||||
|
||||
~Simulator() = default;
|
||||
|
||||
void initialize();
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#include <lockerAndInvokerBase.h>
|
||||
|
||||
namespace smo {
|
||||
|
||||
} // namespace smo
|
||||
@@ -1,13 +0,0 @@
|
||||
add_library(marionette STATIC
|
||||
marionette.cpp
|
||||
salmanoff.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(marionette
|
||||
smocore
|
||||
)
|
||||
|
||||
target_include_directories(marionette PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../include
|
||||
)
|
||||
@@ -0,0 +1,140 @@
|
||||
#include <iostream>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <asynchronousLoop.h>
|
||||
#include <callback.h>
|
||||
#include <component.h>
|
||||
#include <componentThread.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
#include <mindManager/mindManager.h>
|
||||
#include <marionette/marionette.h>
|
||||
|
||||
namespace smo {
|
||||
namespace mrntt {
|
||||
|
||||
class MarionetteComponent::MrnttLifetimeMgmtOp
|
||||
: public PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>
|
||||
{
|
||||
public:
|
||||
MrnttLifetimeMgmtOp(
|
||||
MarionetteComponent &parent, const std::shared_ptr<ComponentThread> &caller,
|
||||
Callback<mrnttLifetimeMgmtOpCbFn> callback)
|
||||
: PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>(
|
||||
caller, callback),
|
||||
parent(parent)
|
||||
{}
|
||||
|
||||
private:
|
||||
MarionetteComponent &parent;
|
||||
|
||||
public:
|
||||
void initializeReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<MrnttLifetimeMgmtOp> context
|
||||
)
|
||||
{
|
||||
auto self = ComponentThread::getSelf();
|
||||
if (self->id != ComponentThread::MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Must be executed on Marionette thread");
|
||||
}
|
||||
|
||||
smo::mind::globalMind = std::make_shared<Mind>();
|
||||
smo::mind::globalMind->initializeReq({context, std::bind(
|
||||
&MrnttLifetimeMgmtOp::initializeReq2,
|
||||
this, context, std::placeholders::_1)});
|
||||
}
|
||||
|
||||
void initializeReq2(
|
||||
std::shared_ptr<MrnttLifetimeMgmtOp> context,
|
||||
bool success
|
||||
)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to initialize globalMind"
|
||||
<< std::endl;
|
||||
context->callOriginalCb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
device::DeviceManager::getInstance().initializeDeviceReattacher();
|
||||
context->callOriginalCb(success);
|
||||
}
|
||||
|
||||
void finalizeReq1_posted(
|
||||
[[maybe_unused]] std::shared_ptr<MrnttLifetimeMgmtOp> context
|
||||
)
|
||||
{
|
||||
auto self = ComponentThread::getSelf();
|
||||
if (self->id != ComponentThread::MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Must be executed on Marionette thread");
|
||||
}
|
||||
|
||||
device::DeviceManager::getInstance().finalizeDeviceReattacher();
|
||||
|
||||
smo::mind::globalMind->finalizeReq({context, std::bind(
|
||||
&MrnttLifetimeMgmtOp::finalizeReq2,
|
||||
this, context, std::placeholders::_1)});
|
||||
}
|
||||
|
||||
void finalizeReq2(
|
||||
std::shared_ptr<MrnttLifetimeMgmtOp> context,
|
||||
bool success
|
||||
)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << __func__ << ": globalMind finalization failed"
|
||||
<< std::endl;
|
||||
context->callOriginalCb(false);
|
||||
return;
|
||||
}
|
||||
|
||||
context->callOriginalCb(success);
|
||||
}
|
||||
};
|
||||
|
||||
void MarionetteComponent::initializeReq(
|
||||
Callback<mrnttLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
auto mrntt = ComponentThread::getSelf();
|
||||
|
||||
if (mrntt->id != ComponentThread::MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Must be executed on Marionette thread");
|
||||
}
|
||||
|
||||
auto request = std::make_shared<MrnttLifetimeMgmtOp>(
|
||||
*this, mrntt, callback);
|
||||
|
||||
mrntt->getIoService().post(
|
||||
std::bind(
|
||||
&MrnttLifetimeMgmtOp::initializeReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
void MarionetteComponent::finalizeReq(
|
||||
Callback<mrnttLifetimeMgmtOpCbFn> callback)
|
||||
{
|
||||
auto mrntt = ComponentThread::getSelf();
|
||||
|
||||
if (mrntt->id != ComponentThread::MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Must be executed on Marionette thread");
|
||||
}
|
||||
|
||||
auto request = std::make_shared<MrnttLifetimeMgmtOp>(
|
||||
*this, mrntt, callback);
|
||||
|
||||
mrntt->getIoService().post(
|
||||
std::bind(
|
||||
&MrnttLifetimeMgmtOp::finalizeReq1_posted,
|
||||
request.get(), request));
|
||||
}
|
||||
|
||||
} // namespace mrntt
|
||||
} // namespace smo
|
||||