301 Commits

Author SHA1 Message Date
hayodea 1d3667ef23 Add AGENTS.md 2026-04-01 21:55:53 -04:00
hayodea 17c0e10be8 Salmanoff: Version increment to v0.01.001 2025-11-23 07:35:21 -04:00
hayodea 9f839df36a Docs: Document ambience stimbuff and high-val param 2025-11-23 07:34:59 -04:00
hayodea 601c7857f4 VSCode: don't display inline greyed out hints 2025-11-23 07:28:04 -04:00
hayodea 0c2a14434b livoxGen1:OCl:collate: cast comparison to float 2025-11-23 07:25:53 -04:00
hayodea ce690bc3f4 PcloudStimProducer,OClCollMeshEngn: Produce ambience stim feature
The collation kernel now also produces the ambience stim feature
values into the ambience stimbuff frames.
2025-11-23 07:20:55 -04:00
hayodea e689063a8c StimFrame: Store ringbuff index as member var
Now each StimFrame knows its index within its parent SpMcRingbuff
object.
2025-11-23 06:15:54 -04:00
hayodea f57236530d OClCollMeshEngn: print intensities from intensity stimframes 2025-11-23 06:07:37 -04:00
hayodea 79df8b3f74 OClCollMeshEngn,PcloudStimProd: Produce into intensity stimbuff
PcloudStimulusBuffer::produceFrameReq():
Now correctly produces into the stim frames for the
PcloudIntensityStimulusBuffer object that's attached to the
PcloudStimulusProducer. If there's no attached I stimbuff, then
the OpenCL kernel will simply not write out the intensity data.

This is the first moment when we actually use the SP-MC ringbuffer
properly and actually cycle through the frames, producing into
them one by one.
2025-11-23 05:57:20 -04:00
hayodea a025d13fce CMake: Add support got clangd in cursor 2025-11-23 04:05:18 -04:00
hayodea 2c891bd2f3 Mrntt: Re-add exceptionInd
This now ensures that finalizeReq is indeed called from mrntt,
since exception-experiencing threads will post an exceptionInd
to mrntt, which will then call finalizeReq.
2025-11-23 03:27:18 -04:00
hayodea 3747dee8a7 CPack: Target ubuntu; deb now works in gdebi 2025-11-20 22:18:05 -04:00
hayodea 9ce1ced92d PcloudStimBuff,IoUringAssmEngn: add frame assembly perf profiling
We now time the frame assembly sequence.
2025-11-20 03:26:43 -04:00
hayodea 9e64c510cc SpMcRingBuff: Add getNextIndexForProducer/abortProduction
These two methods form the core of the SpMcRingbuffer's wrap-around
behaviour.
2025-11-20 03:06:35 -04:00
hayodea 9d9644cb31 PCloudStimBuff: Call stop=>start in destroyAttachedStimBuff
This ensures that we can avoid races when adding and removing
stimbuffs to a stimproducer.

At least in theory. I can think of some ways in which this current
design may result in races or other bad conditions.
2025-11-20 02:18:25 -04:00
hayodea 1bf0a195aa PcloudStimBuff: call stop=>start when adding new StimBuff 2025-11-20 02:10:11 -04:00
hayodea e233dc51d6 OClCollMeshEngn: hide StagingBuffer's firstSlotOffset 2025-11-20 01:48:59 -04:00
hayodea 51d2a70a3f StimProducer: add destroyAttachedStimulusBuffer virtual method
Implemented in base class and in derived class
PcloudStimulusProducer.
2025-11-20 01:25:46 -04:00
hayodea ee6405048a OClCollMeshEngn: use the central ComputeMgr APIs
We no longer create our own context and get our own OpenCL device
in OClCollMeshEngn::setup. We now request a device from the central
ComputeManager.
2025-11-20 00:55:19 -04:00
hayodea 2c7e090ef1 Move ClBuffer/ComputeDevice methods into libattachmentSupport 2025-11-20 00:53:28 -04:00
hayodea 0cfb0a9c07 StagingBuffer: Large slots should be aligned to alignment
Slots whose stride size is larger than the slot alignment value
should have their size rounded up to the alignment size so that
the slots that follow them will also be aligned.
2025-11-20 00:03:50 -04:00
hayodea 5789a31e23 StagingBuffer: add OpenCL buff handles to StimFrames 2025-11-19 23:43:17 -04:00
hayodea 27b43c6686 Add ComputeManager; add SmoHooks for getting ClDevices, buffers
We added a new centralized OpenCL Compute manager. This can later
be extended to support CUDA, SyCL, etc. SMO can be configured at
build time to choose which API it will use for compute.

Moreover, the ComputeMgr allows us to register buffers which are
available to all cl_contexts.
2025-11-19 22:34:25 -04:00
hayodea a910909ad5 Tests: Add test for StagingBuffer 2025-11-19 03:12:43 -04:00
hayodea 41b8385cb2 StimBuff: Use a single StagingBuffer for all StimFrames
We now allocate all the stimFrames for a StimBuffer using a
single StagingBuffer. This gives us all the benefits we're
looking for (pinning, alignment, etc).
2025-11-19 03:11:20 -04:00
hayodea 3f04d1b387 Stimulus[Buffer|Frame]: initial impl, unoptimized for mem use 2025-11-16 16:09:35 -04:00
hayodea a4493b26a1 Move/RN computeNSlotsPerDgram to Device::getNSlotsPerDgram 2025-11-16 12:37:25 -04:00
hayodea a18fab04a5 livoxGen1:Add openClIntensityConstraints to prep for StimBuff impl 2025-11-16 04:54:13 -04:00
hayodea f919385088 DASpec:genericize synonymous param parsing 2025-11-16 04:46:42 -04:00
hayodea 3bcb83894b livoxGen1:PcloudStimProd: move qualeApi param parsing here 2025-11-16 04:38:25 -04:00
hayodea 44435c61eb Formatting 2025-11-16 02:40:37 -04:00
hayodea af5046c933 Devices: Avia0: add mesh, pcloudIntensity and pcloudAmbience qualeIfaceApis 2025-11-16 02:34:33 -04:00
hayodea c5ed453bb4 StimBuff: Make virtual so we can dynamic_cast in getOrCreateStimBuff 2025-11-16 02:23:53 -04:00
hayodea addd2e275d livoxGen1: Rn PcloudIStimulusBuffer=>PcloudIntensityStimulusBuffer 2025-11-16 00:31:47 -04:00
hayodea 336bc52a9d livoxGen1: Rename PcloudXyzStimulusBuffer=>MeshStimulusBuffer 2025-11-16 00:10:33 -04:00
hayodea c060463e82 Gitignore: ignore .tmp files 2025-11-16 00:03:18 -04:00
hayodea 1f7c7f5f28 livoxGen1: n-dgrams-per-frame default value 30=>84 2025-11-15 22:22:08 -04:00
hayodea bed10df499 livoxGen1: add n-dgrams-per-frame param 2025-11-15 22:12:48 -04:00
hayodea b3743560bb IoUringAssmEngn: detect assembly end condition w/eventfdDesc validity
We can simplify and universalize the logic here by acknowledging that
assemblyCycleComplete() will always destroy the current eventfdDesc
object, so we can just check that to see whether we should continue
the assembly cycle.
2025-11-15 22:02:30 -04:00
hayodea 8e48ce6ceb stagingBuffer: rename nDgramsPerFrame=>nSlots 2025-11-15 21:29:59 -04:00
hayodea d277c29394 Move StagingBuffer+FrameAssemblyDesc into libattachmentSupport
This is in preparation for using StagingBuffer to implement
StimulusFrame and StimulusBuffer.
2025-11-15 20:47:40 -04:00
hayodea 2d1c026cc2 livoxGen1: Add correctness checks to attachDeviceReq 2025-11-15 19:46:24 -04:00
hayodea 340604c4ea Avia: update qualeIface to mesh 2025-11-15 16:21:48 -04:00
hayodea 2632917c63 livoxGen1: Execute delays on lib's assigned CompThread 2025-11-15 15:59:34 -04:00
hayodea 7a51f02d97 livoxGen1: Implement StimBuff add/del from StimProducers
There seems to be a bug where two or more stimProducers
or stimBuffs get initialized at once but we can deal with that
tomorrow.
2025-11-15 04:02:25 -04:00
hayodea e215e78aa5 StimulusBuffer should take ref to parent; not sh_ptr to common instance 2025-11-15 01:15:57 -04:00
hayodea 188b09319c livoxProto1: Rn Device::nAttachedStimBuffs=>nAttachedStimulusProducers
More semantically precise name.
2025-11-15 00:56:20 -04:00
hayodea 475f67d36e Todo: update 2025-11-15 00:08:35 -04:00
hayodea 16b51a3b66 Rename PcloudDataProducer=>PcloudStimulusProducer 2025-11-14 23:50:31 -04:00
hayodea 7d86ecadc4 livoxGen1: Rn attachedDataProducers=>attachedStimulusProducers
Also compare producers only by device selector and not by the rest
of their stored DASpec.
2025-11-14 23:26:13 -04:00
hayodea 98a493a8a1 livoxGen1: Add StimBuffs to PcloudStimProd
* PcloudStimulusProducer now has member sh_ptr<StimulusBuffer>s.
* StimulusProducer now has a vector<sh_ptr<StimulusBuffer>s.

Created new stimbuff-type-specific
Pcloud[Xyz|I|Ambience]StimulusBuffer classes for representing each
stim feature exposed by livoxGen1's PcloudStimulusProducer.
2025-11-14 23:19:32 -04:00
hayodea 8a7dc10892 Split StimulusProducer=>StimulusBuffer+StimulusProducer
We're getting ready for the last mile of the StimulusBuffer API
and the proto-completion of the LivoxGen1 StimBuffApi.
2025-11-14 20:44:37 -04:00
hayodea 70c0175a8b Rename StimulusBuffer=>StimulusProducer
Next we'll split the StimulusBuffer-related stuff into a new class
StimulusBuffer.
2025-11-14 19:50:51 -04:00
hayodea 74e3896ae4 Rename PcloudStimulusBuffer=>PcloudDataProducer
This prepares us for the split up of classes. We're going to split
StimulusBuffer into two base classes: StimulusBuffer and
StimulusProducer.
2025-11-14 19:44:18 -04:00
hayodea 7b7ff06219 PcloudStimBuff:start: check engine setup()s for error 2025-11-14 18:07:20 -04:00
hayodea 51a2858214 OClCollMeshEngn:*KernelComplete: use WRITE_INVLDT during finalize()
Doing it this way enables us to get the mapBuffer() call working
during finalize. But we couldn't get the unmap call working. That
has to do with a bug in the Rusticl event waiting code.
2025-11-14 18:04:12 -04:00
hayodea 2e75dd40aa OClCollMeshEngn: Rearrange steps in startCollateKernel
Just to make it match startCompactKernel. No other reason.
2025-11-14 18:01:48 -04:00
hayodea c08e075763 Bug:Rusticl: segfault on waitForEvents(clEnqueueUnmapMemObject) in finalize
For some reason, waiting on the event object returned by
clEnqueueUnmapMemObject, but only when called from within finalize().
Under normal operating conditions when we map and then unmap our
HOST_PTR buffers, everything works just fine.

I can't discern any relevant difference.
Adding a bridged 300ms delay in setup() doesn't help either so it
doesn't seem to be solved by allowing the rusticl worker threads
to finish initializing.

GDB output from the segfault appended. Sadly, no debug symbols for
the ubuntu rusticl package.

```
[New Thread 0xffffdd2ce140 (LWP 1056313)]
validateOpenClVersion: OpenCL platform version: OpenCL 3.0
validateOpenClVersion: OpenCL device version: OpenCL 3.0
[New Thread 0xffffdcabe140 (LWP 1056314)]
[New Thread 0xffffc9a8f140 (LWP 1056315)]
start: Starting stimulus buffer for device 3JEDK380010Z39
attachDeviceReq3: Got return mode (0) for device: 3JEDK380010Z39
discardHeartbeatAck: Lidar not ready for operation: work_state: 0x0 (Initializing), ack_msg: 0x1b
discardHeartbeatAck: Lidar not ready for operation: work_state: 0x0 (Initializing), ack_msg: 0x45
discardHeartbeatAck: Lidar not ready for operation: work_state: 0x0 (Initializing), ack_msg: 0x45
discardHeartbeatAck: Lidar not ready for operation: work_state: 0x0 (Initializing), ack_msg: 0x45
attachDeviceReq5: Failed to enable pcloud data for dev 3JEDK380010Z39
newDeviceAttachmentSpecInd2: Attach failed for device spec Device Identifier: avia0, Sensor Type: e, QualeIface API: structural-qualeiface, QualeIface API Params: (), StimBuff API: livoxGen1, StimBuff API Params: (), Provider: livoxProto1, Provider Params: (smo-ip=10.42.0.2 ), Device Selector: 3JEDK380010Z39

attachAllUnattachedDevicesFromReq2: Failed to attach device: avia0
Mrntt: attached 0 of 2 sense devices.
Mrntt: Body component initialized.
initializeReq2: Failed to initialize globalMind
marionetteInitializeReqCb: Failed to initialize Marionette. Shutting down.
Mrntt: About to detach all sense devices.
Mrntt: Successfully detached 0 of 0 sense devices.
Mrntt: About to finalize all stim buff api libs.
compactKernelCompletecalling w/mapFlags=4. INV=4; READ=1.
mapBuffer 1
mapBuffer 2
mapBuffer 3: cmdQ: 0xffffec013d68, buffer: 0xffffec07b6b8, mapflags: 4
mapBuffer 4
mapBuffer 5
unmapBuffer 1
unmapBuffer 2
unmapBuffer 3
unmapBuffer 4
unmapBuffer 4.1
unmapBuffer 5

Thread 9 "rusticl queue t" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xffffdcabe140 (LWP 1056314)]
Download failed: Invalid argument.  Continuing without source file ./string/../sysdeps/aarch64/multiarch/../memcpy.S.
__memcpy_generic () at ../sysdeps/aarch64/multiarch/../memcpy.S:155
warning: 155    ../sysdeps/aarch64/multiarch/../memcpy.S: No such file or directory
(gdb)
(gdb) bt
(gdb)
```
2025-11-14 17:45:34 -04:00
hayodea 3995f57489 OClCollMeshEngn: call clFlush+clFinish after setup()
This ensures that all operations enqueued during setup() get fully
executed before any requests come in.
2025-11-14 17:42:46 -04:00
hayodea 0720ed9c76 StimBuff: Make produceFrameReq responsive to stop() 2025-11-14 02:25:51 -04:00
hayodea c268414b0d Fix comment 2025-11-14 02:08:03 -04:00
hayodea a1625eb562 OClCollMeshEngn: Use shouldAcceptRequests stop/finalize() pattern
This makes the stop() method capable of synchronously stopping all
engine/server-type async services which don't act in a self-moved
fashion but instead wait for a request.
2025-11-14 01:41:03 -04:00
hayodea 324e3d1f6a SpinLock: Add releasePrematurely for early releases 2025-11-14 01:38:06 -04:00
hayodea 39691175e7 Formatting 2025-11-14 01:03:58 -04:00
hayodea 1df43665c3 IoUringAssmEngn: Implement shouldAcceptRequests daemon/async control
We've reworked the synchronous control functions that govern the
async daemon and in-flight requests for this class. The
shouldAcceptRequests flag represents the readiness state of the
whole engine class. The in-flight async operations consult the
shouldAcceptRequests flag to determine whether they should return
early.

Now the stop() method is solely for setting the locked flag
shouldAcceptRequests=false.

The pair resetAndAssembleFrame()/assemblyCycleComplete manage the
per-assembly cycle state machine, and they don't need to set or
interfere with the shouldAcceptRequests flag.
2025-11-13 23:53:31 -04:00
hayodea 501effe6d5 IoUringAssmEngn: assemFrameReq: exit responsively on stop() 2025-11-13 21:00:26 -04:00
hayodea d01f06904a assembleFrameReq: fix bug where we don't CB before ret 2025-11-13 20:57:10 -04:00
hayodea 16a74a3eb0 IoUringAssmEngn,OClCollMeshEngn: start/stop aren't public iface
Placing these functions in the public section kind of conceptually
confuses the reader since start/stop are indeed public interface
members in StimulusBuffer -- but they're not in the member objects.
2025-11-13 20:54:54 -04:00
hayodea a17072c8d9 IoUringEngn:assembleFrameReq: Implement and use callOriginalCallback 2025-11-13 20:53:53 -04:00
hayodea 67923d5f86 VSCode: idk 2025-11-13 20:52:52 -04:00
hayodea 972d5fc9db AsyncLoop: Add setRemainingIterationsToFailure 2025-11-13 20:52:08 -04:00
hayodea 5c3debecf4 OClCollMeshEngn: fix mem leak in [un]mapBuffer() 2025-11-13 01:41:59 -04:00
hayodea e446d42b3c StimBuff: Deferral: print message at start and end; timestamp too 2025-11-13 01:09:30 -04:00
hayodea 63fa0be91a Document good explanations of ROS transforms
Although I don't think they're good for our project. We don't care
to map our standpoint to some external point/"frame". SMO retains
the FPoV without any external reference point.
2025-11-13 00:33:52 -04:00
hayodea 6d669ee8b2 Docs: document the pcloud vis research 2025-11-12 22:48:59 -04:00
hayodea d60fd98887 Rusticl: document mapping bug's cause: karolherbst
There's a bug in the Rusticl implementation of clEnqueueMapBuffer/
clEnqueueUnmapMemObject because karolherbst doesn't understand
how CL_MEM_USE_HOST_PTR works.
2025-11-12 20:44:42 -04:00
hayodea 5031b22a31 OClCollMeshEngn: use helper fns for parsing version numbers 2025-11-12 20:43:48 -04:00
hayodea df58f324a9 CMake:LivoxGen1: Require OpenCL 1.2+, printf & WRITE_INVALIDATE_REGION 2025-11-12 20:26:29 -04:00
hayodea 7e672bcc9a UdpCmdDemux: formatting 2025-11-12 17:25:55 -04:00
hayodea 371ae5803d Document bugs in OClCollMeshEngn 2025-11-12 16:20:58 -04:00
hayodea 1a9c96c857 whitespace 2025-11-12 15:09:15 -04:00
hayodea 116a642a9f StimBuff: Add opportunity for early lock release 2025-11-12 15:08:44 -04:00
hayodea d87c71b794 OClCollMeshEngn: perf profile and print kernel exec durations 2025-11-12 13:05:13 -04:00
hayodea 33b534355a OpenCL minimum version is 1.2
We use CL_MAP_WRITE_INVALIDATE, and I think one other feature which
both require v1.2 minimum
2025-11-12 13:05:13 -04:00
latentprion 96e64e24b8 OClCollMeshEngn: collBuff only needs MAP_WRITE; silence dbg prints
When mapping in the collationBuff we only need to supply CL_MAP_WRITE
and not CL_MAP_WRITE_INVALIDATE_REGION since we don't care to
preserve the contents of the collation buff as input to the
collation kernel.
2025-11-12 12:49:54 -04:00
hayodea 1dc74065fb OClCollMeshEngn: cleanup and get it working on RPi5+Rusticl+V3D GPU
It seems that whenever you have an HOST_PTR input buffer to be
"transferred" from the host to the GPU, whose contents must be
preserved, you must map it with WRITE_INVALIDATE_REGION on the
RPi5.

This makes little sense, but we'll have to let it be for now.
At least the code works now and we don't have to abandon using
OpenCL.
2025-11-12 12:36:41 -04:00
hayodea d687ca0164 PcloudStimBuff: remove printf clutter 2025-11-12 12:34:30 -04:00
hayodea 91e0fd0f8e IoUringAssmEngn: Disable debugging for compact kernel results 2025-11-12 12:33:38 -04:00
hayodea 4dbb27fd1f StimulusBuffer: properly serialize timeslices
We previously unintentionally allowed multiple production operations
to occur in the same timeslice because we were calling for production
even when deferring timeslices.
2025-11-12 12:31:37 -04:00
hayodea b55e7a8b19 livoxGen1:OpenCL kernels: add debug printfs 2025-11-12 12:30:41 -04:00
hayodea f58f908366 OpenCL checks: Add check for the need to clFlush kernel cmds 2025-11-12 12:29:19 -04:00
hayodea a52685fbdf New bug documented 2025-11-12 12:28:33 -04:00
hayodea 5bb9c9e90b Dbg: Useful printfs for the raspi5 2025-11-10 01:05:20 -04:00
hayodea 401c844fcc PcloudStimBuff: add skeleton produceFrameReq :)
Big waves.
This function wraps the operation of getting a stimframe from
the SpMcRingBuffer, and then eventually assigning it a
SimultaneityStamp. For now we just always pass in the first
stim frame and we don't get any simulstamps.

Its callOriginalCallback() automatically calls
allowNextStimulusFrame() to ensure that it doesn't deadlock future
timeslices.
2025-11-10 01:04:07 -04:00
hayodea eedeb4b803 OClCollMeshEngn: Add method compactCollateAndMeshFrameReq
This method takes an input assembly buffer and selects which
OpenCL kernels need to be executed on that buffer to transform
the input data into the eventual output StimulusFrame for the
current timeslice period.
2025-11-10 00:58:48 -04:00
hayodea 19a79faabe OClCollMeshEngn: stop now just calls stop*Kernel 2025-11-10 00:54:41 -04:00
hayodea 1ac6fa4a16 Rename StimFrame=>StimulusFrame 2025-11-09 22:09:19 -04:00
hayodea 7cae3452fc OClMeshCollEngn: temporarily call stop in CL cbs 2025-11-09 20:23:14 -04:00
hayodea 582aefb02c OClEngn: Split isSetup/Running into collate+compact 2025-11-09 19:58:45 -04:00
hayodea aef251b7e5 IoUringEngn: add random dummy slot generator for debugging 2025-11-09 19:34:02 -04:00
hayodea ad0b8058a4 ClCollMeshEngn: big reworks to clean up. 2025-11-09 19:28:55 -04:00
hayodea b331af4f03 ClCollMeshEngn: Split start into start[Collate|Compact]Kernel()
These prepare each kernel separately. We'll unify them further.
2025-11-09 16:12:10 -04:00
hayodea 683e107b04 livoxG1:OClCollMeshEngn: Wrestling and massaging 2025-11-09 15:18:53 -04:00
hayodea c8cbaed3b1 OClCollAndMeshEngn: formatting 2025-11-09 12:37:30 -04:00
hayodea 5f03e4c392 livoxG1:collateDgrams.cl: Clarify collation offsetting 2025-11-09 12:12:08 -04:00
hayodea 55116b1d41 livoxG1:collateDgrams.cl: Fix unaligned reads 2025-11-09 11:48:53 -04:00
hayodea 7977f0bcc9 OClCollatingMeshingEngn: Compile both kernels side by side 2025-11-09 04:49:37 -04:00
hayodea 6264a128a8 livoxG1: Add point cloud frame collator OpenCL kernel 2025-11-09 04:48:15 -04:00
hayodea 4b60a10bc6 VSCode config: idk 2025-11-09 04:46:00 -04:00
hayodea 01ba68f2b5 livoxG1:OCLEngine: compile compactor program 2025-11-09 03:44:56 -04:00
hayodea 511f1796e8 livoxG1:slotCompactor.cl: mental-validate and refactor 2025-11-09 03:40:46 -04:00
hayodea a0a5aa49ad livoxG1: Add new OpenCl kernel to compact dgrams before collation 2025-11-09 02:39:09 -04:00
hayodea d2e2d9bc3b StagingBuffer: Prefer mlock to io_uring_register_buffers 2025-11-09 01:16:17 -04:00
hayodea 010ba9c7bd Bugfix,IoUringEngn: fill unassembled slots w/dummy; use separate iovecs
We implemented the feature to fill unassembled slots w/dummy header
values for the livox pcloud header.

We also fixed a bug where io uring was writing into the last slot
only because we were using the same iovec for every SQE.
2025-11-09 00:55:58 -04:00
hayodea 72a3415553 Bugfix: Don't use eventfdDesc after stop()
We call stop() inside the assembleFrameReq3, so when it returns,
the eventfdDesc should be destroyed. Don't allow a possibly stale
eventfdDesc obj to permit us to re-arm the eventfdDesc read_some
call.
2025-11-08 23:09:14 -04:00
hayodea a0ab5538df StimBuff: Add mnemonic wrapper for unlocking frameAssmLimiter 2025-11-08 22:07:52 -04:00
hayodea 5b7b4f215a IoUringAssmEngine: Acquire spinlock in stall timeout branch 2025-11-08 21:54:11 -04:00
hayodea d8a3999ad5 PcloudStimBuff: call OClCollMessEngn::setup/finalize in start/stop 2025-11-08 12:23:13 -04:00
hayodea 5ff6a4ee0b OClCollMeshEngn: implement start/stop/setup/finalize 2025-11-08 12:23:13 -04:00
hayodea 6a5bb47e0e PcloudStimBuff: Add OpenClCollatingAndMeshingEngine instance 2025-11-08 12:23:10 -04:00
hayodea 073cdde08f livoxG1: StagingBuff: add getClEngineIovec 2025-11-08 12:18:55 -04:00
hayodea 869160b782 Add bugs.log for heisenbugs 2025-11-08 11:29:27 -04:00
hayodea e1042724fc livoxGen1: nitpicking: use .-prefixed symbol for end 2025-11-08 11:11:05 -04:00
hayodea 28e56653ea livoxGen1: unmangle symbols, add .sizes 2025-11-08 11:09:09 -04:00
hayodea 5dbed56e38 livoxG1: Make collateKernelNBytes a uint32_t for 32bit portability 2025-11-08 10:59:08 -04:00
hayodea 9233f7fdc8 livoxG1: Add OpenCl kernels for collation 2025-11-08 10:26:17 -04:00
hayodea b460c8b2d3 CLTests: Add test for USE_HOST_PTR; fix build warnings 2025-11-08 02:07:43 -04:00
hayodea bc56c83fad Rename: OpenGlSplittingEngine=>OpenGlCollatingAndMeshingEngine 2025-11-08 01:48:56 -04:00
hayodea cb493d7598 StagingBuff: set OpenCL constraints 2025-11-08 01:45:47 -04:00
hayodea 1c50fc0e29 StagingBuff: Move constructor into .cpp file 2025-11-08 00:21:24 -04:00
hayodea 7497f2fd95 StagingBuff: Enhance IoConstraints with frame constraints
Now StagingBuff instances must meed both frame and slot
constraints.
2025-11-08 00:15:29 -04:00
hayodea 5f11a9d6c7 VSCode: Highlight OpenCL C files as C 2025-11-07 23:05:49 -04:00
hayodea 0b21cdd2ba OClSplitEngn: fix build warnings 2025-11-07 22:20:44 -04:00
hayodea f5146738e1 PcloudStimBuff: Add collationBuffer 2025-11-07 22:07:27 -04:00
hayodea 479219db2d StagingBuff: Unify constraints into IOEngineConstraints 2025-11-07 22:05:01 -04:00
hayodea 1afa085fd4 livoxProto1:Device: Spinlock guard heartbeat stop() for races 2025-11-07 21:36:00 -04:00
hayodea 7b092956c0 UdpCmdDemux: Guard start() w/spinlock for races 2025-11-07 21:12:40 -04:00
hayodea e0c0976e0b BcastListener: Guard start() w/spinlock for races 2025-11-07 21:12:05 -04:00
hayodea 887fa1ab6f Bug:UdpCmdDemux: Add SpinLock for races around stop() 2025-11-07 20:45:16 -04:00
hayodea 7d2cb58200 Bug:BcastListener: Add SpinLock for races around stop() 2025-11-07 20:44:44 -04:00
hayodea b598ca8594 libs: Add smohook for getting cmdline opts 2025-11-07 14:59:28 -04:00
hayodea a7a85b0c1f CMake:Boost: Try -DBOOST_ALL_DYN_LINK (didn't work) 2025-11-06 21:55:15 -04:00
hayodea 457d0f9345 Dbg:Add CallableTracer for callables post()ed to boost.asio
This class and its macro allow us to trace the invocation of
callbacks as they're invoked by Boost.asio.
2025-11-06 21:45:16 -04:00
hayodea eeb057effd Dbg:Threading: prefix thread names with "smo:" so they group 2025-11-06 15:09:15 -04:00
hayodea c7e117b08e Dbg:Threading: use pthread_setname_np for debugging ease 2025-11-06 15:04:04 -04:00
hayodea af57c4dfd1 Boost: move top_ link fixer to top of files 2025-11-06 15:03:26 -04:00
hayodea db30001140 livoxG1: Rename stagingBuffer=>assemblyBuffer
This is in preparation for re-using StagingBuffer to also serve
as the collation buffer that we'll use as the intermediate stage
for producing the final output mesh.
2025-11-06 14:09:10 -04:00
latentprion d69636bf7b IoUringAssmEngn: destroy prev cb obj
This should ensure that the sh_ptr<contin> from the prev object
gets destroyed properly.
2025-11-06 09:10:44 -04:00
hayodea 59a584561d CMake:livoxGen1: advise on libOpenCL.so ubuntu pkg name 2025-11-06 09:09:51 -04:00
hayodea f7aba4af4e livoxGen1: find OpenCL using both CMake & pkgConfig 2025-11-06 09:02:05 -04:00
latentprion 21bbaf846e Todo: update 2025-11-06 01:50:03 -04:00
hayodea aacbdd5864 livoxGen1: Add OpenClSplittingEngine 2025-11-06 01:20:02 -04:00
hayodea bb59f47549 IoUringAssmEngn: add assembleFrameReq
Invoke it instimFrameProductionTimesliceInd.

Also, we discovered:
* stream_descriptor::release() doesn't fully release all metadata
  from the fd it was assigned. This suggests that we should go
  through the codebase and do: release()=>reset() whenever we
  wish to release().
* We've confirmed that spinlocks can be used to prevent race
  conditions between stop() and handler methods.
2025-11-06 01:04:10 -04:00
hayodea 1c7277d141 SpinLock: add RAII Guard class 2025-11-05 22:38:04 -04:00
hayodea d29ebafea0 IoUringAssmEngine: Add start/stop() for frame assembly.
With these two functions implemented, this class now actually
assembles frames.
2025-11-05 19:23:30 -04:00
hayodea 94982d50b9 IoUringAssmEngn: map StagingBuff w/mmap; reg w/io_uring; add eventFd
StagingBuffer:
We now allocate memory with mmap(MAP_ANONYMOUS) so that we can be
sure it can be pinned with io_uring_register_buffers(). This
ensures that if DMA is possible, it should be usable.

	IoUringAssemblyEngine:
We now register an eventfd with io_uring so that we can listen
for CQEs with boost::asio.
2025-11-05 15:34:23 -04:00
hayodea 0503705a13 PcloudStimBuff: invoke IOUringAsmEngn::setup/stop in start() 2025-11-04 15:33:55 -04:00
hayodea ef9eef2bc3 PCloudStimBuff: Add useful dbg msg for thread affinity rigour 2025-11-04 00:56:46 -04:00
hayodea 2b3b318abe Formatting 2025-11-04 00:49:15 -04:00
hayodea f3a4c69597 StimBuff: DevAttSpec should be a sh_ptr, not a plain ref 2025-11-04 00:47:01 -04:00
hayodea 032e9ef8d5 DevMgt: Update comments on bg daemon thread selection 2025-11-04 00:33:43 -04:00
hayodea 4a1bcb1516 TODO: update 2025-11-04 00:32:39 -04:00
hayodea d6e1e7ebc0 DevMgmt: StimBuffApi bg tasks should execute on body thread 2025-11-04 00:32:14 -04:00
hayodea 9a4f80a9d6 libAttachmentSupport: convert into shared lib
This ensures that the support routines in this library will be
exposed at the same vaddr to each object in the vaddrspace.
2025-11-04 00:22:25 -04:00
hayodea 7a55a65589 LivoxProto1: Rename pcloudDataSocketDesc=>pcloudDataFdDesc 2025-11-03 23:22:50 -04:00
hayodea 14b97a52ed IoUringAsmEngn: Use pcloudDataSocket from UdpCmdDemux 2025-11-03 23:10:24 -04:00
hayodea 5845f1a41d Bug:Boost: Use shlibs instead of header-only for call_stack::top_
This symbol is defined as a static member object inside of a
boost detail header. When boost headers are used in a project
that uses Boost in both the main binary as well as dlopen()'d
shlibs, the top_ symbol gets duplicated and the metadata gets
partitioned.

We use the Boost shlib to unify both the main binary and the
shlibs to use the same memory address for top_.

This involves marking the templated object call_stack::top_ as
"extern" and then declaring to Boost that we intend to use the
shlibs.
2025-11-03 22:59:52 -04:00
hayodea 6ea90c2dae Squash: into "make use of pcloudDataFdDesc" 2025-11-03 22:12:43 -04:00
hayodea 121b7db045 CMake:attachmentSupport: Don't link against Boost_LIBRARIES 2025-11-03 21:09:55 -04:00
hayodea d88dd2cf44 livoxGen1: Make use of livoxProto1_getPcloudDataFdDesc 2025-11-02 19:16:22 -04:00
hayodea b3bf0e2cb9 StimBuff,commonLibs: Add libattachmentSupport, move fnptrs into .cpp files
We move the methods in StimulusBuffer whose addresses are taken during
program execution into a separate static lib. This guarantees that
they'll have their own, single vaddr at runtime, at least within
each independent code module.
2025-11-02 19:12:28 -04:00
hayodea 45ad5c83ee livoxProto1: Open pcloudDataSocket in UdpCmdDemux
The pcloudData socket is now opened by UdpCommandDemuxer, when
libLivoxProto1 is initialized. We can now just pick up the socket
and be certain it'll be there if the lib is being executed.
2025-11-01 22:41:58 -04:00
hayodea 10e615e75e StimBuff: make start/stop virtual;
We can now have the derived StimBuff class implement its own
start()/stop() preamble and epilogue.
2025-11-01 22:05:52 -04:00
hayodea 05515743c5 Put continuation near frontend func 2025-11-01 21:33:35 -04:00
hayodea b2c73f6bed IoUringAssmEngine: Add skeleton setup/finalize
Also add dependency on liburing.
This patch adds basic io_uring_queue_init and io_uring_exit
support and calls.
2025-11-01 21:30:47 -04:00
hayodea 797a95e6a1 IoUringAssmEngine: formatting/indentation 2025-11-01 20:21:49 -04:00
hayodea 972979cc10 IoUringAssmEngine: Remove dead wood 2025-11-01 20:18:05 -04:00
hayodea ba955ef633 PcloudStimBuff: Add skeleton assembleAndProduceStimulusFrameReq impl 2025-11-01 04:14:07 -04:00
hayodea a32b4f05d1 livoxGen1: call PcloudStimulusBuffer::start/stop
We now call start()/stop() in attach/detachDeviceReq.
2025-11-01 03:32:05 -04:00
hayodea c8474edad7 livox: rename handshakeTimeoutMs=>commandTimeoutMs 2025-11-01 02:45:24 -04:00
hayodea 58e9b09995 livoxGen1: Use RAII to close dlopen handle 2025-11-01 01:54:49 -04:00
hayodea c2c6d409dd DAPSpec: QualeIfaceApiParams: Add histbuffMs 2025-11-01 01:35:29 -04:00
hayodea 8dba0fdfc4 Todo: update 2025-11-01 01:26:51 -04:00
hayodea 67af9f02da DAPSpecs: Update parseRequiredParamAsInt to support all param lists 2025-11-01 01:11:34 -04:00
hayodea e824685c19 DAPSpec: Add params to quale-iface-api
We add params to the quale iface (soon to be renamed to cologex-api).
This allows us to eventually set the history length for stimbuffs.
2025-11-01 00:57:04 -04:00
hayodea 9cf1398f5c PcloudStimBuff: specify that openCl constraints are for input 2025-11-01 00:19:06 -04:00
hayodea f76f718e80 PcloudStimBuff: make OpenCL constraints explicit 2025-11-01 00:17:10 -04:00
hayodea cdade17905 Add SpMcRingBuffer to base class StimulusBuffer
This will hopefully genericise the interface for Stimbuffs.
2025-11-01 00:09:53 -04:00
hayodea 5af7e531b6 Style: use modern C++ style 2025-11-01 00:05:43 -04:00
hayodea 018c1f1e1d SpMcRingBuffer: Added this new class
This will be the foundation for all StimulusBuffers. We can most
likely add this generically to the StimulusBuffer base class rather
than adding it only to StimulusBuffer's derived classes.
2025-10-31 22:58:18 -04:00
hayodea 5e522178d8 SequenceLock: Newly added
What can I say? It's a very portable primitive that allows us to
create many RCU-type data structures with many readers and one
writer, locklessly.
2025-10-31 22:12:23 -04:00
hayodea 7574f3f59a StimBuff: customize stop() delay; add stimFrameProductionTimesliceInd 2025-10-31 13:54:50 -04:00
hayodea 0de031c74b Formatting: move big inlines out of class def 2025-10-31 13:47:00 -04:00
hayodea ebbb2b1345 StimBuff: Add skeleton common impl with rate limiting 2025-10-31 13:43:23 -04:00
hayodea 3bf8146ca3 CMake: STIMBUFF_FRAME_RETRY_PERIOD_MS 3=>1 2025-10-31 12:34:34 -04:00
hayodea f32a472c5d PcloudStimBuff: Add skeleton daemon; frame rate limiting
Basic implementation of the stimbuff mechanism's frame rate
limiting behaviour.
2025-10-31 12:22:07 -04:00
hayodea 7994c2f6e2 CMake,config.h: Add -DSTIMBUFF_FRAME_RETRY_DELAY_MS
This determines how long a stimbuff should wait before retrying
to produce a stimframe, if the previous stimframe is still being
produced.
2025-10-31 12:08:40 -04:00
hayodea 9ab155560a livoxGen1:ioUringAssmEngine: Fix build 2025-10-31 11:49:56 -04:00
hayodea 720babd39d CMake,config.h: Add -DSTIMBUFF_FRAME_PERIOD_MS
This determines the maximum rate at which stimbuffs will be refreshed
with data from their device.
A device may refresh less frequently than this, but not more
frequently. The goal here is to give us control over the max
rate at which a device produces data.
2025-10-31 11:31:04 -04:00
hayodea 5c3bc6c324 livoxGen1: Reduce nDgramsPerFrame 500=>30 ~86400 points/sec 2025-10-31 11:27:43 -04:00
hayodea b53ef42124 livoxGen1: documentation and formatting 2025-10-31 08:57:37 -04:00
hayodea babfda4d0f livoxGen1: Reduce handshakeTimeoutMs default to 5ms
We have no real reason to set it as high as 300.
2025-10-31 08:57:37 -04:00
hayodea 88dd872c95 Todo: Add async bridging idea as sync wait mechanism 2025-10-31 08:57:37 -04:00
hayodea b8255234de Todo: add idea for dealing with late timeouts 2025-10-31 08:57:37 -04:00
hayodea 1a4f7f97bd BUG: Late-timeout during finalize
This adds a script which reproduces this bug after a lot of
iterations in gdb and lets us get a backtrace
2025-10-31 08:57:37 -04:00
hayodea 13a948a2d3 Formatting: use early continue pattern 2025-10-31 08:57:37 -04:00
hayodea 07c48d78d1 Todo: update comments 2025-10-31 08:57:37 -04:00
hayodea 7b6bfbad68 Fix linker error 2025-10-31 08:57:37 -04:00
hayodea 393326052c Todo: update and add comments to finalizeInd 2025-10-31 08:57:37 -04:00
hayodea b3d0565e11 livoxGen1: Committing intermediate state before daemon design 2025-10-31 08:57:37 -04:00
hayodea 287dd6be56 livoxGen1: Fix include name 2025-10-31 08:57:37 -04:00
hayodea 0b2fde3484 livoxGen1:StaginBuffer: simplify buff size/stride calcs 2025-10-31 08:57:37 -04:00
hayodea c1286627ab LivoxGen1: attachDevReq sets nDgramsPerFrame 2025-10-31 08:57:37 -04:00
hayodea 2234df1de2 livoxGen1:attach: alloc PcloudStimBuff after getting return mode 2025-10-31 08:57:37 -04:00
hayodea 4db3581be9 VSCode config: idk 2025-10-31 08:57:37 -04:00
hayodea 7efe622dd2 livoxGen1:attach: call getReturnModeReq before enPcloudDataReq 2025-10-31 08:57:37 -04:00
hayodea f658e97ed0 livoxProto1: export getReturnModeReq; cache result in Device 2025-10-31 08:57:37 -04:00
hayodea f8c5fad841 AssemblyBuffer changes 2025-10-31 08:57:37 -04:00
hayodea 626a84cc78 Formatting 2025-10-31 08:57:37 -04:00
hayodea a68143810e DeviceReattacher: use provided ioThread; not mrntt directly 2025-10-31 08:57:22 -04:00
hayodea 109cd9eb03 DevReattacher: Add 20ms sync delay during stop()
This fixes the bug where in-flight async ops that were triggered
by DevReattacher (such as livoxGen1_attachDeviceReq) that have
unconditional delays in them would fire late, after the state
var they rely upon had already been de-initialized.

We use an async bridge to ensure that those ops get executed in
the background and then resume execution after the delay.
2025-10-31 08:20:33 -04:00
hayodea 7b830f0a68 AsyncBridge: Comment why we don't checkException in loop 2025-10-31 08:20:29 -04:00
hayodea b65b0f2370 UdpCmdDemux: remove stop-"responsiveness" timer
I think it's best to remove the timer tick from UdpCommandDemuxer.
I looked at it again and it doesn't actually help with responsiveness.
Whatever it contributes is no different from what stop() does.
They both just call timer.cancel and cmdsocket.cancel.
So if that doesn't stop the socket in stop(), it won't magically
stop it more effectively if I call it from a timer handler.
2025-10-31 08:20:27 -04:00
hayodea 2a8a6edf22 LivoxGen1: Add basic stimbuff creation & destruction 2025-10-25 23:04:59 -04:00
hayodea 10e19a3237 StimulusBuffer: Fix linker errors 2025-10-25 23:04:28 -04:00
hayodea 9e83a99c9c LivoxProto1:Device: Refcount num stimbuffs attached 2025-10-25 23:03:00 -04:00
hayodea b576d41595 VSCode config 2025-10-25 23:02:08 -04:00
hayodea b89c8cdc4f More work on PcloudStimulusBuffer 2025-10-25 19:42:48 -04:00
hayodea 682b9e121b Some initial notes on postrin path design 2025-10-25 19:35:53 -04:00
hayodea bcf81594e7 Add PcloudFormatDesc 2025-10-25 19:32:10 -04:00
hayodea 1b9acd5603 Rename LidarStimulusBuffer=>PcloudStimulusBuffer 2025-10-25 19:28:18 -04:00
hayodea dc23a61410 Add StimulusBuffer and LidarStimulusBuffer 2025-10-25 19:05:32 -04:00
hayodea e9b4e15b79 Add base class StimulusBuffer 2025-10-25 18:56:30 -04:00
hayodea fca665d44e LivoxGen1: StagingBuff: Update iface 2025-10-25 15:15:52 -04:00
hayodea 862acf0fe3 LivoxGen1: Add StagingBuffer class. 2025-10-25 14:49:24 -04:00
hayodea 6650664529 LivoxProto1: Move data members to one place 2025-10-25 14:43:51 -04:00
hayodea e297a260d9 Contin: Rename Exception propagation macros 2025-10-25 14:12:12 -04:00
latentprion d54ef04c47 Todo: Update 2025-10-25 13:25:14 -04:00
hayodea 92399ba283 Use piecemeal boost headers 2025-10-25 12:55:19 -04:00
hayodea 6f4a2dd649 LivoxGen/Proto1: Move en/disablePcloudData call to Gen1
We no longer try to enable pcloud data as part of the connectReq()
sequence. Instead we separate them so that a device can be connected
but not be issuing pcloud data.
2025-10-25 12:55:19 -04:00
latentprion 0e872042ee Add some compile-time CL utilities 2025-10-25 03:39:42 -04:00
hayodea 266cabcddb LivoxGen1: Add get/setReturnModeReq() 2025-10-25 00:19:06 -04:00
hayodea 444555e9b6 LivoxProto1: Remove superfluous proto methods 2025-10-24 22:23:59 -04:00
hayodea 3373393755 LivoxGen1: Add Get/SetReturn rate proto headers 2025-10-24 22:11:31 -04:00
latentprion dc864bad00 Todo: update with optimization ideas 2025-10-24 16:03:03 -04:00
hayodea 452d1966fc LivoxProto1: Document UDP demuxer purpose & use cases
Also fix bug in transient resource transfer from continuation
to Device object.
2025-10-24 03:09:17 -04:00
hayodea f7dcb7307d LivoxProto1: registerUdpHandler: enforce overwriting
Enforce handler overwriting in the devicesUnderConstruction
collection, for combos of cmd_set+cmd_id that pre-exist the
current invocation
2025-10-24 02:42:50 -04:00
hayodea bd0118531f LivoxProto1: Pcloud socket is set up transiently in contin 2025-10-24 02:05:32 -04:00
hayodea 4bfcdf37da LivoxProto1:enPcloudData: reorder success case here 2025-10-24 01:46:08 -04:00
hayodea 2b8f6b6ad5 Formatting 2025-10-24 01:45:03 -04:00
hayodea a5cf996ed2 LivoxProto1: Get rid of raw FD for pcloudData recv 2025-10-24 01:44:19 -04:00
hayodea 71c2b855ec LivoxProto1: Device: misc logging, cleanup, bugfix 2025-10-24 01:29:18 -04:00
hayodea 06c5f4503f Todo: update 2025-10-24 01:11:19 -04:00
hayodea bede123691 LivoxProto1: Print when Lidar isn't ready for work 2025-10-24 00:51:28 -04:00
hayodea 83c937ae8f LivoxProto1: Cancel async contin branch cbs in oracle fn
We were canceling the registered handler CBs in their branch
segments instead of in the unifying oracle fn.
This caused a bug where handlers were left behind in the
udpCmdDemuxer and thus a handler was invoked twice when a UDP
msg came in.
2025-10-24 00:46:13 -04:00
hayodea d39dfb5334 livoxProto1: connectReq: Add 5ms delay before Sample enable msg 2025-10-23 01:27:35 -04:00
hayodea 44cfd7ab69 LivoxProto1: Add 105ms delay before Sample msg 2025-10-23 01:06:34 -04:00
hayodea b277baa76d Whitespace 2025-10-23 00:24:57 -04:00
hayodea 5db1cfdac8 LivoxProto1: Pcloud data stream now working 2025-10-23 00:24:23 -04:00
hayodea a4d99e5d4d LivoxProto1: ExecuteHandshake uses udpCommandDemuxer
UdpCommandDemuxer also now supports devices "under construction".
2025-10-22 22:13:38 -04:00
hayodea 01ad1ff073 LivoxProto1: udpCmdDemux: use piecemeal boost.asio headers 2025-10-22 13:37:54 -04:00
hayodea 8e1d609ca1 livoxProto1: udpCmdDemux now consults per-device handler registry
Device class objects now have a per-Device unordered_map of handlers
keyed by cmd_set+cmd_id.
2025-10-22 07:28:00 -04:00
hayodea 10afec2532 LivoxProto1: Add UdpCommandDemuxer.
We haven't genericized it with an unordered_map or integrated it
into device.cpp's async methods yet.
2025-10-22 06:17:42 -04:00
hayodea 66a9db13c3 LivoxProto1: invoke enablePcloudDataReq
Sadly we don't get to immediately see the results of our
work because we have to do a unified dispatcher for the incoming
UDP messages on the command channel.
2025-10-22 01:59:04 -04:00
hayodea d9042c6510 LivoxProto1: Add en/disablePcloudDataReq()
Untested, but this should enable us to enable and disable data
from the device.
2025-10-22 01:21:35 -04:00
hayodea 870057a680 Add Stencil class for describing threshold ranges 2025-10-21 20:50:59 -04:00
hayodea e444cd1e04 Move files around for cologex and sitbuff lib impl 2025-10-21 20:02:36 -04:00
hayodea 6bc5bd30d5 Rename: ChronoFrame=>PhenoFrame and ChronoSeq=>PhenoSeq 2025-10-21 19:38:49 -04:00
hayodea 56367402d7 Move intrinEventInd()s from MarionetteComponent=>Director 2025-10-21 19:38:49 -04:00
hayodea 035accf553 Rename: painfulQuale.cpp=>negtrinEvent.cpp 2025-10-21 19:38:49 -04:00
hayodea 55f21c5436 Add skeleton negtrinEventInd, build painfulQuale.cpp again 2025-10-21 19:38:49 -04:00
hayodea d1b99852a8 Move MarionetteComponent out of component.h=>marionette.h 2025-10-21 19:38:49 -04:00
hayodea 66bb30cef5 Move painfulQuale.cpp into marionette 2025-10-21 19:38:49 -04:00
hayodea 068a885bff Goal: now is a MentalEntity 2025-10-21 19:38:49 -04:00
latentprion 7d453beb65 Remove superfluous #includes from headers 2025-10-18 20:16:29 -04:00
latentprion 49c9caa317 Remove superfluous #includes from headers 2025-10-17 16:19:25 -04:00
latentprion b06e9693c5 CMake: toolchain file updates 2025-10-17 14:07:30 -04:00
latentprion e4e700c362 Changes to CMake toolchain files
We still haven't successfully xcompiled, but we're working
toward it.
2025-10-17 12:43:06 -04:00
latentprion d317f1fb06 Whitespace 2025-10-16 03:41:22 -04:00
latentprion edd223b083 Remove unnecessary componentThread.h #includes
Since componentThread #includes boost::asio components, removing
it should improve compile times.
2025-10-16 02:15:16 -04:00
latentprion 945c5b397b Build: remove superfluous io_service.hpp #includes 2025-10-16 01:08:05 -04:00
latentprion 5017bf5f92 Build: use piecemeal boost headers.
Reduces compile time on a 4-core ARM Rpi5 from 1m12s wallclock
(i.e: "real") time to 0m55s; and from 4m+ "user" time to 3m8s.
2025-10-16 01:00:48 -04:00
hayodea 95d5c46e43 Rename: CologexSet=>CologexSeq to be consistent with chronomena 2025-10-05 21:44:12 -04:00
hayodea 4a55ff9bf2 Revamp Chronomenon: ChronoFrame and ChronoSeq are new classes
Chronomenon is now the base class category for stored raw
stim data.
2025-10-05 21:44:12 -04:00
hayodea 27ff4a3a0a Concept,Cologex,Goal: refactor headers
All Cologexes (both Cologexes and CologexSets) are now categorized
as Concepts.
Goals are now also Concepts -- they inherit from Concept as a base
class.

Using Concept as the base class for both Cologex and CologexSet
allows us to treat both cologexes and cologexsets the same way
when comparing in the abstract.
2025-10-05 21:44:12 -04:00
hayodea eddee05e41 Delete: qualeBundle.h 2025-10-05 21:44:12 -04:00
hayodea ccc7fd8e04 Rename: Concept=>Cologex 2025-10-05 21:43:46 -04:00
hayodea eb810e62e9 Todo: update 2025-10-04 14:52:25 -04:00
hayodea 3a50be05f8 Qutex: nRequiredLocks==1 at front should never call backoff 2025-10-04 14:52:02 -04:00
latentprion 168d8d616c Todo: update 2025-10-04 11:16:04 -04:00
latentprion 16775c6f1e Todo: update 2025-10-04 10:42:39 -04:00
hayodea 385b7d5a3c Todo: Investigate MWait to reduce spinlock power usage 2025-10-02 20:19:54 -04:00
hayodea d857999fdf Update comment notes 2025-10-02 01:14:04 -04:00
hayodea fa2609f4ce User /includes: Add common stim frame types header 2025-10-02 00:29:09 -04:00
hayodea a91a995407 Config.h.in: Remove these "legacy" artifacts 2025-10-01 20:04:21 -04:00
hayodea eb5875fe0d Rename: Sense API => Stim Buff API 2025-10-01 20:03:47 -04:00
hayodea 56b8e83a09 Update senseApiDesc.h and opts.cpp for clarity in device attachment and usage examples 2025-10-01 18:20:59 -04:00
hayodea a66d91fa31 DevAttachment:Rename: api=>stimbuffapi, implexor=>qualeiface 2025-10-01 18:10:58 -04:00
hayodea b69572eee7 Update livoxGen1 stimbuffapi 2025-10-01 14:13:27 -04:00
hayodea b771856330 Update docs on DAP specs and DA specs 2025-10-01 13:27:37 -04:00
hayodea c7ca889e9c Rename DASpec.md=>deviceAttachmentPipelineSpec.md 2025-10-01 13:15:11 -04:00
136 changed files with 12922 additions and 1589 deletions
+1
View File
@@ -12,3 +12,4 @@ config.h.in
configure configure
*.swp *.swp
cscope.out cscope.out
*.tmp
+13 -1
View File
@@ -1,5 +1,6 @@
{ {
"files.associations": { "files.associations": {
"*.cl": "c",
"cstdint": "cpp", "cstdint": "cpp",
"array": "cpp", "array": "cpp",
"atomic": "cpp", "atomic": "cpp",
@@ -78,12 +79,23 @@
"*.ipp": "cpp", "*.ipp": "cpp",
"unordered_set": "cpp", "unordered_set": "cpp",
"forward_list": "cpp", "forward_list": "cpp",
"barrier": "cpp" "barrier": "cpp",
"strstream": "cpp",
"regex": "cpp",
"stacktrace": "cpp",
"stdfloat": "cpp",
"cfenv": "cpp",
"expected": "cpp",
"valarray": "cpp",
"core": "cpp",
"nonlinearoptimization": "cpp",
"*.txx": "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,
"editor.inlayHints.enabled": "off",
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"C_Cpp.default.browse.limitSymbolsToIncludedHeaders": true, "C_Cpp.default.browse.limitSymbolsToIncludedHeaders": true,
"C_Cpp.default.browse.path": [ "C_Cpp.default.browse.path": [
+10
View File
@@ -0,0 +1,10 @@
# Project Instructions
- Always break functions into logical subfunctions. No long-scrolling functions, in any language. This applies to source code, scripts, build scripts, CMake, Makefiles, and similar project files. Preserve this subfunction splitting discipline during refactors.
- Modularity is non-negotiable. Always group logically related functions together into a module. Preserve modularity during refactors.
- Reuse or extend existing abstractions instead of duplicating logic wherever possible. Don't repeat yourself. The goal here is to prevent duplication. Not to discourage appropriate logical separation of prior abstractions into new logical abstractions where sensible.
- Always isolate configurable behaviour into configuration variables appropriate for the language and framework being used.
- Never bake in literals; at minimum, declare them at the top of the file with a semantically meaningful name.
- UI should be responsive. Always prefer to use pre-packaged UI toolkit widgets, containers and colour sets harmoniously, instead of writing custom CSS overrides. Write custom CSS only if there's no UI toolkit mechanism available.
- Aggressively isolate, split off, deduplicate and reuse code which can be made into common library code. Do the same with UI elements. Do this both when implementing new features and opportunistically while refactoring or changing old code/UI elements.
- Names of files, functions, classes, abstractions, database fields, etc should be aimed at disambiguating purpose and function, rather than at brevity.
+103 -8
View File
@@ -1,17 +1,19 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(salmanoff VERSION 0.01.000 LANGUAGES CXX) project(salmanoff VERSION 0.01.001 LANGUAGES CXX)
include(CMakeDependentOption) include(CMakeDependentOption)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DAPSS.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DAPSS.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DebugOpts.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DebugOpts.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake)
# Set C++ standard # Set C++ standard
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Build type # Build type
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug FORCE) set(CMAKE_BUILD_TYPE Debug FORCE)
endif() endif()
# Compiler flags # Compiler flags
@@ -32,6 +34,22 @@ if(NOT MRNTT_DEVMGR_REATTACHER_PERIOD_MS GREATER 0)
"MRNTT_DEVMGR_REATTACHER_PERIOD_MS must be a positive integer > 0") "MRNTT_DEVMGR_REATTACHER_PERIOD_MS must be a positive integer > 0")
endif() endif()
# Stimulus buffer frame period configuration
set(STIMBUFF_FRAME_PERIOD_MS 33
CACHE STRING "Stimulus buffer frame period (ms)")
if(NOT STIMBUFF_FRAME_PERIOD_MS GREATER 0)
message(FATAL_ERROR
"STIMBUFF_FRAME_PERIOD_MS must be a positive integer > 0")
endif()
# Stimulus buffer frame retry delay configuration
set(STIMBUFF_FRAME_RETRY_DELAY_MS 1
CACHE STRING "Stimulus buffer frame retry delay (ms)")
if(NOT STIMBUFF_FRAME_RETRY_DELAY_MS GREATER 0)
message(FATAL_ERROR
"STIMBUFF_FRAME_RETRY_DELAY_MS must be a positive integer > 0")
endif()
# World thread configuration # World thread configuration
option(WORLD_USE_BODY_THREAD option(WORLD_USE_BODY_THREAD
"Use body thread for world component instead of separate world thread" OFF) "Use body thread for world component instead of separate world thread" OFF)
@@ -44,6 +62,14 @@ if(ENABLE_DEBUG_LOCKS)
set(CONFIG_ENABLE_DEBUG_LOCKS TRUE) set(CONFIG_ENABLE_DEBUG_LOCKS TRUE)
endif() endif()
# Set the debug trace callables variable for config.h
if(ENABLE_DEBUG_TRACE_CALLABLES)
set(CONFIG_DEBUG_TRACE_CALLABLES TRUE)
# Suppress frame-address warnings when using __builtin_return_address()
# with values above 0 (See callableTracer.h).
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-frame-address")
endif()
# Set the world thread variable for config.h # Set the world thread variable for config.h
if(WORLD_USE_BODY_THREAD) if(WORLD_USE_BODY_THREAD)
set(CONFIG_WORLD_USE_BODY_THREAD TRUE) set(CONFIG_WORLD_USE_BODY_THREAD TRUE)
@@ -51,6 +77,10 @@ endif()
# Set the timeout variable for config.h # Set the timeout variable for config.h
set(CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS ${DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS}) set(CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS ${DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS})
# Set the stimulus buffer frame period variable for config.h
set(CONFIG_STIMBUFF_FRAME_PERIOD_MS ${STIMBUFF_FRAME_PERIOD_MS})
# Set the stimulus buffer frame retry delay variable for config.h
set(CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS ${STIMBUFF_FRAME_RETRY_DELAY_MS})
# Configure config.h # Configure config.h
configure_file( configure_file(
@@ -67,14 +97,70 @@ include_directories(
) )
# Find core dependencies # Find core dependencies
# Boost 1.72.0 is required to ensure that a certain bug where boost::asio # We cannot use header-only Boost.Asio because we need both our dlopen()'d
# objects depend on specific copies of symbols, and boost will cause a segfault # libraries and the main binary to refer to the same instances of boost::asio's
# if boost::asio objects are used inside of a dlopen()'d library, is fixed. # metadata. If we use header-only Boost.Asio, each dlopen()'d library will have
find_package(Boost 1.73.0 REQUIRED COMPONENTS system) # its own copy of boost::asio's metadata, which will cause a segfault if
# boost::asio objects are used inside of a dlopen()'d library.
#
# Honestly, I never liked this whole "header-only" idea so I'm happy to be rid
# of it.
#
# Tell CMake we're linking against the shared library (not header-only)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_HEADER_ONLY OFF)
find_package(Boost REQUIRED COMPONENTS system log)
# Define BOOST_ALL_DYN_LINK project-wide to ensure all Boost libraries use dynamic linking
add_compile_definitions(BOOST_ALL_DYN_LINK)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
find_package(FLEX REQUIRED) find_package(FLEX REQUIRED)
find_package(BISON REQUIRED) find_package(BISON REQUIRED)
# Find OpenCL 1.2 or higher: try find_package first, fall back to pkg-config
find_package(OpenCL 1.2 QUIET)
if(OpenCL_FOUND)
# Normalize find_package variables to match pkg_check_modules naming
set(OPENCL_FOUND TRUE)
set(OPENCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIRS})
# Handle both OpenCL_LIBRARY (singular) and OpenCL_LIBRARIES (plural)
if(OpenCL_LIBRARIES)
set(OPENCL_LIBRARIES ${OpenCL_LIBRARIES})
else()
set(OPENCL_LIBRARIES ${OpenCL_LIBRARY})
endif()
set(OPENCL_LIBRARY_DIRS "")
message(STATUS "Found OpenCL using find_package")
# Check if version is available and validate
if(OpenCL_VERSION)
if(OpenCL_VERSION VERSION_LESS "1.2")
message(FATAL_ERROR
"OpenCL version ${OpenCL_VERSION} found, but 1.2 or higher is required")
endif()
message(STATUS "OpenCL version: ${OpenCL_VERSION}")
else()
message(WARNING
"OpenCL version could not be determined. "
"Version 1.2+ is required at runtime.")
endif()
else()
# Fall back to pkg-config
pkg_check_modules(OPENCL OpenCL)
if(NOT OPENCL_FOUND)
message(FATAL_ERROR
"Failed to find OpenCL: both find_package and "
"pkg_check_modules failed. Try installing the "
"'ocl-icd-opencl-dev' package (or the appropriate "
"OpenCL development package for your system)."
)
endif()
message(STATUS "Found OpenCL using pkg-config")
message(WARNING
"OpenCL version could not be determined via pkg-config. "
"Version 1.2+ is required at runtime.")
endif()
# Need dlopen() and dlsym() # Need dlopen() and dlsym()
find_library(DL_LIBRARY NAMES dl ldl) find_library(DL_LIBRARY NAMES dl ldl)
if(NOT DL_LIBRARY) if(NOT DL_LIBRARY)
@@ -85,19 +171,28 @@ endif()
if(ENABLE_TESTS) if(ENABLE_TESTS)
add_subdirectory(third_party) add_subdirectory(third_party)
endif() endif()
add_subdirectory(compile)
# Add core components # Add core components
add_subdirectory(smocore) add_subdirectory(smocore)
add_subdirectory(commonLibs) add_subdirectory(commonLibs)
add_subdirectory(senseApis) add_subdirectory(stimBuffApis)
add_subdirectory(wilzorApis) add_subdirectory(wilzorApis)
add_subdirectory(devices) 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
Boost::system Boost::log
smocore smocore
${Boost_LIBRARIES}
${DL_LIBRARY} ${DL_LIBRARY}
attachmentSupport
)
# Verify Boost dynamic dependencies after build
add_custom_command(TARGET salmanoff POST_BUILD
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:salmanoff>"
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
COMMENT "Verifying Boost dynamic dependencies for salmanoff"
) )
# Add all registered DAPSS targets as dependencies # Add all registered DAPSS targets as dependencies
+86
View File
@@ -0,0 +1,86 @@
# Bug somehow related to either OpenClCollateAndMeshingEngine or PcloudStimBuff:
printSlotBytes: Slot 21 vaddr=0xfffff7fb4000 (4 bytes):
0000: 05 01 01 00 |....|
printSlotBytes: Slot 22 vaddr=0xfffff7fb5000 (4 bytes):
0000: 05 01 01 00 |....|
printSlotBytes: Slot 23 vaddr=0xfffff7fb6000 (4 bytes):
0000: 05 01 01 00 |....|
printSlotBytes: Slot 24 vaddr=0xfffff7fb7000 (4 bytes):
0000: 05 01 01 00 |....|
printSlotBytes: Slot 25 vaddr=0xfffff7fb8000 (4 bytes):
0000: 05 01 01 00 |....|
printSlotBytes: Slot 26 vaddr=0xfffff7fb9000 (4 bytes):
0000: 05 01 01 00 |....|
printSlotBytes: Slot 27 vaddr=0xfffff7fba000 (4 bytes):
0000: 05 01 01 00 |....|
printSlotBytes: Slot 28 vaddr=0xfffff7fbb000 (4 bytes):
0000: 05 01 01 00 |....|
printSlotBytes: Slot 29 vaddr=0xfffff7fbc000 (4 bytes):
0000: 05 01 01 00 |....|
produceFrameReq2_assembleDone: Successfully assembled frame 29 slots succeeded out of 30 total slots
compactCollateAndMeshFrameReq: Started compact kernel
startKernel: already running, call stop() first
produceFrameReq3_compactCollateDone: Failed to compact and collate frame
Mrntt: About to detach all sense devices.
xcbWindow_detachDeviceReq: Detached X11 window device:
Device Identifier: win0, Sensor Type: e, QualeIface API: visual-qualeiface, QualeIface API Params: (), StimBuff API: xcb, StimBuff API Params: (dev-substring ), Provider: xorg, Provider Params: (display=1 screen=0 ), Device Selector: mut
enDisablePcloudDataReq2: Command timeout for device 3JEDK380010Z39
detachDeviceReq1: Failed to disable pcloud data for stimbuff 3JEDK380010Z39
stop: Stopped stimulus buffer for device 3JEDK380010Z39
disconnectReq: Sent disconnect message to 10.42.0.139:65000
detachDeviceReq2: Successfully detached pcloud stimbuff for device 3JEDK380010Z39 and possibly also destroyed device.
Mrntt: Successfully detached 2 of 2 sense devices.
Mrntt: About to finalize all stim buff api libs.
stop: UDP Command Demuxer stopped
stop: BroadcastListener stopped
Thread 9 "rusticl queue t" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xffffca4ee140 (LWP 11695)]
0x0000fffff48517b0 in std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)>::_Bind(std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> const&) (
this=0xffffdc000c70) at /usr/include/c++/13/functional:581
581 _Bind(const _Bind&) = default;
(gdb) bt
#0 0x0000fffff48517b0 in std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)>::_Bind(std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> const&) (this=0xffffdc000c70) at /usr/include/c++/13/functional:581
#1 0x0000fffff4851818 in std::_Function_base::_Base_manager<std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> >::_M_create<std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> const&>(std::_Any_data&, std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> const&, std::integral_constant<bool, false>) (__dest=..., __f=...)
at /usr/include/c++/13/bits/std_function.h:161
#2 0x0000fffff4850704 in std::_Function_base::_Base_manager<std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr--Type <RET> for more, q to quit, c to continue without paging--c
<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> >::_M_init_functor<std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> const&>(std::_Any_data&, std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> const&) (__functor=..., __f=...) at /usr/include/c++/13/bits/std_function.h:215
#3 0x0000fffff484fbf0 in std::_Function_base::_Base_manager<std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation) (__dest=...,
__source=..., __op=std::__clone_functor) at /usr/include/c++/13/bits/std_function.h:198
#4 0x0000fffff484f0bc in std::_Function_handler<void (int), std::_Bind<void (smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq::*(smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq*, std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq>, int)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation) (__dest=...,
__source=..., __op=std::__clone_functor) at /usr/include/c++/13/bits/std_function.h:282
#5 0x0000fffff484f2b0 in std::function<void (int)>::function(std::function<void (int)> const&) (this=0xffffca4ecd40, __x=...) at /usr/include/c++/13/bits/std_function.h:391
#6 0x0000fffff484e9c0 in std::_Bind<std::function<void (int)> (int)>::_Bind<int&>(std::function<void (int)> const&, int&) (this=0xffffca4ecd40, __f=...)
at /usr/include/c++/13/functional:572
#7 0x0000fffff484e170 in std::bind<std::function<void (int)>&, int&>(std::function<void (int)>&, int&) (__f=...) at /usr/include/c++/13/functional:885
#8 0x0000fffff484aa68 in smo::stim_buff::OpenClCollatingAndMeshingEngine::compactKernelEventCallback (event_command_exec_status=0, user_data=0xffffe4009e80)
at /home/latentprion/gits/salmanoff-git/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.cpp:249
#9 0x0000ffffcb3e34b4 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#10 0x0000ffffcb3d173c in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#11 0x0000ffffcb3d1d28 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#12 0x0000ffffcb3b0b34 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#13 0x0000ffffcb40886c in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#14 0x0000ffffcb39a728 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#15 0x0000ffffcb39a7b0 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#16 0x0000ffffcb3b0a40 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#17 0x0000ffffcb3b130c in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#18 0x0000ffffcb3d2dfc in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#19 0x0000ffffcb371148 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#20 0x0000ffffcb3f9b40 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#21 0x0000ffffcb3713c8 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#22 0x0000ffffcb378988 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#23 0x0000ffffcb37120c in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#24 0x0000ffffcb371000 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#25 0x0000ffffcb392888 in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#26 0x0000ffffcb45f23c in ?? () from /lib/aarch64-linux-gnu/libRusticlOpenCL.so.1
#27 0x0000fffff7ac595c in start_thread (arg=0xfffff58cf880) at ./nptl/pthread_create.c:447
#28 0x0000fffff7b2bb0c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone3.S:76
(gdb)
## Race conditions in OClCollMeshEngn:
engine not set up or invalid
+15 -4
View File
@@ -24,14 +24,21 @@ set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
# Enable deb and rpm generators # Enable deb and rpm generators
set(CPACK_GENERATOR "DEB;RPM") set(CPACK_GENERATOR "DEB;RPM")
# DEB package specific settings # DEB package specific settings (Ubuntu)
set(CPACK_DEBIAN_PACKAGE_MAINTAINER set(CPACK_DEBIAN_PACKAGE_MAINTAINER
"Salmanoff Project <maintainer@salmanoff.org>") "Salmanoff Project <maintainer@salmanoff.org>")
set(CPACK_DEBIAN_PACKAGE_SECTION "science") set(CPACK_DEBIAN_PACKAGE_SECTION "science")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
# Target Ubuntu distribution
set(CPACK_DEBIAN_PACKAGE_DISTRIBUTION "ubuntu")
# Build dependencies (from builddeps file)
# These are needed to build the package from source
set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS
"build-essential, cmake (>= 3.16), libboost-all-dev, flex, bison, ocl-icd-opencl-dev, liburing-dev")
# Runtime dependencies (from builddeps file - runtime equivalents)
set(CPACK_DEBIAN_PACKAGE_DEPENDS set(CPACK_DEBIAN_PACKAGE_DEPENDS
"libboost-system1.74.0 | libboost-system1.73.0 | libboost-system1.72.0, " "libboost-system1.74.0 | libboost-system1.73.0 | libboost-system1.72.0, libboost-log1.74.0 | libboost-log1.73.0 | libboost-log1.72.0, libc6, libstdc++6, ocl-icd-libopencl1 | libopencl1, liburing2 | liburing1")
"libc6, libstdc++6")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libxcb1, libx11-6") set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libxcb1, libx11-6")
set(CPACK_DEBIAN_PACKAGE_SUGGESTS "livox-sdk") set(CPACK_DEBIAN_PACKAGE_SUGGESTS "livox-sdk")
@@ -39,13 +46,17 @@ set(CPACK_DEBIAN_PACKAGE_SUGGESTS "livox-sdk")
set(CPACK_RPM_PACKAGE_LICENSE "Proprietary") set(CPACK_RPM_PACKAGE_LICENSE "Proprietary")
set(CPACK_RPM_PACKAGE_GROUP "Applications/Engineering") set(CPACK_RPM_PACKAGE_GROUP "Applications/Engineering")
set(CPACK_RPM_PACKAGE_URL "https://github.com/salmanoff/salmanoff") 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_REQUIRES "boost-system >= 1.72.0, boost-log >= 1.72.0, glibc, libstdc++, ocl-icd, liburing")
set(CPACK_RPM_PACKAGE_SUGGESTS "xcb, libX11, livox-sdk") set(CPACK_RPM_PACKAGE_SUGGESTS "xcb, libX11, livox-sdk")
# Package file naming using project variables # Package file naming using project variables
set(CPACK_PACKAGE_FILE_NAME set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_PROCESSOR}") "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_PROCESSOR}")
# Enable automatic dependency detection for Debian packages
# This uses dpkg-shlibdeps to automatically detect shared library dependencies
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
# Set compression # Set compression
set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_DEB_COMPONENT_INSTALL ON)
set(CPACK_RPM_COMPONENT_INSTALL ON) set(CPACK_RPM_COMPONENT_INSTALL ON)
+3
View File
@@ -3,6 +3,9 @@
# Enable debug locking features # Enable debug locking features
option(ENABLE_DEBUG_LOCKS "Enable debug features for locking system" ON) option(ENABLE_DEBUG_LOCKS "Enable debug features for locking system" ON)
# Enable callable tracing for debugging boost::asio post operations
option(ENABLE_DEBUG_TRACE_CALLABLES "Enable callable tracing for debugging boost::asio post operations" OFF)
# Qutex deadlock detection configuration # Qutex deadlock detection configuration
# Always define the variable in cache so it appears in ccmake # Always define the variable in cache so it appears in ccmake
set(DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS 500 CACHE STRING set(DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS 500 CACHE STRING
+63
View File
@@ -0,0 +1,63 @@
# SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY
# Verifies that a target file (executable or shared library) has Boost libraries
# in its dynamic dependency list via ldd.
#
# Usage as function:
# SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY(<target_file>)
#
# Usage as script (with -P):
# cmake -DVERIFY_FILE=<target_file> -P VerifyBoostDynamic.cmake
#
# This function/script:
# 1. Runs ldd on the target file
# 2. Checks for boost libraries in the dependency list
# 3. Reports success or failure with appropriate messages
#
function(SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY target_file)
_verify_boost_dynamic_dependency("${target_file}")
endfunction()
# Internal implementation that can be called from script mode or function mode
function(_verify_boost_dynamic_dependency target_file)
if(NOT EXISTS "${target_file}")
message(WARNING "SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY: Target file '${target_file}' does not exist")
return()
endif()
# Run ldd on the target file
execute_process(
COMMAND ldd "${target_file}"
OUTPUT_VARIABLE ldd_output
ERROR_VARIABLE ldd_error
RESULT_VARIABLE ldd_result
)
if(ldd_result)
message(WARNING "SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY: Failed to run ldd on '${target_file}': ${ldd_error}")
return()
endif()
# Check if output contains boost libraries
string(TOLOWER "${ldd_output}" ldd_output_lower)
string(FIND "${ldd_output_lower}" "libboost" boost_found)
if(boost_found EQUAL -1)
message(STATUS "SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY: WARNING - No Boost libraries found in dependencies of '${target_file}'")
message(STATUS "ldd output:")
message(STATUS "${ldd_output}")
else()
# Extract boost library lines
string(REGEX MATCHALL "libboost[^\n]*" boost_libs "${ldd_output}")
message(STATUS "SMO_VERIFY_BOOST_DYNAMIC_DEPENDENCY: SUCCESS - Boost libraries found in '${target_file}':")
foreach(boost_lib ${boost_libs})
string(STRIP "${boost_lib}" boost_lib_stripped)
message(STATUS " ${boost_lib_stripped}")
endforeach()
endif()
endfunction()
# Script mode: if VERIFY_FILE is defined, run the verification
if(VERIFY_FILE)
_verify_boost_dynamic_dependency("${VERIFY_FILE}")
endif()
+1 -1
View File
@@ -31,7 +31,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Search for libraries and headers in the target directories # Search for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# Set pkg-config to use the cross-compiled libraries # Set pkg-config to use the cross-compiled libraries
set(ENV{PKG_CONFIG_PATH} "/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig") set(ENV{PKG_CONFIG_PATH} "/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig")
+70
View File
@@ -0,0 +1,70 @@
# ----------------------------------------------------------------------------------
# MANDATORY USER VARIABLE
# ----------------------------------------------------------------------------------
# IMPORTANT: This variable MUST be set when running CMake to specify where the
# laptop's sysroot (the root directory of the mounted laptop filesystem) is located.
#
# Usage example: cmake -DCMAKE_TOOLCHAIN_FILE=laptop_x86_sysroot.cmake
# -DTARGET_SYSROOT=/mnt/laptop_sysroot/ <path_to_source>
#
# If the variable is not defined, we fall back to a common system root path for safety.
if(NOT DEFINED TARGET_SYSROOT)
set(TARGET_SYSROOT "/usr/lib/x86_64-linux-gnu")
message(STATUS "TARGET_SYSROOT not explicitly defined. Defaulting to ${TARGET_SYSROOT}")
endif()
message(STATUS "Using TARGET_SYSROOT: ${TARGET_SYSROOT}")
set(TARGET_TRIPLE x86_64-linux-gnu) # Standard Debian/Ubuntu triple
# ----------------------------------------------------------------------------------
# SYSROOT and COMPILER CONFIGURATION
# ----------------------------------------------------------------------------------
set(CMAKE_CROSSCOMPILING TRUE)
set(CMAKE_SYSROOT ${TARGET_SYSROOT})
message(STATUS "Using CMAKE_SYSROOT: ${CMAKE_SYSROOT}")
# The CMAKE_FIND_ROOT_PATH tells CMake where to look for programs, libraries, etc.
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# 1. Architecture and Platform Identification
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_C_COMPILER ${TARGET_TRIPLE}-gcc)
set(CMAKE_CXX_COMPILER ${TARGET_TRIPLE}-g++)
# ----------------------------------------------------------------------------------
# PKG-CONFIG CONFIGURATION (CRUCIAL FOR CROSS-COMPILING)
# ----------------------------------------------------------------------------------
# 1. Define the search path for .pc files, relative to the sysroot.
# This ensures we look in the target's standard pkgconfig locations.
set(PKG_CONFIG_SEARCH_PATHS
"${CMAKE_SYSROOT}/usr/lib/${TARGET_TRIPLE}/pkgconfig" # Primary location on Debian/Ubuntu
"${CMAKE_SYSROOT}/usr/share/pkgconfig" # Secondary shared location
"${CMAKE_SYSROOT}/usr/lib/pkgconfig" # Another common location
)
# Join the paths using the system's path separator (colon on Linux)
string(REPLACE ";" ":" PKG_CONFIG_LIBDIR_STRING "${PKG_CONFIG_SEARCH_PATHS}")
# Set the environment variable PKG_CONFIG_LIBDIR
# This tells pkg-config exactly where to find the x86_64 .pc files.
# 2. Set the sysroot directory for pkg-config
# This tells pkg-config to prepend CMAKE_SYSROOT to any paths it finds in the .pc files.
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})
set(ENV{PKG_CONFIG_LIBDIR} ${PKG_CONFIG_LIBDIR_STRING})
set(ENV{PKG_CONFIG_PATH} "")
message(STATUS "PKG_CONFIG_SYSROOT_DIR set to: ${CMAKE_SYSROOT}")
message(STATUS "PKG_CONFIG_LIBDIR set to: ${PKG_CONFIG_LIBDIR_STRING}")
# ----------------------------------------------------------------------------------
# CMAkE FIND BEHAVIOR
# ----------------------------------------------------------------------------------
+1
View File
@@ -1,2 +1,3 @@
add_subdirectory(xcbXorg) add_subdirectory(xcbXorg)
add_subdirectory(livoxProto1) add_subdirectory(livoxProto1)
add_subdirectory(attachmentSupport)
@@ -0,0 +1,26 @@
add_library(attachmentSupport SHARED
compute.cpp
stimulusProducer.cpp
stagingBuffer.cpp
)
target_include_directories(attachmentSupport PUBLIC
${Boost_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
target_link_libraries(attachmentSupport PUBLIC
Boost::system
Boost::log
)
# Verify Boost dynamic dependencies after build
add_custom_command(TARGET attachmentSupport POST_BUILD
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:attachmentSupport>"
-P ${CMAKE_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
COMMENT "Verifying Boost dynamic dependencies for attachmentSupport"
)
# Install rules
install(TARGETS attachmentSupport DESTINATION lib)
+147
View File
@@ -0,0 +1,147 @@
#include <user/compute.h>
#include <stdexcept>
#include <string>
#include <vector>
#include <iostream>
#include <string_view>
namespace smo {
namespace compute {
// Helper function to parse OpenCL version string
static std::pair<int, int> parseOpenClVersion(const std::string& versionStr)
{
size_t spacePos = versionStr.find(' ');
if (spacePos == std::string::npos) { return {-1, -1}; }
std::string versionNum = versionStr.substr(spacePos + 1);
size_t dotPos = versionNum.find('.');
if (dotPos == std::string::npos) { return {-1, -1}; }
try {
int major = std::stoi(versionNum.substr(0, dotPos));
int minor = std::stoi(versionNum.substr(dotPos + 1));
return {major, minor};
} catch (const std::exception&) {
return {-1, -1};
}
}
// Implementation of validateOpenClVersion (declared in user/compute.h)
bool validateOpenClVersion(
std::string_view versionStr, std::string_view versionType,
int minMajor, int minMinor)
{
auto [major, minor] = parseOpenClVersion(std::string(versionStr));
if (major == -1 && minor == -1)
{
std::cerr << __func__ << ": failed to parse OpenCL " << versionType
<< " version: " << versionStr << std::endl;
return false;
}
if (major < minMajor || (major == minMajor && minor < minMinor))
{
std::cerr << __func__ << ": OpenCL " << versionType << " version "
<< major << "." << minor << " found, but " << minMajor << "."
<< minMinor << " or higher is required" << std::endl;
return false;
}
std::cout << __func__ << ": OpenCL " << versionType << " version: "
<< versionStr << std::endl;
return true;
}
ComputeDevice::ComputeDevice(cl_platform_id platformId, cl_device_id deviceId)
: platform(platformId), device(deviceId),
context(nullptr), commandQueue(nullptr)
{
cl_int err;
// Create context for this device
context = clCreateContext(
nullptr, 1, &device,
nullptr, nullptr, &err);
if (err != CL_SUCCESS || !context)
{
throw std::runtime_error(
std::string(__func__) + ": failed to create context for device: " +
std::to_string(err));
}
// Create command queue
cl_command_queue_properties queueProps = 0;
commandQueue = clCreateCommandQueue(
context, device, queueProps, &err);
if (err != CL_SUCCESS || !commandQueue)
{
clReleaseContext(context);
context = nullptr;
throw std::runtime_error(
std::string(__func__) + ": failed to create command queue for "
"device: " + std::to_string(err));
}
}
ClBuffer::ClBuffer(void* hostPtr, size_t size, cl_mem_flags flags,
const std::vector<std::shared_ptr<ComputeDevice>>& devices)
: hostPtr(hostPtr), size(size), flags(flags)
{
associations.reserve(devices.size());
// Create a buffer for each device's context
for (const auto& device : devices)
{
if (!device->context) { continue; }
cl_int err;
cl_mem_flags bufferFlags = CL_MEM_USE_HOST_PTR | flags;
cl_mem buffer = clCreateBuffer(
device->context,
bufferFlags,
size, hostPtr,
&err);
if (err != CL_SUCCESS || !buffer)
{
// Release any buffers already created before throwing
for (auto& assoc : associations)
{
if (assoc.buffer) {
clReleaseMemObject(assoc.buffer);
}
}
throw std::runtime_error(
std::string(__func__) + ": failed to create buffer for "
"device: " + std::to_string(err));
}
associations.emplace_back(buffer, device);
}
}
cl_mem ClBuffer::getAssociatedBufferHandleForDevice(
const std::shared_ptr<ComputeDevice>& device) const
{
if (!device)
{
throw std::invalid_argument(std::string(__func__)
+ ": device is nullptr");
}
for (const auto& assoc : associations)
{
if (assoc.device == device) {
return assoc.buffer;
}
}
return nullptr;
}
} // namespace compute
} // namespace smo
@@ -0,0 +1,236 @@
#include <user/stagingBuffer.h>
#include <unistd.h>
#include <cstdint>
#include <stdexcept>
#include <sys/mman.h>
#include <vector>
#include <user/frameAssemblyDesc.h>
namespace smo {
namespace stim_buff {
// Static defaults for io_uring
const StagingBuffer::IOEngineConstraints
StagingBuffer::IOEngineConstraints::ioUringConstraints(
// slotStartAlignmentByteVal (page alignment for DMA)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
// slotPadToNBytes (MTU 1500 - UDP/IP header 28)
1472,
// frameStartAlignmentByteVal (page alignment for DMA)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
// framePadToNBytes (MTU 1500 - UDP/IP header 28)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE))
);
// Static defaults for OpenCL input
const StagingBuffer::IOEngineConstraints
StagingBuffer::IOEngineConstraints::openClInputConstraints(
// slotStartAlignmentByteVal (page alignment)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
// slotPadToNBytes (XYZI point size)
16,
// frameStartAlignmentByteVal (page alignment)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
// framePadToNBytes (pointer size)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE))
);
// Helper function to calculate maximum alignment needed for first slot
// (must satisfy both frame and slot alignment)
static size_t calculateMaxAlignment(
size_t frameStartAlignmentByteVal,
size_t slotStartAlignmentByteVal)
{
if (frameStartAlignmentByteVal >= slotStartAlignmentByteVal)
{
if (frameStartAlignmentByteVal % slotStartAlignmentByteVal == 0)
{ return frameStartAlignmentByteVal; }
else
{
// Need LCM, but for simplicity use the larger alignment
// In practice, alignments are usually powers of 2, so this should work
return std::max(
frameStartAlignmentByteVal, slotStartAlignmentByteVal);
}
}
else
{
if (slotStartAlignmentByteVal % frameStartAlignmentByteVal == 0)
{ return slotStartAlignmentByteVal; }
else
{
return std::max(
frameStartAlignmentByteVal, slotStartAlignmentByteVal);
}
}
}
void StagingBuffer::computeSlotStrideAndBufferSize()
{
// Slot stride is the maximum of alignment and padding, rounded up to a multiple of alignment
size_t minSlotStride = std::max(
inputConstraints.slotStartAlignmentByteVal,
inputConstraints.slotPadToNBytes);
slotStrideNBytes = ((minSlotStride + inputConstraints.slotStartAlignmentByteVal - 1)
/ inputConstraints.slotStartAlignmentByteVal)
* inputConstraints.slotStartAlignmentByteVal;
// Calculate maximum alignment needed for first slot (must satisfy both frame and slot alignment)
size_t maxAlignment = calculateMaxAlignment(
inputConstraints.frameStartAlignmentByteVal,
inputConstraints.slotStartAlignmentByteVal);
// Calculate minimum buffer size
size_t minBufferSize = std::max(
inputConstraints.framePadToNBytes,
inputConstraints.slotPadToNBytes);
// Calculate total size needed for nSlots slots
size_t slotAreaSize = nSlots * slotStrideNBytes;
// Add padding space at buffer start for alignment offset (worst case: max alignment - 1)
size_t alignmentPadding = maxAlignment - 1;
// Total size needed: alignment padding + slot area, then ensure minimum is met
size_t rawSize = alignmentPadding + slotAreaSize;
if (rawSize < minBufferSize)
{ rawSize = minBufferSize; }
// Align up to the maximum alignment to ensure we can always find a valid offset
bufferNBytes = ((rawSize + maxAlignment - 1) / maxAlignment) * maxAlignment;
}
// Static member function to calculate offset and validate invariants
size_t StagingBuffer::calculateFirstSlotOffsetAndValidate(
uint8_t* buffer,
size_t bufferNBytes,
size_t nSlots,
size_t slotStrideNBytes,
const StagingBuffer::IOEngineConstraints& inputConstraints)
{
// Calculate maximum alignment needed for first slot
size_t maxAlignment = calculateMaxAlignment(
inputConstraints.frameStartAlignmentByteVal,
inputConstraints.slotStartAlignmentByteVal);
// Calculate offset to align first slot to both frame and slot alignment
uintptr_t bufferAddr = reinterpret_cast<uintptr_t>(buffer);
uintptr_t alignedAddr = ((bufferAddr + maxAlignment - 1) / maxAlignment)
* maxAlignment;
size_t firstSlotOffsetNBytes = alignedAddr - bufferAddr;
// Validate invariants with exceptions
uint8_t* firstSlotAddr = buffer + firstSlotOffsetNBytes;
if (
reinterpret_cast<uintptr_t>(firstSlotAddr)
% inputConstraints.frameStartAlignmentByteVal != 0)
{
throw std::runtime_error(std::string(__func__)
+ ": StagingBuffer: first slot address not aligned to "
+ std::to_string(inputConstraints.frameStartAlignmentByteVal));
}
if (
reinterpret_cast<uintptr_t>(firstSlotAddr)
% inputConstraints.slotStartAlignmentByteVal != 0)
{
throw std::runtime_error(std::string(__func__)
+ ": StagingBuffer: first slot address not aligned to "
+ std::to_string(inputConstraints.slotStartAlignmentByteVal));
}
size_t minBufferSize = std::max(
inputConstraints.framePadToNBytes,
inputConstraints.slotPadToNBytes);
if (bufferNBytes < minBufferSize)
{
throw std::runtime_error(std::string(__func__)
+ ": StagingBuffer: buffer size less than minimum required (max of "
+ std::to_string(inputConstraints.framePadToNBytes)
+ " and "
+ std::to_string(inputConstraints.slotPadToNBytes)
+ ")");
}
if (firstSlotOffsetNBytes + nSlots * slotStrideNBytes
> bufferNBytes)
{
throw std::runtime_error(std::string(__func__)
+ ": StagingBuffer: buffer size insufficient to hold "
+ std::to_string(nSlots)
+ " slots with proper alignment and padding");
}
return firstSlotOffsetNBytes;
}
StagingBuffer::StagingBuffer(
const IOEngineConstraints& inputEngineConstraints_,
const IOEngineConstraints& /*outputEngineConstraints*/,
size_t nSlots)
: buffer(nullptr, MmapDeleter(0)), bufferNBytes(0),
nSlots(nSlots), slotStrideNBytes(0),
firstSlotOffsetNBytes(0),
inputConstraints(inputEngineConstraints_),
assemblingFlag(false)
{
if (nSlots == 0)
{
throw std::invalid_argument(std::string(__func__)
+ ": StagingBuffer: nSlots must be > 0");
}
computeSlotStrideAndBufferSize();
/* Allocate buffer using mmap() for io_uring registration
* MAP_ANONYMOUS | MAP_PRIVATE creates anonymous, non-file-backed memory
*/
void* mmapped = mmap(
nullptr, bufferNBytes,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1, 0);
if (mmapped == MAP_FAILED)
{
throw std::runtime_error(std::string(__func__)
+ ": StagingBuffer: mmap() failed");
}
buffer = std::unique_ptr<uint8_t, MmapDeleter>(
static_cast<uint8_t*>(mmapped), MmapDeleter(bufferNBytes));
currentNBytes.store(0);
// Lock the buffer in memory to prevent swapping
if (mlock(buffer.get(), bufferNBytes) != 0)
{
throw std::runtime_error(std::string(__func__)
+ ": StagingBuffer: mlock() failed");
}
// Calculate offset and validate invariants (helper function in .cpp)
firstSlotOffsetNBytes = StagingBuffer::calculateFirstSlotOffsetAndValidate(
buffer.get(), bufferNBytes, nSlots,
slotStrideNBytes, inputConstraints);
// Build FrameAssemblyDesc once
std::vector<FrameAssemblyDesc::SlotDesc> slots;
slots.reserve(nSlots);
uint8_t *frameBase = buffer.get() + firstSlotOffsetNBytes;
for (size_t i = 0; i < nSlots; ++i)
{
size_t off = i * slotStrideNBytes;
FrameAssemblyDesc::SlotDesc s{
off, frameBase + off, inputConstraints.slotPadToNBytes};
slots.push_back(s);
}
frameDesc = std::make_shared<FrameAssemblyDesc>(
nSlots, inputConstraints.slotPadToNBytes, bufferNBytes,
std::move(slots));
}
} // namespace stim_buff
} // namespace smo
@@ -0,0 +1,184 @@
#include <boostAsioLinkageFix.h>
#include <config.h>
#include <iostream>
#include <chrono>
#include <algorithm>
#include <boost/asio/io_service.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/system/error_code.hpp>
#include <opts.h>
#include <componentThread.h>
#include <spinLock.h>
#include <user/stimulusProducer.h>
#include <user/stimulusBuffer.h>
namespace smo {
namespace stim_buff {
std::shared_ptr<StimulusBuffer> StimulusProducer::getAttachedStimulusBuffer(
const std::shared_ptr<device::DeviceAttachmentSpec>& spec) const
{
for (const auto& buffer : attachedStimulusBuffers)
{
if (buffer && buffer->deviceAttachmentSpec &&
*buffer->deviceAttachmentSpec == *spec)
{
return buffer;
}
}
return nullptr;
}
bool StimulusProducer::hasBufferWithQualeIfaceApi(
const std::string& qualeIfaceApi) const
{
for (const auto& buffer : attachedStimulusBuffers)
{
if (!buffer || !buffer->deviceAttachmentSpec)
{
throw std::runtime_error(
"StimulusProducer::hasBufferWithQualeIfaceApi: encountered "
"null buffer or null deviceAttachmentSpec in "
"attachedStimulusBuffers (should never happen)");
}
if (buffer->deviceAttachmentSpec->qualeIfaceApi != qualeIfaceApi)
{ continue; }
return true;
}
return false;
}
void StimulusProducer::destroyAttachedStimulusBuffer(
const std::shared_ptr<StimulusBuffer>& buffer)
{
if (!buffer) { return; }
auto it = std::find(
attachedStimulusBuffers.begin(),
attachedStimulusBuffers.end(),
buffer);
if (it != attachedStimulusBuffers.end()) {
attachedStimulusBuffers.erase(it);
}
}
void StimulusProducer::stop()
{
{
SpinLock::Guard lock(shouldContinueLock);
shouldContinue = false;
}
// Cancel timer immediately
timer.cancel();
std::cout << __func__ << ": Stopped stimulus producer for device "
<< deviceAttachmentSpec->deviceSelector << std::endl;
}
void StimulusProducer::scheduleNextTimeout(int delayMs)
{
if (!shouldContinue)
{ return; }
// Schedule the next timeout using the provided delay
timer.expires_from_now(
boost::posix_time::milliseconds(delayMs));
timer.async_wait(
std::bind(
&StimulusProducer::onTimeout, this, std::placeholders::_1));
}
void StimulusProducer::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 << "StimulusProducer: Timer error: " << error.message()
<< std::endl;
return;
}
SpinLock::Guard lock(shouldContinueLock);
if (!shouldContinue)
{ return; }
/** EXPLANATION:
* We need to ensure that there's only ever one stimframe being produced
* during any CONFIG_STIMBUFF_FRAME_PERIOD_MS period. To guarantee this, we
* use a spinlock.
*
* When a new frame is to be produced, the async producer will first acquire
* the frameAssemblyLimiter spinlock. This way, when the next timeout is
* fired it can check whether its predecessor stimframe has finished being
* produced. If the preceding stimframe is still being produced, then we'll
* sleep for CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS ms before trying again.
*/
int nextWakeupDelayMs;
bool deferred = false;
if (frameAssemblyRateLimiter.tryAcquire())
{
nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS;
// Check if we're ending a deferral period
if (nDeferrals > 0)
{
auto deferralEndTime = std::chrono::high_resolution_clock::now();
auto duration = deferralEndTime - deferralStartTime;
auto durationMs = std::chrono::duration_cast<
std::chrono::milliseconds>(duration);
std::cout << __func__ << ": Deferral period ended. "
<< "Total deferrals: " << nDeferrals
<< ", Duration: " << durationMs.count() << "ms" << std::endl;
nDeferrals = 0;
}
/** EXPLANATION:
* Call the derived class's frame production handler
* Note: The derived class's frame production handler (aka
* its implementation of stimFrameProductionTimesliceInd()) must
* release the lock when frame production completes
*/
stimFrameProductionTimesliceInd();
}
else
{
nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS;
deferred = true;
++nDeferrals;
// If this is first deferral, capture start stamp and print message
if (nDeferrals == 1)
{
deferralStartTime = std::chrono::high_resolution_clock::now();
std::cerr << __func__ << ": Deferral period beginning. "
"Configured deferral period: " << nextWakeupDelayMs << "ms"
<< std::endl;
}
}
scheduleNextTimeout(nextWakeupDelayMs);
// FIXME: We should be able to release the start/stop lock at this point.
if (deferred && OptionParser::getOptions().verbose)
{
std::cerr << __func__ << ": Deferring frame by " << nextWakeupDelayMs
<< "ms due to rate limit." << std::endl;
}
}
} // namespace stim_buff
} // namespace smo
+11 -1
View File
@@ -7,12 +7,22 @@ if(ENABLE_LIB_livoxProto1)
device.cpp device.cpp
protocol.cpp protocol.cpp
broadcastListener.cpp broadcastListener.cpp
udpCommandDemuxer.cpp
) )
# Set config define for header generation # Set config define for header generation
add_compile_definitions(CONFIG_LIB_LIVOXPROTO1_ENABLED) add_compile_definitions(CONFIG_LIB_LIVOXPROTO1_ENABLED)
target_include_directories(livoxProto1 PUBLIC ${Boost_INCLUDE_DIRS}) target_include_directories(livoxProto1 PUBLIC ${Boost_INCLUDE_DIRS})
target_link_libraries(livoxProto1 ${Boost_LIBRARIES}) target_link_libraries(livoxProto1 PUBLIC
Boost::system Boost::log
attachmentSupport)
# Verify Boost dynamic dependencies after build
add_custom_command(TARGET livoxProto1 POST_BUILD
COMMAND ${CMAKE_COMMAND} -DVERIFY_FILE="$<TARGET_FILE:livoxProto1>"
-P ${CMAKE_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake
COMMENT "Verifying Boost dynamic dependencies for livoxProto1"
)
# Install rules # Install rules
install(TARGETS livoxProto1 DESTINATION lib) install(TARGETS livoxProto1 DESTINATION lib)
+35 -26
View File
@@ -1,7 +1,10 @@
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <functional>
#include <opts.h> #include <opts.h>
#include <componentThread.h>
#include "broadcastListener.h" #include "broadcastListener.h"
#include "core.h"
namespace livoxProto1 { namespace livoxProto1 {
namespace comms { namespace comms {
@@ -52,7 +55,6 @@ void BroadcastListener::broadcastMsgInd(
return; return;
} }
// Use placement new to construct BroadcastMessage in the buffer // Use placement new to construct BroadcastMessage in the buffer
BroadcastMessage* msg = new (bcastMsgRecvBuffer) BroadcastMessage; BroadcastMessage* msg = new (bcastMsgRecvBuffer) BroadcastMessage;
@@ -92,30 +94,34 @@ void BroadcastListener::broadcastMsgInd(
reinterpret_cast<const char*>(msg->broadcast_code)); reinterpret_cast<const char*>(msg->broadcast_code));
// Early return if device already exists // Early return if device already exists
smo::SpinLock::Guard lock(isListeningLock);
if (deviceExists(broadcastCode)) if (deviceExists(broadcastCode))
{ {
// Device already exists, just log the update // Device already exists, just log the update
if (OptionParser::getOptions().verbose) if (getProtoState().smoCallbacks.OptionParser_getOptions().verbose)
{ {
std::cout << __func__ std::cout << __func__
<< ": Received broadcast from known device: " << ": Received broadcast from known device: "
<< broadcastCode << " at " << senderIP << "\n"; << broadcastCode << " at " << senderIP << "\n";
} }
return; }
else
{
// 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";
} }
// Create new DiscoveredDevice using conversion constructor startReceive();
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) void BroadcastListener::start(void)
{ {
if (isListening.load()) { return; } if (isListening) { return; }
try try
{ {
@@ -127,10 +133,15 @@ void BroadcastListener::start(void)
* We should also set up a timer to check for devices that have gone * We should also set up a timer to check for devices that have gone
* away. * away.
*/ */
socket.open(boost::asio::ip::udp::v4()); {
socket.bind(listeningEndpoint); smo::SpinLock::Guard lock(isListeningLock);
socket.open(boost::asio::ip::udp::v4());
socket.bind(listeningEndpoint);
isListening = true;
}
isListening.store(true);
// Start the first async receive operation // Start the first async receive operation
startReceive(); startReceive();
std::cout << __func__ << ": BroadcastListener started on port " std::cout << __func__ << ": BroadcastListener started on port "
@@ -138,7 +149,7 @@ void BroadcastListener::start(void)
} }
catch (const boost::system::system_error& e) catch (const boost::system::system_error& e)
{ {
isListening.store(false); isListening = false;
std::cerr << __func__ << ": Failed to start BroadcastListener: " std::cerr << __func__ << ": Failed to start BroadcastListener: "
<< e.what() << std::endl; << e.what() << std::endl;
throw; throw;
@@ -147,27 +158,25 @@ void BroadcastListener::start(void)
void BroadcastListener::startReceive(void) void BroadcastListener::startReceive(void)
{ {
if (!isListening.load()) { return; } if (!isListening) { return; }
socket.async_receive_from( socket.async_receive_from(
boost::asio::buffer(bcastMsgRecvBuffer, sizeof(bcastMsgRecvBuffer)), boost::asio::buffer(bcastMsgRecvBuffer, sizeof(bcastMsgRecvBuffer)),
senderEndpoint, senderEndpoint,
[this](const boost::system::error_code& ec, std::size_t bytes_received) std::bind(
{ &BroadcastListener::broadcastMsgInd, this,
broadcastMsgInd(ec, bytes_received); std::placeholders::_1, std::placeholders::_2)
// Continue listening for the next packet
if (isListening.load())
{ startReceive(); }
}
); );
} }
void BroadcastListener::stop(void) void BroadcastListener::stop(void)
{ {
if (!isListening.load()) { return; } {
smo::SpinLock::Guard lock(isListeningLock);
if (!isListening) { return; }
isListening.store(false); isListening = false;
}
try try
{ {
+5 -1
View File
@@ -1,11 +1,14 @@
#ifndef BROADCAST_LISTENER_H #ifndef BROADCAST_LISTENER_H
#define BROADCAST_LISTENER_H #define BROADCAST_LISTENER_H
#include <boostAsioLinkageFix.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include <memory> #include <memory>
#include <atomic> #include <atomic>
#include <boost/asio/ip/udp.hpp>
#include <user/senseApiDesc.h> #include <user/senseApiDesc.h>
#include <spinLock.h>
#include "device.h" #include "device.h"
namespace livoxProto1 { namespace livoxProto1 {
@@ -65,7 +68,8 @@ private:
boost::asio::ip::udp::socket socket; boost::asio::ip::udp::socket socket;
boost::asio::ip::udp::endpoint listeningEndpoint, senderEndpoint; boost::asio::ip::udp::endpoint listeningEndpoint, senderEndpoint;
std::atomic<bool> isListening; smo::SpinLock isListeningLock;
bool isListening;
uint8_t bcastMsgRecvBuffer[UDP_BCAST_MSG_BUFFER_NBYTES]; uint8_t bcastMsgRecvBuffer[UDP_BCAST_MSG_BUFFER_NBYTES];
}; };
+10 -7
View File
@@ -29,7 +29,8 @@ ProtoState& getProtoState()
} }
DeviceManager::DeviceManager() DeviceManager::DeviceManager()
: broadcastListener(protoState.componentThread) : broadcastListener(protoState.componentThread),
udpCommandDemuxer(protoState.componentThread, *this)
{ {
broadcastListener.setDeviceGoneAwayCb(deviceGoneAwayInd); broadcastListener.setDeviceGoneAwayCb(deviceGoneAwayInd);
} }
@@ -113,7 +114,7 @@ public:
// Connection successful, add device to collection // Connection successful, add device to collection
context->deviceManager.devices.push_back(context->pendingDevice); context->deviceManager.devices.push_back(context->pendingDevice);
if (OptionParser::getOptions().verbose) if (getProtoState().smoCallbacks.OptionParser_getOptions().verbose)
{ {
std::cout << __func__ << ": Successfully connected and added device " std::cout << __func__ << ": Successfully connected and added device "
<< context->pendingDevice->discoveredDevice.deviceIdentifier << context->pendingDevice->discoveredDevice.deviceIdentifier
@@ -128,7 +129,7 @@ public:
void DeviceManager::getOrCreateDeviceReq( void DeviceManager::getOrCreateDeviceReq(
const std::string &deviceIdentifier, const std::string &deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread, const std::shared_ptr<smo::ComponentThread>& componentThread,
int handshakeTimeoutMs, int retryDelayMs, int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits, const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort, uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback) smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback)
@@ -162,7 +163,7 @@ void DeviceManager::getOrCreateDeviceReq(
// Device doesn't exist, create a new one but don't add it to collection yet // Device doesn't exist, create a new one but don't add it to collection yet
auto newDevice = std::make_shared<Device>( auto newDevice = std::make_shared<Device>(
deviceIdentifier, componentThread, deviceIdentifier, componentThread,
handshakeTimeoutMs, retryDelayMs, commandTimeoutMs, retryDelayMs,
smoIp, smoSubnetNbits, smoIp, smoSubnetNbits,
dataPort, cmdPort, imuPort); dataPort, cmdPort, imuPort);
@@ -229,7 +230,7 @@ void DeviceManager::destroyDeviceReq(
std::shared_ptr<Device> device = getDevice(dev->discoveredDevice). std::shared_ptr<Device> device = getDevice(dev->discoveredDevice).
value_or(nullptr); value_or(nullptr);
if (!device) if (!device || device->nAttachedStimulusProducers > 0)
{ {
callback.callbackFn(false); callback.callbackFn(false);
return; return;
@@ -245,7 +246,7 @@ void DeviceManager::destroyDeviceReq(
} }
void main(const std::shared_ptr<smo::ComponentThread> &componentThread, void main(const std::shared_ptr<smo::ComponentThread> &componentThread,
const smo::sense_api::SmoCallbacks& smoCallbacks) const smo::stim_buff::SmoCallbacks& smoCallbacks)
{ {
if (protoState.isInitialized) { if (protoState.isInitialized) {
return; return;
@@ -256,6 +257,7 @@ void main(const std::shared_ptr<smo::ComponentThread> &componentThread,
protoState.smoCallbacks = smoCallbacks; protoState.smoCallbacks = smoCallbacks;
protoState.deviceManager = std::make_unique<DeviceManager>(); protoState.deviceManager = std::make_unique<DeviceManager>();
protoState.deviceManager->broadcastListener.start(); protoState.deviceManager->broadcastListener.start();
protoState.deviceManager->udpCommandDemuxer.start();
} }
void exit(void) void exit(void)
@@ -264,10 +266,11 @@ void exit(void)
return; return;
} }
protoState.deviceManager->udpCommandDemuxer.stop();
protoState.deviceManager->broadcastListener.stop(); protoState.deviceManager->broadcastListener.stop();
protoState.deviceManager.reset(); protoState.deviceManager.reset();
protoState.componentThread.reset(); protoState.componentThread.reset();
protoState.isInitialized = false; protoState.isInitialized = false;
} }
} // namespace livoxProto1 } // namespace livoxProto1
+5 -3
View File
@@ -9,6 +9,7 @@
#include <user/senseApiDesc.h> #include <user/senseApiDesc.h>
#include "device.h" #include "device.h"
#include "broadcastListener.h" #include "broadcastListener.h"
#include "udpCommandDemuxer.h"
#include "livoxProto1.h" #include "livoxProto1.h"
#include <callback.h> #include <callback.h>
@@ -25,7 +26,7 @@ public:
void getOrCreateDeviceReq( void getOrCreateDeviceReq(
const std::string &deviceIdentifier, const std::string &deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread, const std::shared_ptr<smo::ComponentThread>& componentThread,
int handshakeTimeoutMs, int retryDelayMs, int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits, const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort, uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback); smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
@@ -50,6 +51,7 @@ private:
public: public:
std::vector<std::shared_ptr<Device>> devices; std::vector<std::shared_ptr<Device>> devices;
comms::BroadcastListener broadcastListener; comms::BroadcastListener broadcastListener;
comms::UdpCommandDemuxer udpCommandDemuxer;
// Nested continuation class for async device creation // Nested continuation class for async device creation
class GetOrCreateDeviceReq; class GetOrCreateDeviceReq;
@@ -58,7 +60,7 @@ public:
void main( void main(
const std::shared_ptr<smo::ComponentThread> &componentThread, const std::shared_ptr<smo::ComponentThread> &componentThread,
const smo::sense_api::SmoCallbacks& smoCallbacks); const smo::stim_buff::SmoCallbacks& smoCallbacks);
void exit(void); void exit(void);
// Global state structure // Global state structure
@@ -67,7 +69,7 @@ struct ProtoState
bool isInitialized = false; bool isInitialized = false;
std::shared_ptr<smo::ComponentThread> componentThread; std::shared_ptr<smo::ComponentThread> componentThread;
std::unique_ptr<DeviceManager> deviceManager; std::unique_ptr<DeviceManager> deviceManager;
smo::sense_api::SmoCallbacks smoCallbacks; smo::stim_buff::SmoCallbacks smoCallbacks;
}; };
// Access to global state for extern "C" functions // Access to global state for extern "C" functions
File diff suppressed because it is too large Load Diff
+154 -17
View File
@@ -1,19 +1,35 @@
#ifndef LIVOX_PROTO1_DEVICE_H #ifndef LIVOX_PROTO1_DEVICE_H
#define LIVOX_PROTO1_DEVICE_H #define LIVOX_PROTO1_DEVICE_H
#include <boostAsioLinkageFix.h>
#include <string> #include <string>
#include <cstdint> #include <cstdint>
#include <cstddef>
#include <memory> #include <memory>
#include <atomic> #include <atomic>
#include <optional> #include <optional>
#include <functional> #include <functional>
#include <unordered_map>
#include <stdexcept>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <unistd.h> #include <unistd.h>
#include <boost/asio.hpp> #include <boost/asio/deadline_timer.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include "protocol.h" #include "protocol.h"
#include <callback.h> #include <callback.h>
#include <spinLock.h>
// Custom hash function for std::pair<uint8_t, uint8_t>
namespace std {
template<>
struct hash<std::pair<uint8_t, uint8_t>> {
size_t operator()(const std::pair<uint8_t, uint8_t>& p) const noexcept {
return (static_cast<size_t>(p.first) << 8) | static_cast<size_t>(p.second);
}
};
}
// Forward declaration // Forward declaration
namespace smo { namespace smo {
@@ -62,25 +78,15 @@ class Device
public: public:
Device(const std::string &deviceIdentifier, Device(const std::string &deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread, const std::shared_ptr<smo::ComponentThread>& componentThread,
int handshakeTimeoutMs, int retryDelayMs, int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits, const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort); uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
~Device(); ~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: private:
// Heartbeat mechanism // Heartbeat mechanism
void startHeartbeat(); void startHeartbeat();
void stopHeartbeat();
void sendHeartbeat(); void sendHeartbeat();
void onHeartbeatTimer(const boost::system::error_code& error); void onHeartbeatTimer(const boost::system::error_code& error);
std::string generateClientDeviceIpFromSerialNumber( std::string generateClientDeviceIpFromSerialNumber(
@@ -95,21 +101,66 @@ private:
class ConnectByDeviceIdentifierReq; class ConnectByDeviceIdentifierReq;
class ExecuteHandshakeReq; class ExecuteHandshakeReq;
class DisconnectReq; class DisconnectReq;
class EnablePcloudDataReq;
class DisablePcloudDataReq;
class SetReturnModeReq;
class GetReturnModeReq;
public: public:
enum class ReturnMode : uint8_t
{
SingleFirst = 0x00,
SingleStrongest = 0x01,
Dual = 0x02,
Triple = 0x03
};
/**
* Get the number of points per datagram based on return mode
* @param returnMode The return mode (0=SingleFirst, 1=SingleStrongest, 2=Dual, 3=Triple)
* @return Number of points per datagram
*/
static inline size_t getNPointsPerDgram(int returnMode)
{
/*
* Map modes to points per datagram based on Livox docs
* 1: first, 2: strongest -> 96 samples => 96 points
* 3: dual -> 48 samples * 2 points = 96
* 4: triple -> 30 samples * 3 points = 90
*/
switch (returnMode)
{
case static_cast<int>(ReturnMode::SingleFirst):
case static_cast<int>(ReturnMode::SingleStrongest):
case static_cast<int>(ReturnMode::Dual):
return 96u;
case static_cast<int>(ReturnMode::Triple):
return 90u;
default:
throw std::runtime_error(
std::string(__func__) + ": Unknown returnMode "
+ std::to_string(returnMode));
}
}
// Utility methods // Utility methods
std::optional<std::string> getSmoIp(const std::string& deviceIP); std::optional<std::string> getSmoIp(const std::string& deviceIP);
// Callback function type definitions for async methods // Callback function type definitions for async methods
typedef std::function<void(bool success)> connectReqCbFn; typedef std::function<void(bool success)> connectReqCbFn;
typedef std::function< typedef std::function<
void(bool success, const std::string& ipAddr, int fd)> void(bool success, const std::string& ipAddr)>
connectToKnownDeviceReqCbFn; connectToKnownDeviceReqCbFn;
typedef std::function< typedef std::function<
void(bool success, const std::string& ipAddr, int fd)> void(bool success, const std::string& ipAddr)>
connectByDeviceIdentifierReqCbFn; connectByDeviceIdentifierReqCbFn;
typedef std::function<void(bool success, int fd)> executeHandshakeReqCbFn; typedef std::function<void(bool success)> executeHandshakeReqCbFn;
typedef std::function<void(bool success)> disconnectReqCbFn; typedef std::function<void(bool success)> disconnectReqCbFn;
typedef std::function<void(bool success)> enablePcloudDataReqCbFn;
typedef std::function<void(bool success)> disablePcloudDataReqCbFn;
typedef std::function<void(bool success)> setReturnModeReqCbFn;
typedef std::function<void(bool success, uint8_t returnMode)>
getReturnModeReqCbFn;
// Async connection methods // Async connection methods
void connectReq(smo::Callback<connectReqCbFn> callback); void connectReq(smo::Callback<connectReqCbFn> callback);
@@ -121,11 +172,97 @@ public:
const std::string& deviceIP, const std::string& deviceIP,
smo::Callback<executeHandshakeReqCbFn> callback); smo::Callback<executeHandshakeReqCbFn> callback);
void disconnectReq(smo::Callback<disconnectReqCbFn> callback); void disconnectReq(smo::Callback<disconnectReqCbFn> callback);
void enablePcloudDataReq(smo::Callback<enablePcloudDataReqCbFn> callback);
void disablePcloudDataReq(smo::Callback<disablePcloudDataReqCbFn> callback);
void setReturnModeReq(
uint8_t returnMode, smo::Callback<setReturnModeReqCbFn> callback);
void getReturnModeReq(smo::Callback<getReturnModeReqCbFn> callback);
public:
comms::DiscoveredDevice discoveredDevice;
std::atomic<size_t> nAttachedStimulusProducers;
// Configuration
std::shared_ptr<smo::ComponentThread> componentThread;
int commandTimeoutMs, retryDelayMs;
std::string smoIp;
std::string detectedSmoListeningIp;
uint8_t smoSubnetNbits;
uint16_t dataPort, cmdPort, imuPort;
// Heartbeat state // Heartbeat state
std::unique_ptr<boost::asio::deadline_timer> heartbeatTimer; std::unique_ptr<boost::asio::deadline_timer> heartbeatTimer;
int heartbeatFd; // Socket file descriptor used for heartbeat
std::atomic<bool> heartbeatActive; std::atomic<bool> heartbeatActive;
smo::SpinLock heartbeatActiveLock;
// Point cloud data state
std::atomic<bool> pcloudDataActive;
// Cached last-known return mode for this device
ReturnMode currentReturnMode = ReturnMode::SingleFirst;
public:
// UDP datagram handling
void handleUdpDgram(
const uint8_t* data, ssize_t bytesReceived,
const struct sockaddr_in& senderAddr);
// Command handler registration
void registerUdpCommandHandler(
uint8_t cmd_set, uint8_t cmd_id,
std::function<void(
const uint8_t* data, ssize_t bytesReceived,
const struct sockaddr_in& senderAddr)> handler,
const std::string& deviceIP = "");
void unregisterUdpCommandHandler(
uint8_t cmd_set, uint8_t cmd_id, const std::string& deviceIP = "");
private:
// Point cloud data setup
void cleanupPcloudDataSocket();
/** EXPLANATION:
* This is the "straightforward" map of command set and command id to
* handlers. This is useful for any commands which are guaranteed to be
* issued to the device *AFTER* the device has successfully been added
* to the DeviceManager's list of devices.
*
* I.e: it cannot be used for commands which are issued to the device before
* getOrCreateDevice() has added the device to the DeviceManager's list of
* devices.
*/
// Command handler map
std::unordered_map<
std::pair<uint8_t, uint8_t>,
std::function<void(
const uint8_t* data, ssize_t bytesReceived,
const struct sockaddr_in& senderAddr)>> udpCommandHandlers;
public:
/** EXPLANATION:
* This is the "temporary" map of command set and command id to
* handlers. This is useful for any commands which are issued to the device
* while it is being constructed.
*
* I.e: it shouldn't be used for cmds which are issued to the device after
* getOrCreateDevice() has added the device to the DeviceManager's list of
* devices. It will work for such commands, but we'd kind of prefer to use
* the "straightforward" map above for such commands.
*
* NOTE:
* There's a strong argument to be made for just getting rid of the
* "straightforward" map above and just using this one, tho.
*/
struct CommandHandler {
uint8_t cmd_set;
uint8_t cmd_id;
std::function<void(
const uint8_t* data, ssize_t bytesReceived,
const struct sockaddr_in& senderAddr)> handler;
};
static std::unordered_map<std::string, std::vector<CommandHandler>>
devicesUnderConstruction;
}; };
} // namespace livoxProto1 } // namespace livoxProto1
+61 -3
View File
@@ -1,8 +1,11 @@
#include <boostAsioLinkageFix.h>
#include <stdexcept> #include <stdexcept>
#include <callback.h> #include <callback.h>
#include <boost/asio/posix/stream_descriptor.hpp>
#include "livoxProto1.h" #include "livoxProto1.h"
#include "device.h" #include "device.h"
#include "core.h" #include "core.h"
#include "udpCommandDemuxer.h"
extern "C" { extern "C" {
@@ -10,7 +13,7 @@ extern "C" {
void livoxProto1_getOrCreateDeviceReq( void livoxProto1_getOrCreateDeviceReq(
const std::string& deviceIdentifier, const std::string& deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread, const std::shared_ptr<smo::ComponentThread>& componentThread,
int handshakeTimeoutMs, int retryDelayMs, int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits, const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort, uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback
@@ -28,7 +31,7 @@ void livoxProto1_getOrCreateDeviceReq(
// Delegate to DeviceManager // Delegate to DeviceManager
protoState.deviceManager->getOrCreateDeviceReq( protoState.deviceManager->getOrCreateDeviceReq(
deviceIdentifier, componentThread, deviceIdentifier, componentThread,
handshakeTimeoutMs, retryDelayMs, commandTimeoutMs, retryDelayMs,
smoIp, smoSubnetNbits, smoIp, smoSubnetNbits,
dataPort, cmdPort, imuPort, dataPort, cmdPort, imuPort,
callback); callback);
@@ -52,7 +55,7 @@ void livoxProto1_destroyDeviceReq(
void livoxProto1_main( void livoxProto1_main(
const std::shared_ptr<smo::ComponentThread>& componentThread, const std::shared_ptr<smo::ComponentThread>& componentThread,
const smo::sense_api::SmoCallbacks& smoCallbacks) const smo::stim_buff::SmoCallbacks& smoCallbacks)
{ {
livoxProto1::main(componentThread, smoCallbacks); livoxProto1::main(componentThread, smoCallbacks);
} }
@@ -62,4 +65,59 @@ void livoxProto1_exit(void)
livoxProto1::exit(); livoxProto1::exit();
} }
void livoxProto1_device_enablePcloudDataReq(
std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_device_enablePcloudDataReqCbFn> callback
)
{
if (!device)
{
throw std::runtime_error(std::string(__func__)
+ ": Device pointer is null");
}
device->enablePcloudDataReq(callback);
}
void livoxProto1_device_disablePcloudDataReq(
std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_device_disablePcloudDataReqCbFn> callback
)
{
if (!device)
{
throw std::runtime_error(std::string(__func__)
+ ": Device pointer is null");
}
device->disablePcloudDataReq(callback);
}
void livoxProto1_device_getReturnModeReq(
std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_device_getReturnModeReqCbFn> callback
)
{
if (!device)
{
throw std::runtime_error(std::string(__func__)
+ ": Device pointer is null");
}
device->getReturnModeReq(callback);
}
std::shared_ptr<boost::asio::posix::stream_descriptor>
livoxProto1_getPcloudDataFdDesc(void)
{
auto& protoState = livoxProto1::getProtoState();
if (!protoState.deviceManager)
{
throw std::runtime_error(std::string(__func__)
+ ": DeviceManager not initialized");
}
return protoState.deviceManager->udpCommandDemuxer.getPcloudDataFdDesc();
}
} // extern "C" } // extern "C"
+32 -5
View File
@@ -1,15 +1,17 @@
#ifndef LIVOXPROTO1_H #ifndef LIVOXPROTO1_H
#define LIVOXPROTO1_H #define LIVOXPROTO1_H
#include <boostAsioLinkageFix.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <callback.h> #include <callback.h>
#include <boost/asio/posix/stream_descriptor.hpp>
// Forward declarations // Forward declarations
namespace smo { namespace smo {
namespace sense_api { namespace stim_buff {
struct SmoCallbacks; struct SmoCallbacks;
} }
class ComponentThread; class ComponentThread;
@@ -30,7 +32,7 @@ extern "C" {
*/ */
typedef void livoxProto1_mainFn( typedef void livoxProto1_mainFn(
const std::shared_ptr<smo::ComponentThread>& componentThread, const std::shared_ptr<smo::ComponentThread>& componentThread,
const smo::sense_api::SmoCallbacks& smoCallbacks); const smo::stim_buff::SmoCallbacks& smoCallbacks);
/** /**
* Cleanup the Livox protocol library * Cleanup the Livox protocol library
@@ -41,7 +43,7 @@ typedef void livoxProto1_exitFn(void);
* Create a new Livox device connection * Create a new Livox device connection
* @param deviceIdentifier The device identifier (broadcast code) * @param deviceIdentifier The device identifier (broadcast code)
* @param componentThread Component thread for async operations * @param componentThread Component thread for async operations
* @param handshakeTimeoutMs Handshake timeout in milliseconds (default: 1000) * @param commandTimeoutMs Command timeout in milliseconds (default: 1000)
* @param retryDelayMs Retry delay in milliseconds (default: 3000) * @param retryDelayMs Retry delay in milliseconds (default: 3000)
* @param smoIp SMO IP address (empty string for auto-detection) * @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 smoSubnetNbits SMO subnet mask bits (e.g., 24 for /24, 16 for /16)
@@ -57,7 +59,7 @@ typedef std::function<
typedef void livoxProto1_getOrCreateDeviceReqFn( typedef void livoxProto1_getOrCreateDeviceReqFn(
const std::string& deviceIdentifier, const std::string& deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread, const std::shared_ptr<smo::ComponentThread>& componentThread,
int handshakeTimeoutMs, int retryDelayMs, int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits, const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort, uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback); smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
@@ -67,14 +69,39 @@ typedef void livoxProto1_destroyDeviceReqFn(
std::shared_ptr<livoxProto1::Device> device, std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback); smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback);
typedef std::function<void(bool success)>
livoxProto1_device_enablePcloudDataReqCbFn;
typedef void livoxProto1_device_enablePcloudDataReqFn(
std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_device_enablePcloudDataReqCbFn> callback);
typedef std::function<void(bool success)>
livoxProto1_device_disablePcloudDataReqCbFn;
typedef void livoxProto1_device_disablePcloudDataReqFn(
std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_device_disablePcloudDataReqCbFn> callback);
typedef std::function<void(bool success, uint8_t returnMode)>
livoxProto1_device_getReturnModeReqCbFn;
typedef void livoxProto1_device_getReturnModeReqFn(
std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_device_getReturnModeReqCbFn> callback);
typedef std::shared_ptr<boost::asio::posix::stream_descriptor>
livoxProto1_getPcloudDataFdDescFn(void);
livoxProto1_mainFn livoxProto1_main; livoxProto1_mainFn livoxProto1_main;
livoxProto1_exitFn livoxProto1_exit; livoxProto1_exitFn livoxProto1_exit;
livoxProto1_getOrCreateDeviceReqFn livoxProto1_getOrCreateDeviceReq; livoxProto1_getOrCreateDeviceReqFn livoxProto1_getOrCreateDeviceReq;
livoxProto1_destroyDeviceReqFn livoxProto1_destroyDeviceReq; livoxProto1_destroyDeviceReqFn livoxProto1_destroyDeviceReq;
livoxProto1_device_enablePcloudDataReqFn livoxProto1_device_enablePcloudDataReq;
livoxProto1_device_disablePcloudDataReqFn
livoxProto1_device_disablePcloudDataReq;
livoxProto1_device_getReturnModeReqFn livoxProto1_device_getReturnModeReq;
livoxProto1_getPcloudDataFdDescFn livoxProto1_getPcloudDataFdDesc;
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // LIVOXPROTO1_H #endif // LIVOXPROTO1_H
+214 -72
View File
@@ -230,7 +230,6 @@ uint32_t HandshakeRequest::calculateCrc32() const
return comms::calculateCrc32(messageData, messageSize); return comms::calculateCrc32(messageData, messageSize);
} }
void HandshakeRequest::swapContentsToProtocolEndianness() void HandshakeRequest::swapContentsToProtocolEndianness()
{ {
// Protocol uses little-endian, so on little-endian machines, no swap needed // Protocol uses little-endian, so on little-endian machines, no swap needed
@@ -246,26 +245,6 @@ void HandshakeRequest::swapContentsToProtocolEndianness()
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here // 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 // HandshakeResponse methods
void HandshakeResponse::swapContentsToHostEndianness() void HandshakeResponse::swapContentsToHostEndianness()
{ {
@@ -583,7 +562,6 @@ uint32_t HeartbeatMessage::calculateCrc32() const
return comms::calculateCrc32(messageData, messageSize); return comms::calculateCrc32(messageData, messageSize);
} }
void HeartbeatMessage::swapContentsToProtocolEndianness() void HeartbeatMessage::swapContentsToProtocolEndianness()
{ {
// Protocol is little-endian, so if host is already little-endian, no swap needed // Protocol is little-endian, so if host is already little-endian, no swap needed
@@ -598,46 +576,6 @@ void HeartbeatMessage::swapContentsToProtocolEndianness()
// Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here // 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 methods
DisconnectMessage::DisconnectMessage() DisconnectMessage::DisconnectMessage()
{ {
@@ -681,26 +619,69 @@ void DisconnectMessage::swapContentsToProtocolEndianness()
// Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here // Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here
} }
bool DisconnectMessage::sanityCheck() const // StartStopSamplingMessage methods
StartStopSamplingMessage::StartStopSamplingMessage()
{ {
return header.sanityCheck() && // Initialize header
command.sanityCheck() && header.sof = 0xAA;
(command.cmd_set == 0x00) && (command.cmd_id == 0x06) && header.version = 1;
footer.sanityCheck(); header.length = sizeof(StartStopSamplingMessage);
header.cmd_type = 0x02; // MSG type
header.seq_num = 0; // Will be set by caller if needed
header.crc_16 = 0; // Will be calculated
// Initialize command
command.cmd_set = 0x00; // General command set
command.cmd_id = 0x04; // Sampling command ID
// Initialize data - enable flag will be set manually by caller
enable = 0x00; // Default to stop, caller will override
// Initialize footer
footer.crc_32 = 0; // Will be calculated
} }
bool DisconnectMessage::validateCrc32() const uint32_t StartStopSamplingMessage::calculateCrc32() const
{ {
// Use the calculateCrc32 method to avoid code duplication // Calculate CRC32 for the entire message excluding the footer CRC32 field
uint32_t calculatedCrc = calculateCrc32(); const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(StartStopSamplingMessage) - sizeof(footer.crc_32);
// Compare with the CRC in the footer return comms::calculateCrc32(messageData, messageSize);
}
void StartStopSamplingMessage::swapContentsToProtocolEndianness()
{
header.swapToProtocolEndianness();
command.swapToProtocolEndianness();
}
// SamplingResponse methods
void SamplingResponse::swapContentsToHostEndianness()
{
header.swapToHostEndianness();
command.swapToHostEndianness();
footer.swapToHostEndianness();
}
bool SamplingResponse::sanityCheck() const
{
return header.sanityCheck() && command.sanityCheck() && footer.sanityCheck();
}
bool SamplingResponse::validateCrc32() const
{
// Calculate CRC32 for the entire message excluding the footer CRC32 field
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(SamplingResponse) - sizeof(footer.crc_32);
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
bool isValid = (calculatedCrc == footer.crc_32); bool isValid = (calculatedCrc == footer.crc_32);
// Debug output only if validation fails // Debug output only if validation fails
if (!isValid) if (!isValid)
{ {
std::cout << "DisconnectMessage CRC32 Debug: calculated=0x" std::cout << "SamplingResponse CRC32 Debug: calculated=0x"
<< std::hex << calculatedCrc << std::hex << calculatedCrc
<< ", received=0x" << footer.crc_32 << std::dec << std::endl; << ", received=0x" << footer.crc_32 << std::dec << std::endl;
} }
@@ -708,5 +689,166 @@ bool DisconnectMessage::validateCrc32() const
return isValid; return isValid;
} }
// HeartbeatACK methods
void HeartbeatACK::swapContentsToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
// Only swap content fields, not CRC fields
header.swapToHostEndianness();
command.swapToHostEndianness();
ack_msg = __builtin_bswap32(ack_msg);
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
}
bool HeartbeatACK::sanityCheck() const
{
return header.sanityCheck() &&
command.sanityCheck() &&
(command.cmd_set == 0x00) && (command.cmd_id == 0x03) &&
footer.sanityCheck();
}
bool HeartbeatACK::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(HeartbeatACK) - 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 << "HeartbeatACK CRC32 Debug: calculated=0x" << std::hex << calculatedCrc
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
}
return isValid;
}
// SetLiDARReturnMode methods
SetLiDARReturnMode::SetLiDARReturnMode()
{
// Initialize header
header.sof = 0xAA;
header.version = 0x01;
header.length = sizeof(SetLiDARReturnMode);
header.crc_16 = 0; // Will be calculated later
// Initialize command
command.cmd_set = 0x01; // LiDAR Command
command.cmd_id = 0x06; // Set LiDAR Return Mode
// Initialize mode (default to Single Return First)
mode = 0x00;
// Initialize footer
footer.crc_32 = 0; // Will be calculated later
}
uint32_t SetLiDARReturnMode::calculateCrc32() const
{
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(SetLiDARReturnMode) - sizeof(footer.crc_32);
return comms::calculateCrc32(messageData, messageSize);
}
void SetLiDARReturnMode::swapContentsToProtocolEndianness()
{
if (endian::isLittleEndian()) { return; }
header.swapToProtocolEndianness();
command.swapToProtocolEndianness();
// mode is uint8_t, no endianness conversion needed
footer.swapToProtocolEndianness();
}
// SetLiDARReturnModeResponse methods
void SetLiDARReturnModeResponse::swapContentsToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
header.swapToHostEndianness();
command.swapToHostEndianness();
// ret_code is uint8_t, no endianness conversion needed
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
}
bool SetLiDARReturnModeResponse::sanityCheck() const
{
return header.sanityCheck() &&
command.sanityCheck() &&
(command.cmd_set == 0x01) && (command.cmd_id == 0x06) &&
(ret_code <= 0x01) && // Valid return codes: 0x00-0x01
footer.sanityCheck();
}
bool SetLiDARReturnModeResponse::validateCrc32() const
{
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(SetLiDARReturnModeResponse) - sizeof(footer.crc_32);
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
return (calculatedCrc == footer.crc_32);
}
// GetLiDARReturnMode methods
GetLiDARReturnMode::GetLiDARReturnMode()
{
// Initialize header
header.sof = 0xAA;
header.version = 0x01;
header.length = sizeof(GetLiDARReturnMode);
header.crc_16 = 0; // Will be calculated later
// Initialize command
command.cmd_set = 0x01; // LiDAR Command
command.cmd_id = 0x07; // Get LiDAR Return Mode
// Initialize footer
footer.crc_32 = 0; // Will be calculated later
}
uint32_t GetLiDARReturnMode::calculateCrc32() const
{
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(GetLiDARReturnMode) - sizeof(footer.crc_32);
return comms::calculateCrc32(messageData, messageSize);
}
void GetLiDARReturnMode::swapContentsToProtocolEndianness()
{
if (endian::isLittleEndian()) { return; }
header.swapToProtocolEndianness();
command.swapToProtocolEndianness();
footer.swapToProtocolEndianness();
}
// GetLiDARReturnModeResponse methods
void GetLiDARReturnModeResponse::swapContentsToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
header.swapToHostEndianness();
command.swapToHostEndianness();
// ret_code and mode are uint8_t, no endianness conversion needed
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
}
bool GetLiDARReturnModeResponse::sanityCheck() const
{
return header.sanityCheck() &&
command.sanityCheck() &&
(command.cmd_set == 0x01) && (command.cmd_id == 0x07) &&
(ret_code <= 0x01) && // Valid return codes: 0x00-0x01
(mode <= 0x03) && // Valid modes: 0x00-0x03
footer.sanityCheck();
}
bool GetLiDARReturnModeResponse::validateCrc32() const
{
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(GetLiDARReturnModeResponse) - sizeof(footer.crc_32);
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
return (calculatedCrc == footer.crc_32);
}
} // namespace comms } // namespace comms
} // namespace livoxProto1 } // namespace livoxProto1
+114 -5
View File
@@ -1,6 +1,7 @@
#ifndef LIVOXPROTO1_PROTOCOL_H #ifndef LIVOXPROTO1_PROTOCOL_H
#define LIVOXPROTO1_PROTOCOL_H #define LIVOXPROTO1_PROTOCOL_H
#include <boostAsioLinkageFix.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include <memory> #include <memory>
@@ -191,8 +192,6 @@ struct HandshakeRequest
// Calculate CRC32 for the entire message // Calculate CRC32 for the entire message
uint32_t calculateCrc32() const; uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness(); void swapContentsToProtocolEndianness();
void swapContentsToHostEndianness();
bool sanityCheck() const;
} __attribute__((packed)); } __attribute__((packed));
/** EXPLANATION: /** EXPLANATION:
@@ -224,9 +223,6 @@ struct HeartbeatMessage
HeartbeatMessage(); HeartbeatMessage();
uint32_t calculateCrc32() const; uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness(); void swapContentsToProtocolEndianness();
void swapContentsToHostEndianness();
bool sanityCheck() const;
bool validateCrc32() const;
} __attribute__((packed)); } __attribute__((packed));
/** EXPLANATION: /** EXPLANATION:
@@ -242,6 +238,119 @@ struct DisconnectMessage
DisconnectMessage(); DisconnectMessage();
uint32_t calculateCrc32() const; uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness(); void swapContentsToProtocolEndianness();
} __attribute__((packed));
/** EXPLANATION:
* Complete start/stop sampling command frame for enabling/disabling point cloud data from Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct StartStopSamplingMessage
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
uint8_t enable; // 11: Enable flag (0x01 = Start, 0x00 = Stop)
Footer footer; // 12-15: Protocol frame footer
StartStopSamplingMessage();
uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness();
} __attribute__((packed));
/** EXPLANATION:
* Complete sampling response frame from Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct SamplingResponse
{
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 ACK response frame from Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct HeartbeatACK
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
uint8_t work_state; // 12: LiDAR/Hub State (0x00: Initializing, 0x01: Normal, 0x02: Power-Saving, 0x03: Standby, 0x04: Error)
uint8_t feature_msg; // 13: LiDAR Feature Message (Bit0: Rain/Fog Suppression Switch)
uint32_t ack_msg; // 14-17: ACK Message (Initialization Progress or Status Code)
Footer footer; // 18-21: Protocol frame footer
void swapContentsToHostEndianness();
bool sanityCheck() const;
bool validateCrc32() const;
} __attribute__((packed));
/** EXPLANATION:
* Complete set LiDAR return mode command frame for Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct SetLiDARReturnMode
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
uint8_t mode; // 11: Return Mode (0x00: Single Return First, 0x01: Single Return Strongest, 0x02: Dual Return, 0x03: Triple Return)
Footer footer; // 12-15: Protocol frame footer
SetLiDARReturnMode();
uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness();
} __attribute__((packed));
/** EXPLANATION:
* Complete set LiDAR return mode response frame from Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct SetLiDARReturnModeResponse
{
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 get LiDAR return mode command frame for Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct GetLiDARReturnMode
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
Footer footer; // 11-14: Protocol frame footer
GetLiDARReturnMode();
uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness();
} __attribute__((packed));
/** EXPLANATION:
* Complete get LiDAR return mode response frame from Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct GetLiDARReturnModeResponse
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
uint8_t mode; // 12: Return Mode (0x00: Single Return First, 0x01: Single Return Strongest, 0x02: Dual Return, 0x03: Triple Return)
Footer footer; // 13-16: Protocol frame footer
void swapContentsToHostEndianness();
bool sanityCheck() const; bool sanityCheck() const;
bool validateCrc32() const; bool validateCrc32() const;
} __attribute__((packed)); } __attribute__((packed));
@@ -0,0 +1,347 @@
#include <iostream>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include "udpCommandDemuxer.h"
#include "core.h"
#include "device.h"
namespace livoxProto1 {
namespace comms {
UdpCommandDemuxer::UdpCommandDemuxer(
const std::shared_ptr<smo::ComponentThread> &componentThread,
DeviceManager &deviceManager,
uint16_t commandPort,
uint16_t dataPort
)
: componentThread(componentThread), deviceManager(deviceManager),
commandPort(commandPort), dataPort(dataPort),
senderAddrLen(sizeof(senderAddr))
{
}
UdpCommandDemuxer::~UdpCommandDemuxer()
{
stop();
}
void UdpCommandDemuxer::start()
{
if (isActive.load())
{
std::cerr << __func__ << ": Demuxer is already running"
<< std::endl;
return;
}
try
{
{
smo::SpinLock::Guard lock(isActiveAndShouldStopLock);
setupSockets();
isActive.store(true);
shouldStop.store(false);
}
// Start the async receive loop
startAsyncReceive();
std::cout
<< __func__ << ": UDP Command Demuxer started on port "
<< commandPort << std::endl;
}
catch (const std::exception &e)
{
std::cerr
<< __func__ << ": Failed to start demuxer: "
<< e.what() << std::endl;
isActive.store(false);
throw;
}
}
void UdpCommandDemuxer::stop()
{
{
smo::SpinLock::Guard lock(isActiveAndShouldStopLock);
if (!isActive.load())
{ return; }
shouldStop.store(true);
}
// Close socket and cleanup
if (cmdEndpointFdDesc)
{
cmdEndpointFdDesc->cancel();
cmdEndpointFdDesc.reset();
}
if (pcloudDataFdDesc)
{
pcloudDataFdDesc->cancel();
pcloudDataFdDesc.reset();
}
isActive.store(false);
std::cout
<< __func__ << ": UDP Command Demuxer stopped"
<< std::endl;
}
void UdpCommandDemuxer::setupSockets()
{
setupCommandSocket();
setupPcloudDataSocket();
}
void UdpCommandDemuxer::setupCommandSocket()
{
// RAII class to manage socket file descriptor
struct SocketRAII
{
int fd;
SocketRAII(int socketFd) : fd(socketFd) {}
~SocketRAII() { if (fd >= 0) close(fd); }
void commit() { fd = -1; } // Transfer ownership, prevent close
int getFd() const { return fd; }
bool isValid() const { return fd >= 0; }
};
// Create UDP socket
SocketRAII socketGuard(socket(AF_INET, SOCK_DGRAM, 0));
if (!socketGuard.isValid())
{
throw std::runtime_error(
std::string(__func__)
+ ": Failed to create socket: " + strerror(errno));
}
// Set socket to non-blocking mode
int flags = fcntl(socketGuard.getFd(), F_GETFL, 0);
if (flags < 0 || fcntl(
socketGuard.getFd(), F_SETFL, flags | O_NONBLOCK) < 0)
{
throw std::runtime_error(
std::string(__func__)
+ ": Failed to set non-blocking mode: " + strerror(errno));
}
// Bind to command port
struct sockaddr_in localAddr;
memset(&localAddr, 0, sizeof(localAddr));
localAddr.sin_family = AF_INET;
localAddr.sin_addr.s_addr = INADDR_ANY;
localAddr.sin_port = htons(commandPort);
if (bind(
socketGuard.getFd(), (struct sockaddr *)&localAddr,
sizeof(localAddr)) < 0)
{
throw std::runtime_error(
std::string(__func__) + ": Failed to bind to port "
+ std::to_string(commandPort) + ": " + strerror(errno));
}
// Create boost wrapper for async operations
cmdEndpointFdDesc = std::make_shared<boost::asio::posix::stream_descriptor>(
componentThread->getIoService(), socketGuard.getFd());
// Transfer ownership, prevent auto-close
socketGuard.commit();
}
void UdpCommandDemuxer::setupPcloudDataSocket()
{
// RAII class to manage socket file descriptor
struct SocketRAII
{
int fd;
SocketRAII(int socketFd) : fd(socketFd) {}
~SocketRAII() { if (fd >= 0) close(fd); }
void commit() { fd = -1; } // Transfer ownership, prevent close
int getFd() const { return fd; }
bool isValid() const { return fd >= 0; }
};
// Create UDP socket for point cloud data reception
SocketRAII socketGuard(socket(AF_INET, SOCK_DGRAM, 0));
if (!socketGuard.isValid())
{
throw std::runtime_error(
std::string(__func__)
+ ": Failed to create socket: " + strerror(errno));
}
// Set socket to non-blocking mode
int flags = fcntl(socketGuard.getFd(), F_GETFL, 0);
if (flags < 0 ||
fcntl(socketGuard.getFd(), F_SETFL, flags | O_NONBLOCK) < 0)
{
throw std::runtime_error(
std::string(__func__)
+ ": Failed to set non-blocking mode: " + strerror(errno));
}
// Bind to the data port
struct sockaddr_in localAddr;
memset(&localAddr, 0, sizeof(localAddr));
localAddr.sin_family = AF_INET;
localAddr.sin_addr.s_addr = INADDR_ANY;
localAddr.sin_port = htons(dataPort);
if (bind(
socketGuard.getFd(), (struct sockaddr *)&localAddr,
sizeof(localAddr)) < 0)
{
throw std::runtime_error(
std::string(__func__) + ": Failed to bind to data port: "
+ std::to_string(dataPort) + ": " + strerror(errno));
}
// Create boost wrapper for async operations
pcloudDataFdDesc = std::make_shared<boost::asio::posix::stream_descriptor>(
componentThread->getIoService(), socketGuard.getFd());
// Transfer ownership, prevent auto-close
socketGuard.commit();
}
void UdpCommandDemuxer::startAsyncReceive()
{
if (!isActive.load() || shouldStop.load())
{ return; }
cmdEndpointFdDesc->async_wait(
boost::asio::posix::stream_descriptor::wait_read,
std::bind(
&UdpCommandDemuxer::onDataReady, this, std::placeholders::_1));
}
void UdpCommandDemuxer::onDataReady(const boost::system::error_code &error)
{
if (error)
{
if (error != boost::asio::error::operation_aborted)
{
std::cerr
<< __func__ << ": Socket error: "
<< error.message() << std::endl;
}
return;
}
smo::SpinLock::Guard lock(isActiveAndShouldStopLock);
if (!isActive.load() || shouldStop.load())
{ return; }
// Read the data
bytesReceived = recvfrom(
cmdEndpointFdDesc->native_handle(), receiveBuffer,
sizeof(receiveBuffer), 0,
(struct sockaddr *)&senderAddr, &senderAddrLen);
if (bytesReceived > 0) {
processIncomingData();
}
else if (bytesReceived < 0)
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
std::cerr << __func__ << ": recvfrom error: "
<< strerror(errno) << std::endl;
}
}
// Continue listening for more data
startAsyncReceive();
}
void UdpCommandDemuxer::processIncomingData()
{
if (bytesReceived < 2)
{
// Too small to contain any meaningful data
return;
}
// Extract source IP address
char sourceIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &senderAddr.sin_addr, sourceIP, INET_ADDRSTRLEN);
// First, find device with matching IP address in DeviceManager collection
for (const auto &device : deviceManager.devices)
{
if (device->discoveredDevice.ipAddr != sourceIP) { continue; }
// Found matching device, route the datagram to it
try
{
device->handleUdpDgram(
receiveBuffer, bytesReceived, senderAddr);
}
catch (const std::exception &e)
{
std::cerr
<< __func__ << ": Device handler exception for IP "
<< sourceIP << ": " << e.what() << std::endl;
}
return;
}
// If not found in DeviceManager, check temporary collection (devices under construction)
auto tempIt = livoxProto1::Device::devicesUnderConstruction.find(sourceIP);
if (tempIt != livoxProto1::Device::devicesUnderConstruction.end())
{
// Extract command set and command ID from the datagram
if (bytesReceived >= static_cast<ssize_t>(
sizeof(livoxProto1::comms::Header)
+ sizeof(livoxProto1::comms::Command)))
{
uint8_t cmd_set = receiveBuffer[
sizeof(livoxProto1::comms::Header)];
uint8_t cmd_id = receiveBuffer[
sizeof(livoxProto1::comms::Header) + 1];
// Found matching dev in temp collection, invoke matching handlers
for (const auto& cmdHandler : tempIt->second)
{
if (cmdHandler.cmd_set != cmd_set
|| cmdHandler.cmd_id != cmd_id)
{
continue;
}
try
{
cmdHandler.handler(
receiveBuffer, bytesReceived, senderAddr);
}
catch (const std::exception &e)
{
std::cerr << __func__ << ": Temporary device handler "
"exception for IP " << sourceIP << ": " << e.what()
<< std::endl;
}
}
}
return;
}
// No device found with matching IP in either collection, discard the data
std::cerr
<< __func__ << ": No device found for source IP "
<< sourceIP << ", discarding datagram" << std::endl;
}
} // namespace comms
} // namespace livoxProto1
@@ -0,0 +1,99 @@
#ifndef UDP_COMMAND_DEMUXER_H
#define UDP_COMMAND_DEMUXER_H
#include <boostAsioLinkageFix.h>
#include <atomic>
#include <memory>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <componentThread.h>
#include <spinLock.h>
namespace livoxProto1 {
// Forward declarations
class DeviceManager;
namespace comms {
/**
* UdpCommandDemuxer - Routes UDP command datagrams to appropriate devices
*
* This class listens on the command port (65000) for incoming UDP datagrams
* from Livox devices and routes them to the appropriate Device based on
* the source IP address.
*
* The reason we need a whole class for this is because we use the same port
* numbers for all connected devices, so we have no way to distinguish between
* devices except based on the devices' IP addrs. Since all commands are sent
* over UDP, our sockets don't have built-in binding to a specific source IP.
*
* So we need to discriminate between source IPs manually, and demultiplex
* the dgrams received from different devices manually.
*
* We'll prolly also have to do the same thing for point cloud and IMU data, so
* we'll prolly end up renaming this class to UdpResponseDemuxer.
*/
class UdpCommandDemuxer
{
public:
UdpCommandDemuxer(
const std::shared_ptr<smo::ComponentThread>& componentThread,
DeviceManager& deviceManager,
uint16_t commandPort = 56001,
uint16_t dataPort = 56000);
~UdpCommandDemuxer();
void start();
void stop();
bool isRunning() const { return isActive.load(); }
// Get shared pointer to command endpoint for handshake use
std::shared_ptr<boost::asio::posix::stream_descriptor>
getCmdEndpointFdDesc() const
{
return cmdEndpointFdDesc;
}
// Get shared pointer to pcloud data fd for use in IoUringAssemblyEngine
std::shared_ptr<boost::asio::posix::stream_descriptor>
getPcloudDataFdDesc() const
{
return pcloudDataFdDesc;
}
private:
// Socket and async objects
std::shared_ptr<boost::asio::posix::stream_descriptor> pcloudDataFdDesc;
// Socket and async objects
std::shared_ptr<boost::asio::posix::stream_descriptor> cmdEndpointFdDesc;
private:
void setupSockets();
void setupCommandSocket();
void setupPcloudDataSocket();
void startAsyncReceive();
void onDataReady(const boost::system::error_code& error);
void processIncomingData();
std::shared_ptr<smo::ComponentThread> componentThread;
DeviceManager& deviceManager;
uint16_t commandPort;
uint16_t dataPort;
// State management
smo::SpinLock isActiveAndShouldStopLock;
std::atomic<bool> isActive{false};
std::atomic<bool> shouldStop{false};
// Receive buffer
uint8_t receiveBuffer[1024];
struct sockaddr_in senderAddr;
socklen_t senderAddrLen;
ssize_t bytesReceived;
};
} // namespace comms
} // namespace livoxProto1
#endif // UDP_COMMAND_DEMUXER_H
+1 -1
View File
@@ -13,7 +13,7 @@ if(ENABLE_LIB_xcbXorg)
# Set config define for header generation # Set config define for header generation
add_compile_definitions(CONFIG_LIB_XCBXORG_ENABLED) add_compile_definitions(CONFIG_LIB_XCBXORG_ENABLED)
target_include_directories(xcbXorg PUBLIC ${XCB_INCLUDE_DIRS}) target_include_directories(xcbXorg PUBLIC ${XCB_INCLUDE_DIRS})
target_link_libraries(xcbXorg ${XCB_LIBRARIES}) target_link_libraries(xcbXorg ${XCB_LIBRARIES} attachmentSupport)
# Install rules # Install rules
install(TARGETS xcbXorg DESTINATION lib) install(TARGETS xcbXorg DESTINATION lib)
+57
View File
@@ -0,0 +1,57 @@
option(COMPILE_CL_CHECKS "Compile CL checks" OFF)
if(COMPILE_CL_CHECKS)
# Find OpenCL: try find_package first, fall back to pkg-config
find_package(OpenCL QUIET)
if(OpenCL_FOUND)
# Normalize find_package variables to match pkg_check_modules naming
set(OPENCL_FOUND TRUE)
set(OPENCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIRS})
# Handle both OpenCL_LIBRARY (singular) and OpenCL_LIBRARIES (plural)
if(OpenCL_LIBRARIES)
set(OPENCL_LIBRARIES ${OpenCL_LIBRARIES})
else()
set(OPENCL_LIBRARIES ${OpenCL_LIBRARY})
endif()
set(OPENCL_LIBRARY_DIRS "")
message(STATUS "Found OpenCL using find_package")
else()
# Fall back to pkg-config
pkg_check_modules(OPENCL OpenCL)
if(NOT OPENCL_FOUND)
message(FATAL_ERROR
"Failed to find OpenCL: both find_package and "
"pkg_check_modules failed. Try installing the "
"'ocl-icd-opencl-dev' package (or the appropriate "
"OpenCL development package for your system)."
)
endif()
message(STATUS "Found OpenCL using pkg-config")
endif()
add_executable(clhostshmemptrcheck clhostshmemptrcheck.cpp)
target_include_directories(clhostshmemptrcheck
PUBLIC ${OPENCL_INCLUDE_DIRS})
target_link_libraries(clhostshmemptrcheck
${OPENCL_LIBRARIES})
add_executable(clshmemlatency clshmemlatency.cpp)
target_include_directories(clshmemlatency
PUBLIC ${OPENCL_INCLUDE_DIRS})
target_link_libraries(clshmemlatency
${OPENCL_LIBRARIES})
add_executable(clshmemlatency_callback clshmemlatency_callback.cpp)
target_include_directories(clshmemlatency_callback
PUBLIC ${OPENCL_INCLUDE_DIRS})
target_link_libraries(clshmemlatency_callback
${OPENCL_LIBRARIES})
add_executable(clshmemcheck clshmemcheck.cpp)
target_include_directories(clshmemcheck
PUBLIC ${OPENCL_INCLUDE_DIRS})
target_link_libraries(clshmemcheck
${OPENCL_LIBRARIES})
add_executable(clzerocopycheck clzerocopycheck.cpp)
target_include_directories(clzerocopycheck
PUBLIC ${OPENCL_INCLUDE_DIRS})
target_link_libraries(clzerocopycheck
${OPENCL_LIBRARIES})
endif()
+125
View File
@@ -0,0 +1,125 @@
#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <cstring>
static const char* clErrorToStr(cl_int err)
{
switch(err) {
case CL_SUCCESS: return "CL_SUCCESS";
case CL_INVALID_VALUE: return "CL_INVALID_VALUE";
case CL_INVALID_CONTEXT: return "CL_INVALID_CONTEXT";
case CL_INVALID_MEM_OBJECT: return "CL_INVALID_MEM_OBJECT";
case CL_OUT_OF_HOST_MEMORY: return "CL_OUT_OF_HOST_MEMORY";
case CL_INVALID_OPERATION: return "CL_INVALID_OPERATION";
case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "CL_MEM_OBJECT_ALLOCATION_FAILURE";
default: return "UNKNOWN_ERROR";
}
}
// Try creating a USE_HOST_PTR buffer on this device
bool testUseHostPtr(cl_context ctx, cl_device_id dev)
{
const size_t bufSize = 1024;
std::vector<char> host(bufSize, 0);
cl_int err = 0;
cl_mem buf = clCreateBuffer(
ctx,
CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
bufSize,
host.data(),
&err
);
if(err != CL_SUCCESS) {
std::cerr << " clCreateBuffer(CL_MEM_USE_HOST_PTR) failed: "
<< clErrorToStr(err) << "\n";
return false;
}
// Try to enqueue a trivial write to verify it works
cl_queue_properties queueProps[] = {CL_QUEUE_PROPERTIES, 0, 0};
cl_command_queue q = clCreateCommandQueueWithProperties(ctx, dev, queueProps, &err);
if(err != CL_SUCCESS){
std::cerr << " Failed to create command queue: "
<< clErrorToStr(err) << "\n";
clReleaseMemObject(buf);
return false;
}
err = clEnqueueWriteBuffer(q, buf, CL_TRUE, 0, bufSize, host.data(), 0, nullptr, nullptr);
clFinish(q);
bool ok = (err == CL_SUCCESS);
if(!ok) {
std::cerr << " clEnqueueWriteBuffer failed: " << clErrorToStr(err) << "\n";
}
clReleaseCommandQueue(q);
clReleaseMemObject(buf);
return ok;
}
int main()
{
cl_uint numPlatforms = 0;
clGetPlatformIDs(0, nullptr, &numPlatforms);
if(numPlatforms == 0){
std::cout << "No OpenCL platforms.\n";
return 0;
}
std::vector<cl_platform_id> plats(numPlatforms);
clGetPlatformIDs(numPlatforms, plats.data(), nullptr);
for(cl_uint p = 0; p < numPlatforms; ++p)
{
char buf[256];
clGetPlatformInfo(plats[p], CL_PLATFORM_NAME, sizeof(buf), buf, nullptr);
std::cout << "Platform: " << buf << "\n";
cl_uint numDevs = 0;
clGetDeviceIDs(plats[p], CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevs);
if(numDevs == 0) {
std::cout << " No devices found on this platform.\n";
continue;
}
std::vector<cl_device_id> devs(numDevs);
clGetDeviceIDs(plats[p], CL_DEVICE_TYPE_ALL, numDevs, devs.data(), nullptr);
for(cl_uint d = 0; d < numDevs; ++d)
{
clGetDeviceInfo(devs[d], CL_DEVICE_NAME, sizeof(buf), buf, nullptr);
std::cout << " Device: " << buf << "\n";
// Create a context for this device
cl_int err;
cl_context ctx = clCreateContext(nullptr, 1, &devs[d], nullptr, nullptr, &err);
if(err != CL_SUCCESS) {
std::cout << " Failed to create context: "
<< clErrorToStr(err) << "\n";
continue;
}
bool supported = testUseHostPtr(ctx, devs[d]);
if(supported)
std::cout << " HOST_PTR appears supported.\n";
else
std::cout << " HOST_PTR appears NOT supported.\n";
clReleaseContext(ctx);
}
}
return 0;
}
+94
View File
@@ -0,0 +1,94 @@
#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <cstring>
#include <cstdlib>
void checkCLError(cl_int err, const char* msg) {
if (err != CL_SUCCESS) {
std::cerr << "OpenCL Error " << err << " at: " << msg << std::endl;
exit(1);
}
}
int main() {
cl_uint numPlatforms = 0;
checkCLError(clGetPlatformIDs(0, nullptr, &numPlatforms), "get num platforms");
std::vector<cl_platform_id> platforms(numPlatforms);
checkCLError(clGetPlatformIDs(numPlatforms, platforms.data(), nullptr), "get platforms");
std::cout << "Found " << numPlatforms << " OpenCL platforms\n\n";
for (cl_uint p = 0; p < numPlatforms; ++p) {
char platformName[256];
clGetPlatformInfo(platforms[p], CL_PLATFORM_NAME, sizeof(platformName), platformName, nullptr);
std::cout << "Platform " << p << ": " << platformName << "\n";
cl_uint numDevices = 0;
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevices);
std::vector<cl_device_id> devices(numDevices);
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, numDevices, devices.data(), nullptr);
for (cl_uint d = 0; d < numDevices; ++d) {
char deviceName[256];
clGetDeviceInfo(devices[d], CL_DEVICE_NAME, sizeof(deviceName), deviceName, nullptr);
std::cout << " Device " << d << ": " << deviceName << "\n";
cl_bool unifiedMem = CL_FALSE;
clGetDeviceInfo(devices[d], CL_DEVICE_HOST_UNIFIED_MEMORY, sizeof(unifiedMem), &unifiedMem, nullptr);
std::cout << " Host-Device unified memory: " << (unifiedMem ? "Yes" : "No") << "\n";
#ifdef CL_DEVICE_SVM_CAPABILITIES
cl_device_svm_capabilities svmCaps = 0;
clGetDeviceInfo(devices[d], CL_DEVICE_SVM_CAPABILITIES, sizeof(svmCaps), &svmCaps, nullptr);
std::cout << " SVM capabilities:\n";
if (!svmCaps) std::cout << " None\n";
if (svmCaps & CL_DEVICE_SVM_COARSE_GRAIN_BUFFER)
std::cout << " - Coarse-grain buffer sharing\n";
if (svmCaps & CL_DEVICE_SVM_FINE_GRAIN_BUFFER)
std::cout << " - Fine-grain buffer sharing\n";
if (svmCaps & CL_DEVICE_SVM_FINE_GRAIN_SYSTEM)
std::cout << " - Fine-grain system sharing\n";
if (svmCaps & CL_DEVICE_SVM_ATOMICS)
std::cout << " - Atomics supported\n";
#endif
// Optional runtime test: check if CL_MEM_USE_HOST_PTR buffer reuses pointer
const size_t bufSize = 1024 * 1024;
std::vector<char> hostBuffer(bufSize, 42);
cl_int err;
cl_context ctx = clCreateContext(nullptr, 1, &devices[d], nullptr, nullptr, &err);
checkCLError(err, "create context");
cl_mem buf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, bufSize, hostBuffer.data(), &err);
checkCLError(err, "create buffer");
cl_queue_properties queueProps[] = {CL_QUEUE_PROPERTIES, 0, 0};
cl_command_queue q = clCreateCommandQueueWithProperties(ctx, devices[d], queueProps, &err);
checkCLError(err, "create queue");
// Simple host → device → host round-trip test
cl_event evt;
auto start = std::chrono::high_resolution_clock::now();
void* mapped = clEnqueueMapBuffer(q, buf, CL_TRUE, CL_MAP_READ, 0, bufSize, 0, nullptr, &evt, &err);
checkCLError(err, "map buffer");
clWaitForEvents(1, &evt);
clEnqueueUnmapMemObject(q, buf, mapped, 0, nullptr, nullptr);
clReleaseMemObject(buf);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> elapsed = end - start;
std::cout << " Map latency: " << elapsed.count() << " ms (lower → likely zero-copy)\n";
clReleaseCommandQueue(q);
clReleaseContext(ctx);
}
std::cout << std::endl;
}
return 0;
}
+184
View File
@@ -0,0 +1,184 @@
#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <cstring>
#include <cstdlib>
void checkCLError(cl_int err, const char* msg) {
if (err != CL_SUCCESS) {
std::cerr << "OpenCL Error " << err << " at: " << msg << std::endl;
exit(1);
}
}
// --------------------
// Kernel source
// Simple mock kernel that simulates splitting XYZ/I
// Each "point" is 16 bytes (XYZ + Intensity)
const char* kernelSrc = R"CLC(
__kernel void xyz_i_split(__global uchar* assembly,
__global uchar* xyzOut,
__global uchar* iOut,
const uint numPoints) {
uint gid = get_global_id(0);
if (gid >= numPoints) return;
uint offset = gid * 16;
// Copy XYZ (12 bytes) to xyzOut
for (int i=0; i<12; ++i)
xyzOut[gid*12 + i] = assembly[offset + i];
// Copy Intensity (4 bytes) to iOut
for (int i=0; i<4; ++i)
iOut[gid*4 + i] = assembly[offset + 12 + i];
}
)CLC";
int main() {
// --------------------
// CHANGE THIS VALUE to set number of points per assembly buffer
const size_t numPointsPerAssembly = 100000; // e.g., ~3333 points per fill
const size_t bytesPerPoint = 16; // 12 bytes XYZ + 4 bytes I
const size_t assemblyBufSize = numPointsPerAssembly * bytesPerPoint;
const size_t xyzBufSize = numPointsPerAssembly * 12;
const size_t iBufSize = numPointsPerAssembly * 4;
cl_uint numPlatforms = 0;
checkCLError(clGetPlatformIDs(0, nullptr, &numPlatforms), "get num platforms");
std::vector<cl_platform_id> platforms(numPlatforms);
checkCLError(clGetPlatformIDs(numPlatforms, platforms.data(), nullptr), "get platforms");
std::cout << "Found " << numPlatforms << " OpenCL platforms\n\n";
for (cl_uint p = 0; p < numPlatforms; ++p) {
char platformName[256];
clGetPlatformInfo(platforms[p], CL_PLATFORM_NAME, sizeof(platformName), platformName, nullptr);
std::cout << "Platform " << p << ": " << platformName << "\n";
cl_uint numDevices = 0;
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevices);
std::vector<cl_device_id> devices(numDevices);
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, numDevices, devices.data(), nullptr);
for (cl_uint d = 0; d < numDevices; ++d) {
char deviceName[256];
clGetDeviceInfo(devices[d], CL_DEVICE_NAME, sizeof(deviceName), deviceName, nullptr);
std::cout << " Device " << d << ": " << deviceName << "\n";
cl_int err;
cl_context ctx = clCreateContext(nullptr, 1, &devices[d], nullptr, nullptr, &err);
checkCLError(err, "create context");
cl_queue_properties queueProps[] = {CL_QUEUE_PROPERTIES, 0, 0};
cl_command_queue q = clCreateCommandQueueWithProperties(ctx, devices[d], queueProps, &err);
checkCLError(err, "create queue");
// --------------------
// Allocate host buffers
std::vector<unsigned char> assemblyHost(assemblyBufSize, 42);
std::vector<unsigned char> xyzHost(xyzBufSize, 0);
std::vector<unsigned char> iHost(iBufSize, 0);
std::vector<unsigned char> xyzHostCPU(xyzBufSize, 0);
std::vector<unsigned char> iHostCPU(iBufSize, 0);
// Create CL buffers
cl_mem assemblyBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, assemblyBufSize, assemblyHost.data(), &err);
checkCLError(err, "create assembly buffer");
cl_mem xyzBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, xyzBufSize, xyzHost.data(), &err);
checkCLError(err, "create xyz buffer");
cl_mem iBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, iBufSize, iHost.data(), &err);
checkCLError(err, "create i buffer");
// Build program
cl_program prog = clCreateProgramWithSource(ctx, 1, &kernelSrc, nullptr, &err);
checkCLError(err, "create program");
err = clBuildProgram(prog, 1, &devices[d], nullptr, nullptr, nullptr);
if (err != CL_SUCCESS) {
// Print build log
size_t logSize = 0;
clGetProgramBuildInfo(prog, devices[d], CL_PROGRAM_BUILD_LOG, 0, nullptr, &logSize);
std::vector<char> log(logSize);
clGetProgramBuildInfo(prog, devices[d], CL_PROGRAM_BUILD_LOG, logSize, log.data(), nullptr);
std::cerr << log.data() << "\n";
}
checkCLError(err, "build program");
cl_kernel kernel = clCreateKernel(prog, "xyz_i_split", &err);
checkCLError(err, "create kernel");
// Set kernel args
clSetKernelArg(kernel, 0, sizeof(cl_mem), &assemblyBuf);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &xyzBuf);
clSetKernelArg(kernel, 2, sizeof(cl_mem), &iBuf);
clSetKernelArg(kernel, 3, sizeof(cl_uint), &numPointsPerAssembly);
const size_t globalWorkSize = numPointsPerAssembly;
// --------------------
// Run a few iterations
for (int iter = 0; iter < 10; ++iter) {
cl_event evt;
auto t0 = std::chrono::high_resolution_clock::now();
void* mappedAssembly = clEnqueueMapBuffer(q, assemblyBuf, CL_TRUE, CL_MAP_READ, 0, assemblyBufSize, 0, nullptr, &evt, &err);
checkCLError(err, "map assembly buffer");
clWaitForEvents(1, &evt);
auto t1 = std::chrono::high_resolution_clock::now();
err = clEnqueueNDRangeKernel(q, kernel, 1, nullptr, &globalWorkSize, nullptr, 0, nullptr, &evt);
checkCLError(err, "enqueue kernel");
clWaitForEvents(1, &evt);
auto t2 = std::chrono::high_resolution_clock::now();
cl_event unmapEvt;
err = clEnqueueUnmapMemObject(q, assemblyBuf, mappedAssembly, 0, nullptr, &unmapEvt);
checkCLError(err, "unmap assembly buffer");
clWaitForEvents(1, &unmapEvt);
auto t3 = std::chrono::high_resolution_clock::now();
// --------------------
// Host CPU split
auto cpuStart = std::chrono::high_resolution_clock::now();
for (size_t pt = 0; pt < numPointsPerAssembly; ++pt) {
size_t off = pt * 16;
for (int i = 0; i < 12; ++i)
xyzHostCPU[pt*12 + i] = assemblyHost[off + i];
for (int i = 0; i < 4; ++i)
iHostCPU[pt*4 + i] = assemblyHost[off + 12 + i];
}
auto cpuEnd = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> mapElapsed = t1 - t0;
std::chrono::duration<double, std::milli> kernelElapsed = t2 - t1;
std::chrono::duration<double, std::milli> unmapElapsed = t3 - t2;
std::chrono::duration<double, std::milli> cpuElapsed = cpuEnd - cpuStart;
std::cout << "Iteration " << iter
<< " | Map: " << mapElapsed.count()
<< " ms | Kernel: " << kernelElapsed.count()
<< " ms | Unmap: " << unmapElapsed.count()
<< " ms | CPU Split: " << cpuElapsed.count() << " ms\n";
}
// Cleanup
clReleaseKernel(kernel);
clReleaseProgram(prog);
clReleaseMemObject(assemblyBuf);
clReleaseMemObject(xyzBuf);
clReleaseMemObject(iBuf);
clReleaseCommandQueue(q);
clReleaseContext(ctx);
}
std::cout << std::endl;
}
return 0;
}
+300
View File
@@ -0,0 +1,300 @@
#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <cstring>
#include <cstdlib>
#include <mutex>
#include <condition_variable>
void checkCLError(cl_int err, const char* msg) {
if (err != CL_SUCCESS) {
std::cerr << "OpenCL Error " << err << " at: " << msg << std::endl;
exit(1);
}
}
// Callback context for waiting on events
struct CallbackContext {
std::mutex mtx;
std::condition_variable cv;
bool completed;
cl_int status;
std::chrono::high_resolution_clock::time_point* timestamp;
};
// Helper function to wait for callback completion
void waitForCallback(CallbackContext& ctx) {
std::unique_lock<std::mutex> lock(ctx.mtx);
ctx.cv.wait(lock, [&ctx] { return ctx.completed; });
std::cout <<"waitForCallback cv.wait() returned.\n";
}
// Static callback for map buffer event
void CL_CALLBACK mapEventCallback(cl_event /*event*/, cl_int event_command_exec_status, void* user_data) {
CallbackContext* ctx = static_cast<CallbackContext*>(user_data);
std::cout <<"mapEventCallback called and about to lock mutex.\n";
std::unique_lock<std::mutex> lock(ctx->mtx);
ctx->status = event_command_exec_status;
if (ctx->timestamp) {
*ctx->timestamp = std::chrono::high_resolution_clock::now();
}
ctx->completed = true;
ctx->cv.notify_one();
std::cout <<"mapEventCallback just notified.\n";
}
// Static callback for kernel execution event
void CL_CALLBACK kernelEventCallback(cl_event /*event*/, cl_int event_command_exec_status, void* user_data) {
CallbackContext* ctx = static_cast<CallbackContext*>(user_data);
std::cout <<"mapEventCallback called and about to lock mutex.\n";
std::unique_lock<std::mutex> lock(ctx->mtx);
ctx->status = event_command_exec_status;
if (ctx->timestamp) {
*ctx->timestamp = std::chrono::high_resolution_clock::now();
}
ctx->completed = true;
ctx->cv.notify_one();
std::cout <<"mapEventCallback just notified.\n";
}
// Static callback for unmap buffer event
void CL_CALLBACK unmapEventCallback(cl_event /*event*/, cl_int event_command_exec_status, void* user_data) {
CallbackContext* ctx = static_cast<CallbackContext*>(user_data);
std::cout <<"mapEventCallback called and about to lock mutex.\n";
std::unique_lock<std::mutex> lock(ctx->mtx);
ctx->status = event_command_exec_status;
if (ctx->timestamp) {
*ctx->timestamp = std::chrono::high_resolution_clock::now();
}
ctx->completed = true;
ctx->cv.notify_one();
std::cout <<"mapEventCallback just notified.\n";
}
// --------------------
// Kernel source
// Simple mock kernel that simulates splitting XYZ/I
// Each "point" is 16 bytes (XYZ + Intensity)
const char* kernelSrc = R"CLC(
__kernel void xyz_i_split(__global uchar* assembly,
__global uchar* xyzOut,
__global uchar* iOut,
const uint numPoints) {
uint gid = get_global_id(0);
if (gid >= numPoints) return;
uint offset = gid * 16;
// Copy XYZ (12 bytes) to xyzOut
for (int i=0; i<12; ++i)
xyzOut[gid*12 + i] = assembly[offset + i];
// Copy Intensity (4 bytes) to iOut
for (int i=0; i<4; ++i)
iOut[gid*4 + i] = assembly[offset + 12 + i];
}
)CLC";
int main() {
// --------------------
// CHANGE THIS VALUE to set number of points per assembly buffer
const size_t numPointsPerAssembly = 100000; // e.g., ~3333 points per fill
const size_t bytesPerPoint = 16; // 12 bytes XYZ + 4 bytes I
const size_t assemblyBufSize = numPointsPerAssembly * bytesPerPoint;
const size_t xyzBufSize = numPointsPerAssembly * 12;
const size_t iBufSize = numPointsPerAssembly * 4;
cl_uint numPlatforms = 0;
checkCLError(clGetPlatformIDs(0, nullptr, &numPlatforms), "get num platforms");
std::vector<cl_platform_id> platforms(numPlatforms);
checkCLError(clGetPlatformIDs(numPlatforms, platforms.data(), nullptr), "get platforms");
std::cout << "Found " << numPlatforms << " OpenCL platforms\n\n";
for (cl_uint p = 0; p < numPlatforms; ++p) {
char platformName[256];
clGetPlatformInfo(platforms[p], CL_PLATFORM_NAME, sizeof(platformName), platformName, nullptr);
std::cout << "Platform " << p << ": " << platformName << "\n";
cl_uint numDevices = 0;
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevices);
std::vector<cl_device_id> devices(numDevices);
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, numDevices, devices.data(), nullptr);
for (cl_uint d = 0; d < numDevices; ++d) {
char deviceName[256];
clGetDeviceInfo(devices[d], CL_DEVICE_NAME, sizeof(deviceName), deviceName, nullptr);
std::cout << " Device " << d << ": " << deviceName << "\n";
cl_int err;
cl_context ctx = clCreateContext(nullptr, 1, &devices[d], nullptr, nullptr, &err);
checkCLError(err, "create context");
cl_queue_properties queueProps[] = {CL_QUEUE_PROPERTIES, 0, 0};
cl_command_queue q = clCreateCommandQueueWithProperties(ctx, devices[d], queueProps, &err);
checkCLError(err, "create queue");
// --------------------
// Allocate host buffers
std::vector<unsigned char> assemblyHost(assemblyBufSize, 42);
std::vector<unsigned char> xyzHost(xyzBufSize, 0);
std::vector<unsigned char> iHost(iBufSize, 0);
std::vector<unsigned char> xyzHostCPU(xyzBufSize, 0);
std::vector<unsigned char> iHostCPU(iBufSize, 0);
// Create CL buffers
cl_mem assemblyBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, assemblyBufSize, assemblyHost.data(), &err);
checkCLError(err, "create assembly buffer");
cl_mem xyzBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, xyzBufSize, xyzHost.data(), &err);
checkCLError(err, "create xyz buffer");
cl_mem iBuf = clCreateBuffer(ctx, CL_MEM_USE_HOST_PTR, iBufSize, iHost.data(), &err);
checkCLError(err, "create i buffer");
// Build program
cl_program prog = clCreateProgramWithSource(ctx, 1, &kernelSrc, nullptr, &err);
checkCLError(err, "create program");
err = clBuildProgram(prog, 1, &devices[d], nullptr, nullptr, nullptr);
if (err != CL_SUCCESS) {
// Print build log
size_t logSize = 0;
clGetProgramBuildInfo(prog, devices[d], CL_PROGRAM_BUILD_LOG, 0, nullptr, &logSize);
std::vector<char> log(logSize);
clGetProgramBuildInfo(prog, devices[d], CL_PROGRAM_BUILD_LOG, logSize, log.data(), nullptr);
std::cerr << log.data() << "\n";
}
checkCLError(err, "build program");
cl_kernel kernel = clCreateKernel(prog, "xyz_i_split", &err);
checkCLError(err, "create kernel");
// Set kernel args
clSetKernelArg(kernel, 0, sizeof(cl_mem), &assemblyBuf);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &xyzBuf);
clSetKernelArg(kernel, 2, sizeof(cl_mem), &iBuf);
clSetKernelArg(kernel, 3, sizeof(cl_uint), &numPointsPerAssembly);
const size_t globalWorkSize = numPointsPerAssembly;
// --------------------
// Run a few iterations
for (int iter = 0; iter < 10; ++iter) {
auto t0 = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point t1, t2, t3;
cl_event mapEvt;
void* mappedAssembly = clEnqueueMapBuffer(q, assemblyBuf, CL_FALSE, CL_MAP_READ, 0, assemblyBufSize, 0, nullptr, &mapEvt, &err);
checkCLError(err, "map assembly buffer");
// Retain event to keep it alive until callback completes
err = clRetainEvent(mapEvt);
checkCLError(err, "retain map event");
// Wait for map event using callback
CallbackContext mapCtx;
mapCtx.completed = false;
mapCtx.timestamp = &t1;
err = clSetEventCallback(mapEvt, CL_COMPLETE, mapEventCallback, &mapCtx);
checkCLError(err, "set map event callback");
// Force queue flush to ensure event processing
err = clFlush(q);
checkCLError(err, "flush queue");
std::cout <<"About to waitForCalllback for clEnqueueMapBuffer.\n";
waitForCallback(mapCtx);
checkCLError(mapCtx.status, "map buffer");
// Release event after callback completes
err = clReleaseEvent(mapEvt);
checkCLError(err, "release map event");
cl_event kernelEvt;
err = clEnqueueNDRangeKernel(q, kernel, 1, nullptr, &globalWorkSize, nullptr, 0, nullptr, &kernelEvt);
checkCLError(err, "enqueue kernel");
// Retain event to keep it alive until callback completes
err = clRetainEvent(kernelEvt);
checkCLError(err, "retain kernel event");
// Wait for kernel event using callback
CallbackContext kernelCtx;
kernelCtx.completed = false;
kernelCtx.timestamp = &t2;
err = clSetEventCallback(kernelEvt, CL_COMPLETE, kernelEventCallback, &kernelCtx);
checkCLError(err, "set kernel event callback");
// Force queue flush to ensure event processing
err = clFlush(q);
checkCLError(err, "flush queue");
std::cout <<"About to waitForCalllback for clEnqueueNDRangeKernel.\n";
waitForCallback(kernelCtx);
checkCLError(kernelCtx.status, "kernel execution");
// Release event after callback completes
err = clReleaseEvent(kernelEvt);
checkCLError(err, "release kernel event");
cl_event unmapEvt;
err = clEnqueueUnmapMemObject(q, assemblyBuf, mappedAssembly, 0, nullptr, &unmapEvt);
checkCLError(err, "unmap assembly buffer");
// Retain event to keep it alive until callback completes
err = clRetainEvent(unmapEvt);
checkCLError(err, "retain unmap event");
// Wait for unmap event using callback
CallbackContext unmapCtx;
unmapCtx.completed = false;
unmapCtx.timestamp = &t3;
err = clSetEventCallback(unmapEvt, CL_COMPLETE, unmapEventCallback, &unmapCtx);
checkCLError(err, "set unmap event callback");
// Force queue flush to ensure event processing
err = clFlush(q);
checkCLError(err, "flush queue");
std::cout <<"About to waitForCalllback for clEnqueueUnmapMemObject.\n";
waitForCallback(unmapCtx);
checkCLError(unmapCtx.status, "unmap buffer");
// Release event after callback completes
err = clReleaseEvent(unmapEvt);
checkCLError(err, "release unmap event");
// --------------------
// Host CPU split
auto cpuStart = std::chrono::high_resolution_clock::now();
for (size_t pt = 0; pt < numPointsPerAssembly; ++pt) {
size_t off = pt * 16;
for (int i = 0; i < 12; ++i)
xyzHostCPU[pt*12 + i] = assemblyHost[off + i];
for (int i = 0; i < 4; ++i)
iHostCPU[pt*4 + i] = assemblyHost[off + 12 + i];
}
auto cpuEnd = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> mapElapsed = t1 - t0;
std::chrono::duration<double, std::milli> kernelElapsed = t2 - t1;
std::chrono::duration<double, std::milli> unmapElapsed = t3 - t2;
std::chrono::duration<double, std::milli> cpuElapsed = cpuEnd - cpuStart;
std::cout << "Iteration " << iter
<< " | Map: " << mapElapsed.count()
<< " ms | Kernel: " << kernelElapsed.count()
<< " ms | Unmap: " << unmapElapsed.count()
<< " ms | CPU Split: " << cpuElapsed.count() << " ms\n";
}
// Cleanup
clReleaseKernel(kernel);
clReleaseProgram(prog);
clReleaseMemObject(assemblyBuf);
clReleaseMemObject(xyzBuf);
clReleaseMemObject(iBuf);
clReleaseCommandQueue(q);
clReleaseContext(ctx);
}
std::cout << std::endl;
}
return 0;
}
+117
View File
@@ -0,0 +1,117 @@
#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <cstring>
#define CHECK(err, msg) \
if (err != CL_SUCCESS) { \
std::cerr << "ERROR: " << msg << " (" << err << ")\n"; \
return 1; \
}
const char *kernelSrc = R"CLC(
__kernel void check_shared(__global const int* in, __global int* out) {
int gid = get_global_id(0);
out[gid] = in[gid] + 42; // simple deterministic transform
}
)CLC";
int main() {
cl_int err;
// Pick first available device
cl_uint numPlatforms;
CHECK(clGetPlatformIDs(0, nullptr, &numPlatforms), "clGetPlatformIDs count");
std::vector<cl_platform_id> plats(numPlatforms);
CHECK(clGetPlatformIDs(numPlatforms, plats.data(), nullptr), "clGetPlatformIDs");
cl_platform_id plat = plats[0];
cl_device_id dev;
CHECK(clGetDeviceIDs(plat, CL_DEVICE_TYPE_GPU, 1, &dev, nullptr), "clGetDeviceIDs");
cl_context ctx = clCreateContext(nullptr, 1, &dev, nullptr, nullptr, &err);
CHECK(err, "clCreateContext");
cl_queue_properties queueProps[] = {CL_QUEUE_PROPERTIES, 0, 0};
cl_command_queue q = clCreateCommandQueueWithProperties(ctx, dev, queueProps, &err);
CHECK(err, "clCreateCommandQueueWithProperties");
// Create program and kernel
const size_t srcLen = std::strlen(kernelSrc);
cl_program prog = clCreateProgramWithSource(ctx, 1, &kernelSrc, &srcLen, &err);
CHECK(err, "clCreateProgramWithSource");
err = clBuildProgram(prog, 1, &dev, nullptr, nullptr, nullptr);
if (err != CL_SUCCESS) {
size_t logSize;
clGetProgramBuildInfo(prog, dev, CL_PROGRAM_BUILD_LOG, 0, nullptr, &logSize);
std::vector<char> log(logSize);
clGetProgramBuildInfo(prog, dev, CL_PROGRAM_BUILD_LOG, logSize, log.data(), nullptr);
std::cerr << "--- Build Log ---\n" << log.data() << "\n";
return 1;
}
cl_kernel krn = clCreateKernel(prog, "check_shared", &err);
CHECK(err, "clCreateKernel");
const size_t N = 8;
size_t bufSize = N * sizeof(int);
// Allocate host-visible buffer
cl_mem bufIn = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR, bufSize, nullptr, &err);
CHECK(err, "clCreateBuffer input");
cl_mem bufOut = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR, bufSize, nullptr, &err);
CHECK(err, "clCreateBuffer output");
// Map the buffer (should return pointer to real host memory if unified)
int* hostPtr = (int*)clEnqueueMapBuffer(q, bufIn, CL_TRUE, CL_MAP_WRITE, 0, bufSize, 0, nullptr, nullptr, &err);
CHECK(err, "clEnqueueMapBuffer");
std::cout << "Mapped host pointer: " << static_cast<void*>(hostPtr) << "\n";
// Write pattern directly into mapped memory
for (size_t i = 0; i < N; ++i)
hostPtr[i] = 100 + i;
// No clEnqueueWriteBuffer call! We rely on shared memory.
clEnqueueUnmapMemObject(q, bufIn, hostPtr, 0, nullptr, nullptr);
clFinish(q);
// Set kernel args
clSetKernelArg(krn, 0, sizeof(cl_mem), &bufIn);
clSetKernelArg(krn, 1, sizeof(cl_mem), &bufOut);
size_t global = N;
err = clEnqueueNDRangeKernel(q, krn, 1, nullptr, &global, nullptr, 0, nullptr, nullptr);
CHECK(err, "clEnqueueNDRangeKernel");
clFinish(q);
// Read back result
int* outPtr = (int*)clEnqueueMapBuffer(q, bufOut, CL_TRUE, CL_MAP_READ, 0, bufSize, 0, nullptr, nullptr, &err);
CHECK(err, "map output");
std::cout << "Result: ";
for (size_t i = 0; i < N; ++i)
std::cout << outPtr[i] << " ";
std::cout << "\n";
// Validate
bool ok = true;
for (size_t i = 0; i < N; ++i)
if (outPtr[i] != static_cast<int>(142 + i)) ok = false;
std::cout << (ok ? "✅ GPU saw host writes (zero-copy confirmed)\n"
: "❌ GPU did not see host writes (copying or staging occurred)\n");
clEnqueueUnmapMemObject(q, bufOut, outPtr, 0, nullptr, nullptr);
clFinish(q);
clReleaseMemObject(bufIn);
clReleaseMemObject(bufOut);
clReleaseKernel(krn);
clReleaseProgram(prog);
clReleaseCommandQueue(q);
clReleaseContext(ctx);
return 0;
}
+3 -3
View File
@@ -1,3 +1,3 @@
+edev|avia0| +edev|avia0|mesh()|livoxGen1()|livoxProto1()|3JEDK380010Z39||
structural-implexor|livoxGen1()|livoxProto1() +edev|avia0|pcloudIntensity()|livoxGen1()|livoxProto1()|3JEDK380010Z39||
|3JEDK380010Z39 +edev|avia0|pcloudAmbience()|livoxGen1()|livoxProto1()|3JEDK380010Z39
+1 -3
View File
@@ -1,3 +1 @@
+edev|win0| +edev|win0|visual-qualeiface()|xcb(dev-substring)|xorg(display=1|screen=0)|mut
visual-implexor|xcb(dev-substring)|xorg(display=1|screen=0)
|mut
+7 -6
View File
@@ -1,10 +1,11 @@
# DeviceSpec: API `drm()` from server `linux()`: # DeviceSpec: stim-buff-api `drm()` from provider `linux()`:
The API is Linux DRM/KMS. The server is Linux itself. This server provides The stim-buff-api is Linux DRM/KMS. The provider is Linux itself. This provider
direct capture of frames at the kernel so it works for both Linux and Wayland. enables direct capture of frames at the kernel level, so it works for both X11
There's a program known as GPU Screen Recorder that is able to use this to and Wayland. There is a program called GPU Screen Recorder that can use this
capture specific windows on X11, but the window-specific capture doesn't work API to capture specific windows on X11, but window-specific capture does not
with Wayland. Irrespective, whole-screen capture works on both GFX servers. work with Wayland. However, whole-screen capture works on both graphics
servers.
Notes: Notes:
* `modetest` utility in ubuntu package `libdrm-tests` is relevant. * `modetest` utility in ubuntu package `libdrm-tests` is relevant.
+16 -15
View File
@@ -1,24 +1,25 @@
# DeviceSpec: API `xcb`, provider `xorg` # DAP Spec: stim-buff-api `xcb`, provider `xorg`
## Overview ## Overview
The `xcb` API with the `xorg` provider allows Salmanoff to interact with Xorg The `xcb` stim-buff-api with the `xorg` provider allows Salmanoff to interact with Xorg
server windows. This can be used to capture visual data from specific windows server windows. This can be used to capture visual data from specific windows
or entire screens managed by the Xorg server. or entire screens managed by the Xorg server.
## DeviceSpec Format ## DAP Spec Format
The general format of a device-spec for the `xcb` API with the `xorg` provider The general format of a DAP spec for the `xcb` stim-buff-api with the `xorg` provider
is: is:
``` ```
sensor-type|implexor|xcb(api-params)|xorg(provider-params)|deviceSelector sensor-type|dev-identifier|quale-iface-api|xcb(stim-buff-api-params)|xorg(provider-params)|deviceSelector
``` ```
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`' * `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
(extrospector), or '`+adev`' (actuator). (extrospector), or '`+adev`' (actuator).
* `implexor` is the name of the implexor algorithm that should be used with * `dev-identifier` is a user-defined name for this specific device instance.
the data that is provided by the `provider` via the `api`. * `quale-iface-api` is the name of the StimIface library that should be used to
* `api` is `xcb` in this case, and the `api-params` in parentheses may be process the data from the stim buffer.
* `stim-buff-api` is `xcb` in this case, and the `stim-buff-api-params` in parentheses may be
omitted, in which case the parentheses will be empty, but the parentheses omitted, in which case the parentheses will be empty, but the parentheses
must always be written out. must always be written out.
* `provider` is `xorg` in this case, and the `provider-params` in parentheses * `provider` is `xorg` in this case, and the `provider-params` in parentheses
@@ -28,11 +29,11 @@ sensor-type|implexor|xcb(api-params)|xorg(provider-params)|deviceSelector
identify the specific window or screen you want to access via that identify the specific window or screen you want to access via that
`provider`. `provider`.
## `api-params` and `provider-params` ## `stim-buff-api-params` and `provider-params`
### `api-params` ### `stim-buff-api-params`
The `api-params` for the `xcb` API can include the following: The `stim-buff-api-params` for the `xcb` stim-buff-api can include the following:
* `dev-id` or `devid`: Specifies that the `deviceSelector` is a numeric window * `dev-id` or `devid`: Specifies that the `deviceSelector` is a numeric window
ID. The ID can be specified in decimal or hexadecimal format. ID. The ID can be specified in decimal or hexadecimal format.
@@ -97,25 +98,25 @@ xcb(dev-string)|My\ Exact\ Window\ Name
### To attach a specific window by name (substring match): ### To attach a specific window by name (substring match):
``` ```
+edev|visual-implexor|xcb(dev-substring)|xorg(display=0|screen=0)|my-window +edev|my-window|visual-qualeiface|xcb(dev-substring)|xorg(display=0|screen=0)|my-window
``` ```
This will attach to a window whose name contains "my-window" as a substring. This will attach to a window whose name contains "my-window" as a substring.
### To attach a specific window by exact name: ### To attach a specific window by exact name:
``` ```
+edev|visual-implexor|xcb(dev-string)|xorg(display=0|screen=0)|My\ Exact\ Window\ Name +edev|my-window|visual-qualeiface|xcb(dev-string)|xorg(display=0|screen=0)|My\ Exact\ Window\ Name
``` ```
This will attach to a window whose name exactly matches "My Exact Window Name". This will attach to a window whose name exactly matches "My Exact Window Name".
### To attach a specific window by numeric ID: ### To attach a specific window by numeric ID:
``` ```
+edev|visual-implexor|xcb(dev-id)|xorg(display=0|screen=0)|123456 +edev|my-window|visual-qualeiface|xcb(dev-id)|xorg(display=0|screen=0)|123456
``` ```
This will attach to a window with the numeric ID `123456`. This will attach to a window with the numeric ID `123456`.
### To attach the entire screen: ### To attach the entire screen:
``` ```
+edev|visual-implexor|xcb()|xorg(display=0|screen=0)|all +edev|my-screen|visual-qualeiface|xcb()|xorg(display=0|screen=0)|all
``` ```
This will attach to the entire screen `0` of display `0`. This will attach to the entire screen `0` of display `0`.
+17
View File
@@ -0,0 +1,17 @@
# Postrin path design:
## Negtrin and postrin weighting:
I am skeptical that treating negtrins and postrins as having equal importance
will produce a working system.
### Frustrator Negtrin model: Postrins as intrinsically desirable:
In the frustrator model, postrins are intrinsically desirable and negtrins are
only grasped as important in that they forcibly direct Drctr's attention away
from its sampling/pursuit of a postrin. Because of this they're grasped as being
bad because they frustrate the intrinsic goal of pursuing/sampling a postrin.
### Equiprioritized intrin model:
In this model
+294
View File
@@ -0,0 +1,294 @@
# Device Attachment Pipeline (DAP) Specification DSL: attaching sensors and actuators to SMO.
## DAP Specs vs DA Specs:
**DAP (Device Attachment Pipeline) specs** are human-readable DSL
specifications that describe a pipeline of steps to connect a particular
device role on a particular device-identifier to Salmanoff. This document
describes the DAP specification format.
**DA (Device Attachment) specs** are compiled binary structs used internally
by SMO after DAP specs have been parsed into binary format. DA specs are the
internal representation that the system actually uses.
**Multiple Input Formats**: DAP specs can be parsed from multiple
human-readable formats. For example, we intend to eventually extend ROS's
URDF XML format to specify device attachment specs (URDFDA specs), which
would also get compiled into the same DA spec binary format.
## Attaching sensors:
Sensors are input devices to Salmanoff. Salmanoff will perceive them as
perceptual inputs -- like your own sense organs. For example, if you attach a
camera as a sensor, salmanoff will experience it in the same way that you
experience the visual sense data from your eyes.
## QualeIface (Quale Interface):
A QualeIface is a **Quale** **I**nter**face** library that connects to a
particular stim buffer and allows the mind to process the stim features
presented in the device's stim buffers. QualeIface libraries replace the
previous notion of an implexor. They provide the interface between raw device
data and the mind's processing capabilities.
## Device Attachment Pipeline (DAP) Specification Format:
The general format of a DAP specification is:
```
sensor-type|dev-identifier|quale-iface-api(quale-iface-api-params)|stim-buff-api(api-params)|provider(provider-params)|dev-selector
```
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
(extrospector), or '`+adev`' (actuator).
* `dev-identifier` is a user-defined name for this specific device instance.
This represents a logical device that can be accessed through multiple
providers and may expose multiple stim features. In a sense it's like a
sense organ or sense modality.
* `quale-iface-api` is the name of the QualeIface library that should be used to
process the data from the stim buffer. This replaces the previous implexor
concept. The `quale-iface-api-params` in parentheses may be omitted, in which
case the parentheses will be empty, but the parentheses must always be written out.
* `stim-buff-api` is the interface that provides access to a specific stim
buffer from the device. A single device may have multiple stim buffers
(e.g., audio output, microphone input, different data streams). The
`api-params` in parentheses may be omitted, in which case the parentheses
will be empty, but the parentheses must always be written out.
* `provider` may be a userspace daemon or an OS kernel that provides access to
the device's I/O functionality; and thereby allows the `stim-buff-api` to
construct and present a stim-buffer to Salmanoff. The `provider-params` in
parentheses may be omitted, in which case the parentheses will be empty, but
the parentheses must always be written out.
* `dev-selector` is the idiosyncratic label/name used by the `provider` to
identify the specific device you want to attach via that `provider`.
## `quale-iface-api-params`, `stim-buff-api-params` and `provider-params`:
If there's more than one parameter item in a list of `quale-iface-api-params`,
`stim-buff-api-params`, or `provider-params`, then the individual items in a
list should be separated by the h-bar character (`|`). E.g:
```
+edev|soundcard0|audio-qualeiface(param1|param2)|alsa-audio(shmem|param2|param3)|alsa()|cardname
```
Each parameter must be in one of these forms:
* key=value
* key=
* key
### Important Note on `stim-buff-api-params`:
The `stim-buff-api-params` should **never** include options related to the
stim buffer's type or format. The `stim-buff-api` must read and infer such
configuration details from the `quale-iface-api` portion of the DAP spec, and
configure itself accordingly to enable connection by the specified
quale-iface library in the way that it has been configured.
`stim-buff-api-params` are for options that are:
- Device-specific (not modality-wide)
- Specific to this particular stim-feature as provided by this device
- Configuration parameters needed by the stim-buff-api to properly interface
with the device
Examples of appropriate `stim-buff-api-params`:
- Buffer size settings
- Device-specific communication parameters
- Hardware-specific configuration options
- Connection timeouts or retry settings
Examples of **inappropriate** `stim-buff-api-params`:
- Data format specifications (should be inferred from stim-iface-api)
- Color space settings (should be determined by the stim-iface library)
- Processing algorithm parameters (belong to the stim-iface library)
## Logical View and Multiple Access Patterns:
### Single Device, Multiple Providers:
A single `dev-identifier` can unite several `dev-selectors` from multiple
providers. For example, a sound card device `soundcard0` could be accessed
through:
* `ident: soundcard0, provider: alsa` - Provides access to the card via ALSA
API for audio output
* `ident: soundcard0, provider: linux-driver-direct-file-ops` - Provides direct
connection to Linux driver via read/write posix FD calls for beeper sound
output
* `ident: soundcard0, provider: alsa` - Provides access to the card via ALSA
for microphone input
So a single physical device is accessed via multiple providers, each with
different selectors.
### Single Device, Same Provider, Different Stim-Buff-APIs:
A device could have different `stim-buff-apis`, possibly provided by different
shared libraries:
* `ident: soundcard0, provider: alsa, stim-buff-api: alsa-audio` - For audio
output
* `ident: soundcard0, provider: alsa, stim-buff-api: alsa-mic` - For microphone
input
Different stim-buff-apis may be packaged into the same shared library, or
multiple libraries may dlopen a common library behind the scenes.
### Stim Features and Buffers:
Logically, a `dev-identifier` represents a sense modality. Each device can
export multiple stim features. For example, an eye can export:
- Color data
- Light intensity data
- Thermal heat data
- Pain input data
Each stim feature is exposed as a stim buffer, provided by a `stim-buff-api`.
Stim-buff-apis rely upon providers to implement the device-specific operations
required to effectuate the stim-buff controls.
## Examples:
### To attach a particular window from a window manager:
```
+edev|my-window|visual-qualeiface()|wayland()|wayland(server-socket)|window0
```
Connect to the Wayland server that's listening on `server-socket`, using the
`wayland` stim-buff-api. Ask that Wayland server to give salmanoff read-access to all of
the frames composited into the window buffer for `window0`. Use salmanoff's
`visual-qualeiface` to process the visual data from that `window0`'s compositor buffer.
### To attach a window manager's entire rendered desktop:
```
+edev|my-desktop|visual-qualeiface()|wayland()|wayland(listen-socket)|all
```
In most cases, this is basically the same as attempting to attach all of the
underlying GFX server's output.
Connect to the Wayland server that's listening on `listen-socket`, using the
`wayland` stim-buff-api. Ask that Wayland server to give salmanoff read-access to the
entire compositor framebuffer. Use salmanoff's `visual-qualeiface` to process the visual data from
that Wayland server's compositor buffer.
### To attach all of an Xorg server's gfx output to all screens:
```
+edev|my-xorg-display|visual-qualeiface()|x11()|xorg(listen-socket)|all
```
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
stim-buff-api. Ask that Xorg server to let Salmanoff read out all of the frames written
out to all screens. Use salmanoff's `visual-qualeiface` to process the visual data from the
server's gfx framebuffer.
In most cases, this is basically the same as attempting to attach all of the
WM's output.
* Implementation note:
https://stackoverflow.com/questions/33845447/how-do-i-talk-to-an-x-server-in-c-without-a-graphics-library
Seems relevant.
### To attach all of an Xorg server's gfx output to a particular screen:
```
+edev|my-screen|visual-qualeiface()|x11()|xorg(listen-socket)|:0
```
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
stim-buff-api. Ask that Xorg server to let Salmanoff read out all of the frames written
out to display `:0`. Use salmanoff's `visual-qualeiface` to process the visual data from display
`:0`'s framebuffer.
* Implementation note:
https://stackoverflow.com/questions/33845447/how-do-i-talk-to-an-x-server-in-c-without-a-graphics-library
Seems relevant.
### To attach a camera device by connecting directly to its Linux driver:
```
+edev|my-camera|visual-qualeiface()|v4l()|linux()|/dev/video0
```
We specify that we want to use the `linux` kernel's loaded driver to connect
to communicate with `/dev/video0`, via the `Video4Linux` stim-buff-api. We want salmanoff
to use the `visual-qualeiface` library to process the visual data from `/dev/video0`'s stim buffer.
If `/dev/video0` is already consumed by another process, this may likely fail.
### To attach a microphone that's managed by ALSA server:
```
+edev|my-microphone|audio-qualeiface()|alsa-mic(shmem)|alsa()|cardname
```
Connect to the ALSA server via `shmem`, using the `alsa-mic` stim-buff-api. Request access to
the microphone function of the sound card with the name `cardname`. Use the
`audio-qualeiface` library to process the audio data from `cardname`'s microphone stim buffer.
### To attach a thermal sensor managed by Linux:
```
+idev|my-thermal|thermal-qualeiface()|thermal-zone()|linux()|/sys/class/thermal_zone0
```
Use the `thermal-zone` SysFS stim-buff-api provided by `linux` to connect to the sensor
`/sys/class/thermal_zone0`. Use the `thermal-qualeiface` library to process the thermal data from
`thermal_zone0`'s heat stim buffer.
## Multiple Provider Examples:
### Single Sound Card Device with Multiple Providers:
The same physical sound card `soundcard0` can be accessed through different providers:
```
+edev|soundcard0|audio-qualeiface()|alsa-audio()|alsa()|card0
|| +edev|soundcard0|audio-qualeiface()|direct-file-ops()|linux()|/dev/snd/pcmC0D0p
|| +idev|soundcard0|audio-qualeiface()|alsa-mic()|alsa()|card0
```
This shows:
- `soundcard0` accessed via ALSA provider for audio output (`alsa-audio` stim-buff-api)
- `soundcard0` accessed via Linux provider for direct file operations (`direct-file-ops` stim-buff-api)
- `soundcard0` accessed via ALSA provider for microphone input (`alsa-mic` stim-buff-api)
### Single Camera Device with Multiple Stim-Buff-APIs:
A camera device `camera0` might expose different data streams:
```
+edev|camera0|visual-qualeiface()|v4l-rgb()|linux()|/dev/video0
|| +edev|camera0|visual-qualeiface()|v4l-yuv()|linux()|/dev/video0
|| +idev|camera0|thermal-qualeiface()|v4l-thermal()|linux()|/dev/video0
```
This shows the same camera device providing:
- RGB color data via `v4l-rgb` stim-buff-api
- YUV color data via `v4l-yuv` stim-buff-api
- Thermal data via `v4l-thermal` stim-buff-api
## Attaching actuators:
Actuators are Salmanoff's way of enacting changes in the external world.
They're like your libs, or your mouth. Actuators enable salmanoff to write
outputs to the world outside.
### Wilzors:
Actuator devices are analogous to your body's limbs. Salmanoff controls these
by using `wilzor` algorithms. Wilzor is a contraction of **Wil**lpower
Actuat**Or** but with a 'Z' in the middle to make it sound cooler. Different
types of devices will require different wilzor algorithms. You need to know
what type of wilzor algorithm needs to be used to enable salmanoff to control
your actuator device.
The general format for an actuator's device attachment specification follows the same pattern:
```
+adev|dev-identifier|wilzor-algorithm(quale-iface-api-params)|stim-buff-api(api-params)|provider(provider-params)|dev-selector
```
Where `wilzor-algorithm` is the specific wilzor algorithm needed to control the actuator device.
## Device Attachment Pipeline (DAP) specification files:
Inside of a DAP specification file, you can list any number of
DAP specifications.
Separate individual DAP specifications with two consecutive h-bar
characters (`||`),
like this:
```
+edev|my-window|visual-qualeiface()|wayland()|wayland(server-socket)|window0
|| +edev|my-xorg-display|visual-qualeiface()|x11()|xorg(listen-socket)|all
|| +idev|my-thermal|thermal-qualeiface()|thermal-zone()|linux()|/sys/class/thermal_zone0
```
-168
View File
@@ -1,168 +0,0 @@
# Device Attachment Specification DSL: attaching sensors and actuators to SMO.
## Attaching sensors:
Sensors are input devices to Salmanoff. Salmanoff will perceive them as
perceptual inputs -- like your own sense organs. For example, if you attach a
camera as a sensor, salmanoff will experience it in the same way that you
experience the visual sense data from your eyes.
## Implexors:
An implexor is an **Imp**licit **Ex**istent isolat**Or** algorithm. It's
basically what conventional ML/LLM/ANN developers call an ROI ("Region of
Interest") extraction algorithm. An Implex algorithm is used to scan a frame
of input sensor data and detect objects and patterns within it.
## Sensor device attachment specification:
The general format of a device attachment specification for a sensor is:
```
sensor-type|dev-identifier
|implexor|api(api-params)|provider(provider-params)|deviceselector
```
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
(extrospector), or '`+adev`' (actuator).
* `dev-identifier` is a user-defined name for this specific device instance.
* `implexor` is the name of the implexor algorithm that should be used with
the data that is provided by the `provider` via the `api`.
* `api` is the interface that the provider uses to export perceptual data for
salmanoff to read. Salmanoff will run the `implexor` algorithm on the data
from this `api`. The `api-param` in parentheses may be omitted, in which
case the parentheses will be empty, but the parentheses must always be
written out.
* `provider` may be a userspace daemon or an OS kernel that provides perceptual
data via the `api`. The `provider-params` in parentheses may be omitted, in
which case the parenthesis will be empty, but the parentheses must always be
written out.
* `device selector` is the idiosyncratic label/name used by the `provider` to
identify the specific device you want to attach via that `provider`.
## `API-params` and `provider-params`:
If there's more than one parameter item in a list of `api-params` or
`provider-params`, then the individual items in a list of `api-param` or
`provider-params` should be separated by the h-bar character (`|`). E.g:
```
+edev|audio-implexor|alsa(shmem|param2|param3)|alsa()|cardname
```
Each parameter must be in one of these forms:
* key=value
* key=
* key
Some examples follow:
### To attach a particular window from a window manager:
```
+edev|my-window|visual-implexor|wayland()|wayland(server-socket)|window0
```
Connect to the Wayland server that's listening on `server-socket`, using the
`wayland` api. Ask that Wayland server to give salmanoff read-access to all of
the frames composited into the window buffer for `window0`. Use salmanoff's
`visual-implexor` to implex from that `window0`'s compositor data.
### To attach a window manager's entire rendered desktop:
```
+edev|my-desktop|visual-implexor|wayland()|wayland(listen-socket)|all
```
In most cases, this is basically the same as attempting to attach all of the
underlying GFX server's output.
Connect to the Wayland server that's listening on `listen-socket`, using the
`wayland` api. Ask that Wayland server to give salmanoff read-access to the
entire compositor framebuffer. Use salmanoff's `visual-implexor` to implex from
that Wayland server's compositor data.
### To attach all of an Xorg server's gfx output to all screens:
```
+edev|my-xorg-display|visual-implexor|x11()|xorg(listen-socket)|all
```
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
api. Ask that Xorg server to let Salmanoff read out all of the frames written
out to all screens. Use salmanoff's `visual-implexor` to implex from the
server's gfx framebuffer data.
In most cases, this is basically the same as attempting to attach all of the
WM's output.
* Implementation note:
https://stackoverflow.com/questions/33845447/how-do-i-talk-to-an-x-server-in-c-without-a-graphics-library
Seems relevant.
### To attach all of an Xorg server's gfx output to a particular screen:
```
+edev|my-screen|visual-implexor|x11()|xorg(listen-socket)|:0
```
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
api. Ask that Xorg server to let Salmanoff read out all of the frames written
out to display `:0`. Use salmanoff's `visual-implexor` to implex from display
`:0`'s framebuffer data.
* Implementation note:
https://stackoverflow.com/questions/33845447/how-do-i-talk-to-an-x-server-in-c-without-a-graphics-library
Seems relevant.
### To attach a camera device by connecting directly to its Linux driver:
```
+edev|my-camera|visual-implexor|v4l()|linux()|/dev/video0
```
We specify that we want to use the `linux` kernel's loaded driver to connect
to communicate with `/dev/video0`, via the `Video4Linux` API. We want salmanoff
to use the `visual-implexor` algorithm to implex from `/dev/video0`'s data.
If `/dev/video0` is already consumed by another process, this may likely fail.
### To attach a microphone that's managed by ALSA server:
```
+edev|my-microphone|audio-implexor|alsa(shmem)|alsa()|cardname
```
Connect to the ALSA server via `shmem`, using the `alsa` API. Request access to
the microphone function of the sound card with the name `cardname`. Use the
`audio-implexor` algorithm to implex from `cardname`'s microphone data.
### To attach a thermal sensor managed by Linux:
```
+idev|my-thermal|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
```
Use the `thermal-zone` SysFS API provided by `linux` to connect to the sensor
`/sys/class/thermal_zone0`. Use the `thermal-implexor` implexor to implex from
`thermal_zone0`'s heat data.
## Attaching actuators:
Actuators are Salmanoff's way of enacting changes in the external world.
They're like your libs, or your mouth. Actuators enable salmanoff to write
outputs to the world outside.
### Wilzors:
Actuator devices are analogous to your body's limbs. Salmanoff controls these
by using `wilzor` algorithms. Wilzor is a contraction of **Wil**lpower
Actuat**Or** but with a 'Z' in the middle to make it sound cooler. Different
types of devices will require different wilzor algorithms. You need to know
what type of wilzor algorithm needs to be used to enable salmanoff to control
your actuator device.
The general format for an actuator's device attachment specification is:
```
WIP: TBD.
```
## Device attachment specification files:
Inside of a device attachment specification file, you can list any number of
device attachment specifications.
Separate individual device attachment specifications with two consecutive h-bar
characters (`||`),
like this:
```
+edev|my-window|visual-implexor|wayland()|wayland(server-socket)|window0
|| +edev|my-xorg-display|visual-implexor|x11()|xorg(listen-socket)|all
|| +idev|my-thermal|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
```
+86 -41
View File
@@ -1,14 +1,23 @@
# LivoxGen1Lidar Device Attachment Protocol (DAP) Specification # LivoxGen1Lidar Device Attachment Pipeline (DAP) Specification
## Overview ## 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. The LivoxGen1Lidar DAP specification describes how to attach to Livox Gen1
LiDAR devices and access their various data streams. The Livox Gen1 LiDAR
presents multiple stim features, each with its own dedicated stim-buff-api.
## API Structure ## Stim-Buff-API Structure
The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based parameters to specify which data interface to present. The LivoxGen1Lidar DAP uses multiple stim-buff-api names, one for each stim feature it presents:
## Device Attachment Specifications * `livoxGen1-pcloud` - Point cloud coordinate data
* `livoxGen1-pcloudIntensity` - Point cloud intensity/reflectivity data
* `livoxGen1-gyro` - Gyroscope data from internal IMU
* `livoxGen1-accel` - Accelerometer data from internal IMU
Each stim-buff-api is designed to work with specific stim-iface libraries that understand the data format and processing requirements for that particular stim feature.
## DAP Specifications
### 1. Point Cloud Intensity Data Device (Interoceptor) ### 1. Point Cloud Intensity Data Device (Interoceptor)
@@ -16,37 +25,58 @@ The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based param
**Syntax**: **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 +idev | avia0 | pcloudIntensity | livoxGen1-pcloudIntensity(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
``` ```
**Alternative Syntax** (all equivalent): **Stim-Buff-API**: `livoxGen1-pcloudIntensity`
**Quale-Iface-API**: `pcloudIntensity` - Processes intensity/reflectivity data from point clouds
### 2. Point Cloud Ambience Data Device (Interoceptor)
**Purpose**: Provides ambience data from the LiDAR point cloud, counting high-intensity points per slot.
**Syntax**:
``` ```
+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 | pcloudAmbience | livoxGen1-pcloud(high-val=120) | livoxProto1(command-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): **Stim-Buff-API**: `livoxGen1-pcloud`
- `pointCloudIntensity` **Quale-Iface-API**: `pcloudAmbience` - Counts points with intensity >= threshold per slot
- `pcloudIntensity`
- `pCloudIntensity`
- `pCloudI`
- `pcloudI`
### 2. Point Cloud Coordinate Data Device (Extrospector) **Ambience High Value Parameter** (for pcloudAmbience quale-iface-api):
- **Parameter names**: `high-value` or `high-val` (synonyms)
- **Purpose**: Threshold value for counting high-intensity points in the ambience buffer
- **Default value**: 116
- **Usage**: Points with intensity >= `ambienceHighVal` are counted in the ambience buffer per slot
- **Configuration**: Specified in `stim-buff-api-params` when using `pcloudAmbience` quale interface
- **Example**: `high-val=120` or `high-value=120`
### 3. Point Cloud Coordinate Data Device (Extrospector)
**Purpose**: Provides spatial coordinate data from the LiDAR point cloud. **Purpose**: Provides spatial coordinate data from the LiDAR point cloud.
**Syntax**: **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 +edev | avia0 | pcloud(format=xyz) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
``` ```
**Mode Parameter Values** (synonymous): **Example with n-dgrams-per-frame parameter**:
- `pcloud` ```
- `pCloud` +edev | avia0 | pcloud(format=xyz) | livoxGen1-pcloud(data-rate-hz=10,n-dgrams-per-frame=84) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
- `pointCloud` ```
**Format Parameter** (for point cloud modes): **Alternative Format Examples**:
```
+edev | avia0 | pcloud(format=spherical) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
+edev | avia0 | pcloud(format=spherical-cartesian) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
+edev | avia0 | pcloud(format=dual-cartesian) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
+edev | avia0 | pcloud(format=dual-spherical) | livoxGen1-pcloud(data-rate-hz=10) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
```
**Stim-Buff-API**: `livoxGen1-pcloud`
**Quale-Iface-API**: `pcloud` - Processes spatial coordinate data from point clouds
**Format Parameter Values** (for pcloud quale-iface-api):
- `xyz`: Standard Cartesian coordinates (X, Y, Z) - `xyz`: Standard Cartesian coordinates (X, Y, Z)
- `spherical`: Raw spherical coordinates - `spherical`: Raw spherical coordinates
- `spherical-cartesian`: Spherical coordinates converted to Cartesian - `spherical-cartesian`: Spherical coordinates converted to Cartesian
@@ -56,29 +86,29 @@ The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based param
**Alternative Format Parameter Names** (synonymous): **Alternative Format Parameter Names** (synonymous):
- `format` or `fmt` - `format` or `fmt`
### 3. IMU Gyroscope Data Device (Interoceptor) ### 4. IMU Gyroscope Data Device (Interoceptor)
**Purpose**: Provides gyroscope data from the LiDAR's internal IMU. **Purpose**: Provides gyroscope data from the LiDAR's internal IMU.
**Syntax**: **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 +idev | avia0 | gyro | livoxGen1-gyro(data-rate-hz=100) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
``` ```
**Mode Parameter Values**: **Stim-Buff-API**: `livoxGen1-gyro`
- `gyro` **Quale-Iface-API**: `gyro` - Processes gyroscope angular velocity data
### 4. IMU Accelerometer Data Device (Interoceptor) ### 5. IMU Accelerometer Data Device (Interoceptor)
**Purpose**: Provides accelerometer data from the LiDAR's internal IMU. **Purpose**: Provides accelerometer data from the LiDAR's internal IMU.
**Syntax**: **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 +idev | avia0 | accel | livoxGen1-accel(data-rate-hz=100) | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
``` ```
**Mode Parameter Values**: **Stim-Buff-API**: `livoxGen1-accel`
- `accel` **Quale-Iface-API**: `accel` - Processes accelerometer linear acceleration data
## Provider Parameters ## Provider Parameters
@@ -86,10 +116,10 @@ The LivoxGen1Lidar DAP uses a unified API name `livoxGen1` with mode-based param
The `livoxProto1` provider accepts the following parameters: The `livoxProto1` provider accepts the following parameters:
**handshake-timeout-ms** (optional): **command-timeout-ms** / **cmd-timeout-ms** (optional, synonyms):
- Specifies the timeout for handshake operations when connecting to devices - Specifies the timeout for command operations when communicating with devices
- Value: Integer number of milliseconds - Value: Integer number of milliseconds
- Example: `handshake-timeout-ms=1000` (1 second timeout) - Example: `command-timeout-ms=1000` or `cmd-timeout-ms=1000` (1 second timeout)
- Default: 1000ms if not specified - Default: 1000ms if not specified
**retry-delay-ms** (optional): **retry-delay-ms** (optional):
@@ -124,16 +154,29 @@ The `livoxProto1` provider accepts the following parameters:
## Parameter Summary ## Parameter Summary
### Mode/Stim/Affordance Parameter Values ### Stim-Buff-API Names
| Data Type | Mode Values | Description | | Stim Feature | Stim-Buff-API | Quale-Iface-API | Description |
|-----------|-------------|-------------| |--------------|---------------|----------------|-------------|
| Point Cloud Intensity | `pointCloudIntensity`, `pcloudIntensity`, `pCloudIntensity`, `pCloudI`, `pcloudI` | Light intensity/reflectivity data | | Point Cloud Intensity | `livoxGen1-pcloudIntensity` | `pcloudIntensity` | Light intensity/reflectivity data |
| Point Cloud Coordinates | `pcloud`, `pCloud`, `pointCloud` | Spatial coordinate data | | Point Cloud Ambience | `livoxGen1-pcloud` | `pcloudAmbience` | High-intensity point count per slot |
| Gyroscope | `gyro` | Angular velocity measurements | | Point Cloud Coordinates | `livoxGen1-pcloud` | `pcloud` | Spatial coordinate data |
| Accelerometer | `accel` | Linear acceleration measurements | | Gyroscope | `livoxGen1-gyro` | `gyro` | Angular velocity measurements |
| Accelerometer | `livoxGen1-accel` | `accel` | Linear acceleration measurements |
### Format Parameter Values (for point cloud modes) ### Stim-Buff-API Parameters
Each stim-buff-api accepts device-specific parameters:
| Parameter | Description | Default | Example |
|-----------|-------------|---------|---------|
| `data-rate-hz` | Data sampling rate in Hz | - | `data-rate-hz=10` |
| `n-dgrams-per-frame` / `num-dgrams-per-frame` | Number of UDP datagrams per staging buffer frame | 84 | `n-dgrams-per-frame=84` or `num-dgrams-per-frame=84` |
| `high-value` / `high-val` | Threshold for counting high-intensity points in ambience buffer (for `pcloudAmbience` quale-iface-api) | 116 | `high-val=120` or `high-value=120` |
### Quale-Iface-API Parameters
The `pcloud` quale-iface-api accepts format parameters:
| Format | Description | | Format | Description |
|--------|-------------| |--------|-------------|
@@ -143,6 +186,8 @@ The `livoxProto1` provider accepts the following parameters:
| `dual-cartesian` | Dual Cartesian coordinate system | | `dual-cartesian` | Dual Cartesian coordinate system |
| `dual-spherical` | Dual spherical coordinate system | | `dual-spherical` | Dual spherical coordinate system |
The `pcloudAmbience` quale-iface-api uses the `high-value` / `high-val` parameter (documented in Stim-Buff-API Parameters above) to determine the intensity threshold for counting high-intensity points per slot.
## Device Discovery and Connection ## Device Discovery and Connection
The specification uses a retry-based connection strategy with two different approaches: The specification uses a retry-based connection strategy with two different approaches:
+16
View File
@@ -0,0 +1,16 @@
This guy talks about getting it to work using a fake transform:
https://stackoverflow.com/questions/52420672/ros-rviz-how-to-visualize-a-point-cloud-that-doesnt-have-a-fixed-frame-transfo
This thread contains some info about what a transform is:
https://answers.ros.org/question/328839/
Somewhat useful troubleshooter:
https://www.youtube.com/watch?v=b9YZITmCWe4
Excellent, full-featured explanation:
https://www.youtube.com/watch?v=QyvHhY4Y_Y8
+146
View File
@@ -0,0 +1,146 @@
# Quale Interface APIs (QualeIface APIs)
## Overview
QualeIface APIs are libraries that connect to particular stim buffers and allow the mind to process the stim features presented in the device's stim buffers. They provide the interface between raw device data and the mind's processing capabilities.
## Universally Understood Parameters
The following parameters are universally understood across all QualeIface API implementations.
### `history-buffer-duration-ms` / `hist-buff-duration-ms` / `histbuff-duration-ms` / `histbuff-ms`
**Synonyms:**
- `history-buffer-duration-ms`
- `hist-buff-duration-ms`
- `histbuff-duration-ms`
- `histbuff-ms`
**Description:**
This parameter determines how long the history of the particular StimBuff being attached to the DAP spec's device role will be. The value is specified in milliseconds and determines the duration of historical data that will be maintained in the stimulus buffer.
**Specification:**
- The parameter is specified as part of the `quale-iface-api-params` in the DAP specification
- The value is an integer representing milliseconds
- If multiple synonyms are specified, the lattermost (last encountered) synonym takes precedence
- If not specified, a default value of 30000ms (30 seconds) is used
**Example:**
```
+edev|my-device|visual-qualeiface(histbuff-ms=60000)|v4l()|linux()|/dev/video0
```
This example sets the history buffer duration to 60000ms (60 seconds).
**Note:**
This parameter is specific to each stimbuff/deviceRole combination. Different device roles can have different history buffer durations based on their requirements.
## Overview
QualeIface APIs are interface libraries that connect to particular stim buffers and allow the mind to process the stim features presented in the device's stim buffers. They provide the interface between raw device data and the mind's processing capabilities.
## Universally Understood QualeIface API Parameters
This document describes quale-iface-api-params that are universally understood across all QualeIface implementations.
### history-buffer-duration-ms / hist-buff-duration-ms / histbuff-duration-ms / histbuff-ms
**Purpose:** Determines how long the history of the particular StimBuff being attached to the DAP spec's device role will be.
**Synonyms:**
- `history-buffer-duration-ms` (full canonical name)
- `hist-buff-duration-ms` (abbreviated)
- `histbuff-duration-ms` (shortened)
- `histbuff-ms` (shortest)
**Type:** Integer (milliseconds)
**Scope:** Specific to each stimbuff/deviceRole. Each device attachment can specify its own history buffer duration independently.
**Default:** If not specified, implementations typically use a default value (commonly 30000ms = 30 seconds).
**Usage:** The value specifies the duration in milliseconds for which stimulus frames will be retained in the history buffer. This affects how many slots are allocated in the ring buffer: `nSlots = histbuffMs / CONFIG_STIMBUFF_FRAME_PERIOD_MS`.
**Example:**
```
+edev|my-camera|visual-qualeiface(histbuff-ms=60000)|v4l()|linux()|/dev/video0
```
This example sets a 60-second history buffer duration for the visual qualeiface processing the camera's point cloud data.
**Notes:**
- If multiple synonyms are specified in the same parameter list, the lattermost one takes precedence
- The parameter is parsed from the `quale-iface-api-params` section of the DAP specification
- This parameter is specific to each device attachment, allowing different devices to have different history durations
This document describes universally understood quale-iface-api-params that can be used across different QualeIface implementations.
## history-buffer-duration-ms
### Synonyms
The `history-buffer-duration-ms` parameter can be specified using any of the following names:
- `history-buffer-duration-ms` (full form)
- `hist-buff-duration-ms` (abbreviated)
- `histbuff-duration-ms` (abbreviated, no dashes)
- `histbuff-ms` (shortest form)
### Description
The `history-buffer-duration-ms` parameter determines how long the history of the particular StimBuff being attached to the DAP spec's device role will be. This value specifies the duration in milliseconds for which stimulus frame history will be maintained in the buffer.
### Usage
This parameter is **specific to each stimbuff/deviceRole**. Each device attachment can have its own history buffer duration, allowing fine-grained control over memory usage and history retention for different sensor types.
### Example
```
+edev|my-camera|visual-qualeiface(histbuff-ms=30000)|v4l()|linux()|/dev/video0
```
This example sets a 30-second history buffer for the camera device's stimulus buffer.
### Default Value
If not specified, the default value is **30000 milliseconds (30 seconds)**.
### Notes
- The parameter value should be specified as an integer representing milliseconds
- Later synonyms in the parameter list will override earlier ones if multiple are specified
- The actual number of buffer slots allocated will be calculated based on this duration divided by the frame period (CONFIG_STIMBUFF_FRAME_PERIOD_MS)
This document describes universally understood parameters that can be used in quale-iface-api-params for device attachment specifications.
## history-buffer-duration-ms
**Synonyms:**
- `history-buffer-duration-ms`
- `hist-buff-duration-ms`
- `histbuff-duration-ms`
- `histbuff-ms`
**Description:**
The `history-buffer-duration-ms` parameter determines how long the history of the particular StimBuff being attached to the DAP spec's device role will be. This parameter is specific to each stimbuff/deviceRole combination.
**Usage:**
This parameter specifies the duration in milliseconds for which historical stimulus frame data will be retained in the buffer. The value determines the number of frames that can be stored, based on the frame period configured for the stimulus buffer.
**Example:**
```
+edev|avia0|structural-qualeiface(histbuff-ms=60000)|livoxGen1()|livoxProto1()|3JEDK380010Z39
```
This example sets the history buffer duration to 60000ms (60 seconds) for the avia0 device.
**Default Value:**
If not specified, the default value is 30000ms (30 seconds).
**Notes:**
- The parameter value should be specified in milliseconds
- Multiple synonyms can be used, with later synonyms in the parameter list taking precedence
- This parameter is parsed from the quale-iface-api-params, not from stim-buff-api-params or provider-params
- The actual number of buffer slots is calculated as: `histbuffMs / CONFIG_STIMBUFF_FRAME_PERIOD_MS`
+122
View File
@@ -0,0 +1,122 @@
# The reason why Rusticl behaves so weirdly with USE_HOST_PTR
```
[18:21] == rusticluser [~oftc-webi@2803:1500:c00:eb3:c450:9864:8f21:f2fb] has joined #rusticl
[18:22] <rusticluser> Hey guys, I have questions about the implementation of clEnqueueMapBuffer/clEnqueueUnmapMemObject in Rusticl.
[18:22] <rusticluser> This webpage says I should ping karolherbst
[18:22] <rusticluser> https://docs.mesa3d.org/rusticl.html
[18:23] <rusticluser> I am finding some very odd behaviour on the Raspberry Pi 5, when using the v3d GPU via Rusticl
[18:24] <rusticluser> (Gimme a bit to write up my questions)
[18:25] == pbrobinson [~pbrobinso@2001:8b0:fb11:2681:e9:f8b:31b:f797] has joined #rusticl
[18:29] <rusticluser> Here's a dump of the output from running `RUSTICL_ENABLE=v3d clinfo` on my Raspberry Pi 5: https://gist.github.com/latentPrion/9843ff5b98f21b20b9f6d5bce43006b3
[18:30] <rusticluser> Of particular note is that it says that the V3D GPU has a unified memory architecture with the main ARM CPU complex:
[18:30] <rusticluser> > Unified memory for Host and Device Yes
[18:32] <rusticluser> Because all of my target platforms seem to have unified memory with the CL GPUs, I decided that I would aim to optimize my program by using CL_MEM_USE_HOST_PTR, and avoiding using clEnqueueRead/WriteBuffer. I have indeed got it working on both the RPi5 and on my x86 laptop, but some of the things that were required to get it working on the RPi5+Rusticl implementation are a bit odd, and I wanted to confirm whether these behaviours and apparent eccentricities are
[18:32] <rusticluser> intentional
[18:34] <rusticluser> Here is my code, for your perusal and reference.
[18:34] <rusticluser> https://gist.github.com/latentPrion/d9fb3f0604a957d2055786a118072482
[18:36] <rusticluser> So: the long and short of it is: I have an input buffer (called "assemblyBuffer") that was filled with data by io_uring. I create an openCL buffer for assemblyBuff, using CL_MEM_USE_HOST_PTR. I then want to pass this assemblyBuffer into an OpenCL kernel.
[18:37] <rusticluser> The OpenCL kernel doesn't see the data that was written into the buffer unless I use CL_MAP_WRITE_INVALIDATE. I can understand the reasoning behind this, if the reasoning is that the cache invalidation op is performed on the GPU side.
[18:38] <rusticluser> That makes sense because the GPU's caches may hold stale data that prevent it from seeing the data I put into the HOST_PTR buffer. So the need to invalidate the GPU's caches makes perfect sense and I'm not complaining about this.
[18:39] <rusticluser> It's the next bit that is a bit confusing to me, and which I suspect is a bug in RustIcl or the MESA driver behind it.
[18:40] <rusticluser> I have a 2nd buffer, called the "collateBuffer", which is distinct from the "assemblyBuffer". I run a 2nd kernel after the first kernel, which takes the assemblyBuffer as input, and produces its output into the collationBuffer.
[18:42] <rusticluser> Now, since the 1st kernel wrote its output data into the assemblyBuffer, this should mean that the GPU's caches should be up to date with the data that was just written into the assemblyBuffer by the 1st kernel -- because it was the GPU itself which wrote that data into the assembyBuffer
[18:43] <rusticluser> Yet, for some reason, I'm still required to remap the assemblyBuffer with CL_MEM_WRITE_INVALIDATE_REGION when I want to run the 2nd kernel.
[18:43] <rusticluser> 1. I have not modified the assemblyBuffer's data at all on the host CPU. The data in the assemblyBuffer is exactly what was written into it by the 1st kernel when it was running on the GPU.
[18:44] <rusticluser> 2. The 2nd kernel doesn't write into, or modify the assemblyBuffer at all in any way. The 2nd kernel uses the assemblyBuffer as input *ONLY*.
[18:44] <rusticluser>
[18:46] <rusticluser> I guess my question is: why am I required to first map and unmap the assemblyBuffer as CL_MAP_WRITE_INVALIDATE_REGION before the GPU can see the contents of the assemblyBuffer, even though the GPU itself just wrote that data into it, and the GPU's caches should be in sync with it?
[18:47] <rusticluser> (You can see the remapping with CL_MAP_WRITE_INVALIDATE_REGION for the 2nd kernel's execution here: https://gist.github.com/latentPrion/d9fb3f0604a957d2055786a118072482#file-openclcollatingandmeshingengine-cpp-L343)
[18:48] <rusticluser> Technically, I should be able to just map it as CL_MAP_WRITE without needing to specify INVALIDATE_REGION -- am I incorrect?
[18:49] <rusticluser> Basically what you see in that pasted gist is what is required to get this to work on the RPi5, so any decisions you see in the code are constrained by either (1) Rusticl, (2) MESA drivers, (3) the RPi5 hardware
[18:51] <rusticluser> I downloaded the Mesa source code and asked Cursor to scan it and find out what's going on (I don't know Rust, so I can't read the code myself very well) and Cursor says that there's an interediate layer of "shadow buffering" implemented by Rusticl between the host and GPU
[18:52] <rusticluser> And that this intermediate shadow buffering layer is the source of the unexpected behaviours
[19:11] <karolherbst> rusticluser: launching kernels on mapped buffers is undefined behavior
[19:14] <karolherbst> though not sure if that's what you run into, just sounded like it
[19:17] <rusticluser> karolherbst: Yea, but I don't keep them mapped -- notice that I map and then immediately unmap
[19:18] <rusticluser> Literally: mapBuffer(); unmapBuffer() back to back lol -- good pointer though
[19:18] <karolherbst> I'm a bit confused by the code, how do you verify that the GPU is or isn't reading the correct data?
[19:18] <karolherbst> or do you access it through the host pointer directly?
[19:19] <rusticluser> karolherbst: I check using printf() (OpenCL 1.2 extension) inside of the running kernel, and also I check the resulting output after the kernel has been executed
[19:19] <karolherbst> ahh
[19:19] <rusticluser> Would you like to see the kernels? They're just clutter for your headspace, but maybe they might give you some kind of information I don't know about
[19:19] <karolherbst> USE_HOST_PTR is a bit weird, because it doesn't guarnatee coherency
[19:20] <rusticluser> Yea -- I can understand that: the real thing that a developer who's using USE_HOST_PTR wants from the underlying implementation is something like this workflow:
[19:22] <rusticluser> (1) clEnqueueMapBuffer(CL_MAP_WRITE) => /* (2) I write stuff into the buffer */ => (3) clEnqueueUnmapMemObject() /* At this point, during the unmap operation, the CL implementation is expected to write-back the host CPU's caches to main memory, and then invalidate the GPU's caches so that the GPU can see the writes that were stored to main memory
[19:23] <rusticluser> And for the read-side, the workflow that the developer intuitively expects is:
[19:25] <rusticluser> (1) clEnqueueMapBuffer(CL_MAP_READ) /* This mapping call should cause the GPU to write-back to main memory, and should cause the host CPU to invalidate its caches so it can see what was written by the GPU */ => (2) /* I read the stuff from the buffer */ => (3) clEnqueueUnmapMemObject() /* No special maintenance required here */
[19:26] <karolherbst> right.. I think it's potentially also an issue with the rpi driver. It's not really well tested, so random bugs could always exist there. Might want to verify that your application behaves correctly on other GPUs
[19:27] <rusticluser> Yea -- I only have this RPi5 as an ARM testbed, sadly. The other test machine I have is this shitty Intel Core I5 laptop with an Intel HD GPU. The Intel HD GPU doesn't require any mapping/unmapping of any kind -- the cache coherency domain seems to fully cover the GPU on the Intel laptop
[19:28] <rusticluser> Idk, maybe it's a bug, maybe it's not -- I guess I was checking to see if the behaviour I was seeing was intentional and I just didn't properly understand the memory/execution model of OpenCL; or whether it's actually a bug somewhere in the underlying implementation's stack
[19:28] <karolherbst> the intel is the only driver that ever added support for actually mapping host memory into the GPU when it's not page aligned
[19:29] <rusticluser> Ah -- my HOST_PTRs are aligned to _SC_PAGE_SIZE
[19:29] <karolherbst> I don't think the rpi driver supports mapping host memory at all
[19:29] <rusticluser> :(
[19:29] <karolherbst> yeah...
[19:30] <karolherbst> not sure if it's because of missing kernel interfaces or what's the reason there
[19:30] <rusticluser> How can I check and see? I have no understanding of GFX drivers and I hear they're a real domain-specific kind of mess to read;
[19:30] <rusticluser> At minimum, which "module" in the mesa code provides the RPi5 opencl driver/support?
[19:31] <karolherbst> well I know that it doesn't support it on the mesa side, but I haven't checked if there is in theory a kernel interface for it or not
[19:31] <karolherbst> `src/gallium/drivers/v3d/` is the drive inside mesa
[19:31] <rusticluser> *nod*, thanks
[19:31] <rusticluser> Is this worth filing a ticket/issue for?
[19:32] <karolherbst> _not_ sure. Maybe if there is a strong interest to also implement the GL/vulkan features allowing for mapping host memory
[19:33] <rusticluser> Alright -- I'll just keep my eye on it and if it becomes an unmanageable problem, I'll file a ticket and probably also try to add the support myself
[19:34] <rusticluser> These new LLMs really enable you to extend yourself into new domains and contribute to stuff you otherwise wouldn't have the time/insight to be able to, so if it really becomes unmanageable, I'll probably be able to just fix it and submit a patch
[19:34] <karolherbst> though it should still work in theory, so not really sure what's going wrong there
[19:34] <rusticluser> It should lol -- the purpose of the clEnqueueMap/Unmap calls isn't to actually "map" anything -- it's purely to manage the cache synchronization between the host CPU complex and the GPU
[19:35] <karolherbst> but I'd verify if your application behaves as expected on other hardware/drivers as well, maybe even on discrete GPUs
[19:35] <rusticluser> AFAICT, it's probably just a bug in the cache management
[19:35] <rusticluser> It definitely won't work on a GPU that doesn't have shared memory because the design is explicitly for USE_HOST_PTR
[19:36] <karolherbst> then it's broken also for shared memory systems
[19:36] <rusticluser> Hmmm -- could you elaborate on that?
[19:37] <karolherbst> USE_HOST_PTR doesn't really allow for different use csaes as it doesn't really gurantee anything except that the pointer returned by mapBuffer matches the host pointer
[19:37] <karolherbst> aand that's all the additional guarantee it gives you
[19:38] <karolherbst> you still have to use it as if it wouldn't be a host ptr allocation, because synchronization points are the same as with non host ptr allocations
[19:38] <rusticluser> Yes, indeed: but it's also explicitly different from CL_MEM_ALLOC_HOST_PTR, I think? The difference is that CL_MEM_ALLOC_HOST_PTR is likely to be mapping in device MMIO registers
[19:38] <karolherbst> alloc host ptr just means that the allocation is done in host memory instead of VRAM
[19:38] <karolherbst> maybe
[19:38] <karolherbst> it's just a hint
[19:39] <karolherbst> like it uses GART infrastructure and the GPU just accesses memory over PCIe (if a discrete GPU)
[19:39] <karolherbst> for unified memory GPU it shouldn't make any difference
[19:39] <rusticluser> I'm sorry -- am I wrong? CL_MEM_ALLOC_HOST_PTR means only that the buffer returned will be *ACCESSIBLE* by the host. This means that the buffer could be MMIO mapped registers, or some other such memory range
[19:40] <rusticluser> It doesn't actually mean that the buffer is allocated from host mem
[19:40] <rusticluser> It just means that the buffer will be *ACCESSIBLE* from host mem, __POTENTIALLY__ without a copy
[19:40] <karolherbst> it has nothing to do with access
[19:41] <rusticluser> https://registry.khronos.org/OpenCL/sdk/3.0/docs/man/html/clCreateBuffer.html:
[19:41] <karolherbst> sure, but it means something else
[19:41] <rusticluser> > This flag specifies that the application wants the OpenCL implementation to allocate memory from host accessible memory. CL_MEM_ALLOC_HOST_PTR and CL_MEM_USE_HOST_PTR are mutually exclusive.
[19:41] <rusticluser> Ah ok lol
[19:41] <karolherbst> like you can't access the memory allocation either way directly, because you have to map
[19:42] <karolherbst> though CL_MEM_ALLOC_HOST_PTR is more of a "please don't use VRAM, so that reading out the memory on the host is quick"
[19:42] <rusticluser> It seems like the reason why they say that ALLOC_HOST_PTR and USE_HOST_PTR are mutually exclusive is *precisely because* ALLOC_HOST_PTR is not guaranteed to be allocated within host memory lol
[19:42] <karolherbst> well.. you have no control over what address the mapping will have
[19:43] <karolherbst> USE_HOST_PTR already uses host memory, so alloc_host_ptr is meaningless
[19:43] <rusticluser> I am fairly certain that MEM_ALLOC_HOST_PTR means, "You may use VRAM if you wish, but ensure that it's a portion of your internal VRAM that can be exposed and mapped as MMIO. You may also use host RAM if you wish -- both are fine"
[19:43] <rusticluser> [19:43] <karolherbst> USE_HOST_PTR already uses host memory, so alloc_host_ptr is meaningless
[19:43] <rusticluser> ^ Absolutely correct
[19:43] <rusticluser> Wait whoa no
[19:44] <karolherbst> VRAM can always be mapped into host memory, it's just slow
[19:44] <karolherbst> and you have to fight with PCI bar sizes
[19:44] <karolherbst> though you can also set different caching hints etc..
[19:45] <rusticluser> When I say "VRAM" here, I was mimicking your language, but a more accurate term would be "device memory" because there's no guarantee that the OpenCL device is indeed a GPU, or that it exposes all of its global, local or private memory in an MMIO or host-accessible fashion lol
[19:45] <rusticluser> Ok errm, I don't think arguing over this will go very far lol
[19:46] <rusticluser> But I really appreciate your pointers -- I'll look for another test board
[19:46] <rusticluser> Really appreciate your time -- I know this is a volunteer effort on your part
[19:47] <fdobridge_> <leftmostcat> Heheh. Pointers.
[19:47] == rusticluser [~oftc-webi@2803:1500:c00:eb3:c450:9864:8f21:f2fb]
[19:47] == realname : OFTC WebIRC Client
[19:47] == channels : #rusticl
[19:47] == server : weber.oftc.net [Newark, NJ, USA]
[19:47] == realhost : [ip: actually using host]
[19:47] == idle : 0 days 0 hours 1 minutes 20 seconds [connected: Wed Nov 12 18:21:37 2025]
[19:47] == End of WHOIS
[19:49] <karolherbst> yeah anyway.. on the rpi5 driver might as well not use use_host_ptr because rusticl will have to copy things around to fake host_ptr support anyway. So might as well then not use it. But I also wanted to implement more optimized map/unmap paths for single device context with unified memory, because atm it's asuming worst case and isn't really
[19:49] <karolherbst> optimized very well anyway
[19:49] <karolherbst> but those optimizations will also paper over correctness issues
[19:51] <karolherbst> though I'm also not convinced that the emulation code is 100% correct...
[19:52] <karolherbst> there _might_ be a bug if the mapping has different accesses, but I never found anything that ran into issues here
[19:54] <karolherbst> you could run with `RUSTICL_DEBUG=memory` and see if the prints make any sense. It should tell when the memory content is migrated and moved around
[20:00] <rusticluser> karolherbst: Ah that's awesome info, thanks
[20:01] <rusticluser> It would be really useful to have an explicit confirmation of whether I'm actually getting zero-copy
```
+12 -1
View File
@@ -1,8 +1,9 @@
#ifndef ASYNCHRONOUS_BRIDGE_H #ifndef ASYNCHRONOUS_BRIDGE_H
#define ASYNCHRONOUS_BRIDGE_H #define ASYNCHRONOUS_BRIDGE_H
#include <boostAsioLinkageFix.h>
#include <atomic> #include <atomic>
#include <boost/asio.hpp> #include <boost/asio/io_service.hpp>
namespace smo { namespace smo {
@@ -31,6 +32,16 @@ public:
io_service.run_one(); io_service.run_one();
if (isAsyncOperationComplete.load() || io_service.stopped()) if (isAsyncOperationComplete.load() || io_service.stopped())
{ break; } { break; }
/** EXPLANATION:
* In the mrntt and mind thread loops we call checkException() after
* run() returns, but we don't have to do that here because
* setException() calls stop.
*
* So if an exception is set on our thread, we'll break out of this
* loop due to the check for stopped() above, and that'll take us
* back out to the main loop, where we'll catch the exception.
*/
} }
} }
+12 -6
View File
@@ -5,8 +5,8 @@
#include <memory> #include <memory>
#include <exception> #include <exception>
#include <componentThread.h> #include <componentThread.h>
#include <lockSet.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <asynchronousContinuationChainLink.h> #include <asynchronousContinuationChainLink.h>
@@ -50,12 +50,18 @@ public:
* This macro should be used by the caller to bubble the exception to the * This macro should be used by the caller to bubble the exception to the
* caller. * caller.
*/ */
#define CONT_SET_EXC(continuation, type, exc_obj) \ #define CALLEE_SETEXC(continuation, type, exc_obj) \
(continuation)->exception = std::make_exception_ptr<type>(exc_obj) (continuation)->exception = std::make_exception_ptr<type>(exc_obj)
#define CONT_SET_EXC_AND_RET(continuation, type, exc_obj) \ #define CALLEE_SETEXC_CALLCB(continuation, type, exc_obj) \
do { \ do { \
(continuation)->exception = std::make_exception_ptr<type>(exc_obj); \ CALLEE_SETEXC(continuation, type, exc_obj); \
(continuation)->callOriginalCb(); \
} while(0)
#define CALLEE_SETEXC_CALLCB_RET(continuation, type, exc_obj) \
do { \
CALLEE_SETEXC_CALLCB(continuation, type, exc_obj); \
return; \ return; \
} while(0) } while(0)
@@ -136,10 +142,10 @@ public:
.callbackFn) .callbackFn)
{ {
caller->getIoService().post( caller->getIoService().post(
std::bind( STC(std::bind(
AsynchronousContinuation<OriginalCbFnT>::originalCallback AsynchronousContinuation<OriginalCbFnT>::originalCallback
.callbackFn, .callbackFn,
std::forward<Args>(args)...)); std::forward<Args>(args)...)));
} }
} }
+5
View File
@@ -54,6 +54,11 @@ public:
return nTotal == 0; return nTotal == 0;
} }
void setRemainingIterationsToFailure()
{
nFailed.store(nTotal - nSucceeded.load());
}
public: public:
unsigned int nTotal; unsigned int nTotal;
std::atomic<unsigned int> nSucceeded, nFailed; std::atomic<unsigned int> nSucceeded, nFailed;
+138
View File
@@ -0,0 +1,138 @@
#ifndef CALLABLE_TRACER_H
#define CALLABLE_TRACER_H
#include <config.h>
#include <string>
#include <functional>
#include <iostream>
#include <cstdint>
#include <opts.h>
namespace smo {
/**
* @brief CallableTracer - Wraps callables with metadata for debugging
*
* This class wraps any callable object with metadata (caller function name,
* line number, and return addresses) to help debug cases where callables
* posted to boost::asio::io_service have gone out of scope. The metadata
* can be accessed from the callable's address when debugging.
*/
class CallableTracer
{
public:
/**
* @brief Constructor that wraps a callable with metadata
* @param callerFuncName The name of the function that created this callable
* @param callerLine The line number where this callable was created
* @param returnAddr0 The return address of the direct caller
* @param returnAddr1 The return address of the caller before that
* @param callable The callable object to wrap
*/
template<typename CallableT>
explicit CallableTracer(
const char* callerFuncName,
int callerLine,
void* returnAddr0,
void* returnAddr1,
CallableT&& callable)
: callerFuncName(callerFuncName),
callerLine(callerLine),
returnAddr0(returnAddr0),
returnAddr1(returnAddr1),
callable(std::forward<CallableT>(callable))
{}
void operator()()
{
if (OptionParser::getOptions().traceCallables)
{
std::cout << "" << __func__ << ": On thread "
<< (ComponentThread::tlsInitialized()
? ComponentThread::getSelf()->name : "<TLS un-init'ed>")
<< ": Calling callable posted by:\n"
<< "\t" << callerFuncName << "\n\tat line " << (int)callerLine
<< " return addr 0: " << returnAddr0
<< ", return addr 1: " << returnAddr1
<< std::endl;
}
callable();
}
public:
/// Name of the function that created this callable
std::string callerFuncName;
/// Line number where this callable was created
int callerLine;
/// Return address of the direct caller
void* returnAddr0;
/// Return address of the caller before that
void* returnAddr1;
private:
/// The wrapped callable (type-erased using std::function)
std::function<void()> callable;
};
} // namespace smo
/**
* @brief STC - SMO Traceable Callable macro
*
* When CONFIG_DEBUG_TRACE_CALLABLES is defined, wraps the callable with
* CallableTracer to store metadata (caller function name, line number,
* and return addresses). When not defined, returns the callable directly
* with no overhead.
*
* Uses compiler-specific macros to get fully qualified function names:
* - GCC/Clang: __PRETTY_FUNCTION__ (includes full signature with namespace/class)
* - MSVC: __FUNCSIG__ (includes full signature)
* - Fallback: __func__ (unqualified function name only)
*
* Uses compiler-specific builtins to get return addresses:
* - GCC/Clang: __builtin_return_address(0) and __builtin_return_address(1)
* - MSVC: _ReturnAddress() (only one level available)
* - Fallback: nullptr for return addresses
*
* Usage:
* thread->getIoService().post(
* STC(std::bind(&SomeClass::method, this, arg1, arg2)));
*/
#ifdef CONFIG_DEBUG_TRACE_CALLABLES
#if defined(__GNUC__) || defined(__clang__)
// GCC/Clang: __PRETTY_FUNCTION__ gives full signature
// e.g., "void smo::SomeClass::method(int, int)"
// __builtin_return_address(0) = direct caller
// __builtin_return_address(1) = caller before that
#define STC(arg) smo::CallableTracer( \
__PRETTY_FUNCTION__, \
__LINE__, \
__builtin_return_address(0), \
__builtin_return_address(1), \
arg)
#elif defined(_MSC_VER)
// MSVC: __FUNCSIG__ gives full signature
// e.g., "void __cdecl smo::SomeClass::method(int, int)"
// _ReturnAddress() = direct caller (only one level available)
#include <intrin.h>
#define STC(arg) smo::CallableTracer( \
__FUNCSIG__, \
__LINE__, \
_ReturnAddress(), \
nullptr, \
arg)
#else
// Fallback to standard __func__ (unqualified name only)
// No return address support
#define STC(arg) smo::CallableTracer( \
__func__, \
__LINE__, \
nullptr, \
nullptr, \
arg)
#endif
#else
#define STC(arg) arg
#endif
#endif // CALLABLE_TRACER_H
-1
View File
@@ -2,7 +2,6 @@
#define CALLBACK_H #define CALLBACK_H
#include <memory> #include <memory>
#include <functional>
namespace smo { namespace smo {
+16 -22
View File
@@ -11,6 +11,9 @@
/* Device manager reattacher configuration */ /* Device manager reattacher configuration */
#define CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS @MRNTT_DEVMGR_REATTACHER_PERIOD_MS@ #define CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS @MRNTT_DEVMGR_REATTACHER_PERIOD_MS@
/* Stimulus buffer frame period configuration */
#define CONFIG_STIMBUFF_FRAME_PERIOD_MS @CONFIG_STIMBUFF_FRAME_PERIOD_MS@
#define CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS @CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS@
/* World thread configuration */ /* World thread configuration */
#cmakedefine CONFIG_WORLD_USE_BODY_THREAD #cmakedefine CONFIG_WORLD_USE_BODY_THREAD
@@ -19,6 +22,9 @@
#cmakedefine CONFIG_ENABLE_DEBUG_LOCKS #cmakedefine CONFIG_ENABLE_DEBUG_LOCKS
#cmakedefine CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS @DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS@ #cmakedefine CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS @DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS@
/* Debug callable tracing configuration */
#cmakedefine CONFIG_DEBUG_TRACE_CALLABLES
/* Cross-compilation configuration */ /* Cross-compilation configuration */
#cmakedefine CMAKE_CROSSCOMPILING #cmakedefine CMAKE_CROSSCOMPILING
@@ -26,32 +32,20 @@
#cmakedefine CONFIG_LIB_XCBXORG_ENABLED #cmakedefine CONFIG_LIB_XCBXORG_ENABLED
#cmakedefine CONFIG_LIB_ALSA_ENABLED #cmakedefine CONFIG_LIB_ALSA_ENABLED
/* Sense APIs */ /* Stim Buff APIs */
#cmakedefine CONFIG_SENSEAPI_XCBWINDOW_ENABLED #cmakedefine CONFIG_STIMBUFFAPI_XCBWINDOW_ENABLED
#cmakedefine CONFIG_SENSEAPI_V4L_ENABLED #cmakedefine CONFIG_STIMBUFFAPI_LIVOXGEN1_ENABLED
#cmakedefine CONFIG_SENSEAPI_ALSAMIC_ENABLED #cmakedefine CONFIG_STIMBUFFAPI_V4L_ENABLED
#cmakedefine CONFIG_SENSEAPI_LIVOX_ENABLED #cmakedefine CONFIG_STIMBUFFAPI_ALSAMIC_ENABLED
#cmakedefine CONFIG_SENSEAPI_R3LIVE_ENABLED #cmakedefine CONFIG_STIMBUFFAPI_LIVOX_ENABLED
#cmakedefine CONFIG_SENSEAPI_FASTLIO2_ENABLED #cmakedefine CONFIG_STIMBUFFAPI_R3LIVE_ENABLED
#cmakedefine CONFIG_SENSEAPI_ADALIO2_ENABLED #cmakedefine CONFIG_STIMBUFFAPI_FASTLIO2_ENABLED
#cmakedefine CONFIG_SENSEAPI_DEEPLIO2_ENABLED #cmakedefine CONFIG_STIMBUFFAPI_ADALIO2_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_DEEPLIO2_ENABLED
/* Wilzor APIs */ /* Wilzor APIs */
#cmakedefine CONFIG_WILZORAPI_XCBMOUSE_ENABLED #cmakedefine CONFIG_WILZORAPI_XCBMOUSE_ENABLED
#cmakedefine CONFIG_WILZORAPI_XCBKEYBOARD_ENABLED #cmakedefine CONFIG_WILZORAPI_XCBKEYBOARD_ENABLED
#cmakedefine CONFIG_WILZORAPI_ALSAVOICE_ENABLED #cmakedefine CONFIG_WILZORAPI_ALSAVOICE_ENABLED
/* Legacy defines for backward compatibility */
#cmakedefine CONFIG_XCBWINDOW_ENABLED
#cmakedefine CONFIG_V4L_ENABLED
#cmakedefine CONFIG_ALSAMIC_ENABLED
#cmakedefine CONFIG_LIVOX_ENABLED
#cmakedefine CONFIG_R3LIVE_ENABLED
#cmakedefine CONFIG_FASTLIO2_ENABLED
#cmakedefine CONFIG_ADALIO2_ENABLED
#cmakedefine CONFIG_DEEPLIO2_ENABLED
#cmakedefine CONFIG_XCBMOUSE_ENABLED
#cmakedefine CONFIG_XCBKEYBOARD_ENABLED
#cmakedefine CONFIG_ALSAVOICE_ENABLED
#endif /* _CONFIG_H */ #endif /* _CONFIG_H */
-2
View File
@@ -2,8 +2,6 @@
#define LOCK_SET_H #define LOCK_SET_H
#include <vector> #include <vector>
#include <functional>
#include <atomic>
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
#include <memory> #include <memory>
-1
View File
@@ -1,7 +1,6 @@
#ifndef LOCKER_AND_INVOKER_BASE_H #ifndef LOCKER_AND_INVOKER_BASE_H
#define LOCKER_AND_INVOKER_BASE_H #define LOCKER_AND_INVOKER_BASE_H
#include <functional>
#include <list> #include <list>
#include <memory> #include <memory>
-2
View File
@@ -3,10 +3,8 @@
#include <config.h> #include <config.h>
#include <list> #include <list>
#include <atomic>
#include <memory> #include <memory>
#include <string> #include <string>
#include <stdexcept>
#include <spinLock.h> #include <spinLock.h>
#include <lockerAndInvokerBase.h> #include <lockerAndInvokerBase.h>
@@ -2,7 +2,6 @@
#define SERIALIZED_ASYNCHRONOUS_CONTINUATION_H #define SERIALIZED_ASYNCHRONOUS_CONTINUATION_H
#include <config.h> #include <config.h>
#include <functional>
#include <memory> #include <memory>
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
+40
View File
@@ -72,6 +72,46 @@ public:
locked.store(false); locked.store(false);
} }
/**
* @brief RAII guard for SpinLock
* Locks the spinlock on construction and unlocks on destruction
*/
class Guard
{
public:
explicit Guard(SpinLock& lock)
: lock_(lock), unlocked_(false)
{
lock_.acquire();
}
~Guard()
{
if (!unlocked_) {
lock_.release();
}
}
void unlockPrematurely()
{
if (!unlocked_)
{
lock_.release();
unlocked_ = true;
}
}
// Non-copyable, non-movable
Guard(const Guard&) = delete;
Guard& operator=(const Guard&) = delete;
Guard(Guard&&) = delete;
Guard& operator=(Guard&&) = delete;
private:
SpinLock& lock_;
bool unlocked_;
};
private: private:
std::atomic<bool> locked; std::atomic<bool> locked;
}; };
+63
View File
@@ -0,0 +1,63 @@
#ifndef _COMBINATORIAL_LOGIC_EXPRESSION_H
#define _COMBINATORIAL_LOGIC_EXPRESSION_H
#include <vector>
#include <memory>
#include <user/logic.h>
#include <mentalEntity.h>
#include <concept.h>
#include <user/stimFrame.h>
namespace smo {
namespace cologex {
class Comparator
: public MentalEntity, public logic::Operand
{
public:
/** EXPLANATION:
* The reference for a Comparator is the fixed mentity or range of mentities
* that this comparator is intended to validate a match against.
*
* There are several mentities against which a comparator can match. At the
* time of writing, we're fairly sure that these will be at minimum,
* qualia, chronomena and mentena.
*/
std::shared_ptr<MentalEntity> reference;
};
class ComparatorExpression
: public logic::UnaryExpression
{
public:
ComparatorExpression(
logic::Operator &op, std::shared_ptr<Comparator> &comparator
)
: logic::UnaryExpression(
op, std::static_pointer_cast<logic::Operand>(comparator))
{}
};
class CombinatorialLogicExpression
: public MentalEntity, public logic::Expression, public Concept
{
public:
};
class CombinatorialLogicExpressionSeq
: public MentalEntity, public Concept
{
public:
std::vector<
std::pair<stim_buff::SimultaneityStamp, CombinatorialLogicExpression>
> expressions;
};
typedef CombinatorialLogicExpression Cologex;
typedef CombinatorialLogicExpressionSeq CologexSeq;
} // namespace cologex
} // namespace smo
#endif
+134
View File
@@ -0,0 +1,134 @@
#ifndef _USER_COMPUTE_H
#define _USER_COMPUTE_H
#include <memory>
#include <vector>
#include <string_view>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
namespace smo {
namespace compute {
// Helper function to validate OpenCL version
bool validateOpenClVersion(
std::string_view versionStr, std::string_view versionType,
int minMajor, int minMinor);
/**
* @brief OpenCL compute device information
*
* Manages a single OpenCL device, creating and owning its context and command
* queue.
*/
class ComputeDevice
{
public:
/**
* @brief Construct a ComputeDevice from platform and device IDs
*
* Creates the OpenCL context and command queue for the device.
* Throws std::runtime_error if context or queue creation fails.
*
* @param platformId OpenCL platform ID
* @param deviceId OpenCL device ID
*/
ComputeDevice(cl_platform_id platformId, cl_device_id deviceId);
~ComputeDevice()
{
if (commandQueue)
{
clReleaseCommandQueue(commandQueue);
commandQueue = nullptr;
}
if (context)
{
clReleaseContext(context);
context = nullptr;
}
}
// Non-copyable
ComputeDevice(const ComputeDevice&) = delete;
ComputeDevice& operator=(const ComputeDevice&) = delete;
cl_platform_id platform;
cl_device_id device;
cl_context context;
cl_command_queue commandQueue;
};
/**
* @brief Association between an OpenCL buffer and a compute device
*/
struct ClBufferDeviceAssociation
{
ClBufferDeviceAssociation(
cl_mem buf, const std::shared_ptr<ComputeDevice>& dev)
: buffer(buf), device(dev)
{}
cl_mem buffer;
std::shared_ptr<ComputeDevice> device;
};
/**
* @brief OpenCL buffer created on all compute devices
*
* Manages a USE_HOST_PTR buffer created on all available compute devices.
* The constructor creates buffers for all devices, and the destructor releases
* them.
*/
class ClBuffer
{
public:
/**
* @brief Construct a ClBuffer and create buffers on all devices
*
* Creates a USE_HOST_PTR buffer on each device's context.
* Throws std::runtime_error if buffer creation fails for any device.
*
* @param hostPtr Host pointer to use
* @param size Size of buffer in bytes
* @param flags Additional OpenCL memory flags
* @param devices Vector of compute devices to create buffers on
*/
ClBuffer(
void* hostPtr, size_t size, cl_mem_flags flags,
const std::vector<std::shared_ptr<ComputeDevice>>& devices);
~ClBuffer()
{
for (auto& assoc : associations)
{
if (assoc.buffer)
{
clReleaseMemObject(assoc.buffer);
assoc.buffer = nullptr;
}
}
}
// Non-copyable
ClBuffer(const ClBuffer&) = delete;
ClBuffer& operator=(const ClBuffer&) = delete;
/**
* @brief Get the cl_mem handle for a specific compute device
* @param device The compute device to find the buffer for
* @return The cl_mem handle for the device, or nullptr if not found
*/
cl_mem getAssociatedBufferHandleForDevice(
const std::shared_ptr<ComputeDevice>& device) const;
void* hostPtr;
size_t size;
cl_mem_flags flags;
std::vector<ClBufferDeviceAssociation> associations;
};
} // namespace compute
} // namespace smo
#endif // _USER_COMPUTE_H
+56 -13
View File
@@ -25,6 +25,8 @@ public:
{ {
return deviceIdentifier == other.deviceIdentifier && return deviceIdentifier == other.deviceIdentifier &&
sensorType == other.sensorType && sensorType == other.sensorType &&
qualeIfaceApi == other.qualeIfaceApi &&
stimBuffApi == other.stimBuffApi &&
provider == other.provider && provider == other.provider &&
deviceSelector == other.deviceSelector; deviceSelector == other.deviceSelector;
} }
@@ -32,9 +34,10 @@ public:
public: public:
std::string deviceIdentifier; std::string deviceIdentifier;
char sensorType; char sensorType;
std::string implexor; std::string qualeIfaceApi;
std::string api; std::vector<std::pair<std::string,std::string>> qualeIfaceApiParams;
std::vector<std::pair<std::string,std::string>> apiParams; std::string stimBuffApi;
std::vector<std::pair<std::string,std::string>> stimBuffApiParams;
std::string provider; std::string provider;
std::vector<std::pair<std::string,std::string>> providerParams; std::vector<std::pair<std::string,std::string>> providerParams;
std::string deviceSelector; std::string deviceSelector;
@@ -44,9 +47,18 @@ public:
std::ostringstream os; std::ostringstream os;
os << "Device Identifier: " << deviceIdentifier os << "Device Identifier: " << deviceIdentifier
<< ", Sensor Type: " << sensorType << ", Sensor Type: " << sensorType
<< ", Implexor: " << implexor << ", API: " << api << ", QualeIface API: " << qualeIfaceApi << ", QualeIface API Params: (";
<< ", API Params: ("; for (const auto& param : qualeIfaceApiParams)
for (const auto& param : apiParams) {
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
os << "), StimBuff API: " << stimBuffApi
<< ", StimBuff API Params: (";
for (const auto& param : stimBuffApiParams)
{ {
os << param.first; os << param.first;
if (!param.second.empty()) { if (!param.second.empty()) {
@@ -69,28 +81,29 @@ public:
} }
/** /**
* @brief Parse a required integer parameter from provider parameters * @brief Parse a required integer parameter from a parameter list
* @param spec The device attachment specification * @param params The parameter vector to search in
* @param paramName The name of the parameter to parse * @param paramName The name of the parameter to parse
* @return The parsed integer value * @return The parsed integer value
* @throws std::runtime_error if parameter is not found or cannot be parsed * @throws std::runtime_error if parameter is not found or cannot be parsed
*/ */
static int parseRequiredParamAsInt( static int parseRequiredParamAsInt(
const DeviceAttachmentSpec& spec, const std::string& paramName const std::vector<std::pair<std::string,std::string>>& params,
const std::string& paramName
) )
{ {
auto it = std::find_if( auto it = std::find_if(
spec.providerParams.begin(), params.begin(),
spec.providerParams.end(), params.end(),
[&paramName](const auto& param) { [&paramName](const auto& param) {
return param.first == paramName; return param.first == paramName;
} }
); );
if (it == spec.providerParams.end()) if (it == params.end())
{ {
throw std::runtime_error( throw std::runtime_error(
"No " + paramName + " specified in provider params"); "No " + paramName + " specified in params");
} }
try { try {
@@ -101,6 +114,36 @@ public:
+ it->second + "' as integer: " + e.what()); + it->second + "' as integer: " + e.what());
} }
} }
/**
* @brief Parse an optional integer parameter from a parameter list using synonyms
* @param params The parameter vector to search in
* @param synonymNames The collection of synonymous parameter names to try
* @param defaultValue The default value to return if no parameter is found
* @return The parsed integer value, or defaultValue if none found
* @note Synonyms are tried in reverse order; lattermost synonym wins if multiple are present
*/
static int parseOptionalParamAsIntWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const std::vector<std::string>& synonymNames,
int defaultValue
)
{
// Loop through synonyms in reverse order; lattermost synonym wins.
for (auto synIt = synonymNames.rbegin();
synIt != synonymNames.rend(); ++synIt)
{
const auto& paramName = *synIt;
try {
return parseRequiredParamAsInt(params, paramName);
} catch (const std::exception&) {
// Parameter not found or parse error, continue to next synonym
continue;
}
}
return defaultValue;
}
}; };
class InteroceptorDevAttachmentSpec : public DeviceAttachmentSpec class InteroceptorDevAttachmentSpec : public DeviceAttachmentSpec
+64
View File
@@ -0,0 +1,64 @@
#ifndef _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
#define _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
#include <cstddef>
#include <cstdint>
#include <vector>
#include <string>
#include <sstream>
namespace smo {
namespace stim_buff {
class FrameAssemblyDesc
{
public:
struct SlotDesc
{
size_t offsetBytes; // offset from frame base
uint8_t* vaddr; // direct pointer into StagingBuffer memory
size_t nBytes; // slot capacity in bytes
};
public:
FrameAssemblyDesc() = default;
FrameAssemblyDesc(
size_t n, size_t slotSize,
size_t frameStride,
std::vector<SlotDesc> slotList)
: numSlots(n), slotSizeBytes(slotSize),
frameStrideBytes(frameStride),
slots(std::move(slotList)) {}
inline std::string stringify() const {
std::ostringstream oss;
oss << "FrameAssemblyDesc{"
<< "numSlots=" << numSlots
<< ", slotSizeBytes=" << slotSizeBytes
<< ", frameStrideBytes=" << frameStrideBytes
<< ", slots=[";
const size_t preview = slots.size() < 4 ? slots.size() : 4;
for (size_t i = 0; i < preview; ++i) {
oss << "{off=" << slots[i].offsetBytes
<< ", nBytes=" << slots[i].nBytes
<< ", vaddr=" << (const void*)slots[i].vaddr << "}";
if (i + 1 < preview) oss << ",";
}
if (slots.size() > preview) oss << ", ...";
oss << "]}";
return oss.str();
}
public:
size_t numSlots;
size_t slotSizeBytes;
size_t frameStrideBytes;
std::vector<SlotDesc> slots;
};
} // namespace stim_buff
} // namespace smo
#endif // _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
+82 -28
View File
@@ -4,14 +4,27 @@
#include <stdbool.h> #include <stdbool.h>
#include <optional> #include <optional>
#include <string> #include <string>
#include <functional>
#include <memory> #include <memory>
#include <vector>
#include <preprocessor.h> #include <preprocessor.h>
#include <componentThread.h>
#include <user/deviceAttachmentSpec.h> #include <user/deviceAttachmentSpec.h>
#include <callback.h> #include <callback.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
class OptionParser;
namespace smo { namespace smo {
namespace sense_api {
class ComponentThread;
namespace compute {
class ClBuffer;
class ComputeDevice;
} // namespace compute
namespace stim_buff {
/** /**
* @brief Threading model descriptor for senseApi libraries. * @brief Threading model descriptor for senseApi libraries.
@@ -78,11 +91,52 @@ struct SmoCallbacks
* equivalent to calling ComponentThread::getSelf(). * equivalent to calling ComponentThread::getSelf().
*/ */
std::shared_ptr<ComponentThread> (*ComponentThread_getSelf)(void); std::shared_ptr<ComponentThread> (*ComponentThread_getSelf)(void);
/**
* @brief Get the OptionParser singleton instance
* @return Reference to the OptionParser singleton
*
* This function provides access to the OptionParser singleton instance,
* equivalent to calling OptionParser::getOptions().
*/
OptionParser& (*OptionParser_getOptions)(void);
/**
* @brief Create a USE_HOST_PTR buffer on all OpenCL contexts
* @param hostPtr Host pointer to the memory
* @param size Size of the buffer in bytes
* @param flags Additional OpenCL memory flags
* @return Shared pointer to ClBuffer managing buffers on all devices
*/
std::shared_ptr<smo::compute::ClBuffer>
(*ComputeManager_createUseHostPtrBuffer)(
void* hostPtr, size_t size, cl_mem_flags flags);
/**
* @brief Release USE_HOST_PTR buffers from all contexts
* @param buffer Shared pointer to ClBuffer to release
*/
void (*ComputeManager_releaseUseHostPtrBuffer)(
std::shared_ptr<smo::compute::ClBuffer> buffer);
/**
* @brief Get a compute device
* @return Shared pointer to ComputeDevice, or nullptr if no devices available
*/
std::shared_ptr<smo::compute::ComputeDevice>
(*ComputeManager_getDevice)(void);
/**
* @brief Release a compute device
* @param device Shared pointer to ComputeDevice to release
*/
void (*ComputeManager_releaseDevice)(
std::shared_ptr<smo::compute::ComputeDevice> device);
}; };
struct Sal_Mgmt_LibOps struct Sal_Mgmt_LibOps
{ {
/* When Salmanoff loads a sense API lib, it calls this function to initialize /* When Salmanoff loads a stim buff API lib, it calls this function to initialize
* the lib. When this returns, the lib should be ready to attach devices. * the lib. When this returns, the lib should be ready to attach devices.
*/ */
sal_mlo_initializeIndFn *initializeInd; sal_mlo_initializeIndFn *initializeInd;
@@ -91,7 +145,7 @@ struct Sal_Mgmt_LibOps
*/ */
sal_mlo_finalizeIndFn *finalizeInd; sal_mlo_finalizeIndFn *finalizeInd;
/* Salmanoff calls this to attach a device to the lib. When it returns, the /* Salmanoff calls this to attach a device to the lib. When it returns, the
* device should be attached and ready to be implexed. * device should be attached and ready to present its stimbuff.
*/ */
sal_mlo_attachDeviceReqFn *attachDeviceReq; sal_mlo_attachDeviceReqFn *attachDeviceReq;
// When this returns, the device should be detached. // When this returns, the device should be detached.
@@ -109,16 +163,16 @@ struct Sal_Mgmt_LibOps
} }
}; };
/* Exported by all sense API Libraries to tell Salmanoff what API the lib uses /* Exported by all stim buff API Libraries to tell Salmanoff what API the lib uses
* to connect to providers; and also to state which implexor APIs it exports. * to connect to providers; and also to state which quale-iface APIs it exports.
*/ */
class SenseApiDesc class StimBuffApiDesc
{ {
public: public:
class ExportedImplexorApiDesc class ExportedQualeIfaceApiDesc
{ {
public: public:
static bool sanityCheck(const ExportedImplexorApiDesc &desc) static bool sanityCheck(const ExportedQualeIfaceApiDesc &desc)
{ {
if (desc.name.empty()) { return false; } if (desc.name.empty()) { return false; }
return true; return true;
@@ -132,45 +186,45 @@ public:
std::string stringify() const std::string stringify() const
{ {
std::string result = "Name: " + name + "\n"; std::string result = "Name: " + name + "\n";
result += "Exported Implexor APIs:\n"; result += "Exported QualeIface APIs:\n";
for (const auto& api : exportedImplexorApis) { for (const auto& api : exportedQualeIfaceApis) {
result += " - " + api.name + "\n"; result += " - " + api.name + "\n";
} }
return result; return result;
} }
static bool sanityCheck(const SenseApiDesc &desc) static bool sanityCheck(const StimBuffApiDesc &desc)
{ {
if (desc.name.empty() || desc.exportedImplexorApis.empty()) { if (desc.name.empty() || desc.exportedQualeIfaceApis.empty()) {
return false; return false;
} }
for (const auto& api : desc.exportedImplexorApis) { for (const auto& api : desc.exportedQualeIfaceApis) {
if (!ExportedImplexorApiDesc::sanityCheck(api)) { return false; } if (!ExportedQualeIfaceApiDesc::sanityCheck(api)) { return false; }
} }
return Sal_Mgmt_LibOps::sanityCheck(desc.sal_mgmt_libOps); return Sal_Mgmt_LibOps::sanityCheck(desc.sal_mgmt_libOps);
} }
std::string name; std::string name;
// These are the implexors whose APIs this lib exports. // These are the quale-iface APIs this lib exports.
std::vector<ExportedImplexorApiDesc> exportedImplexorApis; std::vector<ExportedQualeIfaceApiDesc> exportedQualeIfaceApis;
Sal_Mgmt_LibOps sal_mgmt_libOps; Sal_Mgmt_LibOps sal_mgmt_libOps;
}; };
#define SMO_GET_SENSE_API_DESC_FN_NAME getSenseApiDesc #define SMO_GET_STIM_BUFF_API_DESC_FN_NAME getStimBuffApiDesc
#define SMO_GET_SENSE_API_DESC_FN_NAME_STR \ #define SMO_GET_STIM_BUFF_API_DESC_FN_NAME_STR \
SMO_QUOTE(SMO_GET_SENSE_API_DESC_FN_NAME) SMO_QUOTE(SMO_GET_STIM_BUFF_API_DESC_FN_NAME)
#define SMO_GET_SENSE_API_DESC_FN_TYPEDEF \ #define SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF \
SMO_CONCAT(SMO_GET_SENSE_API_DESC_FN_NAME, Fn) SMO_CONCAT(SMO_GET_STIM_BUFF_API_DESC_FN_NAME, Fn)
/* Every Sense API library must define a global instance of this /* Every Stim Buff API library must define a global instance of this
* function. Salmanoff will search for it and invoke it via dlsym(). * function. Salmanoff will search for it and invoke it via dlsym().
* *
* The function must return a SenseApiDesc struct that Smo will tell * The function must return a StimBuffApiDesc struct that Smo will tell
* Smo what implexors can be used with it & what APIs it exports. * Smo what quale-iface APIs can be used with it & what APIs it exports.
* The SenseApiDesc struct also gives Smo pointers to API functions * The StimBuffApiDesc 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 SmoCallbacks parameter provides the library with access to * The SmoCallbacks parameter provides the library with access to
@@ -178,11 +232,11 @@ public:
* The SmoThreadingModelDesc parameter provides the library with access to * The SmoThreadingModelDesc parameter provides the library with access to
* the io_service for network operations and event handling. * the io_service for network operations and event handling.
*/ */
typedef const SenseApiDesc &(SMO_GET_SENSE_API_DESC_FN_TYPEDEF)( typedef const StimBuffApiDesc &(SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF)(
const SmoCallbacks& callbacks, const SmoCallbacks& callbacks,
const SmoThreadingModelDesc& threadingModel); const SmoThreadingModelDesc& threadingModel);
} // namespace sense_api } // namespace stim_buff
} // namespace smo } // namespace smo
#endif // __USER_SENSE_API_LIB_H__ #endif // __USER_SENSE_API_LIB_H__
+76
View File
@@ -0,0 +1,76 @@
#ifndef _SEQUENCE_LOCK_H
#define _SEQUENCE_LOCK_H
#include <atomic>
#include <optional>
namespace smo {
/**
* @brief Sequence lock synchronization primitive
*
* A reader-writer synchronization primitive where writers increment the
* sequence number (odd = writing in progress, even = stable) and readers
* check the sequence number to detect concurrent modifications.
*/
class SequenceLock
{
public:
SequenceLock()
: sequenceNo(0)
{}
~SequenceLock() = default;
// Non-copyable, non-movable (std::atomic is neither copyable nor movable)
SequenceLock(const SequenceLock&) = delete;
SequenceLock& operator=(const SequenceLock&) = delete;
SequenceLock(SequenceLock&&) = delete;
SequenceLock& operator=(SequenceLock&&) = delete;
/* Atomically increments sequenceNo and issues a release barrier.
* Makes the sequence number odd, indicating a write is in progress.
*/
void writeAcquire()
{ sequenceNo.fetch_add(1, std::memory_order_release); }
/* Atomically increments sequenceNo and issues a release barrier.
* Makes the sequence number even again, indicating write is complete.
*/
void writeRelease()
{ sequenceNo.fetch_add(1, std::memory_order_release); }
/* Issues an acquire barrier and checks if the sequence number is even
* (stable state). If odd (writer active), returns nullopt. Otherwise
* returns the sequence number.
*
* @return std::nullopt if writer is active, otherwise the sequence number
*/
std::optional<size_t> readAcquire()
{
size_t seq = sequenceNo.load(std::memory_order_acquire);
if (seq & 1) {
return std::nullopt;
}
return seq;
}
/* Issues an acquire barrier and checks if the sequence number matches
* the original value from readAcquire(). If equal, the read was consistent.
*
* @param originalSequenceNo The sequence number obtained from readAcquire()
* @return true if read was consistent, false if writer modified during read
*/
bool readRelease(size_t originalSequenceNo)
{
size_t seq = sequenceNo.load(std::memory_order_acquire);
return seq == originalSequenceNo;
}
private:
std::atomic<size_t> sequenceNo;
};
} // namespace smo
#endif // _SEQUENCE_LOCK_H
+157
View File
@@ -0,0 +1,157 @@
#ifndef _SP_MC_RING_BUFFER_H
#define _SP_MC_RING_BUFFER_H
#include <vector>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <new>
#include <memory>
#include <user/stimulusFrame.h>
#include <user/frameAssemblyDesc.h>
#include <user/sequenceLock.h>
#include <user/senseApiDesc.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
namespace smo {
namespace stim_buff {
/**
* @brief Single-producer, multi-consumer ring buffer w/per-slot sequence locks
*
* A ring buffer that maintains data alignment constraints while providing
* lock-free read access through per-slot sequence locks. The locks are kept
* separate from the data to preserve alignment requirements for the input
* engine.
*/
class SpMcRingBuffer
{
public:
/** EXPLANATION:
* Constructor initializes the ring buffer with FrameAssemblyDesc.
* Allocates frames vector with properly constructed StimulusFrame instances,
* each initialized with a SlotDesc from the FrameAssemblyDesc.
*/
explicit SpMcRingBuffer(
const std::shared_ptr<FrameAssemblyDesc> &frameAssemblyDesc_,
const SmoCallbacks& callbacks,
cl_mem_flags flags)
:
nBuffers(frameAssemblyDesc_ ? frameAssemblyDesc_->slots.size() : 0),
frameAssemblyDesc(frameAssemblyDesc_),
slots(nBuffers), // Default-construct all frames
producerNextUsableIndex(0)
{
if (!frameAssemblyDesc)
{
throw std::invalid_argument(std::string(__func__)
+ ": SpMcRingBuffer: frameAssemblyDesc must not be null");
}
if (nBuffers == 0)
{
throw std::invalid_argument(std::string(__func__)
+ ": SpMcRingBuffer: frameAssemblyDesc must have at least one "
"slot");
}
// Re-invoke constructors w/placement new on default-constructed frames
for (size_t i = 0; i < nBuffers; ++i)
{
slots[i].~StimulusFrame(); // Destroy default-constructed object
new (&slots[i]) StimulusFrame(
frameAssemblyDesc->slots[i], callbacks, flags, i);
}
}
~SpMcRingBuffer() = default;
// Non-copyable, non-movable (slots are non-movable)
SpMcRingBuffer(const SpMcRingBuffer&) = delete;
SpMcRingBuffer& operator=(const SpMcRingBuffer&) = delete;
SpMcRingBuffer(SpMcRingBuffer&&) = delete;
SpMcRingBuffer& operator=(SpMcRingBuffer&&) = delete;
public:
/**
* @brief Get a reference to the StimulusFrame at the specified slot
*
* @param slotIndex The index of the slot (0-based)
* @return Reference to StimulusFrame at the slot
* @throws std::out_of_range if slotIndex >= nBuffers
*/
StimulusFrame& getDataAtSlot(size_t slotIndex)
{
if (slotIndex >= nBuffers)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: slotIndex must be < nBuffers");
}
return slots[slotIndex];
}
SequenceLock& getSequenceLockAtSlot(size_t slotIndex)
{
if (slotIndex >= nBuffers)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: slotIndex must be < nBuffers");
}
return slots[slotIndex].lock;
}
/**
* @brief Get the next index to produce into, atomically incrementing it
*
* Uses sequence lock to perform an emulated fetch_add with modulo nBuffers
* applied, ensuring the returned index is always < nBuffers.
*
* @return The index to produce into (always < nBuffers)
*/
size_t getIndexToProduceInto()
{
producerNextUsableIndexLock.writeAcquire();
size_t currentIndex = producerNextUsableIndex;
size_t nextIndex = (currentIndex + 1) % nBuffers;
producerNextUsableIndex = nextIndex;
producerNextUsableIndexLock.writeRelease();
return currentIndex;
}
/**
* @brief Abort production by setting the producer index to a specific value
*
* @param index The index to set (must be < nBuffers)
* @throws std::out_of_range if index >= nBuffers
*/
void abortProduction(size_t index)
{
if (index >= nBuffers)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: index must be < nBuffers");
}
producerNextUsableIndexLock.writeAcquire();
producerNextUsableIndex = index;
producerNextUsableIndexLock.writeRelease();
}
public:
// Layout/invariants
size_t nBuffers;
private:
// FrameAssemblyDesc describing the memory layout
std::shared_ptr<FrameAssemblyDesc> frameAssemblyDesc;
// Frames vector: each frame contains a sequence lock and SlotDesc
std::vector<StimulusFrame> slots;
SequenceLock producerNextUsableIndexLock;
size_t producerNextUsableIndex;
};
} // namespace stim_buff
} // namespace smo
#endif // _SP_MC_RING_BUFFER_H
+199
View File
@@ -0,0 +1,199 @@
#ifndef STAGINGBUFFER_H
#define STAGINGBUFFER_H
#include <memory>
#include <cstdint>
#include <atomic>
#include <string>
#include <sstream>
#include <sys/mman.h>
#include <sys/uio.h>
namespace smo {
namespace stim_buff {
// Forward declaration
class FrameAssemblyDesc;
/**
* StagingBuffer manages a large buffer to guide io_uring in assembling some
* number of Livox Avia pcloud UDP dgrams into a single stim frame.
*
* The buffer operates in a cycle:
* 1. io_uring assembles UDP dgrams into the buffer until it's full
* 2. Buffer is handed off to the stimbuff layer to be appended to the stimbuff.
* 3. When the stimbuff layer has appended the current assembled frame, the
* assembly buffer is reset and cycle repeats.
*/
class StagingBuffer
{
public:
class IOEngineConstraints
{
public:
// Default constructor creates uninitialized constraints
IOEngineConstraints() = default;
IOEngineConstraints(
size_t slotStartAlignmentByteVal_,
size_t slotPadToNBytes_,
size_t frameStartAlignmentByteVal_,
size_t framePadToNBytes_)
: slotStartAlignmentByteVal(slotStartAlignmentByteVal_),
slotPadToNBytes(slotPadToNBytes_),
frameStartAlignmentByteVal(frameStartAlignmentByteVal_),
framePadToNBytes(framePadToNBytes_)
{}
~IOEngineConstraints() = default;
size_t slotStartAlignmentByteVal, slotPadToNBytes,
frameStartAlignmentByteVal, framePadToNBytes;
// Static defaults for io_uring and OpenCL
static const IOEngineConstraints ioUringConstraints;
static const IOEngineConstraints openClInputConstraints;
inline std::string stringify() const
{
std::ostringstream oss;
oss << "IOEngineConstraints{"
<< "slotStartAlignmentByteVal=" << slotStartAlignmentByteVal
<< ", slotPadToNBytes=" << slotPadToNBytes
<< ", frameStartAlignmentByteVal=" << frameStartAlignmentByteVal
<< ", framePadToNBytes=" << framePadToNBytes
<< "}";
return oss.str();
}
};
public:
/** EXPLANATION:
* Default constructor creates uninitialized buffer.
* Must be properly initialized using placement new with the parameterized constructor.
*/
StagingBuffer() = default;
/** EXPLANATION:
* We use the input and output engine constraints to determine the total
* amount of memory required internally to assemble a single frame with
* the given number of points per frame.
*/
explicit StagingBuffer(
const IOEngineConstraints& inputEngineConstraints,
const IOEngineConstraints& outputEngineConstraints,
size_t nSlots);
~StagingBuffer() = default;
// Non-copyable, movable
StagingBuffer(const StagingBuffer&) = delete;
StagingBuffer& operator=(const StagingBuffer&) = delete;
StagingBuffer(StagingBuffer&&) = default;
StagingBuffer& operator=(StagingBuffer&&) = default;
public:
/** EXPLANATION:
* Returns an input-engine-agnostic descriptor describing per-frame packet
* slot layout. Different input engines should be able to convert this into
* engine-specific metadata. E.g: io_uring's SQE descriptor.
*/
operator std::shared_ptr<FrameAssemblyDesc>() const { return frameDesc; }
// operator OpenClSharedBufferDescriptor() const;
bool isAssembling() const { return assemblingFlag.load(); }
void startAssembly() { assemblingFlag.store(true); }
void stopAssembly() { assemblingFlag.store(false); }
/** EXPLANATION:
* Returns an iovec for io_uring registration.
* The buffer is mmap()-allocated and suitable for IORING_REGISTER_BUFFERS.
*/
struct iovec getIoUringRegisterIoVec() const
{
struct iovec iov;
iov.iov_base = buffer.get();
iov.iov_len = bufferNBytes;
return iov;
}
/** EXPLANATION:
* Returns an iovec for OpenCL engine buffer access.
* The buffer is mmap()-allocated and suitable for CL_MEM_USE_HOST_PTR.
* Returns pointer to first slot (offset by firstSlotOffsetNBytes) and
* size from first slot to end of buffer.
*/
struct iovec getClEngineIovec() const
{
struct iovec iov;
iov.iov_base = buffer.get() + firstSlotOffsetNBytes;
iov.iov_len = bufferNBytes - firstSlotOffsetNBytes;
return iov;
}
inline std::string stringify() const
{
std::ostringstream oss;
oss << "StagingBuffer{"
<< "nSlots=" << nSlots
<< ", bufferNBytes=" << bufferNBytes
<< ", slotStrideNBytes=" << slotStrideNBytes
<< ", constraints=" << inputConstraints.stringify()
<< "}";
return oss.str();
}
private:
void computeSlotStrideAndBufferSize();
static size_t calculateFirstSlotOffsetAndValidate(
uint8_t* buffer,
size_t bufferNBytes,
size_t nSlots,
size_t slotStrideNBytes,
const IOEngineConstraints& inputConstraints);
// Custom deleter for mmap-allocated buffer
struct MmapDeleter
{
size_t size;
// Default constructor for use with default-constructed StagingBuffer
MmapDeleter() : size(0) {}
MmapDeleter(size_t s) : size(s) {}
void operator()(uint8_t* ptr) const
{
if (ptr != nullptr && size > 0)
{
munlock(ptr, size);
munmap(ptr, size);
}
}
};
// Buffer data - mmap-allocated for io_uring registration
// Using unique_ptr<uint8_t, MmapDeleter> instead of array syntax
// since we have a custom deleter that knows the size
std::unique_ptr<uint8_t, MmapDeleter> buffer;
size_t bufferNBytes;
// Layout/invariants
size_t nSlots;
public:
size_t slotStrideNBytes;
size_t firstSlotOffsetNBytes; // offset from buffer start to first slot
private:
IOEngineConstraints inputConstraints;
// Descriptor (computed once; reused across frames)
mutable std::shared_ptr<FrameAssemblyDesc> frameDesc;
// Current state
std::atomic<size_t> currentNBytes;
std::atomic<bool> assemblingFlag;
};
} // namespace stim_buff
} // namespace smo
#endif // STAGINGBUFFER_H
+83
View File
@@ -0,0 +1,83 @@
#ifndef _STENCIL_H
#define _STENCIL_H
#include <vector>
#include <memory>
#include <user/stimFrame.h>
#include <mentalEntity.h>
namespace smo {
namespace cologex {
/**
* Stencil represents range descriptions for sub-regions of sensor data frames.
*
* When a sensor yields frames with multiple values per frame, the Stencil class
* allows the stimbufflib driver to describe the subset of the input data that
* is relevant to SMO. For example:
*
* * A HSB format camera might treat brightness values above 128 as
* negtrins, creating a Stencil that denotes all offsets in a
* frame that exceed 128.
*
* * A lidar yielding XYZI[ntensity] might consider I values exceeding 128 to be
* negtrins, creating a Stencil listing all values in the point
* cloud that exceed 128.
*
* The Stencil internally represents offsets with ranges or other efficient
* formats to describe offsets (e.g., by row). The internal format is opaque to
* the stimbufflib, which describes relevant ranges by calling Stencil methods.
*/
class Stencil
{
public:
/**
* Constructor that takes a shared_ptr to StimFrame and produces a completed
* Stencil. The Stencil scans the StimFrame and efficiently allocates
* internal structures to describe the stencil ranges.
*
* @param frame Shared pointer to the StimFrame to analyze
* @param threshold The threshold value for determining relevant data
*/
Stencil(
const std::shared_ptr<stim_buff::StimFrame> &frame,
const uint32_t threshold)
: frame(frame), threshold(threshold)
{}
virtual ~Stencil() = default;
/**
* Pure virtual method for derived classes to implement their specific
* threshold analysis logic. Returns true if there are values above threshold,
* false otherwise.
*/
virtual bool analyzeFrame() = 0;
/**
* Stencil is constructed from a StimFrame. If the input StimFrame had no
* values above threshold, then the Stencil will have no data.
*/
virtual bool hasData() const = 0;
operator bool() const { return hasData(); }
bool operator!() const { return !hasData(); }
// Return the number of relevant ranges/offsets in this Stencil.
virtual size_t getRelevantCount() const = 0;
// Return true if the offset is relevant, false otherwise
virtual bool isRelevant(size_t offset) const = 0;
/**
* Build internal stencil metadata from the shared_ptr member to describe
* the range of StimFrame values that are relevant.
*/
virtual bool buildStencilMetadata() = 0;
protected:
uint32_t threshold;
std::shared_ptr<stim_buff::StimFrame> frame;
};
} // namespace cologex
} // namespace smo
#endif // _STENCIL_H
+71
View File
@@ -0,0 +1,71 @@
#ifndef _STIMULUS_BUFFER_H
#define _STIMULUS_BUFFER_H
#include <config.h>
#include <vector>
#include <memory>
#include <user/spMcRingBuffer.h>
#include <user/stagingBuffer.h>
#include <user/frameAssemblyDesc.h>
#include <user/senseApiDesc.h>
#include "stimulusFrame.h"
#include "deviceAttachmentSpec.h"
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
namespace smo {
namespace stim_buff {
// Forward declaration
class StimulusProducer;
/**
* StimulusBuffer manages a collection of stimulus frames and ring buffer.
*
* This buffer holds the actual frame storage and ring buffer for stimulus
* data. It maintains a reference to its parent StimulusProducer.
*/
class StimulusBuffer
{
public:
explicit StimulusBuffer(
StimulusProducer& parent,
const std::shared_ptr<device::DeviceAttachmentSpec>
&deviceAttachmentSpec,
int histbuffMs,
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
const SmoCallbacks& callbacks,
cl_mem_flags flags)
: parent(parent),
deviceAttachmentSpec(deviceAttachmentSpec),
histbuffMs(histbuffMs),
stagingBuffer(
inputEngineConstraints,
outputEngineConstraints,
static_cast<size_t>(histbuffMs / CONFIG_STIMBUFF_FRAME_PERIOD_MS)),
ringBuffer(
static_cast<std::shared_ptr<FrameAssemblyDesc>>(stagingBuffer),
callbacks, flags)
{}
virtual ~StimulusBuffer() = default;
// Non-copyable, movable
StimulusBuffer(const StimulusBuffer&) = delete;
StimulusBuffer& operator=(const StimulusBuffer&) = delete;
StimulusBuffer(StimulusBuffer&&) = default;
StimulusBuffer& operator=(StimulusBuffer&&) = default;
public:
StimulusProducer& parent;
std::shared_ptr<device::DeviceAttachmentSpec> deviceAttachmentSpec;
int histbuffMs;
StagingBuffer stagingBuffer;
SpMcRingBuffer ringBuffer;
};
} // namespace stim_buff
} // namespace smo
#endif // _STIMULUS_BUFFER_H
+123
View File
@@ -0,0 +1,123 @@
#ifndef _ATTACHMENT_SUPPORT_STIMULUS_FRAME_H
#define _ATTACHMENT_SUPPORT_STIMULUS_FRAME_H
#include <cstdint>
#include <memory>
#include <user/frameAssemblyDesc.h>
#include <user/sequenceLock.h>
#include <user/compute.h>
#include <user/senseApiDesc.h>
#define CL_TARGET_OPENCL_VERSION 120
#include <CL/cl.h>
namespace smo {
namespace stim_buff {
/** EXPLANATION:
* A simultaneity stamp is a timestamp that is used to determine whether two
* stimulus frames occured simultaneously. Its purpose is adamantly *NOT* to
* record or denote the "absolute" time of the stimulus frames. I cannot stress
* this enough. The simultaneity stamp is NOT used to record "when" the stimulus
* frame occured. It is used *SOLELY* to record that two or more stimulus frames
* occured at the same time.
*
* The SMO has absolutely no notion of "absolute" time. It only has a notion of
* simultaneity among stimulus frames. Any notions of "absolute" time are built
* up consciously and volitionally by the running mind, and not baked into the
* underlying software (i.e: Salmanoff).
*
* We need about 36 bits of unique simultaneity per year, assuming that we only
* expect to capture 1000 stim frames per second. 1000 is a lot of stim frames
* per second. If we use a 64 bit integer, that leaves us with 2^28 years
* before our simultaneity stamps roll over. That's 256 million years.
*
* The calculation we used to arrive at 36 bits is as follows:
* hex(86400 * 400 * 1000) = 0x80befc000
* * 86400 = seconds per day.
* * 400 = days per year.
* * 1000 = stim frames per second.
* As you can see, our extremely cautious calculation resulted in 36 bits.
* If we use a UUID (128 bits), we can basically be fairly sure we won't
* rollover for ...aeons. Now the question is: should we use a UUID or a 64 bit
* integer?
*
* It's important to note that simultaneity stamps are not used in all mental
* entities. They're only used in raw chronomena recordings, and possibly
* also in artificed memory chronomena. Among the artificed chronomena, their
* simultaneity lifetime is usually self-contained. Only the raw, observed
* chronomena have to retain a lifetime that is basically "the person's
* lifespan" (though not even necessarily that long).
*
* It may not even necessarily need to be lifespan-unique because the purpose of
* simultaneity stamps is to denote simultaneity among the stim frames that are
* __actually stored__ in the mind's memories. So if we forgot all stim frames
* with simultaneity stamps that older than say, 1000, then we can re-use all
* the simultaneity stamps that are numerically less than 1000. So there's some
* dynamic recycling, and we can prolly keep track of the oldest simultaneity
* stamp that we are currently using.
*
* Also, since simultaneity stamps are *NOT* used to record "when" the stimulus
* frame occured, we can also periodically run a reclaiming daemon process on
* our stored memories, which will try to defragment the simultaneity stamps
* in use by currently stored chronomena. Or we can silently mutate the
* simultaneity stamps of chronomena when committing them to backing storage;
* as well as when loading them from backing storage.
*/
typedef uint64_t SimultaneityStamp;
class StimulusFrame
{
public:
/** EXPLANATION:
* Default constructor creates uninitialized frame.
* Must be properly initialized using placement new with the parameterized constructor.
*/
StimulusFrame() = default;
StimulusFrame(
const FrameAssemblyDesc::SlotDesc& slotDesc_,
const SmoCallbacks& callbacks,
cl_mem_flags flags,
size_t ringBufferIndex_)
: slotDesc(slotDesc_),
ringBufferIndex(ringBufferIndex_)
{
if (!callbacks.ComputeManager_createUseHostPtrBuffer)
{
throw std::runtime_error(std::string(__func__)
+ ": StimulusFrame: ComputeManager_createUseHostPtrBuffer "
"callback is null");
}
clBuffer = callbacks.ComputeManager_createUseHostPtrBuffer(
slotDesc.vaddr, slotDesc.nBytes, flags);
if (!clBuffer)
{
throw std::runtime_error(std::string(__func__)
+ ": StimulusFrame: failed to create clBuffer");
}
// std::cout << __func__ << ": StimulusFrame: created clBuffer with size " << slotDesc.nBytes << " bytes @ " << (const void*)slotDesc.vaddr << std::endl;
}
~StimulusFrame() = default;
// Non-copyable, movable
StimulusFrame(const StimulusFrame&) = delete;
StimulusFrame& operator=(const StimulusFrame&) = delete;
StimulusFrame(StimulusFrame&&) = default;
StimulusFrame& operator=(StimulusFrame&&) = default;
public:
SequenceLock lock;
SimultaneityStamp simultaneityStamp;
FrameAssemblyDesc::SlotDesc slotDesc;
std::shared_ptr<smo::compute::ClBuffer> clBuffer;
size_t ringBufferIndex;
};
} // namespace stim_buff
} // namespace smo
#endif // _ATTACHMENT_SUPPORT_STIMULUS_FRAME_H
+119
View File
@@ -0,0 +1,119 @@
#ifndef _STIMULUS_PRODUCER_H
#define _STIMULUS_PRODUCER_H
#include <boostAsioLinkageFix.h>
#include <vector>
#include <memory>
#include <cstdint>
#include <atomic>
#include <mutex>
#include <functional>
#include <iostream>
#include <chrono>
#include <config.h>
#include <boost/asio/io_service.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <spinLock.h>
#include "deviceAttachmentSpec.h"
namespace smo {
namespace stim_buff {
// Forward declaration
class StimulusBuffer;
/**
* StimulusProducer manages a collection of stimulus frames with simultaneity stamps.
*
* This producer is designed to hold stimulus frames that have been assembled
* from raw sensor data (e.g., Livox Avia point cloud data) and are ready
* for processing by the mind layer.
*
* The producer provides thread-safe operations for adding frames, retrieving
* frames, and managing the producer state.
*/
class StimulusProducer
{
public:
explicit StimulusProducer(
const std::shared_ptr<device::DeviceAttachmentSpec>
&deviceAttachmentSpec,
boost::asio::io_service& ioService_)
: deviceAttachmentSpec(deviceAttachmentSpec),
ioService(ioService_),
shouldContinue(false), timer(ioService),
nDeferrals(0)
{}
virtual ~StimulusProducer() = default;
// Non-copyable, movable
StimulusProducer(const StimulusProducer&) = delete;
StimulusProducer& operator=(const StimulusProducer&) = delete;
StimulusProducer(StimulusProducer&&) = default;
StimulusProducer& operator=(StimulusProducer&&) = default;
// Control methods
virtual void start()
{
std::cout << __func__ << ": Starting stimulus producer for device "
<< deviceAttachmentSpec->deviceSelector << std::endl;
shouldContinue = true;
nDeferrals = 0;
scheduleNextTimeout();
}
virtual void stop();
void allowNextStimulusFrame()
{ frameAssemblyRateLimiter.release(); }
virtual std::shared_ptr<StimulusBuffer> getAttachedStimulusBuffer(
const std::shared_ptr<device::DeviceAttachmentSpec>& spec) const;
virtual std::shared_ptr<StimulusBuffer> getOrCreateAttachedStimulusBuffer(
const std::shared_ptr<device::DeviceAttachmentSpec>
&deviceAttachmentSpec) = 0;
virtual void destroyAttachedStimulusBuffer(
const std::shared_ptr<StimulusBuffer>& buffer);
// Check if any attached buffer has the specified qualeIfaceApi
bool hasBufferWithQualeIfaceApi(const std::string& qualeIfaceApi) const;
protected:
SpinLock frameAssemblyRateLimiter;
// Virtual functions for derived classes to override
virtual int getStopDelayMs() const
{
return CONFIG_STIMBUFF_FRAME_PERIOD_MS;
}
virtual void stimFrameProductionTimesliceInd() = 0;
private:
void onTimeout(const boost::system::error_code& error);
public:
std::shared_ptr<device::DeviceAttachmentSpec> deviceAttachmentSpec;
std::vector<std::shared_ptr<StimulusBuffer>> attachedStimulusBuffers;
private:
boost::asio::io_service& ioService;
protected:
SpinLock shouldContinueLock;
bool shouldContinue;
private:
boost::asio::deadline_timer timer;
size_t nDeferrals;
std::chrono::high_resolution_clock::time_point deferralStartTime;
void scheduleNextTimeout(int delayMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS);
};
} // namespace stim_buff
} // namespace smo
#endif // _STIMULUS_PRODUCER_H
+2
View File
@@ -1,10 +1,12 @@
#include <iostream> #include <iostream>
#include <pthread.h>
#include <componentThread.h> #include <componentThread.h>
#include <marionette/marionette.h> #include <marionette/marionette.h>
int main(int argc, char *argv[], char *envp[]) int main(int argc, char *argv[], char *envp[])
{ {
pthread_setname_np(pthread_self(), "smo:CRT:main");
/* We don't do anything inside of main() /* We don't do anything inside of main()
* Main merely waits for the marionette thread to exit. * Main merely waits for the marionette thread to exit.
*/ */
@@ -0,0 +1,171 @@
==========================================
Iteration 67 - Thu Oct 30 08:41:13 PM AST 2025
==========================================
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from salmanoff...
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) [answered Y; input not from terminal]
Waiting 2281ms before sending SIGINT...
Starting program...
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.ubuntu.com>
Enable debuginfod for this session? (y or [n]) [answered N; input not from terminal]
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff77ff6c0 (LWP 805031)]
CRT:main: about to JOLT Mrntt with cmdline args
main: Waiting for command line JOLT
Mrntt:operator():JOLTED: setting cmdline args
main: salmanoff 0.01.000
main: DAP Specs:
DAP Spec Files: devices/bodies/dell-laptop.daps
Stim Buff API Library Paths: commonLibs/livoxProto1/ commonLibs/xcbXorg/ stimBuffApis/xcbWindow/ stimBuffApis/livoxGen1/
Stim Buff API Libraries: libxcbWindow.so liblivoxGen1.so
initializeSalmanoff: Entered.
main: Entering event loop
[New Thread 0x7ffff6ffe6c0 (LWP 805032)]
[New Thread 0x7ffff67fd6c0 (LWP 805033)]
[New Thread 0x7ffff5ffc6c0 (LWP 805034)]
[New Thread 0x7ffff57fb6c0 (LWP 805035)]
[New Thread 0x7ffff4ffa6c0 (LWP 805036)]
distributeAndPinThreadsAcrossCpus: Distributed 5 threads across 4 CPUs
joltThreadReq1_posted: Thread 'director': handling JOLT request.
joltThreadReq1_posted: Thread 'simulator': handling JOLT request.
joltThreadReq1_posted: Thread 'subconscious': handling JOLT request.
joltThreadReq1_posted: Thread 'body': handling JOLT request.
body:main: Entering event loop
simulator:main: Entering event loop
subconscious:main: Entering event loop
joltThreadReq1_posted: Thread 'world': handling JOLT request.
world:main: Entering event loop
Mrntt: All mind threads JOLTed.
director:main: Entering event loop
startThreadReq1_posted: Thread 'director': handling startThread.
startThreadReq1_posted: Thread 'body': handling startThread.
startThreadReq1_posted: Thread 'simulator': handling startThread.
startThreadReq1_posted: Thread 'world': handling startThread.
startThreadReq1_posted: Thread 'subconscious': handling startThread.
Mrntt: All mind threads started.
Library Path: libxcbWindow.so
Stim Buff API Descriptor: Name: xcb
Exported QualeIface APIs:
- visual-qualeiface
Library Path: liblivoxGen1.so
Stim Buff API Descriptor: Name: livoxGen1
Exported QualeIface APIs:
- pcloud
- pcloudIntensity
- gyro
- accel
start: BroadcastListener started on port 55000
start: UDP Command Demuxer started on port 56001
attachStimBuffDeviceReq1_posted: Attaching edev win0 to world thread
xcbWindow_attachDeviceReq: Attached X11 window:
Display: 1, Screen: 0, MatchType: substring, Target: "mut", Found: "mutter guard window" (matched substring 'mut')
attachStimBuffDeviceReq1_posted: Attaching edev avia0 to world thread
getOrCreateDeviceReq1: Connection failed for device 3JEDK380010Z39
attachDeviceReq1: Failed to create/find Livox device: 3JEDK380010Z39
newDeviceAttachmentSpecInd2: Attach failed for device spec Device Identifier: avia0, Sensor Type: e, QualeIface API: structural-qualeiface, StimBuff API: livoxGen1, StimBuff API Params: (), Provider: livoxProto1, Provider Params: (), Device Selector: 3JEDK380010Z39
attachAllUnattachedDevicesFromReq2: Failed to attach device: avia0
Mrntt: attached 1 of 2 sense devices.
Mrntt: Body component initialized.
negtrinEventInd: Handling negtrin event.
marionetteInitializeReqCb: Marionette initialized.
broadcastMsgInd: Discovered new Livox device: DiscoveredDevice{identifier='3JEDK380010Z391', ipAddr='10.42.0.139', deviceType=7 (Avia)}
attachStimBuffDeviceReq1_posted: Attaching edev avia0 to world thread
attachDeviceReq1: Successfully attached/found Livox device: 3JEDK380010Z39 (ID: avia0)
Sending SIGINT to program (PID: 805028)...
SIGINT (Ctrl+C) received. Initiating shutdown...
Mrntt: About to detach all sense devices.
xcbWindow_detachDeviceReq: Detached X11 window device:
Device Identifier: win0, Sensor Type: e, QualeIface API: visual-qualeiface, StimBuff API: xcb, StimBuff API Params: (dev-substring ), Provider: xorg, Provider Params: (display=1 screen=0 ), Device Selector: mut
Mrntt: Successfully detached 1 of 1 sense devices.
Mrntt: About to finalize all stim buff api libs.
stop: UDP Command Demuxer stopped
stop: BroadcastListener stopped
broadcastMsgInd: Error receiving broadcast message: Operation canceled
Mrntt: About to unload all stim buff api libs.
Thread 7 "salmanoff" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff4ffa6c0 (LWP 805036)]
0x0000000000000000 in ?? ()
=== SEGFAULT DETECTED ===
#0 0x0000000000000000 in ?? ()
#1 0x00007ffff7ace057 in smo::stim_buff::AttachDeviceReq::attachDeviceReq2 (this=0x7ffff00098a0,
context=std::shared_ptr<smo::stim_buff::AttachDeviceReq> (use count 4, weak count 1) = {...}, error=...) at /home/latentprion/gits/salmanoff-git/stimBuffApis/livoxGen1/livoxGen1.cpp:160
#2 0x00007ffff7ae6584 in std::__invoke_impl<void, void (smo::stim_buff::AttachDeviceReq::*&)(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&), smo::stim_buff::AttachDeviceReq*&, std::shared_ptr<smo::stim_buff::AttachDeviceReq>&, boost::system::error_code const&> (
__f=@0x7ffff4ff9a80: (void (smo::stim_buff::AttachDeviceReq::*)(smo::stim_buff::AttachDeviceReq * const, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, const boost::system::error_code &)) 0x7ffff7acde68 <smo::stim_buff::AttachDeviceReq::attachDeviceReq2(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>,
__t=@0x7ffff4ff9aa0: 0x7ffff00098a0) at /usr/include/c++/13/bits/invoke.h:74
#3 0x00007ffff7ae42b1 in std::__invoke<void (smo::stim_buff::AttachDeviceReq::*&)(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&), smo::stim_buff::AttachDeviceReq*&, std::shared_ptr<smo::stim_buff::AttachDeviceReq>&, boost::system::error_code const&> (
__fn=@0x7ffff4ff9a80: (void (smo::stim_buff::AttachDeviceReq::*)(smo::stim_buff::AttachDeviceReq * const, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, const boost::system::error_code &)) 0x7ffff7acde68 <smo::stim_buff::AttachDeviceReq::attachDeviceReq2(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>)
at /usr/include/c++/13/bits/invoke.h:96
#4 0x00007ffff7ae1fe1 in std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>::__call<void, boost::system::error_code const&, 0ul, 1ul, 2ul>(std::tuple<boost::system::error_code const&>&&, std::_Index_tuple<0ul, 1ul, 2ul>) (this=0x7ffff4ff9a80, __args=...) at /usr/include/c++/13/functional:506
#5 0x00007ffff7ade79a in std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>::operator()<boost::system::error_code const&, void>(boost::system::error_code const&) (this=0x7ffff4ff9a80)
at /usr/include/c++/13/functional:591
#6 0x00007ffff7aec999 in boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>::operator()() (this=0x7ffff4ff9a80)
at /usr/include/boost/asio/detail/bind_handler.hpp:171
#7 0x00007ffff7aebd0e in boost::asio::asio_handler_invoke<boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code> >(boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>&, ...) (function=...) at /usr/include/boost/asio/handler_invoke_hook.hpp:88
#8 0x00007ffff7aea450 in boost_asio_handler_invoke_helpers::invoke<boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>, std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)> >(boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>&, std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>&) (function=..., context=...) at /usr/include/boost/asio/detail/handler_invoke_helpers.hpp:54
#9 0x00007ffff7ae8790 in boost::asio::detail::handler_work<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::asio::any_io_executor, void>::complete<boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code> >(boost::asio::detail::binder1<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::system::error_code>&, std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>&) (this=0x7ffff4ff9a40, function=..., handler=...) at /usr/include/boost/asio/detail/handler_work.hpp:524
#10 0x00007ffff7ae6986 in boost::asio::detail::wait_handler<std::_Bind<void (smo::stim_buff::AttachDeviceReq::*(smo::stim_buff::AttachDeviceReq*, std::shared_ptr<smo::stim_buff::AttachDeviceReq>, std::_Placeholder<1>))(std::shared_ptr<smo::stim_buff::AttachDeviceReq>, boost::system::error_code const&)>, boost::asio::any_io_executor>::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) (owner=0x7ffff0007970, base=0x7fffe400a180) at /usr/include/boost/asio/detail/wait_handler.hpp:76
#11 0x000055555556d35e in boost::asio::detail::scheduler_operation::complete (this=0x7fffe400a180, owner=0x7ffff0007970, ec=..., bytes_transferred=0)
at /usr/include/boost/asio/detail/scheduler_operation.hpp:40
#12 0x00005555555706e7 in boost::asio::detail::scheduler::do_run_one (this=0x7ffff0007970, lock=..., this_thread=..., ec=...) at /usr/include/boost/asio/detail/impl/scheduler.ipp:493
#13 0x00005555555700b9 in boost::asio::detail::scheduler::run (this=0x7ffff0007970, ec=...) at /usr/include/boost/asio/detail/impl/scheduler.ipp:210
#14 0x0000555555570a9d in boost::asio::io_context::run (this=0x7ffff0007900) at /usr/include/boost/asio/impl/io_context.ipp:64
#15 0x00005555555f6b10 in smo::MindThread::main (self=...) at /home/latentprion/gits/salmanoff-git/smocore/componentThread.cpp:82
#16 0x00005555555f4ed3 in std::__invoke_impl<void, void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > (
__f=@0x7ffff0007bf0: 0x5555555f6984 <smo::MindThread::main(smo::MindThread&)>) at /usr/include/c++/13/bits/invoke.h:61
#17 0x00005555555f4e41 in std::__invoke<void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > (
__fn=@0x7ffff0007bf0: 0x5555555f6984 <smo::MindThread::main(smo::MindThread&)>) at /usr/include/c++/13/bits/invoke.h:96
#18 0x00005555555f4d2f in std::thread::_Invoker<std::tuple<void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > >::_M_invoke<0ul, 1ul> (this=0x7ffff0007be8)
at /usr/include/c++/13/bits/std_thread.h:292
#19 0x00005555555f4c88 in std::thread::_Invoker<std::tuple<void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > >::operator() (this=0x7ffff0007be8)
at /usr/include/c++/13/bits/std_thread.h:299
#20 0x00005555555f4bdc in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(smo::MindThread&), std::reference_wrapper<smo::MindThread> > > >::_M_run (this=0x7ffff0007be0)
at /usr/include/c++/13/bits/std_thread.h:244
#21 0x00007ffff7cecdb4 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#22 0x00007ffff789caa4 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:447
#23 0x00007ffff7929c6c in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78
=== GDB is now interactive - you can inspect the state ===
[Thread 0x7ffff4ffa6c0 (LWP 805036) exited]
[Thread 0x7ffff57fb6c0 (LWP 805035) exited]
[Thread 0x7ffff5ffc6c0 (LWP 805034) exited]
[Thread 0x7ffff67fd6c0 (LWP 805033) exited]
[Thread 0x7ffff6ffe6c0 (LWP 805032) exited]
[Thread 0x7ffff7f5f780 (LWP 805028) exited]
[Thread 0x7ffff77ff6c0 (LWP 805031) exited]
[New process 805028]
Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb)
+123
View File
@@ -0,0 +1,123 @@
# GDB command file for reproducing UdpCommandDemuxer heisenbug
# This script runs salmanoff, waits a random time, sends SIGINT, and catches segfaults
# Disable pager so output doesn't pause for user input
set pagination off
# Set up signal handling - catch segfaults and stop
handle SIGSEGV stop print
# Allow SIGINT to pass through to program silently - make it unremarkable
# nostop: don't stop execution, noprint: don't print message, pass: pass to program
handle SIGINT nostop noprint pass
# Use Python to set up automatic handling of stop events and SIGINT injection
python
import time
import random
import threading
import os
import signal
sigint_thread_started = False
def send_sigint_after_delay():
# Wait random milliseconds between 2000-3000
delay_ms = random.randint(2000, 3000)
print(f"Waiting {delay_ms}ms before sending SIGINT...")
time.sleep(delay_ms / 1000.0)
# Send SIGINT directly to the process using its PID
# This works even when the program is running (unlike gdb.execute("signal SIGINT"))
try:
inferior = gdb.selected_inferior()
if inferior and inferior.is_valid():
pid = inferior.pid
print(f"Sending SIGINT to program (PID: {pid})...")
os.kill(pid, signal.SIGINT)
else:
print("Program is not running - cannot send SIGINT")
except Exception as e:
print(f"Failed to send SIGINT: {e}")
def start_sigint_thread():
global sigint_thread_started
if not sigint_thread_started:
sigint_thread_started = True
thread = threading.Thread(target=send_sigint_after_delay, daemon=True)
thread.start()
# Hook to check stop reason and handle segfaults
def stop_handler(event):
if isinstance(event, gdb.SignalEvent):
if event.stop_signal == "SIGSEGV":
# Segfault detected
print("\n=== SEGFAULT DETECTED ===")
gdb.execute("bt")
print("\n=== GDB is now interactive - you can inspect the state ===")
# Don't quit - stay in interactive mode
elif event.stop_signal == "SIGINT":
# SIGINT received - with "nostop pass", SIGINT should pass through automatically
# But if we get here (shouldn't happen with nostop), just let it pass
pass
elif isinstance(event, gdb.ExitedEvent):
# Program exited normally
if event.exit_code == 0:
print("\nProgram exited normally. Continuing loop...")
gdb.post_event(lambda: gdb.execute("quit", False))
else:
print(f"\nProgram exited with code {event.exit_code}")
gdb.post_event(lambda: gdb.execute("quit", False))
# Hook for when program continues/starts running
def cont_handler(event):
# When program continues (or starts running), start the SIGINT thread
start_sigint_thread()
# Register event handlers
gdb.events.stop.connect(stop_handler)
gdb.events.cont.connect(cont_handler)
# Start SIGINT thread before running - it will wait and then send SIGINT
# The thread will send SIGINT even if program is stopped (signal will be delivered on continue)
start_sigint_thread()
end
# Start the program
echo Starting program...\n
run
# After run completes, check if program exited or stopped
# If program exited, quit GDB. If program stopped (has threads), continue.
python
try:
inferior = gdb.selected_inferior()
if inferior and inferior.is_valid():
# Check if there are threads (indicates program has not exited)
try:
threads = inferior.threads()
if threads:
# Program has threads - continue execution
# SIGINT thread is already running and will send signal when ready
gdb.execute("continue", False)
else:
# No threads - program has exited
print("\nProgram has exited (no threads found).")
gdb.execute("quit", False)
except Exception as e:
# If we can't check threads, assume program exited
print(f"\nError checking threads: {e}")
print("Assuming program exited.")
gdb.execute("quit", False)
else:
# Inferior is not valid - program has exited
print("\nProgram has exited (inferior not valid).")
gdb.execute("quit", False)
except Exception as e:
print(f"Error checking program state: {e}")
# If we can't determine state, try to quit
try:
gdb.execute("quit", False)
except:
pass
end
+115
View File
@@ -0,0 +1,115 @@
#!/bin/bash
# Script to reproduce UdpCommandDemuxer race condition heisenbug
# Runs salmanoff in GDB repeatedly, injecting SIGINT at random intervals
#
# Usage: ./reproduce_heisenbug.sh [WORKING_DIR]
# WORKING_DIR: Working directory where salmanoff binary and all paths are relative to
# If not provided, uses WORKING_DIR environment variable, or defaults to project root
#
# Environment variables:
# WORKING_DIR: Working directory (can be overridden by command-line argument)
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Determine working directory (command-line arg > env var > default)
if [ -n "$1" ]; then
WORKING_DIR="$1"
elif [ -n "$WORKING_DIR" ]; then
# Use environment variable
:
else
# Default to project root
WORKING_DIR="$PROJECT_ROOT"
fi
# Convert to absolute path
WORKING_DIR="$(cd "$WORKING_DIR" && pwd)"
# Check if working directory exists
if [ ! -d "$WORKING_DIR" ]; then
echo "Error: Working directory does not exist: $WORKING_DIR" >&2
exit 1
fi
# Paths - all relative to working directory
SALMANOFF_BINARY="$WORKING_DIR/salmanoff"
GDB_SCRIPT="$SCRIPT_DIR/gdb_heisenbug.gdb"
# Check if binary exists
if [ ! -f "$SALMANOFF_BINARY" ]; then
echo "Error: salmanoff binary not found at $SALMANOFF_BINARY" >&2
echo "Working directory: $WORKING_DIR" >&2
exit 1
fi
# Check if GDB script exists
if [ ! -f "$GDB_SCRIPT" ]; then
echo "Error: GDB script not found at $GDB_SCRIPT" >&2
exit 1
fi
# Command line arguments for salmanoff
SALMANOFF_ARGS=(
-p commonLibs/livoxProto1/
-p commonLibs/xcbXorg/
-p stimBuffApis/xcbWindow/
-p stimBuffApis/livoxGen1/
-a libxcbWindow.so
-a liblivoxGen1.so
-d devices/bodies/dell-laptop.daps
)
echo "=== UdpCommandDemuxer Heisenbug Reproduction Script ==="
echo "Working Directory: $WORKING_DIR"
echo "Binary: $SALMANOFF_BINARY"
echo "GDB Script: $GDB_SCRIPT"
echo "Arguments: ${SALMANOFF_ARGS[*]}"
echo ""
echo "Press Ctrl+C to stop the loop"
echo ""
# Change to working directory so all relative paths are resolved correctly
cd "$WORKING_DIR"
# Loop counter
ITERATION=0
# Main loop
while true; do
ITERATION=$((ITERATION + 1))
echo "=========================================="
echo "Iteration $ITERATION - $(date)"
echo "=========================================="
echo ""
# Run GDB with the command file
# GDB will stay interactive on segfault, exit on normal completion
# When GDB stays interactive (on segfault), this will wait for user to quit GDB
# When GDB exits normally (program completed), exit code will be 0 and loop continues
# Note: We use a relative path to salmanoff binary since we're already in WORKING_DIR
SALMANOFF_RELATIVE="salmanoff"
if gdb -x "$GDB_SCRIPT" --args "$SALMANOFF_RELATIVE" "${SALMANOFF_ARGS[@]}"; then
# GDB exited successfully (program completed normally)
EXIT_CODE=0
else
# GDB exited with error (unexpected exit or user interrupted)
EXIT_CODE=$?
echo ""
echo "GDB exited with code $EXIT_CODE"
if [ $EXIT_CODE -ne 0 ] && [ $EXIT_CODE -ne 130 ]; then
# Exit code 130 is SIGINT (user pressed Ctrl+C), which is expected
echo "Unexpected GDB exit - check output above"
fi
fi
echo ""
echo "Iteration $ITERATION complete. Starting next iteration in 1 second..."
sleep 1
echo ""
done
echo ""
echo "Loop terminated."
-22
View File
@@ -1,22 +0,0 @@
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
@@ -1,416 +0,0 @@
#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
+20 -3
View File
@@ -8,18 +8,21 @@ add_library(smocore STATIC
opts.cpp opts.cpp
componentThread.cpp componentThread.cpp
component.cpp component.cpp
painfulQuale.cpp
qutex.cpp qutex.cpp
lockerAndInvokerBase.cpp lockerAndInvokerBase.cpp
# Body # Body
body/body.cpp body/body.cpp
# Director
director/director.cpp
# Marionette # Marionette
marionette/main.cpp marionette/main.cpp
marionette/salmanoff.cpp marionette/salmanoff.cpp
marionette/lifetime.cpp marionette/lifetime.cpp
marionette/qualeEvent.cpp marionette/qualeEvent.cpp
marionette/negtrinEvent.cpp
# DeviceManager # DeviceManager
deviceManager/deviceManager.cpp deviceManager/deviceManager.cpp
@@ -29,7 +32,10 @@ add_library(smocore STATIC
${YACC_OUTPUT} ${YACC_OUTPUT}
# SenseApis # SenseApis
senseApis/senseApiManager.cpp stimBuffApis/stimBuffApiManager.cpp
# ComputeManager
computeManager/computeManager.cpp
# MindManager # MindManager
mindManager/mindManager.cpp mindManager/mindManager.cpp
@@ -43,8 +49,19 @@ 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} ${CMAKE_CURRENT_BINARY_DIR}
${Boost_INCLUDE_DIRS}
${OPENCL_INCLUDE_DIRS}
) )
# 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
Boost::system
Boost::log
${OPENCL_LIBRARIES}
attachmentSupport
)
target_link_directories(smocore PRIVATE
${OPENCL_LIBRARY_DIRS}
)
+17 -15
View File
@@ -3,10 +3,11 @@
#include <asynchronousContinuation.h> #include <asynchronousContinuation.h>
#include <asynchronousLoop.h> #include <asynchronousLoop.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <body/body.h> #include <body/body.h>
#include <componentThread.h> #include <componentThread.h>
#include <mind.h> #include <mind.h>
#include <senseApis/senseApiManager.h> #include <stimBuffApis/stimBuffApiManager.h>
#include <deviceManager/deviceManager.h> #include <deviceManager/deviceManager.h>
namespace smo { namespace smo {
@@ -50,11 +51,11 @@ public:
* For example, liblivoxProto1's BroadcastListener will use this thread * For example, liblivoxProto1's BroadcastListener will use this thread
* to listen for UDP broadcast dgrams from Livox devices. * to listen for UDP broadcast dgrams from Livox devices.
* *
* Right now we use Marionette, but there's a strong argument for using * We used to use Marionette, but there's a strong argument for using
* Body instead since it's meant to handle device-management operations. * Body instead since it's meant to handle device-management operations.
*/ */
sense_api::SenseApiManager::getInstance() stim_buff::StimBuffApiManager::getInstance()
.loadAllSenseApiLibsFromOptions(caller); .loadAllStimBuffApiLibsFromOptions(parent.body.thread);
/** EXPLANATION: /** EXPLANATION:
* Consider body::initializeReq to have been called if even one of its * Consider body::initializeReq to have been called if even one of its
@@ -63,15 +64,16 @@ public:
*/ */
context->parent.bodyComponentInitialized = true; context->parent.bodyComponentInitialized = true;
std::cout << sense_api::SenseApiManager::getInstance().stringifyLibs() std::cout << stim_buff::StimBuffApiManager::getInstance().stringifyLibs()
<< std::endl; << std::endl;
if (OptionParser::getOptions().verbose) if (OptionParser::getOptions().verbose)
{ {
std::cout << __func__ << ": About to initializeAllSenseApiLibs" std::cout << __func__ << ": About to initializeAllStimBuffApiLibs"
<< '\n'; << '\n';
} }
sense_api::SenseApiManager::getInstance().initializeAllSenseApiLibs(); stim_buff::StimBuffApiManager::getInstance()
.initializeAllStimBuffApiLibs();
if (OptionParser::getOptions().verbose) if (OptionParser::getOptions().verbose)
{ {
@@ -134,11 +136,11 @@ public:
<< results.nSucceeded << " of " << results.nTotal << results.nSucceeded << " of " << results.nTotal
<< " sense devices." << "\n"; << " sense devices." << "\n";
std::cout << "Mrntt: About to finalize all sense api libs." << "\n"; std::cout << "Mrntt: About to finalize all stim buff api libs." << "\n";
sense_api::SenseApiManager::getInstance().finalizeAllSenseApiLibs(); stim_buff::StimBuffApiManager::getInstance().finalizeAllStimBuffApiLibs();
std::cout << "Mrntt: About to unload all sense api libs." << "\n"; std::cout << "Mrntt: About to unload all stim buff api libs." << "\n";
sense_api::SenseApiManager::getInstance().unloadAllSenseApiLibs(); stim_buff::StimBuffApiManager::getInstance().unloadAllStimBuffApiLibs();
callOriginalCb(results.nSucceeded == results.nTotal); callOriginalCb(results.nSucceeded == results.nTotal);
} }
}; };
@@ -157,9 +159,9 @@ void Body::initializeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
parent, mrntt, callback); parent, mrntt, callback);
thread->getIoService().post( thread->getIoService().post(
std::bind( STC(std::bind(
&InitializeReq::initializeReq1_posted, &InitializeReq::initializeReq1_posted,
request.get(), request)); request.get(), request)));
} }
void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback) void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
@@ -186,9 +188,9 @@ void Body::finalizeReq(Callback<bodyLifetimeMgmtOpCbFn> callback)
parent, mrntt, callback); parent, mrntt, callback);
thread->getIoService().post( thread->getIoService().post(
std::bind( STC(std::bind(
&FinalizeReq::finalizeReq1_posted, &FinalizeReq::finalizeReq1_posted,
request.get(), request)); request.get(), request)));
} }
} // namespace body } // namespace body
+1
View File
@@ -1,4 +1,5 @@
#include <component.h> #include <component.h>
#include <marionette/marionette.h>
namespace smo { namespace smo {
+24 -16
View File
@@ -1,11 +1,14 @@
#include <boostAsioLinkageFix.h>
#include <unistd.h> #include <unistd.h>
#include <iostream> #include <iostream>
#include <string>
#include <pthread.h> #include <pthread.h>
#include <sched.h> #include <sched.h>
#include <boost/asio.hpp> #include <boost/asio/io_service.hpp>
#include <opts.h> #include <opts.h>
#include <asynchronousContinuation.h> #include <asynchronousContinuation.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <mind.h> #include <mind.h>
#include <mindManager/mindManager.h> #include <mindManager/mindManager.h>
#include <componentThread.h> #include <componentThread.h>
@@ -31,6 +34,11 @@ void MindThread::initializeTls(void)
thisComponentThread = shared_from_this(); thisComponentThread = shared_from_this();
} }
bool ComponentThread::tlsInitialized(void)
{
return thisComponentThread != nullptr;
}
const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void) const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void)
{ {
if (!thisComponentThread) if (!thisComponentThread)
@@ -44,6 +52,8 @@ const std::shared_ptr<ComponentThread> ComponentThread::getSelf(void)
void MindThread::main(MindThread& self) void MindThread::main(MindThread& self)
{ {
std::string threadName = "smo:" + self.name;
pthread_setname_np(pthread_self(), threadName.c_str());
if (OptionParser::getOptions().verbose) if (OptionParser::getOptions().verbose)
{ {
@@ -96,9 +106,7 @@ void MindThread::main(MindThread& self)
if (sendExceptionInd) if (sendExceptionInd)
{ {
mrntt::mrntt.finalizeReq( mrntt::mrntt.exceptionInd();
{nullptr, std::bind(
&mrntt::marionetteFinalizeReqCb, std::placeholders::_1)});
} }
} }
@@ -236,9 +244,9 @@ void MindThread::joltThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
mrntt, target, callback); mrntt, target, callback);
this->getIoService().post( this->getIoService().post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::joltThreadReq1_posted, &ThreadLifetimeMgmtOp::joltThreadReq1_posted,
request.get(), request)); request.get(), request)));
} }
// Thread management method implementations // Thread management method implementations
@@ -249,9 +257,9 @@ void MindThread::startThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
caller, shared_from_this(), callback); caller, shared_from_this(), callback);
this->getIoService().post( this->getIoService().post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::startThreadReq1_posted, &ThreadLifetimeMgmtOp::startThreadReq1_posted,
request.get(), request)); request.get(), request)));
} }
void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback) void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
@@ -261,14 +269,14 @@ void MindThread::exitThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
caller, shared_from_this(), callback); caller, shared_from_this(), callback);
this->getIoService().post( this->getIoService().post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted, &ThreadLifetimeMgmtOp::exitThreadReq1_mainQueue_posted,
request.get(), request)); request.get(), request)));
pause_io_service.post( pause_io_service.post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted, &ThreadLifetimeMgmtOp::exitThreadReq1_pauseQueue_posted,
request.get(), request)); request.get(), request)));
} }
void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback) void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
@@ -284,9 +292,9 @@ void MindThread::pauseThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
caller, shared_from_this(), callback); caller, shared_from_this(), callback);
this->getIoService().post( this->getIoService().post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::pauseThreadReq1_posted, &ThreadLifetimeMgmtOp::pauseThreadReq1_posted,
request.get(), request)); request.get(), request)));
} }
void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback) void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
@@ -303,9 +311,9 @@ void MindThread::resumeThreadReq(Callback<threadLifetimeMgmtOpCbFn> callback)
caller, shared_from_this(), callback); caller, shared_from_this(), callback);
pause_io_service.post( pause_io_service.post(
std::bind( STC(std::bind(
&ThreadLifetimeMgmtOp::resumeThreadReq1_posted, &ThreadLifetimeMgmtOp::resumeThreadReq1_posted,
request.get(), request)); request.get(), request)));
} }
// CPU management method implementations // CPU management method implementations
+192
View File
@@ -0,0 +1,192 @@
#include <iostream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <algorithm>
#include <computeManager/computeManager.h>
#include <user/compute.h>
namespace smo {
namespace compute {
void ComputeManager::initialize()
{
if (initialized) { return; }
cl_int err;
// Get number of platforms
cl_uint numPlatforms = 0;
err = clGetPlatformIDs(0, nullptr, &numPlatforms);
if (err != CL_SUCCESS)
{
throw std::runtime_error(
std::string(__func__) + ": failed to get OpenCL platforms: " +
std::to_string(err));
}
if (numPlatforms == 0)
{
throw std::runtime_error(
std::string(__func__) + ": no OpenCL platforms found");
}
// Get all platforms
std::vector<cl_platform_id> platforms(numPlatforms);
err = clGetPlatformIDs(numPlatforms, platforms.data(), nullptr);
if (err != CL_SUCCESS)
{
throw std::runtime_error(
std::string(__func__) + ": failed to enumerate OpenCL platforms: " +
std::to_string(err));
}
// Enumerate devices for each platform
for (cl_uint p = 0; p < numPlatforms; ++p)
{
cl_platform_id platform = platforms[p];
// Check platform version
char platformVersion[128];
err = clGetPlatformInfo(
platform, CL_PLATFORM_VERSION,
sizeof(platformVersion), platformVersion, nullptr);
if (err == CL_SUCCESS)
{
if (!validateOpenClVersion(platformVersion, "platform", 1, 2))
{
std::cout << __func__ << ": skipping platform " << p
<< " with incompatible OpenCL version "
<< std::string(platformVersion) << std::endl;
continue;
}
}
// Get number of devices
cl_uint numDevices = 0;
err = clGetDeviceIDs(
platform, CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevices);
if (err != CL_SUCCESS || numDevices == 0)
{
std::cout << __func__ << ": skipping platform " << p
<< " with no devices" << std::endl;
continue;
}
// Get all devices
std::vector<cl_device_id> platformDevices(numDevices);
err = clGetDeviceIDs(
platform, CL_DEVICE_TYPE_ALL, numDevices,
platformDevices.data(), nullptr);
if (err != CL_SUCCESS)
{
throw std::runtime_error(
std::string(__func__) + ": failed to enumerate devices for "
"platform " + std::to_string(p) + ": " + std::to_string(err));
}
// Create ComputeDevice for each device
for (cl_uint d = 0; d < numDevices; ++d)
{
cl_device_id device = platformDevices[d];
// Check device version
char deviceVersion[128];
err = clGetDeviceInfo(
device, CL_DEVICE_VERSION,
sizeof(deviceVersion), deviceVersion, nullptr);
if (err == CL_SUCCESS)
{
if (!validateOpenClVersion(deviceVersion, "device", 1, 2))
{
std::cout << __func__ << ": skipping device " << d
<< " with incompatible OpenCL version "
<< std::string(deviceVersion) << std::endl;
continue;
}
}
// Create ComputeDevice (constructor creates context and queue)
try
{
auto deviceObj = std::make_shared<ComputeDevice>(
platform, device);
devices.push_back(deviceObj);
}
catch (const std::runtime_error& e)
{
// Re-throw with more context about which device/platform
throw std::runtime_error(
std::string(__func__) + ": failed to create ComputeDevice "
"for device " + std::to_string(d) + " on platform " +
std::to_string(p) + ": " + e.what());
}
}
}
if (devices.empty())
{
throw std::runtime_error(
std::string(__func__) + ": no compatible OpenCL devices found");
}
initialized = true;
std::cout << __func__ << ": Initialized with " << devices.size()
<< " compute device(s)" << std::endl;
}
void ComputeManager::finalize()
{
if (!initialized) { return; }
// Release all devices (their shared_ptrs will clean up contexts/queues)
devices.clear();
initialized = false;
std::cout << __func__ << ": Finalized" << std::endl;
}
std::shared_ptr<ClBuffer>
ComputeManager::createUseHostPtrBuffer(
void* hostPtr, size_t size, cl_mem_flags flags)
{
if (!initialized)
{
std::cerr << __func__ << ": ComputeManager not initialized"
<< std::endl;
throw std::runtime_error(
std::string(__func__) + ": ComputeManager not initialized");
}
return std::make_shared<ClBuffer>(hostPtr, size, flags, devices);
}
void ComputeManager::releaseUseHostPtrBuffer(std::shared_ptr<ClBuffer> buffer)
{
// No-op: ClBuffer's destructor handles cleanup automatically
// This function exists for API compatibility
(void)buffer;
}
std::shared_ptr<ComputeDevice> ComputeManager::getDevice()
{
if (!initialized || devices.empty()) {
return nullptr;
}
// Return first available device
// In the future, this will filter based on ComputeDeviceConstraints
return devices[0];
}
void ComputeManager::releaseDevice(std::shared_ptr<ComputeDevice> device)
{
// Placeholder for future refcounting implementation
// Devices are only removed in finalize()
(void)device;
}
} // namespace compute
} // namespace smo
@@ -106,18 +106,20 @@ extrospector_spec:
; ;
spec_body: spec_body:
STRING PIPE STRING PIPE STRING LPAREN opt_params RPAREN PIPE STRING LPAREN opt_params RPAREN PIPE STRING { STRING PIPE STRING LPAREN opt_params RPAREN PIPE STRING LPAREN opt_params RPAREN PIPE STRING LPAREN opt_params RPAREN PIPE STRING {
$$ = new smo::device::DeviceAttachmentSpec(); $$ = new smo::device::DeviceAttachmentSpec();
$$->deviceIdentifier = std::string($1); $$->deviceIdentifier = std::string($1);
$$->sensorType = '\0'; // This will be set by the parent rule $$->sensorType = '\0'; // This will be set by the parent rule
$$->implexor = std::string($3); $$->qualeIfaceApi = std::string($3);
$$->api = std::string($5); $$->qualeIfaceApiParams = std::move(*$5);
$$->apiParams = std::move(*$7); $$->stimBuffApi = std::string($8);
$$->provider = std::string($10); $$->stimBuffApiParams = std::move(*$10);
$$->providerParams = std::move(*$12); $$->provider = std::string($13);
$$->deviceSelector = std::string($15); $$->providerParams = std::move(*$15);
delete $7; $$->deviceSelector = std::string($18);
delete $12; delete $5;
delete $10;
delete $15;
} }
; ;
+69 -65
View File
@@ -9,10 +9,11 @@
#include <asynchronousContinuation.h> #include <asynchronousContinuation.h>
#include <serializedAsynchronousContinuation.h> #include <serializedAsynchronousContinuation.h>
#include <callback.h> #include <callback.h>
#include <callableTracer.h>
#include <componentThread.h> #include <componentThread.h>
#include <deviceManager/deviceManager.h> #include <deviceManager/deviceManager.h>
#include <deviceManager/deviceReattacher.h> #include <deviceManager/deviceReattacher.h>
#include <senseApis/senseApiManager.h> #include <stimBuffApis/stimBuffApiManager.h>
#include <marionette/marionette.h> #include <marionette/marionette.h>
#include <mind.h> #include <mind.h>
@@ -132,7 +133,7 @@ public:
return; return;
} }
DeviceManager::getInstance().attachSenseDeviceReq( DeviceManager::getInstance().attachStimBuffDeviceReq(
specPtr, specPtr,
{context, std::bind( {context, std::bind(
&NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd2, &NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd2,
@@ -209,8 +210,8 @@ public:
return; return;
} }
// Call detachSenseDeviceReq first - only clean up metadata if this succeeds // Call detachStimBuffDeviceReq first - only clean up metadata if this succeeds
DeviceManager::getInstance().detachSenseDeviceReq( DeviceManager::getInstance().detachStimBuffDeviceReq(
specPtr, specPtr,
{context, std::bind( {context, std::bind(
&RemoveDeviceAttachmentSpecReq::removeDeviceAttachmentSpecReq2, &RemoveDeviceAttachmentSpecReq::removeDeviceAttachmentSpecReq2,
@@ -329,24 +330,25 @@ void DeviceManager::removeDeviceAttachmentSpecReq(
request.get(), request)); request.get(), request));
} }
class DeviceManager::AttachSenseDeviceReq class DeviceManager::AttachStimBuffDeviceReq
: public SerializedAsynchronousContinuation<attachSenseDeviceReqCbFn> : public SerializedAsynchronousContinuation<
DeviceManager::attachStimBuffDeviceReqCbFn>
{ {
public: public:
AttachSenseDeviceReq( AttachStimBuffDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec, const std::shared_ptr<DeviceAttachmentSpec>& spec,
const std::shared_ptr<ComponentThread> &caller, const std::shared_ptr<ComponentThread> &caller,
Callback<attachSenseDeviceReqCbFn> cb, Callback<DeviceManager::attachStimBuffDeviceReqCbFn> cb,
std::shared_ptr<sense_api::SenseApiLib> &senseApiLib, std::shared_ptr<stim_buff::StimBuffApiLib> &stimBuffApiLib,
std::vector<std::reference_wrapper<Qutex>> requiredLocks) std::vector<std::reference_wrapper<Qutex>> requiredLocks)
: SerializedAsynchronousContinuation<attachSenseDeviceReqCbFn>( : SerializedAsynchronousContinuation<attachStimBuffDeviceReqCbFn>(
caller, cb, requiredLocks), caller, cb, requiredLocks),
spec(spec), senseApiLib(senseApiLib) spec(spec), stimBuffApiLib(stimBuffApiLib)
{} {}
public: public:
void attachSenseDeviceReq1_posted( void attachStimBuffDeviceReq1_posted(
[[maybe_unused]] std::shared_ptr<AttachSenseDeviceReq> context [[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context
) )
{ {
if (caller->id != ComponentThread::MRNTT) if (caller->id != ComponentThread::MRNTT)
@@ -358,23 +360,24 @@ public:
return; return;
} }
if (senseApiLib->isBeingDestroyed.load()) if (stimBuffApiLib->isBeingDestroyed.load())
{ {
std::cerr << std::string(__func__) + ": Library is being destroyed" std::cerr << std::string(__func__) + ": Library is being destroyed"
<< " for API '" << spec->api << "'. Bailing out." << std::endl; << " for API '" << spec->stimBuffApi << "'. Bailing out.\n";
callOriginalCb(false, spec); callOriginalCb(false, spec);
return; return;
} }
if (!senseApiLib->senseApiDesc.sal_mgmt_libOps.attachDeviceReq) if (!stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.attachDeviceReq)
{ {
std::cerr << std::string(__func__) + ": attachDeviceReq() is NULL " std::cerr << std::string(__func__) + ": attachDeviceReq() is NULL "
"for library '" << senseApiLib->libraryPath << "'" << std::endl; "for library '" << stimBuffApiLib->libraryPath << "'"
<< std::endl;
callOriginalCb(false, spec); callOriginalCb(false, spec);
return; return;
} }
releaseQutexEarly(sense_api::SenseApiManager::getInstance().qutex); releaseQutexEarly(stim_buff::StimBuffApiManager::getInstance().qutex);
/** EXPLANATION: /** EXPLANATION:
* We pass in either the body or world thread here, depending on whether * We pass in either the body or world thread here, depending on whether
@@ -397,16 +400,16 @@ public:
<< spec->deviceIdentifier << " to body thread" << "\n"; << spec->deviceIdentifier << " to body thread" << "\n";
} }
senseApiLib->senseApiDesc.sal_mgmt_libOps.attachDeviceReq( stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.attachDeviceReq(
spec, threadForAttachment, spec, threadForAttachment,
{context, std::bind( {context, std::bind(
&AttachSenseDeviceReq::attachSenseDeviceReq2, &AttachStimBuffDeviceReq::attachStimBuffDeviceReq2,
context.get(), context, context.get(), context,
std::placeholders::_1, std::placeholders::_2)}); std::placeholders::_1, std::placeholders::_2)});
} }
void attachSenseDeviceReq2( void attachStimBuffDeviceReq2(
[[maybe_unused]] std::shared_ptr<AttachSenseDeviceReq> context, [[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context,
bool success, bool success,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec std::shared_ptr<DeviceAttachmentSpec> deviceSpec
) )
@@ -414,8 +417,8 @@ public:
callOriginalCb(success, deviceSpec); callOriginalCb(success, deviceSpec);
} }
void detachSenseDeviceReq1_posted( void detachStimBuffDeviceReq1_posted(
[[maybe_unused]] std::shared_ptr<DetachSenseDeviceReq> context [[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context
) )
{ {
if (caller->id != ComponentThread::MRNTT) if (caller->id != ComponentThread::MRNTT)
@@ -427,34 +430,35 @@ public:
return; return;
} }
if (senseApiLib->isBeingDestroyed.load()) if (stimBuffApiLib->isBeingDestroyed.load())
{ {
std::cerr << std::string(__func__) + ": Library is being destroyed" std::cerr << std::string(__func__) + ": Library is being destroyed"
<< " for API '" << spec->api << "'. Bailing out." << std::endl; << " for API '" << spec->stimBuffApi << "'. Bailing out.\n";
callOriginalCb(false, spec); callOriginalCb(false, spec);
return; return;
} }
if (!senseApiLib->senseApiDesc.sal_mgmt_libOps.detachDeviceReq) if (!stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.detachDeviceReq)
{ {
std::cerr << std::string(__func__) + ": detachDeviceReq() is NULL " std::cerr << std::string(__func__) + ": detachDeviceReq() is NULL "
"for library '" << senseApiLib->libraryPath << "'" << std::endl; "for library '" << stimBuffApiLib->libraryPath << "'"
<< std::endl;
callOriginalCb(false, spec); callOriginalCb(false, spec);
return; return;
} }
releaseQutexEarly(sense_api::SenseApiManager::getInstance().qutex); releaseQutexEarly(stim_buff::StimBuffApiManager::getInstance().qutex);
senseApiLib->senseApiDesc.sal_mgmt_libOps.detachDeviceReq( stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.detachDeviceReq(
spec, spec,
{context, std::bind( {context, std::bind(
&DetachSenseDeviceReq::detachSenseDeviceReq2, &AttachStimBuffDeviceReq::detachStimBuffDeviceReq2,
context.get(), context, context.get(), context,
std::placeholders::_1, std::placeholders::_2)}); std::placeholders::_1, std::placeholders::_2)});
} }
void detachSenseDeviceReq2( void detachStimBuffDeviceReq2(
[[maybe_unused]] std::shared_ptr<DetachSenseDeviceReq> context, [[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context,
bool success, bool success,
std::shared_ptr<DeviceAttachmentSpec> deviceSpec std::shared_ptr<DeviceAttachmentSpec> deviceSpec
) )
@@ -464,76 +468,76 @@ public:
public: public:
std::shared_ptr<DeviceAttachmentSpec> spec; std::shared_ptr<DeviceAttachmentSpec> spec;
std::shared_ptr<sense_api::SenseApiLib> senseApiLib; std::shared_ptr<stim_buff::StimBuffApiLib> stimBuffApiLib;
}; };
void DeviceManager::attachSenseDeviceReq( void DeviceManager::attachStimBuffDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec, const std::shared_ptr<DeviceAttachmentSpec>& spec,
Callback<attachSenseDeviceReqCbFn> cb Callback<attachStimBuffDeviceReqCbFn> cb
) )
{ {
const auto& caller = ComponentThread::getSelf(); const auto& caller = ComponentThread::getSelf();
// Get the sense API lib's qutex // Get the stim buff API lib's qutex
auto libOpt = sense_api::SenseApiManager::getInstance() auto libOpt = stim_buff::StimBuffApiManager::getInstance()
.getSenseApiLibByApiName(spec->api); .getStimBuffApiLibByApiName(spec->stimBuffApi);
if (!libOpt) if (!libOpt)
{ {
std::cerr << "attachSenseDeviceReq: No library found for API '" std::cerr << "attachStimBuffDeviceReq: No library found for API '"
<< spec->api << "'" << std::endl; << spec->stimBuffApi << "'" << std::endl;
cb.callbackFn(false, spec); cb.callbackFn(false, spec);
return; return;
} }
auto& lib = *libOpt.value(); auto& lib = *libOpt.value();
auto request = std::make_shared<AttachSenseDeviceReq>( auto request = std::make_shared<AttachStimBuffDeviceReq>(
spec, caller, cb, libOpt.value(), spec, caller, cb, libOpt.value(),
LockSet<attachSenseDeviceReqCbFn>::Set{ LockSet<attachStimBuffDeviceReqCbFn>::Set{
std::ref(sense_api::SenseApiManager::getInstance().qutex), std::ref(stim_buff::StimBuffApiManager::getInstance().qutex),
std::ref(lib.qutex) std::ref(lib.qutex)
}); });
AttachSenseDeviceReq::LockerAndInvoker lockvoker( AttachStimBuffDeviceReq::LockerAndInvoker lockvoker(
*request, mrntt::mrntt.thread, *request, mrntt::mrntt.thread,
std::bind( std::bind(
&AttachSenseDeviceReq::attachSenseDeviceReq1_posted, &AttachStimBuffDeviceReq::attachStimBuffDeviceReq1_posted,
request.get(), request)); request.get(), request));
} }
void DeviceManager::detachSenseDeviceReq( void DeviceManager::detachStimBuffDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec, const std::shared_ptr<DeviceAttachmentSpec>& spec,
Callback<detachSenseDeviceReqCbFn> cb Callback<detachStimBuffDeviceReqCbFn> cb
) )
{ {
const auto& caller = ComponentThread::getSelf(); const auto& caller = ComponentThread::getSelf();
// Get the sense API lib's qutex // Get the stim buff API lib's qutex
auto libOpt = sense_api::SenseApiManager::getInstance() auto libOpt = stim_buff::StimBuffApiManager::getInstance()
.getSenseApiLibByApiName(spec->api); .getStimBuffApiLibByApiName(spec->stimBuffApi);
if (!libOpt) if (!libOpt)
{ {
std::cerr << "detachSenseDeviceReq: No library found for API '" std::cerr << "detachStimBuffDeviceReq: No library found for API '"
<< spec->api << "'" << std::endl; << spec->stimBuffApi << "'" << std::endl;
cb.callbackFn(false, spec); cb.callbackFn(false, spec);
return; return;
} }
auto& lib = *libOpt.value(); auto& lib = *libOpt.value();
auto request = std::make_shared<DetachSenseDeviceReq>( auto request = std::make_shared<DetachStimBuffDeviceReq>(
spec, caller, cb, libOpt.value(), spec, caller, cb, libOpt.value(),
LockSet<detachSenseDeviceReqCbFn>::Set{ LockSet<detachStimBuffDeviceReqCbFn>::Set{
std::ref(sense_api::SenseApiManager::getInstance().qutex), std::ref(stim_buff::StimBuffApiManager::getInstance().qutex),
std::ref(lib.qutex) std::ref(lib.qutex)
}); });
DetachSenseDeviceReq::LockerAndInvoker lockvoker( DetachStimBuffDeviceReq::LockerAndInvoker lockvoker(
*request, mrntt::mrntt.thread, *request, mrntt::mrntt.thread,
std::bind( std::bind(
&DetachSenseDeviceReq::detachSenseDeviceReq1_posted, &DetachStimBuffDeviceReq::detachStimBuffDeviceReq1_posted,
request.get(), request)); request.get(), request));
} }
@@ -623,10 +627,10 @@ void DeviceManager::attachAllUnattachedDevicesFromReq(
specs->size(), specs, caller, std::move(cb)); specs->size(), specs, caller, std::move(cb));
mrntt::mrntt.thread->getIoService().post( mrntt::mrntt.thread->getIoService().post(
std::bind( STC(std::bind(
&AttachAllUnattachedDevicesFromReq &AttachAllUnattachedDevicesFromReq
::attachAllUnattachedDevicesFromReq1_posted, ::attachAllUnattachedDevicesFromReq1_posted,
request.get(), request)); request.get(), request)));
} }
void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq( void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq(
@@ -711,7 +715,7 @@ void DeviceManager::attachAllUnattachedDevicesFromKnownListReq(
) )
{ {
const auto& caller = ComponentThread::getSelf(); const auto& caller = ComponentThread::getSelf();
auto request = std::make_shared<AttachAllUnattachedDevicesFromKnownListReq>( auto request = std::make_shared<AttachAllUnattachedDevicesFromKnownListReq>(
caller, cb, caller, cb,
LockSet<attachAllUnattachedDevicesFromReqCbFn>::Set{ LockSet<attachAllUnattachedDevicesFromReqCbFn>::Set{
@@ -746,7 +750,7 @@ public:
{ {
for (const auto& deviceRole : DeviceManager::attachedDeviceRoles) for (const auto& deviceRole : DeviceManager::attachedDeviceRoles)
{ {
DeviceManager::getInstance().detachSenseDeviceReq( DeviceManager::getInstance().detachStimBuffDeviceReq(
deviceRole->deviceAttachmentSpec, deviceRole->deviceAttachmentSpec,
{context, std::bind( {context, std::bind(
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles2, &DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles2,
@@ -804,9 +808,9 @@ void DeviceManager::detachAllAttachedDeviceRoles(
caller, std::move(cb)); caller, std::move(cb));
mrntt::mrntt.thread->getIoService().post( mrntt::mrntt.thread->getIoService().post(
std::bind( STC(std::bind(
&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted, &DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted,
request.get(), request)); request.get(), request)));
} }
void DeviceManager::initializeDeviceReattacher() void DeviceManager::initializeDeviceReattacher()
@@ -3,6 +3,7 @@
#include <functional> #include <functional>
#include <componentThread.h> #include <componentThread.h>
#include <callback.h> #include <callback.h>
#include <asynchronousBridge.h>
#include <deviceManager/deviceReattacher.h> #include <deviceManager/deviceReattacher.h>
#include <deviceManager/deviceManager.h> #include <deviceManager/deviceManager.h>
@@ -35,6 +36,24 @@ void DeviceReattacher::stop()
{ {
shouldContinue.store(false); shouldContinue.store(false);
timer.cancel(); timer.cancel();
// Set up a timeout bridge using the provided ioThread's io_service
auto& ioService = ioThread->getIoService();
boost::asio::deadline_timer timeoutTimer(ioService);
AsynchronousBridge bridge(ioService);
// Set up the timeout for ~10ms
timeoutTimer.expires_from_now(boost::posix_time::milliseconds(20));
timeoutTimer.async_wait(
[&bridge](const boost::system::error_code& error)
{
(void)error;
// Always signal complete, whether timeout expired or was cancelled
bridge.setAsyncOperationComplete();
});
bridge.waitForAsyncOperationCompleteOrIoServiceStopped();
} }
void DeviceReattacher::scheduleNextTimeout() void DeviceReattacher::scheduleNextTimeout()
+61
View File
@@ -0,0 +1,61 @@
#include <iostream>
#include <director/director.h>
#include <goal.h>
namespace smo {
namespace director {
void Director::negtrinEventInd(void)
{
/** EXPLANATION:
* The essence of a negtrin event, to Director is that it generates a new Goal
* object and enqueues it onto the Director's negtrins queue. It's this auto-goal
* generation that gives negtrins their intrinsic undesirability.
*
* We don't sample the negtrin, evaluate it and then conclude that it's
* undesirable. We don't even produce a negative value judgment. We skip
* right past both the evaluation and the value judgment production and
* just generate the goal immediately.
*
* I'm unsure whether this is correct: it may well be that we ought to
* simply produce a negative value judgment and then let the Director
* create a goal to alleviate the negtrin.
*
* At any rate, for now, this is our implementation.
*/
std::cout << __func__ << ": Handling negtrin event." << std::endl;
}
void Director::intrinEventInd(void)
{
std::cout << __func__ << ": Handling intrin event." << std::endl;
}
void Director::postrinEventInd(void)
{
/** EXPLANATION:
* When a postrin event occurs, a goal is auto-generated, but this goal is
* a bit different from the goals that are auto-generated for negtrins.
*
* A negtrin's goal is to either: get to 0; reduce the negtrin below its
* intolerable threshold; or reduce it somewhat even if not below the
* tolerable threshold. This is very easy to represent as a cologex.
*
* A postrin's goal is to: persist the postrin indefinitely; and increase
* its intensity if possible; and to store it away as something worth
* re-triggering if some external distractor/frustrator interrupts the
* persistent sampling of the postrin.
*
* I can think of how to encode the negtrin goal as a cologex, but I can't
* think of how to encode the postrin goal as a cologex.
*
* With respect to the "store away for future re-triggering" aspect of the
* postrin goal we can encode this by merely refusing to remove any postrin
* goal from the goal prioQ. I suppose negtrins differ in that we do remove
* them from the goal prioQ when they're resolved.
*/
std::cout << __func__ << ": Handling postrin event." << std::endl;
}
} // namespace director
} // namespace smo
+25
View File
@@ -0,0 +1,25 @@
#ifndef BOOST_ASIO_LINKAGE_FIX_H
#define BOOST_ASIO_LINKAGE_FIX_H
#include <boost/asio/detail/call_stack.hpp>
#include <boost/asio/detail/thread_context.hpp>
#include <boost/asio/detail/tss_ptr.hpp>
namespace boost {
namespace asio {
namespace detail {
/** EXPLANATION:
* Extern declaration of the template instantiation
* This ensures that the .o translation units don't have their
* own copies of `call_stack<>::top_` defined in them.
*/
extern template
tss_ptr<call_stack<thread_context, thread_info_base>::context>
call_stack<thread_context, thread_info_base>::top_;
} // namespace detail
} // namespace asio
} // namespace boost
#endif // BOOST_ASIO_LINKAGE_FIX_H
+3 -21
View File
@@ -1,30 +1,12 @@
#ifndef _CHRONOMENON_H #ifndef _CHRONOMENON_H
#define _CHRONOMENON_H #define _CHRONOMENON_H
#include <vector> namespace smo {
#include <qualeBundle.h>
#include <mentalEntity.h>
class Chronomenon class Chronomenon
: public MentalEntity
{ {
public:
class Timestamp
{
uintptr_t value;
};
class Duration
{
uintptr_t value;
};
public:
Chronomenon extract(Timestamp start, Duration len);
public:
std::vector<QualeBundle> qualia;
}; };
#endif } // namespace smo
#endif // _CHRONOMENON_H
-20
View File
@@ -34,26 +34,6 @@ public:
Mind &parent; 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 } // namespace smo
#endif // COMPONENT_H #endif // COMPONENT_H
+3 -1
View File
@@ -1,10 +1,11 @@
#ifndef COMPONENT_THREAD_H #ifndef COMPONENT_THREAD_H
#define COMPONENT_THREAD_H #define COMPONENT_THREAD_H
#include <boostAsioLinkageFix.h>
#include <atomic> #include <atomic>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include <boost/asio.hpp> #include <boost/asio/io_service.hpp>
#include <stdexcept> #include <stdexcept>
#include <queue> #include <queue>
#include <functional> #include <functional>
@@ -48,6 +49,7 @@ public:
boost::asio::io_service& getIoService(void) { return io_service; } boost::asio::io_service& getIoService(void) { return io_service; }
static const std::shared_ptr<ComponentThread> getSelf(void); static const std::shared_ptr<ComponentThread> getSelf(void);
static bool tlsInitialized(void);
static std::shared_ptr<MarionetteThread> getMrntt(); static std::shared_ptr<MarionetteThread> getMrntt();
typedef void (mainFn)(ComponentThread &self); typedef void (mainFn)(ComponentThread &self);
@@ -0,0 +1,105 @@
#ifndef _COMPUTE_MANAGER_H
#define _COMPUTE_MANAGER_H
#include <memory>
#include <vector>
#include <user/compute.h>
namespace smo {
namespace compute {
/**
* @brief Centralized OpenCL platform and device management
*
* Enumerates all OpenCL platforms and devices, maintains contexts and command
* queues, and provides methods to create buffers and access devices.
*/
class ComputeManager
{
public:
static ComputeManager& getInstance()
{
static ComputeManager instance;
return instance;
}
/**
* @brief Initialize ComputeManager by enumerating platforms and devices
*
* Enumerates all OpenCL platforms, then all devices on each platform,
* creating contexts and command queues for each device.
* Idempotent - can be called multiple times safely.
*/
void initialize();
/**
* @brief Finalize ComputeManager, releasing all resources
*
* Releases all contexts, command queues, and clears device list.
* Safe to call even if not initialized.
*/
void finalize();
/**
* @brief Create USE_HOST_PTR buffers on all contexts
*
* Creates a buffer using CL_MEM_USE_HOST_PTR on each device's context.
*
* @param hostPtr Host pointer to use
* @param size Size of buffer in bytes
* @param flags Additional OpenCL memory flags
* @return Shared pointer to ClBuffer managing buffers on all devices
*/
std::shared_ptr<ClBuffer> createUseHostPtrBuffer(
void* hostPtr, size_t size, cl_mem_flags flags);
/**
* @brief Release USE_HOST_PTR buffers
*
* Releases all buffers. This is a no-op since ClBuffer's destructor
* handles cleanup automatically.
*
* @param buffer Shared pointer to ClBuffer to release
*/
void releaseUseHostPtrBuffer(std::shared_ptr<ClBuffer> buffer);
/**
* @brief Get a compute device
*
* Returns the first available device. Later will accept
* ComputeDeviceConstraints for filtering.
*
* @return Shared pointer to ComputeDevice, or nullptr if no devices available
*/
std::shared_ptr<ComputeDevice> getDevice();
/**
* @brief Release a compute device
*
* Placeholder for future refcounting implementation.
* Currently a no-op - devices are only removed in finalize().
*
* @param device Shared pointer to ComputeDevice to release
*/
void releaseDevice(std::shared_ptr<ComputeDevice> device);
private:
ComputeManager() : initialized(false) {}
~ComputeManager() {}
// Non-copyable, non-movable
ComputeManager(const ComputeManager&) = delete;
ComputeManager& operator=(const ComputeManager&) = delete;
ComputeManager(ComputeManager&&) = delete;
ComputeManager& operator=(ComputeManager&&) = delete;
bool initialized;
std::vector<std::shared_ptr<ComputeDevice>> devices;
};
} // namespace compute
} // namespace smo
#endif // _COMPUTE_MANAGER_H
+4 -41
View File
@@ -1,51 +1,14 @@
#ifndef _CONCEPT_H #ifndef _CONCEPT_H
#define _CONCEPT_H #define _CONCEPT_H
#include <vector>
#include <memory>
#include <logic.h>
#include <mentalEntity.h>
namespace smo { namespace smo {
namespace concepts { namespace cologex {
class Comparator class Concept
: public MentalEntity, public logic::Operand
{ {
public:
/** EXPLANATION:
* The reference for a Comparator is the fixed mentity or range of mentities
* that this comparator is intended to validate a match against.
*
* There are several mentities against which a comparator can match. At the
* time of writing, we're fairly sure that these will be at minimum,
* qualia, chronomena and mentena.
*/
std::shared_ptr<MentalEntity> reference;
}; };
class ComparatorExpression } // namespace cologex
: public logic::UnaryExpression
{
public:
ComparatorExpression(
logic::Operator &op, std::shared_ptr<Comparator> &comparator
)
: logic::UnaryExpression(
op, std::static_pointer_cast<logic::Operand>(comparator))
{}
};
class CombinatorialLogicExpression
: public MentalEntity, public logic::Expression
{
public:
};
typedef CombinatorialLogicExpression Concept;
} // namespace concept
} // namespace smo } // namespace smo
#endif #endif // _CONCEPT_H
@@ -61,15 +61,15 @@ public:
Callback<removeDeviceAttachmentSpecReqCbFn> callback); Callback<removeDeviceAttachmentSpecReqCbFn> callback);
// Device attachment/detachment methods moved from SenseApiManager // Device attachment/detachment methods moved from SenseApiManager
typedef sense_api::sal_mlo_attachDeviceReqCbFn attachSenseDeviceReqCbFn; typedef stim_buff::sal_mlo_attachDeviceReqCbFn attachStimBuffDeviceReqCbFn;
typedef sense_api::sal_mlo_detachDeviceReqCbFn detachSenseDeviceReqCbFn; typedef stim_buff::sal_mlo_detachDeviceReqCbFn detachStimBuffDeviceReqCbFn;
void attachSenseDeviceReq( void attachStimBuffDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec, const std::shared_ptr<DeviceAttachmentSpec>& spec,
Callback<attachSenseDeviceReqCbFn> cb); Callback<attachStimBuffDeviceReqCbFn> cb);
void detachSenseDeviceReq( void detachStimBuffDeviceReq(
const std::shared_ptr<DeviceAttachmentSpec>& spec, const std::shared_ptr<DeviceAttachmentSpec>& spec,
Callback<detachSenseDeviceReqCbFn> cb); Callback<detachStimBuffDeviceReqCbFn> cb);
typedef std::function<void(AsynchronousLoop &results)> typedef std::function<void(AsynchronousLoop &results)>
attachAllUnattachedDevicesFromReqCbFn; attachAllUnattachedDevicesFromReqCbFn;
@@ -108,8 +108,8 @@ private:
class NewDeviceAttachmentSpecInd; class NewDeviceAttachmentSpecInd;
class RemoveDeviceAttachmentSpecReq; class RemoveDeviceAttachmentSpecReq;
class AttachSenseDeviceReq; class AttachStimBuffDeviceReq;
typedef AttachSenseDeviceReq DetachSenseDeviceReq; typedef AttachStimBuffDeviceReq DetachStimBuffDeviceReq;
class AttachAllUnattachedDevicesFromReq; class AttachAllUnattachedDevicesFromReq;
class AttachAllUnattachedDevicesFromKnownListReq; class AttachAllUnattachedDevicesFromKnownListReq;
class DetachAllAttachedDeviceRoles; class DetachAllAttachedDeviceRoles;
@@ -1,9 +1,10 @@
#ifndef DEVICEREATTACHER_H #ifndef DEVICEREATTACHER_H
#define DEVICEREATTACHER_H #define DEVICEREATTACHER_H
#include <boostAsioLinkageFix.h>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <boost/asio.hpp> #include <boost/asio/deadline_timer.hpp>
namespace smo { namespace smo {
+4
View File
@@ -23,6 +23,10 @@ public:
~Director() = default; ~Director() = default;
void negtrinEventInd(void);
void intrinEventInd(void);
void postrinEventInd(void);
/** EXPLANATION: /** EXPLANATION:
* We allow SMO to prioritize negtrins over injected goals, so that it can * We allow SMO to prioritize negtrins over injected goals, so that it can
* prioritize pain mitigation. We may decide to change this in the future. * prioritize pain mitigation. We may decide to change this in the future.
+4 -2
View File
@@ -2,11 +2,13 @@
#define _GOAL_H #define _GOAL_H
#include <concept.h> #include <concept.h>
#include <mentalEntity.h>
namespace smo { namespace smo {
class Goal class Goal
: public concepts::Concept { : public cologex::Concept, public MentalEntity
{
public: public:
Goal() = default; Goal() = default;
~Goal() = default; ~Goal() = default;
@@ -14,4 +16,4 @@ public:
} // namespace smo } // namespace smo
#endif #endif // _GOAL_H
-65
View File
@@ -1,65 +0,0 @@
#ifndef _LOGIC_H
#define _LOGIC_H
#include <memory>
#include <vector>
namespace smo {
namespace logic {
class ExpressionPart
{
};
class Operator
: public ExpressionPart
{
};
class OperatorAnd
: public Operator
{
};
class OperatorOr
: public Operator
{
};
class OperatorNot
: public Operator
{
};
class Operand
: public ExpressionPart
{
};
class UnaryExpression
: public ExpressionPart
{
public:
UnaryExpression(Operator &op, const std::shared_ptr<Operand> &operand)
: parts(std::make_pair(op, operand))
{}
public:
std::pair<Operator, std::shared_ptr<Operand>> parts;
};
// Expressions can be chained as parts of a larger expression
class Expression
: public ExpressionPart
{
public:
// This will eventually take in some data to be evaluated for a match.
virtual bool evaluate(void) = 0;
public:
std::vector<std::shared_ptr<ExpressionPart>> parts;
};
} // namespace logic
} // namespace smo
#endif
-1
View File
@@ -20,7 +20,6 @@ namespace smo {
* dropped. So we may very well literally forget those qualia that get dropped * dropped. So we may very well literally forget those qualia that get dropped
* from the LruLifos. (Because LruLifos have a fixed size.) * from the LruLifos. (Because LruLifos have a fixed size.)
*/ */
class LruLifo { class LruLifo {
public: public:
LruLifo() = default; LruLifo() = default;
+19 -1
View File
@@ -4,7 +4,6 @@
#include <cstdint> #include <cstdint>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <componentThread.h>
#include <component.h> #include <component.h>
namespace smo { namespace smo {
@@ -13,6 +12,25 @@ class MarionetteThread;
namespace mrntt { 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);
// Intentionally doesn't take a callback.
void exceptionInd();
private:
class MrnttLifetimeMgmtOp;
class TerminationEvent;
};
extern std::atomic<int> exitCode; extern std::atomic<int> exitCode;
void exitMarionetteLoop(); void exitMarionetteLoop();
void marionetteFinalizeReqCb(bool success); void marionetteFinalizeReqCb(bool success);
+3 -2
View File
@@ -11,7 +11,8 @@
class OptionParser class OptionParser
{ {
public: public:
OptionParser() : verbose(false), printUsage(false) {} OptionParser() : verbose(false), printUsage(false), traceCallables(false)
{}
~OptionParser() = default; ~OptionParser() = default;
void parseArguments(int argc, char *argv[], char **envp); void parseArguments(int argc, char *argv[], char **envp);
@@ -38,7 +39,7 @@ public:
std::vector<std::string> senseApiLibs; std::vector<std::string> senseApiLibs;
std::string dapSpecs; std::string dapSpecs;
std::vector<std::string> dapSpecFiles; std::vector<std::string> dapSpecFiles;
bool verbose, printUsage; bool verbose, printUsage, traceCallables;
static struct option longOptions[]; static struct option longOptions[];
}; };
+31
View File
@@ -0,0 +1,31 @@
#ifndef _PHENO_FRAME_H
#define _PHENO_FRAME_H
#include <vector>
#include <chronomenon.h>
#include <mentalEntity.h>
#include <user/stimFrame.h>
namespace smo {
class PhenoFrame
: public Chronomenon, public MentalEntity
{
public:
/** FIXME:
* May be better to use a std::map here, where the key is a kind of ID
* assigned to the stimulus source.
*/
std::vector<stim_buff::StimFrame> stimuli;
};
class PhenoSeq
: public Chronomenon, public MentalEntity
{
public:
std::vector<std::pair<stim_buff::SimultaneityStamp, PhenoFrame>> frames;
};
} // namespace smo
#endif // _PHENO_FRAME_H
-16
View File
@@ -1,16 +0,0 @@
#ifndef _QUALE_BUNDLE_H
#define _QUALE_BUNDLE_H
#include <config.h>
#include <array>
#include <quale.h>
#define CONFIG_NUM_SENSORS 5
typedef std::array<Quale, CONFIG_NUM_SENSORS> QualeBundle_t;
class QualeBundle
{
QualeBundle_t qualia;
};
#endif

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