228 Commits

Author SHA1 Message Date
hayodea da1ca774e7 Version: Bump version to v0.01.000 2025-10-01 11:30:19 -04:00
hayodea abc11bc7fc LivoxGen1: Remove obsolete locking comment
We have now implemented serialization for device at/detachment
2025-10-01 11:28:59 -04:00
hayodea 1b29f0e521 CMake: Terser config tokens 2025-10-01 11:08:59 -04:00
hayodea f40225681c Update todo 2025-10-01 11:02:53 -04:00
hayodea e9e273c012 CMake: PRefix config opt with CONFIG_ 2025-10-01 11:02:28 -04:00
hayodea 942f9d8515 DevMgr: formatting 2025-10-01 10:21:32 -04:00
hayodea 1e22656299 DevMgr: Call uniq_ptr.release on DeviceReattacher 2025-10-01 10:19:08 -04:00
hayodea 80004ab1e7 CMake: Device Reattacher period is now 2000ms 2025-09-30 23:10:10 -04:00
hayodea 55fe2675df DevMgr: Use releaseQutexEarly 2025-09-30 22:53:17 -04:00
hayodea af19125ac2 Qutex: Add check for double-release() 2025-09-30 22:53:01 -04:00
hayodea eb7fe11de4 SerialAsyncContin/LockSet: add releaseQutexEarly()
This method allows us to avoid double-release()ing qutexes when we
want to release a qutex early. If we don't use releaseQutexEarly(),
then inside of callOriginalCb when LockSet::release is called, the
Qutex will be release()d a second time.
2025-09-30 22:47:54 -04:00
hayodea 7ddbde1a2f DevMgr:removeDASpec: Make serialized; acquire DevMgr qutex 2025-09-30 22:10:39 -04:00
hayodea ac3d97b3ec DevMgr:newDASpecInd: now lockvoked, acquires DevMgr qutex
We now acquire the DevMgr qutex when doing the newDASpecInd async
op. The qutex is held across an async sequence with potentially
a real hardware blocking bottleneck.
2025-09-30 22:01:34 -04:00
hayodea 274143e41d Formatting 2025-09-30 21:40:26 -04:00
hayodea d75430ef82 Qutex: Document the need to deal with early release()s 2025-09-30 21:25:58 -04:00
hayodea 06f3f2eebe DevMgr:attachAllUnattachedDevicesFromKnownListReq: acquire qutex
We should acquire the qutex here before iterating through the list
of DA specs.
2025-09-30 21:24:51 -04:00
hayodea 07609c6d6c SenseApiLib:Add isBeingDestroyed atomic flag for getter bailout
Since we have no choice but to access the sh_ptr<SenseApiLib> for
a lib before we can get its Qutex, we use this flag to ensure that
we can know whether the SenseApiLib data structure and its Qutex
are still valid when we enter -- i.e, we ensure that the SenseApiLib
object wasn't destroyed under our feet.
2025-09-30 21:01:00 -04:00
hayodea 1bad358921 DevMgr:at/detachSenseDevReq: release SenseApiMgr Qutex early
Release it early when we no longer need it.
2025-09-30 20:29:39 -04:00
hayodea 77e123ff4a DevMgr:at/detachSenseDevReq: acquire SenseApiMgr qutex 2025-09-30 19:53:57 -04:00
hayodea 9c3a8ea695 Indentation 2025-09-30 19:51:50 -04:00
hayodea 0ff86a0a5e SenseApiMgr: Add Qutex 2025-09-30 19:51:01 -04:00
hayodea 8ad5179a61 DevMgr:at/detachSenseDevReq: only acquire API lib lock
We don't actually manipulate any of SenseApiMgr or DevMgr's state
so there's no need to acquire their locks.
2025-09-30 19:47:23 -04:00
hayodea e600b0f96e DevMgr:at/detachSenseDevReq: acquire Api lib Qutex 2025-09-30 18:12:20 -04:00
hayodea b0d61c3e38 DevMgr:at/detachSenseDevReq: Acquire DevMgr Qutex 2025-09-30 14:22:51 -04:00
hayodea 296e517389 SerAsyncContin: Make LockSet vector constructor arg mandatory 2025-09-30 14:22:12 -04:00
hayodea 758586bb3c LockerAndInvoker: Move template method impls around 2025-09-30 14:07:13 -04:00
hayodea ccf0ab77bf Move SerializedAsyncContin template method impls into header 2025-09-30 13:59:07 -04:00
hayodea 774661e1f0 AsyncContinChainLink: Make getCallersContinuationShPtr() const 2025-09-30 13:59:07 -04:00
hayodea 33b61f429a LockSet: Move method impls into template header 2025-09-30 13:42:13 -04:00
hayodea 7ded9d287d Split comment into relevant locations 2025-09-30 13:27:06 -04:00
hayodea a55bcc2a03 LockerAndInvoker: better var naming 2025-09-30 10:43:30 -04:00
hayodea f15c07bc83 LockSet:tryAcquireOrBackoff use optional<ref_wrapper<Qutex>>
Replace the previous Qutex** raw pointer with a std::optional.
2025-09-30 10:36:19 -04:00
hayodea e3ce533fe4 Use optional for type-safe retval 2025-09-30 10:22:28 -04:00
hayodea 2075c0b797 Get Lval for sh_ptr and fix build error 2025-09-30 03:53:47 -04:00
hayodea 37b8cb0c7f Lockvoker: Take singleton by ref and not by copy 2025-09-30 03:53:24 -04:00
hayodea 4ed36eb88a LockerAndInvoker: Reorder this to silence warning 2025-09-30 03:52:37 -04:00
hayodea e53b0be7e2 Lockset: make locks vec public so LockerAndInvoker can access 2025-09-30 03:51:36 -04:00
hayodea e299e956e5 Move LockSet::release into header 2025-09-30 03:01:13 -04:00
hayodea ca9eae197f Lockvoker: Add registerInLockSet
We now create the sh_ptr to the copy of a lockvoker object in its
own supplied register method. This enables us to retain type safety
when calling make_shared, by copying the most derived version of
the lockvoker object. Then we can pass in the LockerAndInvokerBase
to the rest of the call chain.
2025-09-30 02:55:25 -04:00
hayodea 08122c086c LockSet:tryAcquireOrBackoff: should take Qutex**, not Qutex*
No idea how this error survived in here for so long.
2025-09-30 02:53:54 -04:00
hayodea cb8b13d0cd LockSet:release() Remove superfluous Lockvoker arg
We decoupled unregistration from release() so we no longer need
to pass in a lockvoker to release().
2025-09-30 02:17:46 -04:00
hayodea 46686db07e SerializedAsyncContin: LockSet should take Qutex, not SpinLock
Apparently this error was here the whole time and it flew under
the radar.
2025-09-30 02:12:04 -04:00
hayodea 97d93c670e Indentation 2025-09-30 01:15:30 -04:00
hayodea 9d77e8b345 SenseApiLib: Add qutex for lib state access serialization 2025-09-30 01:14:00 -04:00
hayodea 4bf19fda90 Get rid of rvalue ref returns 2025-09-30 00:37:54 -04:00
hayodea ed9635582f Lockvoker: add handleGridlock and invoke it 2025-09-30 00:32:54 -04:00
hayodea b7ff100499 VSCode config 2025-09-30 00:26:41 -04:00
hayodea d06ba8957a QtxAcqHistTracker: Implement complete algo; add depend graph
The dependency graph class enables us to perform analysis on the
qutex acquisition history data. By generating the graph and
detecting cycles in it, we can find true gridlocks.

We use this graph analysis code to implement the algorithmically
complete version of the gridlock detector.
2025-09-30 00:24:33 -04:00
hayodea 1aec779351 CMake: Conditionally build QtxAcquisitionHistTracker 2025-09-29 21:09:31 -04:00
hayodea 6baa0bb008 Silence build warning 2025-09-29 20:48:49 -04:00
hayodea 274edc1013 QtxHistTracker: Print message on heuristic gridlock find 2025-09-29 20:47:04 -04:00
hayodea c0eecf76d4 QtxAcqHistTracker: Add spinlock and use it 2025-09-29 20:34:56 -04:00
hayodea 2390042892 QtxAcqHistTracker: implement heuristic gridlock detector 2025-09-29 20:02:23 -04:00
hayodea d32cb1ddac Lockvoker: print contin vaddr in false-positive ack 2025-09-29 19:46:44 -04:00
hayodea 0cf86cf18a Lockvoker: Gridlock only algo likely if heuristically likely
Only run the algorithmically complete scan if the heuristic
scan detected it as likely.
2025-09-29 19:43:28 -04:00
hayodea 5c7a92b3a4 SerializedAsyncContin:getAcquiredQutexHistory returns rval ref
This ensures that the caller takes ownership.
2025-09-29 19:40:51 -04:00
hayodea 21da27649e Formattign 2025-09-29 19:30:20 -04:00
hayodea 71564b4d83 Add QutexAcquisitionHistoryTracker; integrate plumbing
We add the new Qutex acquisision history tracker that allows us
to dynamically detect qutex gridlocks. We've integrated it into
LockerAndInvoker::operator() in a preliminary way.

We also moved all of the trace*ForGridlockOn() methods into the
new QutexAcquisitionHistoryTracker singleton class. They're
more appropriately located there. They're still unimplemented
though.
2025-09-29 19:27:02 -04:00
hayodea 8123ec1227 Qutex: Deprecate old gridlock trace; add skeleton new trace
We add the skeleton of a correct history tracer for gridlocks. The
previous history tracer made the incorrect assumption that we would
find the foreign sequence's currently desired LockSet inside of the
lockvoker that it has stored inside of Qutex::currOwner.
2025-09-29 18:14:10 -04:00
hayodea 2a60fdd9df Qutex: currOwner should use registered sh_ptr to Lockvoker
This ensures that the Lockvoker object we access from currOwner
remains valid past the lifetime of the Lockvoker object that
gets copied and invoked by boost::asio
2025-09-29 14:37:16 -04:00
hayodea 462247d743 Qutex: Add gridlock detection 2025-09-29 13:38:53 -04:00
hayodea 6b4fe05fc0 Qutex: Add gridlock detection wireframing
This is the basic wireframing required to enable us to detect
gridlocks among requests.
2025-09-29 12:58:41 -04:00
hayodea 0090aa6e3a Qutex: Add getter for currOwner pointer 2025-09-29 12:46:58 -04:00
hayodea 7e514a1fa3 Qutex: Add currOwner pointer for debugging
We'll use this to detect gridlock-type deadlocks between
two requests
2025-09-29 12:40:43 -04:00
hayodea f1ce1ab19c xcbWindow: Callback w/early ret if window not found 2025-09-29 12:19:03 -04:00
hayodea 542e3081ad DevReattacher: Use aggregate init for Callback<> 2025-09-29 01:59:52 -04:00
hayodea 65b9460a3a LivoxProto1:connectReq: Remove retryDelay; DevReattacher replaces
The new DeviceReattacher daemon in DeviceManager makes this retry
delay feature obsolete.
2025-09-29 01:18:36 -04:00
hayodea cea65dcd00 Mrntt: Call initializeDeviceReattacher in init
It works surprisingly well on the first try.
2025-09-29 01:07:32 -04:00
hayodea 6cebf6856e Indentation 2025-09-28 23:35:20 -04:00
hayodea 20d568a063 DevMgr: Call attachAll*FromKnownListReq in DeviceReattacher
Well, I guess now we have auto-reattaching devices.
2025-09-28 23:34:39 -04:00
hayodea bd52e49ba3 DevMgr: Add attachAllUnattachedDevicesFromKnownListReq
This function is the backbone for the DeviceReattacher daemon. It
assembles a list of all DA specs which are known to Mrntt, but which
haven't been successfully attached as yet, and attempts to attach
them.
2025-09-28 23:15:22 -04:00
hayodea 93103aa8d4 DevMgr: Add DeviceReattacher daemon plumbing
This is the plumbing for a periodic polling thread that tries
to reattach DA specs that failed to attach in the startup phase.
2025-09-28 23:07:39 -04:00
hayodea 2be78401b5 Center logo 2025-09-28 15:20:09 -04:00
hayodea eca3f47884 Add 512 and 256p logos 2025-09-28 15:15:40 -04:00
hayodea f3909fc000 New salmanoff logo 2025-09-28 15:09:23 -04:00
hayodea c0798d1bdb DevMgr: detachAll only detaches from attachedDeviceRoles
We no longer try to detach from the collection of specs. We
detach from the collection of attachedDeviceRoles. This means
our cleanup sequence no longer tries to clean up things that were
never set up to begin with.
2025-09-28 13:04:07 -04:00
hayodea e45a9ee5d1 DAP.yy: use cmdlineDASpecs; DevMgr: add attachAllUnattachedDevicesFromCmdlineReq
This method wraps around attachAllUnattachedDevicesFromReq and supplies
it with a sh_ptr<> collection of all DASpecs parsed by the DAP parser
from the cmdline.

The initialization sequence now correctly initializes all DAP specs
given on the cmdline again.
2025-09-28 12:52:59 -04:00
hayodea b43ffcb677 DevMgr: attachAllUnattachedDevsFrom: now takes sh_ptr<vector<Spec>>
This method now accepts a sh_ptr<vector<DeviceAttachmentSpec>> to
tell it specifically which specs to attempt to attach.

This enables us to implement different frontends that supply it
with collections of devices from different sources (GUI, cmdline,
previously failed-to-attach/hot-removed devices, etc).

SMO temporarily initializes none of the devices from the cmdline
during this commit as we transition to implementing the cmdline
collection frontend.
2025-09-28 12:39:45 -04:00
hayodea 2c60248127 DevMgr: Rename at/detachAll*Req():
We've renamed these now to better reflect what they do.
* attachAllSenseDevicesFromSpecsReq=>attachAllUnattachedDevicesFromReq
* detachAllSenseDevicesReq=>detachAllAttachedDeviceRoles

This is also the first step in changing
attachAllUnattachedDevicesFrom to accept a sh_ptr<> to a collection
of DeviceAttachmentSpecs. This will enable us to unify the underlying
spec attachment logic and just create several front-ends for attaching
specs from multiple sources.
2025-09-28 12:19:56 -04:00
hayodea 993bf568fc DevMgr: implement removeDeviceAttachmentSpecReq
This reverts all state changes made by newDeviceAttachmentSpecInd.
2025-09-28 11:41:20 -04:00
hayodea 27e707a22d DevMgr:newDevAttSpecInd: take ref & not sh_ptr to DASpec
Because NewDevAttSpecInd should be the function that creates the
persistent sh_ptr state within DevMgr.
2025-09-28 11:14:55 -04:00
hayodea 7ab6e7b2c3 Formatting 2025-09-28 11:05:04 -04:00
hayodea 5b2354bfe0 DevMgr:newDevAttSpecInd: creates Device in frontend
There's no reason why we have to only create the Device at the
end if everything succeeds. The Device isn't the same thing as the
DeviceRole.
2025-09-28 10:57:43 -04:00
hayodea 42ab935da6 Reorder operations 2025-09-28 09:19:09 -04:00
hayodea 572a8612ed DevMgr,Dev: Add Qutexes
Add Qutex locks to both DeviceManager and Device. These will be
properly used in the upcoming patches.
2025-09-28 01:27:32 -04:00
hayodea 51b70b179c DevMgr: call newDevAttSpecInd & not attDevReq in body:initReq
This performs a more complete device initialization and attachment
sequence. We'll do the corresponding teardown in the shutdown
sequence later.

We might probably do it as deviceRoleGoneAwayInd()
2025-09-28 01:15:36 -04:00
hayodea 1a56e2a107 DevMgr: Add DeviceRoles; attachedDevices unrelated to device state now
We've decided to add a separate notion of a DeviceRole to track attached
device roles now. We no longer use the collection of deviceSpecs to
track which roles have been attached. Rather, this list will simply
collate all known deviceAttachment specs which are expected to be
maintained in an attached state.

SMO can periodically scan through these and cross-reference this
collection with the collection of attachedDeviceRoles. Then it can
re-try to attach those which aren't currently attached at any given
moment. This will give resilience against device attachment failures
or device resets/malfunctions, at runtime.
2025-09-28 00:50:05 -04:00
hayodea e6b8d3e85d DevMgr: Move at/detachSenseDevs[FromSpecs] into DeviceMgr::
This is logically cleaner and it begins preparing our next set
of restructuring changes. To wit: we're revamping the device
manager to distinguish between devices and their roles.
2025-09-27 23:16:46 -04:00
hayodea 52567406ca Lockset: check for registration b4 unregistering 2025-09-27 22:22:35 -04:00
hayodea 2f18ade4ab Locking: pre-emptively detect deadlocks in Lockvoker constructor
We can now check for deadlocks both pre-emptively and reactively.
We can eventually add function vaddrs and even function names to
our tracing code.
2025-09-27 21:27:04 -04:00
hayodea f8bf8083af Locking: Add contin tracing to detect deadlocks
We added the code to trace all the contins linked to a particular
Lockvoker, into SerializedAsyncContinuation. This basically
ensures that we'll almost never deal with a deadlock. So cool.
2025-09-27 20:51:20 -04:00
hayodea 782bcd4567 Async: add sh_ptr<ContinuationChainLink> to Callback<>
This change enables us to finally implement the tracing of
continuations backward from the point of acquisition for deadlock
debugging.
2025-09-27 18:30:09 -04:00
hayodea 2212aec080 VScode stuff that didn't work to make it use /b and not /build 2025-09-22 21:32:17 -04:00
hayodea d0303becd7 Async: Add a new Callback class for accessing caller's contin
We previously passed a sh_ptr to the caller's contin as arguments
to the std::bind() callable. In order for us to be able to trace
deadlocks, we need to be able to access them explicitly.

So here's that change.
2025-09-22 21:30:33 -04:00
hayodea 27bebeb702 Squash: Into debug locks commit 2025-09-22 21:30:14 -04:00
hayodea 09a0041f20 Squash: into debug locks intro commit 2025-09-22 21:29:43 -04:00
hayodea c6c3d6c9e8 Todo: Update 2025-09-22 20:49:00 -04:00
hayodea 092a0954a0 Locking: Add basic reactive deadlock detection foundation
We added a timestamp to each Lockvoker so that we can detect when
a lockvoker has been in a qutex for "too long", where "too long"
is defined arbitrarily as 500ms.

Next we're going to change the way we create callbacks to enable
us to more explicitly access the sh_ptr<AsyncContin> via
the callback object.
2025-09-22 20:45:36 -04:00
hayodea d2ed525106 Debug:Qutex: Add deadlock detection based on elapsed time
We now detect that a deadlock is likely when
CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS has elapsed. This is the
preliminary work required to do a backtrace through the call
stack and figure out if a deadlock has really occured.

To do this, we'd have to go through the async call chain and
search for a previous caller which acquired the same qutex as
the one that first failed during this Lockvoker LockSet acquisition
attempt.
2025-09-21 15:11:28 -04:00
hayodea dbc9569775 Async: Add exception bubbling
CONT_SET_EXC: Set exception on the continuation, to be rethrown
	by the caller.
CONT_SET_EXC_AND_RET: Convenience which returns immediately
	after setting the exception.
2025-09-21 14:21:55 -04:00
hayodea 1e2cc5ef16 scripts: Add sloc 2025-09-20 19:57:13 -04:00
hayodea 31cadb2ee4 Test: Add googletest; Add qutex_tests.cpp
Well, we now have this.
2025-09-20 19:41:57 -04:00
hayodea 329d57a16d Formatting 2025-09-20 18:23:03 -04:00
hayodea 32179eee5e Qutexes: Implement them and supporting classes
Implements: LockSet, SerializedAsynchronousContinuation,
	LockerAndInvoker, LockerAndInvokerBase, Qutex.

Very big leap in functionality here. See qutexes.md for
an explanation of what we've done.
2025-09-20 18:20:52 -04:00
hayodea f05c465d61 Docs:Qutexes Update design but it's not reflective of impl 2025-09-20 18:14:17 -04:00
hayodea 79c50ff191 Docs: Qutex.md: update 2025-09-19 21:36:59 -04:00
hayodea d10217f3f5 Docs: update qutex algo plan 2025-09-19 18:52:27 -04:00
hayodea 4b0e832e27 Asnc: LockSet is now a member of serializedAsyncCont; not base class
We needed to pass in a ref to the parent SerializedAsyncContin, so
we had to make it a member var instead of a base class.
2025-09-19 01:32:52 -04:00
hayodea dd9ce63cb8 Locking:Qutex: Skeleton for Qutex class 2025-09-19 01:19:09 -04:00
hayodea b9322c5e89 Docs: Qutex updated notes -- still WIP 2025-09-19 01:18:32 -04:00
hayodea 2dfa615eb7 Rename: lockSpec.h=>lockSet.h 2025-09-18 23:05:44 -04:00
hayodea 596bc1fbd2 SpinLock: Add acquire/release() and spinPause()
We're very careful to ensure that we add a cpu_relax hint.
2025-09-18 23:01:49 -04:00
hayodea ec50526804 Rename: LockSpec=>LockSet 2025-09-18 22:21:06 -04:00
hayodea eb3366cfd2 Async: Move callOriginalCb out of AsyncCont base, into NonPostedAC
The reasoning here is to prevent silent invocations of
callOriginalCb from a base class pointer/ref.
2025-09-18 22:10:17 -04:00
hayodea b49e281010 Docs: New qutexes design 2025-09-18 20:29:37 -04:00
hayodea 9a23dbbe95 Whitespace 2025-09-17 18:18:09 -04:00
hayodea 5a3c0699f7 Locking: Add LockerAndInvoker to provide spinQing behaviour
We haven't implemented or tested this yet but it looks promising.
Also, it's surprisingly clean!
2025-09-17 16:39:11 -04:00
hayodea 816a047920 Async: new hierachy; manages reply posting and unlocking
Async: Use new [Non]PostedAsyncCont and callOriginalCb

This new hierarchy of classes gives us a central mechanism for
managing both reply-posting and lockSpec unlocking.

* callOriginalCb: Now uses a modern C++ variadic template design
  enabling it to handle both direct calling and std::bind()
  re-binding of an arbitrary number of arguments from the caller.

This enables us to mostly eliminate the repeated, bespoke
definitions of callOriginalCb littered throughout the codebase.

We've also propagated these changes throughout the codebase in
this patch.
2025-09-17 16:38:48 -04:00
hayodea 33c006b178 Locking: Add LockSpec class which manages sets of spinlocks
This class allows us to list a series of spinlocks that are all
acquired and released together. It has simple, primitive detection
for deadlocks and will throw if it detects one.
2025-09-17 16:29:58 -04:00
hayodea 0733bb9a68 Locking: add SpinLock class
Nothing much to add: add a spinlock which has a tryAcquire method.
This will be used as a primitive for building our spinQing locking
system.
2025-09-17 16:28:07 -04:00
hayodea eeaa4ed2df CMake: Add preliminary CPack support
We'll tune it later.
2025-09-16 22:19:48 -04:00
hayodea 0788bbd799 Cmake,DAPS: Add support for DAPS preprocessing
We add a new extension, .dapss for preprocessable DAPS spec files.
We now run the C preprocessor on the .dapss files to produce
.daps files.
2025-09-16 21:30:50 -04:00
hayodea a6eccede4a DAPSpecs: Formatting 2025-09-16 20:44:46 -04:00
hayodea 437f7ea10f Postfix these files with .dapss 2025-09-16 20:08:38 -04:00
hayodea 58f7df49ae Move DAP specs into /devices 2025-09-16 20:04:05 -04:00
hayodea 03da91f5e5 Move DAP specs into /bodies/devices 2025-09-16 19:57:19 -04:00
hayodea 02c071394b Update todos 2025-09-16 19:54:32 -04:00
hayodea c52c447a78 Formatting 2025-09-16 18:46:30 -04:00
hayodea ddd6f6d6c6 CompThr: Comment on posted CBs 2025-09-16 18:45:55 -04:00
hayodea f5c359a6a9 Update todos 2025-09-16 18:40:40 -04:00
hayodea af33b7f097 SenseApiMgr: Make at/detachSenseDev & at/detachAllSenseDevs posted
They are posted to Marionette.

* We also fixed callOriginCb invocations;
* Also made posted CBs use std::bind instead of greedily
  early-invoking the CB on the servicing thread's stack.
2025-09-16 18:38:06 -04:00
hayodea 92e55641a0 LivoxGen1: Replace throw with cb+early ret 2025-09-16 18:36:50 -04:00
hayodea 9e00cd1530 Formatting, indentation 2025-09-16 18:36:11 -04:00
hayodea 8fd8826f8d Mind:threadMgmt ops: move 0-iter callback to top 2025-09-16 18:24:16 -04:00
hayodea 5d30941aab AsyncLoop: Add copy constr + assignment op
Make nTotal non-const.
2025-09-16 18:20:08 -04:00
hayodea 429bd2a349 Exc: Replace with cb+ret 2025-09-16 15:10:28 -04:00
hayodea 5c79a89cd4 Body: Don't forget to finalizeAllLibs 2025-09-16 15:09:56 -04:00
hayodea a931f9f01a CompThr: Name segments to indicate that they're posted 2025-09-15 15:15:40 -04:00
hayodea 7f3bfec835 Mind:init/finiReq: now posted to mrntt; callbacks std:bind
We now have mind::initialize/finalizeReq post their requests
to Mrntt instead of executing on the caller's thread context.

We also fixed the way that we invoke callbacks by properly wrapping
it in a std::bind.
2025-09-15 15:01:26 -04:00
hayodea 19b39d391f DevMgr: Move helper function to top 2025-09-15 14:33:42 -04:00
hayodea 674d74cfb9 DevMgr:newDevSpecInd: fix posting and async pattern conformance 2025-09-15 14:32:26 -04:00
hayodea b768739b96 CompThr: Delete shutdownInd & exceptionInd
We no longer need them because we now have
mrntt::mrntt.finalizeReq(), which does a more holistic job of
shutting down Marionette (and thus, ultimately, Salmanoff).
2025-09-15 13:43:11 -04:00
hayodea 77acbdd8de Indentation 2025-09-15 13:10:30 -04:00
hayodea 7b699d5d36 LivoxGen1: Check for 3 '.'s in smo-ip 2025-09-15 13:05:58 -04:00
hayodea db8f047322 SApiMgr:attDevReq: use body||world thread for i/e-devs
We now pass in the correct ComponentThread based on the type of
device that's being attached.
2025-09-15 12:47:37 -04:00
hayodea 472184bbbc Fix build errors with mind::globalMind and Qualia headers 2025-09-15 12:47:09 -04:00
hayodea dd3d5fea66 Todo: update completed tasks 2025-09-15 12:45:52 -04:00
hayodea 6573a1b14d CMake: delete subdir CMakeLists; use one CMakeList for smocore 2025-09-15 12:44:57 -04:00
hayodea 0759461c69 Mrntt:main: call mrntt:finalizeReq on exception
We'll wrap this in some exception-specific wrapper later.
2025-09-15 11:42:38 -04:00
hayodea 62cada2547 Bodies:dev: rename d0 to win0 2025-09-15 08:36:14 -04:00
hayodea e755383e72 Body: postfix _posted to posted sequence methods 2025-09-15 08:33:49 -04:00
hayodea d1e4c1a2ea Body:finalize: Will run if even one initReq step was executed
If even one step in Body.initializeReq was executed at all, then
whether or not it succeeded, we consider the body component to have
been initialized, at least with respect to whether finalizeReq
ought to run.
2025-09-15 08:30:17 -04:00
hayodea 29b192b2ee Formatting, spam-reduction 2025-09-15 08:25:49 -04:00
hayodea 7c48abbcca Body:init: Return true if any devices were initialized at all 2025-09-15 08:25:20 -04:00
hayodea 0ec227cf9e Body:finalize: Run even if body.init wasn't called
We now run body.finalizeReq even if body.init wasn't called. We'll
do a finer-grained check on each aspect of Body that needs to be
finalized now. This check was too large-grained.
2025-09-15 08:23:54 -04:00
hayodea baad2a9890 mrntt:main: Get rid of finalizeInd
This leverages the new clean dynamic allocation of the globalMind
object to make the mrntt::main and SMO's initialization and
shutdown much cleaner. We no longer concern ourselves with
shutting down the Mind threads inside of mrntt::main, but rather
we leave that state machine to the Mind class and Mrntt component.
2025-09-14 23:31:12 -04:00
hayodea 91ccd16b33 Add Mrntt component; init globalMind in mrntt.initializeReq
This makes the initialization sequence much cleaner and conceptually
well encapsulated.

We also now dynamically allocate the Mind objects. They're allocated
dynamically by Mrntt inside of initializeReq. This means that we no
longer have to worry about jolting and cleaning up the running threads
of global mind object even when we never explicitly called
Mind.initializeReq.

Along with other conceptual improvements to our abstractions, this
patch also gets us to a real "end of program initialization" point
for the first time.
2025-09-14 22:17:19 -04:00
hayodea 16865dc36f Rename these files and change ifdef guards 2025-09-14 13:16:02 -04:00
hayodea da0ef64f62 Split CompThread=>MindThr+MrnttThr; alloc globalMind in mrnttMain
We now allocate globalMind locally inside of marionetteMain. Why?

Before now, we had an asymmetric threading situation where the
globalMind's threads were initialized at during global constructor
invocation and not on demand. This meant that we had to shut down
those threads even if we had never got to the point of calling
Mind::initializeReq.

This significantly complicated our shutdown sequence since we had
to factor in the lifetime of the std::thread objects inside of the
ComponentThreads which were inside of the globalMind object.

Now, if we hadn't called Mind::initializeReq, we don't have to
perform any Mind::finalizeReq or adjacent operations. Shutdown is
symmetrically mirrored against the operations we actually performed
during execution.

We introduced some complexity by splitting ComponentThreads into
two derivative types (MindThread and MarionetteThread) but I think
in the long term we'll be able to massage this split into a much
cleaner situation overall.
2025-09-14 11:07:05 -04:00
hayodea 83af74f4be Update todo 2025-09-14 11:01:52 -04:00
hayodea 7cb6c8521e MindThread:shutdownInd: explicitly invoke on globalMind 2025-09-14 10:59:52 -04:00
hayodea 1d3d929ddd Mind: Use state variables to manage shutdown
We now allow the shutdown*Req() methods of Mind:: to return early
if their aspect of the object in question hasn't actually been
initialized.
2025-09-13 18:59:44 -04:00
hayodea 25a9721f92 Mind: Implement initialize/finalizeBodyReq()
We've done a lot of general work on the init sequencing.
2025-09-12 16:09:26 -04:00
hayodea b99b147959 CMake: libsmocore links against libsenseApis 2025-09-12 16:00:19 -04:00
hayodea 96bf653167 Add comments about Mrntt/Manager vs Mind sync/async design 2025-09-12 09:52:02 -04:00
hayodea 4429135539 Mind,Mrntt: Use async pattern in Mind; init threads before initializeSmo
In Mrntt, we now initialize Mind:: object threads before calling
initializeSalmanoffReq().

We've also propagated the spinscale async pattern into the Mind
class.
2025-09-11 20:11:10 -04:00
hayodea 89947dfc71 AsyncLoop: Add test for zero-iteration loops
This mnemonically ensures that we'll remember to check for
these kinds of async loops.
2025-09-11 20:10:01 -04:00
hayodea fb17c51ef6 ComponentThread: Remove lambdas; use standard async pattern
We've finally cleaned this code up by removing these dirty lambdas.
Next we do the Mind:: class sequences.
2025-09-11 18:41:45 -04:00
hayodea b8c931397d Async: add TargetedContinuation
This class enables us to consistently represent continuations
that are intended to be posted on a particular target handling
thread. It hols a sh_ptr to the caller so that the target thread
can re-enqueue the response on the caller after processing the
REQ/IND op.
2025-09-11 18:37:48 -04:00
hayodea f5195450e4 Formatting 2025-09-11 09:10:12 -04:00
hayodea b4102e6ee1 CMake: enable livoxGen1 and xcbWindow by default 2025-09-10 19:51:34 -04:00
hayodea 20034513ad CMake: fix usage of cmake_dependent_option 2025-09-10 19:44:17 -04:00
hayodea 3a852bfb9d Update todos 2025-09-10 18:17:15 -04:00
hayodea 4f74e1cd31 Async: Document bridging pattern 2025-09-10 18:16:55 -04:00
hayodea b7cf4c9135 Docs: Document the locking mechanism we plan to use
This new locking mechanism is very cumbersome, but highly
throughput maximizing. It trades high memory usage to gain
high throughput.

We may end up even being able to get the high throughput
without incurring the high memory usage by using std::bind
objects, etc.
2025-09-10 18:14:20 -04:00
hayodea e08dc0678b Make [at|de]tachAllSenseDevices[FromSpecs] and initializeSalmanoff async
This is the culmination of a lot of changes over the last week. We're
making SMO basically fully async in many areas, and then preparing to
implement the spinqueueing mechanism for locking.
2025-09-10 18:12:08 -04:00
hayodea 81842e4571 SenseApiDesc: SAL_MLO: attach/detachDeviceReq: return parsed DA spec 2025-09-10 18:11:08 -04:00
hayodea 428a32a950 asyncLoop: Make nTotal const 2025-09-10 15:13:02 -04:00
hayodea 01da06b051 Rename this file 2025-09-10 15:10:10 -04:00
hayodea a989256f22 SenseApiMgr: Use new loop convenience methods 2025-09-10 11:55:53 -04:00
hayodea 21d2df4d34 AsyncLoop: Add auto-incrementing & completion testing methods
These basically make it almost impossible to commit errors in async
loop logic implementation.
2025-09-10 11:54:26 -04:00
hayodea 8d18765a3a SenseApi: Fix async loop logic 2025-09-10 11:42:05 -04:00
hayodea 0c43c88554 AsyncBridge: Fix build warning 2025-09-10 11:38:33 -04:00
hayodea 07937a4846 SenseApiMgr: Use new bridge and loop classes 2025-09-10 11:37:35 -04:00
hayodea 067c928e47 AsyncBridge: Add new wrapper class that bridges async sequences
This class encapsulates all the logic and operations required to
correctly bridge an async operation into a sync function.
In particular, it also makes it less easy to forget to check if the
io_service exited because it was stop()ped.
2025-09-10 11:35:09 -04:00
hayodea 680977b211 formatting 2025-09-10 11:07:42 -04:00
hayodea 4143541adf Async: add AsyncLoop bounds tracker 2025-09-10 11:06:29 -04:00
hayodea 1b6b12256d SenseApis: Make attachDeviceReq async in drivers and SenseApiMgr
Slowly retrogressively making these sequences async
2025-09-10 06:51:55 -04:00
hayodea 5b5a701c69 Formatting 2025-09-10 04:02:27 -04:00
hayodea 3457efcbf8 Whitespace 2025-09-10 03:57:37 -04:00
hayodea bb1c7e4be1 Todo: update TODO list 2025-09-09 20:12:59 -04:00
hayodea cc33b333d2 livoxProto1: Document potential bug 2025-09-09 20:09:57 -04:00
hayodea 0449e557b0 livoxGen/Proto1: Refine auto-detection vs heuristic logic 2025-09-09 19:54:14 -04:00
hayodea 73b2d981f9 livoxGen1: Implement attach/detachDeviceReq by sync-bridging Proto1
We perform bridged synchronous calls into liblivoxProto1 in order
to support attach/detachDeviceReq. We'll eventually make
attachDetachDeviceReq fully asynchronous but for now we're happy
that we have this working driver for this fairly tricky device.
2025-09-09 12:09:59 -04:00
hayodea 20cdf64afb livoxProto1: Implement async getOrCreateDeviceReq+destroyDeviceReq
These are now both fully asynchronous. They also work fully
and both connect and disconnect to/from the Avia just fine,
in all tested scenarios.
2025-09-09 12:07:49 -04:00
hayodea 48121ec84c SenseApiMgr: Provide ComponentThread::getSelf access to libs 2025-09-09 12:03:50 -04:00
hayodea 725f8772b1 Indentation 2025-09-09 12:02:31 -04:00
hayodea 8413277847 Verbose: Print these messages only in verbose mode 2025-09-09 12:02:03 -04:00
hayodea 576d3ed7a5 VSCode: no idea 2025-09-09 12:00:15 -04:00
hayodea 381b29c12d Bodies:dev: Avia0: Rely on default source-code timeout/retry values 2025-09-09 11:59:07 -04:00
hayodea 0dcb4ce65f Async: Add a new AsyncContinuation base class
This will hold the std::function callback that persists
caller's async context.
2025-09-09 11:50:19 -04:00
hayodea c880f5b73e Threading: call initializeSalmanoff after io_service.reset()
This enables us to do asynchronous bridging in the functions called
by initializeSalmanoff
2025-09-07 18:42:28 -04:00
hayodea 6ba6cb9cf0 Mrntt: Add ability to listen for SIGSEGV
But don't actually use it because it makes it difficult to
exit SMO from the cmdline.
2025-09-07 11:45:54 -04:00
hayodea f587b45b38 livoxProto1: Connecting to bcast-advertised device works :)
We tested it.
It's important to note that between test runs, we need to take
into account the fact that the Avia stops sending bcast adverts
when it's been handshaken.

So the retry-delay-ms may be longer due to the fact that the Avia
may not be sending adverts for a good portion of that retry-delay-ms
time.
2025-09-07 11:42:32 -04:00
hayodea 1ededb85b9 livoxProto1:bug workaround: for boost:asio:udp async ops on dlopen libs
Updated Boost dependency to version 1.73.0 to address segfault issues with boost::asio in dynamic libraries. Refactored heartbeat socket management to use raw UDP sockets instead of boost::asio, improving compatibility and error handling during socket operations.
2025-09-07 08:14:46 -04:00
hayodea 9a500e39ab livoxProto1: Fix source and dest port for handshake & heartbeat
Handshake: We must wait for the handshake ACK on the same port as
the one we sent the handshake REQ from.
Heartbeat: We must send the heartbeats from the same source port
as the one we sent the handshake REQ from.
2025-09-07 06:47:53 -04:00
hayodea 8f0e945f0c livoxGen1: Validate smo-ip provider-param 2025-09-06 22:46:03 -04:00
hayodea 5d55157ffd livoxGen1: Implement attachDevice/detachDevice
Preliminary draft implementation. Unsurprisingly it doesn't
work. The handshake doesn't get accepted by the device it seems?
And we can't receive bcast dgrams since we're blocking
synchronously -- therefore we're not processing the incoming UDP
dgrams.
2025-09-06 22:46:03 -04:00
hayodea 7f367fd6e3 Debug: silence excessive couts 2025-09-06 22:46:03 -04:00
hayodea b0596d12f9 Formatting 2025-09-06 22:46:03 -04:00
hayodea 3b07a15e11 Move getRequiredParamAsInt into deviceAttachmentSpec.h
Make it accessible for all senseApis.
2025-09-06 22:46:03 -04:00
hayodea 38b29ddfc0 Comments and formatting 2025-09-06 22:46:03 -04:00
hayodea d2bf5aceee livoxProto1: detectSmoIp should be based on target dev IP
We previously used the smoIp provided by the user, but this
function is intended to enable us to figure out the correct
IP to send to the target device in the Handshake message.
2025-09-06 22:46:03 -04:00
hayodea a0d577bf81 LivoxProto1: Implemented Device and getOrCreateDevice
Includes everything from sending heartbeat msgs to performing
the connection handshake. We also accept many params to
provider-params to customize and make things easier.
2025-09-06 22:46:03 -04:00
hayodea 25234c4229 Docs: LivoxGen1 DAP spec: re-add ports for cmd/data/imu 2025-09-06 22:46:03 -04:00
hayodea 71c448a1d7 Docs: Update DAP spec for livoxGen1 2025-09-06 22:46:03 -04:00
hayodea 93fd2ac0ab VSCode config 2025-09-06 22:46:03 -04:00
hayodea 3e9eecc279 livoxProto1: Keep protocol headers pure;
Split the BroadcastListener, DiscoveredDevice and other concerns
out of the protocol header and implementation files.
2025-09-06 22:46:03 -04:00
hayodea 5e4597b8fd Docs: update DAP specs for livoxGen1 devices 2025-09-06 22:46:03 -04:00
hayodea 6eb6fa1eb0 Livox: Add new loadable libs for Protocolv1 and gen1 lidars
We've added two new libs:
* commonLibs/livoxProto1
* senseApis/livoxGen1

They currently get to the point of detecting my Livox Avia on the
network over UDP. This was really easy to get done in one night
using boost::asio and Cursor, honestly.
2025-09-06 22:46:03 -04:00
hayodea f00e1c7cf1 Formatting 2025-09-06 22:46:03 -04:00
hayodea 26be261cff SenseApiDesc: Rename SalmanoffCallbacks=> SmoCallbacks 2025-09-06 22:46:03 -04:00
hayodea e5a3c41c20 SenseApis: Add threading model info to initialization info
We provide access to a thread whose event queue the sense API
libs can use for device-independent event management.
2025-09-06 22:46:03 -04:00
hayodea 1d9b8a2cf6 Todo: Add note to rename struct 2025-09-06 22:46:03 -04:00
hayodea 4ce4c9f9f8 DAP Specs: Add specs for LivoxGen1 devices 2025-09-06 22:46:03 -04:00
hayodea fe5a7d480d CMake: tried to get project building in Clang. Failed. 2025-09-06 22:45:25 -04:00
hayodea 83273ae806 Bodies:dev: avia: don't set default subnet to 192.168.1.0 2025-09-06 22:06:37 -04:00
hayodea 04a3631881 Bodies:dev: Add DAPS files for test window and Avia 2025-09-06 22:02:12 -04:00
115 changed files with 11036 additions and 1350 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "third_party/googletest"]
path = third_party/googletest
url = https://github.com/google/googletest.git
+4 -4
View File
@@ -3,12 +3,11 @@
{ {
"name": "Linux", "name": "Linux",
"includePath": [ "includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/include", "${workspaceFolder}/include",
"${workspaceFolder}/smocore/include", "${workspaceFolder}/smocore/include",
"${workspaceFolder}/b/include",
"/usr/include", "/usr/include",
"/usr/local/include", "/usr/local/include"
"${workspaceFolder}/b/include"
], ],
"defines": [], "defines": [],
"compilerPath": "/usr/bin/g++", "compilerPath": "/usr/bin/g++",
@@ -24,7 +23,8 @@
}, },
"forcedInclude": [ "forcedInclude": [
"${workspaceFolder}/b/include/config.h" "${workspaceFolder}/b/include/config.h"
] ],
"configurationProvider": "ms-vscode.cmake-tools"
} }
], ],
"version": 4 "version": 4
+19 -2
View File
@@ -74,10 +74,27 @@
"future": "cpp", "future": "cpp",
"shared_mutex": "cpp", "shared_mutex": "cpp",
"typeindex": "cpp", "typeindex": "cpp",
"bitset": "cpp" "bitset": "cpp",
"*.ipp": "cpp",
"unordered_set": "cpp",
"forward_list": "cpp",
"barrier": "cpp"
}, },
"editor.rulers": [80, 120], "editor.rulers": [80, 120],
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.insertSpaces": false, "editor.insertSpaces": false,
"editor.detectIndentation": false "editor.detectIndentation": false,
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"C_Cpp.default.browse.limitSymbolsToIncludedHeaders": true,
"C_Cpp.default.browse.path": [
"${workspaceFolder}",
"${workspaceFolder}/b"
],
"C_Cpp.default.includePath": [
"${workspaceFolder}/include",
"${workspaceFolder}/smocore/include",
"${workspaceFolder}/b/include",
"/usr/include",
"/usr/local/include"
]
} }
+69 -5
View File
@@ -1,7 +1,9 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(salmanoff VERSION 0.00.004 LANGUAGES CXX) project(salmanoff VERSION 0.01.000 LANGUAGES CXX)
include(CMakeDependentOption) include(CMakeDependentOption)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DAPSS.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DebugOpts.cmake)
# Set C++ standard # Set C++ standard
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
@@ -22,10 +24,39 @@ if(NOT MIND_VOSCILLATOR_PERIOD_MS GREATER 0)
endif() endif()
math(EXPR MIND_VOSCILLATOR_FREQ_MS "1000 / ${MIND_VOSCILLATOR_PERIOD_MS}") 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 config.h
configure_file( configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/include/config.h ${CMAKE_CURRENT_BINARY_DIR}/include/config.h
@ONLY
) )
# Include directories # Include directories
@@ -36,7 +67,10 @@ include_directories(
) )
# Find core dependencies # Find core dependencies
find_package(Boost 1.69.0 REQUIRED COMPONENTS system) # Boost 1.72.0 is required to ensure that a certain bug where boost::asio
# objects depend on specific copies of symbols, and boost will cause a segfault
# if boost::asio objects are used inside of a dlopen()'d library, is fixed.
find_package(Boost 1.73.0 REQUIRED COMPONENTS system)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
find_package(FLEX REQUIRED) find_package(FLEX REQUIRED)
find_package(BISON REQUIRED) find_package(BISON REQUIRED)
@@ -47,21 +81,51 @@ if(NOT DL_LIBRARY)
message(FATAL_ERROR "Dynamic linking library (libdl/libldl) not found") message(FATAL_ERROR "Dynamic linking library (libdl/libldl) not found")
endif() endif()
# Add third-party dependencies
if(ENABLE_TESTS)
add_subdirectory(third_party)
endif()
# Add core components # Add core components
add_subdirectory(smocore) add_subdirectory(smocore)
add_subdirectory(commonLibs) add_subdirectory(commonLibs)
add_subdirectory(senseApis) add_subdirectory(senseApis)
add_subdirectory(wilzorApis) add_subdirectory(wilzorApis)
add_subdirectory(devices)
# Main executable # Main executable
add_executable(salmanoff main.cpp) add_executable(salmanoff main.cpp)
target_link_libraries(salmanoff target_link_libraries(salmanoff
smocore smocore
marionette
deviceManager
senseApis
${Boost_LIBRARIES} ${Boost_LIBRARIES}
${DL_LIBRARY} ${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(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)
+25
View File
@@ -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.
+84
View File
@@ -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
+5 -1
View File
@@ -1,6 +1,8 @@
# The Salmanoff Project: # The Salmanoff Project:
![Salmanoff project](docs/img/salmanoff-logo-dark-512.png) <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: 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. * Gregory `SAL`mieri.
@@ -8,3 +10,5 @@ This project, Salmanoff (pronounced: Sal-man-off), is an ROS rewrite of the Hari
* Leonard Peik`OFF`. * 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. 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).
+51
View File
@@ -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)
+153
View File
@@ -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()
+22
View File
@@ -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()
+91
View File
@@ -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}")
+72
View File
@@ -0,0 +1,72 @@
# Generic Flex/Yacc Generation Functions
# This file provides reusable functions for generating C++ files from Flex/Bison sources
# Function to generate Flex lexer files
# Usage: generate_flex_lexer(OUTPUT_VAR INPUT_FILE [PREFIX] [HEADER_DEPENDENCY])
# OUTPUT_VAR: Variable name to store the output file path
# INPUT_FILE: Path to the .ll input file
# PREFIX: Optional prefix for the generated files (defaults to basename of input file)
# HEADER_DEPENDENCY: Optional header file that the lexer depends on (e.g., from Bison)
function(generate_flex_lexer OUTPUT_VAR INPUT_FILE)
get_filename_component(INPUT_BASENAME ${INPUT_FILE} NAME_WE)
if(ARGC GREATER 2)
set(PREFIX ${ARGV2})
else()
set(PREFIX ${INPUT_BASENAME})
endif()
set(LEX_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.cc)
set(LEX_HEADER ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.hh)
# Set up dependencies
set(DEPENDENCIES ${INPUT_FILE})
if(ARGC GREATER 3)
list(APPEND DEPENDENCIES ${ARGV3})
endif()
add_custom_command(
OUTPUT ${LEX_OUTPUT}
DEPENDS ${DEPENDENCIES}
COMMAND ${FLEX_EXECUTABLE} --header-file=${LEX_HEADER} -o ${LEX_OUTPUT} ${INPUT_FILE}
COMMENT "Generating ${PREFIX}.cc from ${INPUT_FILE}"
)
set(${OUTPUT_VAR} ${LEX_OUTPUT} PARENT_SCOPE)
endfunction()
# Function to generate Bison parser files
# Usage: generate_bison_parser(OUTPUT_VAR HEADER_VAR INPUT_FILE [PREFIX])
# OUTPUT_VAR: Variable name to store the output .cc file path
# HEADER_VAR: Variable name to store the output .hh file path
# INPUT_FILE: Path to the .yy input file
# PREFIX: Optional prefix for the generated files (defaults to basename of input file)
function(generate_bison_parser OUTPUT_VAR HEADER_VAR INPUT_FILE)
get_filename_component(INPUT_BASENAME ${INPUT_FILE} NAME_WE)
if(ARGC GREATER 3)
set(PREFIX ${ARGV3})
else()
set(PREFIX ${INPUT_BASENAME})
endif()
set(YACC_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.cc)
set(YACC_HEADER ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.hh)
add_custom_command(
OUTPUT ${YACC_OUTPUT} ${YACC_HEADER}
DEPENDS ${INPUT_FILE}
COMMAND ${BISON_EXECUTABLE} -p ${PREFIX} --header=${YACC_HEADER} -o ${YACC_OUTPUT} ${INPUT_FILE}
COMMENT "Generating ${PREFIX}.cc and ${PREFIX}.hh from ${INPUT_FILE}"
)
set(${OUTPUT_VAR} ${YACC_OUTPUT} PARENT_SCOPE)
set(${HEADER_VAR} ${YACC_HEADER} PARENT_SCOPE)
endfunction()
# Generate device attachment parser files using the generic functions
# Generate Bison parser first (creates the header file)
generate_bison_parser(YACC_OUTPUT YACC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/deviceManager/deviceAttachmentPipeSpecp.yy deviceAttachmentPipeSpecp)
# Generate Flex lexer with dependency on Bison header
generate_flex_lexer(LEX_OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/deviceManager/deviceAttachmentPipeSpecl.ll deviceAttachmentPipeSpecl ${YACC_HEADER})
+1
View File
@@ -1 +1,2 @@
add_subdirectory(xcbXorg) add_subdirectory(xcbXorg)
add_subdirectory(livoxProto1)
+19
View File
@@ -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
+273
View File
@@ -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
+78
View File
@@ -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
+133
View File
@@ -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
+65
View File
@@ -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"
+80
View File
@@ -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
+712
View File
@@ -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
+252
View File
@@ -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
+13
View File
@@ -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")
+3
View File
@@ -0,0 +1,3 @@
+edev|avia0|
structural-implexor|livoxGen1()|livoxProto1()
|3JEDK380010Z39
+17
View File
@@ -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")
+3
View File
@@ -0,0 +1,3 @@
#include "../win0.dapss"
||
#include "../avia0.dapss"
+3
View File
@@ -0,0 +1,3 @@
#include "../win0.dapss"
||
#include "../avia0.dapss"
+3
View File
@@ -0,0 +1,3 @@
+edev|win0|
visual-implexor|xcb(dev-substring)|xorg(display=1|screen=0)
|mut
+67
View File
@@ -0,0 +1,67 @@
Ok: I realized I may be able to bridge async sequences without needing needing to make a bunch of functions async. The basic thing is:
```
funcThatCallsAsyncFuncsButWhoseSignatureIsItselfSync(ComponentThread &ct)
{
std::atomic continuationCondition(false);
async_call(ct, [] {
// Do stuff that will enqueue events on ct.
continuationCondition.store(true);
ComponentThread::getSelf()->getIoService()
.post([]{});
});
for (;;)
{
/* We, the thread actually executing this sequence
* here, may not actually be the thread that owns
* ct, in which case, ct's owner will dequeue the stuff
* that async_call() sent it to do, then conclude its
* processing. It may or may not send a message back
* to the thread executing this sync sequence here.
*
* If we are the same thread as ct, then wonderful:
* we will dequeue the messsage ourself and process
* it, then conclude the processing. We may or may
* not send a message back to ourself at the end of
* the processing but it doesn't matter since we'll
* have to check the condvar in the loop post-
* conditions before the next iteration.
*
* The problem is the first case where ct is a foreign
* thread which may not send us a wakeup message
* when condvar has been modified, and we may
* hypothetically never get another signal from any
* other thread.
* This can be solved by just ensuring that this thread
* always gives a callback to async_call() which first
* modifies condvar, and then sends an empty message
* to itself.
*/
ct.run_one();
if (continuationCondition.load()==true
||!ct.isRunnable())
{
break;
}
}
/* We've now bridged the async calls into this
* sync function's body without losing any
* event responsiveness for the main loop.
*/
// Continue executing normally...
}
```
This should be a complete solution for async bridging.
Now let's try it first in the sendHandshake sequence. We'll try to use non-blocking socket api calls to send the heartbeat and wait for a response asynchronously. Don't use boost:asio:socket functions because they cause a segfault due to a bug (see /CMakelists.txt). To wait for events on the socket, setup boost to wait on the socket or bind FD.
Async_call is OBVIOUSLY NOT a function you're expected to implement. It's merely a placeholder for any async sequence we call inside of the sync function.
You're expected to get rid of the io_context auto-scope object inside of executeHandshake, and use the stored Device.componentThread's io_context instead. You are expected to refrain from associating Device.componentThread.io_service with any boost::asio::socket stuff. You will have to write manual posix socket api code, and use a boost::file_descriptor to catch wakeup events on the socket from the UDP events like recv-data-ready.
You're basically going to break up executeHandshake into a series of lambdas, but use the pattern I described above to keep it as one synchronous function by bridging it. executeHandshake is the synchronous function that must remain synchronous to its caller. Internally, you must split up executeHandshake into several lambdas, and then at the end you set the condvar and post a message back to CompnentThread::getSelf()->io_service. Then, since executeHandshake() has a bridging sequence that loops and calls run_one() until the condvar is set, executeHandshake will resume executing after that loop exits.
Makes sense?
@@ -1,282 +0,0 @@
# Adaptive Resource Acquisition with Re-queuing
## Overview
This document describes a novel synchronization pattern that combines the benefits of spinlocks, mutexes, and queuing systems while avoiding their respective drawbacks. The pattern is designed for high-throughput async systems where multiple threads need to coordinate access to shared resources without blocking or wasting CPU cycles.
## Problem Statement
Traditional synchronization mechanisms have significant trade-offs that limit system performance:
- **Mutexes**: Block threads, causing context switches and reduced throughput
- **Spinlocks**: Waste CPU cycles while waiting, preventing other work from proceeding
- **Pure Queuing**: Serializes all operations, reducing parallelism unnecessarily
The challenge is to maintain data consistency across multi-segment async operations while maximizing system throughput. In high-performance systems, the overhead of context switching can be substantial, and CPU cycles are precious resources that should not be wasted on busy-waiting.
## Core Concept
The Adaptive Resource Acquisition pattern uses **atomic flags on shared objects** combined with **immediate re-queuing** to achieve optimal performance characteristics:
1. **No thread blocking** - Threads never sleep or context switch, maintaining maximum responsiveness
2. **No CPU waste** - No busy-waiting when other work could proceed, ensuring efficient resource utilization
3. **Maximum throughput** - Threads always process available work, maximizing system productivity
4. **Data consistency** - Atomic resource acquisition preserves integrity without traditional locking overhead
This approach fundamentally changes how we think about resource coordination, treating it as a flow management problem rather than a blocking synchronization problem.
## Architecture
### Resource Objects
Each shared object that requires synchronization carries an atomic flag that indicates its availability. This flag serves as the primary coordination mechanism, allowing threads to atomically claim ownership without the overhead of traditional locks.
The resource object structure is intentionally simple, containing only the essential coordination mechanism and the resource-specific data. This minimalism reduces memory overhead and improves cache locality.
### Request Structure
Async operations are encapsulated as requests that specify their resource requirements and the operation to be performed. This encapsulation allows the system to reason about resource dependencies before attempting execution, enabling intelligent scheduling decisions.
The request structure includes metadata such as priority levels, which can be used for advanced scheduling policies. This flexibility allows the system to adapt to different workload characteristics and business requirements.
### Resource Manager
The core component orchestrates resource acquisition and request processing through a sophisticated coordination mechanism. It maintains a registry of all available resources and manages the flow of requests through the system.
The resource manager operates on a simple principle: attempt to acquire all required resources atomically, and if successful, execute the operation immediately. If any resource is unavailable, the request is immediately re-queued for later processing without any blocking or waiting.
## Algorithm
### Resource Acquisition Process
The resource acquisition process follows a simple but effective strategy. For each request, the system attempts to atomically acquire all required resources in a single pass. This atomicity is crucial for maintaining data consistency and preventing race conditions.
If all resources can be acquired atomically, the operation proceeds immediately. This represents the optimal case where no coordination overhead is incurred beyond the atomic operations themselves. The system achieves maximum throughput in this scenario.
If any resource cannot be acquired, the system immediately releases any resources that were successfully acquired and re-queues the request. This approach ensures that resources are never held unnecessarily and that the system can continue processing other requests without delay.
The key insight is that failed acquisition attempts are not failures in the traditional sense, but rather normal flow control mechanisms. The system treats resource contention as a scheduling opportunity rather than a blocking condition.
#### Atomic Resource Acquisition Pseudocode
```
TRY_ACQUIRE_RESOURCES(resource_names):
acquired_resources = []
FOR EACH resource_name IN resource_names:
resource = GET_RESOURCE(resource_name)
expected_value = false
desired_value = true
// Atomic compare-and-swap operation
IF ATOMIC_COMPARE_EXCHANGE_STRONG(resource.flag, expected_value, desired_value):
// Successfully acquired this resource
acquired_resources.ADD(resource)
ELSE:
// Failed to acquire this resource
// Release all previously acquired resources
FOR EACH acquired_resource IN acquired_resources:
ATOMIC_STORE(acquired_resource.flag, false)
RETURN false
// Successfully acquired all resources
RETURN true
```
### Request Processing Workflow
The request processing workflow is designed for maximum efficiency. Each request is processed exactly once per cycle, either by successful execution or by re-queuing for later processing.
When a request is successfully processed, the system immediately releases all acquired resources, making them available for other requests. This rapid resource turnover maximizes system throughput and minimizes resource contention.
The re-queuing mechanism ensures that no request is lost, while the immediate nature of the re-queuing prevents any blocking or waiting. Requests that cannot be processed immediately simply wait their turn in the queue, allowing other requests to proceed without interference.
#### Basic Processing Algorithm
```
PROCESS_REQUEST(request):
// Step 1: Dequeue the request
request = DEQUEUE_FROM_QUEUE()
// Step 2: Attempt atomic resource acquisition
resources_acquired = []
acquisition_successful = true
FOR EACH resource_name IN request.required_resources:
resource = GET_RESOURCE(resource_name)
IF ATOMIC_COMPARE_EXCHANGE(resource.flag, false, true):
resources_acquired.ADD(resource)
ELSE:
acquisition_successful = false
BREAK
// Step 3: Handle acquisition result
IF acquisition_successful:
// Execute the operation
EXECUTE_OPERATION(request.operation)
// Release all acquired resources
FOR EACH resource IN resources_acquired:
ATOMIC_STORE(resource.flag, false)
ELSE:
// Release any partially acquired resources
FOR EACH resource IN resources_acquired:
ATOMIC_STORE(resource.flag, false)
// Re-queue the request for later processing
ENQUEUE_REQUEST(request)
```
### Event Loop Management
The event loop continuously processes requests from the queue until no more requests are available. This simple loop structure ensures that the system is always making progress on available work.
The loop processes requests in the order they were queued, providing a natural fairness mechanism. However, the system can be extended with priority queuing or other scheduling policies to meet specific requirements.
The event loop is designed to be efficient and non-blocking, ensuring that the system remains responsive even under high load conditions.
#### Main Event Loop Pseudocode
```
MAIN_EVENT_LOOP():
WHILE true:
// Check if there are requests to process
IF QUEUE_IS_EMPTY():
BREAK
// Dequeue the next request
request = DEQUEUE_FROM_QUEUE()
// Process the request (this includes re-queuing if needed)
PROCESS_REQUEST(request)
// Continue with next request
CONTINUE
```
#### Multi-threaded Worker Loop
```
WORKER_THREAD():
WHILE true:
// Wait for work to become available
request = WAIT_FOR_REQUEST()
// Process the request
PROCESS_REQUEST(request)
// Return to waiting state
CONTINUE
```
## Multi-Threaded Implementation
### Thread-Safe Coordination
In a multi-threaded environment, the resource manager must coordinate access to its internal data structures while maintaining the non-blocking characteristics of the pattern. This coordination is achieved through careful use of atomic operations and minimal locking.
The queue management uses traditional mutex-based synchronization, but only for the queue operations themselves. The critical resource acquisition path remains lock-free, ensuring that the performance benefits of the pattern are preserved.
Worker threads continuously process requests from the shared queue, attempting to acquire resources and execute operations. The coordination between threads is handled implicitly through the atomic resource flags, eliminating the need for explicit thread synchronization in the critical path.
### Worker Thread Behavior
Worker threads operate in a continuous loop, processing requests as they become available. Each thread independently attempts to acquire resources and execute operations, creating natural parallelism without explicit coordination.
The worker threads are designed to be lightweight and efficient, with minimal overhead beyond the actual resource acquisition and operation execution. This design allows the system to scale effectively with the number of available CPU cores.
The thread coordination is handled through the shared queue and atomic resource flags, creating a self-balancing system that naturally distributes work across available threads.
## Use Cases
### Device Management Systems
In device management systems, multiple operations may need to coordinate access to physical or logical devices. The adaptive resource acquisition pattern provides an elegant solution for managing these complex coordination requirements.
For example, when attaching a device, the system may need to coordinate access to the device itself, the device registry, and various system resources. The pattern allows these operations to proceed atomically when resources are available, while gracefully handling contention through re-queuing.
The device management system can handle complex multi-step operations that require coordination across multiple resources, all while maintaining high throughput and responsiveness.
### Database Connection Pools
Database connection pools are a natural fit for the adaptive resource acquisition pattern. Each database operation requires access to a connection from the pool, and the pattern provides efficient coordination without the overhead of traditional locking.
The pattern allows the system to process multiple database operations concurrently when connections are available, while gracefully handling periods of high contention. The re-queuing mechanism ensures that no operations are lost, even during peak load periods.
The connection pool can implement sophisticated scheduling policies, such as priority queuing for different types of operations, while maintaining the performance benefits of the pattern.
## Performance Characteristics
### Throughput Analysis
The performance characteristics of the adaptive resource acquisition pattern are determined by the resource contention patterns in the system. In the best case, when resources are readily available, the system achieves maximum throughput with minimal overhead.
In the worst case, when resources are heavily contended, the system gracefully degrades to a queuing behavior, ensuring that all operations eventually complete. The system maintains fairness and prevents starvation through the natural ordering of the queue.
The average case performance represents the typical operating conditions, where the system achieves optimal parallelism while handling occasional resource contention through re-queuing.
### Comparison with Traditional Methods
The adaptive resource acquisition pattern provides a unique combination of performance characteristics that are not achievable with traditional synchronization mechanisms:
- **Mutexes** provide data consistency but at the cost of thread blocking and context switching overhead
- **Spinlocks** avoid context switching but waste CPU cycles during contention
- **Pure queuing** avoids both blocking and CPU waste but serializes operations unnecessarily
The adaptive pattern combines the best aspects of these approaches while avoiding their drawbacks, creating a solution that is both efficient and practical.
## Advanced Features
### Priority Queuing
The system can be extended with priority queuing to handle different types of operations with varying importance. High-priority operations can be processed before lower-priority operations, ensuring that critical operations receive timely attention.
The priority queuing mechanism integrates seamlessly with the existing re-queuing behavior, allowing the system to maintain its performance characteristics while providing sophisticated scheduling capabilities.
### Resource Groups
Complex operations may require coordination across multiple related resources. Resource groups allow the system to treat related resources as a single unit for acquisition purposes, simplifying the coordination logic for complex operations.
Resource groups can be used to implement sophisticated resource management policies, such as ensuring that related resources are always acquired together or implementing resource reservation mechanisms.
### Fairness Mechanisms
The system can implement various fairness mechanisms to ensure that all requests receive fair treatment over time. Round-robin processing, aging mechanisms, and other fairness policies can be implemented while maintaining the performance benefits of the pattern.
Fairness mechanisms are particularly important in systems where different types of operations have different resource requirements, ensuring that no operation type dominates the system resources.
## Implementation Considerations
### Memory Management
The pattern requires careful attention to memory management, particularly for the request objects and resource metadata. Smart pointers and object pooling can be used to minimize memory allocation overhead and improve performance.
The system should implement proper cleanup mechanisms for failed operations and ensure that resources are always released, even in error conditions.
### Error Handling
Robust error handling is essential for maintaining system reliability. The system should gracefully handle operation failures, resource unavailability, and other error conditions without affecting the overall system performance.
Retry mechanisms with exponential backoff can be implemented for transient failures, while deadlock detection and resolution mechanisms can handle more complex failure scenarios.
### Monitoring and Debugging
The system should provide comprehensive monitoring capabilities to track performance metrics, resource utilization, and queue behavior. These metrics are essential for tuning the system and identifying performance bottlenecks.
Debugging support should include detailed logging of resource acquisition attempts, queue operations, and operation execution, allowing developers to understand and optimize system behavior.
## Conclusion
The Adaptive Resource Acquisition pattern provides a novel solution to the classic synchronization dilemma. By combining atomic operations with intelligent re-queuing, it achieves maximum throughput while maintaining data consistency and avoiding the overhead of traditional synchronization mechanisms.
This pattern is particularly well-suited for high-performance async systems where traditional synchronization mechanisms would create unacceptable overhead. The pattern's simplicity and effectiveness make it a valuable addition to the toolkit of concurrent programming patterns.
The pattern represents a fundamental shift in how we think about resource coordination, treating it as a flow management problem rather than a blocking synchronization problem. This shift enables new levels of performance and scalability in concurrent systems.
The adaptive resource acquisition pattern is particularly valuable in:
- High-performance async systems where throughput is critical
- Resource-constrained environments where CPU cycles are precious
- Systems requiring predictable latency and responsiveness
- Multi-threaded applications with complex shared state requirements
By providing a practical solution to the synchronization dilemma, this pattern enables developers to build high-performance concurrent systems without sacrificing simplicity or reliability.
+603
View File
@@ -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;
};
```
+59
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 710 KiB

+214
View File
@@ -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
+47
View File
@@ -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
+152
View File
@@ -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
+64
View File
@@ -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
+32
View File
@@ -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
+10
View File
@@ -9,6 +9,16 @@
#define CONFIG_MIND_VOSCILLATOR_PERIOD_MS @MIND_VOSCILLATOR_PERIOD_MS@ #define CONFIG_MIND_VOSCILLATOR_PERIOD_MS @MIND_VOSCILLATOR_PERIOD_MS@
#define CONFIG_MIND_VOSCILLATOR_FREQ_MS @MIND_VOSCILLATOR_FREQ_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 */ /* Cross-compilation configuration */
#cmakedefine CMAKE_CROSSCOMPILING #cmakedefine CMAKE_CROSSCOMPILING
+85
View File
@@ -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
+262
View File
@@ -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
+88
View File
@@ -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
+109
View File
@@ -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
+164
View File
@@ -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
+81
View File
@@ -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
+36
View File
@@ -5,6 +5,8 @@
#include <string> #include <string>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <algorithm>
#include <stdexcept>
namespace smo { namespace smo {
namespace device { namespace device {
@@ -65,6 +67,40 @@ public:
return os.str(); 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(),
[&paramName](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 InteroceptorDevAttachmentSpec : public DeviceAttachmentSpec
+51 -7
View File
@@ -6,17 +6,49 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <preprocessor.h> #include <preprocessor.h>
#include <componentThread.h>
#include <user/deviceAttachmentSpec.h> #include <user/deviceAttachmentSpec.h>
#include <callback.h>
namespace smo { namespace smo {
namespace sense_api { namespace sense_api {
/**
* @brief Threading model descriptor for senseApi libraries.
*
* This structure provides senseApi libraries with access to the information and
* resources they need to operate with SMO's threading model.
*/
struct SmoThreadingModelDesc
{
/**
* @brief sh_ptr to ComponentThread for device-independent state mgt.
*
* This ComponentThread should be used by senseApis for state management
* that's independent of any particular device or attachment spec.
* SMO will usually pass in the Marionette thread here.
*
* State management that's tied to a particular attachment spec should be
* done on the ComponentThread for the thread that SMO provided in the
* attachDeviceReq call.
*/
std::shared_ptr<ComponentThread> componentThread;
};
typedef std::function<void(bool, std::shared_ptr<device::DeviceAttachmentSpec>)>
sal_mlo_attachDeviceReqCbFn;
typedef std::function<void(bool, std::shared_ptr<device::DeviceAttachmentSpec>)>
sal_mlo_detachDeviceReqCbFn;
typedef int (sal_mlo_initializeIndFn)(void); typedef int (sal_mlo_initializeIndFn)(void);
typedef int (sal_mlo_finalizeIndFn)(void); typedef int (sal_mlo_finalizeIndFn)(void);
typedef int (sal_mlo_attachDeviceReqFn)( typedef void (sal_mlo_attachDeviceReqFn)(
const std::shared_ptr<device::DeviceAttachmentSpec>& desc); const std::shared_ptr<device::DeviceAttachmentSpec>& desc,
typedef int (sal_mlo_detachDeviceReqFn)( const std::shared_ptr<ComponentThread>& componentThread,
const std::shared_ptr<device::DeviceAttachmentSpec>& desc); 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. * @brief Hooks provided by Salmanoff to senseApi libraries.
@@ -24,7 +56,7 @@ typedef int (sal_mlo_detachDeviceReqFn)(
* This structure contains function pointers that senseApi libraries can use * This structure contains function pointers that senseApi libraries can use
* to interact with Salmanoff's functionality, such as searching for commonLibs. * to interact with Salmanoff's functionality, such as searching for commonLibs.
*/ */
struct SalmanoffCallbacks struct SmoCallbacks
{ {
/** /**
* @brief Search for a library in Salmanoff's search paths * @brief Search for a library in Salmanoff's search paths
@@ -37,6 +69,15 @@ struct SalmanoffCallbacks
*/ */
std::optional<std::string> (*searchForLibInSmoSearchPaths)( std::optional<std::string> (*searchForLibInSmoSearchPaths)(
const std::string& libraryPath); 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 struct Sal_Mgmt_LibOps
@@ -132,11 +173,14 @@ public:
* The SenseApiDesc struct also gives Smo pointers to API functions * The SenseApiDesc struct also gives Smo pointers to API functions
* to invoke for communication between Smo and the library. * to invoke for communication between Smo and the library.
* *
* The SalmanoffCallbacks parameter provides the library with access to * The SmoCallbacks parameter provides the library with access to
* Salmanoff's hooks. * 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)( typedef const SenseApiDesc &(SMO_GET_SENSE_API_DESC_FN_TYPEDEF)(
const SalmanoffCallbacks& callbacks); const SmoCallbacks& callbacks,
const SmoThreadingModelDesc& threadingModel);
} // namespace sense_api } // namespace sense_api
} // namespace smo } // namespace smo
+3 -3
View File
@@ -10,17 +10,17 @@ int main(int argc, char *argv[], char *envp[])
*/ */
std::cout << "CRT:" << __func__ << ": about to JOLT Mrntt with cmdline args" std::cout << "CRT:" << __func__ << ": about to JOLT Mrntt with cmdline args"
<< '\n'; << '\n';
smo::mrntt::mrntt->getIoService().post( smo::mrntt::thread->getIoService().post(
[argc, argv, envp]() [argc, argv, envp]()
{ {
std::cout << "Mrntt:" << __func__ << ":JOLTED: setting cmdline args" std::cout << "Mrntt:" << __func__ << ":JOLTED: setting cmdline args"
<< '\n'; << '\n';
smo::CrtCommandLineArgs::set(argc, argv, envp); smo::CrtCommandLineArgs::set(argc, argv, envp);
smo::mrntt::mrntt->getIoService().stop(); smo::mrntt::thread->getIoService().stop();
} }
); );
smo::mrntt::mrntt->thread.join(); smo::mrntt::thread->thread.join();
std::cout << "CRT:" << __func__ << ": Mrntt exited with code '" std::cout << "CRT:" << __func__ << ": Mrntt exited with code '"
<< smo::mrntt::exitCode << "'\n"; << smo::mrntt::exitCode << "'\n";
return smo::mrntt::exitCode; return smo::mrntt::exitCode;
+20
View File
@@ -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"
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
# Script to count C/C++ lines of code in the salmanoff project
# Excludes third-party dependencies, build artifacts, and generated files
# set -e # Commented out to prevent early exit on empty file lists
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${BLUE}=== Salmanoff Project C/C++ Line Count ===${NC}"
echo
# Change to project root directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_ROOT"
echo -e "${YELLOW}Project root: $PROJECT_ROOT${NC}"
echo
# Find all C/C++ files, excluding third-party and build artifacts
CPP_FILES=$(find . -name "*.cpp" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles)
H_FILES=$(find . -name "*.h" -o -name "*.hpp" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles)
C_FILES=$(find . -name "*.c" | grep -v third_party | grep -v build | grep -v b/ | grep -v "Livox-sdk-git" | grep -v CMakeFiles)
# Count lines for each file type
echo -e "${BLUE}=== C++ Source Files (.cpp) ===${NC}"
CPP_LINES=0
if [ -n "$CPP_FILES" ]; then
echo "$CPP_FILES" | xargs wc -l
CPP_LINES=$(echo "$CPP_FILES" | xargs wc -l | tail -1 | awk '{print $1}')
else
echo "No C++ source files found"
fi
echo
echo -e "${BLUE}=== Header Files (.h, .hpp) ===${NC}"
H_LINES=0
if [ -n "$H_FILES" ]; then
echo "$H_FILES" | xargs wc -l
H_LINES=$(echo "$H_FILES" | xargs wc -l | tail -1 | awk '{print $1}')
else
echo "No header files found"
fi
echo
echo -e "${BLUE}=== C Source Files (.c) ===${NC}"
C_LINES=0
if [ -n "$C_FILES" ]; then
echo "$C_FILES" | xargs wc -l
C_LINES=$(echo "$C_FILES" | xargs wc -l | tail -1 | awk '{print $1}')
else
echo "No C source files found"
fi
echo
# Calculate total
TOTAL_LINES=$((CPP_LINES + H_LINES + C_LINES))
echo -e "${GREEN}=== SUMMARY ===${NC}"
echo -e "C++ Source Files (.cpp): ${YELLOW}$CPP_LINES${NC} lines"
echo -e "Header Files (.h/.hpp): ${YELLOW}$H_LINES${NC} lines"
echo -e "C Source Files (.c): ${YELLOW}$C_LINES${NC} lines"
echo -e "${GREEN}─────────────────────────${NC}"
echo -e "${GREEN}Total C/C++ Lines: ${YELLOW}$TOTAL_LINES${NC} lines${NC}"
echo
# Show what's excluded
echo -e "${BLUE}=== Excluded from count: ===${NC}"
echo "• third_party/ directory (googletest, etc.)"
echo "• build*/ directories (build artifacts)"
echo "• b/ directories (build artifacts)"
echo "• Livox-sdk-git/ directory (third-party SDK)"
echo "• CMakeFiles/ directories (generated files)"
echo "• Generated files (*.d, *.o, etc.)"
echo
# Optional: Show file count breakdown by directory
echo -e "${BLUE}=== Files by directory: ===${NC}"
echo "C++ Source Files:"
echo "$CPP_FILES" | sed 's|^\./||' | cut -d'/' -f1 | sort | uniq -c | sort -nr | head -10
echo
echo "Header Files:"
echo "$H_FILES" | sed 's|^\./||' | cut -d'/' -f1 | sort | uniq -c | sort -nr | head -10
+1
View File
@@ -1 +1,2 @@
add_subdirectory(xcbWindow) add_subdirectory(xcbWindow)
add_subdirectory(livoxGen1)
+22
View File
@@ -0,0 +1,22 @@
cmake_dependent_option(ENABLE_SENSEAPI_livoxGen1
"Enable Livox Gen1 LiDAR sense API" ON
"ENABLE_LIB_livoxProto1" OFF)
if(ENABLE_SENSEAPI_livoxGen1)
add_library(livoxGen1 SHARED
livoxGen1.cpp
)
# Set config define for header generation
add_compile_definitions(CONFIG_SENSEAPI_LIVOXGEN1_ENABLED)
target_include_directories(livoxGen1 PUBLIC
${Boost_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/commonLibs
)
target_link_libraries(livoxGen1
${Boost_LIBRARIES}
)
# Install rules
install(TARGETS livoxGen1 DESTINATION lib)
endif()
+416
View File
@@ -0,0 +1,416 @@
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <map>
#include <functional>
#include <algorithm>
#include <dlfcn.h>
#include <opts.h>
#include <user/senseApiDesc.h>
#include <user/deviceAttachmentSpec.h>
#include <callback.h>
#include <livoxProto1/livoxProto1.h>
#include <livoxProto1/device.h>
#include <asynchronousContinuation.h>
namespace smo {
namespace sense_api {
// Salmanoff hooks, obtained from SMO_GET_SENSE_API_DESC_FN_NAME().
static const SmoCallbacks* smoHooksPtr = nullptr;
static SmoThreadingModelDesc smoThreadingModelDesc;
// LivoxProto1 library state
struct LivoxProto1DllState
{
LivoxProto1DllState()
: dlopenHandle(nullptr, DlCloser),
livoxProto1_main(nullptr),
livoxProto1_exit(nullptr),
livoxProto1_getOrCreateDeviceReq(nullptr),
livoxProto1_destroyDeviceReq(nullptr)
{}
static void DlCloser(void* handle)
{
if (handle) {
dlclose(handle);
}
}
std::unique_ptr<void, void(*)(void*)> dlopenHandle;
livoxProto1_mainFn *livoxProto1_main;
livoxProto1_exitFn *livoxProto1_exit;
livoxProto1_getOrCreateDeviceReqFn *livoxProto1_getOrCreateDeviceReq;
livoxProto1_destroyDeviceReqFn *livoxProto1_destroyDeviceReq;
};
static LivoxProto1DllState livoxProto1;
// Attached Livox devices
static std::vector<std::shared_ptr<livoxProto1::Device>> g_attachedDevices;
// Continuation classes for async operations
class AttachDeviceReq
: public smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>
{
public:
AttachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
smo::Callback<sal_mlo_attachDeviceReqCbFn> cb)
: smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>(
std::move(cb)),
spec(spec)
{}
public:
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
public:
void attachDeviceReq1(
std::shared_ptr<AttachDeviceReq> context,
bool success, std::shared_ptr<livoxProto1::Device> dev)
{
if (!dev)
{
std::cerr << __func__ << ": Failed to create Livox device: "
<< context->spec->deviceSelector << std::endl;
context->callOriginalCb(false, context->spec);
return;
}
g_attachedDevices.push_back(dev);
if (1 || OptionParser::getOptions().verbose)
{
std::cout << __func__ << ": Successfully attached Livox "
"device: " << context->spec->deviceSelector << " (ID: "
<< context->spec->deviceIdentifier << ")\n";
}
context->callOriginalCb(success, context->spec);
}
};
class DetachDeviceReq
: public smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>
{
public:
DetachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
smo::Callback<sal_mlo_detachDeviceReqCbFn> cb)
: smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>(
std::move(cb)),
spec(spec)
{}
public:
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
public:
void detachDeviceReq1(
std::shared_ptr<DetachDeviceReq> context,
bool success)
{
if (!success)
{
std::cerr << __func__ << ": Failed to destroy Livox device: "
<< context->spec->deviceIdentifier << "\n";
context->callOriginalCb(false, context->spec);
return;
}
// Find the device in g_attachedDevices and remove it.
auto eraseIt = std::find_if(
g_attachedDevices.begin(), g_attachedDevices.end(),
[context](const std::shared_ptr<livoxProto1::Device>& dev)
{
const std::string& devId = dev->discoveredDevice.deviceIdentifier;
std::string devIdPrefix = devId.substr(
0, std::min<size_t>(14, devId.size()));
return devIdPrefix == context->spec->deviceSelector.substr(
0, std::min<size_t>(14, context->spec->deviceSelector.size()));
}
);
if (eraseIt == g_attachedDevices.end())
{
std::cerr << __func__ << ": Race condition: device not found "
"in g_attachedDevices for detachment: "
<< context->spec->deviceIdentifier << "\n";
context->callOriginalCb(false, context->spec);
return;
}
g_attachedDevices.erase(eraseIt);
std::cout << __func__ << ": Successfully detached Livox device: "
<< context->spec->deviceIdentifier << "\n";
context->callOriginalCb(success, context->spec);
}
};
// Callback function declarations
extern "C" sal_mlo_initializeIndFn livoxGen1_initializeInd;
extern "C" sal_mlo_finalizeIndFn livoxGen1_finalizeInd;
extern "C" sal_mlo_attachDeviceReqFn livoxGen1_attachDeviceReq;
extern "C" sal_mlo_detachDeviceReqFn livoxGen1_detachDeviceReq;
// Sense API descriptor
static const SenseApiDesc livoxGen1ApiDesc = {
.name = "livoxGen1",
.exportedImplexorApis = {
{.name = "pointCloudCoords"},
{.name = "pointCloudIntensity"},
{.name = "gyro"},
{.name = "accel"}
},
.sal_mgmt_libOps = {
.initializeInd = livoxGen1_initializeInd,
.finalizeInd = livoxGen1_finalizeInd,
.attachDeviceReq = livoxGen1_attachDeviceReq,
.detachDeviceReq = livoxGen1_detachDeviceReq
}
};
// Callback function implementations
extern "C" int livoxGen1_initializeInd(void)
{
if (!smoHooksPtr)
{
throw std::runtime_error(std::string(__func__) + ": SMO hooks "
"pointers not filled in.");
}
// Load LivoxProto1 library
auto libPath = smoHooksPtr->searchForLibInSmoSearchPaths(
"liblivoxProto1.so");
livoxProto1.dlopenHandle.reset(dlopen(
libPath.value_or("liblivoxProto1.so").c_str(), RTLD_LAZY));
if (!livoxProto1.dlopenHandle)
{
throw std::runtime_error(
std::string(__func__) +
": Failed to load LivoxProto1 library: " +
(dlerror() ? dlerror() : "unknown error"));
}
// Get LivoxProto1 library functions
livoxProto1.livoxProto1_main = reinterpret_cast<livoxProto1_mainFn *>(
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_main"));
livoxProto1.livoxProto1_exit = reinterpret_cast<livoxProto1_exitFn *>(
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_exit"));
livoxProto1.livoxProto1_getOrCreateDeviceReq = reinterpret_cast<
livoxProto1_getOrCreateDeviceReqFn *>(
dlsym(
livoxProto1.dlopenHandle.get(),
"livoxProto1_getOrCreateDeviceReq"));
livoxProto1.livoxProto1_destroyDeviceReq = reinterpret_cast<
livoxProto1_destroyDeviceReqFn *>(
dlsym(
livoxProto1.dlopenHandle.get(),
"livoxProto1_destroyDeviceReq"));
if (!livoxProto1.livoxProto1_main || !livoxProto1.livoxProto1_exit
|| !livoxProto1.livoxProto1_getOrCreateDeviceReq
|| !livoxProto1.livoxProto1_destroyDeviceReq)
{
throw std::runtime_error(
std::string(__func__) +
": Failed to get LivoxProto1 library functions");
}
// Call LivoxProto1 library main function
(*livoxProto1.livoxProto1_main)(
smoThreadingModelDesc.componentThread, *smoHooksPtr);
return 0; // Success
}
extern "C" int livoxGen1_finalizeInd(void)
{
// Clear all attached devices
g_attachedDevices.clear();
// Call LivoxProto1 library exit function
if (livoxProto1.livoxProto1_exit) {
(*livoxProto1.livoxProto1_exit)();
}
if (livoxProto1.dlopenHandle)
{
dlclose(livoxProto1.dlopenHandle.get());
livoxProto1.dlopenHandle.reset();
}
livoxProto1 = LivoxProto1DllState();
return 0; // Success
}
extern "C" void livoxGen1_attachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<smo::ComponentThread>& componentThread,
Callback<smo::sense_api::sal_mlo_attachDeviceReqCbFn> cb
)
{
if (!livoxProto1.livoxProto1_getOrCreateDeviceReq)
{
throw std::runtime_error(
std::string(__func__) + ": LivoxProto1 getOrCreateDevice function "
"not available");
}
for (const auto& dev : g_attachedDevices)
{
if (dev->discoveredDevice.deviceIdentifier == desc->deviceIdentifier)
{
cb.callbackFn(true, desc);
return;
}
}
// Parse integer parameters from provider params with defaults
/* The Livox Avia will generally respond to a handshake request within
* 50ms. So we set the handshake timeout to 300ms to be safe.
*/
int handshakeTimeoutMs = 300; // Default: 50ms
/* Based on testing on a Livox Avia, the device will generally resume
* sending broadcast advertisement dgrams after about 5 seconds at most.
* Generally, it will resume sending them within 1-2 seconds.
*/
int retryDelayMs = 5250; // Default: 500ms
uint8_t smoSubnetNbits = 24; // Default: /24 subnet
uint16_t dataPort = 56000; // Default data port
uint16_t cmdPort = 56001; // Default command port
uint16_t imuPort = 56002; // Default IMU port
// Default: empty string (will trigger IP auto-detection)
std::string smoIp = "";
// Parse optional integer parameters from provider params
for (const auto& param : desc->providerParams)
{
if (param.first == "handshake-timeout-ms")
{
handshakeTimeoutMs = smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(*desc, "handshake-timeout-ms");
} else if (param.first == "retry-delay-ms")
{
retryDelayMs = smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(*desc, "retry-delay-ms");
} else if (param.first == "smo-subnet-nbits")
{
smoSubnetNbits = static_cast<uint8_t>(
smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(*desc, "smo-subnet-nbits"));
} else if (param.first == "data-port")
{
dataPort = static_cast<uint16_t>(
smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(*desc, "data-port"));
} else if (param.first == "cmd-port")
{
cmdPort = static_cast<uint16_t>(
smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(*desc, "cmd-port"));
} else if (param.first == "imu-port")
{
imuPort = static_cast<uint16_t>(
smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(*desc, "imu-port"));
} else if (param.first == "smo-ip")
{
if (param.second.empty())
{
throw std::runtime_error(
std::string(__func__) + ": smo-ip parameter is empty");
}
if (param.second.find('.') == std::string::npos ||
std::count(param.second.begin(), param.second.end(), '.') != 3)
{
throw std::runtime_error(
std::string(__func__) + ": smo-ip parameter is not an "
"IPv4 address");
}
smoIp = param.second;
}
else
{
throw std::runtime_error(
std::string(__func__) + ": Unknown provider parameter: "
+ param.first);
}
}
auto request = std::make_shared<AttachDeviceReq>(desc, cb);
(*livoxProto1.livoxProto1_getOrCreateDeviceReq)(
desc->deviceSelector, // deviceIdentifier (broadcast code)
componentThread,
handshakeTimeoutMs, retryDelayMs,
smoIp, smoSubnetNbits,
dataPort, cmdPort, imuPort,
{request, std::bind(
&AttachDeviceReq::attachDeviceReq1,
request.get(), request,
std::placeholders::_1, std::placeholders::_2)});
}
extern "C" void livoxGen1_detachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
Callback<smo::sense_api::sal_mlo_detachDeviceReqCbFn> cb
)
{
// Find and remove the device from our collection
auto it = std::find_if(g_attachedDevices.begin(), g_attachedDevices.end(),
[&desc](const std::shared_ptr<livoxProto1::Device>& dev) {
/** EXPLANATION:
* Compare the first 14 characters of the deviceIdentifier with
* the first 14 characters of the deviceSelector
*/
const std::string& devId = dev->discoveredDevice.deviceIdentifier;
std::string devIdPrefix = devId.substr(
0, std::min<size_t>(14, devId.size()));
return devIdPrefix == desc->deviceSelector.substr(
0, std::min<size_t>(14, desc->deviceSelector.size()));
}
);
if (it == g_attachedDevices.end())
{
std::cerr << std::string(__func__)
<< ": Device not found for detachment: "
<< desc->deviceIdentifier << std::endl;
cb.callbackFn(false, desc);
return;
}
auto request = std::make_shared<DetachDeviceReq>(desc, cb);
(*livoxProto1.livoxProto1_destroyDeviceReq)(
*it,
{request, std::bind(
&DetachDeviceReq::detachDeviceReq1,
request.get(), request,
std::placeholders::_1)});
}
// Exported function
extern "C" smo::sense_api::SMO_GET_SENSE_API_DESC_FN_TYPEDEF
SMO_GET_SENSE_API_DESC_FN_NAME;
const smo::sense_api::SenseApiDesc& SMO_GET_SENSE_API_DESC_FN_NAME(
const smo::sense_api::SmoCallbacks& callbacks,
const smo::sense_api::SmoThreadingModelDesc& threadingModel)
{
smoHooksPtr = &callbacks;
smoThreadingModelDesc = threadingModel;
return livoxGen1ApiDesc;
}
} // namespace sense_api
} // namespace smo
+1 -1
View File
@@ -1,7 +1,7 @@
# XCB/Xorg Window Attaching SenseAPI backend # XCB/Xorg Window Attaching SenseAPI backend
cmake_dependent_option(ENABLE_SENSEAPI_xcbWindow cmake_dependent_option(ENABLE_SENSEAPI_xcbWindow
"Enable XCB/Xorg Window Attaching SenseAPI backend" ON "Enable XCB/Xorg Window Attaching SenseAPI backend" ON
"ENABLE_LIB_xcbXorg" ON) "ENABLE_LIB_xcbXorg" OFF)
if(ENABLE_SENSEAPI_xcbWindow) if(ENABLE_SENSEAPI_xcbWindow)
add_library(xcbWindow SHARED add_library(xcbWindow SHARED
+35 -37
View File
@@ -8,6 +8,7 @@
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <user/senseApiDesc.h> #include <user/senseApiDesc.h>
#include <user/deviceAttachmentSpec.h> #include <user/deviceAttachmentSpec.h>
#include <callback.h>
#include <xcbXorg/xcbXorg.h> #include <xcbXorg/xcbXorg.h>
#include "xcbWindow.h" #include "xcbWindow.h"
@@ -28,7 +29,8 @@ struct XcbXorgDllState
static XcbXorgDllState xcbXorg; static XcbXorgDllState xcbXorg;
// Salmanoff hooks, obtained from SMO_GET_SENSE_API_DESC_FN_NAME(). // Salmanoff hooks, obtained from SMO_GET_SENSE_API_DESC_FN_NAME().
static const smo::sense_api::SalmanoffCallbacks* smoHooksPtr = nullptr; static const smo::sense_api::SmoCallbacks* smoHooksPtr = nullptr;
static smo::sense_api::SmoThreadingModelDesc smoThreadingModelDesc;
// Attached windows. // Attached windows.
static std::vector<std::unique_ptr<xcb_window::AttachedWindow>> static std::vector<std::unique_ptr<xcb_window::AttachedWindow>>
@@ -68,8 +70,11 @@ AttachedWindow::AttachedWindow(
": Required xcbXorg function pointers not available"); ": Required xcbXorg function pointers not available");
} }
windowSelector.display = getRequiredParamAsInt(*spec, "display"); windowSelector.display = smo::device::DeviceAttachmentSpec
windowSelector.screen = getRequiredParamAsInt(*spec, "screen"); ::parseRequiredParamAsInt(*spec, "display");
windowSelector.screen = smo::device::DeviceAttachmentSpec
::parseRequiredParamAsInt(*spec, "screen");
parseWindowSelector(*spec); parseWindowSelector(*spec);
// Get connection from libxcbXorg // Get connection from libxcbXorg
@@ -148,32 +153,6 @@ void AttachedWindow::parseWindowSelector(
} }
} }
int AttachedWindow::getRequiredParamAsInt(const smo::device::DeviceAttachmentSpec& spec,
const std::string& paramName)
{
auto it = std::find_if(
spec.providerParams.begin(),
spec.providerParams.end(),
[&paramName](const auto& param) {
return param.first == paramName;
}
);
if (it == spec.providerParams.end())
{
throw std::runtime_error(
"No " + paramName + " specified in provider params");
}
try {
return std::stoi(it->second);
} catch (const std::exception& e) {
throw std::runtime_error(
"Failed to parse '" + paramName + "' param value '"
+ it->second + "' as integer: " + e.what());
}
}
std::string AttachedWindow::stringify() const { std::string AttachedWindow::stringify() const {
std::ostringstream os; std::ostringstream os;
@@ -295,21 +274,35 @@ static int xcbWindow_finalizeInd(void)
return 0; return 0;
} }
static int xcbWindow_attachDeviceReq( static void xcbWindow_attachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<smo::ComponentThread>& componentThread,
smo::Callback<smo::sense_api::sal_mlo_attachDeviceReqCbFn> cb
) )
{ {
// Not used yet, but may be used later.
(void)componentThread;
try {
g_attachedWindows.emplace_back( g_attachedWindows.emplace_back(
std::make_unique<xcb_window::AttachedWindow>(desc)); std::make_unique<xcb_window::AttachedWindow>(desc));
} catch (const std::exception& exc) {
std::cerr << __func__ << ": Exception while attaching X11 window: "
<< exc.what() << "\n";
cb.callbackFn(false, desc);
return;
}
std::cout << __func__ << ": Attached X11 window:\n " std::cout << __func__ << ": Attached X11 window:\n "
<< g_attachedWindows.back()->stringify() << g_attachedWindows.back()->stringify()
<< "\n"; << "\n";
return 0;
cb.callbackFn(true, desc);
} }
static int xcbWindow_detachDeviceReq( static void xcbWindow_detachDeviceReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
smo::Callback<smo::sense_api::sal_mlo_detachDeviceReqCbFn> cb
) )
{ {
auto it = std::find_if(g_attachedWindows.begin(), g_attachedWindows.end(), auto it = std::find_if(g_attachedWindows.begin(), g_attachedWindows.end(),
@@ -322,13 +315,16 @@ static int xcbWindow_detachDeviceReq(
{ {
std::cerr << __func__ << ": Device not found for detachment:\n" std::cerr << __func__ << ": Device not found for detachment:\n"
<< spec->stringify() << "\n"; << spec->stringify() << "\n";
return -1;
cb.callbackFn(false, spec);
return;
} }
g_attachedWindows.erase(it); g_attachedWindows.erase(it);
std::cout << __func__ << ": Detached X11 window device:\n" std::cout << __func__ << ": Detached X11 window device:\n"
<< spec->stringify() << "\n"; << spec->stringify() << "\n";
return 0;
cb.callbackFn(true, spec);
} }
// SenseApi descriptor // SenseApi descriptor
@@ -348,8 +344,10 @@ extern "C" smo::sense_api::SMO_GET_SENSE_API_DESC_FN_TYPEDEF
SMO_GET_SENSE_API_DESC_FN_NAME; SMO_GET_SENSE_API_DESC_FN_NAME;
const smo::sense_api::SenseApiDesc& SMO_GET_SENSE_API_DESC_FN_NAME( const smo::sense_api::SenseApiDesc& SMO_GET_SENSE_API_DESC_FN_NAME(
const smo::sense_api::SalmanoffCallbacks& callbacks) const smo::sense_api::SmoCallbacks& callbacks,
const smo::sense_api::SmoThreadingModelDesc& threadingModel)
{ {
smoHooksPtr = &callbacks; smoHooksPtr = &callbacks;
smoThreadingModelDesc = threadingModel;
return xcbWindowApiDesc; return xcbWindowApiDesc;
} }
-3
View File
@@ -44,9 +44,6 @@ public:
private: private:
void parseWindowSelector(const smo::device::DeviceAttachmentSpec& spec); void parseWindowSelector(const smo::device::DeviceAttachmentSpec& spec);
int getRequiredParamAsInt(
const smo::device::DeviceAttachmentSpec& spec,
const std::string& paramName);
std::shared_ptr<smo::device::DeviceAttachmentSpec> deviceAttachmentSpec; std::shared_ptr<smo::device::DeviceAttachmentSpec> deviceAttachmentSpec;
WindowSelector windowSelector; WindowSelector windowSelector;
+37 -5
View File
@@ -1,18 +1,50 @@
# Core library # Include Flex/Bison generation rules
include(${CMAKE_SOURCE_DIR}/cmake/flexYacc.cmake)
# Consolidated smocore library with all source files
add_library(smocore STATIC add_library(smocore STATIC
# Core files
mind.cpp mind.cpp
opts.cpp opts.cpp
componentThread.cpp componentThread.cpp
component.cpp
painfulQuale.cpp
qutex.cpp
lockerAndInvokerBase.cpp
# Body
body/body.cpp
# Marionette
marionette/main.cpp
marionette/salmanoff.cpp
marionette/lifetime.cpp
marionette/qualeEvent.cpp
# DeviceManager
deviceManager/deviceManager.cpp
deviceManager/deviceReattacher.cpp
deviceManager/deviceAttachmentPipeSpecParser.cpp
${LEX_OUTPUT}
${YACC_OUTPUT}
# SenseApis
senseApis/senseApiManager.cpp
# MindManager
mindManager/mindManager.cpp
) )
# Conditionally add qutexAcquisitionHistoryTracker.cpp only when debug locks are enabled
if(ENABLE_DEBUG_LOCKS)
target_sources(smocore PRIVATE qutexAcquisitionHistoryTracker.cpp)
endif()
target_include_directories(smocore PUBLIC target_include_directories(smocore PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_BINARY_DIR}
) )
# Link against pthread for CPU affinity functions # Link against pthread for CPU affinity functions
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
target_link_libraries(smocore PRIVATE Threads::Threads) target_link_libraries(smocore PRIVATE Threads::Threads)
add_subdirectory(marionette)
add_subdirectory(deviceManager)
add_subdirectory(senseApis)
+195
View File
@@ -0,0 +1,195 @@
#include <iostream>
#include <opts.h>
#include <asynchronousContinuation.h>
#include <asynchronousLoop.h>
#include <callback.h>
#include <body/body.h>
#include <componentThread.h>
#include <mind.h>
#include <senseApis/senseApiManager.h>
#include <deviceManager/deviceManager.h>
namespace smo {
namespace body {
Body::Body(Mind &parent, const std::shared_ptr<ComponentThread> &thread)
: MindComponent(parent, thread)
{
}
class Body::InitializeReq
: public PostedAsynchronousContinuation<bodyLifetimeMgmtOpCbFn>
{
public:
InitializeReq(
Mind &parent, const std::shared_ptr<ComponentThread> &caller,
Callback<bodyLifetimeMgmtOpCbFn> callback)
: PostedAsynchronousContinuation<bodyLifetimeMgmtOpCbFn>(caller, callback),
parent(parent)
{}
private:
Mind &parent;
public:
void initializeReq1_posted(
[[maybe_unused]] std::shared_ptr<InitializeReq> context
)
{
auto self = ComponentThread::getSelf();
if (self->id != ComponentThread::BODY)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Body thread");
}
/** EXPLANATION:
* The ComponentThread instance we pass in here is the one that will be
* used by Senseapi libs to perform device-independent background
* operations.
* For example, liblivoxProto1's BroadcastListener will use this thread
* to listen for UDP broadcast dgrams from Livox devices.
*
* Right now we use Marionette, but there's a strong argument for using
* Body instead since it's meant to handle device-management operations.
*/
sense_api::SenseApiManager::getInstance()
.loadAllSenseApiLibsFromOptions(caller);
/** EXPLANATION:
* Consider body::initializeReq to have been called if even one of its
* operations was executed at all, whether successfully or
* unsuccessfully.
*/
context->parent.bodyComponentInitialized = true;
std::cout << sense_api::SenseApiManager::getInstance().stringifyLibs()
<< std::endl;
if (OptionParser::getOptions().verbose)
{
std::cout << __func__ << ": About to initializeAllSenseApiLibs"
<< '\n';
}
sense_api::SenseApiManager::getInstance().initializeAllSenseApiLibs();
if (OptionParser::getOptions().verbose)
{
std::cout << __func__ << ": About to attachAllSenseDevicesFromSpecs"
<< '\n';
}
device::DeviceManager::getInstance()
.attachAllUnattachedDevicesFromCmdlineReq(
{context, std::bind(
&InitializeReq::initializeReq2,
context.get(), context,
std::placeholders::_1)});
}
void initializeReq2(
[[maybe_unused]] std::shared_ptr<InitializeReq> context,
smo::AsynchronousLoop &results
)
{
std::cout << "Mrntt: attached "
<< results.nSucceeded << " of " << results.nTotal
<< " sense devices." << "\n";
callOriginalCb(results.nSucceeded > 0);
}
};
class Body::FinalizeReq
: public InitializeReq
{
public:
using InitializeReq::InitializeReq;
public:
void finalizeReq1_posted(
[[maybe_unused]] std::shared_ptr<FinalizeReq> context
)
{
auto self = ComponentThread::getSelf();
if (self->id != ComponentThread::BODY)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Body thread");
}
std::cout << "Mrntt: About to detach all sense devices." << "\n";
device::DeviceManager::getInstance().detachAllAttachedDeviceRoles(
{context, std::bind(
&FinalizeReq::finalizeReq2,
context.get(), context,
std::placeholders::_1)});
}
void finalizeReq2(
[[maybe_unused]] std::shared_ptr<FinalizeReq> context,
smo::AsynchronousLoop &results
)
{
std::cout << "Mrntt: Successfully detached "
<< results.nSucceeded << " of " << results.nTotal
<< " sense devices." << "\n";
std::cout << "Mrntt: About to finalize all sense api libs." << "\n";
sense_api::SenseApiManager::getInstance().finalizeAllSenseApiLibs();
std::cout << "Mrntt: About to unload all sense api libs." << "\n";
sense_api::SenseApiManager::getInstance().unloadAllSenseApiLibs();
callOriginalCb(results.nSucceeded == results.nTotal);
}
};
void Body::initializeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
{
auto mrntt = ComponentThread::getSelf();
if (mrntt->id != ComponentThread::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be invoked by Mrntt thread");
}
auto request = std::make_shared<InitializeReq>(
parent, mrntt, callback);
thread->getIoService().post(
std::bind(
&InitializeReq::initializeReq1_posted,
request.get(), request));
}
void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
{
auto mrntt = ComponentThread::getSelf();
if (mrntt->id != ComponentThread::MRNTT)
{
std::cerr << __func__ << ": Must be invoked by Mrntt thread"
<< std::endl;
callback.callbackFn(false);
return;
}
if (!parent.bodyComponentInitialized)
{
std::cout << "Mrntt: Body component not initialized. "
<< "Skipping finalization." << "\n";
callback.callbackFn(true);
return;
}
auto request = std::make_shared<FinalizeReq>(
parent, mrntt, callback);
thread->getIoService().post(
std::bind(
&FinalizeReq::finalizeReq1_posted,
request.get(), request));
}
} // namespace body
} // namespace smo
+27
View File
@@ -0,0 +1,27 @@
#include <component.h>
namespace smo {
Component::Component(const std::shared_ptr<ComponentThread> &thread)
: thread(thread)
{
}
MindComponent::MindComponent(
Mind &parent, const std::shared_ptr<ComponentThread> &thread)
: Component(thread),
parent(parent)
{
}
namespace mrntt {
MarionetteComponent::MarionetteComponent(
const std::shared_ptr<ComponentThread> &thread)
: Component(thread)
{
}
} // namespace mrntt
} // namespace smo
+212 -150
View File
@@ -3,7 +3,11 @@
#include <pthread.h> #include <pthread.h>
#include <sched.h> #include <sched.h>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <opts.h>
#include <asynchronousContinuation.h>
#include <callback.h>
#include <mind.h> #include <mind.h>
#include <mindManager/mindManager.h>
#include <componentThread.h> #include <componentThread.h>
#include <marionette/marionette.h> #include <marionette/marionette.h>
@@ -11,17 +15,18 @@ namespace smo {
thread_local std::shared_ptr<ComponentThread> thisComponentThread; thread_local std::shared_ptr<ComponentThread> thisComponentThread;
namespace mrntt {
extern std::shared_ptr<ComponentThread> mrntt;
}
// Implementation of static method // Implementation of static method
std::shared_ptr<ComponentThread> ComponentThread::getMrntt() std::shared_ptr<MarionetteThread> ComponentThread::getMrntt()
{ {
return mrntt::mrntt; return mrntt::thread;
} }
void ComponentThread::initializeTls(void) void MarionetteThread::initializeTls(void)
{
thisComponentThread = shared_from_this();
}
void MindThread::initializeTls(void)
{ {
thisComponentThread = shared_from_this(); thisComponentThread = shared_from_this();
} }
@@ -37,9 +42,15 @@ const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void)
return thisComponentThread; return thisComponentThread;
} }
void ComponentThread::main(ComponentThread& self) void MindThread::main(MindThread& self)
{ {
std::cout << self.name << ":" << __func__ << ": Waiting for JOLT" <<"\n";
if (OptionParser::getOptions().verbose)
{
std::cout << self.name << ":" << __func__ << ": Waiting for JOLT"
<<"\n";
}
self.getIoService().run(); self.getIoService().run();
self.initializeTls(); self.initializeTls();
@@ -57,6 +68,16 @@ void ComponentThread::main(ComponentThread& self)
bool sendExceptionInd = false; bool sendExceptionInd = false;
try { try {
/** EXPLANATION:
* This reset() call is crucial for async bridging patterns
* to work.
* When the outermost thread's io_service is stop()ped (e.g.,
* from JOLT sequence), it won't process any new work until
* reset() is called, even if nested async operations try to
* post work to it. This means async bridges invoked from
* the outermost thread main sequence won't work until this
* reset() call.
*/
self.getIoService().reset(); self.getIoService().reset();
self.getIoService().run(); self.getIoService().run();
} }
@@ -73,180 +94,218 @@ void ComponentThread::main(ComponentThread& self)
<< ": Unknown exception occurred" << "\n"; << ": Unknown exception occurred" << "\n";
} }
if (sendExceptionInd) { mrntt::mrntt->exceptionInd(self); } if (sendExceptionInd)
{
mrntt::mrntt.finalizeReq(
{nullptr, std::bind(
&mrntt::marionetteFinalizeReqCb, std::placeholders::_1)});
}
} }
std::cout << self.name << ":" << __func__ << ": Exited event loop" << "\n"; std::cout << self.name << ":" << __func__ << ": Exited event loop" << "\n";
} }
// Thread management method implementations class MindThread::ThreadLifetimeMgmtOp
void ComponentThread::startThreadReq(std::function<void()> callback) : public PostedAsynchronousContinuation<threadLifetimeMgmtOpCbFn>
{ {
this->getIoService().post([this, caller = getSelf(), callback]() public:
ThreadLifetimeMgmtOp(
const std::shared_ptr<ComponentThread> &caller,
const std::shared_ptr<MindThread> &target,
Callback<threadLifetimeMgmtOpCbFn> callback)
: PostedAsynchronousContinuation<threadLifetimeMgmtOpCbFn>(
caller, callback),
target(target)
{}
public:
const std::shared_ptr<MindThread> target;
public:
void joltThreadReq1_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{ {
std::cout << "Thread '" << name << "': handling startThread." << "\n"; std::cout << __func__ << ": Thread '" << target->name << "': handling "
"JOLT request."
<< "\n";
target->io_service.stop();
callOriginalCb();
}
void startThreadReq1_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"startThread."
<< "\n";
// Execute private setup sequence here // Execute private setup sequence here
// This is where each thread would implement its specific initialization // This is where each thread would implement its specific initialization
if (callback) { callOriginalCb();
caller->getIoService().post(callback);
} }
});
} void exitThreadReq1_mainQueue_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"exitThread (main queue)." << "\n";
target->cleanup();
target->io_service.stop();
callOriginalCb();
}
void exitThreadReq1_pauseQueue_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"exitThread (pause queue)."<< "\n";
target->cleanup();
target->pause_io_service.stop();
target->io_service.stop();
callOriginalCb();
}
void pauseThreadReq1_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"pauseThread." << "\n";
/* We have to invoke the callback here before moving on because
* our next operation is going to block the thread, so it won't
* have a chance to invoke the callback until it's unblocked.
*/
callOriginalCb();
target->pause_io_service.reset();
target->pause_io_service.run();
}
void resumeThreadReq1_posted(
[[maybe_unused]] std::shared_ptr<ThreadLifetimeMgmtOp> context
)
{
std::cout << __func__ << ": Thread '" << target->name << "': handling "
"resumeThread." << "\n";
target->pause_io_service.stop();
callOriginalCb();
}
};
void ComponentThread::cleanup(void) void ComponentThread::cleanup(void)
{ {
this->keepLooping = false; this->keepLooping = false;
} }
void ComponentThread::exitThreadReq(std::function<void()> callback) void MindThread::joltThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{ {
// Post to the main io_service /** EXPLANATION:
this->getIoService().post([this, caller = getSelf(), callback]() * We can't use shared_from_this() here because JOLTing occurs prior to
{ * TLS being set up.
std::cout << "Thread '" << name << "': handling exitThread "
"(main queue)." << std::endl;
cleanup();
// Stop the main io_service to exit the thread
io_service.stop();
if (callback) { caller->getIoService().post(callback); }
});
// Also post to the pause io_service
this->pause_io_service.post([this, caller = getSelf(), callback]()
{
std::cout << "Thread '" << name << "': handling exitThread "
"(pause queue)." << std::endl;
cleanup();
// Stop both io_services to exit the thread
pause_io_service.stop();
io_service.stop();
if (callback) { caller->getIoService().post(callback); }
});
}
void ComponentThread::pauseThreadReq(std::function<void()> callback)
{
this->getIoService().post([this, caller = getSelf(), callback]()
{
std::cout << "Thread '" << name << "': handling pauseThread."
<< std::endl;
if (callback) {
caller->getIoService().post(callback);
}
// Reset the pause io_service before running to ensure it can run again
pause_io_service.reset();
// Run the pause io_service to block this thread
pause_io_service.run();
});
}
void ComponentThread::resumeThreadReq(std::function<void()> callback)
{
// Post to the pause_io_service to unblock the paused thread
pause_io_service.post([this, caller = getSelf(), callback]()
{
std::cout << "Thread '" << name << "': handling resumeThread."
<< std::endl;
if (callback) {
caller->getIoService().post(callback);
}
// Stop the pause_io_service to unblock the thread
pause_io_service.stop();
});
}
void ComponentThread::joltThreadReq(std::function<void()> callback)
{
this->getIoService().post([this, caller = getSelf(), callback]()
{
std::cout << "Thread '" << name << "': handling JOLT request." << "\n";
// Stop the main io_service to jolt the thread
io_service.stop();
if (callback) {
caller->getIoService().post(callback);
}
});
}
/* This shouldn't take a callback because the caller shouldn't expect to
* Mrntt to send a reply signal to it. Sending this Indication means that
* Mrntt will send the calling thread an exitThreadReq. When the caller
* processes that exitThreadReq(), the caller will exit its event loop and then
* terminate.
* *
* Even if Mrntt sent a RDY response, the caller shouldn't actually be executing * We also can't use getSelf() as yet for the same reason: getSelf()
* any longer to receive it anyway. * requires TLS to be set up.
*
* To obtain a sh_ptr to the caller, we just supply the mrntt thread since
* JOLT is always invoked by the mrntt thread. The JOLT sequence that the
* CRT main() function invokes on the mrntt thread is special since it
* supplies cmdline args and envp.
*
* To obtain a sh_ptr to the target thread, we explicitly look it up in the
* Mind object's collection of component threads.
*/ */
void ComponentThread::exceptionInd(ComponentThread& thread) if (id == ComponentThread::MRNTT)
{
if (this->id != ComponentThread::MRNTT)
{ {
throw std::runtime_error(std::string(__func__) throw std::runtime_error(std::string(__func__)
+ ": invoked on non-mrntt thread " + thread.name); + ": invoked on mrntt thread");
} }
// Post the exception to the mrntt thread. std::shared_ptr<MarionetteThread> mrntt = mrntt::thread;
this->getIoService().post( std::shared_ptr<MindThread> target = getParent().getComponentThread(id);
[&thread]()
{
std::cerr << "Mrntt: Exception occurred: in thread "
<< thread.name << ". Killing Salmanoff." << "\n";
/** EXPLANATION: auto request = std::make_shared<ThreadLifetimeMgmtOp>(
* An exception has occurred in one of a mind's threads. We need to mrntt, target, callback);
* shut down all of that particular mind's threads.
*/ this->getIoService().post(
thread.parent.finalizeReq([]() { std::bind(
/** FIXME: &ThreadLifetimeMgmtOp::joltThreadReq1_posted,
* When we eventually support multiple minds, we should remove this request.get(), request));
* since it causes marionette to exit, even if there are other minds
* that are still running.
*/
smo::mrntt::exitMarionetteLoop();
});
});
} }
void ComponentThread::userShutdownInd() // Thread management method implementations
void MindThread::startThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{ {
if (this->id != ComponentThread::MRNTT) std::shared_ptr<ComponentThread> caller = getSelf();
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
caller, shared_from_this(), callback);
this->getIoService().post(
std::bind(
&ThreadLifetimeMgmtOp::startThreadReq1_posted,
request.get(), request));
}
void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{
std::shared_ptr<ComponentThread> caller = getSelf();
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
caller, shared_from_this(), callback);
this->getIoService().post(
std::bind(
&ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted,
request.get(), request));
pause_io_service.post(
std::bind(
&ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted,
request.get(), request));
}
void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
{
if (id == ComponentThread::MRNTT)
{ {
throw std::runtime_error(std::string(__func__) throw std::runtime_error(std::string(__func__)
+ ": invoked on non-mrntt thread " + this->name); + ": invoked on mrntt thread");
} }
// Post the user shutdown to the mrntt thread. std::shared_ptr<ComponentThread> caller = getSelf();
this->getIoService().post( auto request = std::make_shared<ThreadLifetimeMgmtOp>(
[this]() caller, shared_from_this(), callback);
{
std::cerr << "Mrntt: User requested shutdown (SIGINT)."
<< " Killing Salmanoff." << "\n";
/** EXPLANATION: this->getIoService().post(
* A user has requested a shutdown. We need to shut down all of the std::bind(
* threads in all running Minds. &ThreadLifetimeMgmtOp::pauseThreadReq1_posted,
*/ request.get(), request));
parent.finalizeReq([]() { }
/** FIXME:
* When we eventually support multiple minds, we should remove this void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
* since it causes marionette to exit, even if there are other minds {
* that are still running. if (id == ComponentThread::MRNTT)
*/ {
smo::mrntt::exitMarionetteLoop(); throw std::runtime_error(std::string(__func__)
}); + ": invoked on mrntt thread");
}); }
// Post to the pause_io_service to unblock the paused thread
std::shared_ptr<ComponentThread> caller = getSelf();
auto request = std::make_shared<ThreadLifetimeMgmtOp>(
caller, shared_from_this(), callback);
pause_io_service.post(
std::bind(
&ThreadLifetimeMgmtOp::resumeThreadReq1_posted,
request.get(), request));
} }
// CPU management method implementations // CPU management method implementations
@@ -272,7 +331,7 @@ int ComponentThread::getAvailableCpuCount()
return cpuCount; return cpuCount;
} }
void ComponentThread::pinToCpu(int cpuId) void MindThread::pinToCpu(int cpuId)
{ {
if (cpuId < 0) if (cpuId < 0)
{ {
@@ -294,7 +353,10 @@ void ComponentThread::pinToCpu(int cpuId)
} }
pinnedCpuId = cpuId; pinnedCpuId = cpuId;
if (OptionParser::getOptions().verbose)
{
std::cout << name << ": Pinned to CPU " << cpuId << "\n"; std::cout << name << ": Pinned to CPU " << cpuId << "\n";
}
} }
} // namespace smo } // namespace smo
-29
View File
@@ -1,29 +0,0 @@
# Flex/Bison generated files
set(LEX_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/deviceAttachmentPipeSpecl.cc)
set(YACC_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/deviceAttachmentPipeSpecp.cc)
set(YACC_HEADER ${CMAKE_CURRENT_BINARY_DIR}/deviceAttachmentPipeSpecp.hh)
# Generate Flex/Bison files using custom commands
add_custom_command(
OUTPUT ${LEX_OUTPUT}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/deviceAttachmentPipeSpecl.ll ${YACC_HEADER}
COMMAND ${FLEX_EXECUTABLE} --header-file=${CMAKE_CURRENT_BINARY_DIR}/deviceAttachmentPipeSpecl.hh -o ${LEX_OUTPUT} ${CMAKE_CURRENT_SOURCE_DIR}/deviceAttachmentPipeSpecl.ll
COMMENT "Generating deviceAttachmentPipeSpecl.cc from deviceAttachmentPipeSpecl.ll"
)
add_custom_command(
OUTPUT ${YACC_OUTPUT} ${YACC_HEADER}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/deviceAttachmentPipeSpecp.yy
COMMAND ${BISON_EXECUTABLE} -p deviceAttachmentPipeSpecp --header=${YACC_HEADER} -o ${YACC_OUTPUT} ${CMAKE_CURRENT_SOURCE_DIR}/deviceAttachmentPipeSpecp.yy
COMMENT "Generating deviceAttachmentPipeSpecp.cc and deviceAttachmentPipeSpecp.hh from deviceAttachmentPipeSpecp.yy"
)
# Device manager library
add_library(deviceManager STATIC
deviceManager.cpp
deviceAttachmentPipeSpecParser.cpp
${LEX_OUTPUT}
${YACC_OUTPUT}
)
target_include_directories(deviceManager PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
@@ -87,8 +87,7 @@ interoceptor_spec:
*static_cast<smo::device::InteroceptorDevAttachmentSpec *>($3)); *static_cast<smo::device::InteroceptorDevAttachmentSpec *>($3));
spec->sensorType = $1; spec->sensorType = $1;
smo::device::DeviceManager::interoceptorDeviceSpecs.push_back(spec); smo::device::DeviceManager::commandLineDASpecs.push_back(*spec);
smo::device::DeviceManager::deviceAttachmentSpecs.push_back(spec);
delete $3; delete $3;
} }
@@ -100,8 +99,7 @@ extrospector_spec:
*static_cast<smo::device::ExtrospectorDevAttachmentSpec *>($3)); *static_cast<smo::device::ExtrospectorDevAttachmentSpec *>($3));
spec->sensorType = $1; spec->sensorType = $1;
smo::device::DeviceManager::extrospectorDeviceSpecs.push_back(spec); smo::device::DeviceManager::commandLineDASpecs.push_back(*spec);
smo::device::DeviceManager::deviceAttachmentSpecs.push_back(spec);
delete $3; delete $3;
} }
+773 -58
View File
@@ -6,108 +6,823 @@
#include <sstream> #include <sstream>
#include <memory> #include <memory>
#include <opts.h> #include <opts.h>
#include <asynchronousContinuation.h>
#include <serializedAsynchronousContinuation.h>
#include <callback.h>
#include <componentThread.h>
#include <deviceManager/deviceManager.h> #include <deviceManager/deviceManager.h>
#include <deviceManager/deviceReattacher.h>
#include <senseApis/senseApiManager.h> #include <senseApis/senseApiManager.h>
#include <marionette/marionette.h>
#include <mind.h>
namespace smo { namespace smo {
namespace device { namespace device {
std::vector<std::shared_ptr<InteroceptorDevAttachmentSpec>>
DeviceManager::interoceptorDeviceSpecs;
std::vector<std::shared_ptr<ExtrospectorDevAttachmentSpec>>
DeviceManager::extrospectorDeviceSpecs;
std::vector<std::shared_ptr<DeviceAttachmentSpec>> std::vector<std::shared_ptr<DeviceAttachmentSpec>>
DeviceManager::deviceAttachmentSpecs; DeviceManager::deviceAttachmentSpecs;
std::vector<std::shared_ptr<Device>> std::vector<std::shared_ptr<Device>>
DeviceManager::devices; DeviceManager::devices;
std::vector<std::shared_ptr<DeviceRole>>
DeviceManager::attachedDeviceRoles;
std::vector<DeviceAttachmentSpec>
DeviceManager::commandLineDASpecs;
// Async continuation structure DeviceManager::~DeviceManager()
struct DeviceAttachmentContinuation { {
std::shared_ptr<DeviceAttachmentSpec> spec; }
std::function<void(
bool success, std::shared_ptr<Device> device,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec)
> callback;
DeviceAttachmentContinuation(
std::shared_ptr<DeviceAttachmentSpec> s,
std::function<void(
bool success, std::shared_ptr<Device> device,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec)
> cb)
: spec(s), callback(cb)
{}
};
const std::string DeviceManager::stringifyDeviceSpecs(void) const std::string DeviceManager::stringifyDeviceSpecs(void)
{ {
std::ostringstream oss; std::ostringstream oss;
for (const auto& spec : DeviceManager::interoceptorDeviceSpecs) { for (const auto& spec : DeviceManager::deviceAttachmentSpecs) {
oss << "Interoceptor " << spec->stringify(); oss << "Device Attachment Spec: " << spec->stringify();
}
for (const auto& spec : DeviceManager::extrospectorDeviceSpecs) {
oss << "Extrospector " << spec->stringify();
} }
return oss.str(); return oss.str();
} }
void DeviceManager::newDeviceAttachmentSpecInd( class DeviceManager::NewDeviceAttachmentSpecInd
std::shared_ptr<DeviceAttachmentSpec> spec, : public SerializedAsynchronousContinuation<newDeviceAttachmentSpecIndCbFn>
std::function<void(
bool success, std::shared_ptr<Device> device,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec)
> callback)
{ {
// Create async continuation public:
auto continuation = std::make_shared<DeviceAttachmentContinuation>( NewDeviceAttachmentSpecInd(
spec, callback); const DeviceAttachmentSpec &spec,
const std::shared_ptr<ComponentThread> &caller,
Callback<newDeviceAttachmentSpecIndCbFn> cb,
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
: SerializedAsynchronousContinuation<newDeviceAttachmentSpecIndCbFn>(
caller, cb, requiredLocks),
spec(spec)
{}
// Check if a DeviceAttachmentSpec already matches public:
for (const auto& existingSpec : deviceAttachmentSpecs) DeviceAttachmentSpec spec;
std::shared_ptr<DeviceAttachmentSpec> specPtr;
std::shared_ptr<Device> device;
public:
void newDeviceAttachmentSpecInd1_posted(
[[maybe_unused]] std::shared_ptr<NewDeviceAttachmentSpecInd> context
)
{ {
if (!(*existingSpec == *spec)) { continue; } // First, add the spec to deviceAttachmentSpecs if it's not already there
// Already exists, callback with error bool specExists = false;
callback(false, nullptr, nullptr); for (const auto& existingSpec : DeviceManager::deviceAttachmentSpecs)
return; {
if (*existingSpec == spec)
{
specExists = true;
specPtr = existingSpec;
break;
}
} }
// Try to attach the sense device if (!specExists)
try {
sense_api::SenseApiManager::getInstance().attachSenseDevice(spec);
// Look for existing Device with same identifier
std::shared_ptr<Device> device = nullptr;
for (const auto& existingDevice : devices)
{ {
if (existingDevice->deviceIdentifier != spec->deviceIdentifier) specPtr = std::make_shared<DeviceAttachmentSpec>(spec);
DeviceManager::deviceAttachmentSpecs.push_back(specPtr);
}
bool deviceExists = false;
for (const auto& existingDevice : DeviceManager::devices)
{
if (existingDevice->deviceIdentifier != spec.deviceIdentifier)
{ continue; } { continue; }
device = existingDevice; device = existingDevice;
deviceExists = true;
break; break;
} }
// If device doesn't exist, create a new one and add it // If device doesn't exist, create a new one and add it
if (!device) if (!device)
{ {
device = std::make_shared<Device>(spec->deviceIdentifier); device = std::make_shared<Device>(spec.deviceIdentifier);
devices.push_back(device); DeviceManager::devices.push_back(device);
} }
// Add DeviceAttachmentSpec to device's list // Check if a DeviceRole w/ this spec already exists in attachedDeviceRoles
device->deviceAttachmentSpecs.push_back(spec); bool deviceRoleExists = false;
std::shared_ptr<DeviceRole> existingDeviceRole = nullptr;
for (const auto& role : DeviceManager::attachedDeviceRoles)
{
if (*role->deviceAttachmentSpec == spec)
{
deviceRoleExists = true;
existingDeviceRole = role;
break;
}
}
// Add DeviceAttachmentSpec to DeviceManager's list // If DeviceRole exists, both spec and device must also exist
deviceAttachmentSpecs.push_back(spec); if (deviceRoleExists)
{
if (!specExists || !deviceExists)
{
throw std::runtime_error(
"Program error: DeviceRole exists but spec or device doesn't "
"pre-exist. specExists=" + std::to_string(specExists) +
", deviceExists=" + std::to_string(deviceExists));
}
// Already attached, callback with success and return
callOriginalCb(true, existingDeviceRole, specPtr);
return;
}
DeviceManager::getInstance().attachSenseDeviceReq(
specPtr,
{context, std::bind(
&NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd2,
context.get(), context,
std::placeholders::_1, std::placeholders::_2)});
}
void newDeviceAttachmentSpecInd2(
[[maybe_unused]] std::shared_ptr<NewDeviceAttachmentSpecInd> context,
bool success,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
)
{
if (!success)
{
std::cerr << __func__ << ": Attach failed for device spec "
<< deviceSpec->stringify() << std::endl;
callOriginalCb(false, nullptr, deviceSpec);
return;
}
try {
// Create DeviceRole and add it to both DeviceManager's and Device's collections
auto deviceRole = std::make_shared<DeviceRole>(*device, specPtr);
device->deviceRoles.push_back(deviceRole);
DeviceManager::attachedDeviceRoles.push_back(deviceRole);
// Callback with success // Callback with success
callback(true, device, spec); callOriginalCb(true, deviceRole, specPtr);
} catch (const std::exception& e) { } catch (const std::exception& e) {
// Attach failed, callback with error // Attach failed, callback with error
callback(false, nullptr, nullptr); callOriginalCb(false, nullptr, specPtr);
} }
}
};
class DeviceManager::RemoveDeviceAttachmentSpecReq
: public SerializedAsynchronousContinuation<removeDeviceAttachmentSpecReqCbFn>
{
public:
RemoveDeviceAttachmentSpecReq(
const DeviceAttachmentSpec &spec,
const std::shared_ptr<ComponentThread> &caller,
Callback<removeDeviceAttachmentSpecReqCbFn> cb,
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
: SerializedAsynchronousContinuation<removeDeviceAttachmentSpecReqCbFn>(
caller, cb, requiredLocks),
spec(spec)
{}
public:
DeviceAttachmentSpec spec;
std::shared_ptr<DeviceAttachmentSpec> specPtr;
public:
void removeDeviceAttachmentSpecReq1_posted(
[[maybe_unused]] std::shared_ptr<RemoveDeviceAttachmentSpecReq> context
)
{
// Find the shared_ptr to the spec in the collection
for (const auto& existingSpec : DeviceManager::deviceAttachmentSpecs)
{
if (*existingSpec == spec)
{
specPtr = existingSpec;
break;
}
}
if (!specPtr)
{
// Spec not found, callback with failure and return
callOriginalCb(false, nullptr);
return;
}
// Call detachSenseDeviceReq first - only clean up metadata if this succeeds
DeviceManager::getInstance().detachSenseDeviceReq(
specPtr,
{context, std::bind(
&RemoveDeviceAttachmentSpecReq::removeDeviceAttachmentSpecReq2,
context.get(), context,
std::placeholders::_1, std::placeholders::_2)});
}
void removeDeviceAttachmentSpecReq2(
[[maybe_unused]] std::shared_ptr<RemoveDeviceAttachmentSpecReq> context,
bool success,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
)
{
if (!success)
{
// Detach failed, callback with failure (metadata remains intact)
callOriginalCb(false, deviceSpec);
return;
}
// Detach succeeded, now find and clean up metadata
try {
// Find the DeviceRole in attachedDeviceRoles
auto deviceRoleIt = std::find_if(
DeviceManager::attachedDeviceRoles.begin(),
DeviceManager::attachedDeviceRoles.end(),
[&specPtr = specPtr](const std::shared_ptr<DeviceRole> &role) {
return *role->deviceAttachmentSpec == *specPtr;
}
);
if (deviceRoleIt == DeviceManager::attachedDeviceRoles.end())
{
// DeviceRole not found, callback with failure
callOriginalCb(false, deviceSpec);
return;
}
auto deviceRole = *deviceRoleIt;
auto& device = deviceRole->parentDevice;
// Remove DeviceRole from DeviceManager's collection
DeviceManager::attachedDeviceRoles.erase(deviceRoleIt);
// Remove DeviceRole from Device's collection
auto deviceRoleIt2 = std::find(
device.deviceRoles.begin(),
device.deviceRoles.end(),
deviceRole);
if (deviceRoleIt2 != device.deviceRoles.end())
{
device.deviceRoles.erase(deviceRoleIt2);
}
// Remove DeviceAttachmentSpec from deviceAttachmentSpecs collection
auto specIt = std::find_if(
DeviceManager::deviceAttachmentSpecs.begin(),
DeviceManager::deviceAttachmentSpecs.end(),
[&specPtr = specPtr](
const std::shared_ptr<DeviceAttachmentSpec> &existingSpec)
{
return *existingSpec == *specPtr;
}
);
if (specIt != DeviceManager::deviceAttachmentSpecs.end())
{
DeviceManager::deviceAttachmentSpecs.erase(specIt);
}
// Callback with success
callOriginalCb(true, deviceSpec);
} catch (const std::exception& e) {
// Cleanup failed, callback with error
callOriginalCb(false, deviceSpec);
}
}
};
void DeviceManager::newDeviceAttachmentSpecInd(
const DeviceAttachmentSpec &spec,
Callback<newDeviceAttachmentSpecIndCbFn> callback)
{
const auto& caller = ComponentThread::getSelf();
auto request = std::make_shared<NewDeviceAttachmentSpecInd>(
spec, caller, callback,
LockSet<newDeviceAttachmentSpecIndCbFn>::Set{
std::ref(DeviceManager::getInstance().qutex)
});
NewDeviceAttachmentSpecInd::LockerAndInvoker lockvoker(
*request, mrntt::mrntt.thread,
std::bind(
&NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd1_posted,
request.get(), request));
}
void DeviceManager::removeDeviceAttachmentSpecReq(
const DeviceAttachmentSpec &spec,
Callback<removeDeviceAttachmentSpecReqCbFn> callback)
{
const auto& caller = ComponentThread::getSelf();
auto request = std::make_shared<RemoveDeviceAttachmentSpecReq>(
spec, caller, callback,
LockSet<removeDeviceAttachmentSpecReqCbFn>::Set{
std::ref(DeviceManager::getInstance().qutex)
});
RemoveDeviceAttachmentSpecReq::LockerAndInvoker lockvoker(
*request, mrntt::mrntt.thread,
std::bind(
&RemoveDeviceAttachmentSpecReq
::removeDeviceAttachmentSpecReq1_posted,
request.get(), request));
}
class DeviceManager::AttachSenseDeviceReq
: public SerializedAsynchronousContinuation<attachSenseDeviceReqCbFn>
{
public:
AttachSenseDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec,
const std::shared_ptr<ComponentThread> &caller,
Callback<attachSenseDeviceReqCbFn> cb,
std::shared_ptr<sense_api::SenseApiLib> &senseApiLib,
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
: SerializedAsynchronousContinuation<attachSenseDeviceReqCbFn>(
caller, cb, requiredLocks),
spec(spec), senseApiLib(senseApiLib)
{}
public:
void attachSenseDeviceReq1_posted(
[[maybe_unused]] std::shared_ptr<AttachSenseDeviceReq> context
)
{
if (caller->id != ComponentThread::MRNTT)
{
std::cerr << std::string(__func__)
<< ": executed on non-mrntt thread: "
<< caller->name << std::endl;
callOriginalCb(false, spec);
return;
}
if (senseApiLib->isBeingDestroyed.load())
{
std::cerr << std::string(__func__) + ": Library is being destroyed"
<< " for API '" << spec->api << "'. Bailing out." << std::endl;
callOriginalCb(false, spec);
return;
}
if (!senseApiLib->senseApiDesc.sal_mgmt_libOps.attachDeviceReq)
{
std::cerr << std::string(__func__) + ": attachDeviceReq() is NULL "
"for library '" << senseApiLib->libraryPath << "'" << std::endl;
callOriginalCb(false, spec);
return;
}
releaseQutexEarly(sense_api::SenseApiManager::getInstance().qutex);
/** EXPLANATION:
* We pass in either the body or world thread here, depending on whether
* the device is an introspector (idev) or extrospector (edev).
*
* Introspectors are attached to the body thread; extrospectors are
* attached to the world thread.
*/
std::shared_ptr<ComponentThread> threadForAttachment;
if (spec->sensorType == 'e')
{
threadForAttachment = mind::globalMind->world.thread;
std::cout << __func__ << ": Attaching edev "
<< spec->deviceIdentifier << " to world thread" << "\n";
}
else
{
threadForAttachment = mind::globalMind->body.thread;
std::cout << __func__ << ": Attaching non-edev "
<< spec->deviceIdentifier << " to body thread" << "\n";
}
senseApiLib->senseApiDesc.sal_mgmt_libOps.attachDeviceReq(
spec, threadForAttachment,
{context, std::bind(
&AttachSenseDeviceReq::attachSenseDeviceReq2,
context.get(), context,
std::placeholders::_1, std::placeholders::_2)});
}
void attachSenseDeviceReq2(
[[maybe_unused]] std::shared_ptr<AttachSenseDeviceReq> context,
bool success,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
)
{
callOriginalCb(success, deviceSpec);
}
void detachSenseDeviceReq1_posted(
[[maybe_unused]] std::shared_ptr<DetachSenseDeviceReq> context
)
{
if (caller->id != ComponentThread::MRNTT)
{
std::cerr << std::string(__func__)
<< ": executed on non-mrntt thread: "
<< caller->name << std::endl;
callOriginalCb(false, spec);
return;
}
if (senseApiLib->isBeingDestroyed.load())
{
std::cerr << std::string(__func__) + ": Library is being destroyed"
<< " for API '" << spec->api << "'. Bailing out." << std::endl;
callOriginalCb(false, spec);
return;
}
if (!senseApiLib->senseApiDesc.sal_mgmt_libOps.detachDeviceReq)
{
std::cerr << std::string(__func__) + ": detachDeviceReq() is NULL "
"for library '" << senseApiLib->libraryPath << "'" << std::endl;
callOriginalCb(false, spec);
return;
}
releaseQutexEarly(sense_api::SenseApiManager::getInstance().qutex);
senseApiLib->senseApiDesc.sal_mgmt_libOps.detachDeviceReq(
spec,
{context, std::bind(
&DetachSenseDeviceReq::detachSenseDeviceReq2,
context.get(), context,
std::placeholders::_1, std::placeholders::_2)});
}
void detachSenseDeviceReq2(
[[maybe_unused]] std::shared_ptr<DetachSenseDeviceReq> context,
bool success,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec
)
{
callOriginalCb(success, deviceSpec);
}
public:
std::shared_ptr<DeviceAttachmentSpec> spec;
std::shared_ptr<sense_api::SenseApiLib> senseApiLib;
};
void DeviceManager::attachSenseDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec,
Callback<attachSenseDeviceReqCbFn> cb
)
{
const auto& caller = ComponentThread::getSelf();
// Get the sense API lib's qutex
auto libOpt = sense_api::SenseApiManager::getInstance()
.getSenseApiLibByApiName(spec->api);
if (!libOpt)
{
std::cerr << "attachSenseDeviceReq: No library found for API '"
<< spec->api << "'" << std::endl;
cb.callbackFn(false, spec);
return;
}
auto& lib = *libOpt.value();
auto request = std::make_shared<AttachSenseDeviceReq>(
spec, caller, cb, libOpt.value(),
LockSet<attachSenseDeviceReqCbFn>::Set{
std::ref(sense_api::SenseApiManager::getInstance().qutex),
std::ref(lib.qutex)
});
AttachSenseDeviceReq::LockerAndInvoker lockvoker(
*request, mrntt::mrntt.thread,
std::bind(
&AttachSenseDeviceReq::attachSenseDeviceReq1_posted,
request.get(), request));
}
void DeviceManager::detachSenseDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec,
Callback<detachSenseDeviceReqCbFn> cb
)
{
const auto& caller = ComponentThread::getSelf();
// Get the sense API lib's qutex
auto libOpt = sense_api::SenseApiManager::getInstance()
.getSenseApiLibByApiName(spec->api);
if (!libOpt)
{
std::cerr << "detachSenseDeviceReq: No library found for API '"
<< spec->api << "'" << std::endl;
cb.callbackFn(false, spec);
return;
}
auto& lib = *libOpt.value();
auto request = std::make_shared<DetachSenseDeviceReq>(
spec, caller, cb, libOpt.value(),
LockSet<detachSenseDeviceReqCbFn>::Set{
std::ref(sense_api::SenseApiManager::getInstance().qutex),
std::ref(lib.qutex)
});
DetachSenseDeviceReq::LockerAndInvoker lockvoker(
*request, mrntt::mrntt.thread,
std::bind(
&DetachSenseDeviceReq::detachSenseDeviceReq1_posted,
request.get(), request));
}
class DeviceManager::AttachAllUnattachedDevicesFromReq
: public PostedAsynchronousContinuation<
attachAllUnattachedDevicesFromReqCbFn>
{
public:
AttachAllUnattachedDevicesFromReq(
const unsigned int totalNSpecs,
const std::shared_ptr<std::vector<DeviceAttachmentSpec>>& specs,
const std::shared_ptr<ComponentThread>& caller,
Callback<attachAllUnattachedDevicesFromReqCbFn> cb)
: PostedAsynchronousContinuation<attachAllUnattachedDevicesFromReqCbFn>(
caller, cb),
loop(totalNSpecs), specs(specs)
{}
public:
void attachAllUnattachedDevicesFromReq1_posted(
[[maybe_unused]] std::shared_ptr<AttachAllUnattachedDevicesFromReq>
context
)
{
for (const auto& spec : *specs)
{
DeviceManager::getInstance().newDeviceAttachmentSpecInd(
spec,
{context, std::bind(
&AttachAllUnattachedDevicesFromReq
::attachAllUnattachedDevicesFromReq2,
context.get(), context,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3)});
}
}
// Callback methods for the attachment sequence
void attachAllUnattachedDevicesFromReq2(
std::shared_ptr<AttachAllUnattachedDevicesFromReq> context,
bool success, [[maybe_unused]] std::shared_ptr<DeviceRole> deviceRole,
std::shared_ptr<DeviceAttachmentSpec> spec
)
{
if (!success)
{
std::cerr << __func__ << ": Failed to attach device: "
<< spec->deviceIdentifier << "\n";
// Fallthrough.
}
if (!context->loop.incrementSuccessOrFailureAndTestForCompletionDueTo(
success))
{
return;
}
if (OptionParser::getOptions().verbose)
{
std::cout << __func__ << ": " << context->loop.nSucceeded.load()
<< " devices attached, "
<< context->loop.nFailed.load() << " devices failed\n";
}
context->callOriginalCb(loop);
}
public:
AsynchronousLoop loop;
std::shared_ptr<std::vector<DeviceAttachmentSpec>> specs;
};
void DeviceManager::attachAllUnattachedDevicesFromReq(
const std::shared_ptr<std::vector<DeviceAttachmentSpec>> &specs,
Callback<attachAllUnattachedDevicesFromReqCbFn> cb
)
{
if (specs->size() == 0)
{
AsynchronousLoop tmp(0);
cb.callbackFn(tmp);
return;
}
const auto& caller = ComponentThread::getSelf();
auto request = std::make_shared<AttachAllUnattachedDevicesFromReq>(
specs->size(), specs, caller, std::move(cb));
mrntt::mrntt.thread->getIoService().post(
std::bind(
&AttachAllUnattachedDevicesFromReq
::attachAllUnattachedDevicesFromReq1_posted,
request.get(), request));
}
void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq(
Callback<attachAllUnattachedDevicesFromReqCbFn> cb
)
{
auto specs = std::make_shared<std::vector<DeviceAttachmentSpec>>(
commandLineDASpecs);
attachAllUnattachedDevicesFromReq(specs, std::move(cb));
}
class DeviceManager::AttachAllUnattachedDevicesFromKnownListReq
: public SerializedAsynchronousContinuation<
attachAllUnattachedDevicesFromReqCbFn>
{
public:
AttachAllUnattachedDevicesFromKnownListReq(
const std::shared_ptr<ComponentThread> &caller,
Callback<attachAllUnattachedDevicesFromReqCbFn> cb,
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
: SerializedAsynchronousContinuation<
attachAllUnattachedDevicesFromReqCbFn>(
caller, cb, requiredLocks)
{}
public:
void attachAllUnattachedDevicesFromKnownListReq1_posted(
[[maybe_unused]]
std::shared_ptr<AttachAllUnattachedDevicesFromKnownListReq> context
)
{
// Create a vector to hold unattached device specs
auto unattachedSpecs = std::make_shared<
std::vector<DeviceAttachmentSpec>>();
// Cycle through all DA specs in deviceAttachmentSpecs
for (const auto& spec : DeviceManager::deviceAttachmentSpecs)
{
bool isAttached = false;
// Cross reference with attachedDeviceRoles
for (const auto& role : DeviceManager::attachedDeviceRoles)
{
if (*role->deviceAttachmentSpec == *spec)
{
isAttached = true;
break;
}
}
// If spec doesn't appear in attachedDeviceRoles, add it to vector
if (!isAttached) {
unattachedSpecs->push_back(*spec);
}
}
// Release the DeviceManager qutex early before calling the inner method
releaseQutexEarly(DeviceManager::getInstance().qutex);
// Pass the vector to the existing function
DeviceManager::getInstance().attachAllUnattachedDevicesFromReq(
unattachedSpecs,
{context, std::bind(
&AttachAllUnattachedDevicesFromKnownListReq
::attachAllUnattachedDevicesFromKnownListReq2,
context.get(), context,
std::placeholders::_1)});
}
void attachAllUnattachedDevicesFromKnownListReq2(
[[maybe_unused]]
std::shared_ptr<AttachAllUnattachedDevicesFromKnownListReq> context,
AsynchronousLoop loop
)
{
callOriginalCb(loop);
}
};
void DeviceManager::attachAllUnattachedDevicesFromKnownListReq(
Callback<attachAllUnattachedDevicesFromReqCbFn> cb
)
{
const auto& caller = ComponentThread::getSelf();
auto request = std::make_shared<AttachAllUnattachedDevicesFromKnownListReq>(
caller, cb,
LockSet<attachAllUnattachedDevicesFromReqCbFn>::Set{
std::ref(DeviceManager::getInstance().qutex)
});
AttachAllUnattachedDevicesFromKnownListReq::LockerAndInvoker lockvoker(
*request, mrntt::mrntt.thread,
std::bind(
&AttachAllUnattachedDevicesFromKnownListReq
::attachAllUnattachedDevicesFromKnownListReq1_posted,
request.get(), request));
}
class DeviceManager::DetachAllAttachedDeviceRoles
: public PostedAsynchronousContinuation<
detachAllAttachedDeviceRolesCbFn>
{
public:
DetachAllAttachedDeviceRoles(
const unsigned int totalNSpecs,
const std::shared_ptr<ComponentThread>& caller,
Callback<detachAllAttachedDeviceRolesCbFn> cb)
: PostedAsynchronousContinuation<detachAllAttachedDeviceRolesCbFn>(
caller, cb),
loop(totalNSpecs)
{}
void detachAllAttachedDeviceRoles1_posted(
[[maybe_unused]] std::shared_ptr<DetachAllAttachedDeviceRoles> context
)
{
for (const auto& deviceRole : DeviceManager::attachedDeviceRoles)
{
DeviceManager::getInstance().detachSenseDeviceReq(
deviceRole->deviceAttachmentSpec,
{context, std::bind(
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles2,
context.get(), context,
std::placeholders::_1, std::placeholders::_2)});
}
}
void detachAllAttachedDeviceRoles2(
std::shared_ptr<DetachAllAttachedDeviceRoles> context,
bool success, std::shared_ptr<DeviceAttachmentSpec> spec
)
{
if (!success)
{
std::cerr << __func__ << ": Failed to detach device: "
<< spec->deviceIdentifier << "\n";
// Fallthrough.
}
if (!context->loop.incrementSuccessOrFailureAndTestForCompletionDueTo(
success))
{
return;
}
if (OptionParser::getOptions().verbose)
{
std::cout << __func__ << ": " << context->loop.nSucceeded.load()
<< " devices detached, "
<< context->loop.nFailed.load() << " devices failed\n";
}
context->callOriginalCb(loop);
}
public:
AsynchronousLoop loop;
};
void DeviceManager::detachAllAttachedDeviceRoles(
Callback<detachAllAttachedDeviceRolesCbFn> cb
)
{
if (DeviceManager::getInstance().attachedDeviceRoles.size() == 0)
{
AsynchronousLoop tmp(0);
cb.callbackFn(tmp);
return;
}
const auto& caller = ComponentThread::getSelf();
auto request = std::make_shared<DetachAllAttachedDeviceRoles>(
DeviceManager::getInstance().attachedDeviceRoles.size(),
caller, std::move(cb));
mrntt::mrntt.thread->getIoService().post(
std::bind(
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted,
request.get(), request));
}
void DeviceManager::initializeDeviceReattacher()
{
deviceReattacher = std::make_unique<DeviceReattacher>(
*this, mrntt::mrntt.thread);
deviceReattacher->start();
}
void DeviceManager::finalizeDeviceReattacher()
{
if (!deviceReattacher) { return; }
deviceReattacher->stop();
deviceReattacher.reset();
} }
} // namespace device } // namespace device
@@ -0,0 +1,82 @@
#include <config.h>
#include <iostream>
#include <functional>
#include <componentThread.h>
#include <callback.h>
#include <deviceManager/deviceReattacher.h>
#include <deviceManager/deviceManager.h>
namespace smo {
namespace device {
static void reattachmentCb(AsynchronousLoop& results)
{
if (results.nTotal == 0) { return; }
std::cout << "DeviceReattacher: Successfully reattached "
<< results.nSucceeded << " of " << results.nTotal << " devices"
<< std::endl;
}
DeviceReattacher::DeviceReattacher(
DeviceManager& parent, std::shared_ptr<ComponentThread> ioThread)
: parent(parent), ioThread(ioThread), shouldContinue(false),
timer(ioThread->getIoService())
{
}
void DeviceReattacher::start()
{
shouldContinue.store(true);
scheduleNextTimeout();
}
void DeviceReattacher::stop()
{
shouldContinue.store(false);
timer.cancel();
}
void DeviceReattacher::scheduleNextTimeout()
{
if (!shouldContinue.load()) {
return;
}
// Schedule the next timeout using the configured period
timer.expires_from_now(
boost::posix_time::milliseconds(
CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS));
timer.async_wait(
std::bind(&DeviceReattacher::onTimeout, this, std::placeholders::_1));
}
void DeviceReattacher::onTimeout(const boost::system::error_code& error)
{
// Timer was cancelled, which is expected when stopping
if (error == boost::asio::error::operation_aborted) {
return;
}
if (error)
{
std::cerr << "DeviceReattacher: Timer error: " << error.message()
<< std::endl;
return;
}
if (!shouldContinue.load()) {
return;
}
// Attempt to reattach all unattached devices from the known list
parent.attachAllUnattachedDevicesFromKnownListReq(
{ nullptr, reattachmentCb});
// Schedule the next timeout
scheduleNextTimeout();
}
} // namespace device
} // namespace smo
+34
View File
@@ -0,0 +1,34 @@
#ifndef _BODY_COMPONENT_H
#define _BODY_COMPONENT_H
#include <component.h>
#include <functional>
#include <callback.h>
namespace smo {
class Mind;
class ComponentThread;
namespace body {
class Body
: public MindComponent
{
public:
Body(Mind &parent, const std::shared_ptr<ComponentThread> &thread);
~Body() = default;
typedef std::function<void(bool)> bodyLifetimeMgmtOpCbFn;
void initializeReq(Callback<bodyLifetimeMgmtOpCbFn> callback);
void finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback);
private:
class InitializeReq;
class FinalizeReq;
};
} // namespace body
} // namespace smo
#endif // _BODY_COMPONENT_H
+59
View File
@@ -0,0 +1,59 @@
#ifndef COMPONENT_H
#define COMPONENT_H
#include <config.h>
#include <memory>
#include <functional>
#include <callback.h>
namespace smo {
class Mind;
class ComponentThread;
class Component
{
public:
Component(const std::shared_ptr<ComponentThread> &thread);
~Component() = default;
public:
std::shared_ptr<ComponentThread> thread;
public:
};
class MindComponent
: public Component
{
public:
MindComponent(Mind &parent, const std::shared_ptr<ComponentThread> &thread);
~MindComponent() = default;
public:
Mind &parent;
};
namespace mrntt {
class MarionetteComponent
: public Component
{
public:
MarionetteComponent(const std::shared_ptr<ComponentThread> &thread);
~MarionetteComponent() = default;
public:
typedef std::function<void(bool)> mrnttLifetimeMgmtOpCbFn;
void initializeReq(Callback<mrnttLifetimeMgmtOpCbFn> callback);
void finalizeReq(Callback<mrnttLifetimeMgmtOpCbFn> callback);
private:
class MrnttLifetimeMgmtOp;
};
} // namespace mrntt
} // namespace smo
#endif // COMPONENT_H
+92 -51
View File
@@ -12,13 +12,15 @@
#include <sched.h> #include <sched.h>
#include <unistd.h> #include <unistd.h>
#include <memory> #include <memory>
#include <callback.h>
namespace smo { namespace smo {
class Mind; // Forward declaration class Mind; // Forward declaration
class MarionetteThread;
class MindThread;
class ComponentThread class ComponentThread
: public std::enable_shared_from_this<ComponentThread>
{ {
public: public:
enum ThreadId enum ThreadId
@@ -32,77 +34,39 @@ public:
N_ITEMS N_ITEMS
}; };
ComponentThread(ThreadId _id, Mind& parent) protected:
: id(_id), name(getThreadName(_id)), parent(parent), ComponentThread(ThreadId _id)
work(io_service), pause_work(pause_io_service), : id(_id), name(getThreadName(_id)),
pinnedCpuId(-1), work(io_service)
thread(
((id == MRNTT) ? marionetteMain : main),
std::ref(*this))
{} {}
public:
virtual ~ComponentThread() = default;
void cleanup(void); void cleanup(void);
boost::asio::io_service& getIoService(void) { return io_service; } boost::asio::io_service& getIoService(void) { return io_service; }
void initializeTls(void);
static const std::shared_ptr<ComponentThread> getSelf(void); static const std::shared_ptr<ComponentThread> getSelf(void);
static std::shared_ptr<MarionetteThread> getMrntt();
static std::shared_ptr<ComponentThread> getMrntt();
Mind& getParent() const { return parent; }
typedef void (mainFn)(ComponentThread &self); typedef void (mainFn)(ComponentThread &self);
static mainFn main, marionetteMain;
// Thread management methods
void startThreadReq(std::function<void()> callback = nullptr);
void exitThreadReq(std::function<void()> callback = nullptr);
void pauseThreadReq(std::function<void()> callback = nullptr);
void resumeThreadReq(std::function<void()> callback = nullptr);
/**
* JOLTs this thread to begin processing after global initialization.
*
* JOLTing is the mechanism that allows threads to enter their main
* event loops and set up TLS vars after all global constructors have
* completed. This prevents race conditions during system startup.
*/
void joltThreadReq(std::function<void()> callback = nullptr);
// CPU management methods // CPU management methods
static int getAvailableCpuCount(); static int getAvailableCpuCount();
void pinToCpu(int cpuId);
enum class ThreadOp
{
START,
PAUSE,
RESUME,
EXIT,
JOLT,
N_ITEMS
};
typedef std::function<void()> mindShutdownIndOpCbFn;
// Intentionally doesn't take a callback. // Intentionally doesn't take a callback.
void exceptionInd(ComponentThread& thread); void exceptionInd(const std::shared_ptr<ComponentThread> &faultyThread);
// Intentionally doesn't take a callback. // Intentionally doesn't take a callback.
void userShutdownInd(); void userShutdownInd();
public: public:
ThreadId id; ThreadId id;
std::string name; std::string name;
Mind &parent;
boost::asio::io_service io_service; boost::asio::io_service io_service;
boost::asio::io_service::work work; boost::asio::io_service::work work;
boost::asio::io_service pause_io_service;
boost::asio::io_service::work pause_work;
std::atomic<bool> keepLooping; std::atomic<bool> keepLooping;
int pinnedCpuId;
/* Always ensure that this is last so that the thread is spawned after
* everything else is constructed.
*/
std::thread thread;
static const std::string getThreadName(ThreadId id) static const std::string getThreadName(ThreadId id)
{ {
@@ -126,9 +90,86 @@ public:
} }
}; };
class MarionetteThread
: public std::enable_shared_from_this<MarionetteThread>,
public ComponentThread
{
public:
MarionetteThread()
: ComponentThread(MRNTT),
thread(main, std::ref(*this))
{
}
static void main(MarionetteThread& self);
void initializeTls(void);
public:
std::thread thread;
};
class MindThread
: public std::enable_shared_from_this<MindThread>, public ComponentThread
{
public:
enum class ThreadOp
{
START,
PAUSE,
RESUME,
EXIT,
JOLT,
N_ITEMS
};
MindThread(ThreadId _id, Mind& parent)
: ComponentThread(_id),
pinnedCpuId(-1),
pause_work(pause_io_service),
parent(parent),
thread(main, std::ref(*this))
{
}
static void main(MindThread& self);
void initializeTls(void);
Mind& getParent() const { return parent; }
// Thread management methods
typedef std::function<void()> threadLifetimeMgmtOpCbFn;
void startThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
void exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
void pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
void resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
/**
* JOLTs this thread to begin processing after global initialization.
*
* JOLTing is the mechanism that allows threads to enter their main
* event loops and set up TLS vars after all global constructors have
* completed. This prevents race conditions during system startup.
*/
void joltThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback);
// CPU management methods
void pinToCpu(int cpuId);
public:
int pinnedCpuId;
boost::asio::io_service pause_io_service;
boost::asio::io_service::work pause_work;
Mind& parent;
std::thread thread;
public:
class ThreadLifetimeMgmtOp;
class MindShutdownIndOp;
};
namespace mrntt { namespace mrntt {
extern std::shared_ptr<ComponentThread> mrntt; extern std::shared_ptr<MarionetteThread> thread;
} } // namespace mrntt
} // namespace smo } // namespace smo
+13 -7
View File
@@ -6,6 +6,8 @@
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <user/deviceAttachmentSpec.h> #include <user/deviceAttachmentSpec.h>
#include <deviceManager/deviceRole.h>
#include <qutex.h>
namespace smo { namespace smo {
namespace device { namespace device {
@@ -13,21 +15,25 @@ namespace device {
class Device class Device
{ {
public: public:
std::string deviceIdentifier; Device(const std::string& identifier)
std::vector<std::shared_ptr<DeviceAttachmentSpec>> deviceAttachmentSpecs; : deviceIdentifier(identifier), qutex("Device-" + identifier)
{}
Device(const std::string& identifier) : deviceIdentifier(identifier) {}
std::string stringify() const std::string stringify() const
{ {
std::ostringstream os; std::ostringstream os;
os << "Device Identifier: " << deviceIdentifier os << "Device Identifier: " << deviceIdentifier
<< ", Attachment Specs: " << deviceAttachmentSpecs.size() << std::endl; << ", Device Roles: " << deviceRoles.size() << std::endl;
for (const auto& spec : deviceAttachmentSpecs) { for (const auto& deviceRole : deviceRoles) {
os << " " << spec->stringify(); os << " " << deviceRole->deviceAttachmentSpec->stringify();
} }
return os.str(); return os.str();
} }
public:
std::string deviceIdentifier;
std::vector<std::shared_ptr<DeviceRole>> deviceRoles;
Qutex qutex;
}; };
} // namespace device } // namespace device
+75 -11
View File
@@ -7,13 +7,21 @@
#include <opts.h> #include <opts.h>
#include <utility> #include <utility>
#include <iostream> #include <iostream>
#include <user/deviceAttachmentSpec.h>
#include <deviceManager/device.h>
#include <functional> #include <functional>
#include <user/senseApiDesc.h>
#include <user/deviceAttachmentSpec.h>
#include <asynchronousLoop.h>
#include <deviceManager/device.h>
#include <deviceManager/deviceRole.h>
#include <deviceManager/deviceReattacher.h>
#include <callback.h>
#include <qutex.h>
namespace smo { namespace smo {
namespace device { namespace device {
class DeviceReattacher;
class DeviceManager class DeviceManager
{ {
public: public:
@@ -23,32 +31,88 @@ public:
return instance; return instance;
} }
void initialize(void)
{};
void finalize(void)
{};
void initializeDeviceReattacher();
void finalizeDeviceReattacher();
std::string readDapSpecFile(const std::string& filename); std::string readDapSpecFile(const std::string& filename);
void collateAllDapSpecs(void); void collateAllDapSpecs(void);
void parseAllDapSpecs(void); void parseAllDapSpecs(void);
static const std::string stringifyDeviceSpecs(void); static const std::string stringifyDeviceSpecs(void);
// New async function for device attachment typedef std::function<void(
bool success, std::shared_ptr<DeviceRole> deviceRole,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec)>
newDeviceAttachmentSpecIndCbFn;
typedef std::function<void(
bool success, std::shared_ptr<DeviceAttachmentSpec> deviceSpec)>
removeDeviceAttachmentSpecReqCbFn;
void newDeviceAttachmentSpecInd( void newDeviceAttachmentSpecInd(
std::shared_ptr<DeviceAttachmentSpec> spec, const DeviceAttachmentSpec &spec,
std::function<void(bool success, std::shared_ptr<Device> device, std::shared_ptr<DeviceAttachmentSpec> deviceSpec)> callback); Callback<newDeviceAttachmentSpecIndCbFn> callback);
void removeDeviceAttachmentSpecReq(
const DeviceAttachmentSpec &spec,
Callback<removeDeviceAttachmentSpecReqCbFn> callback);
// Device attachment/detachment methods moved from SenseApiManager
typedef sense_api::sal_mlo_attachDeviceReqCbFn attachSenseDeviceReqCbFn;
typedef sense_api::sal_mlo_detachDeviceReqCbFn detachSenseDeviceReqCbFn;
void attachSenseDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec,
Callback<attachSenseDeviceReqCbFn> cb);
void detachSenseDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec,
Callback<detachSenseDeviceReqCbFn> cb);
typedef std::function<void(AsynchronousLoop &results)>
attachAllUnattachedDevicesFromReqCbFn;
typedef std::function<void(AsynchronousLoop &results)>
detachAllAttachedDeviceRolesCbFn;
void attachAllUnattachedDevicesFromReq(
const std::shared_ptr<std::vector<DeviceAttachmentSpec>> &specs,
Callback<attachAllUnattachedDevicesFromReqCbFn> cb);
void attachAllUnattachedDevicesFromKnownListReq(
Callback<attachAllUnattachedDevicesFromReqCbFn> cb);
void attachAllUnattachedDevicesFromCmdlineReq(
Callback<attachAllUnattachedDevicesFromReqCbFn> cb);
void detachAllAttachedDeviceRoles(
Callback<detachAllAttachedDeviceRolesCbFn> cb);
private: private:
DeviceManager() = default; DeviceManager()
~DeviceManager() = default; : qutex("DeviceManager"), deviceReattacher(nullptr)
{}
~DeviceManager();
DeviceManager(const DeviceManager&) = delete; DeviceManager(const DeviceManager&) = delete;
DeviceManager& operator=(const DeviceManager&) = delete; DeviceManager& operator=(const DeviceManager&) = delete;
public: public:
Qutex qutex;
std::string allDapSpecs; std::string allDapSpecs;
static std::vector<std::shared_ptr<InteroceptorDevAttachmentSpec>>
interoceptorDeviceSpecs;
static std::vector<std::shared_ptr<ExtrospectorDevAttachmentSpec>>
extrospectorDeviceSpecs;
static std::vector<std::shared_ptr<DeviceAttachmentSpec>> static std::vector<std::shared_ptr<DeviceAttachmentSpec>>
deviceAttachmentSpecs; deviceAttachmentSpecs;
static std::vector<std::shared_ptr<Device>> devices; static std::vector<std::shared_ptr<Device>> devices;
static std::vector<std::shared_ptr<DeviceRole>> attachedDeviceRoles;
static std::vector<DeviceAttachmentSpec> commandLineDASpecs;
private:
std::unique_ptr<DeviceReattacher> deviceReattacher;
class NewDeviceAttachmentSpecInd;
class RemoveDeviceAttachmentSpecReq;
class AttachSenseDeviceReq;
typedef AttachSenseDeviceReq DetachSenseDeviceReq;
class AttachAllUnattachedDevicesFromReq;
class AttachAllUnattachedDevicesFromKnownListReq;
class DetachAllAttachedDeviceRoles;
}; };
} // namespace device } // namespace device
@@ -0,0 +1,44 @@
#ifndef DEVICEREATTACHER_H
#define DEVICEREATTACHER_H
#include <atomic>
#include <memory>
#include <boost/asio.hpp>
namespace smo {
class ComponentThread;
namespace device {
class DeviceManager;
class DeviceReattacher
{
public:
DeviceReattacher(
DeviceManager& parent, std::shared_ptr<ComponentThread> ioThread);
~DeviceReattacher() = default;
// Non-copyable
DeviceReattacher(const DeviceReattacher&) = delete;
DeviceReattacher& operator=(const DeviceReattacher&) = delete;
// Control methods
void start();
void stop();
private:
void scheduleNextTimeout();
void onTimeout(const boost::system::error_code& error);
DeviceManager &parent;
std::shared_ptr<ComponentThread> ioThread;
std::atomic<bool> shouldContinue;
boost::asio::deadline_timer timer;
};
} // namespace device
} // namespace smo
#endif // DEVICEREATTACHER_H
@@ -0,0 +1,28 @@
#ifndef DEVICEROLE_H
#define DEVICEROLE_H
#include <memory>
#include <user/deviceAttachmentSpec.h>
namespace smo {
namespace device {
class Device; // Forward declaration
class DeviceRole
{
public:
DeviceRole(
Device& parentDevice,
std::shared_ptr<DeviceAttachmentSpec>& spec)
: parentDevice(parentDevice), deviceAttachmentSpec(spec)
{}
Device& parentDevice;
std::shared_ptr<DeviceAttachmentSpec> deviceAttachmentSpec;
};
} // namespace device
} // namespace smo
#endif // DEVICEROLE_H
+12 -2
View File
@@ -2,15 +2,25 @@
#define DIRECTOR_H #define DIRECTOR_H
#include <config.h> #include <config.h>
#include <component.h>
#include <goal.h> #include <goal.h>
#include <lruLifo.h> #include <lruLifo.h>
namespace smo { namespace smo {
class Mind;
class ComponentThread;
namespace director { namespace director {
class Director { class Director
: public MindComponent
{
public: public:
Director() = default; Director(Mind &parent, const std::shared_ptr<ComponentThread> &thread)
: MindComponent(parent, thread)
{}
~Director() = default; ~Director() = default;
/** EXPLANATION: /** EXPLANATION:
-17
View File
@@ -1,17 +0,0 @@
#ifndef _BODY_H
#define _BODY_H
namespace mrntt {
namespace body {
class Body
{
public:
Body() = default;
~Body() = default;
};
} // namespace body
} // namespace mrntt
#endif // _BODY_H
+17
View File
@@ -0,0 +1,17 @@
#ifndef _MRNTT_BODYMAP_BODY_H
#define _MRNTT_BODYMAP_BODY_H
namespace mrntt {
namespace bodyMap {
class Body
{
public:
Body() = default;
~Body() = default;
};
} // namespace bodyMap
} // namespace mrntt
#endif // _MRNTT_BODYMAP_BODY_H
@@ -1,12 +1,12 @@
#ifndef MRNTT_BODY_BODYMAP_H #ifndef MRNTT_BODYMAP_BODYMAP_H
#define MRNTT_BODY_BODYMAP_H #define MRNTT_BODYMAP_BODYMAP_H
#include <set> #include <set>
#include <cstdint> #include <cstdint>
#include <body/limb.h> #include <bodyMap/limb.h>
namespace mrntt { namespace mrntt {
namespace body { namespace bodyMap {
class BodyMap { class BodyMap {
public: public:
@@ -17,7 +17,7 @@ public:
std::set<uint32_t, Limb> limbs; std::set<uint32_t, Limb> limbs;
}; };
} // namespace body } // namespace bodyMap
} // namespace mrntt } // namespace mrntt
#endif // MRNTT_BODY_BODYMAP_H #endif // MRNTT_BODYMAP_BODYMAP_H
@@ -1,14 +1,14 @@
#ifndef MRNTT_BODY_BODYMESSAGE_H #ifndef MRNTT_BODYMAP_BODYMESSAGE_H
#define MRNTT_BODY_BODYMESSAGE_H #define MRNTT_BODYMAP_BODYMESSAGE_H
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
#include <body/limb.h> #include <bodyMap/limb.h>
#include <body/part.h> #include <bodyMap/part.h>
namespace mrntt { namespace mrntt {
namespace body { namespace bodyMap {
class BodyMessage class BodyMessage
{ {
@@ -60,7 +60,7 @@ public:
const Part& part; const Part& part;
}; };
} // namespace body } // namespace bodyMap
} // namespace mrntt } // namespace mrntt
#endif // MRNTT_BODY_BODYMESSAGE_H #endif // MRNTT_BODYMAP_BODYMESSAGE_H
@@ -1,14 +1,14 @@
#ifndef MRNTT_BODY_LIMB_H #ifndef MRNTT_BODYMAP_LIMB_H
#define MRNTT_BODY_LIMB_H #define MRNTT_BODYMAP_LIMB_H
#include <string> #include <string>
#include <set> #include <set>
#include <cstdint> #include <cstdint>
#include <body/part.h> #include <bodyMap/part.h>
namespace mrntt { namespace mrntt {
namespace body { namespace bodyMap {
class Limb class Limb
{ {
@@ -28,7 +28,7 @@ public:
std::set<uint32_t, Part> parts; std::set<uint32_t, Part> parts;
}; };
} // namespace body } // namespace bodyMap
} // namespace mrntt } // namespace mrntt
#endif // MRNTT_BODY_LIMB_H #endif // MRNTT_BODYMAP_LIMB_H
@@ -1,13 +1,13 @@
#ifndef _BODY_MAP_H #ifndef _BODYMAP_MAP_H
#define _BODY_MAP_H #define _BODYMAP_MAP_H
#include <set> #include <set>
#include <cstdint> #include <cstdint>
#include <body/limb.h> #include <bodyMap/limb.h>
namespace mrntt { namespace mrntt {
namespace body { namespace bodyMap {
class BodyMap { class BodyMap {
public: public:
@@ -17,7 +17,7 @@ public:
std::set<uint32_t, Limb> limbs; std::set<uint32_t, Limb> limbs;
}; };
} // namespace body } // namespace bodyMap
} // namespace mrntt } // namespace mrntt
#endif // _BODY_MAP_H #endif // _BODYMAP_MAP_H
@@ -1,5 +1,5 @@
#ifndef BODYPART_H #ifndef BODYMAP_PART_H
#define BODYPART_H #define BODYMAP_PART_H
#include <cstdint> #include <cstdint>
#include <string> #include <string>
@@ -8,7 +8,7 @@
#include <sensors/interoceptor.h> #include <sensors/interoceptor.h>
namespace mrntt { namespace mrntt {
namespace body { namespace bodyMap {
class Spot class Spot
{ {
@@ -42,7 +42,7 @@ public:
std::set<uint32_t, Spot> spots; std::set<uint32_t, Spot> spots;
}; };
} // namespace body } // namespace bodyMap
} // namespace mrntt } // namespace mrntt
#endif // BODYPART_H #endif // BODYMAP_PART_H
+8 -5
View File
@@ -3,17 +3,20 @@
#include <cstdint> #include <cstdint>
#include <atomic> #include <atomic>
#include <memory>
#include <componentThread.h>
#include <component.h>
namespace smo { namespace smo {
class MarionetteThread;
namespace mrntt { namespace mrntt {
extern std::atomic<int> exitCode; extern std::atomic<int> exitCode;
class Marionette
{
};
void exitMarionetteLoop(); void exitMarionetteLoop();
void marionetteFinalizeReqCb(bool success);
extern mrntt::MarionetteComponent mrntt;
} // namespace mrntt } // namespace mrntt
+35 -19
View File
@@ -7,10 +7,13 @@
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
#include <string> #include <string>
#include <callback.h>
#include <component.h>
#include <componentThread.h>
#include <director/director.h> #include <director/director.h>
#include <simulator/simulator.h> #include <simulator/simulator.h>
#include <componentThread.h> #include <body/body.h>
namespace smo { namespace smo {
@@ -20,37 +23,45 @@ public:
Mind(void); Mind(void);
~Mind(void) = default; ~Mind(void) = default;
void initialize(void); typedef std::function<void(bool)> mindLifetimeMgmtOpCbFn;
void execute(void); void initializeReq(Callback<mindLifetimeMgmtOpCbFn> callback);
void finalizeReq(std::function<void()> callback); void finalizeReq(Callback<mindLifetimeMgmtOpCbFn> callback);
// ComponentThread access methods // ComponentThread access methods
std::shared_ptr<ComponentThread> getComponentThread( std::shared_ptr<MindThread> getComponentThread(
ComponentThread::ThreadId id) const; ComponentThread::ThreadId id) const;
std::shared_ptr<ComponentThread> getComponentThread( std::shared_ptr<MindThread> getComponentThread(
const std::string& name) const; const std::string& name) const;
// Get all this Mind's component threads. // Get all this Mind's component threads.
std::vector<std::shared_ptr<ComponentThread>> getMindThreads() const; std::vector<std::shared_ptr<MindThread>> getMindThreads() const;
// Thread management methods (moved from ComponentThread) // Thread management methods (moved from ComponentThread)
void startAllMindThreadsReq(std::function<void()> callback = nullptr); typedef std::function<void()> mindThreadLifetimeMgmtOpCbFn;
void pauseAllMindThreadsReq(std::function<void()> callback = nullptr); void joltAllMindThreadsReq(Callback<mindThreadLifetimeMgmtOpCbFn> callback);
void resumeAllMindThreadsReq(std::function<void()> callback = nullptr); void startAllMindThreadsReq(
void exitAllMindThreadsReq(std::function<void()> callback = nullptr); Callback<mindThreadLifetimeMgmtOpCbFn> callback);
void joltAllMindThreadsReq(std::function<void()> callback = nullptr); void pauseAllMindThreadsReq(
Callback<mindThreadLifetimeMgmtOpCbFn> callback);
void resumeAllMindThreadsReq(
Callback<mindThreadLifetimeMgmtOpCbFn> callback);
void exitAllMindThreadsReq(Callback<mindThreadLifetimeMgmtOpCbFn> callback);
// CPU distribution method // CPU distribution method
void distributeAndPinThreadsAcrossCpus(); void distributeAndPinThreadsAcrossCpus();
public: private:
std::thread directorThread; // Collection of ComponentThread instances (excluding marionette)
std::thread simulatorThread; std::vector<std::shared_ptr<MindThread>> componentThreads;
std::thread subconsciousThread;
public:
director::Director director; director::Director director;
simulator::Simulator canvas; simulator::Simulator canvas;
MindComponent subconscious;
body::Body body;
MindComponent world;
private: private:
friend class body::Body;
/** /**
* Indicates whether all mind threads have been JOLTed at least once. * Indicates whether all mind threads have been JOLTed at least once.
* *
@@ -70,13 +81,18 @@ private:
* This flag ensures that JOLTing happens exactly once and provides * This flag ensures that JOLTing happens exactly once and provides
* a synchronization point for the entire system initialization. * a synchronization point for the entire system initialization.
*/ */
bool threadsHaveBeenJolted = false; bool threadsHaveBeenJolted = false,
// Collection of ComponentThread instances (excluding marionette) bodyComponentInitialized = false;
std::vector<std::shared_ptr<ComponentThread>> componentThreads;
private:
class MindLifetimeMgmtOp;
class MindThreadLifetimeMgmtOp;
}; };
namespace mind {
// Global Mind instance will be defined in marionette.cpp // Global Mind instance will be defined in marionette.cpp
extern std::shared_ptr<Mind> globalMind; extern std::shared_ptr<Mind> globalMind;
} // namespace mind
} // namespace smo } // namespace smo
+46
View File
@@ -0,0 +1,46 @@
#ifndef _SMO_MIND_MANAGER_H
#define _SMO_MIND_MANAGER_H
#include <config.h>
#include <memory>
#include <vector>
#include <mind.h>
namespace smo {
namespace mind {
/** EXPLANATION:
* MindManager is responsible for managing the lifecycle of all minds.
* It is responsible for creating, destroying, and managing the minds.
*
* For now it does nothing since we haven't yet added support for multiple
* minds.
*/
class MindManager
{
public:
MindManager(void) = default;
~MindManager(void) = default;
static MindManager& getInstance()
{
static MindManager instance;
return instance;
}
public:
void initialize(void) {};
void finalize(void) {};
std::shared_ptr<Mind> getMind(void) const;
void addMind(const std::shared_ptr<Mind>& mind);
void removeMind(const std::shared_ptr<Mind>& mind);
public:
std::vector<std::shared_ptr<Mind>> minds;
};
} // namespace mind
} // namespace smo
#endif // _SMO_MIND_MANAGER_H
+4
View File
@@ -3,6 +3,8 @@
#include <quale.h> #include <quale.h>
namespace smo {
class PleasurableQuale class PleasurableQuale
: public NonNeutralQuale : public NonNeutralQuale
{ {
@@ -17,4 +19,6 @@ public:
virtual void eventInd(void); virtual void eventInd(void);
}; };
} // namespace smo
#endif #endif
+3 -1
View File
@@ -2,8 +2,10 @@
#define _QUALE_H #define _QUALE_H
#include <cstdint> #include <cstdint>
#include <mentalEntity.h>
#include <mentenon.h>
#include <attentionTrigger.h> #include <attentionTrigger.h>
#include <implexa.h> #include <implex/implix.h>
namespace smo { namespace smo {
+4
View File
@@ -1,6 +1,10 @@
#ifndef _SALMANOFF_H #ifndef _SALMANOFF_H
#define _SALMANOFF_H #define _SALMANOFF_H
#include <memory>
#include <functional>
#include <componentThread.h>
namespace smo { namespace smo {
void initializeSalmanoff(void); void initializeSalmanoff(void);
+5 -1
View File
@@ -3,10 +3,12 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <atomic>
#include <vector> #include <vector>
#include <dlfcn.h> #include <dlfcn.h>
#include <functional> #include <functional>
#include <user/senseApiDesc.h> #include <user/senseApiDesc.h>
#include <qutex.h>
namespace smo { namespace smo {
namespace sense_api { namespace sense_api {
@@ -29,7 +31,7 @@ public:
SenseApiLib( SenseApiLib(
const std::string& path, void *_dlopen_handle, const std::string& path, void *_dlopen_handle,
SMO_GET_SENSE_API_DESC_FN_TYPEDEF *descFn) SMO_GET_SENSE_API_DESC_FN_TYPEDEF *descFn)
: libraryPath(path), : libraryPath(path), qutex("SenseApiLib-" + path), isBeingDestroyed(false),
dlopen_handle(_dlopen_handle, DlCloser()), dlopen_handle(_dlopen_handle, DlCloser()),
SMO_GET_SENSE_API_DESC_FN_NAME(descFn) SMO_GET_SENSE_API_DESC_FN_NAME(descFn)
{} {}
@@ -48,6 +50,8 @@ public:
public: public:
std::string libraryPath; std::string libraryPath;
Qutex qutex;
std::atomic<bool> isBeingDestroyed;
std::unique_ptr<void, DlCloser> dlopen_handle; std::unique_ptr<void, DlCloser> dlopen_handle;
/* UNIMPLEMENTED: API-specific cmdline options. These affect this specific /* UNIMPLEMENTED: API-specific cmdline options. These affect this specific
* sense api lib's behaviour globally. * sense api lib's behaviour globally.
+22 -10
View File
@@ -7,8 +7,12 @@
#include <string> #include <string>
#include <optional> #include <optional>
#include <functional> #include <functional>
#include <componentThread.h>
#include <asynchronousLoop.h>
#include <senseApis/senseApiLib.h> #include <senseApis/senseApiLib.h>
#include <user/deviceAttachmentSpec.h> #include <user/deviceAttachmentSpec.h>
#include <callback.h>
#include <qutex.h>
namespace smo { namespace smo {
namespace sense_api { namespace sense_api {
@@ -22,7 +26,15 @@ public:
return instance; return instance;
} }
SenseApiLib& loadSenseApiLib(const std::string& libraryPath); void initialize(void)
{};
void finalize(void)
{};
SenseApiLib& loadSenseApiLib(
const std::string& libraryPath,
const std::shared_ptr<ComponentThread>& componentThread);
std::optional<std::shared_ptr<SenseApiLib>> getSenseApiLib( std::optional<std::shared_ptr<SenseApiLib>> getSenseApiLib(
const std::string& libraryPath); const std::string& libraryPath);
std::optional<std::shared_ptr<SenseApiLib>> getSenseApiLibByApiName( std::optional<std::shared_ptr<SenseApiLib>> getSenseApiLibByApiName(
@@ -32,22 +44,19 @@ public:
void initializeSenseApiLib(SenseApiLib& lib); void initializeSenseApiLib(SenseApiLib& lib);
void finalizeSenseApiLib(SenseApiLib& lib); void finalizeSenseApiLib(SenseApiLib& lib);
void loadAllSenseApiLibsFromOptions(void); void loadAllSenseApiLibsFromOptions(
const std::shared_ptr<ComponentThread>& componentThread);
void unloadAllSenseApiLibs(void); void unloadAllSenseApiLibs(void);
void initializeAllSenseApiLibs(void); void initializeAllSenseApiLibs(void);
void finalizeAllSenseApiLibs(void); void finalizeAllSenseApiLibs(void);
void attachAllSenseDevicesFromSpecs(void);
void attachSenseDevice(
const std::shared_ptr<device::DeviceAttachmentSpec>& spec);
void detachSenseDevice(
const std::shared_ptr<device::DeviceAttachmentSpec>& spec);
void detachAllSenseDevices(void);
std::string stringifyLibs() const; std::string stringifyLibs() const;
private: private:
SenseApiManager() = default; SenseApiManager()
: qutex("SenseApiManager")
{}
~SenseApiManager() = default; ~SenseApiManager() = default;
SenseApiManager(const SenseApiManager&) = delete; SenseApiManager(const SenseApiManager&) = delete;
@@ -55,6 +64,9 @@ private:
std::vector<std::shared_ptr<SenseApiLib>> senseApiLibs; std::vector<std::shared_ptr<SenseApiLib>> senseApiLibs;
public:
Qutex qutex;
public: public:
static std::optional<std::string> searchForLibInSmoSearchPaths( static std::optional<std::string> searchForLibInSmoSearchPaths(
const std::string& libraryPath); const std::string& libraryPath);
+12 -2
View File
@@ -2,14 +2,24 @@
#define SIMULATOR_H #define SIMULATOR_H
#include <config.h> #include <config.h>
#include <component.h>
#include <simulator/scene.h> #include <simulator/scene.h>
namespace smo { namespace smo {
class Mind;
class ComponentThread;
namespace simulator { namespace simulator {
class Simulator { class Simulator
: public MindComponent
{
public: public:
Simulator() = default; Simulator(Mind &parent, const std::shared_ptr<ComponentThread> &thread)
: MindComponent(parent, thread)
{}
~Simulator() = default; ~Simulator() = default;
void initialize(); void initialize();
+5
View File
@@ -0,0 +1,5 @@
#include <lockerAndInvokerBase.h>
namespace smo {
} // namespace smo
-13
View File
@@ -1,13 +0,0 @@
add_library(marionette STATIC
marionette.cpp
salmanoff.cpp
)
target_link_libraries(marionette
smocore
)
target_include_directories(marionette PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/../include
)
+140
View File
@@ -0,0 +1,140 @@
#include <iostream>
#include <asynchronousContinuation.h>
#include <asynchronousLoop.h>
#include <callback.h>
#include <component.h>
#include <componentThread.h>
#include <deviceManager/deviceManager.h>
#include <mindManager/mindManager.h>
#include <marionette/marionette.h>
namespace smo {
namespace mrntt {
class MarionetteComponent::MrnttLifetimeMgmtOp
: public PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>
{
public:
MrnttLifetimeMgmtOp(
MarionetteComponent &parent, const std::shared_ptr<ComponentThread> &caller,
Callback<mrnttLifetimeMgmtOpCbFn> callback)
: PostedAsynchronousContinuation<mrnttLifetimeMgmtOpCbFn>(
caller, callback),
parent(parent)
{}
private:
MarionetteComponent &parent;
public:
void initializeReq1_posted(
[[maybe_unused]] std::shared_ptr<MrnttLifetimeMgmtOp> context
)
{
auto self = ComponentThread::getSelf();
if (self->id != ComponentThread::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Marionette thread");
}
smo::mind::globalMind = std::make_shared<Mind>();
smo::mind::globalMind->initializeReq({context, std::bind(
&MrnttLifetimeMgmtOp::initializeReq2,
this, context, std::placeholders::_1)});
}
void initializeReq2(
std::shared_ptr<MrnttLifetimeMgmtOp> context,
bool success
)
{
if (!success)
{
std::cerr << __func__ << ": Failed to initialize globalMind"
<< std::endl;
context->callOriginalCb(false);
return;
}
device::DeviceManager::getInstance().initializeDeviceReattacher();
context->callOriginalCb(success);
}
void finalizeReq1_posted(
[[maybe_unused]] std::shared_ptr<MrnttLifetimeMgmtOp> context
)
{
auto self = ComponentThread::getSelf();
if (self->id != ComponentThread::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Marionette thread");
}
device::DeviceManager::getInstance().finalizeDeviceReattacher();
smo::mind::globalMind->finalizeReq({context, std::bind(
&MrnttLifetimeMgmtOp::finalizeReq2,
this, context, std::placeholders::_1)});
}
void finalizeReq2(
std::shared_ptr<MrnttLifetimeMgmtOp> context,
bool success
)
{
if (!success)
{
std::cerr << __func__ << ": globalMind finalization failed"
<< std::endl;
context->callOriginalCb(false);
return;
}
context->callOriginalCb(success);
}
};
void MarionetteComponent::initializeReq(
Callback<mrnttLifetimeMgmtOpCbFn> callback)
{
auto mrntt = ComponentThread::getSelf();
if (mrntt->id != ComponentThread::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Marionette thread");
}
auto request = std::make_shared<MrnttLifetimeMgmtOp>(
*this, mrntt, callback);
mrntt->getIoService().post(
std::bind(
&MrnttLifetimeMgmtOp::initializeReq1_posted,
request.get(), request));
}
void MarionetteComponent::finalizeReq(
Callback<mrnttLifetimeMgmtOpCbFn> callback)
{
auto mrntt = ComponentThread::getSelf();
if (mrntt->id != ComponentThread::MRNTT)
{
throw std::runtime_error(std::string(__func__)
+ ": Must be executed on Marionette thread");
}
auto request = std::make_shared<MrnttLifetimeMgmtOp>(
*this, mrntt, callback);
mrntt->getIoService().post(
std::bind(
&MrnttLifetimeMgmtOp::finalizeReq1_posted,
request.get(), request));
}
} // namespace mrntt
} // namespace smo

Some files were not shown because too many files have changed in this diff Show More