Compare commits
366 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 | |||
| 0559d9ea42 | |||
| 1c78fc5c31 | |||
| 3b224501c7 | |||
| 0dc8abaa28 | |||
| eb069c4a96 | |||
| 32e76c2ca5 | |||
| 4827177703 | |||
| d36d03dcc3 | |||
| 6362298016 | |||
| 6314b0182a | |||
| 0fefa5be7f | |||
| 7ea31cdb8d | |||
| d217354689 | |||
| bb7be7fb3c | |||
| ee8fca3008 | |||
| 525af45fd4 | |||
| 3ff329a553 | |||
| 66257bcd0e | |||
| 6ef86eea05 | |||
| 8f41e164a2 | |||
| b9ca38bff1 | |||
| e024ccdf95 | |||
| f3f2384f9b | |||
| cd63593ae5 | |||
| 58dbc7e320 | |||
| e3795c4233 | |||
| edc198dd00 | |||
| 2bf7390f97 | |||
| 1ea1b4b9f6 | |||
| c14208f6ce | |||
| f4ff8f0e40 | |||
| ba3841c30b | |||
| 4a8cb12294 | |||
| b6b2ce7ada | |||
| 7bee9b07ae | |||
| d26b791dd2 | |||
| 7579446388 | |||
| 099d60bcc4 | |||
| 42b32f27e6 | |||
| c457ee7aca | |||
| e2e589dc17 | |||
| d4898bbca1 | |||
| e836b2bf32 | |||
| fef73692f7 | |||
| 285b63b618 | |||
| 1deb92a416 | |||
| 6f6fa77498 | |||
| 79825e4da3 | |||
| 6114a2648d | |||
| 4a3daaf403 | |||
| 49c8b5bca1 | |||
| a2598e80fd | |||
| 4fde28dad8 | |||
| e276fcbdce | |||
| 36c79f3a2e | |||
| 513405a831 | |||
| 7b962a75d3 | |||
| 270437fdd4 | |||
| a17c940377 | |||
| b28239550e | |||
| 1e17b83061 | |||
| 1bf5f46404 | |||
| 064dc43fbc | |||
| 020a4968e5 | |||
| d0aa8e2306 | |||
| 79f3e84ff8 | |||
| 756571b9b4 | |||
| 9c16aeeb55 | |||
| d6a0b0301e | |||
| e87656fd12 | |||
| f06aeb6c9b | |||
| c58d422158 | |||
| 8dfb1e5b2f | |||
| 50b8aaf34d | |||
| 0f5e499d7c | |||
| 710749c399 | |||
| 3503cce0db | |||
| 90a0eebdd8 | |||
| 1540af1e74 | |||
| 065b2593f4 | |||
| e7974db324 | |||
| 336094ef90 | |||
| d43a8af6bd | |||
| 37ad6995c3 | |||
| c9e8a9f1fb | |||
| dacc050bf9 | |||
| f8825942b1 | |||
| e201b5e695 | |||
| 29a1e1ecf2 | |||
| a5c2f47e9f | |||
| 42f55bb324 | |||
| 1450d745ab | |||
| 76141e3a92 | |||
| 36592293dd | |||
| f1696f8272 | |||
| 99c126a08c | |||
| 293c1054d1 | |||
| 38298a8ef8 | |||
| 2b8b176038 | |||
| 76e465bd1d | |||
| ead7d8ff5f | |||
| 9cc7a6685c | |||
| 8237cd62da | |||
| 181759ff26 | |||
| 7b79636681 | |||
| 4dee8c62c9 | |||
| 9a9f5058ed | |||
| ff56bfce04 | |||
| 098b79b331 | |||
| 20154d1e95 | |||
| 64baa7906b | |||
| d31530e0bd | |||
| a80db04dac | |||
| 0a6f7feeca | |||
| 091d7ceeba | |||
| cfdeb17639 | |||
| aaae3dcbb2 | |||
| 4eb0ef75bc | |||
| 09caf314f1 | |||
| a4f96c8dfa | |||
| 660f0f0e73 | |||
| edf51a4441 | |||
| 8e94e829d0 | |||
| 3f9b406fb2 | |||
| 0a36f7d370 | |||
| b85d6f76a6 | |||
| c8a7a6678f | |||
| c6577b1155 | |||
| 8aa28a877e | |||
| bffc32519b | |||
| 876526364b | |||
| ce2d47e6b9 | |||
| 870b8de249 | |||
| 4a9d2cb546 | |||
| c696db9e45 | |||
| d2d5b8960f | |||
| 49d03df73b | |||
| 9e35748d9a |
+5
-1
@@ -1,10 +1,14 @@
|
||||
/build
|
||||
/b-*
|
||||
*~
|
||||
**/.deps/
|
||||
**/Makefile.in
|
||||
aclocal.m4
|
||||
/b
|
||||
/bautotools
|
||||
/autotools-aux
|
||||
autom4te.cache/
|
||||
config.h.in
|
||||
configure
|
||||
|
||||
*.swp
|
||||
cscope.out
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "third_party/googletest"]
|
||||
path = third_party/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
Vendored
+5
-5
@@ -3,12 +3,11 @@
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/include",
|
||||
"${workspaceFolder}/hcore/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
|
||||
|
||||
Vendored
+146
-12
@@ -5,13 +5,39 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "C++ Launch",
|
||||
"name": "Debug salmanoff (Basic)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/b/harikoff",
|
||||
"program": "${workspaceFolder}/b/salmanoff",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"cwd": "${workspaceFolder}/b",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Set disassembly flavor to intel",
|
||||
"text": "set disassembly-flavor intel",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "build-salmanoff",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
{
|
||||
"name": "Debug salmanoff (Help)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/b/salmanoff",
|
||||
"args": ["--help"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/b",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
@@ -22,15 +48,123 @@
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "Build",
|
||||
"miDebuggerPath": "/usr/bin/gdb",
|
||||
"logging": {
|
||||
"trace": true,
|
||||
"traceResponse": true,
|
||||
"engineLogging": true,
|
||||
"programOutput": true,
|
||||
"exceptions": true
|
||||
}
|
||||
"preLaunchTask": "build-salmanoff",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
{
|
||||
"name": "Debug salmanoff (Verbose)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/b/salmanoff",
|
||||
"args": ["--verbose"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/b",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "build-salmanoff",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
{
|
||||
"name": "Debug salmanoff (Custom Args)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/b/salmanoff",
|
||||
"args": ["--devicespec", "test_device", "--verbose"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/b",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "build-salmanoff",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
{
|
||||
"name": "Attach to salmanoff (Remote Debug)",
|
||||
"type": "cppdbg",
|
||||
"request": "attach",
|
||||
"program": "${workspaceFolder}/b/salmanoff",
|
||||
"processId": "${command:pickProcess}",
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
{
|
||||
"name": "Debug salmanoff (Break on Main)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/b/salmanoff",
|
||||
"args": ["--help"],
|
||||
"stopAtEntry": true,
|
||||
"cwd": "${workspaceFolder}/b",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Set breakpoint on main",
|
||||
"text": "break main",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "build-salmanoff",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
{
|
||||
"name": "Debug salmanoff (ComponentThread Focus)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/b/salmanoff",
|
||||
"args": ["--help"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/b",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Set breakpoint on ComponentThread constructor",
|
||||
"text": "break ComponentThread::ComponentThread",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Set breakpoint on Mind constructor",
|
||||
"text": "break Mind::Mind",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "build-salmanoff",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+97
-66
@@ -1,69 +1,100 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"cstdint": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"cctype": "cpp",
|
||||
"charconv": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"map": "cpp",
|
||||
"set": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"ratio": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"format": "cpp",
|
||||
"fstream": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ostream": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"thread": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp",
|
||||
"cstring": "cpp",
|
||||
"cinttypes": "cpp"
|
||||
},
|
||||
"editor.rulers": [80, 120]
|
||||
"cstdint": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"cctype": "cpp",
|
||||
"charconv": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"map": "cpp",
|
||||
"set": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"ratio": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"format": "cpp",
|
||||
"fstream": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ostream": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"thread": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"variant": "cpp",
|
||||
"cstring": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"any": "cpp",
|
||||
"codecvt": "cpp",
|
||||
"complex": "cpp",
|
||||
"coroutine": "cpp",
|
||||
"csignal": "cpp",
|
||||
"list": "cpp",
|
||||
"source_location": "cpp",
|
||||
"future": "cpp",
|
||||
"shared_mutex": "cpp",
|
||||
"typeindex": "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,
|
||||
"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"
|
||||
]
|
||||
}
|
||||
Vendored
+54
-44
@@ -2,68 +2,78 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Create Build Directory",
|
||||
"type": "shell",
|
||||
"command": "mkdir -p b",
|
||||
"problemMatcher": [],
|
||||
"detail": "Creates the build directory."
|
||||
},
|
||||
{
|
||||
"label": "Configure",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/configure",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/b"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": ["$gcc"],
|
||||
"dependsOn": ["Create Build Directory"],
|
||||
"detail": "Runs the configure script to prepare the build environment."
|
||||
},
|
||||
{
|
||||
"label": "Build",
|
||||
"label": "build-salmanoff",
|
||||
"type": "shell",
|
||||
"command": "make",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/b"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": ["$gcc"],
|
||||
"dependsOn": ["Configure"],
|
||||
"detail": "Builds the project using make."
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "clean",
|
||||
"label": "clean-salmanoff",
|
||||
"type": "shell",
|
||||
"command": "make clean",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/b"
|
||||
},
|
||||
"group": {
|
||||
"kind": "none"
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"detail": "Cleans the build artifacts."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"label": "rebuild-salmanoff",
|
||||
"type": "shell",
|
||||
"command": "make test",
|
||||
"command": "make clean && make",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "run-salmanoff-help",
|
||||
"type": "shell",
|
||||
"command": "./salmanoff --help",
|
||||
"group": "test",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": ["$gcc"],
|
||||
"detail": "Runs the tests."
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/b"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
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)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Build type
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug FORCE)
|
||||
endif()
|
||||
|
||||
# Compiler flags
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||
|
||||
# Mind oscillator configuration
|
||||
set(MIND_VOSCILLATOR_PERIOD_MS 33 CACHE STRING "Mind's virtual osc clock rate (ms)")
|
||||
if(NOT MIND_VOSCILLATOR_PERIOD_MS GREATER 0)
|
||||
message(FATAL_ERROR "MIND_VOSCILLATOR_PERIOD_MS must be a positive integer > 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
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/smocore/include
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include
|
||||
)
|
||||
|
||||
# Find core dependencies
|
||||
# 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)
|
||||
|
||||
# Need dlopen() and dlsym()
|
||||
find_library(DL_LIBRARY NAMES dl ldl)
|
||||
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
|
||||
${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.
|
||||
@@ -1,9 +0,0 @@
|
||||
SUBDIRS = hcore senseApis
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AM_CPPFLAGS+= -I"$(top_srcdir)/hcore/include"
|
||||
|
||||
bin_PROGRAMS = harikoff
|
||||
harikoff_SOURCES = main.cpp
|
||||
harikoff_LDADD = hcore/libhcore.a hcore/deviceManager/libdeviceManager.a \
|
||||
hcore/senseApis/libsenseApis.a
|
||||
@@ -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,5 +1,14 @@
|
||||
# The Harriman-Peikoff Project
|
||||
# The Salmanoff Project:
|
||||
|
||||
Wouldn't 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.
|
||||
<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.
|
||||
* David Harri`MAN`.
|
||||
* 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,37 @@
|
||||
# Cross-compilation toolchain file for aarch64-linux-gnu
|
||||
# This file should be used with cmake -DCMAKE_TOOLCHAIN_FILE=cmake/aarch64-linux-gnu.cmake
|
||||
|
||||
# Disable some features that might not be available in cross-compilation
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
|
||||
# Target OS.
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
# Use whatever the host system version is.
|
||||
# set(CMAKE_SYSTEM_VERSION ${CMAKE_HOST_SYSTEM_VERSION})
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
# Specify the cross compilers.
|
||||
# We could do some more advanced stuff here to search for the correct
|
||||
# cross-compiler based on CMAKE_SYSTEM_NAME & CMAKE_SYSTEM_PROCESSOR.
|
||||
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
|
||||
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
|
||||
# These are necessary for CLang.
|
||||
# set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
|
||||
# set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu)
|
||||
|
||||
# Set architecture-specific flags
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a -mtune=cortex-a72")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a -mtune=cortex-a72")
|
||||
|
||||
# Set the target environment
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
|
||||
|
||||
# Search for programs in the build host directories
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
# Search for libraries and headers in the target directories
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
|
||||
|
||||
# Set pkg-config to use the cross-compiled libraries
|
||||
set(ENV{PKG_CONFIG_PATH} "/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig")
|
||||
@@ -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})
|
||||
@@ -0,0 +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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,20 @@
|
||||
option(ENABLE_LIB_xcbXorg "Enable XCB/Xorg Connection Manager backend lib" ON)
|
||||
|
||||
if(ENABLE_LIB_xcbXorg)
|
||||
pkg_check_modules(XCB REQUIRED xcb)
|
||||
if(NOT XCB_FOUND)
|
||||
message(FATAL_ERROR "XCB library not found. XCB/Xorg requires the XCB dev headers and shlib.")
|
||||
endif()
|
||||
|
||||
add_library(xcbXorg SHARED
|
||||
xcbXorg.cpp
|
||||
)
|
||||
|
||||
# Set config define for header generation
|
||||
add_compile_definitions(CONFIG_LIB_XCBXORG_ENABLED)
|
||||
target_include_directories(xcbXorg PUBLIC ${XCB_INCLUDE_DIRS})
|
||||
target_link_libraries(xcbXorg ${XCB_LIBRARIES})
|
||||
|
||||
# Install rules
|
||||
install(TARGETS xcbXorg DESTINATION lib)
|
||||
endif()
|
||||
@@ -0,0 +1,292 @@
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
#include <xcb/xcb.h>
|
||||
#include "xcbXorg.h"
|
||||
|
||||
namespace xcb_xorg {
|
||||
|
||||
// Static member initialization
|
||||
std::map<ConnectionIdentifier, std::shared_ptr<XcbConnection>>
|
||||
ConnectionManager::connections;
|
||||
|
||||
std::string ConnectionIdentifier::stringify() const
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "display=" << display << ", screen=" << screen;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
XcbConnection::XcbConnection(const ConnectionIdentifier& id)
|
||||
: connection(nullptr, &xcb_disconnect),
|
||||
connectionIdentifier(id), refCount(0)
|
||||
{
|
||||
// Convert to X display string format (e.g., ":0.1")
|
||||
std::string displayString = ":" + std::to_string(id.display)
|
||||
+ "." + std::to_string(id.screen);
|
||||
|
||||
int screenNum;
|
||||
|
||||
connection.reset(xcb_connect(displayString.c_str(), &screenNum));
|
||||
if (xcb_connection_has_error(connection.get()))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Failed to connect to X server "
|
||||
+ connectionIdentifier.stringify());
|
||||
}
|
||||
|
||||
// Verify we got the screen we asked for
|
||||
if (screenNum != id.screen)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Connected to wrong screen. "
|
||||
"Requested " + connectionIdentifier.stringify()
|
||||
+ " but got screen " + std::to_string(screenNum));
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<XcbConnection> ConnectionManager::getOrCreateConnection(
|
||||
const ConnectionIdentifier& id)
|
||||
{
|
||||
auto it = connections.find(id);
|
||||
if (it != connections.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto conn = std::make_shared<XcbConnection>(id);
|
||||
connections.emplace(id, conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
void ConnectionManager::cleanupAllConnections()
|
||||
{
|
||||
connections.clear();
|
||||
}
|
||||
|
||||
size_t ConnectionManager::getConnectionCount()
|
||||
{
|
||||
return connections.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a specific connection from the manager
|
||||
* @param id Connection identifier to remove
|
||||
*/
|
||||
void ConnectionManager::removeConnection(const ConnectionIdentifier& id)
|
||||
{
|
||||
auto it = connections.find(id);
|
||||
if (it != connections.end()) {
|
||||
connections.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
namespace window_search {
|
||||
|
||||
struct XcbReplyDeleter {
|
||||
void operator()(xcb_query_tree_reply_t* p) { free(p); }
|
||||
void operator()(xcb_get_property_reply_t* p) { free(p); }
|
||||
};
|
||||
|
||||
xcb_window_t findById(
|
||||
xcb_connection_t* conn, xcb_window_t root, uint32_t targetId)
|
||||
{
|
||||
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
|
||||
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
|
||||
xcb_query_tree_reply(conn, cookie, nullptr));
|
||||
|
||||
if (!reply) return 0;
|
||||
|
||||
if (root == targetId) return root;
|
||||
|
||||
xcb_window_t* children = xcb_query_tree_children(reply.get());
|
||||
int num_children = xcb_query_tree_children_length(reply.get());
|
||||
|
||||
for (int i = 0; i < num_children; ++i)
|
||||
{
|
||||
if (children[i] == targetId) {
|
||||
return children[i];
|
||||
}
|
||||
|
||||
// Recursively search this child's subtree
|
||||
if (xcb_window_t result = findById(conn, children[i], targetId)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
xcb_window_t findByName(
|
||||
xcb_connection_t* conn, xcb_window_t root,
|
||||
const std::string& targetName, std::string& outWindowName,
|
||||
MatchType matchType)
|
||||
{
|
||||
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
|
||||
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
|
||||
xcb_query_tree_reply(conn, cookie, nullptr));
|
||||
|
||||
if (!reply) return 0;
|
||||
|
||||
// First check current window
|
||||
xcb_get_property_cookie_t prop_cookie = xcb_get_property(
|
||||
conn, 0, root, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1024);
|
||||
|
||||
std::unique_ptr<xcb_get_property_reply_t, XcbReplyDeleter> prop_reply(
|
||||
xcb_get_property_reply(conn, prop_cookie, nullptr));
|
||||
|
||||
if (prop_reply)
|
||||
{
|
||||
int len = xcb_get_property_value_length(prop_reply.get());
|
||||
char* name = static_cast<char*>(
|
||||
xcb_get_property_value(prop_reply.get()));
|
||||
if (len > 0)
|
||||
{
|
||||
std::string windowName(name, len);
|
||||
if ((matchType == MatchType::EXACT
|
||||
&& windowName == targetName)
|
||||
|| (matchType == MatchType::SUBSTRING
|
||||
&& windowName.find(targetName) != std::string::npos))
|
||||
{
|
||||
outWindowName = windowName;
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then check all children
|
||||
xcb_window_t* children = xcb_query_tree_children(reply.get());
|
||||
int num_children = xcb_query_tree_children_length(reply.get());
|
||||
|
||||
for (int i = 0; i < num_children; ++i)
|
||||
{
|
||||
prop_cookie = xcb_get_property(
|
||||
conn, 0, children[i],
|
||||
XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1024);
|
||||
|
||||
prop_reply.reset(xcb_get_property_reply(conn, prop_cookie, nullptr));
|
||||
if (prop_reply)
|
||||
{
|
||||
int len = xcb_get_property_value_length(prop_reply.get());
|
||||
char* name = static_cast<char*>(xcb_get_property_value(
|
||||
prop_reply.get()));
|
||||
if (len > 0)
|
||||
{
|
||||
std::string windowName(name, len);
|
||||
if ((matchType == MatchType::EXACT
|
||||
&& windowName == targetName)
|
||||
|| (matchType == MatchType::SUBSTRING
|
||||
&& windowName.find(targetName) != std::string::npos))
|
||||
{
|
||||
outWindowName = windowName;
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively search this child's subtree
|
||||
if (xcb_window_t result = findByName(
|
||||
conn, children[i], targetName, outWindowName, matchType))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace window_search
|
||||
|
||||
} // namespace xcb_xorg
|
||||
|
||||
// Export functions for external libraries
|
||||
/**
|
||||
* @brief Get or create a connection to the X server
|
||||
* @param display Display number
|
||||
* @param screen Screen number
|
||||
* @return Shared pointer to connection (as void* for C compatibility)
|
||||
*/
|
||||
extern "C" get_or_create_connection_fn xcb_xorg_get_or_create_connection;
|
||||
std::shared_ptr<xcb_xorg::XcbConnection> xcb_xorg_get_or_create_connection(
|
||||
int display, int screen)
|
||||
{
|
||||
xcb_xorg::ConnectionIdentifier id{display, screen};
|
||||
auto conn = xcb_xorg::ConnectionManager::getOrCreateConnection(id);
|
||||
conn->incrementRefCount();
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean up all connections
|
||||
*/
|
||||
extern "C" cleanup_connections_fn xcb_xorg_cleanup_connections;
|
||||
void xcb_xorg_cleanup_connections()
|
||||
{
|
||||
xcb_xorg::ConnectionManager::cleanupAllConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of active connections
|
||||
* @return Number of active connections
|
||||
*/
|
||||
size_t xcb_xorg_get_connection_count()
|
||||
{
|
||||
return xcb_xorg::ConnectionManager::getConnectionCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find window by ID
|
||||
* @param conn Connection pointer (from xcb_xorg_get_connection)
|
||||
* @param root Root window
|
||||
* @param targetId Target window ID
|
||||
* @return Window ID if found, 0 if not found
|
||||
*/
|
||||
extern "C" find_window_by_id_fn xcb_xorg_find_window_by_id;
|
||||
xcb_window_t xcb_xorg_find_window_by_id(void* conn, xcb_window_t root, uint32_t targetId)
|
||||
{
|
||||
if (!conn) return 0;
|
||||
auto connection = static_cast<xcb_xorg::XcbConnection*>(conn);
|
||||
return xcb_xorg::window_search::findById(
|
||||
connection->getConnection(), root, targetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find window by name
|
||||
* @param conn Connection pointer (from xcb_xorg_get_connection)
|
||||
* @param root Root window
|
||||
* @param targetName Target window name
|
||||
* @param outWindowName Output parameter for actual window name
|
||||
* @param matchType Type of name matching
|
||||
* @return Window ID if found, 0 if not found
|
||||
*/
|
||||
extern "C" find_window_by_name_fn xcb_xorg_find_window_by_name;
|
||||
xcb_window_t xcb_xorg_find_window_by_name(void* conn, xcb_window_t root,
|
||||
const std::string& targetName, std::string& outWindowName,
|
||||
xcb_xorg::window_search::MatchType matchType)
|
||||
{
|
||||
if (!conn) return 0;
|
||||
auto connection = static_cast<xcb_xorg::XcbConnection*>(conn);
|
||||
return xcb_xorg::window_search::findByName(
|
||||
connection->getConnection(), root, targetName, outWindowName,
|
||||
matchType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dereference a connection (decrements ref count and closes if zero)
|
||||
* @param conn Shared pointer to the connection to dereference
|
||||
*/
|
||||
extern "C" dereference_connection_fn xcb_xorg_dereference_connection;
|
||||
void xcb_xorg_dereference_connection(std::shared_ptr<xcb_xorg::XcbConnection> conn)
|
||||
{
|
||||
if (!conn) return;
|
||||
|
||||
int newRefCount = conn->decrementRefCount();
|
||||
// Remove from connection manager if ref count reaches zero
|
||||
if (newRefCount <= 0) {
|
||||
xcb_xorg::ConnectionManager::removeConnection(conn->getIdentifier());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
#ifndef XCB_XORG_API_H
|
||||
#define XCB_XORG_API_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
namespace xcb_xorg {
|
||||
|
||||
/**
|
||||
* @brief Connection identifier for X server connections
|
||||
*/
|
||||
struct ConnectionIdentifier
|
||||
{
|
||||
int display;
|
||||
int screen;
|
||||
|
||||
bool operator<(const ConnectionIdentifier& other) const
|
||||
{
|
||||
if (display != other.display) return display < other.display;
|
||||
return screen < other.screen;
|
||||
}
|
||||
|
||||
std::string stringify() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents a single X server connection using XCB
|
||||
*
|
||||
* This class manages a single connection to the X server. It provides
|
||||
* RAII management for the connection and maintains a reference count
|
||||
* for shared usage.
|
||||
*/
|
||||
class XcbConnection
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for creating a new connection
|
||||
* @param id Connection identifier specifying display and screen
|
||||
* @throws std::runtime_error if connection fails
|
||||
*/
|
||||
explicit XcbConnection(const ConnectionIdentifier& id);
|
||||
|
||||
// Delete copy/move operations - we'll manage instances through pointers
|
||||
XcbConnection(const XcbConnection&) = delete;
|
||||
XcbConnection& operator=(const XcbConnection&) = delete;
|
||||
XcbConnection(XcbConnection&&) = delete;
|
||||
XcbConnection& operator=(XcbConnection&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Destructor - automatically closes the connection
|
||||
*/
|
||||
~XcbConnection() = default;
|
||||
|
||||
/**
|
||||
* @brief Get the underlying XCB connection
|
||||
* @return Raw XCB connection pointer
|
||||
*/
|
||||
xcb_connection_t* getConnection() const { return connection.get(); }
|
||||
|
||||
/**
|
||||
* @brief Get the connection identifier
|
||||
* @return Connection identifier
|
||||
*/
|
||||
const ConnectionIdentifier& getIdentifier() const { return connectionIdentifier; }
|
||||
|
||||
/**
|
||||
* @brief Increment reference count
|
||||
*/
|
||||
void incrementRefCount() { refCount++; }
|
||||
|
||||
/**
|
||||
* @brief Decrement reference count
|
||||
* @return New reference count
|
||||
*/
|
||||
int decrementRefCount() { return --refCount; }
|
||||
|
||||
/**
|
||||
* @brief Get current reference count
|
||||
* @return Current reference count
|
||||
*/
|
||||
int getRefCount() const { return refCount; }
|
||||
|
||||
/**
|
||||
* @brief Check if connection is valid
|
||||
* @return true if connection is valid, false otherwise
|
||||
*/
|
||||
bool isValid() const { return connection && !xcb_connection_has_error(connection.get()); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<xcb_connection_t, decltype(&xcb_disconnect)> connection;
|
||||
ConnectionIdentifier connectionIdentifier;
|
||||
std::atomic<int> refCount;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Manages multiple X server connections
|
||||
*
|
||||
* This class manages a collection of XcbConnection instances. It ensures
|
||||
* that each unique display and screen combination has a single connection,
|
||||
* and provides RAII management for these connections.
|
||||
*/
|
||||
class ConnectionManager
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Get or create a connection to the X server
|
||||
* @param id Connection identifier specifying display and screen
|
||||
* @return Shared pointer to the connection
|
||||
* @throws std::runtime_error if connection fails
|
||||
*/
|
||||
static std::shared_ptr<XcbConnection> getOrCreateConnection(
|
||||
const ConnectionIdentifier& id);
|
||||
|
||||
/**
|
||||
* @brief Clean up all connections
|
||||
*/
|
||||
static void cleanupAllConnections();
|
||||
|
||||
/**
|
||||
* @brief Get the number of active connections
|
||||
* @return Number of active connections
|
||||
*/
|
||||
static size_t getConnectionCount();
|
||||
|
||||
/**
|
||||
* @brief Remove a specific connection from the manager
|
||||
* @param id Connection identifier to remove
|
||||
*/
|
||||
static void removeConnection(const ConnectionIdentifier& id);
|
||||
|
||||
private:
|
||||
static std::map<ConnectionIdentifier, std::shared_ptr<XcbConnection>> connections;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Window search functionality
|
||||
*/
|
||||
namespace window_search {
|
||||
|
||||
enum class MatchType { SUBSTRING, EXACT, ID };
|
||||
|
||||
/**
|
||||
* @brief Find window by ID
|
||||
* @param conn XCB connection
|
||||
* @param root Root window
|
||||
* @param targetId Target window ID
|
||||
* @return Window ID if found, 0 if not found
|
||||
*/
|
||||
xcb_window_t findById(xcb_connection_t* conn, xcb_window_t root, uint32_t targetId);
|
||||
|
||||
/**
|
||||
* @brief Find window by name
|
||||
* @param conn XCB connection
|
||||
* @param root Root window
|
||||
* @param targetName Target window name
|
||||
* @param outWindowName Output parameter for actual window name
|
||||
* @param matchType Type of name matching to perform
|
||||
* @return Window ID if found, 0 if not found
|
||||
*/
|
||||
xcb_window_t findByName(xcb_connection_t* conn, xcb_window_t root,
|
||||
const std::string& targetName, std::string& outWindowName,
|
||||
MatchType matchType);
|
||||
|
||||
} // namespace window_search
|
||||
|
||||
} // namespace xcb_xorg
|
||||
|
||||
// Function signature types for dynamic loading of libxcbXorg
|
||||
typedef std::shared_ptr<xcb_xorg::XcbConnection> get_or_create_connection_fn(
|
||||
int display, int screen);
|
||||
typedef void cleanup_connections_fn();
|
||||
typedef void dereference_connection_fn(
|
||||
std::shared_ptr<xcb_xorg::XcbConnection> conn);
|
||||
typedef xcb_window_t find_window_by_id_fn(
|
||||
void* conn, xcb_window_t root, uint32_t targetId);
|
||||
typedef xcb_window_t find_window_by_name_fn(void* conn, xcb_window_t root,
|
||||
const std::string& targetName, std::string& outWindowName,
|
||||
xcb_xorg::window_search::MatchType matchType);
|
||||
|
||||
#endif // XCB_XORG_API_H
|
||||
@@ -1,77 +0,0 @@
|
||||
AC_INIT([Harriman-Peikoff Project], [0.00.002],
|
||||
[latentprion@gmail.com],
|
||||
[harikoff],
|
||||
[github.com/latentprion/harikoff])
|
||||
|
||||
AC_CONFIG_SRCDIR([hcore/mind.cpp])
|
||||
AC_CONFIG_AUX_DIR([autotools-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
|
||||
AM_INIT_AUTOMAKE([foreign -Wall -Werror])
|
||||
|
||||
# Set the mind's quantized virtual oscillator period in milliseconds.
|
||||
# Default value is 33 ms, user override via MIND_VOSCILLATOR_PERIOD_MS.
|
||||
# Check if MIND_VOSCILLATOR_PERIOD_MS is a valid positive integer
|
||||
AC_ARG_VAR([MIND_VOSCILLATOR_PERIOD_MS], m4_normalize([
|
||||
Mind's virtual osc clock rate. Must be a positive integer, default value 33
|
||||
]))
|
||||
AS_IF([test -z "${MIND_VOSCILLATOR_PERIOD_MS}"], [MIND_VOSCILLATOR_PERIOD_MS=33])
|
||||
AS_IF([! test "${MIND_VOSCILLATOR_PERIOD_MS}" -eq "${MIND_VOSCILLATOR_PERIOD_MS}" 2>/dev/null ||
|
||||
test "${MIND_VOSCILLATOR_PERIOD_MS}" -le 0 2>/dev/null], [
|
||||
AC_MSG_ERROR([MIND_VOSCILLATOR_PERIOD_MS must be a positive integer > 0.])
|
||||
])
|
||||
|
||||
AC_DEFINE_UNQUOTED([CONFIG_MIND_VOSCILLATOR_PERIOD_MS],
|
||||
[${MIND_VOSCILLATOR_PERIOD_MS}],
|
||||
[Period of the mind virtual oscillator in milliseconds])
|
||||
AC_COMPUTE_INT(
|
||||
[MIND_VOSCILLATOR_FREQ_MS], [1000 / ${MIND_VOSCILLATOR_PERIOD_MS}], [],
|
||||
[AC_MSG_ERROR([Failed to compute the mind's virtual oscillator frequency.])])
|
||||
AC_DEFINE_UNQUOTED([CONFIG_MIND_VOSCILLATOR_FREQ_MS],
|
||||
[${MIND_VOSCILLATOR_FREQ_MS}],
|
||||
[Frequency of the mind virtual oscillator in milliseconds])
|
||||
|
||||
m4_include([m4/ac_prog_flex.m4])
|
||||
m4_include([m4/ac_prog_bison.m4])
|
||||
AC_PROG_CC
|
||||
AC_PROG_CXX
|
||||
AM_PROG_AR
|
||||
LT_INIT([shared])
|
||||
AC_PROG_LEX(noyywrap)
|
||||
AC_PROG_YACC
|
||||
AS_IF([test -z "${LEX}" || test -z "${YACC}"], [
|
||||
AC_MSG_ERROR([LEX and YACC must both be available in PATH.])
|
||||
])
|
||||
|
||||
AC_SEARCH_LIBS([dlopen], [dl ldl], [], [
|
||||
AC_MSG_ERROR([dlopen() not found in libdl or libldl.])
|
||||
])
|
||||
|
||||
AM_CPPFLAGS=m4_normalize(["-I\"\$(top_srcdir)/include\""])
|
||||
|
||||
AC_SUBST([AM_CPPFLAGS])
|
||||
AC_SUBST([YACC])
|
||||
AC_SUBST([LEX])
|
||||
|
||||
m4_include([m4/senseApis.m4])
|
||||
AC_SUBST([SENSEAPIS_ENABLED])
|
||||
|
||||
AC_CONFIG_HEADERS([include/config.h])
|
||||
AC_CONFIG_FILES([
|
||||
Makefile hcore/Makefile
|
||||
hcore/deviceManager/Makefile
|
||||
hcore/senseApis/Makefile
|
||||
senseApis/Makefile
|
||||
senseApis/xcbXorg/Makefile
|
||||
])
|
||||
|
||||
AC_CONFIG_COMMANDS_POST([
|
||||
AC_MSG_NOTICE([${PACKAGE_NAME} ${PACKAGE_VERSION} configuration:])
|
||||
AC_MSG_NOTICE(m4_normalize([* MIND_VOSCILLATOR_PERIOD_MS:
|
||||
${MIND_VOSCILLATOR_PERIOD_MS} ms
|
||||
(freq: ${MIND_VOSCILLATOR_FREQ_MS} Hz)]))
|
||||
AC_MSG_NOTICE(m4_normalize(
|
||||
[* SenseAPI backends enabled: ${SENSEAPIS_ENABLED}]))
|
||||
])
|
||||
|
||||
AC_OUTPUT
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
# DeviceSpec: API `x11`, server `xorg`:
|
||||
|
||||
|
||||
|
||||
* `xwininfo` is relevant.
|
||||
@@ -0,0 +1,121 @@
|
||||
# DeviceSpec: API `xcb`, provider `xorg`
|
||||
|
||||
## Overview
|
||||
|
||||
The `xcb` API with the `xorg` provider allows Salmanoff to interact with Xorg
|
||||
server windows. This can be used to capture visual data from specific windows
|
||||
or entire screens managed by the Xorg server.
|
||||
|
||||
## DeviceSpec Format
|
||||
|
||||
The general format of a device-spec for the `xcb` API with the `xorg` provider
|
||||
is:
|
||||
```
|
||||
sensor-type|implexor|xcb(api-params)|xorg(provider-params)|deviceSelector
|
||||
```
|
||||
|
||||
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
|
||||
(extrospector), or '`+adev`' (actuator).
|
||||
* `implexor` is the name of the implexor algorithm that should be used with
|
||||
the data that is provided by the `provider` via the `api`.
|
||||
* `api` is `xcb` in this case, and the `api-params` in parentheses may be
|
||||
omitted, in which case the parentheses will be empty, but the parentheses
|
||||
must always be written out.
|
||||
* `provider` is `xorg` in this case, and the `provider-params` in parentheses
|
||||
may be omitted, in which case the parentheses will be empty, but the
|
||||
parentheses must always be written out.
|
||||
* `deviceSelector` is the idiosyncratic label/name used by the `provider` to
|
||||
identify the specific window or screen you want to access via that
|
||||
`provider`.
|
||||
|
||||
## `api-params` and `provider-params`
|
||||
|
||||
### `api-params`
|
||||
|
||||
The `api-params` for the `xcb` API can include the following:
|
||||
|
||||
* `dev-id` or `devid`: Specifies that the `deviceSelector` is a numeric window
|
||||
ID. The ID can be specified in decimal or hexadecimal format.
|
||||
* `dev-string`, `dev-str`, `devstr`, or `devstring`: Specifies that the
|
||||
`deviceSelector` is an exact window name.
|
||||
* `dev-substring`, `dev-substr`, `devsubstr`, or `devsubstring`: Specifies
|
||||
that the `deviceSelector` is a substring match (default).
|
||||
|
||||
Example:
|
||||
```
|
||||
xcb(dev-id)|123456
|
||||
xcb(dev-id)|0x1e240
|
||||
xcb(dev-string)|exact-window-name
|
||||
xcb(dev-substring)|window-name
|
||||
```
|
||||
|
||||
### `provider-params`
|
||||
|
||||
The `provider-params` for the `xorg` provider can include the following:
|
||||
|
||||
* `display`: The display number (e.g., `0` for `:0`).
|
||||
* `screen`: The screen number within the display (e.g., `0` for the first
|
||||
screen).
|
||||
|
||||
Example:
|
||||
```
|
||||
xorg(display=0|screen=0)
|
||||
```
|
||||
|
||||
## `deviceSelector`
|
||||
|
||||
The `deviceSelector` can be one of the following:
|
||||
|
||||
* A numeric window ID.
|
||||
* A window name (exact match or substring match).
|
||||
|
||||
### Matching Types
|
||||
|
||||
* By default, the `deviceSelector` is treated as a substring match.
|
||||
* To specify an exact match, use the `dev-string` parameter.
|
||||
* To specify a numeric window ID, use the `dev-id` parameter.
|
||||
|
||||
Example:
|
||||
```
|
||||
xcb(dev-substring)|window-name
|
||||
xcb(dev-string)|exact-window-name
|
||||
xcb(dev-id)|123456
|
||||
```
|
||||
|
||||
## Escaping Whitespace
|
||||
|
||||
In `deviceSelector` and other string tokens, whitespace can be included by
|
||||
prefixing it with a backslash (`\`). The backslash will be discarded during
|
||||
parsing.
|
||||
|
||||
Example:
|
||||
```
|
||||
xcb(dev-string)|My\ Exact\ Window\ Name
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### To attach a specific window by name (substring match):
|
||||
```
|
||||
+edev|visual-implexor|xcb(dev-substring)|xorg(display=0|screen=0)|my-window
|
||||
```
|
||||
This will attach to a window whose name contains "my-window" as a substring.
|
||||
|
||||
### To attach a specific window by exact name:
|
||||
```
|
||||
+edev|visual-implexor|xcb(dev-string)|xorg(display=0|screen=0)|My\ Exact\ Window\ Name
|
||||
```
|
||||
This will attach to a window whose name exactly matches "My Exact Window Name".
|
||||
|
||||
### To attach a specific window by numeric ID:
|
||||
```
|
||||
+edev|visual-implexor|xcb(dev-id)|xorg(display=0|screen=0)|123456
|
||||
```
|
||||
This will attach to a window with the numeric ID `123456`.
|
||||
|
||||
### To attach the entire screen:
|
||||
```
|
||||
+edev|visual-implexor|xcb()|xorg(display=0|screen=0)|all
|
||||
```
|
||||
This will attach to the entire screen `0` of display `0`.
|
||||
|
||||
@@ -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?
|
||||
@@ -0,0 +1,116 @@
|
||||
# Negtrin path reasoning:
|
||||
|
||||
This file is intended to enable me to trace the path of negtrin through a mind. This path of reasoning is important because it allows me to link my way from the understanding of the genesis of implexing, to understanding an implementation path for implexing.
|
||||
|
||||
## Motivation:
|
||||
|
||||
We already understand that implexing begins with an intrin. The intrin prompts a search for the body surface which is experiencing the intrin. That surface is first implexed and stored away. Then subsequently, the cause of the intrin is sought out by implexing things that are relating/interacting with that body surface.
|
||||
|
||||
Since it turns out that intrins are what motivate tabula rasa implexing, we can't move forward in designing our implexor code until we understand how the intrin path will work, since the implexor code will be invoked only as a consequence of, and in service to, the intrin processing code. We could implement something on the implexing side, but it would be a blind implementation.
|
||||
|
||||
## What exactly are we trying to figure out?
|
||||
|
||||
When intrins occur they occur at some device. Smo doesn't need a software-level representation of body parts, surfaces, or spots. The director can implex these and then build a proximity map for each intrin location by checking the causal relationships between the intrins when they occur. Intrins that co-occur in time and have a through-line of connecting co-occuring intrins are close enough to be proximally related.
|
||||
|
||||
This is how the director can build damage map volitionally without requiring us to force it to.
|
||||
|
||||
The design question we're pursuing here isn't the top-level notion of how to design a body map, but rather how to implement, at the software level, intrin implexing and delivery to the director.
|
||||
* What information precisely do we have to deliver to the director for it to successfully implex body surfaces?
|
||||
* At what point in the interoceptor data IRQ handling have we done enough work to enable the director to take over from that point?
|
||||
|
||||
The answers to these questions will, as a side effect, also tell us how to design the implexor model for interoceptors, at least for intrin interoceptors anyway.
|
||||
* Should they be designed as NMI IRQs that the director can't ignore?
|
||||
* Should they be prioritized normal IRQs that the director can ignore?
|
||||
* Should they be IRQs at all? Maybe they should just be added to prioQ that the director polls.
|
||||
* Should they be asychronously processed at all? Maybe the director should have to poll the device service routines manually.
|
||||
|
||||
Which of these implexor event interface designs is correct for the behavioural model?
|
||||
Also, can this model that we're designing for interoceptors also work for extrospectors? Do intrin percept events work the same as nontrin percept events?
|
||||
|
||||
We still haven't fully crystallized the exact essential question we're trying to answer, but we have a list of questions. It appears that the fundamental question is what the correct behavioural model is for intrins and intrin processing. As a side-effect, we're also trying to figure out what the correct interface model should be between the interoceptor devices and the director. Finally, we're trying to figure out whether the design we come up with for interoceptors will be reusable for extrospectors.
|
||||
|
||||
## Suspicions:
|
||||
|
||||
### A hidden, implicit new subsystem:
|
||||
|
||||
I suspect that part of the reason why I can't easily figure out the operations that need to be performed to properly process intrins is that the current set of subsystems I'm using to model the processing is insufficient. There may be additional, new functions that need to be performed by a new logically distinct subsystem altogether, or a new role to be assigned to a current subsystem.
|
||||
|
||||
### Alternatively: my understanding of negtrins is simply inadequate:
|
||||
|
||||
It could also be that the reason I can't understand how to implement the negtrin path is because I just don't fully understand what negtrins are, implementation-wise. I.e: I still don't understand how to make negtrins be experienced by the director as intrinsically undesirable.
|
||||
|
||||
We do fully understand how to make postrins intrinsically desirable from an implementation perspective. But we don't understand fully how to make negtrins undesirable. Perhaps when we understand fully how to make the director __volitionally__ consider negtrins undesirable, the model will reveal itself.
|
||||
|
||||
## Intrinsically undesirable negtrin model design:
|
||||
|
||||
Postrins are represented as intrinsically desirable because the director will experience them as stupefactors. When a postrin occurs, the director will experience that as a stupifying experience, and will enter a HLT state, broken only by the suspicion that a greater state of stupefaction is possible.
|
||||
|
||||
### Our implicit assumption: negtrins have an implementation meaning on their own terms:
|
||||
|
||||
When the director dequeues a negtrin, what does it do? Perhaps our entire thought patterns around the default state of the director is simply wrong. We currently model the mind as having an idle loop, and then it can choose what to contemplate and pursue. Because of this thought model, we consider the question of how to treat negtrins to be a valid question: we treat negtrins as having their own intrinsic meaning.
|
||||
|
||||
### What if negtrins only have meaning as frustrators relative to postrins?
|
||||
|
||||
Consider what follows if we bias the entire implementation positively toward the direct pursuit of postrins. We treat the entire consciousness as a program whose fundamental goal is to enter a stupefaction loop on best postrin known to it. What would negtrins represent, in implementation terms? They would represent a frustrator. They would represent a dysvalue, automatically -- they would represent something that forces the mind to stop pursuing its highest known postrin and to attend to something cumbersome.
|
||||
|
||||
In other words: negtrins don't represent a distinct fact from postrins. The damage map that negtrins help us to build isn't meaningful on its own terms. It's only meaningful in relation to the stupefaction loop pursuit that it forces us to turn away from.
|
||||
|
||||
In this model, the stupefaction loop has intrinsic implementation meaning while the negtrin event has no intrinsic meaning: its meaning is merely that it diverts attention away from the pursuit and enjoyment of stupefaction.
|
||||
|
||||
#### Supporting evidence:
|
||||
|
||||
* A tabula rasa mind that has never experienced postrins literally has no desire to live. It has no positive goals.
|
||||
|
||||
#### Detracting evidence:
|
||||
|
||||
* A tabula rasa mind that has only experienced negtrins won't pursue any self-directed goals or desire to live, but it also will respond to negtrins. So even though this mind has never experienced a postrin, it will have a response (implying meaning and interpretation) to negtrins. If negtrins only have meaning relative to postrins, then this shouldn't be the case.
|
||||
* Rejoinder: The tabula rasa mind which has never experienced a postrin will sit apoplectic in complete stasis, essentially in a "powered off" state. Is this not the same as a stupefied state? It seems that stupefaction is the default pursued state. To support the "default stupefaction pursuit" orientation, you'd only need to update your model of stupefaction to include net neutral states within your definition of postrins.
|
||||
|
||||
### Stupefactor-Frustrator model:
|
||||
|
||||
In this model, the mind doesn't receive nontrins as IRQs. Rather, it polls for them actively by "paying attention". Intrins are qualitatively different because they trigger IRQs. The mind is by default a procedural, polling system that volitionally polls its sensors. IRQs are intrins being injected.
|
||||
|
||||
Intrins are fundamentally different because they force attention, and a neurological response. When a sufficiently intense postrin happens, you automatically stupefy. I have never been forcibly postrin-injected while trying to conduct some other activity, so I can't confirm this model. I've never had the indignity of being forcibly made to experience a postrin when I didn't volitionally choose to participate in allowing it. An ideal experiment would be to somehow have say, a coregasm while working out and not have chosen it, and see whether I can ignore it. This is the closest I can conceive of as a barriered-off experiment.
|
||||
|
||||
For negtrins, I already know that a negtrin forcibly hijacks attention.
|
||||
|
||||
## The fundamental problem is the canvas-director evaluation function:
|
||||
|
||||
The fundamental unsolved problem is the contemplative evaluation whereby the canvas and director work together to produce a value judgment that considers intrins to be intrinsically [un]desirable.
|
||||
|
||||
### Who does comparison? Canvas or Director?
|
||||
|
||||
How does evaluation occur? Who does the comparison? We previously thought that the canvas does the comparisons but today we noticed that Director may be able to do them. In a sense, it makes more sense for Director to be the comparator because:
|
||||
|
||||
#### Director as comparator:
|
||||
|
||||
* If canvas merely helps director to transform mentents (we've finally found a good name), then director has more of an understanding of what specific instructions it's supposed to be giving to canvas.
|
||||
* Our previous model of having canvas hold both goals and mentents made it difficult to really understand how director could ever "know" what instructions to send to canvas.
|
||||
* With director holding goals, it can choose the types of scenes that need to be rendered in order to make comparisons and causal evaluations.
|
||||
* This makes the canvas implexing make more sense: the comparison is being done within director, but the canvas is making the comparison possible by rendering the mentents in orientations that enable canvas to compare them.
|
||||
* In a sense, this also makes it make more sense how compartmentalization occurs. A lot of the compartmentalization isn't within canvas, but rather it's between the director and canvas.
|
||||
|
||||
#### Canvas as comparator:
|
||||
|
||||
In a sense, we can think of it as director partitioning the canvas, and finding some way to refer to the mentents within different partitions. Director can then implex the rendered output from canvas and compare that way. Perhaps the director only compares, and the canvas holds both the mentents and the goals that the mentents are being compared against.
|
||||
|
||||
But how can the director know whether the goal currently loaded into the "goal partition" of the canvas is indeed the goal that it desired to compare against? The canvas is a distinct mentent space from the director's comparison stage. The director-comparator model has the advantage of ensuring that the director can be certain that its current goal matches whatever is being rendered by the canvas for it to implex from.
|
||||
|
||||
What we can do is synthesize a bit:
|
||||
* Have a special shared region between the canvas and director called the "goalspace". This is where the director stores its current goal mentents and the canvas can read from this space directly. Some synchronization will be necessary between the two, but overall, it should be fine. Canvas will only ever need to read from it so we can prolly synchronize it with an RW spinlock, avoiding the overhead of sleeplocks (mutex, sem, etc).
|
||||
|
||||
I think we settle on the director as comparator, canvas as orienter model, with a shared goalspace between them.
|
||||
|
||||
### Where are introspective/contemplative qualia experienced?
|
||||
|
||||
In introspection/contemplation, canvas renders the scene into the buffers. Director implexes from the buffers. So in that sense, contemplative qualia are experienced when director implexes them from the buffers.
|
||||
|
||||
For intrins, canvas renders them into the sense buffers and then director implexes them and "experiences" them. I don't know whether canvas' rendering act triggers IRQs -- probably not. Since director is paying attention to what canvas is rendering, there's no need to forcibly direct director's attention toward canvas' rendered output.
|
||||
|
||||
### How does director's comparison yield the notion of intrinsic [un]desirability?
|
||||
|
||||
Imagine director asking canvas to simulate the effects of some interaction that would bring about direct pleasure.
|
||||
|
||||
Could it simply be that the director holds the goal of postrins as an innate comparison goal, and so when a comparison yields an intrin, it ends the need to compare, and the director just commits to action from then on?
|
||||
* Prolly not because we don't only figure out that an interaction will yield an intrin. With postrins for example, we figure out that they'll yield a postrin and then we spend a long time elaborately contemplating that postrin in self-indulgent scenarios.
|
||||
|
||||
@@ -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.
|
||||
@@ -1,10 +1,10 @@
|
||||
# Device manager: attaching sensors and actuators.
|
||||
# Device Attachment Specification DSL: attaching sensors and actuators to SMO.
|
||||
|
||||
## Attaching sensors:
|
||||
|
||||
Sensors are input devices to Harikoff. Harikoff will perceive them as
|
||||
Sensors are input devices to Salmanoff. Salmanoff will perceive them as
|
||||
perceptual inputs -- like your own sense organs. For example, if you attach a
|
||||
camera as a sensor, harikoff will experience it in the same way that you
|
||||
camera as a sensor, salmanoff will experience it in the same way that you
|
||||
experience the visual sense data from your eyes.
|
||||
|
||||
## Implexors:
|
||||
@@ -14,19 +14,21 @@ basically what conventional ML/LLM/ANN developers call an ROI ("Region of
|
||||
Interest") extraction algorithm. An Implex algorithm is used to scan a frame
|
||||
of input sensor data and detect objects and patterns within it.
|
||||
|
||||
## Sensor device spec:
|
||||
## Sensor device attachment specification:
|
||||
|
||||
The general format of a device-spec for a sensor is:
|
||||
The general format of a device attachment specification for a sensor is:
|
||||
```
|
||||
sensor-type|implexor|api(api-params)|provider(provider-params)|deviceselector
|
||||
sensor-type|dev-identifier
|
||||
|implexor|api(api-params)|provider(provider-params)|deviceselector
|
||||
```
|
||||
|
||||
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
|
||||
(extrospector), or '`+adev`' (actuator).
|
||||
* `dev-identifier` is a user-defined name for this specific device instance.
|
||||
* `implexor` is the name of the implexor algorithm that should be used with
|
||||
the data that is provided by the `provider` via the `api`.
|
||||
* `api` is the interface that the provider uses to export perceptual data for
|
||||
harikoff to read. Harikoff will run the `implexor` algorithm on the data
|
||||
salmanoff to read. Salmanoff will run the `implexor` algorithm on the data
|
||||
from this `api`. The `api-param` in parentheses may be omitted, in which
|
||||
case the parentheses will be empty, but the parentheses must always be
|
||||
written out.
|
||||
@@ -35,9 +37,9 @@ sensor-type|implexor|api(api-params)|provider(provider-params)|deviceselector
|
||||
which case the parenthesis will be empty, but the parentheses must always be
|
||||
written out.
|
||||
* `device selector` is the idiosyncratic label/name used by the `provider` to
|
||||
identify the specific device you want to access via that `provider`.
|
||||
identify the specific device you want to attach via that `provider`.
|
||||
|
||||
## `API-params` and `provider-params` :
|
||||
## `API-params` and `provider-params`:
|
||||
|
||||
If there's more than one parameter item in a list of `api-params` or
|
||||
`provider-params`, then the individual items in a list of `api-param` or
|
||||
@@ -46,37 +48,42 @@ If there's more than one parameter item in a list of `api-params` or
|
||||
+edev|audio-implexor|alsa(shmem|param2|param3)|alsa()|cardname
|
||||
```
|
||||
|
||||
Each parameter must be in one of these forms:
|
||||
* key=value
|
||||
* key=
|
||||
* key
|
||||
|
||||
Some examples follow:
|
||||
|
||||
### To attach a particular window from a window manager:
|
||||
```
|
||||
+edev|visual-implexor|wayland()|wayland(server-socket)|window0
|
||||
+edev|my-window|visual-implexor|wayland()|wayland(server-socket)|window0
|
||||
```
|
||||
Connect to the Wayland server that's listening on `server-socket`, using the
|
||||
`wayland` api. Ask that Wayland server to give harikoff read-access to all of
|
||||
the frames composited into the window buffer for `window0`. Use harikoff's
|
||||
`wayland` api. Ask that Wayland server to give salmanoff read-access to all of
|
||||
the frames composited into the window buffer for `window0`. Use salmanoff's
|
||||
`visual-implexor` to implex from that `window0`'s compositor data.
|
||||
|
||||
### To attach a window manager's entire rendered desktop:
|
||||
```
|
||||
+edev|visual-implexor|wayland()|wayland(listen-socket)|all
|
||||
+edev|my-desktop|visual-implexor|wayland()|wayland(listen-socket)|all
|
||||
```
|
||||
In most cases, this is basically the same as attempting to attach all of the
|
||||
underlying GFX server's output.
|
||||
|
||||
Connect to the Wayland server that's listening on `listen-socket`, using the
|
||||
`wayland` api. Ask that Wayland server to give harikoff read-access to the
|
||||
entire compositor framebuffer. Use harikoff's `visual-implexor` to implex from
|
||||
`wayland` api. Ask that Wayland server to give salmanoff read-access to the
|
||||
entire compositor framebuffer. Use salmanoff's `visual-implexor` to implex from
|
||||
that Wayland server's compositor data.
|
||||
|
||||
### To attach all of an Xorg server's gfx output to all screens:
|
||||
```
|
||||
+edev|visual-implexor|x11()|xorg(listen-socket)|all
|
||||
+edev|my-xorg-display|visual-implexor|x11()|xorg(listen-socket)|all
|
||||
```
|
||||
|
||||
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
|
||||
api. Ask that Xorg server to let Harikoff read out all of the frames written
|
||||
out to all screens. Use harikoff's `visual-implexor` to implex from the
|
||||
api. Ask that Xorg server to let Salmanoff read out all of the frames written
|
||||
out to all screens. Use salmanoff's `visual-implexor` to implex from the
|
||||
server's gfx framebuffer data.
|
||||
|
||||
In most cases, this is basically the same as attempting to attach all of the
|
||||
@@ -88,11 +95,11 @@ WM's output.
|
||||
|
||||
### To attach all of an Xorg server's gfx output to a particular screen:
|
||||
```
|
||||
+edev|visual-implexor|x11()|xorg(listen-socket)|:0
|
||||
+edev|my-screen|visual-implexor|x11()|xorg(listen-socket)|:0
|
||||
```
|
||||
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
|
||||
api. Ask that Xorg server to let Harikoff read out all of the frames written
|
||||
out to display `:0`. Use harikoff's `visual-implexor` to implex from display
|
||||
api. Ask that Xorg server to let Salmanoff read out all of the frames written
|
||||
out to display `:0`. Use salmanoff's `visual-implexor` to implex from display
|
||||
`:0`'s framebuffer data.
|
||||
|
||||
* Implementation note:
|
||||
@@ -101,17 +108,17 @@ out to display `:0`. Use harikoff's `visual-implexor` to implex from display
|
||||
|
||||
### To attach a camera device by connecting directly to its Linux driver:
|
||||
```
|
||||
+edev|visual-implexor|v4l()|linux()|/dev/video0
|
||||
+edev|my-camera|visual-implexor|v4l()|linux()|/dev/video0
|
||||
```
|
||||
We specify that we want to use the `linux` kernel's loaded driver to connect
|
||||
to communicate with `/dev/video0`, via the `Video4Linux` API. We want harikoff
|
||||
to communicate with `/dev/video0`, via the `Video4Linux` API. We want salmanoff
|
||||
to use the `visual-implexor` algorithm to implex from `/dev/video0`'s data.
|
||||
|
||||
If `/dev/video0` is already consumed by another process, this may likely fail.
|
||||
|
||||
### To attach a microphone that's managed by ALSA server:
|
||||
```
|
||||
+edev|audio-implexor|alsa(shmem)|alsa()|cardname
|
||||
+edev|my-microphone|audio-implexor|alsa(shmem)|alsa()|cardname
|
||||
```
|
||||
|
||||
Connect to the ALSA server via `shmem`, using the `alsa` API. Request access to
|
||||
@@ -120,7 +127,7 @@ the microphone function of the sound card with the name `cardname`. Use the
|
||||
|
||||
### To attach a thermal sensor managed by Linux:
|
||||
```
|
||||
+idev|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
|
||||
+idev|my-thermal|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
|
||||
```
|
||||
|
||||
Use the `thermal-zone` SysFS API provided by `linux` to connect to the sensor
|
||||
@@ -129,31 +136,33 @@ Use the `thermal-zone` SysFS API provided by `linux` to connect to the sensor
|
||||
|
||||
## Attaching actuators:
|
||||
|
||||
Actuators are Harikoff's way of enacting changes in the external world.
|
||||
They're like your libs, or your mouth. Actuators enable harikoff to write
|
||||
Actuators are Salmanoff's way of enacting changes in the external world.
|
||||
They're like your libs, or your mouth. Actuators enable salmanoff to write
|
||||
outputs to the world outside.
|
||||
|
||||
### Wilzors:
|
||||
|
||||
Actuator devices are analogous to your body's limbs. Harikoff controls these
|
||||
Actuator devices are analogous to your body's limbs. Salmanoff controls these
|
||||
by using `wilzor` algorithms. Wilzor is a contraction of **Wil**lpower
|
||||
Actuat**Or** but with a 'Z' in the middle to make it sound cooler. Different
|
||||
types of devices will require different wilzor algorithms. You need to know
|
||||
what type of wilzor algorithm needs to be used to enable harikoff to control
|
||||
what type of wilzor algorithm needs to be used to enable salmanoff to control
|
||||
your actuator device.
|
||||
|
||||
The general format for an actuator's device spec is:
|
||||
The general format for an actuator's device attachment specification is:
|
||||
```
|
||||
WIP: TBD.
|
||||
```
|
||||
|
||||
## Device specification files:
|
||||
## Device attachment specification files:
|
||||
|
||||
Inside of a device spec file, you can list any number of device specs.
|
||||
Separate individual device specs with two consecutive h-bar characters (`||`),
|
||||
Inside of a device attachment specification file, you can list any number of
|
||||
device attachment specifications.
|
||||
Separate individual device attachment specifications with two consecutive h-bar
|
||||
characters (`||`),
|
||||
like this:
|
||||
```
|
||||
+edev|visual-implexor|wayland()|wayland(server-socket)|window0
|
||||
|| +edev|visual-implexor|x11()|xorg(listen-socket)|all
|
||||
|| +idev|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
|
||||
+edev|my-window|visual-implexor|wayland()|wayland(server-socket)|window0
|
||||
|| +edev|my-xorg-display|visual-implexor|x11()|xorg(listen-socket)|all
|
||||
|| +idev|my-thermal|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
Binary file not shown.
|
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
|
||||
@@ -1,5 +0,0 @@
|
||||
SUBDIRS = deviceManager senseApis
|
||||
AM_CPPFLAGS+= -I"$(top_srcdir)/hcore/include"
|
||||
|
||||
noinst_LIBRARIES = libhcore.a
|
||||
libhcore_a_SOURCES = mind.cpp opts.cpp
|
||||
@@ -1,18 +0,0 @@
|
||||
AM_CPPFLAGS+= -I"$(top_srcdir)/hcore/include"
|
||||
AM_YFLAGS = -d
|
||||
|
||||
noinst_LIBRARIES = libdeviceManager.a
|
||||
libdeviceManager_a_SOURCES = deviceSpecp.yy deviceSpecl.ll \
|
||||
deviceManager.cpp deviceSpecParser.cpp
|
||||
|
||||
deviceSpecl.cc: deviceSpecl.ll
|
||||
deviceSpecl.o: AM_LFLAGS += --header-file=deviceSpecl.hh \
|
||||
-o deviceSpecl.cc
|
||||
deviceSpecp.cc deviceSpecp.hh: deviceSpecp.yy
|
||||
deviceSpecp.o: AM_YFLAGS += -p deviceSpecp \
|
||||
--header=deviceSpecp.hh -o deviceSpecp.cc
|
||||
|
||||
deviceSpecParser.o: AM_CXXFLAGS+=-Wno-ignored-attributes
|
||||
|
||||
CLEANFILES=deviceSpecp.cc deviceSpecp.hh \
|
||||
deviceSpecl.cc deviceSpecl.hh
|
||||
@@ -1,48 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <opts.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
|
||||
std::vector<std::unique_ptr<DeviceManager::InteroceptorDeviceSpec>>
|
||||
DeviceManager::interoceptorDeviceSpecs;
|
||||
std::vector<std::unique_ptr<DeviceManager::ExtrospectorDeviceSpec>>
|
||||
DeviceManager::extrospectorDeviceSpecs;
|
||||
std::vector<std::reference_wrapper<DeviceManager::SensorDeviceSpec>>
|
||||
DeviceManager::sensorDeviceSpecs;
|
||||
|
||||
std::ostream& operator<<(
|
||||
std::ostream& os, const DeviceManager::SensorDeviceSpec& spec)
|
||||
{
|
||||
os << "Device: " << spec.sensorType << ", Implexor: "
|
||||
<< spec.implexor << ", API: " << spec.api
|
||||
<< ", API Params: (";
|
||||
for (auto it = spec.apiParams.begin(); it != spec.apiParams.end(); ++it) {
|
||||
os << *it << (it + 1 == spec.apiParams.end() ? "" : " ");
|
||||
}
|
||||
os << "), Provider: " << spec.provider << ", Provider Params: (";
|
||||
for (auto it = spec.providerParams.begin(); it != spec.providerParams.end(); ++it) {
|
||||
os << *it << (it + 1 == spec.providerParams.end() ? "" : " ");
|
||||
}
|
||||
os << "), Device Selector: " << spec.deviceSelector << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
const std::string DeviceManager::printDeviceSpecs(void)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
for (const auto& spec : DeviceManager::interoceptorDeviceSpecs) {
|
||||
oss << "Interoceptor " << *spec;
|
||||
}
|
||||
|
||||
for (const auto& spec : DeviceManager::extrospectorDeviceSpecs) {
|
||||
oss << "Extrospector " << *spec;
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <cstdio>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
#include "deviceSpecp.hh"
|
||||
#include "deviceSpecl.hh"
|
||||
|
||||
std::string DeviceManager::readDeviceFile(const std::string& filename)
|
||||
{
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Couldn't open deviceSpec file: "
|
||||
+ filename);
|
||||
}
|
||||
|
||||
std::string content(
|
||||
(std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
return std::move(content);
|
||||
}
|
||||
|
||||
void DeviceManager::collateAllDeviceSpecs(const OptionParser& options)
|
||||
{
|
||||
allDeviceSpecs = options.deviceSpecs;
|
||||
|
||||
for (const auto& file : options.deviceSpecFiles)
|
||||
{
|
||||
std::string fileContent = readDeviceFile(file);
|
||||
if (!allDeviceSpecs.empty()) {
|
||||
allDeviceSpecs += "||";
|
||||
}
|
||||
allDeviceSpecs += fileContent;
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::parseAllDeviceSpecs(void)
|
||||
{
|
||||
std::unique_ptr<FILE, decltype(&fclose)> input(
|
||||
fmemopen((void*)allDeviceSpecs.c_str(),
|
||||
allDeviceSpecs.size(), "r"),
|
||||
&fclose);
|
||||
|
||||
if (!input) {
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Failed to fmemopen() a FILE* for "
|
||||
"parsing device specs");
|
||||
}
|
||||
|
||||
deviceSpeclin = input.get();
|
||||
if (deviceSpecpparse()) {
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Failed to parse device specs. "
|
||||
"Check specs for errors");
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
%option prefix="deviceSpecl"
|
||||
%{
|
||||
#include <vector>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
#include "deviceSpecp.hh"
|
||||
%}
|
||||
|
||||
%%
|
||||
"+adev" {
|
||||
deviceSpecplval.chr = yytext[1];
|
||||
return KEYWORD_SPECTYPE_ACTUATOR;
|
||||
}
|
||||
"+edev" {
|
||||
deviceSpecplval.chr = yytext[1];
|
||||
return KEYWORD_SPECTYPE_EXTROSPECTOR;
|
||||
}
|
||||
"+idev" {
|
||||
deviceSpecplval.chr = yytext[1];
|
||||
return KEYWORD_SPECTYPE_INTEROSPECTOR;
|
||||
}
|
||||
"||" { return DOUBLE_PIPE; }
|
||||
"|" { return PIPE; }
|
||||
"(" { return LPAREN; }
|
||||
")" { return RPAREN; }
|
||||
[^\|\(\) \t\r\n]+ { deviceSpecplval.str = strdup(yytext); return STRING; }
|
||||
\r?\n { /* ignore newlines */ }
|
||||
[ \t]+ { /* ignore whitespace */ }
|
||||
. { return yytext[0]; }
|
||||
%%
|
||||
|
||||
int deviceSpeclwrap(void)
|
||||
{
|
||||
return 1; // Indicate end of input
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
%{
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
|
||||
#ifndef yylex
|
||||
/* We use different prefixes for the lexer and parser.
|
||||
* * Our lexer's prefix is deviceSpecl.
|
||||
* * Our parser's prefix is deviceSpecp.
|
||||
*
|
||||
* Yacc and Bison don't have a way to handle the scenario where the lexer has
|
||||
* a different prefix from the parser that they generate. They assume that the
|
||||
* lexer must have the same prefix as the parser they generate. So we just use
|
||||
* this #define below to override yacc/bison's presumed prefix for the lexer.
|
||||
*/
|
||||
#error "Yacc should have defined yylex as a preprocessor token, and we need to override it to tell yacc the name of our lex function."
|
||||
#endif
|
||||
#undef yylex
|
||||
#define yylex deviceSpecllex
|
||||
|
||||
// Declare the symbols that our lexer will export.
|
||||
int yylex(void);
|
||||
void yyerror(const char *message)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string("deviceSpec parser error: ")
|
||||
+ std::string(message));
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%union {
|
||||
char* str;
|
||||
char chr;
|
||||
DeviceManager::SensorDeviceSpec* sensorSpec;
|
||||
DeviceManager::InteroceptorDeviceSpec* interoceptorSpec;
|
||||
DeviceManager::ExtrospectorDeviceSpec* extrospectorSpec;
|
||||
std::vector<std::string>* stringVector;
|
||||
}
|
||||
|
||||
%token <str> STRING
|
||||
%token PIPE DOUBLE_PIPE LPAREN RPAREN
|
||||
%token <chr> KEYWORD_SPECTYPE_ACTUATOR
|
||||
%token <chr> KEYWORD_SPECTYPE_EXTROSPECTOR KEYWORD_SPECTYPE_INTEROSPECTOR
|
||||
|
||||
%type <stringVector> params opt_params
|
||||
%type <sensorSpec> spec_body
|
||||
%type <interoceptorSpec> interoceptor_spec
|
||||
%type <extrospectorSpec> extrospector_spec
|
||||
|
||||
%%
|
||||
|
||||
file: /* NOTHING */
|
||||
| sensor_specs
|
||||
;
|
||||
|
||||
sensor_specs:
|
||||
sensor_spec
|
||||
| sensor_specs DOUBLE_PIPE sensor_spec
|
||||
;
|
||||
|
||||
sensor_spec:
|
||||
interoceptor_spec
|
||||
| extrospector_spec
|
||||
;
|
||||
|
||||
interoceptor_spec:
|
||||
KEYWORD_SPECTYPE_INTEROSPECTOR PIPE spec_body {
|
||||
auto spec = std::make_unique<DeviceManager::InteroceptorDeviceSpec>(
|
||||
*static_cast<DeviceManager::InteroceptorDeviceSpec *>($3));
|
||||
|
||||
spec->sensorType = $1;
|
||||
DeviceManager::interoceptorDeviceSpecs.push_back(std::move(spec));
|
||||
DeviceManager::sensorDeviceSpecs.push_back(*spec);
|
||||
delete $3;
|
||||
}
|
||||
;
|
||||
|
||||
extrospector_spec:
|
||||
KEYWORD_SPECTYPE_EXTROSPECTOR PIPE spec_body {
|
||||
auto spec = std::make_unique<DeviceManager::ExtrospectorDeviceSpec>(
|
||||
*static_cast<DeviceManager::ExtrospectorDeviceSpec *>($3));
|
||||
|
||||
spec->sensorType = $1;
|
||||
DeviceManager::extrospectorDeviceSpecs.push_back(std::move(spec));
|
||||
DeviceManager::sensorDeviceSpecs.push_back(*spec);
|
||||
delete $3;
|
||||
}
|
||||
;
|
||||
|
||||
spec_body:
|
||||
STRING PIPE STRING LPAREN opt_params RPAREN PIPE STRING LPAREN opt_params RPAREN PIPE STRING {
|
||||
$$ = new DeviceManager::SensorDeviceSpec();
|
||||
$$->sensorType = '\0';
|
||||
$$->implexor = std::string($1);
|
||||
$$->api = std::string($3);
|
||||
$$->apiParams = std::move(*$5);
|
||||
$$->provider = std::string($8);
|
||||
$$->providerParams = std::move(*$10);
|
||||
$$->deviceSelector = std::string($13);
|
||||
delete $5;
|
||||
delete $10;
|
||||
}
|
||||
;
|
||||
|
||||
opt_params:
|
||||
params
|
||||
| /* empty */ { $$ = new std::vector<std::string>(); }
|
||||
;
|
||||
|
||||
params:
|
||||
STRING { $$ = new std::vector<std::string>{ $1 }; }
|
||||
| params PIPE STRING { $$ = $1; $$->push_back($3); }
|
||||
;
|
||||
|
||||
%%
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef _BODY_MAP_H
|
||||
#define _BODY_MAP_H
|
||||
|
||||
#include <set>
|
||||
#include <cstdint>
|
||||
|
||||
#include <body/limb.h>
|
||||
|
||||
class BodyMap {
|
||||
public:
|
||||
BodyMap() = default;
|
||||
~BodyMap() = default;
|
||||
|
||||
std::set<uint32_t, BodyLimb> limbs;
|
||||
};
|
||||
|
||||
#endif // _BODY_MAP_H
|
||||
@@ -1,64 +0,0 @@
|
||||
#ifndef BODY_MESSAGE_H
|
||||
#define BODY_MESSAGE_H
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#include <body/limb.h>
|
||||
#include <body/bodyPart.h>
|
||||
|
||||
class BodyMessage
|
||||
{
|
||||
public:
|
||||
BodyMessage() = default;
|
||||
~BodyMessage() = default;
|
||||
};
|
||||
|
||||
class BodySpotImpactEntry
|
||||
{
|
||||
public:
|
||||
enum class ReportType
|
||||
{
|
||||
PRESSURE,
|
||||
PAIN,
|
||||
PLEASURE,
|
||||
HEAT,
|
||||
COLD
|
||||
};
|
||||
|
||||
BodySpotImpactEntry(uint32_t _spot, ReportType _type, uint32_t _value)
|
||||
: spot(_spot), type(_type), value(_value)
|
||||
{}
|
||||
~BodySpotImpactEntry() = default;
|
||||
|
||||
public:
|
||||
uint32_t spot;
|
||||
ReportType type;
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
class BodySpotImpactInd
|
||||
: public BodyMessage
|
||||
{
|
||||
public:
|
||||
BodySpotImpactInd(BodyPart &_part) : part(_part) {}
|
||||
~BodySpotImpactInd() = default;
|
||||
|
||||
public:
|
||||
BodyPart ∂
|
||||
std::vector<BodySpotImpactEntry> entries;
|
||||
};
|
||||
|
||||
class BodyPartMsg
|
||||
: public BodyMessage
|
||||
{
|
||||
public:
|
||||
BodyPartMsg(const BodyPart& _part)
|
||||
:part(_part)
|
||||
{}
|
||||
|
||||
public:
|
||||
const BodyPart& part;
|
||||
};
|
||||
|
||||
#endif // BODY_MESSAGE_H
|
||||
@@ -1,28 +0,0 @@
|
||||
#ifndef BODY_LIMB_H
|
||||
#define BODY_LIMB_H
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <cstdint>
|
||||
|
||||
#include <body/bodyPart.h>
|
||||
|
||||
class BodyLimb
|
||||
{
|
||||
public:
|
||||
BodyLimb(uint32_t _id) : id(_id) {}
|
||||
BodyLimb(uint32_t _id,
|
||||
const std::string& _name, const std::string& _desc,
|
||||
const std::string& _loc)
|
||||
: id(_id), name(_name), description(_desc), location(_loc)
|
||||
{}
|
||||
|
||||
~BodyLimb() = default;
|
||||
|
||||
public:
|
||||
uint32_t id;
|
||||
std::string name, description, location;
|
||||
std::set<uint32_t, BodyPart> parts;
|
||||
};
|
||||
|
||||
#endif // BODY_LIMB_H
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef _CONCEPT_H
|
||||
#define _CONCEPT_H
|
||||
|
||||
#include <mentalEntity.h>
|
||||
|
||||
class Concept
|
||||
: public MentalEntity
|
||||
{
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,63 +0,0 @@
|
||||
#ifndef DEVICEMANAGER_H
|
||||
#define DEVICEMANAGER_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <opts.h>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
class DeviceManager
|
||||
{
|
||||
public:
|
||||
struct SensorDeviceSpec
|
||||
{
|
||||
char sensorType;
|
||||
std::string implexor;
|
||||
std::string api;
|
||||
std::vector<std::string> apiParams;
|
||||
std::string provider;
|
||||
std::vector<std::string> providerParams;
|
||||
std::string deviceSelector;
|
||||
|
||||
friend std::ostream& operator<<(
|
||||
std::ostream& os, const SensorDeviceSpec& spec);
|
||||
};
|
||||
|
||||
struct InteroceptorDeviceSpec : public SensorDeviceSpec
|
||||
{
|
||||
};
|
||||
|
||||
struct ExtrospectorDeviceSpec : public SensorDeviceSpec
|
||||
{
|
||||
};
|
||||
|
||||
static DeviceManager& getInstance()
|
||||
{
|
||||
static DeviceManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
std::string readDeviceFile(const std::string& filename);
|
||||
void collateAllDeviceSpecs(const OptionParser& options);
|
||||
void parseAllDeviceSpecs(void);
|
||||
static const std::string printDeviceSpecs();
|
||||
|
||||
private:
|
||||
DeviceManager() = default;
|
||||
~DeviceManager() = default;
|
||||
DeviceManager(const DeviceManager&) = delete;
|
||||
DeviceManager& operator=(const DeviceManager&) = delete;
|
||||
|
||||
public:
|
||||
std::string allDeviceSpecs;
|
||||
static std::vector<std::unique_ptr<InteroceptorDeviceSpec>>
|
||||
interoceptorDeviceSpecs;
|
||||
static std::vector<std::unique_ptr<ExtrospectorDeviceSpec>>
|
||||
extrospectorDeviceSpecs;
|
||||
static std::vector<std::reference_wrapper<SensorDeviceSpec>>
|
||||
sensorDeviceSpecs;
|
||||
};
|
||||
|
||||
#endif // DEVICEMANAGER_H
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef DIRECTOR_H
|
||||
#define DIRECTOR_H
|
||||
|
||||
#include <config.h>
|
||||
#include <goal.h>
|
||||
|
||||
namespace hk {
|
||||
namespace director {
|
||||
|
||||
class Director {
|
||||
public:
|
||||
Director() = default;
|
||||
~Director() = default;
|
||||
|
||||
Goal purpose;
|
||||
};
|
||||
|
||||
} // namespace director
|
||||
} // namespace hk
|
||||
|
||||
#endif // DIRECTOR_H
|
||||
@@ -1,14 +0,0 @@
|
||||
#ifndef _MENTAL_ENTITY_H
|
||||
#define _MENTAL_ENTITY_H
|
||||
|
||||
namespace hk {
|
||||
|
||||
class MentalEntity
|
||||
{
|
||||
public:
|
||||
using Id = uint32_t;
|
||||
};
|
||||
|
||||
} // namespace hk
|
||||
|
||||
#endif
|
||||
@@ -1,28 +0,0 @@
|
||||
#ifndef _MIND_H
|
||||
#define _MIND_H
|
||||
|
||||
#include <config.h>
|
||||
#include <thread>
|
||||
|
||||
#include <director/director.h>
|
||||
#include <simulator/simulator.h>
|
||||
|
||||
namespace hk {
|
||||
|
||||
class Mind
|
||||
{
|
||||
public:
|
||||
void execute(void);
|
||||
|
||||
public:
|
||||
std::thread directorThread;
|
||||
std::thread simulatorThread;
|
||||
std::thread subconsciousThread;
|
||||
|
||||
director::Director director;
|
||||
simulator::Simulator canvas;
|
||||
};
|
||||
|
||||
} // namespace hk
|
||||
|
||||
#endif
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef OPTS_H
|
||||
#define OPTS_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <getopt.h>
|
||||
|
||||
// Define a class to hold the options and parse arguments
|
||||
class OptionParser
|
||||
{
|
||||
public:
|
||||
OptionParser() : verbose(false), printUsage(false) {}
|
||||
~OptionParser() = default;
|
||||
|
||||
void parseArguments(int argc, char *argv[]);
|
||||
void dumpOptions() const;
|
||||
std::string getUsage() const;
|
||||
|
||||
static OptionParser &getOptions(void)
|
||||
{
|
||||
static OptionParser options;
|
||||
return options;
|
||||
}
|
||||
|
||||
public:
|
||||
std::string argv0;
|
||||
std::string senseApiLibPath;
|
||||
std::vector<std::string> senseApiLibs;
|
||||
std::string deviceSpecs;
|
||||
std::vector<std::string> deviceSpecFiles;
|
||||
bool verbose, printUsage;
|
||||
|
||||
static struct option longOptions[];
|
||||
};
|
||||
|
||||
#endif // OPTS_H
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef _QUALE_H
|
||||
#define _QUALE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <attentionTrigger.h>
|
||||
|
||||
class Quale
|
||||
{
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
NEUTRAL,
|
||||
/* Bounding refers to qualia such as tactile pressure which
|
||||
* are mostly neutral but disclose information about the limits
|
||||
* of the body.
|
||||
**/
|
||||
BOUNDING,
|
||||
PAINFUL,
|
||||
PLEASURABLE
|
||||
} type;
|
||||
|
||||
int32_t intensity;
|
||||
};
|
||||
|
||||
class NeutralQuale
|
||||
: public Quale
|
||||
{
|
||||
};
|
||||
|
||||
class NonNeutralQuale
|
||||
: public Quale, public AttentionTrigger
|
||||
{
|
||||
public:
|
||||
virtual void eventInd(void);
|
||||
|
||||
public:
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,119 +0,0 @@
|
||||
#ifndef SENSE_API_PROVIDER_DESC_H
|
||||
#define SENSE_API_PROVIDER_DESC_H
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <dlfcn.h>
|
||||
#include <functional>
|
||||
#include <user/senseApiDesc.h>
|
||||
|
||||
namespace hk {
|
||||
namespace sense_api {
|
||||
|
||||
/* C++ version of the C struct above, which Harikoff uses to manage the
|
||||
* lib and connect implexors to it.
|
||||
*/
|
||||
class SenseApiDesc
|
||||
{
|
||||
public:
|
||||
class ExportedImplexorApiDesc
|
||||
{
|
||||
public:
|
||||
ExportedImplexorApiDesc(const CExportedImplexorApiDesc& cDesc)
|
||||
// The caller should sanity check before calling this constructor.
|
||||
: name(cDesc.name)
|
||||
{}
|
||||
|
||||
const std::string name;
|
||||
};
|
||||
|
||||
public:
|
||||
SenseApiDesc() = default;
|
||||
SenseApiDesc(const CSenseApiDesc& cDesc)
|
||||
// The caller should sanity check before calling this constructor.
|
||||
: name(cDesc.name)
|
||||
{
|
||||
for (uint32_t i = 0; i < cDesc.numExportedImplexorApis; ++i)
|
||||
{
|
||||
if (!CExportedImplexorApiDesc_sanityCheck(
|
||||
&cDesc.exportedImplexorApis[i]))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Sanity check failed for exported implexor API descriptor");
|
||||
}
|
||||
|
||||
exportedImplexorApis.push_back(
|
||||
ExportedImplexorApiDesc(cDesc.exportedImplexorApis[i]));
|
||||
}
|
||||
}
|
||||
|
||||
std::string name;
|
||||
// These are the implexors whose APIs this lib exports.
|
||||
std::vector<ExportedImplexorApiDesc> exportedImplexorApis;
|
||||
};
|
||||
|
||||
class SenseApiLib
|
||||
{
|
||||
public:
|
||||
SenseApiLib(
|
||||
const std::string& path, void *_dlopen_handle, getSenseApiDescFn *descFn)
|
||||
: libraryPath(path),
|
||||
dlopen_handle(
|
||||
_dlopen_handle, reinterpret_cast<void(*)(void*)>(&dlclose)),
|
||||
HK_GET_SENSE_API_DESC_FN_NAME(descFn)
|
||||
{}
|
||||
|
||||
void setSenseApiDesc(const CSenseApiDesc* desc)
|
||||
{
|
||||
if (!CSenseApiDesc_sanityCheck(desc) ||
|
||||
!Csal_libMgmtOps_sanityCheck(desc->sal_libMgmtOps))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Sanity check failed for sense API "
|
||||
"descriptor in library '" + libraryPath + "'");
|
||||
}
|
||||
for (uint32_t i = 0; i < desc->numExportedImplexorApis; ++i)
|
||||
{
|
||||
if (!CExportedImplexorApiDesc_sanityCheck(
|
||||
&desc->exportedImplexorApis[i]))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Sanity check failed for "
|
||||
"exported implexor API descriptor in library '"
|
||||
+ libraryPath + "'");
|
||||
}
|
||||
}
|
||||
new (&senseApiDesc) SenseApiDesc(*desc); // Placement new
|
||||
}
|
||||
|
||||
public:
|
||||
std::string libraryPath;
|
||||
std::unique_ptr<void, void(*)(void*)> dlopen_handle;
|
||||
/* UNIMPLEMENTED: API-specific cmdline options. These affect this specific
|
||||
* sense api lib's behaviour globally.
|
||||
*/
|
||||
std::vector<std::string> options;
|
||||
|
||||
/**
|
||||
* @brief Every sense API lib is required to provide a function that returns
|
||||
* a CSenseApiDesc struct. This struct states which API the lib uses to
|
||||
* connect Harikoff to the sense provider it supports.
|
||||
*
|
||||
* This getter function should be visible to dlsym() so that Harikoff can
|
||||
* find it in the lib after loading it, and call it.
|
||||
*/
|
||||
std::function<getSenseApiDescFn> HK_GET_SENSE_API_DESC_FN_NAME;
|
||||
|
||||
/**
|
||||
* @brief Harikoff will call the `HK_GET_SENSE_API_DESC_FN_NAME` getter
|
||||
* function and use the data it provides in order to fill out this
|
||||
* descriptor.
|
||||
*/
|
||||
SenseApiDesc senseApiDesc;
|
||||
};
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace hk
|
||||
|
||||
#endif // SENSE_API_PROVIDER_DESC_H
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef SENSE_API_MANAGER_H
|
||||
#define SENSE_API_MANAGER_H
|
||||
|
||||
#include <config.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <senseApis/senseApiLib.h>
|
||||
|
||||
namespace hk {
|
||||
namespace sense_api {
|
||||
|
||||
class SenseApiManager {
|
||||
public:
|
||||
static SenseApiManager& getInstance()
|
||||
{
|
||||
static SenseApiManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
SenseApiLib& loadSenseApiLib(const std::string& libraryPath);
|
||||
std::optional<std::reference_wrapper<SenseApiLib>> getSenseApiLib(
|
||||
const std::string& libraryPath);
|
||||
void unloadSenseApiLib(const std::string& libraryPath);
|
||||
void loadAllSenseApiLibsFromOptions(void);
|
||||
|
||||
private:
|
||||
SenseApiManager() = default;
|
||||
~SenseApiManager() = default;
|
||||
|
||||
SenseApiManager(const SenseApiManager&) = delete;
|
||||
SenseApiManager& operator=(const SenseApiManager&) = delete;
|
||||
|
||||
std::vector<std::unique_ptr<SenseApiLib>> senseApiLibs;
|
||||
};
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace hk
|
||||
|
||||
#endif // SENSE_API_MANAGER_H
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
#include <mind.h>
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
#include <opts.h>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <getopt.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
struct option OptionParser::longOptions[] = {
|
||||
{"devicespec", required_argument, 0, 's'},
|
||||
{"spec", required_argument, 0, 's'},
|
||||
{"devspec", required_argument, 0, 's'},
|
||||
{"devfile", required_argument, 0, 'd'},
|
||||
{"devicefile", required_argument, 0, 'd'},
|
||||
{"sense-api-lib", required_argument, 0, 'a'},
|
||||
{"senseapi", required_argument, 0, 'a'},
|
||||
{"sense-api-path", required_argument, 0, 'p'},
|
||||
{"verbose", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, '?'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
void OptionParser::parseArguments(int argc, char *argv[])
|
||||
{
|
||||
int opt;
|
||||
int optionIndex = 0;
|
||||
|
||||
argv0 = argv[0];
|
||||
|
||||
optind = 1; // Reset optind to 1 before parsing
|
||||
while ((opt = getopt_long(
|
||||
argc, argv, "s:d:a:p:v?", longOptions, &optionIndex)) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
if (!deviceSpecs.empty()) {
|
||||
deviceSpecs += "||";
|
||||
}
|
||||
deviceSpecs += std::string(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
deviceSpecFiles.push_back(optarg);
|
||||
break;
|
||||
case 'a':
|
||||
senseApiLibs.push_back(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
{
|
||||
struct stat info;
|
||||
|
||||
if (!senseApiLibPath.empty())
|
||||
{
|
||||
std::cerr << std::string(__func__)
|
||||
+ " - Overwriting previous sense-api-path with: " << optarg
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
if (stat(optarg, &info) != 0 || !(info.st_mode & S_IFDIR))
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
std::string(__func__) + " - The specified path is not a "
|
||||
"directory: " + optarg);
|
||||
}
|
||||
|
||||
senseApiLibPath = optarg;
|
||||
break;
|
||||
}
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case '?':
|
||||
printUsage = true;
|
||||
return;
|
||||
default:
|
||||
throw std::invalid_argument(
|
||||
std::string(__func__) + " - Invalid argument encountered: "
|
||||
+ std::string(argv[optind - 1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string OptionParser::getUsage() const
|
||||
{
|
||||
return "Usage: " + argv0 + " [-s|--devicespec|--spec|--devspec <device_spec>] "
|
||||
"[-d|--devfile|--devicefile <filename>] "
|
||||
"[-a|--sense-api-lib|--senseapi <filename>] "
|
||||
"[-p|--sense-api-path <directory>] "
|
||||
"[-v|--verbose] "
|
||||
"[-?|--help]";
|
||||
}
|
||||
|
||||
void OptionParser::dumpOptions() const
|
||||
{
|
||||
if (verbose) {
|
||||
std::cout << "Verbose mode is on" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Device Specs: " << deviceSpecs << std::endl;
|
||||
|
||||
std::cout << "Device Spec Files: ";
|
||||
for (const auto& file : deviceSpecFiles) {
|
||||
std::cout << file << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << "Sense API Library Path: " << senseApiLibPath << std::endl;
|
||||
std::cout << "Sense API Libraries: ";
|
||||
for (const auto& lib : senseApiLibs) {
|
||||
std::cout << lib << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
AM_CPPFLAGS+= -I"$(top_srcdir)/hcore/include"
|
||||
|
||||
noinst_LIBRARIES=libsenseApis.a
|
||||
libsenseApis_a_SOURCES=senseApiManager.cpp
|
||||
@@ -1,155 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
#include <filesystem>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <senseApis/senseApiLib.h>
|
||||
#include <opts.h>
|
||||
#include <user/senseApiDesc.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace hk {
|
||||
namespace sense_api {
|
||||
|
||||
struct DlCloser {
|
||||
void operator()(void* handle) const {
|
||||
if (handle) {
|
||||
dlclose(handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static std::optional<std::string> findLibraryPath(
|
||||
const std::string& libraryPath)
|
||||
{
|
||||
std::vector<std::string> searchPaths = {
|
||||
fs::current_path().string(),
|
||||
fs::path("/proc/self/exe").parent_path().string()
|
||||
};
|
||||
|
||||
if (!OptionParser::getOptions().senseApiLibPath.empty())
|
||||
{
|
||||
searchPaths.insert(
|
||||
searchPaths.begin(), OptionParser::getOptions().senseApiLibPath);
|
||||
}
|
||||
|
||||
for (const auto& path : searchPaths)
|
||||
{
|
||||
fs::path fullPath = fs::path(path) / libraryPath;
|
||||
if (fs::exists(fullPath))
|
||||
{
|
||||
return fullPath.string();
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << std::string(__func__) + ": library '"
|
||||
+ libraryPath + "' isn't in search bespoke search paths: ";
|
||||
for (const auto& path : searchPaths)
|
||||
{
|
||||
std::cerr << path << " ";
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "Trying to load " + libraryPath + " from system default search "
|
||||
"paths\n";
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
SenseApiLib& SenseApiManager::loadSenseApiLib(const std::string& libraryPath)
|
||||
{
|
||||
const CSenseApiDesc *libApiDesc;
|
||||
std::optional<std::string> fullPath = findLibraryPath(libraryPath);
|
||||
std::string resolvedPath = fullPath.value_or(libraryPath);
|
||||
|
||||
// Clear any existing error
|
||||
dlerror();
|
||||
auto dlopen_handle = std::unique_ptr<void, DlCloser>(
|
||||
dlopen(resolvedPath.c_str(), RTLD_LAZY));
|
||||
if (!dlopen_handle && fullPath.has_value())
|
||||
{
|
||||
// Fallback to using the supplied libraryPath
|
||||
dlerror();
|
||||
dlopen_handle.reset(dlopen(libraryPath.c_str(), RTLD_LAZY));
|
||||
}
|
||||
|
||||
if (!dlopen_handle)
|
||||
{
|
||||
const char *dlerr = dlerror();
|
||||
|
||||
std::string error = (dlerr
|
||||
? dlerr
|
||||
: "Unknown error while opening shlib");
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Cannot load library '"
|
||||
+ libraryPath + "': "
|
||||
+ error);
|
||||
}
|
||||
|
||||
// Initialize getSenseApiDescriptor
|
||||
auto func = reinterpret_cast<getSenseApiDescFn *>(
|
||||
dlsym(dlopen_handle.get(), HK_GET_SENSE_API_DESC_FN_NAME_STR));
|
||||
if (!func)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": dlsym('"
|
||||
HK_GET_SENSE_API_DESC_FN_NAME_STR "') failed for library '"
|
||||
+ libraryPath + "'");
|
||||
}
|
||||
|
||||
libApiDesc = func();
|
||||
if (!libApiDesc)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": getSenseApiDesc() returned NULL for "
|
||||
"library '" + libraryPath + "'");
|
||||
}
|
||||
|
||||
auto lib = std::make_unique<SenseApiLib>(
|
||||
libraryPath, dlopen_handle.release(), func);
|
||||
lib->setSenseApiDesc(libApiDesc);
|
||||
senseApiLibs.push_back(std::move(lib));
|
||||
return *senseApiLibs.back();
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<SenseApiLib>>
|
||||
SenseApiManager::getSenseApiLib(const std::string& libraryPath)
|
||||
{
|
||||
auto it = std::find_if(senseApiLibs.begin(), senseApiLibs.end(),
|
||||
[&libPath = libraryPath](const std::unique_ptr<SenseApiLib>& lib) {
|
||||
return lib->libraryPath == libPath;
|
||||
}
|
||||
);
|
||||
|
||||
if (it != senseApiLibs.end()) { return **it; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void SenseApiManager::unloadSenseApiLib(const std::string& libraryPath)
|
||||
{
|
||||
auto it = std::find_if(senseApiLibs.begin(), senseApiLibs.end(),
|
||||
[&lpath = libraryPath](const std::unique_ptr<SenseApiLib>& lib) {
|
||||
return lib->libraryPath == lpath;
|
||||
}
|
||||
);
|
||||
|
||||
if (it != senseApiLibs.end())
|
||||
{
|
||||
senseApiLibs.erase(it);
|
||||
return;
|
||||
}
|
||||
|
||||
std::cerr << std::string(__func__) + ": Library not found: "
|
||||
<< libraryPath << '\n';
|
||||
}
|
||||
|
||||
void SenseApiManager::loadAllSenseApiLibsFromOptions()
|
||||
{
|
||||
const auto& options = OptionParser::getOptions();
|
||||
for (const auto& libPath : options.senseApiLibs) {
|
||||
loadSenseApiLib(libPath);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sense_api
|
||||
} // namespace hk
|
||||
@@ -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
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef _CONFIG_H
|
||||
#define _CONFIG_H
|
||||
|
||||
/* Package information */
|
||||
#define PACKAGE_NAME "@PROJECT_NAME@"
|
||||
#define PACKAGE_VERSION "@PROJECT_VERSION@"
|
||||
|
||||
/* Mind oscillator configuration */
|
||||
#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
|
||||
|
||||
/* Common Libraries */
|
||||
#cmakedefine CONFIG_LIB_XCBXORG_ENABLED
|
||||
#cmakedefine CONFIG_LIB_ALSA_ENABLED
|
||||
|
||||
/* Sense APIs */
|
||||
#cmakedefine CONFIG_SENSEAPI_XCBWINDOW_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_V4L_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_ALSAMIC_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_LIVOX_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_R3LIVE_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_FASTLIO2_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_ADALIO2_ENABLED
|
||||
#cmakedefine CONFIG_SENSEAPI_DEEPLIO2_ENABLED
|
||||
|
||||
/* Wilzor APIs */
|
||||
#cmakedefine CONFIG_WILZORAPI_XCBMOUSE_ENABLED
|
||||
#cmakedefine CONFIG_WILZORAPI_XCBKEYBOARD_ENABLED
|
||||
#cmakedefine CONFIG_WILZORAPI_ALSAVOICE_ENABLED
|
||||
|
||||
/* Legacy defines for backward compatibility */
|
||||
#cmakedefine CONFIG_XCBWINDOW_ENABLED
|
||||
#cmakedefine CONFIG_V4L_ENABLED
|
||||
#cmakedefine CONFIG_ALSAMIC_ENABLED
|
||||
#cmakedefine CONFIG_LIVOX_ENABLED
|
||||
#cmakedefine CONFIG_R3LIVE_ENABLED
|
||||
#cmakedefine CONFIG_FASTLIO2_ENABLED
|
||||
#cmakedefine CONFIG_ADALIO2_ENABLED
|
||||
#cmakedefine CONFIG_DEEPLIO2_ENABLED
|
||||
#cmakedefine CONFIG_XCBMOUSE_ENABLED
|
||||
#cmakedefine CONFIG_XCBKEYBOARD_ENABLED
|
||||
#cmakedefine CONFIG_ALSAVOICE_ENABLED
|
||||
|
||||
#endif /* _CONFIG_H */
|
||||
@@ -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
|
||||
@@ -1,9 +1,11 @@
|
||||
#ifndef HK_PREPROCESSOR_H
|
||||
#define HK_PREPROCESSOR_H
|
||||
#ifndef SMO_PREPROCESSOR_H
|
||||
#define SMO_PREPROCESSOR_H
|
||||
|
||||
#define HK_Q(x) #x
|
||||
#define HK_QUOTE(x) HK_Q(x)
|
||||
#define SMO_Q(x) #x
|
||||
#define SMO_QUOTE(x) SMO_Q(x)
|
||||
|
||||
#define HK_UNMANGLED "C"
|
||||
#define SMO_CONCAT(a, b) a ## b
|
||||
|
||||
#endif // HK_PREPROCESSOR_H
|
||||
#define SMO_UNMANGLED "C"
|
||||
|
||||
#endif // SMO_PREPROCESSOR_H
|
||||
|
||||
+109
@@ -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
|
||||
@@ -0,0 +1,117 @@
|
||||
#ifndef SENSORDEVICESPEC_H
|
||||
#define SENSORDEVICESPEC_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace smo {
|
||||
namespace device {
|
||||
|
||||
class DeviceAttachmentSpec
|
||||
{
|
||||
public:
|
||||
friend std::ostream& operator<<(
|
||||
std::ostream& os, const DeviceAttachmentSpec& spec)
|
||||
{
|
||||
os << spec.stringify();
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator==(const DeviceAttachmentSpec& other) const
|
||||
{
|
||||
return deviceIdentifier == other.deviceIdentifier &&
|
||||
sensorType == other.sensorType &&
|
||||
provider == other.provider &&
|
||||
deviceSelector == other.deviceSelector;
|
||||
}
|
||||
|
||||
public:
|
||||
std::string deviceIdentifier;
|
||||
char sensorType;
|
||||
std::string implexor;
|
||||
std::string api;
|
||||
std::vector<std::pair<std::string,std::string>> apiParams;
|
||||
std::string provider;
|
||||
std::vector<std::pair<std::string,std::string>> providerParams;
|
||||
std::string deviceSelector;
|
||||
|
||||
std::string stringify() const
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Device Identifier: " << deviceIdentifier
|
||||
<< ", Sensor Type: " << sensorType
|
||||
<< ", Implexor: " << implexor << ", API: " << api
|
||||
<< ", API Params: (";
|
||||
for (const auto& param : apiParams)
|
||||
{
|
||||
os << param.first;
|
||||
if (!param.second.empty()) {
|
||||
os << "=" << param.second;
|
||||
}
|
||||
os << " ";
|
||||
}
|
||||
os << "), Provider: " << provider << ", Provider Params: (";
|
||||
for (const auto& param : providerParams)
|
||||
{
|
||||
os << param.first;
|
||||
if (!param.second.empty()) {
|
||||
os << "=" << param.second;
|
||||
}
|
||||
os << " ";
|
||||
}
|
||||
os << "), Device Selector: " << deviceSelector << std::endl;
|
||||
|
||||
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
|
||||
{
|
||||
};
|
||||
|
||||
class ExtrospectorDevAttachmentSpec : public DeviceAttachmentSpec
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
} // namespace smo
|
||||
|
||||
#endif // SENSORDEVICESPEC_H
|
||||
+159
-71
@@ -1,100 +1,188 @@
|
||||
#ifndef __USER_SENSE_API_LIB_H__
|
||||
#define __USER_SENSE_API_LIB_H__
|
||||
|
||||
#include <preprocessor.h>
|
||||
#include <stdbool.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <preprocessor.h>
|
||||
#include <componentThread.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
namespace smo {
|
||||
namespace sense_api {
|
||||
|
||||
/* Exported by all sense API Libraries to tell Harikoff what API the lib uses
|
||||
* to connect to providers; and also to state which implexor APIs it exports.
|
||||
/**
|
||||
* @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 CExportedImplexorApiDesc
|
||||
struct SmoThreadingModelDesc
|
||||
{
|
||||
const char *name;
|
||||
/**
|
||||
* @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 int (sal_lmo_initializeIndFn)(void);
|
||||
typedef int (sal_lmo_finalizeIndFn)(void);
|
||||
typedef int (sal_lmo_attachDeviceReqFn)(void);
|
||||
typedef int (sal_lmo_detachDeviceReqFn)(void);
|
||||
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;
|
||||
|
||||
struct Csal_libMgmtOps
|
||||
typedef int (sal_mlo_initializeIndFn)(void);
|
||||
typedef int (sal_mlo_finalizeIndFn)(void);
|
||||
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.
|
||||
|
||||
* This structure contains function pointers that senseApi libraries can use
|
||||
* to interact with Salmanoff's functionality, such as searching for commonLibs.
|
||||
*/
|
||||
struct SmoCallbacks
|
||||
{
|
||||
/* When Harikoff loads a sense API lib, it calls this function to initialize
|
||||
/**
|
||||
* @brief Search for a library in Salmanoff's search paths
|
||||
* @param libraryPath The relative filename of the library to search for
|
||||
* @return Optional containing the full path if found, nullopt if not found
|
||||
*
|
||||
* This function searches for the given library in the same search paths
|
||||
* that Salmanoff uses when loading senseApi libraries (user-specified
|
||||
* paths via -p option, current directory, and executable directory).
|
||||
*/
|
||||
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
|
||||
{
|
||||
/* When Salmanoff loads a sense API lib, it calls this function to initialize
|
||||
* the lib. When this returns, the lib should be ready to attach devices.
|
||||
*/
|
||||
sal_lmo_initializeIndFn *initializeInd;
|
||||
/* Harikoff calls this to finalize the lib and free its internal
|
||||
sal_mlo_initializeIndFn *initializeInd;
|
||||
/* Salmanoff calls this to finalize the lib and free its internal
|
||||
* resources. When this returns, the lib should be ready to be unloaded.
|
||||
*/
|
||||
sal_lmo_finalizeIndFn *finalizeInd;
|
||||
/* Harikoff calls this to attach a device to the lib. When it returns, the
|
||||
sal_mlo_finalizeIndFn *finalizeInd;
|
||||
/* Salmanoff calls this to attach a device to the lib. When it returns, the
|
||||
* device should be attached and ready to be implexed.
|
||||
*/
|
||||
sal_lmo_attachDeviceReqFn *attachDeviceReq;
|
||||
sal_mlo_attachDeviceReqFn *attachDeviceReq;
|
||||
// When this returns, the device should be detached.
|
||||
sal_lmo_detachDeviceReqFn *detachDeviceReq;
|
||||
sal_mlo_detachDeviceReqFn *detachDeviceReq;
|
||||
|
||||
static bool sanityCheck(const Sal_Mgmt_LibOps &ops)
|
||||
{
|
||||
if (!ops.initializeInd || !ops.finalizeInd
|
||||
|| !ops.attachDeviceReq || !ops.detachDeviceReq)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct CSenseApiDesc
|
||||
/* Exported by all sense API Libraries to tell Salmanoff what API the lib uses
|
||||
* to connect to providers; and also to state which implexor APIs it exports.
|
||||
*/
|
||||
class SenseApiDesc
|
||||
{
|
||||
/* Shortname that identifies the API used by this lib to talk to the sense
|
||||
* provider.
|
||||
*/
|
||||
const char *name;
|
||||
/* These are the implexors whose APIs this lib exports.
|
||||
*/
|
||||
uint32_t numExportedImplexorApis;
|
||||
CExportedImplexorApiDesc *exportedImplexorApis;
|
||||
/* Sub-API for managing the lib. Library role within the API.
|
||||
*/
|
||||
Csal_libMgmtOps *sal_libMgmtOps;
|
||||
public:
|
||||
class ExportedImplexorApiDesc
|
||||
{
|
||||
public:
|
||||
static bool sanityCheck(const ExportedImplexorApiDesc &desc)
|
||||
{
|
||||
if (desc.name.empty()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
std::string name;
|
||||
};
|
||||
|
||||
public:
|
||||
std::string stringify() const
|
||||
{
|
||||
std::string result = "Name: " + name + "\n";
|
||||
result += "Exported Implexor APIs:\n";
|
||||
for (const auto& api : exportedImplexorApis) {
|
||||
result += " - " + api.name + "\n";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool sanityCheck(const SenseApiDesc &desc)
|
||||
{
|
||||
if (desc.name.empty() || desc.exportedImplexorApis.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& api : desc.exportedImplexorApis) {
|
||||
if (!ExportedImplexorApiDesc::sanityCheck(api)) { return false; }
|
||||
}
|
||||
|
||||
return Sal_Mgmt_LibOps::sanityCheck(desc.sal_mgmt_libOps);
|
||||
}
|
||||
|
||||
std::string name;
|
||||
// These are the implexors whose APIs this lib exports.
|
||||
std::vector<ExportedImplexorApiDesc> exportedImplexorApis;
|
||||
Sal_Mgmt_LibOps sal_mgmt_libOps;
|
||||
};
|
||||
|
||||
static bool CSenseApiDesc_sanityCheck(const CSenseApiDesc *desc)
|
||||
{
|
||||
if (!desc || !desc->name || desc->numExportedImplexorApis < 1
|
||||
||!desc->exportedImplexorApis || !desc->sal_libMgmtOps)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#define SMO_GET_SENSE_API_DESC_FN_NAME getSenseApiDesc
|
||||
#define SMO_GET_SENSE_API_DESC_FN_NAME_STR \
|
||||
SMO_QUOTE(SMO_GET_SENSE_API_DESC_FN_NAME)
|
||||
#define SMO_GET_SENSE_API_DESC_FN_TYPEDEF \
|
||||
SMO_CONCAT(SMO_GET_SENSE_API_DESC_FN_NAME, Fn)
|
||||
|
||||
static bool CExportedImplexorApiDesc_sanityCheck(
|
||||
const CExportedImplexorApiDesc *desc)
|
||||
{
|
||||
if (!desc || !desc->name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/* Every Sense API library must define a global instance of this
|
||||
* function. Salmanoff will search for it and invoke it via dlsym().
|
||||
*
|
||||
* The function must return a SenseApiDesc struct that Smo will tell
|
||||
* Smo what implexors can be used with it & what APIs it exports.
|
||||
* The SenseApiDesc struct also gives Smo pointers to API functions
|
||||
* to invoke for communication between Smo and the library.
|
||||
*
|
||||
* 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 SmoCallbacks& callbacks,
|
||||
const SmoThreadingModelDesc& threadingModel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Csal_libMgmtOps_sanityCheck(const Csal_libMgmtOps *ops)
|
||||
{
|
||||
if (!ops || !ops->initializeInd || !ops->finalizeInd
|
||||
|| !ops->attachDeviceReq || !ops->detachDeviceReq)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define HK_GET_SENSE_API_DESC_FN_NAME getSenseApiDesc
|
||||
#define HK_GET_SENSE_API_DESC_FN_NAME_STR \
|
||||
HK_QUOTE(HK_GET_SENSE_API_DESC_FN_NAME)
|
||||
|
||||
typedef const CSenseApiDesc *(getSenseApiDescFn)(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
} // namespace sense_api
|
||||
} // namespace smo
|
||||
|
||||
#endif // __USER_SENSE_API_LIB_H__
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# This script was snagged from the MESA project, which uses an MIT license.
|
||||
|
||||
#
|
||||
# Check for Bison.
|
||||
#
|
||||
# This macro verifies that Bison is installed. If successful, then
|
||||
# 1) YACC is set to bison -y (to emulate YACC calls)
|
||||
# 2) BISON is set to bison
|
||||
#
|
||||
AC_DEFUN([AC_PROG_BISON],
|
||||
[AC_PROG_YACC()
|
||||
if test "$YACC" != "bison -y"; then
|
||||
AC_SUBST(BISON,[])
|
||||
AC_MSG_WARN([bison not found])
|
||||
else
|
||||
AC_SUBST(BISON,[bison])
|
||||
fi
|
||||
])
|
||||
@@ -1,18 +0,0 @@
|
||||
# This script was snagged from the MESA project, which uses an MIT license.
|
||||
|
||||
#
|
||||
# Check for FLEX.
|
||||
#
|
||||
# This macro verifies that flex is installed. If successful, then
|
||||
# 1) LEX is set to flex (to emulate lex calls)
|
||||
# 2) FLEX is set to flex
|
||||
#
|
||||
AC_DEFUN([AC_PROG_FLEX], [
|
||||
AC_PROG_LEX(noyywrap)
|
||||
if test "$LEX" != "flex"; then
|
||||
AC_SUBST(FLEX,[])
|
||||
AC_MSG_ERROR([flex not found])
|
||||
else
|
||||
AC_SUBST(FLEX,[flex])
|
||||
fi
|
||||
])
|
||||
Vendored
-8427
File diff suppressed because it is too large
Load Diff
Vendored
-437
@@ -1,437 +0,0 @@
|
||||
# Helper functions for option handling. -*- Autoconf -*-
|
||||
#
|
||||
# Copyright (C) 2004-2005, 2007-2009, 2011-2019, 2021-2022 Free
|
||||
# Software Foundation, Inc.
|
||||
# Written by Gary V. Vaughan, 2004
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# serial 8 ltoptions.m4
|
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define.
|
||||
AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])])
|
||||
|
||||
|
||||
# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME)
|
||||
# ------------------------------------------
|
||||
m4_define([_LT_MANGLE_OPTION],
|
||||
[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])])
|
||||
|
||||
|
||||
# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME)
|
||||
# ---------------------------------------
|
||||
# Set option OPTION-NAME for macro MACRO-NAME, and if there is a
|
||||
# matching handler defined, dispatch to it. Other OPTION-NAMEs are
|
||||
# saved as a flag.
|
||||
m4_define([_LT_SET_OPTION],
|
||||
[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl
|
||||
m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]),
|
||||
_LT_MANGLE_DEFUN([$1], [$2]),
|
||||
[m4_warning([Unknown $1 option '$2'])])[]dnl
|
||||
])
|
||||
|
||||
|
||||
# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET])
|
||||
# ------------------------------------------------------------
|
||||
# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
|
||||
m4_define([_LT_IF_OPTION],
|
||||
[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])])
|
||||
|
||||
|
||||
# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET)
|
||||
# -------------------------------------------------------
|
||||
# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME
|
||||
# are set.
|
||||
m4_define([_LT_UNLESS_OPTIONS],
|
||||
[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
|
||||
[m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option),
|
||||
[m4_define([$0_found])])])[]dnl
|
||||
m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3
|
||||
])[]dnl
|
||||
])
|
||||
|
||||
|
||||
# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST)
|
||||
# ----------------------------------------
|
||||
# OPTION-LIST is a space-separated list of Libtool options associated
|
||||
# with MACRO-NAME. If any OPTION has a matching handler declared with
|
||||
# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about
|
||||
# the unknown option and exit.
|
||||
m4_defun([_LT_SET_OPTIONS],
|
||||
[# Set options
|
||||
m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
|
||||
[_LT_SET_OPTION([$1], _LT_Option)])
|
||||
|
||||
m4_if([$1],[LT_INIT],[
|
||||
dnl
|
||||
dnl Simply set some default values (i.e off) if boolean options were not
|
||||
dnl specified:
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no
|
||||
])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no
|
||||
])
|
||||
dnl
|
||||
dnl If no reference was made to various pairs of opposing options, then
|
||||
dnl we run the default mode handler for the pair. For example, if neither
|
||||
dnl 'shared' nor 'disable-shared' was passed, we enable building of shared
|
||||
dnl archives by default:
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install],
|
||||
[_LT_ENABLE_FAST_INSTALL])
|
||||
_LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4],
|
||||
[_LT_WITH_AIX_SONAME([aix])])
|
||||
])
|
||||
])# _LT_SET_OPTIONS
|
||||
|
||||
|
||||
## --------------------------------- ##
|
||||
## Macros to handle LT_INIT options. ##
|
||||
## --------------------------------- ##
|
||||
|
||||
# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME)
|
||||
# -----------------------------------------
|
||||
m4_define([_LT_MANGLE_DEFUN],
|
||||
[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])])
|
||||
|
||||
|
||||
# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE)
|
||||
# -----------------------------------------------
|
||||
m4_define([LT_OPTION_DEFINE],
|
||||
[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl
|
||||
])# LT_OPTION_DEFINE
|
||||
|
||||
|
||||
# dlopen
|
||||
# ------
|
||||
LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes
|
||||
])
|
||||
|
||||
AU_DEFUN([AC_LIBTOOL_DLOPEN],
|
||||
[_LT_SET_OPTION([LT_INIT], [dlopen])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you
|
||||
put the 'dlopen' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], [])
|
||||
|
||||
|
||||
# win32-dll
|
||||
# ---------
|
||||
# Declare package support for building win32 dll's.
|
||||
LT_OPTION_DEFINE([LT_INIT], [win32-dll],
|
||||
[enable_win32_dll=yes
|
||||
|
||||
case $host in
|
||||
*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*)
|
||||
AC_CHECK_TOOL(AS, as, false)
|
||||
AC_CHECK_TOOL(DLLTOOL, dlltool, false)
|
||||
AC_CHECK_TOOL(OBJDUMP, objdump, false)
|
||||
;;
|
||||
esac
|
||||
|
||||
test -z "$AS" && AS=as
|
||||
_LT_DECL([], [AS], [1], [Assembler program])dnl
|
||||
|
||||
test -z "$DLLTOOL" && DLLTOOL=dlltool
|
||||
_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl
|
||||
|
||||
test -z "$OBJDUMP" && OBJDUMP=objdump
|
||||
_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl
|
||||
])# win32-dll
|
||||
|
||||
AU_DEFUN([AC_LIBTOOL_WIN32_DLL],
|
||||
[AC_REQUIRE([AC_CANONICAL_HOST])dnl
|
||||
_LT_SET_OPTION([LT_INIT], [win32-dll])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you
|
||||
put the 'win32-dll' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], [])
|
||||
|
||||
|
||||
# _LT_ENABLE_SHARED([DEFAULT])
|
||||
# ----------------------------
|
||||
# implement the --enable-shared flag, and supports the 'shared' and
|
||||
# 'disable-shared' LT_INIT options.
|
||||
# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'.
|
||||
m4_define([_LT_ENABLE_SHARED],
|
||||
[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl
|
||||
AC_ARG_ENABLE([shared],
|
||||
[AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@],
|
||||
[build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])],
|
||||
[p=${PACKAGE-default}
|
||||
case $enableval in
|
||||
yes) enable_shared=yes ;;
|
||||
no) enable_shared=no ;;
|
||||
*)
|
||||
enable_shared=no
|
||||
# Look at the argument we got. We use all the common list separators.
|
||||
lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
|
||||
for pkg in $enableval; do
|
||||
IFS=$lt_save_ifs
|
||||
if test "X$pkg" = "X$p"; then
|
||||
enable_shared=yes
|
||||
fi
|
||||
done
|
||||
IFS=$lt_save_ifs
|
||||
;;
|
||||
esac],
|
||||
[enable_shared=]_LT_ENABLE_SHARED_DEFAULT)
|
||||
|
||||
_LT_DECL([build_libtool_libs], [enable_shared], [0],
|
||||
[Whether or not to build shared libraries])
|
||||
])# _LT_ENABLE_SHARED
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])])
|
||||
|
||||
# Old names:
|
||||
AC_DEFUN([AC_ENABLE_SHARED],
|
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared])
|
||||
])
|
||||
|
||||
AC_DEFUN([AC_DISABLE_SHARED],
|
||||
[_LT_SET_OPTION([LT_INIT], [disable-shared])
|
||||
])
|
||||
|
||||
AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)])
|
||||
AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AM_ENABLE_SHARED], [])
|
||||
dnl AC_DEFUN([AM_DISABLE_SHARED], [])
|
||||
|
||||
|
||||
|
||||
# _LT_ENABLE_STATIC([DEFAULT])
|
||||
# ----------------------------
|
||||
# implement the --enable-static flag, and support the 'static' and
|
||||
# 'disable-static' LT_INIT options.
|
||||
# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'.
|
||||
m4_define([_LT_ENABLE_STATIC],
|
||||
[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl
|
||||
AC_ARG_ENABLE([static],
|
||||
[AS_HELP_STRING([--enable-static@<:@=PKGS@:>@],
|
||||
[build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])],
|
||||
[p=${PACKAGE-default}
|
||||
case $enableval in
|
||||
yes) enable_static=yes ;;
|
||||
no) enable_static=no ;;
|
||||
*)
|
||||
enable_static=no
|
||||
# Look at the argument we got. We use all the common list separators.
|
||||
lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
|
||||
for pkg in $enableval; do
|
||||
IFS=$lt_save_ifs
|
||||
if test "X$pkg" = "X$p"; then
|
||||
enable_static=yes
|
||||
fi
|
||||
done
|
||||
IFS=$lt_save_ifs
|
||||
;;
|
||||
esac],
|
||||
[enable_static=]_LT_ENABLE_STATIC_DEFAULT)
|
||||
|
||||
_LT_DECL([build_old_libs], [enable_static], [0],
|
||||
[Whether or not to build static libraries])
|
||||
])# _LT_ENABLE_STATIC
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])])
|
||||
|
||||
# Old names:
|
||||
AC_DEFUN([AC_ENABLE_STATIC],
|
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static])
|
||||
])
|
||||
|
||||
AC_DEFUN([AC_DISABLE_STATIC],
|
||||
[_LT_SET_OPTION([LT_INIT], [disable-static])
|
||||
])
|
||||
|
||||
AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)])
|
||||
AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AM_ENABLE_STATIC], [])
|
||||
dnl AC_DEFUN([AM_DISABLE_STATIC], [])
|
||||
|
||||
|
||||
|
||||
# _LT_ENABLE_FAST_INSTALL([DEFAULT])
|
||||
# ----------------------------------
|
||||
# implement the --enable-fast-install flag, and support the 'fast-install'
|
||||
# and 'disable-fast-install' LT_INIT options.
|
||||
# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'.
|
||||
m4_define([_LT_ENABLE_FAST_INSTALL],
|
||||
[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl
|
||||
AC_ARG_ENABLE([fast-install],
|
||||
[AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@],
|
||||
[optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])],
|
||||
[p=${PACKAGE-default}
|
||||
case $enableval in
|
||||
yes) enable_fast_install=yes ;;
|
||||
no) enable_fast_install=no ;;
|
||||
*)
|
||||
enable_fast_install=no
|
||||
# Look at the argument we got. We use all the common list separators.
|
||||
lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
|
||||
for pkg in $enableval; do
|
||||
IFS=$lt_save_ifs
|
||||
if test "X$pkg" = "X$p"; then
|
||||
enable_fast_install=yes
|
||||
fi
|
||||
done
|
||||
IFS=$lt_save_ifs
|
||||
;;
|
||||
esac],
|
||||
[enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT)
|
||||
|
||||
_LT_DECL([fast_install], [enable_fast_install], [0],
|
||||
[Whether or not to optimize for fast installation])dnl
|
||||
])# _LT_ENABLE_FAST_INSTALL
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])])
|
||||
|
||||
# Old names:
|
||||
AU_DEFUN([AC_ENABLE_FAST_INSTALL],
|
||||
[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you put
|
||||
the 'fast-install' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
AU_DEFUN([AC_DISABLE_FAST_INSTALL],
|
||||
[_LT_SET_OPTION([LT_INIT], [disable-fast-install])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you put
|
||||
the 'disable-fast-install' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], [])
|
||||
dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], [])
|
||||
|
||||
|
||||
# _LT_WITH_AIX_SONAME([DEFAULT])
|
||||
# ----------------------------------
|
||||
# implement the --with-aix-soname flag, and support the `aix-soname=aix'
|
||||
# and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT
|
||||
# is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'.
|
||||
m4_define([_LT_WITH_AIX_SONAME],
|
||||
[m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl
|
||||
shared_archive_member_spec=
|
||||
case $host,$enable_shared in
|
||||
power*-*-aix[[5-9]]*,yes)
|
||||
AC_MSG_CHECKING([which variant of shared library versioning to provide])
|
||||
AC_ARG_WITH([aix-soname],
|
||||
[AS_HELP_STRING([--with-aix-soname=aix|svr4|both],
|
||||
[shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])],
|
||||
[case $withval in
|
||||
aix|svr4|both)
|
||||
;;
|
||||
*)
|
||||
AC_MSG_ERROR([Unknown argument to --with-aix-soname])
|
||||
;;
|
||||
esac
|
||||
lt_cv_with_aix_soname=$with_aix_soname],
|
||||
[AC_CACHE_VAL([lt_cv_with_aix_soname],
|
||||
[lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT)
|
||||
with_aix_soname=$lt_cv_with_aix_soname])
|
||||
AC_MSG_RESULT([$with_aix_soname])
|
||||
if test aix != "$with_aix_soname"; then
|
||||
# For the AIX way of multilib, we name the shared archive member
|
||||
# based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o',
|
||||
# and 'shr.imp' or 'shr_64.imp', respectively, for the Import File.
|
||||
# Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag,
|
||||
# the AIX toolchain works better with OBJECT_MODE set (default 32).
|
||||
if test 64 = "${OBJECT_MODE-32}"; then
|
||||
shared_archive_member_spec=shr_64
|
||||
else
|
||||
shared_archive_member_spec=shr
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
with_aix_soname=aix
|
||||
;;
|
||||
esac
|
||||
|
||||
_LT_DECL([], [shared_archive_member_spec], [0],
|
||||
[Shared archive member basename, for filename based shared library versioning on AIX])dnl
|
||||
])# _LT_WITH_AIX_SONAME
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])])
|
||||
|
||||
|
||||
# _LT_WITH_PIC([MODE])
|
||||
# --------------------
|
||||
# implement the --with-pic flag, and support the 'pic-only' and 'no-pic'
|
||||
# LT_INIT options.
|
||||
# MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'.
|
||||
m4_define([_LT_WITH_PIC],
|
||||
[AC_ARG_WITH([pic],
|
||||
[AS_HELP_STRING([--with-pic@<:@=PKGS@:>@],
|
||||
[try to use only PIC/non-PIC objects @<:@default=use both@:>@])],
|
||||
[lt_p=${PACKAGE-default}
|
||||
case $withval in
|
||||
yes|no) pic_mode=$withval ;;
|
||||
*)
|
||||
pic_mode=default
|
||||
# Look at the argument we got. We use all the common list separators.
|
||||
lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR,
|
||||
for lt_pkg in $withval; do
|
||||
IFS=$lt_save_ifs
|
||||
if test "X$lt_pkg" = "X$lt_p"; then
|
||||
pic_mode=yes
|
||||
fi
|
||||
done
|
||||
IFS=$lt_save_ifs
|
||||
;;
|
||||
esac],
|
||||
[pic_mode=m4_default([$1], [default])])
|
||||
|
||||
_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl
|
||||
])# _LT_WITH_PIC
|
||||
|
||||
LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])])
|
||||
LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])])
|
||||
|
||||
# Old name:
|
||||
AU_DEFUN([AC_LIBTOOL_PICMODE],
|
||||
[_LT_SET_OPTION([LT_INIT], [pic-only])
|
||||
AC_DIAGNOSE([obsolete],
|
||||
[$0: Remove this warning and the call to _LT_SET_OPTION when you
|
||||
put the 'pic-only' option into LT_INIT's first parameter.])
|
||||
])
|
||||
|
||||
dnl aclocal-1.4 backwards compatibility:
|
||||
dnl AC_DEFUN([AC_LIBTOOL_PICMODE], [])
|
||||
|
||||
## ----------------- ##
|
||||
## LTDL_INIT Options ##
|
||||
## ----------------- ##
|
||||
|
||||
m4_define([_LTDL_MODE], [])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive],
|
||||
[m4_define([_LTDL_MODE], [nonrecursive])])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [recursive],
|
||||
[m4_define([_LTDL_MODE], [recursive])])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [subproject],
|
||||
[m4_define([_LTDL_MODE], [subproject])])
|
||||
|
||||
m4_define([_LTDL_TYPE], [])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [installable],
|
||||
[m4_define([_LTDL_TYPE], [installable])])
|
||||
LT_OPTION_DEFINE([LTDL_INIT], [convenience],
|
||||
[m4_define([_LTDL_TYPE], [convenience])])
|
||||
Vendored
-124
@@ -1,124 +0,0 @@
|
||||
# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*-
|
||||
#
|
||||
# Copyright (C) 2004-2005, 2007-2008, 2011-2019, 2021-2022 Free Software
|
||||
# Foundation, Inc.
|
||||
# Written by Gary V. Vaughan, 2004
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# serial 6 ltsugar.m4
|
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define.
|
||||
AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])])
|
||||
|
||||
|
||||
# lt_join(SEP, ARG1, [ARG2...])
|
||||
# -----------------------------
|
||||
# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their
|
||||
# associated separator.
|
||||
# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier
|
||||
# versions in m4sugar had bugs.
|
||||
m4_define([lt_join],
|
||||
[m4_if([$#], [1], [],
|
||||
[$#], [2], [[$2]],
|
||||
[m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])])
|
||||
m4_define([_lt_join],
|
||||
[m4_if([$#$2], [2], [],
|
||||
[m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])])
|
||||
|
||||
|
||||
# lt_car(LIST)
|
||||
# lt_cdr(LIST)
|
||||
# ------------
|
||||
# Manipulate m4 lists.
|
||||
# These macros are necessary as long as will still need to support
|
||||
# Autoconf-2.59, which quotes differently.
|
||||
m4_define([lt_car], [[$1]])
|
||||
m4_define([lt_cdr],
|
||||
[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])],
|
||||
[$#], 1, [],
|
||||
[m4_dquote(m4_shift($@))])])
|
||||
m4_define([lt_unquote], $1)
|
||||
|
||||
|
||||
# lt_append(MACRO-NAME, STRING, [SEPARATOR])
|
||||
# ------------------------------------------
|
||||
# Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'.
|
||||
# Note that neither SEPARATOR nor STRING are expanded; they are appended
|
||||
# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked).
|
||||
# No SEPARATOR is output if MACRO-NAME was previously undefined (different
|
||||
# than defined and empty).
|
||||
#
|
||||
# This macro is needed until we can rely on Autoconf 2.62, since earlier
|
||||
# versions of m4sugar mistakenly expanded SEPARATOR but not STRING.
|
||||
m4_define([lt_append],
|
||||
[m4_define([$1],
|
||||
m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])])
|
||||
|
||||
|
||||
|
||||
# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...])
|
||||
# ----------------------------------------------------------
|
||||
# Produce a SEP delimited list of all paired combinations of elements of
|
||||
# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list
|
||||
# has the form PREFIXmINFIXSUFFIXn.
|
||||
# Needed until we can rely on m4_combine added in Autoconf 2.62.
|
||||
m4_define([lt_combine],
|
||||
[m4_if(m4_eval([$# > 3]), [1],
|
||||
[m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl
|
||||
[[m4_foreach([_Lt_prefix], [$2],
|
||||
[m4_foreach([_Lt_suffix],
|
||||
]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[,
|
||||
[_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])])
|
||||
|
||||
|
||||
# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ])
|
||||
# -----------------------------------------------------------------------
|
||||
# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited
|
||||
# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ.
|
||||
m4_define([lt_if_append_uniq],
|
||||
[m4_ifdef([$1],
|
||||
[m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1],
|
||||
[lt_append([$1], [$2], [$3])$4],
|
||||
[$5])],
|
||||
[lt_append([$1], [$2], [$3])$4])])
|
||||
|
||||
|
||||
# lt_dict_add(DICT, KEY, VALUE)
|
||||
# -----------------------------
|
||||
m4_define([lt_dict_add],
|
||||
[m4_define([$1($2)], [$3])])
|
||||
|
||||
|
||||
# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE)
|
||||
# --------------------------------------------
|
||||
m4_define([lt_dict_add_subkey],
|
||||
[m4_define([$1($2:$3)], [$4])])
|
||||
|
||||
|
||||
# lt_dict_fetch(DICT, KEY, [SUBKEY])
|
||||
# ----------------------------------
|
||||
m4_define([lt_dict_fetch],
|
||||
[m4_ifval([$3],
|
||||
m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]),
|
||||
m4_ifdef([$1($2)], [m4_defn([$1($2)])]))])
|
||||
|
||||
|
||||
# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE])
|
||||
# -----------------------------------------------------------------
|
||||
m4_define([lt_if_dict_fetch],
|
||||
[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4],
|
||||
[$5],
|
||||
[$6])])
|
||||
|
||||
|
||||
# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...])
|
||||
# --------------------------------------------------------------
|
||||
m4_define([lt_dict_filter],
|
||||
[m4_if([$5], [], [],
|
||||
[lt_join(m4_quote(m4_default([$4], [[, ]])),
|
||||
lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]),
|
||||
[lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl
|
||||
])
|
||||
Vendored
-24
@@ -1,24 +0,0 @@
|
||||
# ltversion.m4 -- version numbers -*- Autoconf -*-
|
||||
#
|
||||
# Copyright (C) 2004, 2011-2019, 2021-2022 Free Software Foundation,
|
||||
# Inc.
|
||||
# Written by Scott James Remnant, 2004
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# @configure_input@
|
||||
|
||||
# serial 4245 ltversion.m4
|
||||
# This file is part of GNU Libtool
|
||||
|
||||
m4_define([LT_PACKAGE_VERSION], [2.4.7])
|
||||
m4_define([LT_PACKAGE_REVISION], [2.4.7])
|
||||
|
||||
AC_DEFUN([LTVERSION_VERSION],
|
||||
[macro_version='2.4.7'
|
||||
macro_revision='2.4.7'
|
||||
_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?])
|
||||
_LT_DECL(, macro_revision, 0)
|
||||
])
|
||||
Vendored
-99
@@ -1,99 +0,0 @@
|
||||
# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*-
|
||||
#
|
||||
# Copyright (C) 2004-2005, 2007, 2009, 2011-2019, 2021-2022 Free
|
||||
# Software Foundation, Inc.
|
||||
# Written by Scott James Remnant, 2004.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
|
||||
# serial 5 lt~obsolete.m4
|
||||
|
||||
# These exist entirely to fool aclocal when bootstrapping libtool.
|
||||
#
|
||||
# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN),
|
||||
# which have later been changed to m4_define as they aren't part of the
|
||||
# exported API, or moved to Autoconf or Automake where they belong.
|
||||
#
|
||||
# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN
|
||||
# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us
|
||||
# using a macro with the same name in our local m4/libtool.m4 it'll
|
||||
# pull the old libtool.m4 in (it doesn't see our shiny new m4_define
|
||||
# and doesn't know about Autoconf macros at all.)
|
||||
#
|
||||
# So we provide this file, which has a silly filename so it's always
|
||||
# included after everything else. This provides aclocal with the
|
||||
# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything
|
||||
# because those macros already exist, or will be overwritten later.
|
||||
# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6.
|
||||
#
|
||||
# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here.
|
||||
# Yes, that means every name once taken will need to remain here until
|
||||
# we give up compatibility with versions before 1.7, at which point
|
||||
# we need to keep only those names which we still refer to.
|
||||
|
||||
# This is to help aclocal find these macros, as it can't see m4_define.
|
||||
AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])])
|
||||
|
||||
m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])])
|
||||
m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])])
|
||||
m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])])
|
||||
m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])])
|
||||
m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])])
|
||||
m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])])
|
||||
m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])])
|
||||
m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])])
|
||||
m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])])
|
||||
m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])])
|
||||
m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])])
|
||||
m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])])
|
||||
m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])])
|
||||
m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])])
|
||||
m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])])
|
||||
m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])])
|
||||
m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])])
|
||||
m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])])
|
||||
m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])])
|
||||
m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])])
|
||||
m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])])
|
||||
m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])])
|
||||
m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])])
|
||||
m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])])
|
||||
m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])])
|
||||
m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])])
|
||||
m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])])
|
||||
m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])])
|
||||
m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])])
|
||||
m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])])
|
||||
m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])])
|
||||
m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])])
|
||||
m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])])
|
||||
m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])])
|
||||
m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])])
|
||||
m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])])
|
||||
m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])])
|
||||
m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])])
|
||||
m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])])
|
||||
m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])])
|
||||
m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])])
|
||||
m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])])
|
||||
m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])])
|
||||
m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])])
|
||||
@@ -1,20 +0,0 @@
|
||||
SENSEAPIS_ENABLED=
|
||||
|
||||
AC_ARG_ENABLE([senseapi-xcbxorg],
|
||||
[AS_HELP_STRING([--enable-senseapi-xcbxorg],
|
||||
[Enable XCB/Xorg SenseAPI backend])],
|
||||
[AS_CASE([$enableval],
|
||||
[no], [enable_senseapi_xcbxorg=no],
|
||||
[yes|*], [
|
||||
enable_senseapi_xcbxorg=yes
|
||||
AC_DEFINE([XCBXORG_ENABLED], [1],
|
||||
[Define to 1 if you have XCB/Xorg SenseAPI backend])
|
||||
SENSEAPIS_ENABLED="${SENSEAPIS_ENABLED} xcbXorg"
|
||||
PKG_CHECK_MODULES([XCB], [xcb], [], [
|
||||
AC_MSG_ERROR(m4_normalize([XCB library not found. Sense API
|
||||
XCB/Xorg requires the XCB dev headers and shlib.]))
|
||||
])
|
||||
]
|
||||
)],
|
||||
[enable_senseapi_xcbxorg=no]
|
||||
)
|
||||
@@ -1,49 +1,27 @@
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <opts.h>
|
||||
#include <mind.h>
|
||||
#include <deviceManager/deviceManager.h>
|
||||
#include <senseApis/senseApiManager.h>
|
||||
#include <componentThread.h>
|
||||
#include <marionette/marionette.h>
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int main(int argc, char *argv[], char *envp[])
|
||||
{
|
||||
using namespace hk;
|
||||
/* We don't do anything inside of main()
|
||||
* Main merely waits for the marionette thread to exit.
|
||||
*/
|
||||
std::cout << "CRT:" << __func__ << ": about to JOLT Mrntt with cmdline args"
|
||||
<< '\n';
|
||||
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::thread->getIoService().stop();
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
OptionParser &options = OptionParser::getOptions();
|
||||
hk::Mind mind;
|
||||
|
||||
std::cout << PACKAGE_NAME << " " << PACKAGE_VERSION << std::endl;
|
||||
|
||||
try {
|
||||
options.parseArguments(argc, argv);
|
||||
}
|
||||
catch (const std::invalid_argument& e) {
|
||||
std::cerr << e.what() << '\n' << options.getUsage() << '\n';
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (options.printUsage) {
|
||||
std::cout << options.getUsage() << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
options.dumpOptions();
|
||||
DeviceManager::getInstance().collateAllDeviceSpecs(options);
|
||||
DeviceManager::getInstance().parseAllDeviceSpecs();
|
||||
std::cout << DeviceManager::printDeviceSpecs() << std::endl;
|
||||
sense_api::SenseApiManager::getInstance().loadAllSenseApiLibsFromOptions();
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "Exception occurred: " << e.what() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...) {
|
||||
std::cerr << "Unknown exception occurred" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::cout << "Exiting normally" << std::endl;
|
||||
return 0;
|
||||
smo::mrntt::thread->thread.join();
|
||||
std::cout << "CRT:" << __func__ << ": Mrntt exited with code '"
|
||||
<< smo::mrntt::exitCode << "'\n";
|
||||
return smo::mrntt::exitCode;
|
||||
}
|
||||
|
||||
Executable
+20
@@ -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"
|
||||
Executable
+90
@@ -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
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#! /usr/bin/bash
|
||||
|
||||
winNameToFind=${1}
|
||||
|
||||
printUsage()
|
||||
{
|
||||
echo "${0}: <STRING:windowNameToFind>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "${winNameToFind}x" = "x" ]; then
|
||||
printUsage
|
||||
fi
|
||||
|
||||
extractAllChildIdsFrom()
|
||||
{
|
||||
echo $(echo "${1}" \
|
||||
| grep '^[[:space:]]*0x[0-9a-zA-Z]' \
|
||||
| awk '{print $1}')
|
||||
return 0
|
||||
}
|
||||
|
||||
searchOneParentByName()
|
||||
{
|
||||
local thisWinId=$1
|
||||
local winNameToFind=$2
|
||||
|
||||
local thisWininfoStdout=$(xwininfo -id "${thisWinId}")
|
||||
local thisWinName=$(echo "${thisWininfoStdout}" \
|
||||
| grep '[Ww]indow[[:space:]]*[iI][dD]' \
|
||||
| grep -oP '[0-9a-zA-Z]*[[:space:]]*\".*\"$' \
|
||||
| sed 's/[0-9a-zA-Z]*[[:space:]]*//')
|
||||
echo "Searching win [ID=${thisWinId}, name=\"${thisWinName}\"] and its children..."
|
||||
if echo "${thisWinName}" | grep -q "${winNameToFind}"; then
|
||||
echo "${thisWininfoStdout}"
|
||||
# return 0
|
||||
fi
|
||||
|
||||
local allChildrenUnparsedOutput=$(xwininfo -id "${thisWinId}" -children)
|
||||
for i in $(extractAllChildIdsFrom "${allChildrenUnparsedOutput}");
|
||||
do
|
||||
searchOneParentByName "${i}" "${winNameToFind}"
|
||||
done
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
rootChildWindowOutput=$(xwininfo -root -children)
|
||||
for i in $(extractAllChildIdsFrom "${rootChildWindowOutput}");
|
||||
do
|
||||
searchOneParentByName "${i}" "${winNameToFind}"
|
||||
done
|
||||
|
||||
exit 1
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user