552 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

They currently get to the point of detecting my Livox Avia on the
network over UDP. This was really easy to get done in one night
using boost::asio and Cursor, honestly.
2025-09-06 22:46:03 -04:00
hayodea f00e1c7cf1 Formatting 2025-09-06 22:46:03 -04:00
hayodea 26be261cff SenseApiDesc: Rename SalmanoffCallbacks=> SmoCallbacks 2025-09-06 22:46:03 -04:00
hayodea e5a3c41c20 SenseApis: Add threading model info to initialization info
We provide access to a thread whose event queue the sense API
libs can use for device-independent event management.
2025-09-06 22:46:03 -04:00
hayodea 1d9b8a2cf6 Todo: Add note to rename struct 2025-09-06 22:46:03 -04:00
hayodea 4ce4c9f9f8 DAP Specs: Add specs for LivoxGen1 devices 2025-09-06 22:46:03 -04:00
hayodea fe5a7d480d CMake: tried to get project building in Clang. Failed. 2025-09-06 22:45:25 -04:00
hayodea 83273ae806 Bodies:dev: avia: don't set default subnet to 192.168.1.0 2025-09-06 22:06:37 -04:00
hayodea 04a3631881 Bodies:dev: Add DAPS files for test window and Avia 2025-09-06 22:02:12 -04:00
hayodea 0559d9ea42 Build: Update CMake project version: v0.00.004 2025-09-03 15:30:30 -04:00
hayodea 1c78fc5c31 Cmdline: Update specs to mention DAP 2025-09-03 15:28:58 -04:00
hayodea 3b224501c7 VSCode: Add launch tasks for debugging in vscode 2025-09-03 14:56:49 -04:00
hayodea 0dc8abaa28 Rework: Modularize Mind
Now we have modularized the Mind class to contain all of its
ComponentThreads. This enables us to run multiple mind instances
within the same SMO process, at least in theory.

We probably won't actually do this, but we want to ensure that the
design is clean enough to enable it.
2025-09-03 14:56:00 -04:00
hayodea eb069c4a96 LRU-LIFO: Add Lufos, add LUFOs to Director
This represents our realization that we can represent qualia
inputs using LRU LIFOs
2025-09-02 17:02:50 -04:00
hayodea 32e76c2ca5 Goal: Goals are now Comblogic exprs 2025-09-02 17:01:47 -04:00
hayodea 4827177703 Add combinational logic and concept classes 2025-09-02 17:01:01 -04:00
hayodea d36d03dcc3 Logic/Concepts.h: Add combinational logic expr classes 2025-09-02 13:04:54 -04:00
hayodea 6362298016 Rename senseApiXcbWindow=>xcbWindow 2025-08-29 20:24:04 -04:00
hayodea 6314b0182a VSCode config: indentation should be 4-width tabs 2025-08-29 20:23:44 -04:00
hayodea 0fefa5be7f Add synchronization notes for queuespin mechanism 2025-08-29 19:59:22 -04:00
hayodea 7ea31cdb8d DAPSpecParser: Get rid of this warning 2025-08-29 17:52:39 -04:00
hayodea d217354689 Add our first async thread sequence
Gave me some ideas about how things should be structured. Apparently
merely using region-data-locked threads doesn't eliminate the need
for synchronization/locking. It just means your synchronization is much
lighter, in the form of localized variables.

It seems we'll need to maintain boolean trackers for certain
operations that shouldn't be performed concomitantly, and deny
the caller access to those operations in order to preserve
data sanity.

I guess we still ended up using locking after all. Tbh, I'm not even
sure this will make things end up being lighter: we may have to bounce
requests off, or perhaps re-enqueue them into the queue?

So maybe instead of bouncing requests off, we could re-add them to the
rear of the queue when they conflict with an ongoing request.
2025-08-29 17:42:13 -04:00
hayodea bb7be7fb3c Rename: senseDeviceSpecs => deviceAttachmentSpecs 2025-08-29 16:33:17 -04:00
hayodea ee8fca3008 Add todo 2025-08-29 16:13:03 -04:00
hayodea 525af45fd4 VSCode config: use 8-width tabs 2025-08-29 16:12:47 -04:00
hayodea 3ff329a553 Rename: DeviceSpec=>DAP Spec 2025-08-29 16:12:30 -04:00
hayodea 66257bcd0e Rename DSL: DeviceAttachmentSpec => deviceAttachmentPipeSpec
The new name is nicer and more distinguishing.
2025-08-29 15:55:11 -04:00
hayodea 6ef86eea05 Rename DSL: deviceSpec => deviceAttachmentSpec
This language is used broadly to specify how to attach (and thus
also how to detach) devices to/from Salmanoff. The next bit of work
we'll do is split off the DSL parsing from the management of the
list of parsed binary attached spec objects.

We'll be creating a PipeDeviceAttachmentParser, and later on when
we support URDF, we'll create a URDFDeviceAttachmentParser.
2025-08-29 15:16:11 -04:00
hayodea 8f41e164a2 Rename readDeviceFile=>readDeviceSpecFile 2025-08-29 13:29:18 -04:00
hayodea b9ca38bff1 Make senseApiLibs a vector<sh_ptr>; getters return sh_ptr
Proper reference and object lifetime management.
2025-08-29 13:20:15 -04:00
hayodea e024ccdf95 Add todo 2025-08-29 12:51:52 -04:00
hayodea f3f2384f9b SenseApiMgr: Refcount device spec objects 2025-08-29 09:50:26 -04:00
hayodea cd63593ae5 CMake: Add support for cross compiling to aarch64-linux-gnu 2025-08-23 20:59:39 -04:00
hayodea 58dbc7e320 Gitignore: Ignore all build dirs 2025-08-23 19:38:52 -04:00
hayodea e3795c4233 Fix includes to use angle brackets 2025-08-15 14:01:46 -04:00
hayodea edc198dd00 Document the purpose and mechanics behind JOLTing 2025-08-15 13:16:23 -04:00
hayodea 2bf7390f97 Rename implexa.h => mentenon.h 2025-08-15 09:43:38 -04:00
hayodea 1ea1b4b9f6 Fix typo 2025-08-15 09:41:34 -04:00
hayodea c14208f6ce Ignore /build dir 2025-08-15 09:38:43 -04:00
hayodea f4ff8f0e40 Delete superfluous files 2025-08-15 09:36:24 -04:00
hayodea ba3841c30b Rename classes from MentalExistent=>MentalPhenomenon 2025-08-13 16:06:34 -04:00
hayodea 4a8cb12294 Mrntt: initializeSalmanoff exceptions handled in outermode catch block
We don't post a message to the event loop telling it to execute
initializeSalmanoff anymore. We now execute it in the main control
flow.

Also, we've unified the logic to call finalizeReq() in response to
exceptions in the outermost try block.
2025-08-13 10:03:18 -04:00
hayodea b6b2ce7ada Cmdline: use exceptions for control flow
This is generally frowned upon but it makes this code 10x cleaner.
We handle commandLine usage msg printing by using exceptions for
control flow. This allows us to centralize the logic for killing
the Mind threads in one place. At least with respect to printing
the usage msg.
2025-08-13 09:49:54 -04:00
hayodea 7bee9b07ae Mrntt: Don't call Mind::initialize after exiting loop. 2025-08-13 09:08:57 -04:00
hayodea d26b791dd2 Handle SIGINT (CTL+C) by gracefully shutting down 2025-08-10 14:07:27 -04:00
hayodea 7579446388 Mrntt: set Mrntt::exitCode = EXIT_FAILURE for all exceptions 2025-08-10 13:40:13 -04:00
hayodea 099d60bcc4 Moved JOLT state tracking into ComponentThread:: 2025-08-10 13:29:34 -04:00
hayodea 42b32f27e6 Remove test exception 2025-08-10 13:17:11 -04:00
hayodea c457ee7aca smo::Mind instance now global; track & manage JOLT state in Mind
We moved the instance of smo::Mind to global scope. I suppose we'll
only support one instance of Mind per SMO process at least for now.

We now track the state of Mind threads' JOLT-waiting. This allows us
to centralize the Mind thread shutdown logic. Mind::finalizeReq()
now takes care of all Mind thread shutdown state logic by tracking
whether Mind threads need to be JOLTed first before being told to
exit.
2025-08-10 13:12:17 -04:00
hayodea e2e589dc17 ExceptionInd: Remove duplicate invocations 2025-08-03 10:33:34 -04:00
hayodea d4898bbca1 Add comments 2025-08-03 10:22:28 -04:00
hayodea e836b2bf32 Cmdline: Suppress getopt stderr msg, bubble exception upward instead 2025-08-03 10:19:22 -04:00
hayodea fef73692f7 Mrntt: Shut down mind threads before printing usage & exiting 2025-08-03 09:57:29 -04:00
hayodea 285b63b618 Mind: Distribute and pin Mind threads to CPUs
At startup, Marionette will distribute and pin the Mind
threads across the available CPUs, warning if it couldn't
do so.
2025-08-03 09:18:45 -04:00
hayodea 1deb92a416 CompThreads: create execOpOnAllMindThreads common helper
This allows us to execute an op on all mind threads without having
to repeatedly write loops. We've implemented wrappers to handle
start, pause, resume, exit and JOLT sequences.
2025-08-03 08:22:45 -04:00
hayodea 6f6fa77498 Get rid of enum Quale::Type, create BoundingQuale; use typeid()
We just learned about typeid. Apparently it was either not mentioned
in the Stroupstrup book or we totally missed it.
2025-08-03 05:23:05 -04:00
hayodea 79825e4da3 Make Quale derive from MentalExistent. 2025-08-03 05:06:14 -04:00
hayodea 6114a2648d Add MentalExistent hierarchy of classes.
These differ from the MentalEntity class hierarchy in that
MentalExistents are a narrower subset of MentalEntities. MentalEntities
refer to all mental content that needs to be persistently stored to
represent cognitive and personna state.

MentalExistents are content of the mind that specifically represents
existents. I.e: perceptual data, whether structural or implicative.
2025-08-03 05:06:14 -04:00
hayodea 4a3daaf403 Update README.md 2025-08-02 01:26:06 +00:00
hayodea 49c8b5bca1 SenseApiMgr: Print dev info when detaching, use early return pattern 2025-07-30 10:33:50 -04:00
hayodea a2598e80fd Mrntt: Call mind.finalize() on mind object. 2025-07-30 10:14:18 -04:00
hayodea 4fde28dad8 Mrntt: Call shutdownSalmanoff after exiting main loop
We moved initializeSalmanoff and shutdownSalmanoff into
salmanoff.cpp. Now we also invoke shutdownSalmanoff when exiting
to destroy subsystems and components gracefully.

This fixes the segfault that was thrown on every program exit
when xcbWindow had captured a window.
2025-07-30 10:09:25 -04:00
hayodea e276fcbdce Mrntt: use keepLooping after exception; Mind: split code from mrntt
Mrntt now has the event loop structure required to orderly shut itself
down when it itself generates an exception. We can now post a message
within the catch{} blocks for Mrntt's event loop, telling Mrntt
to shut down the Mind threads and then shut itself down.

We also split the code to initialize threads etc out of mrntt and
put it into the Mind:: namespace.
2025-07-30 09:09:38 -04:00
hayodea 36c79f3a2e Threading: run all code in PThreads, add JOLTing & exception bubbling
This commit significantly restructures the way we setup threading in
SMO. We now don't use the CRT main() thread at all. It's only used
as a mechanism to ensure that Marionette doesn't execute before
global constructors have been executed.

JOLTing:

This is a simple ASIO post()ed message that makes each thread setup
its thread-local data pointer to its own ComponentThread object,
and then enter its main ASIO run() loop to await commands from
Marionette.

Exception bubbling:

We now cleanly cause mind threads to report their exceptions
to marionette, so that marionette can cleanly shut the mind down
in an orderly fashion.

Thread Control messaging API:

A namespace of asynchronous messages to be post()ed to threads to
control them. It enables us to pause and resume threads. This will
be very useful for Marionette when we add the ability for it to
suspend Salmanoff's running mind, inject new goals, inspect current
state, etc; and then resume the mind's execution.
2025-07-28 07:20:44 -04:00
hayodea 513405a831 Cmdline: Rename senseApiLibs, senseApiPaths to apiLibs, apiLibPaths 2025-07-25 01:51:36 -04:00
hayodea 7b962a75d3 VSCode: update config 2025-07-25 01:22:13 -04:00
hayodea 270437fdd4 xcbOrg/Window: Destroy connections when no longer in use 2025-07-25 01:21:26 -04:00
hayodea a17c940377 xcbWindow: Use angle bracketed includes 2025-07-24 06:15:09 -04:00
hayodea b28239550e xcbWindow: Group xcbXorg DLL state together 2025-07-24 06:14:08 -04:00
hayodea 1e17b83061 Split xcbXorg into xcbXorg and xcbWindow 2025-07-24 06:00:35 -04:00
hayodea 1bf5f46404 Provide dlopen() path searching hook to senseApi libs 2025-07-24 02:12:31 -04:00
hayodea 064dc43fbc SenseApiMgr: Permit multiple lib search paths 2025-07-23 00:12:50 -04:00
hayodea 020a4968e5 CMake: Add config.h.in 2025-07-22 23:28:59 -04:00
hayodea d0aa8e2306 Rename hk=>smo 2025-07-22 06:48:04 -04:00
hayodea 79f3e84ff8 CMake: update include path here? 2025-07-22 06:17:18 -04:00
hayodea 756571b9b4 Rename hcore=>smocore 2025-07-22 06:15:12 -04:00
hayodea 9c16aeeb55 Docs: rename harikoff=>salmanoff 2025-07-22 06:03:47 -04:00
hayodea d6a0b0301e Build: Rename Harikoff=>Salmanoff 2025-07-22 06:00:00 -04:00
hayodea e87656fd12 Fix build error from non-POD initializer list 2025-07-22 05:50:19 -04:00
hayodea f06aeb6c9b Formatting 2025-07-22 05:48:39 -04:00
hayodea c58d422158 Add images 2025-07-22 05:47:33 -04:00
hayodea 8dfb1e5b2f Use smaller PNG 2025-07-22 05:46:02 -04:00
hayodea 50b8aaf34d Update README for logo 2025-07-22 05:40:51 -04:00
hayodea 0f5e499d7c Add new logo for Salmanoff 2025-07-22 05:39:19 -04:00
hayodea 710749c399 Delete the autotools build system 2025-07-22 05:28:42 -04:00
hayodea 3503cce0db CMake: Check for libDl, conditionally check for libXCB 2025-07-22 05:19:26 -04:00
hayodea 90a0eebdd8 Add cscope ignore to .gitignore 2025-07-22 03:08:50 -04:00
hayodea 1540af1e74 Add temporary gitignore for the old autotools build dir 2025-07-22 02:46:43 -04:00
hayodea 065b2593f4 Add new CMake build system 2025-07-22 02:46:16 -04:00
hayodea e7974db324 Add Marionette 2025-07-22 02:03:45 -04:00
hayodea 336094ef90 Marionette: Create include dir and move body map headers in 2025-07-22 02:03:45 -04:00
hayodea d43a8af6bd Build: Print out the enabled common and wilzor libs 2025-07-22 02:03:45 -04:00
hayodea 37ad6995c3 Build: Add SenseApi DeepLIO2 Lidar fusion algo 2025-07-22 02:03:45 -04:00
hayodea c9e8a9f1fb Build: senseApis: Update help messages 2025-07-22 02:03:45 -04:00
hayodea dacc050bf9 Livox and R3Live shouldn't be over Eth intrinsically 2025-07-22 02:03:45 -04:00
hayodea f8825942b1 Add senseApis for Lidar devs and fusion APIs 2025-07-22 02:03:45 -04:00
hayodea e201b5e695 Build: Support common+sense+wilzor API libs; Move xcbXorg into commonLibs
xcbXorg is a connection manager lib that'll be used in common by all of
the xcb API frontends: xcbMouse, xcbWindow and xcbKeyboard. We moved
it into commonLibs to make it make more sense.

We also cleaned up the M4 scripting around AC_ARG_VAR-ing new common
libs as well as sense/wilzor libs.
2025-07-22 02:03:45 -04:00
hayodea 29a1e1ecf2 Docs:negtrin path: Add new thoughts 2025-07-22 02:03:45 -04:00
hayodea a5c2f47e9f Add design thoughts for intrins, compartmentalization, value-judgment 2025-07-22 02:03:45 -04:00
hayodea 42f55bb324 SenseApiDesc: Delete Sal_Mgmt_HkOps & document iface
We don't need the Sal_Mgmt_HkOps anymore because we won't
be using a callback model anymore. We'll be enqueuing messages.
2025-07-22 02:03:45 -04:00
hayodea 1450d745ab SenseApiDesc: use CPP concat to reduce typos 2025-07-22 02:03:45 -04:00
hayodea 76141e3a92 Fix indentation 2025-07-22 02:03:45 -04:00
hayodea 36592293dd DevSpec:lex: fix indentation 2025-07-22 02:03:45 -04:00
hayodea f1696f8272 DevSpec:lex: Remove annotation comment by GPT 2025-07-22 02:03:45 -04:00
hayodea 99c126a08c DevSpec:lex: Fix whitespace around EQUALS, fix backslash at EOF
* We had a prior issue where EQUALS would require that there be no
  whitespace between itself and its operands on either side. We got
  a bad solution from ChatGPT 4o. We got a proper solution now from
  o1.
* Previously, if a string ended with a backslash right before EOF, the
  backslash would be included. Now it will be dropped.
* Merge the two regexes for ignoring whitespace into one.
2025-07-22 02:03:45 -04:00
hayodea 293c1054d1 ComponentThreads: Add 2 new threads: body, world; comment threads.
We add 2 new threads for handling the interoceptors and extrospector
events. Also add comments explaining the purpose and role of each of
these major threads.
2025-07-22 02:03:45 -04:00
hayodea 38298a8ef8 DevSpec:Lex: silence warning about yyinput 2025-07-22 02:03:45 -04:00
hayodea 2b8b176038 xcbXorg: Add comments to major classes and funcs 2025-07-22 02:03:45 -04:00
hayodea 76e465bd1d devSpec:lex: Allow backslash escaping of strings
Now we can escape special characters without issue.
2025-07-22 02:03:45 -04:00
latentprion ead7d8ff5f Add findxwindow script 2025-07-21 23:24:53 -04:00
hayodea 9cc7a6685c Build: Add -Woverride with AM_INIT_AUTOMAKE 2025-01-31 14:45:52 -04:00
hayodea 8237cd62da xcbXorg: Rename api tag to "xcb", instead of "xcb-xorg" 2025-01-14 23:39:22 -04:00
hayodea 181759ff26 Docs:xcbXorg: Document apiParams, providerParams and devSelector
Add documentation that explains how to construct a devSpec for matching
and attaching windows from Xorg using the xcbXorg sense API lib.
2025-01-14 23:29:26 -04:00
hayodea 7b79636681 devSpec:yacc: Put param after params 2025-01-14 23:14:19 -04:00
hayodea 4dee8c62c9 devSpec:yacc: We now print out the current lex token string
We used some preprocessor logic to enable access to yytext and now
we can have verbose, useful error messages from the parser :)
2025-01-14 23:13:02 -04:00
hayodea 9a9f5058ed devSpec: allow backslash escaped whitespace in STRING tokens
This allows us to use spaces when specifying window name selectors.
Which is very convenient and cool.
2025-01-14 23:11:30 -04:00
hayodea ff56bfce04 xcbXorg: Indentation 2025-01-14 21:12:41 -04:00
hayodea 098b79b331 xcbXorg: Add new param keys: devsubstring, devstring
* devsubstring: substring match on window name.
* devstring: exact match on window name
2025-01-14 21:11:47 -04:00
hayodea 20154d1e95 xcbXorg: Use apiParams to choose match method for window attachment
We now use param keys in the API params to choose what type of ID
the deviceSelector holds, and how to match it.

* dev-id/devid: matches by ID.
* dev-substr/dev-substring/devsubstr: Matches window name by substring.
* dev-string/devstr/dev-str: matches window name by exact whole string.
2025-01-14 20:59:28 -04:00
hayodea 64baa7906b xcbXorg: Implement window search by ID and name
The name search doesn't quite seem to work, but we captured all 4
of our regularly active windows (including the browser!!!) using
window IDs.
2025-01-14 20:22:12 -04:00
hayodea d31530e0bd xcbXorg: indentation 2025-01-14 17:01:16 -04:00
hayodea a80db04dac xcbXorg: Replace display+screen with XConnectionIdentifier 2025-01-14 16:58:22 -04:00
hayodea 0a6f7feeca xcbXorg: Now properly connects to requested display *AND* screen
We append the desired screen number to the connection string.
2025-01-14 16:50:37 -04:00
hayodea 091d7ceeba xcbXorg: Parse devSpec params, connect to Xorg displays
This patch adds some nicely weighty code for connecting to X displays
and managing those connections. Attaching devices will automatically
connect to their required X display. Removing all devices dependent
on a given X display connection will also disconnect from that
Xdisplay.
2025-01-14 14:17:05 -04:00
hayodea cfdeb17639 SenseApiMgr: Add doxygen comment explaining the lib search priorities 2025-01-14 14:17:05 -04:00
hayodea aaae3dcbb2 DevSpec: Grammar now parses params as key[=[val]]
* Updated docs to reflect this.
* This was important in allowing us to write the xcbXorg connection
 code.
2025-01-14 14:17:05 -04:00
hayodea 4eb0ef75bc DevSpec: Yacc: Silence unused warning about yyunput() 2025-01-13 22:01:17 -04:00
hayodea 09caf314f1 Eliminate the C FFI; Namespace lib API and DeviceManager
We decided to get rid of the C FFI for libs. It was becoming too intricate
and complicated. It was becoming a technical burden and expanding into
too much extra code. It's unfortunate, but we'll have to give up on getting
out-of-tree hot-loadable libraries the easy way.

It's possible to still do it with cross compilation or by keeping track
of the libstdc++ version that the running harikoff binary was compiled
against. Then we can ensure that our loadable lib code is linked against
that same libstdc++ code and this should ensure ABI stability.
2025-01-13 21:57:11 -04:00
hayodea a4f96c8dfa Senselib FFI: Use devDesc object in SenseApiLib, also add per-device metadata to xcbXorg
We also had to write conversion constructors and so on to CSenseDeviceDesc.
The technical debt that's being piled up from this C FFI is excessive.
I think we'll end it here. API Libs will have to be written in C++
from now on. API Lib interfaces will be in C++.

We'll use cross compilers to ensure stability for out-of-tree lib
development.
2025-01-13 11:53:38 -04:00
hayodea 660f0f0e73 DevMgr: Make vectors hold sh_ptr and not uniq_ptr
This allows us to mirror the contents of the two lists into the unified
list much more easily.
2025-01-13 08:02:59 -04:00
hayodea edf51a4441 DevSpecp.yy: Fix mirror list use of std::move()d objects
We were building a list of mirrored items filled with the memory
pointed to by unique_ptrs which had already been std::move()d into
other unique_ptrs.
2025-01-13 07:06:28 -04:00
hayodea 8e94e829d0 SenseDevSpec: add NULL checks stringify, ostream::<< 2025-01-13 07:05:05 -04:00
hayodea 3f9b406fb2 Build: handle "" enableval in --enable-senseapi-xcbxorg 2025-01-12 14:36:27 -04:00
hayodea 0a36f7d370 Build: Add XCB_LIBS; Skeleton: mlo_initializeInd, mlo_attachDeviceReq
* Renamed some of the Sense API lib classes
  (CSensorDeviceDesc=>CSenseDeviceDesc,
  SensorDeviceDesc=>SenseDeviceDesc).
* Moved SenseApiDesc into /include/user/senseApiDesc.
* Add conversion constructor to convert from SenseDeviceDesc
  to
* Wireframe mlo_initializeInd to call xcb_connect().
* Add $(XCB_LIBS) to libxcbXorg_LDFLAGS.
* Wireframe mlo_attachDeviceReq().
2025-01-12 14:31:33 -04:00
hayodea b85d6f76a6 SenseApiMgr: add initialize/finalizeAllSenseApiLibs()
Also, SenseApiDesc: initializeInd() now takes void. We no longer
try to pass a struct of marionette-role ops into the libs. We'll
be using message queueing for the handshake side of async calls now.
2025-01-12 09:44:49 -04:00
hayodea c8a7a6678f Fix annoying build warning 2025-01-12 09:44:08 -04:00
hayodea c6577b1155 Add stringifiers to SenseApi related classes 2025-01-11 06:40:43 -04:00
hayodea 8aa28a877e Marionette: Post initializeHarikoff() as a lambda 2025-01-11 06:19:11 -04:00
hayodea bffc32519b ComponentThreads: now basics are working.
Next step is to get the unified event loops working generically
and then we can begin region-splitting up the data in harikoff.

We'll assign all the global resource managers to Marionette and
then assign the Mind components to the respective component threads.
2025-01-11 04:35:11 -04:00
hayodea 876526364b Build: Added -Wall,extra,-pedantic, fixed warnings and peeves. 2025-01-10 18:27:10 -04:00
hayodea ce2d47e6b9 Build: formatting: add m4 quotes 2025-01-10 17:41:17 -04:00
hayodea 870b8de249 Marionette: Introduce concept and add other 3 component threads
We introduce the 4 main component threads of execution for Harikoff:
* Marionette: This is the resident hijacking module that makes Harikoff
  instances non-persons, if configured to allow hijacking.
* Director: :)
* Canvas: :)
* Subconscious: DB, storage and recall.
2025-01-10 17:37:49 -04:00
hayodea 4a9d2cb546 xcbXorg: Update initializationIndFn func prototype 2025-01-10 17:36:39 -04:00
hayodea c696db9e45 Build: Require Boost.Asio to be v1.69.0+ 2025-01-10 17:35:22 -04:00
hayodea d2d5b8960f Build: PRefix project link with http:// 2025-01-09 18:34:58 -04:00
hayodea 49d03df73b Build: Add AX_BOOST_[BASE/ASIO].m4, check for BOOST_ASIO 2025-01-09 18:21:21 -04:00
hayodea 9e35748d9a Rename Csal_lmo=>Csal_mlo, and introduce a Csal_mho role
* Introduce the hk end of the management role for the senseApiLibs.
* Implement a basic set of operations and callbacks for this role.
2025-01-09 17:18:24 -04:00
hayodea 53583e5735 SenseApiLib, SenseApiDesc improvements, new Sense API Mgmt Lib Ops role
SenseApiDesc:
* Use a number count for num exported implexor APIs instead of
  NULL-terminated list.
* Add sanity checker functions for structs.

SenseApiLib:
* Invoke the new sanity checkers on new Lib objects.
* SenseApiDesc is now a member object instead of being
  pointed to.

SenseApiManager:
* loadSenseApiLib now calls the SenseApiDesc getter function.
* loadSenseApiLib now fills out the SenseApiLib class object.

New Sense API Mgmt Sub-API:

This sub-api (metalanguage, some might call it) is used to initialize
the lib's connection to the provider. After this call, the lib should
be ready to attach new devices to its provider on behalf of Hk.
2025-01-09 06:03:43 -04:00
hayodea 2a397ae064 Build: Check for libXCB when building XcbXorg sense API 2025-01-08 18:36:34 -04:00
hayodea 88df316013 build: Conditionally compile senseApis 2025-01-08 18:27:40 -04:00
hayodea 396bcefbf4 XcbXorg: Fill in correct placeholder implexor algo name 2025-01-08 18:00:07 -04:00
hayodea 62db724246 senseApiMgr: Fix formatting 2025-01-08 17:58:08 -04:00
hayodea bbbd6c36cd Release: Tagging v0.00.002:
* Loadable sense API libraries.
2025-01-08 17:34:33 -04:00
hayodea d14cef5328 Move senseApi libs into their own outer subdir 2025-01-08 17:28:21 -04:00
hayodea 988e84a545 Pretty up exception message 2025-01-08 15:09:18 -04:00
hayodea 4f2fbaa255 Fix NULL ptr use from dlerror() 2025-01-08 15:08:51 -04:00
hayodea 01ddb6d842 Only search in senseApiLibPath if it's actually set 2025-01-08 15:08:23 -04:00
hayodea 1178970728 cmd:opts: Now set OptionParser::argv0 to argv[0] 2025-01-08 15:07:42 -04:00
hayodea 090f0d3b02 senseApiMgr: new method: loadAllSenseApisFromOptions
This method dlopens() all senseApi libs that were referenced by
device specs.
2025-01-08 15:06:31 -04:00
hayodea 04db7bf76c Fix build warning about decltype(dlclose) 2025-01-08 15:05:56 -04:00
hayodea 2dc3083cdb whitespace 2025-01-08 13:47:16 -04:00
hayodea bffa2b837c Opts: Make singleton; senseApi: check senseApiLibPath, get senseApiDescFn pointer.
* OptionsParser now has a singleton.
* We now use the cmdline opt -p <senseApiLibPath>, and search for
  the specified library in:
  * senseApiLibPath, then CWD, then the place where our executable
    is running from, and then finally we let the hosting OS do
    its own search.
* We now call dlsym() on dlopen()'d libs to to get the senseApiDescFn
  pointer.
2025-01-08 13:43:44 -04:00
hayodea b40790ee4a Whitespace 2025-01-08 11:50:16 -04:00
hayodea c864bcfdd2 SenseApis: Define descriptors exported by libs 2025-01-08 11:49:28 -04:00
hayodea f5e3986644 DeviceSpec:parser: improve exception messages 2025-01-08 11:46:59 -04:00
hayodea 5a5e2fa25f Fix const_cast and rearrange logic in senseApi method 2025-01-08 07:56:10 -04:00
hayodea b9aa53822f Indentation 2025-01-08 07:27:48 -04:00
hayodea 376b29871b Change version to 0.00.001. 2025-01-08 06:35:34 -04:00
hayodea fe3f911db4 SenseApis: New senseApiManager and X11XcbApi
Still fleshing these out but ultimately senseApiMgr will manage
sense apis, and the X11XcbApi is where we'll connect to Xcb and
read the screen.
2025-01-08 06:26:36 -04:00
hayodea f594d29a2d DevMgr: Add sensorDevSpecs list and make intero/extro lists use unique_ptr 2025-01-08 06:23:34 -04:00
hayodea 6a494f7ff7 cmd:opts: New -a (sense-api shlibs) and -p (api shlib search dir) opts 2025-01-08 06:19:46 -04:00
hayodea 36acbdfc36 cmd:opts: Add senseApiPath in prep for new cmd opts 2025-01-08 00:32:07 -04:00
hayodea dd7a75d9b5 devSpec: Update preprocessor error message 2025-01-07 20:21:15 -04:00
213 changed files with 21100 additions and 1200 deletions
+5 -1
View File
@@ -1,10 +1,14 @@
/build
/b-*
*~
**/.deps/
**/Makefile.in
aclocal.m4
/b
/bautotools
/autotools-aux
autom4te.cache/
config.h.in
configure
*.swp
cscope.out
+3
View File
@@ -0,0 +1,3 @@
[submodule "third_party/googletest"]
path = third_party/googletest
url = https://github.com/google/googletest.git
+5 -5
View File
@@ -3,12 +3,11 @@
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/include",
"${workspaceFolder}/hcore/include",
"${workspaceFolder}/smocore/include",
"${workspaceFolder}/b/include",
"/usr/include",
"/usr/local/include",
"${workspaceFolder}/b/include"
"/usr/local/include"
],
"defines": [],
"compilerPath": "/usr/bin/g++",
@@ -24,7 +23,8 @@
},
"forcedInclude": [
"${workspaceFolder}/b/include/config.h"
]
],
"configurationProvider": "ms-vscode.cmake-tools"
}
],
"version": 4
+146 -12
View File
@@ -5,13 +5,39 @@
"version": "0.2.0",
"configurations": [
{
"name": "C++ Launch",
"name": "Debug salmanoff (Basic)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/b/harikoff",
"program": "${workspaceFolder}/b/salmanoff",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"cwd": "${workspaceFolder}/b",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set disassembly flavor to intel",
"text": "set disassembly-flavor intel",
"ignoreFailures": true
}
],
"preLaunchTask": "build-salmanoff",
"miDebuggerPath": "/usr/bin/gdb"
},
{
"name": "Debug salmanoff (Help)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/b/salmanoff",
"args": ["--help"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/b",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
@@ -22,15 +48,123 @@
"ignoreFailures": true
}
],
"preLaunchTask": "Build",
"miDebuggerPath": "/usr/bin/gdb",
"logging": {
"trace": true,
"traceResponse": true,
"engineLogging": true,
"programOutput": true,
"exceptions": true
}
"preLaunchTask": "build-salmanoff",
"miDebuggerPath": "/usr/bin/gdb"
},
{
"name": "Debug salmanoff (Verbose)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/b/salmanoff",
"args": ["--verbose"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/b",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "build-salmanoff",
"miDebuggerPath": "/usr/bin/gdb"
},
{
"name": "Debug salmanoff (Custom Args)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/b/salmanoff",
"args": ["--devicespec", "test_device", "--verbose"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/b",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "build-salmanoff",
"miDebuggerPath": "/usr/bin/gdb"
},
{
"name": "Attach to salmanoff (Remote Debug)",
"type": "cppdbg",
"request": "attach",
"program": "${workspaceFolder}/b/salmanoff",
"processId": "${command:pickProcess}",
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"miDebuggerPath": "/usr/bin/gdb"
},
{
"name": "Debug salmanoff (Break on Main)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/b/salmanoff",
"args": ["--help"],
"stopAtEntry": true,
"cwd": "${workspaceFolder}/b",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set breakpoint on main",
"text": "break main",
"ignoreFailures": true
}
],
"preLaunchTask": "build-salmanoff",
"miDebuggerPath": "/usr/bin/gdb"
},
{
"name": "Debug salmanoff (ComponentThread Focus)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/b/salmanoff",
"args": ["--help"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/b",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set breakpoint on ComponentThread constructor",
"text": "break ComponentThread::ComponentThread",
"ignoreFailures": true
},
{
"description": "Set breakpoint on Mind constructor",
"text": "break Mind::Mind",
"ignoreFailures": true
}
],
"preLaunchTask": "build-salmanoff",
"miDebuggerPath": "/usr/bin/gdb"
}
]
}
+101 -66
View File
@@ -1,69 +1,104 @@
{
"files.associations": {
"cstdint": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"typeinfo": "cpp",
"variant": "cpp",
"cstring": "cpp",
"cinttypes": "cpp"
},
"editor.rulers": [80, 120]
"cstdint": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"typeinfo": "cpp",
"variant": "cpp",
"cstring": "cpp",
"cinttypes": "cpp",
"any": "cpp",
"codecvt": "cpp",
"complex": "cpp",
"coroutine": "cpp",
"csignal": "cpp",
"list": "cpp",
"source_location": "cpp",
"future": "cpp",
"shared_mutex": "cpp",
"typeindex": "cpp",
"bitset": "cpp",
"*.ipp": "cpp",
"unordered_set": "cpp",
"forward_list": "cpp",
"barrier": "cpp",
"strstream": "cpp",
"regex": "cpp",
"stacktrace": "cpp",
"stdfloat": "cpp"
},
"editor.rulers": [80, 120],
"editor.tabSize": 4,
"editor.insertSpaces": false,
"editor.detectIndentation": false,
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"C_Cpp.default.browse.limitSymbolsToIncludedHeaders": true,
"C_Cpp.default.browse.path": [
"${workspaceFolder}",
"${workspaceFolder}/b"
],
"C_Cpp.default.includePath": [
"${workspaceFolder}/include",
"${workspaceFolder}/smocore/include",
"${workspaceFolder}/b/include",
"/usr/include",
"/usr/local/include"
]
}
+54 -44
View File
@@ -2,68 +2,78 @@
"version": "2.0.0",
"tasks": [
{
"label": "Create Build Directory",
"type": "shell",
"command": "mkdir -p b",
"problemMatcher": [],
"detail": "Creates the build directory."
},
{
"label": "Configure",
"type": "shell",
"command": "${workspaceFolder}/configure",
"options": {
"cwd": "${workspaceFolder}/b"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$gcc"],
"dependsOn": ["Create Build Directory"],
"detail": "Runs the configure script to prepare the build environment."
},
{
"label": "Build",
"label": "build-salmanoff",
"type": "shell",
"command": "make",
"options": {
"cwd": "${workspaceFolder}/b"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$gcc"],
"dependsOn": ["Configure"],
"detail": "Builds the project using make."
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": [
"$gcc"
],
"options": {
"cwd": "${workspaceFolder}/b"
}
},
{
"label": "clean",
"label": "clean-salmanoff",
"type": "shell",
"command": "make clean",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"options": {
"cwd": "${workspaceFolder}/b"
},
"group": {
"kind": "none"
},
"problemMatcher": [],
"detail": "Cleans the build artifacts."
}
},
{
"label": "test",
"label": "rebuild-salmanoff",
"type": "shell",
"command": "make test",
"command": "make clean && make",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"options": {
"cwd": "${workspaceFolder}/b"
}
},
{
"label": "run-salmanoff-help",
"type": "shell",
"command": "./salmanoff --help",
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"group": {
"kind": "test",
"isDefault": true
},
"problemMatcher": ["$gcc"],
"detail": "Runs the tests."
"options": {
"cwd": "${workspaceFolder}/b"
}
}
]
}
+181
View File
@@ -0,0 +1,181 @@
cmake_minimum_required(VERSION 3.16)
project(salmanoff VERSION 0.01.000 LANGUAGES CXX)
include(CMakeDependentOption)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DAPSS.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/DebugOpts.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/VerifyBoostDynamic.cmake)
# Set C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Build type
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug FORCE)
endif()
# Compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
# Mind oscillator configuration
set(MIND_VOSCILLATOR_PERIOD_MS 33 CACHE STRING "Mind's virtual osc clock rate (ms)")
if(NOT MIND_VOSCILLATOR_PERIOD_MS GREATER 0)
message(FATAL_ERROR "MIND_VOSCILLATOR_PERIOD_MS must be a positive integer > 0")
endif()
math(EXPR MIND_VOSCILLATOR_FREQ_MS "1000 / ${MIND_VOSCILLATOR_PERIOD_MS}")
# Device manager reattacher configuration
set(MRNTT_DEVMGR_REATTACHER_PERIOD_MS 2000
CACHE STRING "Device manager reattacher period (ms)")
if(NOT MRNTT_DEVMGR_REATTACHER_PERIOD_MS GREATER 0)
message(FATAL_ERROR
"MRNTT_DEVMGR_REATTACHER_PERIOD_MS must be a positive integer > 0")
endif()
# 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
option(WORLD_USE_BODY_THREAD
"Use body thread for world component instead of separate world thread" OFF)
# Test configuration
option(ENABLE_TESTS "Enable building tests" OFF)
# Set the debug locks variable for config.h
if(ENABLE_DEBUG_LOCKS)
set(CONFIG_ENABLE_DEBUG_LOCKS TRUE)
endif()
# Set the 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
if(WORLD_USE_BODY_THREAD)
set(CONFIG_WORLD_USE_BODY_THREAD TRUE)
endif()
# Set the timeout variable for config.h
set(CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS ${DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS})
# 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_file(
${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/include/config.h
@ONLY
)
# Include directories
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/smocore/include
${CMAKE_CURRENT_BINARY_DIR}/include
)
# Find core dependencies
# We cannot use header-only Boost.Asio because we need both our dlopen()'d
# libraries and the main binary to refer to the same instances of boost::asio's
# metadata. If we use header-only Boost.Asio, each dlopen()'d library will have
# 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(FLEX REQUIRED)
find_package(BISON REQUIRED)
# Need dlopen() and dlsym()
find_library(DL_LIBRARY NAMES dl ldl)
if(NOT DL_LIBRARY)
message(FATAL_ERROR "Dynamic linking library (libdl/libldl) not found")
endif()
# Add third-party dependencies
if(ENABLE_TESTS)
add_subdirectory(third_party)
endif()
add_subdirectory(compile)
# Add core components
add_subdirectory(smocore)
add_subdirectory(commonLibs)
add_subdirectory(stimBuffApis)
add_subdirectory(wilzorApis)
add_subdirectory(devices)
# Main executable
add_executable(salmanoff main.cpp)
target_link_libraries(salmanoff
Boost::system Boost::log
smocore
${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_daps_dependencies()
# Add tests if enabled
if(ENABLE_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
install(TARGETS salmanoff DESTINATION bin)
# Install device configuration files (preprocessed .daps files)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/devices/
DESTINATION share/salmanoff/devices
FILES_MATCHING PATTERN "*.daps"
)
# Install documentation
install(FILES README.md DESTINATION share/doc/salmanoff)
install(FILES LICENSE DESTINATION share/doc/salmanoff)
# Install example configurations if they exist
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples")
install(DIRECTORY examples/ DESTINATION share/salmanoff/examples)
endif()
# Include CPack configuration
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/CPackConfig.cmake)
include(CPack)
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2024 Salmanoff Project. All rights reserved.
PROPRIETARY SOFTWARE LICENSE
This software and associated documentation files (the "Software") are
proprietary and confidential. The Software is owned exclusively by the
Salmanoff Project and is protected by copyright laws and international
treaty provisions.
NO LICENSE GRANTED. No person or entity is granted any rights or
permissions to use, copy, modify, merge, publish, distribute, sublicense,
sell, or otherwise transfer the Software or any portion thereof without
explicit written permission from the Salmanoff Project.
UNAUTHORIZED USE PROHIBITED. Any unauthorized use, reproduction, or
distribution of the Software is strictly prohibited and may result in
severe civil and criminal penalties.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-7
View File
@@ -1,7 +0,0 @@
SUBDIRS = hcore
AM_CPPFLAGS+= -I"$(top_srcdir)/hcore/include"
bin_PROGRAMS = harikoff
harikoff_SOURCES = main.cpp
harikoff_LDADD = hcore/libhcore.a hcore/deviceManager/libdeviceManager.a
+84
View File
@@ -0,0 +1,84 @@
# Package Generation
This project supports generating both Debian (.deb) and RPM (.rpm) packages
using CPack.
## Manual Package Generation
### Prerequisites
- CMake 3.16 or later
- Make or Ninja build system
- For RPM packages: `rpmbuild` utility
### Build Process
1. **Create build directory:**
```bash
mkdir -p build-package
cd build-package
```
2. **Configure with CMake:**
```bash
cmake .. -DCMAKE_BUILD_TYPE=Release
```
3. **Build the project:**
```bash
make -j$(nproc)
```
4. **Generate packages:**
```bash
cpack -G DEB # Generate Debian package
cpack -G RPM # Generate RPM package (requires rpmbuild)
```
### Requirements for RPM Generation
To generate RPM packages, you need `rpmbuild` installed:
- **Ubuntu/Debian**: `sudo apt-get install rpm`
- **CentOS/RHEL**: `sudo yum install rpm-build`
- **Fedora**: `sudo dnf install rpm-build`
### Package Contents
The generated packages include:
- **Main executable**: `/usr/bin/salmanoff`
- **Shared libraries**: `/usr/lib/lib*.so`
- **Device configurations**: `/usr/share/salmanoff/devices/` (preprocessed
.daps files)
- **Documentation**: `/usr/share/doc/salmanoff/`
### Installing Packages
**Debian/Ubuntu:**
```bash
sudo dpkg -i salmanoff-0.00.004-x86_64.deb
```
**CentOS/RHEL/Fedora:**
```bash
sudo rpm -i salmanoff-0.00.004-x86_64.rpm
```
### Package Configuration
Package metadata and configuration is defined in
`cmake/CPackConfig.cmake`. This includes:
- Package name, version, and description
- Dependencies and recommendations
- License information
- File naming conventions
### Troubleshooting
- **RPM generation fails**: Ensure `rpmbuild` is installed
- **Missing dependencies**: Check that all build dependencies are
installed
- **Permission errors**: Ensure you have write permissions in the build
directory
+13 -4
View File
@@ -1,5 +1,14 @@
# The Harriman-Peikoff Project
# The Salmanoff Project:
Wouldn't you like to know what this project is and does? Well, it's a secret!
But you can find out by reading the code. Or you could just ask me. Or you
could wait until I release it. But that's no fun.
<p align="center">
<img src="docs/img/salmanoff-logo-512.png" alt="Salmanoff project logo" />
</p>
This project, Salmanoff (pronounced: Sal-man-off), is an ROS rewrite of the Harikoff project. The name is more reflective of the people whose ideas sparked the solutions in my mind. These people are:
* Gregory `SAL`mieri.
* David Harri`MAN`.
* Leonard Peik`OFF`.
Would you like to know what this project is and does? Well, it's a secret! But you can find out by reading the code. Or you could just ask me. Or you could wait until I release it. But that's no fun.
For package generation instructions, see [PACKAGING.md](PACKAGING.md).
+51
View File
@@ -0,0 +1,51 @@
# CPack configuration for package generation
# This file contains all CPack settings for generating deb and rpm packages
# Set package metadata using project variables
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
"Salmanoff - A sensor management and control system")
set(CPACK_PACKAGE_VENDOR "Salmanoff Project")
set(CPACK_PACKAGE_CONTACT "maintainer@salmanoff.org")
# Set package description
set(CPACK_PACKAGE_DESCRIPTION
"Salmanoff is a comprehensive sensor management and control system that\n"
"provides unified interfaces for various sensor devices including LiDAR\n"
"systems. It features modular architecture with support for multiple\n"
"device types, asynchronous processing, and real-time data handling."
)
# License information
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
# Enable deb and rpm generators
set(CPACK_GENERATOR "DEB;RPM")
# DEB package specific settings
set(CPACK_DEBIAN_PACKAGE_MAINTAINER
"Salmanoff Project <maintainer@salmanoff.org>")
set(CPACK_DEBIAN_PACKAGE_SECTION "science")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_DEPENDS
"libboost-system1.74.0 | libboost-system1.73.0 | libboost-system1.72.0, "
"libc6, libstdc++6")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libxcb1, libx11-6")
set(CPACK_DEBIAN_PACKAGE_SUGGESTS "livox-sdk")
# RPM package specific settings
set(CPACK_RPM_PACKAGE_LICENSE "Proprietary")
set(CPACK_RPM_PACKAGE_GROUP "Applications/Engineering")
set(CPACK_RPM_PACKAGE_URL "https://github.com/salmanoff/salmanoff")
set(CPACK_RPM_PACKAGE_REQUIRES "boost-system >= 1.72.0, glibc, libstdc++")
set(CPACK_RPM_PACKAGE_SUGGESTS "xcb, libX11, livox-sdk")
# Package file naming using project variables
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_PROCESSOR}")
# Set compression
set(CPACK_DEB_COMPONENT_INSTALL ON)
set(CPACK_RPM_COMPONENT_INSTALL ON)
+153
View File
@@ -0,0 +1,153 @@
# DAPSS (Device Attachment Pipe Specification Source) preprocessing module
# This module provides functionality to preprocess .dapss files to .daps files
# using the C preprocessor, respecting include directories and target dependencies.
#
# Usage:
# add_daps_target(target_name SOURCES file1.dapss file2.dapss ...)
# register_daps_target(target_name) # In subdirectories
# add_all_daps_dependencies() # In main CMakeLists.txt
#
# Examples:
# add_daps_target(device_specs SOURCES devices/avia0.dapss devices/win0.dapss)
# register_daps_target(device_specs)
# add_all_daps_dependencies()
#
# The preprocessed .daps files will be placed in ${CMAKE_CURRENT_BINARY_DIR}/
# Function to add a DAPSS preprocessing target
# Usage: add_daps_target(target_name SOURCES file1.dapss file2.dapss ...)
function(add_daps_target target_name)
set(options)
set(oneValueArgs)
set(multiValueArgs SOURCES)
cmake_parse_arguments(DAPS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT DAPS_SOURCES)
message(FATAL_ERROR "add_daps_target: No SOURCES specified for target ${target_name}")
endif()
# Use binary directory directly for processed files
# This ensures files are created in the same directory as the target
set(output_dir "${CMAKE_CURRENT_BINARY_DIR}")
# List to store all output files
set(output_files)
# Process each source file
foreach(source_file ${DAPS_SOURCES})
# Get the base name without extension
get_filename_component(base_name ${source_file} NAME_WE)
get_filename_component(source_dir ${source_file} DIRECTORY)
# Create output file path
set(output_file "${output_dir}/${base_name}.daps")
list(APPEND output_files ${output_file})
# Get include directories from current directory and target
get_directory_property(include_dirs INCLUDE_DIRECTORIES)
# Build include flags
set(include_flags)
foreach(include_dir ${include_dirs})
list(APPEND include_flags "-I${include_dir}")
endforeach()
# Add current source directory to includes if it's not already there
if(source_dir)
list(APPEND include_flags "-I${source_dir}")
endif()
# Convert list to space-separated string
string(REPLACE ";" " " include_flags_str "${include_flags}")
# Find C compiler if not already set
if(NOT CMAKE_C_COMPILER)
find_program(CMAKE_C_COMPILER gcc cc clang)
if(NOT CMAKE_C_COMPILER)
message(FATAL_ERROR "No C compiler found for DAPSS preprocessing")
endif()
endif()
# Create custom command to preprocess the file
add_custom_command(
OUTPUT ${output_file}
COMMAND sh -c "\"${CMAKE_C_COMPILER}\" -E -P -x c ${include_flags_str} \"${CMAKE_CURRENT_SOURCE_DIR}/${source_file}\" > \"${output_file}\""
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${source_file}
COMMENT "Preprocessing ${source_file} to ${base_name}.daps"
VERBATIM
)
endforeach()
# Create custom target that depends on all output files
add_custom_target(${target_name} DEPENDS ${output_files})
# Make the target part of the ALL target so it gets built by default
# This ensures it gets built when building just this subdirectory
set_target_properties(${target_name} PROPERTIES
FOLDER "${CMAKE_CURRENT_SOURCE_DIR}"
EXCLUDE_FROM_ALL FALSE
)
# Set target properties
set_target_properties(${target_name} PROPERTIES
DAPS_OUTPUT_DIR ${output_dir}
DAPS_OUTPUT_FILES "${output_files}"
)
# Make the target available globally
set(${target_name}_OUTPUT_DIR ${output_dir} PARENT_SCOPE)
set(${target_name}_OUTPUT_FILES "${output_files}" PARENT_SCOPE)
endfunction()
# Function to register a DAPSS target for later dependency addition
# Usage: register_daps_target(target_name)
# This stores the target name in a global property for later use
function(register_daps_target target_name)
# Store the target name in a global property
get_property(registered_targets GLOBAL PROPERTY DAPS_REGISTERED_TARGETS)
list(APPEND registered_targets ${target_name})
set_property(GLOBAL PROPERTY DAPS_REGISTERED_TARGETS ${registered_targets})
message(STATUS "Registered DAPSS target ${target_name} for later dependency addition")
endfunction()
# Function to add all registered DAPSS targets as dependencies
# Usage: add_all_daps_dependencies([TARGET main_target] [CONDITION condition_expression])
# This should be called from the main CMakeLists.txt after all subdirectories are processed
function(add_all_daps_dependencies)
set(options)
set(oneValueArgs TARGET CONDITION)
set(multiValueArgs)
cmake_parse_arguments(DAPS_ALL "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Default target is PROJECT_NAME
if(DAPS_ALL_TARGET)
set(dep_target ${DAPS_ALL_TARGET})
else()
set(dep_target ${PROJECT_NAME})
endif()
# Get all registered targets
get_property(registered_targets GLOBAL PROPERTY DAPS_REGISTERED_TARGETS)
if(registered_targets)
foreach(target_name ${registered_targets})
if(TARGET ${target_name})
if(DAPS_ALL_CONDITION)
if(${DAPS_ALL_CONDITION})
add_dependencies(${dep_target} ${target_name})
message(STATUS "Added registered DAPSS target ${target_name} as dependency of ${dep_target} (condition: ${DAPS_ALL_CONDITION})")
else()
message(STATUS "Skipped registered DAPSS target ${target_name} (condition: ${DAPS_ALL_CONDITION} not met)")
endif()
else()
add_dependencies(${dep_target} ${target_name})
message(STATUS "Added registered DAPSS target ${target_name} as dependency of ${dep_target}")
endif()
else()
message(WARNING "Registered DAPSS target ${target_name} does not exist")
endif()
endforeach()
else()
message(STATUS "No DAPSS targets registered for dependency addition")
endif()
endfunction()
+25
View File
@@ -0,0 +1,25 @@
# DebugOpts.cmake - Debug configuration options
# Enable debug locking features
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
# Always define the variable in cache so it appears in ccmake
set(DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS 500 CACHE STRING
"Timeout in milliseconds for deadlock detection in qutex system")
if(ENABLE_DEBUG_LOCKS)
# Validate the timeout value
if(NOT DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS OR DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS STREQUAL "")
message(FATAL_ERROR "DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS must be a positive integer > 0")
endif()
# Convert to integer and validate
math(EXPR timeout_int "${DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS}")
if(timeout_int LESS_EQUAL 0)
message(FATAL_ERROR "DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS must be a positive integer > 0")
endif()
endif()
+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()
+37
View File
@@ -0,0 +1,37 @@
# Cross-compilation toolchain file for aarch64-linux-gnu
# This file should be used with cmake -DCMAKE_TOOLCHAIN_FILE=cmake/aarch64-linux-gnu.cmake
# Disable some features that might not be available in cross-compilation
set(CMAKE_CROSSCOMPILING TRUE)
# Target OS.
set(CMAKE_SYSTEM_NAME Linux)
# Use whatever the host system version is.
# set(CMAKE_SYSTEM_VERSION ${CMAKE_HOST_SYSTEM_VERSION})
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# Specify the cross compilers.
# We could do some more advanced stuff here to search for the correct
# cross-compiler based on CMAKE_SYSTEM_NAME & CMAKE_SYSTEM_PROCESSOR.
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
# These are necessary for CLang.
# set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
# set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu)
# Set architecture-specific flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a -mtune=cortex-a72")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a -mtune=cortex-a72")
# Set the target environment
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
# Search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Search for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# 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")
+91
View File
@@ -0,0 +1,91 @@
# Clang toolchain file for native builds
# This file should be used with cmake -DCMAKE_TOOLCHAIN_FILE=cmake/clang-native.cmake
# Disable cross-compilation
set(CMAKE_CROSSCOMPILING FALSE)
# Target OS (native)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_HOST_SYSTEM_PROCESSOR})
# Specify the Clang compilers
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
# Set Clang-specific compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic")
# Enable C++20 standard (as specified in main CMakeLists.txt)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Set Clang-specific optimization flags
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -DNDEBUG")
# Set debug flags
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
# Enable address sanitizer in debug builds (optional)
# Uncomment the following lines if you want to enable address sanitizer
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer")
# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer")
# set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address")
# set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=address")
# Enable undefined behavior sanitizer in debug builds (optional)
# Uncomment the following lines if you want to enable UBSan
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined -fno-omit-frame-pointer")
# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined -fno-omit-frame-pointer")
# set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=undefined")
# set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=undefined")
# Set native search paths (use system defaults)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
# Clang-specific linker flags
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld")
# Enable link-time optimization in release builds (optional)
# Uncomment the following lines if you want to enable LTO
# set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
# set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
# set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -flto")
# set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -flto")
# Set Clang-specific C++ features
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++")
# Alternative: Use libstdc++ instead of libc++ (uncomment if preferred)
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libstdc++")
# set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libstdc++")
# Set compiler-specific features
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics")
# Enable all warnings and treat them as errors in debug builds (optional)
# Uncomment the following lines if you want to treat warnings as errors
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror")
# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Werror")
# Set Clang-specific optimization flags
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=native -mtune=native")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -march=native -mtune=native")
# Print configuration information
message(STATUS "Clang toolchain configuration:")
message(STATUS " C Compiler: ${CMAKE_C_COMPILER}")
message(STATUS " CXX Compiler: ${CMAKE_CXX_COMPILER}")
message(STATUS " CXX Standard: ${CMAKE_CXX_STANDARD}")
message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}")
message(STATUS " Cross-compiling: ${CMAKE_CROSSCOMPILING}")
+72
View File
@@ -0,0 +1,72 @@
# Generic Flex/Yacc Generation Functions
# This file provides reusable functions for generating C++ files from Flex/Bison sources
# Function to generate Flex lexer files
# Usage: generate_flex_lexer(OUTPUT_VAR INPUT_FILE [PREFIX] [HEADER_DEPENDENCY])
# OUTPUT_VAR: Variable name to store the output file path
# INPUT_FILE: Path to the .ll input file
# PREFIX: Optional prefix for the generated files (defaults to basename of input file)
# HEADER_DEPENDENCY: Optional header file that the lexer depends on (e.g., from Bison)
function(generate_flex_lexer OUTPUT_VAR INPUT_FILE)
get_filename_component(INPUT_BASENAME ${INPUT_FILE} NAME_WE)
if(ARGC GREATER 2)
set(PREFIX ${ARGV2})
else()
set(PREFIX ${INPUT_BASENAME})
endif()
set(LEX_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.cc)
set(LEX_HEADER ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.hh)
# Set up dependencies
set(DEPENDENCIES ${INPUT_FILE})
if(ARGC GREATER 3)
list(APPEND DEPENDENCIES ${ARGV3})
endif()
add_custom_command(
OUTPUT ${LEX_OUTPUT}
DEPENDS ${DEPENDENCIES}
COMMAND ${FLEX_EXECUTABLE} --header-file=${LEX_HEADER} -o ${LEX_OUTPUT} ${INPUT_FILE}
COMMENT "Generating ${PREFIX}.cc from ${INPUT_FILE}"
)
set(${OUTPUT_VAR} ${LEX_OUTPUT} PARENT_SCOPE)
endfunction()
# Function to generate Bison parser files
# Usage: generate_bison_parser(OUTPUT_VAR HEADER_VAR INPUT_FILE [PREFIX])
# OUTPUT_VAR: Variable name to store the output .cc file path
# HEADER_VAR: Variable name to store the output .hh file path
# INPUT_FILE: Path to the .yy input file
# PREFIX: Optional prefix for the generated files (defaults to basename of input file)
function(generate_bison_parser OUTPUT_VAR HEADER_VAR INPUT_FILE)
get_filename_component(INPUT_BASENAME ${INPUT_FILE} NAME_WE)
if(ARGC GREATER 3)
set(PREFIX ${ARGV3})
else()
set(PREFIX ${INPUT_BASENAME})
endif()
set(YACC_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.cc)
set(YACC_HEADER ${CMAKE_CURRENT_BINARY_DIR}/${PREFIX}.hh)
add_custom_command(
OUTPUT ${YACC_OUTPUT} ${YACC_HEADER}
DEPENDS ${INPUT_FILE}
COMMAND ${BISON_EXECUTABLE} -p ${PREFIX} --header=${YACC_HEADER} -o ${YACC_OUTPUT} ${INPUT_FILE}
COMMENT "Generating ${PREFIX}.cc and ${PREFIX}.hh from ${INPUT_FILE}"
)
set(${OUTPUT_VAR} ${YACC_OUTPUT} PARENT_SCOPE)
set(${HEADER_VAR} ${YACC_HEADER} PARENT_SCOPE)
endfunction()
# Generate device attachment parser files using the generic functions
# Generate Bison parser first (creates the header file)
generate_bison_parser(YACC_OUTPUT YACC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/deviceManager/deviceAttachmentPipeSpecp.yy deviceAttachmentPipeSpecp)
# Generate Flex lexer with dependency on Bison header
generate_flex_lexer(LEX_OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/deviceManager/deviceAttachmentPipeSpecl.ll deviceAttachmentPipeSpecl ${YACC_HEADER})
+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
# ----------------------------------------------------------------------------------
+3
View File
@@ -0,0 +1,3 @@
add_subdirectory(xcbXorg)
add_subdirectory(livoxProto1)
add_subdirectory(attachmentSupport)
@@ -0,0 +1,24 @@
add_library(attachmentSupport SHARED
stimulusBuffer.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)
@@ -0,0 +1,103 @@
#include <boostAsioLinkageFix.h>
#include <iostream>
#include <config.h>
#include <componentThread.h>
#include <boost/asio/io_service.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/system/error_code.hpp>
#include <spinLock.h>
#include <asynchronousBridge.h>
#include <user/stimulusBuffer.h>
namespace smo {
namespace stim_buff {
void StimulusBuffer::stop()
{
shouldContinue.store(false);
// Set up a timeout bridge using the io_service
boost::asio::deadline_timer delayTimer(ioService);
AsynchronousBridge bridge(ioService);
// Set up the delay to let in-flight operation finish
delayTimer.expires_from_now(
boost::posix_time::milliseconds(getStopDelayMs()));
delayTimer.async_wait(
[&bridge](const boost::system::error_code& error)
{
(void)error;
// Always signal complete, whether timeout expired or was cancelled
bridge.setAsyncOperationComplete();
});
bridge.waitForAsyncOperationCompleteOrIoServiceStopped();
std::cout << __func__ << ": Stopped stimulus buffer for device "
<< deviceAttachmentSpec->deviceSelector << std::endl;
// After delay, cancel timer and perform cleanup
timer.cancel();
}
void StimulusBuffer::scheduleNextTimeout(int delayMs)
{
if (!shouldContinue.load())
{ return; }
// Schedule the next timeout using the provided delay
timer.expires_from_now(
boost::posix_time::milliseconds(delayMs));
timer.async_wait(
std::bind(
&StimulusBuffer::onTimeout, this, std::placeholders::_1));
}
void StimulusBuffer::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 << "StimulusBuffer: Timer error: " << error.message()
<< std::endl;
return;
}
if (!shouldContinue.load())
{ 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;
if (frameAssemblyRateLimiter.tryAcquire())
{ nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS; }
else
{ nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS; }
// Call the derived class's frame production handler
stimFrameProductionTimesliceInd();
// Note: The lock should be released when frame production completes
// Schedule next timeout with the pre-determined duration
scheduleNextTimeout(nextWakeupDelayMs);
}
} // namespace stim_buff
} // namespace smo
+29
View File
@@ -0,0 +1,29 @@
option(ENABLE_LIB_livoxProto1 "Enable Livox Protocol v1 backend lib" ON)
if(ENABLE_LIB_livoxProto1)
add_library(livoxProto1 SHARED
livoxProto1.cpp
core.cpp
device.cpp
protocol.cpp
broadcastListener.cpp
udpCommandDemuxer.cpp
)
# Set config define for header generation
add_compile_definitions(CONFIG_LIB_LIVOXPROTO1_ENABLED)
target_include_directories(livoxProto1 PUBLIC ${Boost_INCLUDE_DIRS})
target_link_libraries(livoxProto1 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(TARGETS livoxProto1 DESTINATION lib)
endif()
@@ -0,0 +1,187 @@
#include <algorithm>
#include <iostream>
#include <opts.h>
#include <componentThread.h>
#include "broadcastListener.h"
namespace livoxProto1 {
namespace comms {
BroadcastListener::BroadcastListener(
const std::shared_ptr<smo::ComponentThread>& componentThread,
uint16_t listeningPort, uint16_t connectPort
)
: componentThread(componentThread),
listeningPort(listeningPort),
connectPort(connectPort),
deviceGoneAwayCb(nullptr),
socket(componentThread->getIoService()),
listeningEndpoint(boost::asio::ip::udp::v4(), listeningPort),
isListening(false)
{
}
std::shared_ptr<DiscoveredDevice>
BroadcastListener::getDevice(const std::string &deviceIdentifier) const
{
auto it = std::find_if(discoveredDevices.begin(), discoveredDevices.end(),
[&deviceIdentifier](const std::shared_ptr<DiscoveredDevice>& device) {
return comms::deviceIdentifiersEqual(
device->deviceIdentifier, deviceIdentifier);
}
);
return it != discoveredDevices.end() ? *it : nullptr;
}
void BroadcastListener::broadcastMsgInd(
const boost::system::error_code& ec, std::size_t bytes_received)
{
if (ec)
{
std::cerr << __func__ << ": Error receiving broadcast message: "
<< ec.message() << std::endl;
return;
}
if (bytes_received < sizeof(BroadcastMessage))
{
std::cerr << __func__
<< ": Received packet too small: " << bytes_received
<< " bytes (expected at least "
<< sizeof(BroadcastMessage) << ")" << std::endl;
return;
}
// Use placement new to construct BroadcastMessage in the buffer
BroadcastMessage* msg = new (bcastMsgRecvBuffer) BroadcastMessage;
// Following the clean receiving flow:
// 1. Swap CRC32 to host endianness first
msg->footer.swapCrc32ToHostEndianness();
// 2. Validate CRC32 (on whole message excluding footer CRC32 field)
if (!msg->validateCrc32())
{
std::cerr << __func__
<< ": Broadcast message failed CRC32 validation" << std::endl;
return;
}
// 3. Swap CRC16 to host endianness
msg->header.swapCrc16ToHostEndianness();
// 4. Validate CRC16 (on header only)
if (!msg->header.validateCrc16())
{
std::cerr << __func__
<< ": Broadcast message failed CRC16 validation" << std::endl;
return;
}
// 5. Swap content to host endianness
msg->swapContentsToHostEndianness();
// 6. Validate message sanity
if (!msg->sanityCheck())
{
std::cerr << __func__
<< ": Broadcast message failed sanity check" << std::endl;
return;
}
// Extract device information
std::string senderIP = senderEndpoint.address().to_string();
std::string broadcastCode(
reinterpret_cast<const char*>(msg->broadcast_code));
// Early return if device already exists
if (deviceExists(broadcastCode))
{
// Device already exists, just log the update
if (OptionParser::getOptions().verbose)
{
std::cout << __func__
<< ": Received broadcast from known device: "
<< broadcastCode << " at " << senderIP << "\n";
}
return;
}
// Create new DiscoveredDevice using conversion constructor
auto device = std::make_shared<DiscoveredDevice>(*msg, senderIP);
discoveredDevices.push_back(device);
// Output device information using stringify
std::cout << __func__ << ": Discovered new Livox device: "
<< device->stringify() << "\n";
}
void BroadcastListener::start(void)
{
if (isListening.load()) { return; }
try
{
/** EXPLANATION:
* Set up a boost::asio udp listening socket on the broadcast listening
* port.
*
* FIXME:
* We should also set up a timer to check for devices that have gone
* away.
*/
socket.open(boost::asio::ip::udp::v4());
socket.bind(listeningEndpoint);
isListening.store(true);
// Start the first async receive operation
startReceive();
std::cout << __func__ << ": BroadcastListener started on port "
<< listeningPort << std::endl;
}
catch (const boost::system::system_error& e)
{
isListening.store(false);
std::cerr << __func__ << ": Failed to start BroadcastListener: "
<< e.what() << std::endl;
throw;
}
}
void BroadcastListener::startReceive(void)
{
if (!isListening.load()) { return; }
socket.async_receive_from(
boost::asio::buffer(bcastMsgRecvBuffer, sizeof(bcastMsgRecvBuffer)),
senderEndpoint,
[this](const boost::system::error_code& ec, std::size_t bytes_received)
{
broadcastMsgInd(ec, bytes_received);
// Continue listening for the next packet
if (isListening.load())
{ startReceive(); }
}
);
}
void BroadcastListener::stop(void)
{
if (!isListening.load()) { return; }
isListening.store(false);
try
{
socket.close();
std::cout << __func__ << ": BroadcastListener stopped" << std::endl;
}
catch (const boost::system::system_error& e)
{
std::cerr << __func__ << ": Error stopping BroadcastListener: " << e.what()
<< std::endl;
throw;
}
}
} // namespace comms
} // namespace livoxProto1
@@ -0,0 +1,78 @@
#ifndef BROADCAST_LISTENER_H
#define BROADCAST_LISTENER_H
#include <boostAsioLinkageFix.h>
#include <vector>
#include <string>
#include <memory>
#include <atomic>
#include <boost/asio/ip/udp.hpp>
#include <user/senseApiDesc.h>
#include "device.h"
namespace livoxProto1 {
namespace comms {
/** EXPLANATION:
* This class merely listens for UDP bcast dgrams on the designated listening
* port. It then builds a list of client device IP addrs that it has heard from.
* It doesn't connect to them or signal any events to the rest of the lib,
* except in the case that a device which the lib is using has gone away.
*
* Other than that, its role is to tell the lib which devices are available
* on the network.
*/
#define UDP_BCAST_MSG_BUFFER_NBYTES (1024)
class BroadcastListener
{
public:
BroadcastListener(
const std::shared_ptr<smo::ComponentThread>& componentThread,
uint16_t listeningPort=55000, uint16_t connectPort=65000);
~BroadcastListener() = default;
typedef void (DeviceGoneAwayCbFn)(const DiscoveredDevice &device);
void setDeviceGoneAwayCb(DeviceGoneAwayCbFn *cb)
{ deviceGoneAwayCb = cb; }
bool deviceExists(const std::string &deviceIdentifier) const
{ return getDevice(deviceIdentifier) != nullptr; }
std::shared_ptr<DiscoveredDevice>
getDevice(const std::string &deviceIdentifier) const;
void start(void);
void stop(void);
void broadcastMsgInd(
const boost::system::error_code& ec, std::size_t bytes_received);
private:
void startReceive(void);
private:
std::shared_ptr<smo::ComponentThread> componentThread;
/** EXPLANATION:
* The Livox proto says that client devices will spam broadcast UDP
* dgrams to us on the listening port. We can then use the source IP from
* the bcast dgram to figure out the client device's IP addr. Then we
* should send a connect dgram to the connect port. This will tell the
* client device our IP addr.
*/
uint16_t listeningPort, connectPort;
DeviceGoneAwayCbFn *deviceGoneAwayCb;
std::vector<std::shared_ptr<DiscoveredDevice>> discoveredDevices;
boost::asio::ip::udp::socket socket;
boost::asio::ip::udp::endpoint listeningEndpoint, senderEndpoint;
std::atomic<bool> isListening;
uint8_t bcastMsgRecvBuffer[UDP_BCAST_MSG_BUFFER_NBYTES];
};
} // namespace comms
} // namespace livoxProto1
#endif // BROADCAST_LISTENER_H
+276
View File
@@ -0,0 +1,276 @@
#include <algorithm>
#include <iostream>
#include <functional>
#include <optional>
#include <opts.h>
#include <asynchronousContinuation.h>
#include <callback.h>
#include <user/senseApiDesc.h>
#include "protocol.h"
#include "core.h"
#include "device.h"
#include "broadcastListener.h"
#include "livoxProto1.h"
namespace livoxProto1 {
static ProtoState protoState =
{
.isInitialized = false,
.componentThread = nullptr,
.deviceManager = nullptr,
.smoCallbacks = {}
};
ProtoState& getProtoState()
{
return protoState;
}
DeviceManager::DeviceManager()
: broadcastListener(protoState.componentThread),
udpCommandDemuxer(protoState.componentThread, *this)
{
broadcastListener.setDeviceGoneAwayCb(deviceGoneAwayInd);
}
void DeviceManager::deviceGoneAwayInd(const comms::DiscoveredDevice &device)
{
std::cout << "Device gone away: " << device.stringify() << std::endl;
// Check if device exists in our collection
if (!protoState.deviceManager->getDevice(device)) {
return;
}
// Find and remove the device from the collection
auto it = std::find_if(
protoState.deviceManager->devices.begin(),
protoState.deviceManager->devices.end(),
[&device](const std::shared_ptr<Device> &d) {
return d->discoveredDevice == device;
}
);
if (it != protoState.deviceManager->devices.end()) {
protoState.deviceManager->devices.erase(it);
}
}
std::optional<std::shared_ptr<Device>> DeviceManager::getDevice(
const std::string &deviceIdentifier
)
{
for (auto& device : devices)
{
if (comms::deviceIdentifiersEqual(
device->discoveredDevice.deviceIdentifier, deviceIdentifier))
{
return device;
}
}
return std::nullopt;
}
// GetOrCreateDeviceReq nested class implementation
class DeviceManager::GetOrCreateDeviceReq
: public smo::NonPostedAsynchronousContinuation<
livoxProto1_getOrCreateDeviceReqCbFn>
{
public:
DeviceManager& deviceManager;
// The device we're trying to connect (holds all connection parameters)
std::shared_ptr<Device> pendingDevice;
public:
GetOrCreateDeviceReq(
DeviceManager& mgr,
std::shared_ptr<Device> device,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> cb)
: smo::NonPostedAsynchronousContinuation<
livoxProto1_getOrCreateDeviceReqCbFn>(std::move(cb)),
deviceManager(mgr), pendingDevice(device)
{}
// Public accessor for the original callback
void callOriginalCallback(bool success, std::shared_ptr<Device> device)
{ callOriginalCb(success, device); }
void callOriginalCallbackWithFailure()
{ callOriginalCallback(false, nullptr); }
void getOrCreateDeviceReq1(
std::shared_ptr<GetOrCreateDeviceReq> context, bool connectSuccess
)
{
if (!connectSuccess)
{
std::cerr << __func__ << ": Connection failed for device "
<< context->pendingDevice->discoveredDevice.deviceIdentifier
<< std::endl;
context->callOriginalCallbackWithFailure();
return;
}
// Connection successful, add device to collection
context->deviceManager.devices.push_back(context->pendingDevice);
if (OptionParser::getOptions().verbose)
{
std::cout << __func__ << ": Successfully connected and added device "
<< context->pendingDevice->discoveredDevice.deviceIdentifier
<< std::endl;
}
// Return success with the connected device
context->callOriginalCallback(true, context->pendingDevice);
}
};
void DeviceManager::getOrCreateDeviceReq(
const std::string &deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread,
int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback)
{
// Validate smoIp format using Boost.Asio IPv4 validation
if (!smoIp.empty() && !comms::isValidIPv4(smoIp))
{
throw std::invalid_argument(
std::string(__func__) +
": Invalid IPv4 smoIp format: " + smoIp);
}
// Validate subnet nbits
if (smoSubnetNbits > 32)
{
throw std::invalid_argument(
std::string(__func__) +
": smoSubnetNbits must be between 0 and 32, got: " +
std::to_string(smoSubnetNbits));
}
// First try to get existing device
auto existingDevice = getDevice(deviceIdentifier);
if (existingDevice)
{
// Device already exists and is connected, return it
callback.callbackFn(true, existingDevice.value());
return;
}
// Device doesn't exist, create a new one but don't add it to collection yet
auto newDevice = std::make_shared<Device>(
deviceIdentifier, componentThread,
commandTimeoutMs, retryDelayMs,
smoIp, smoSubnetNbits,
dataPort, cmdPort, imuPort);
// Create the continuation request object to hold state and callbacks
auto request = std::make_shared<GetOrCreateDeviceReq>(
*this, newDevice, std::move(callback));
// Start the connection process - only add to collection on success
request->pendingDevice->connectReq(
{request, std::bind(
&DeviceManager::GetOrCreateDeviceReq::getOrCreateDeviceReq1,
request.get(), request, std::placeholders::_1)});
}
class DeviceManager::DestroyDeviceReq
: public smo::NonPostedAsynchronousContinuation<
livoxProto1_destroyDeviceReqCbFn>
{
public:
DeviceManager& deviceManager;
std::shared_ptr<Device> pendingDevice;
public:
DestroyDeviceReq(
DeviceManager& mgr,
std::shared_ptr<Device> device,
smo::Callback<livoxProto1_destroyDeviceReqCbFn> cb)
: smo::NonPostedAsynchronousContinuation<
livoxProto1_destroyDeviceReqCbFn>(std::move(cb)),
deviceManager(mgr), pendingDevice(device)
{}
// Public accessor for the original callback
void callOriginalCallback(bool success)
{ callOriginalCb(success); }
void callOriginalCallbackWithFailure()
{ callOriginalCallback(false); }
void destroyDeviceReq1(
std::shared_ptr<DestroyDeviceReq> context, bool success
)
{
context->deviceManager.devices.erase(
std::remove(
context->deviceManager.devices.begin(),
context->deviceManager.devices.end(),
context->pendingDevice),
context->deviceManager.devices.end());
context->callOriginalCallback(success);
}
};
void DeviceManager::destroyDeviceReq(
std::shared_ptr<Device> dev,
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback
)
{
/** EXPLANATION:
* Check to see if the device is in our collection. If so, call
* disconnectReq and then remove it.
*/
std::shared_ptr<Device> device = getDevice(dev->discoveredDevice).
value_or(nullptr);
if (!device || device->nAttachedStimBuffs > 0)
{
callback.callbackFn(false);
return;
}
auto request = std::make_shared<DestroyDeviceReq>(
*this, device, std::move(callback));
device->disconnectReq(
{request, std::bind(
&DeviceManager::DestroyDeviceReq::destroyDeviceReq1,
request.get(), request, std::placeholders::_1)});
}
void main(const std::shared_ptr<smo::ComponentThread> &componentThread,
const smo::stim_buff::SmoCallbacks& smoCallbacks)
{
if (protoState.isInitialized) {
return;
}
protoState.isInitialized = true;
protoState.componentThread = componentThread;
protoState.smoCallbacks = smoCallbacks;
protoState.deviceManager = std::make_unique<DeviceManager>();
protoState.deviceManager->broadcastListener.start();
protoState.deviceManager->udpCommandDemuxer.start();
}
void exit(void)
{
if (!protoState.isInitialized) {
return;
}
protoState.deviceManager->udpCommandDemuxer.stop();
protoState.deviceManager->broadcastListener.stop();
protoState.deviceManager.reset();
protoState.componentThread.reset();
protoState.isInitialized = false;
}
} // namespace livoxProto1
+80
View File
@@ -0,0 +1,80 @@
#ifndef LIVOXPROTO1_CORE_H
#define LIVOXPROTO1_CORE_H
#include <vector>
#include <string>
#include <memory>
#include <cstdint>
#include <optional>
#include <user/senseApiDesc.h>
#include "device.h"
#include "broadcastListener.h"
#include "udpCommandDemuxer.h"
#include "livoxProto1.h"
#include <callback.h>
namespace livoxProto1 {
class DeviceManager
{
public:
DeviceManager();
~DeviceManager() = default;
static void deviceGoneAwayInd(const comms::DiscoveredDevice &device);
void getOrCreateDeviceReq(
const std::string &deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread,
int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
void destroyDeviceReq(
std::shared_ptr<Device> device,
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback);
std::optional<std::shared_ptr<Device>> getDevice(
const std::string &deviceIdentifier);
std::optional<std::shared_ptr<Device>> getDevice(
const comms::DiscoveredDevice &device)
{
return getDevice(device.deviceIdentifier);
}
private:
// Configuration
static constexpr int RETRY_DELAY_SECONDS = 3; // <N> seconds delay
public:
std::vector<std::shared_ptr<Device>> devices;
comms::BroadcastListener broadcastListener;
comms::UdpCommandDemuxer udpCommandDemuxer;
// Nested continuation class for async device creation
class GetOrCreateDeviceReq;
class DestroyDeviceReq;
};
void main(
const std::shared_ptr<smo::ComponentThread> &componentThread,
const smo::stim_buff::SmoCallbacks& smoCallbacks);
void exit(void);
// Global state structure
struct ProtoState
{
bool isInitialized = false;
std::shared_ptr<smo::ComponentThread> componentThread;
std::unique_ptr<DeviceManager> deviceManager;
smo::stim_buff::SmoCallbacks smoCallbacks;
};
// Access to global state for extern "C" functions
ProtoState& getProtoState();
} // namespace livoxProto1
#endif // LIVOXPROTO1_CORE_H
File diff suppressed because it is too large Load Diff
+238
View File
@@ -0,0 +1,238 @@
#ifndef LIVOX_PROTO1_DEVICE_H
#define LIVOX_PROTO1_DEVICE_H
#include <boostAsioLinkageFix.h>
#include <string>
#include <cstdint>
#include <memory>
#include <atomic>
#include <optional>
#include <functional>
#include <unordered_map>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include "protocol.h"
#include <callback.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
namespace smo {
class ComponentThread;
}
namespace livoxProto1 {
namespace comms {
/** EXPLANATION:
* This class represents a discovered device. It is used to store the
* device identifier and IP address of a discovered device.
*/
class DiscoveredDevice
{
public:
DiscoveredDevice(
const std::string &deviceIdentifier,
DeviceType deviceType,
const std::string &ipAddr);
// "Conversion" constructor from BroadcastMessage
DiscoveredDevice(const BroadcastMessage &msg, const std::string &ipAddr);
~DiscoveredDevice() = default;
bool operator==(const DiscoveredDevice &other) const
{
return comms::deviceIdentifiersEqual(
deviceIdentifier, other.deviceIdentifier);
}
std::string stringify(void) const;
std::string getDeviceTypeName(void) const;
public:
std::string deviceIdentifier;
DeviceType deviceType;
std::string ipAddr;
};
} // namespace comms
class Device
{
public:
Device(const std::string &deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread,
int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
~Device();
private:
// Heartbeat mechanism
void startHeartbeat();
void stopHeartbeat();
void sendHeartbeat();
void onHeartbeatTimer(const boost::system::error_code& error);
std::string generateClientDeviceIpFromSerialNumber(
const std::string& broadcastCode);
// IP detection methods
std::optional<std::string> detectSmoIp(const std::string& deviceIP);
uint32_t getSubnetMaskFor(uint8_t nbits);
class ConnectReq;
class ConnectToKnownDeviceReq;
class ConnectByDeviceIdentifierReq;
class ExecuteHandshakeReq;
class DisconnectReq;
class EnablePcloudDataReq;
class DisablePcloudDataReq;
class SetReturnModeReq;
class GetReturnModeReq;
public:
enum class ReturnMode : uint8_t
{
SingleFirst = 0x00,
SingleStrongest = 0x01,
Dual = 0x02,
Triple = 0x03
};
// Utility methods
std::optional<std::string> getSmoIp(const std::string& deviceIP);
// Callback function type definitions for async methods
typedef std::function<void(bool success)> connectReqCbFn;
typedef std::function<
void(bool success, const std::string& ipAddr)>
connectToKnownDeviceReqCbFn;
typedef std::function<
void(bool success, const std::string& ipAddr)>
connectByDeviceIdentifierReqCbFn;
typedef std::function<void(bool success)> executeHandshakeReqCbFn;
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
void connectReq(smo::Callback<connectReqCbFn> callback);
void connectToKnownDeviceReq(
smo::Callback<connectToKnownDeviceReqCbFn> callback);
void connectByDeviceIdentifierReq(
smo::Callback<connectByDeviceIdentifierReqCbFn> callback);
void executeHandshakeReq(
const std::string& deviceIP,
smo::Callback<executeHandshakeReqCbFn> callback);
void disconnectReq(smo::Callback<disconnectReqCbFn> callback);
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> nAttachedStimBuffs;
// 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
std::unique_ptr<boost::asio::deadline_timer> heartbeatTimer;
std::atomic<bool> heartbeatActive;
// 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
#endif // LIVOX_PROTO1_DEVICE_H
+123
View File
@@ -0,0 +1,123 @@
#include <boostAsioLinkageFix.h>
#include <stdexcept>
#include <callback.h>
#include <boost/asio/posix/stream_descriptor.hpp>
#include "livoxProto1.h"
#include "device.h"
#include "core.h"
#include "udpCommandDemuxer.h"
extern "C" {
void livoxProto1_getOrCreateDeviceReq(
const std::string& deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread,
int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback
)
{
// Get the global DeviceManager instance
auto& protoState = livoxProto1::getProtoState();
if (!protoState.deviceManager)
{
throw std::runtime_error(
std::string(__func__) + ": LivoxProto1 not initialized - call "
"livoxProto1_main first");
}
// Delegate to DeviceManager
protoState.deviceManager->getOrCreateDeviceReq(
deviceIdentifier, componentThread,
commandTimeoutMs, retryDelayMs,
smoIp, smoSubnetNbits,
dataPort, cmdPort, imuPort,
callback);
}
void livoxProto1_destroyDeviceReq(
std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback
)
{
auto& protoState = livoxProto1::getProtoState();
if (!protoState.deviceManager)
{
throw std::runtime_error(std::string(__func__)
+ ": DeviceManager not initialized");
}
protoState.deviceManager->destroyDeviceReq(
device, callback);
}
void livoxProto1_main(
const std::shared_ptr<smo::ComponentThread>& componentThread,
const smo::stim_buff::SmoCallbacks& smoCallbacks)
{
livoxProto1::main(componentThread, smoCallbacks);
}
void livoxProto1_exit(void)
{
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"
+107
View File
@@ -0,0 +1,107 @@
#ifndef LIVOXPROTO1_H
#define LIVOXPROTO1_H
#include <boostAsioLinkageFix.h>
#include <memory>
#include <string>
#include <cstdint>
#include <functional>
#include <callback.h>
#include <boost/asio/posix/stream_descriptor.hpp>
// Forward declarations
namespace smo {
namespace stim_buff {
struct SmoCallbacks;
}
class ComponentThread;
}
namespace livoxProto1 {
class Device;
}
#ifdef __cplusplus
extern "C" {
#endif
/**
* Initialize the Livox protocol library
* @param componentThread Component thread shared pointer
* @param smoCallbacks Callbacks provided by SMO
*/
typedef void livoxProto1_mainFn(
const std::shared_ptr<smo::ComponentThread>& componentThread,
const smo::stim_buff::SmoCallbacks& smoCallbacks);
/**
* Cleanup the Livox protocol library
*/
typedef void livoxProto1_exitFn(void);
/**
* Create a new Livox device connection
* @param deviceIdentifier The device identifier (broadcast code)
* @param componentThread Component thread for async operations
* @param commandTimeoutMs Command timeout in milliseconds (default: 1000)
* @param retryDelayMs Retry delay in milliseconds (default: 3000)
* @param smoIp SMO IP address (empty string for auto-detection)
* @param smoSubnetNbits SMO subnet mask bits (e.g., 24 for /24, 16 for /16)
* @param dataPort Data port for point cloud (default: 56000)
* @param cmdPort Command port (default: 56001)
* @param imuPort IMU port (default: 56002)
* @return Device pointer on success, nullptr on failure
*/
typedef std::function<
void(bool success, std::shared_ptr<livoxProto1::Device> device)>
livoxProto1_getOrCreateDeviceReqCbFn;
typedef void livoxProto1_getOrCreateDeviceReqFn(
const std::string& deviceIdentifier,
const std::shared_ptr<smo::ComponentThread>& componentThread,
int commandTimeoutMs, int retryDelayMs,
const std::string& smoIp, uint8_t smoSubnetNbits,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort,
smo::Callback<livoxProto1_getOrCreateDeviceReqCbFn> callback);
typedef std::function<void(bool success)> livoxProto1_destroyDeviceReqCbFn;
typedef void livoxProto1_destroyDeviceReqFn(
std::shared_ptr<livoxProto1::Device> device,
smo::Callback<livoxProto1_destroyDeviceReqCbFn> callback);
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_exitFn livoxProto1_exit;
livoxProto1_getOrCreateDeviceReqFn livoxProto1_getOrCreateDeviceReq;
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
}
#endif
#endif // LIVOXPROTO1_H
+854
View File
@@ -0,0 +1,854 @@
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <cstring>
#include "protocol.h"
namespace livoxProto1 {
namespace comms {
// Command methods
void Command::swapToHostEndianness()
{
// No multi-byte fields to swap
}
void Command::swapToProtocolEndianness()
{
// No multi-byte fields to swap
}
bool Command::sanityCheck() const
{
// Basic validation - can be extended for specific command sets
return true;
}
// Header methods
void Header::swapToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
length = __builtin_bswap16(length);
seq_num = __builtin_bswap16(seq_num);
crc_16 = __builtin_bswap16(crc_16);
}
void Header::swapToProtocolEndianness()
{
// Protocol is little-endian, so if host is already little-endian, no swap needed
if (endian::isLittleEndian()) { return; }
// Host is big-endian, need to swap to little-endian
length = __builtin_bswap16(length);
seq_num = __builtin_bswap16(seq_num);
crc_16 = __builtin_bswap16(crc_16);
}
bool Header::sanityCheck() const
{
return (sof == 0xAA) && (version == 1);
}
uint16_t Header::calculateCrc16() const
{
// Calculate CRC16 for the header excluding the crc_16 field itself
// This matches the Livox SDK approach: calculate over raw bytes excluding CRC16 field
const uint8_t* headerData = reinterpret_cast<const uint8_t*>(this);
size_t headerSize = sizeof(Header) - sizeof(crc_16); // Exclude CRC16 field
return comms::calculateCrc16(headerData, headerSize);
}
bool Header::validateCrc16() const
{
// Calculate CRC16 for the header excluding the crc_16 field itself
uint16_t calculatedCrc = calculateCrc16();
// Compare with the CRC in the header
bool isValid = (calculatedCrc == crc_16);
// Debug output only if validation fails
if (!isValid) {
std::cout << "CRC16 Debug: calculated=0x" << std::hex << calculatedCrc
<< ", received=0x" << crc_16 << std::dec << std::endl;
}
return isValid;
}
void Header::setCrc16FromRawBytes()
{
// Calculate CRC16 on raw bytes and set it (after endianness swap)
crc_16 = calculateCrc16();
}
void Header::swapCrc16ToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
crc_16 = __builtin_bswap16(crc_16);
}
void Header::swapCrc16ToProtocolEndianness()
{
if (endian::isLittleEndian()) { return; }
crc_16 = __builtin_bswap16(crc_16);
}
// Footer methods
void Footer::swapToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
crc_32 = __builtin_bswap32(crc_32);
}
void Footer::swapToProtocolEndianness()
{
// Protocol is little-endian, so if host is already little-endian, no swap needed
if (endian::isLittleEndian()) { return; }
// Host is big-endian, need to swap to little-endian
crc_32 = __builtin_bswap32(crc_32);
}
void Footer::swapCrc32ToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
crc_32 = __builtin_bswap32(crc_32);
}
void Footer::swapCrc32ToProtocolEndianness()
{
if (endian::isLittleEndian()) { return; }
crc_32 = __builtin_bswap32(crc_32);
}
bool Footer::validateCrc32() const
{
// This method should validate the CRC32 against the message content
// For now, we'll return true since the validation is done on raw bytes
// before struct construction in the receiving flow
return true;
}
bool Footer::sanityCheck() const
{
/** FIXME:
* Add CRC validation here.
*/
return true;
}
// BroadcastMessage methods
void BroadcastMessage::swapContentsToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
// Only swap content fields, not CRC fields
header.swapToHostEndianness();
command.swapToHostEndianness();
reserved = __builtin_bswap16(reserved);
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
}
bool BroadcastMessage::sanityCheck() const
{
return header.sanityCheck() &&
command.sanityCheck() &&
(command.cmd_set == 0x00) &&
(command.cmd_id == 0x00) &&
(header.cmd_type == 0x02) &&
footer.sanityCheck();
}
bool BroadcastMessage::validateCrc32() const
{
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
// Try calculating on the raw bytes of the entire message (excluding CRC field)
uint32_t calculatedCrc = 0xFFFFFFFF;
// Calculate CRC32 over the entire message except the CRC field itself
// The message structure is: header + command + broadcast_code + dev_type + reserved + footer(without crc_32)
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(BroadcastMessage) - sizeof(footer.crc_32);
calculatedCrc = comms::calculateCrc32(messageData, messageSize);
// Compare with the CRC in the footer
bool isValid = (calculatedCrc == footer.crc_32);
// Debug output only if validation fails
if (!isValid) {
std::cout << "BroadcastMessage CRC32 Debug: calculated=0x" << std::hex << calculatedCrc
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
}
return isValid;
}
// HandshakeRequest methods
HandshakeRequest::HandshakeRequest(
const std::string& hostIP,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort
)
{
// Initialize header
header.sof = 0xAA;
header.version = 1;
header.length = sizeof(HandshakeRequest);
header.cmd_type = 0x00; // CMD (request)
header.seq_num = 1; // Sequence number
header.crc_16 = 0; // Will be calculated later
// Initialize command
command.cmd_set = 0x00; // General Command Set
command.cmd_id = 0x01; // Handshake Command
// Parse host IP address
std::istringstream iss(hostIP);
std::string token;
int i = 0;
while (std::getline(iss, token, '.') && i < 4)
{
user_ip[i] = static_cast<uint8_t>(std::stoi(token));
i++;
}
// Set ports
this->data_port = dataPort;
this->cmd_port = cmdPort;
this->imu_port = imuPort;
// Initialize footer
footer.crc_32 = 0; // Will be calculated later
// Note: CRC16 will be calculated before sending (in swapToProtocolEndianness)
}
uint32_t HandshakeRequest::calculateCrc32() const
{
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(HandshakeRequest) - sizeof(footer.crc_32);
return comms::calculateCrc32(messageData, messageSize);
}
void HandshakeRequest::swapContentsToProtocolEndianness()
{
// Protocol uses little-endian, so on little-endian machines, no swap needed
if (endian::isLittleEndian()) { return; }
// On big-endian machines, swap to little-endian for wire transmission
// Only swap content fields, not CRC fields
header.swapToHostEndianness();
command.swapToHostEndianness();
data_port = __builtin_bswap16(data_port);
cmd_port = __builtin_bswap16(cmd_port);
imu_port = __builtin_bswap16(imu_port);
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
}
// HandshakeResponse methods
void HandshakeResponse::swapContentsToHostEndianness()
{
if (endian::isLittleEndian()) { return; }
// Only swap content fields, not CRC fields
header.swapToHostEndianness();
command.swapToHostEndianness();
// Note: footer.swapToHostEndianness() swaps CRC, so we skip it here
}
bool HandshakeResponse::sanityCheck() const
{
return header.sanityCheck() &&
command.sanityCheck() &&
(command.cmd_set == 0x00) && (command.cmd_id == 0x01) &&
footer.sanityCheck();
}
bool HandshakeResponse::validateCrc32() const
{
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(HandshakeResponse) - sizeof(footer.crc_32);
uint32_t calculatedCrc = comms::calculateCrc32(messageData, messageSize);
// Compare with the CRC in the footer
bool isValid = (calculatedCrc == footer.crc_32);
// Debug output only if validation fails
if (!isValid) {
std::cout << "HandshakeResponse CRC32 Debug: calculated=0x" << std::hex << calculatedCrc
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
}
return isValid;
}
// Standalone CRC16 calculation utility
uint16_t calculateCrc16(const uint8_t* data, size_t length)
{
/** EXPLANATION:
* Livox SDK CRC16 implementation (exact copy from FastCRC library)
* This matches the exact implementation used by Livox devices
*/
static const uint16_t crc_table_mcrf4xx[1024] = {
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};
// Livox SDK seed
uint16_t crc = LIVOX_CRC16_SEED;
// Simple implementation for now - can be optimized later
for (size_t i = 0; i < length; ++i) {
crc = (crc >> 8) ^ crc_table_mcrf4xx[(crc & 0xff) ^ data[i]];
}
return crc;
}
// Standalone CRC32 calculation utility
uint32_t calculateCrc32(const uint8_t* data, size_t length)
{
/** EXPLANATION:
* Livox SDK CRC32 implementation (exact copy from FastCRC library)
* This matches the exact implementation used by Livox devices
*/
static const uint32_t crc_table_crc32[256] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
// Livox SDK seed XORed with 0xffffffff
uint32_t crc = LIVOX_CRC32_SEED ^ 0xffffffff;
for (size_t i = 0; i < length; ++i) {
crc = (crc >> 8) ^ crc_table_crc32[(crc & 0xff) ^ data[i]];
}
return crc ^ 0xffffffff;
}
// IP address parsing utility
std::optional<IPOctets> parseIPv4Address(const std::string& ipAddress)
{
IPOctets result;
std::istringstream iss(ipAddress);
if (std::getline(iss, result.octet1, '.') &&
std::getline(iss, result.octet2, '.') &&
std::getline(iss, result.octet3, '.') &&
std::getline(iss, result.octet4, '.'))
{
return result;
}
return std::nullopt;
}
// HeartbeatMessage methods
HeartbeatMessage::HeartbeatMessage()
{
// Initialize header
header.sof = 0xAA;
header.version = 0x01;
header.length = sizeof(Header) + sizeof(Command) + sizeof(Footer);
header.cmd_type = 0x00; // kCommandTypeCmd
header.seq_num = 0x0001; // Simple sequence number
header.crc_16 = 0; // Will be calculated
// Initialize command
command.cmd_set = 0x00; // kCommandSetGeneral
command.cmd_id = 0x03; // kCommandIDGeneralHeartbeat
// Initialize footer
footer.crc_32 = 0; // Will be calculated
// Note: CRC16 will be calculated before sending (in swapToProtocolEndianness)
}
uint32_t HeartbeatMessage::calculateCrc32() const
{
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(HeartbeatMessage) - sizeof(footer.crc_32);
return comms::calculateCrc32(messageData, messageSize);
}
void HeartbeatMessage::swapContentsToProtocolEndianness()
{
// Protocol is little-endian, so if host is already little-endian, no swap needed
if (endian::isLittleEndian()) {
return;
}
// Host is big-endian, need to swap to little-endian
// Only swap content fields, not CRC fields
header.swapToProtocolEndianness();
command.swapToProtocolEndianness();
// Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here
}
// DisconnectMessage methods
DisconnectMessage::DisconnectMessage()
{
// Initialize header
header.sof = 0xAA;
header.version = 0x01;
header.length = sizeof(Header) + sizeof(Command) + sizeof(Footer);
header.cmd_type = 0x00; // kCommandTypeCmd
header.seq_num = 0x0001; // Simple sequence number
header.crc_16 = 0; // Will be calculated
// Initialize command
command.cmd_set = 0x00; // kCommandSetGeneral
command.cmd_id = 0x06; // kCommandIDGeneralDisconnect
// Initialize footer
footer.crc_32 = 0; // Will be calculated
// Note: CRC16 will be calculated before sending (in swapToProtocolEndianness)
}
uint32_t DisconnectMessage::calculateCrc32() const
{
// Calculate CRC32 for the entire message excluding the footer.crc_32 field
const uint8_t* messageData = reinterpret_cast<const uint8_t*>(this);
size_t messageSize = sizeof(DisconnectMessage) - sizeof(footer.crc_32);
return comms::calculateCrc32(messageData, messageSize);
}
void DisconnectMessage::swapContentsToProtocolEndianness()
{
// Protocol is little-endian, so if host is already little-endian, no swap needed
if (endian::isLittleEndian()) {
return;
}
// Host is big-endian, need to swap to little-endian
// Only swap content fields, not CRC fields
header.swapToProtocolEndianness();
command.swapToProtocolEndianness();
// Note: footer.swapToProtocolEndianness() swaps CRC, so we skip it here
}
// StartStopSamplingMessage methods
StartStopSamplingMessage::StartStopSamplingMessage()
{
// Initialize header
header.sof = 0xAA;
header.version = 1;
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
}
uint32_t StartStopSamplingMessage::calculateCrc32() 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(StartStopSamplingMessage) - sizeof(footer.crc_32);
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);
// Debug output only if validation fails
if (!isValid)
{
std::cout << "SamplingResponse CRC32 Debug: calculated=0x"
<< std::hex << calculatedCrc
<< ", received=0x" << footer.crc_32 << std::dec << std::endl;
}
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 livoxProto1
+361
View File
@@ -0,0 +1,361 @@
#ifndef LIVOXPROTO1_PROTOCOL_H
#define LIVOXPROTO1_PROTOCOL_H
#include <boostAsioLinkageFix.h>
#include <vector>
#include <string>
#include <memory>
#include <sstream>
#include <atomic>
#include <cstdint>
#include <boost/asio/ip/address_v4.hpp>
#include <user/senseApiDesc.h>
namespace livoxProto1 {
namespace comms {
/** EXPLANATION:
* Undocumented Livox SDK CRC seed constants. These were found in the Livox SDK
* source code.
*/
constexpr uint16_t LIVOX_CRC16_SEED = 0x4c49;
constexpr uint32_t LIVOX_CRC32_SEED = 0x564f580a;
// Endianness detection
namespace endian {
inline bool isLittleEndian() {
union {
uint32_t i;
char c[4];
} test = {0x01020304};
return test.c[0] == 4;
}
}
// IPv4 address validation
inline bool isValidIPv4(const std::string& ipAddress) {
boost::system::error_code ec;
boost::asio::ip::address_v4::from_string(ipAddress, ec);
return !ec;
}
// CRC calculation utilities
uint16_t calculateCrc16(
const uint8_t* data, size_t length);
uint32_t calculateCrc32(
const uint8_t* data, size_t length);
// IP address parsing utility
struct IPOctets {
std::string octet1, octet2, octet3, octet4;
};
std::optional<IPOctets> parseIPv4Address(const std::string& ipAddress);
// Device identifier comparison
inline bool deviceIdentifiersEqual(
const std::string& id1, const std::string& id2
)
{
// Use pointers to avoid unnecessary string copies
const std::string* serial1_ptr;
const std::string* serial2_ptr;
// Local copies only needed for 15-character broadcast codes
std::string serial1_copy, serial2_copy;
// Determine if id1 is serial (14 chars) or broadcast code (15 chars)
if (id1.length() == 14) {
serial1_ptr = &id1; // No copy needed, use original string
} else if (id1.length() == 15) {
serial1_copy = id1.substr(0, 14); // Copy only when necessary
serial1_ptr = &serial1_copy;
} else {
return false; // Invalid length
}
// Determine if id2 is serial (14 chars) or broadcast code (15 chars)
if (id2.length() == 14) {
serial2_ptr = &id2; // No copy needed, use original string
} else if (id2.length() == 15) {
serial2_copy = id2.substr(0, 14); // Copy only when necessary
serial2_ptr = &serial2_copy;
} else {
return false; // Invalid length
}
// Compare the serial numbers using pointers
return *serial1_ptr == *serial2_ptr;
}
/** EXPLANATION:
* Device types as defined in the Livox protocol specification
*/
enum class DeviceType : uint8_t {
Hub = 0,
Mid40 = 1,
Tele15 = 2,
Horizon = 3,
Mid70 = 6,
Avia = 7
};
/** EXPLANATION:
* Protocol frame header structure.
* All multi-byte fields are in little-endian format as per protocol spec.
*/
struct Header
{
uint8_t sof; // 0: Start of Frame (0xAA)
uint8_t version; // 1: Protocol Version (1)
uint16_t length; // 2-3: Frame Length (little-endian)
uint8_t cmd_type; // 4: Command Type (0x02 = MSG for broadcast)
uint16_t seq_num; // 5-6: Sequence Number (little-endian)
uint16_t crc_16; // 7-8: Header Checksum (little-endian)
void swapToHostEndianness();
void swapToProtocolEndianness();
void swapCrc16ToHostEndianness();
void swapCrc16ToProtocolEndianness();
bool sanityCheck() const;
uint16_t calculateCrc16() const;
bool validateCrc16() const;
void setCrc16FromRawBytes();
} __attribute__((packed));
/** EXPLANATION:
* Protocol frame footer structure.
* All multi-byte fields are in little-endian format as per protocol spec.
*/
struct Footer
{
uint32_t crc_32; // 0-3: Whole Frame Checksum (little-endian)
void swapToHostEndianness();
void swapToProtocolEndianness();
void swapCrc32ToHostEndianness();
void swapCrc32ToProtocolEndianness();
bool validateCrc32() const;
bool sanityCheck() const;
} __attribute__((packed));
/** EXPLANATION:
* Command identification structure used in all Livox protocol messages.
* Contains the command set and command ID fields.
*/
struct Command
{
uint8_t cmd_set; // 0: Command Set (0x00 = General, etc.)
uint8_t cmd_id; // 1: Command ID (0x00 = Broadcast, 0x01 = Handshake, etc.)
void swapToHostEndianness();
void swapToProtocolEndianness();
bool sanityCheck() const;
} __attribute__((packed));
/** EXPLANATION:
* Complete wire format for Livox broadcast messages.
* All multi-byte fields are in little-endian format as per protocol spec.
*/
struct BroadcastMessage
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
uint8_t broadcast_code[16]; // 11-26: Device Broadcast Code (null-terminated string)
uint8_t dev_type; // 27: Device Type
uint16_t reserved; // 28-29: Reserved (little-endian)
Footer footer; // 30-33: Protocol frame footer
void swapContentsToHostEndianness();
bool sanityCheck() const;
bool validateCrc32() const;
} __attribute__((packed));
/** EXPLANATION:
* Complete handshake request frame for connecting to Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct HandshakeRequest
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
uint8_t user_ip[4]; // 11-14: Host IP Address (little-endian)
uint16_t data_port; // 15-16: Host Point Cloud Data UDP Destination Port (little-endian)
uint16_t cmd_port; // 17-18: Host Control Command UDP Destination Port (little-endian)
uint16_t imu_port; // 19-20: Host IMU UDP Destination Port (little-endian)
Footer footer; // 21-24: Protocol frame footer
HandshakeRequest(
const std::string& hostIP,
uint16_t dataPort, uint16_t cmdPort, uint16_t imuPort);
// Calculate CRC32 for the entire message
uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness();
} __attribute__((packed));
/** EXPLANATION:
* Complete handshake response frame from Livox devices.
* This is the complete wire format including header, command fields, data, and footer.
*/
struct HandshakeResponse
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
uint8_t ret_code; // 11: Return Code (0x00 = Success, 0x01 = Fail)
Footer footer; // 12-15: Protocol frame footer
void swapContentsToHostEndianness();
bool sanityCheck() const;
bool validateCrc32() const;
} __attribute__((packed));
/** EXPLANATION:
* Complete heartbeat command frame for maintaining connection with Livox devices.
* This is the complete wire format including header, command fields, and footer.
*/
struct HeartbeatMessage
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
Footer footer; // 11-14: Protocol frame footer
HeartbeatMessage();
uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness();
} __attribute__((packed));
/** EXPLANATION:
* Complete disconnect command frame for disconnecting from Livox devices.
* This is the complete wire format including header, command fields, and footer.
*/
struct DisconnectMessage
{
Header header; // 0-8: Protocol frame header
Command command; // 9-10: Command identification
Footer footer; // 11-14: Protocol frame footer
DisconnectMessage();
uint32_t calculateCrc32() const;
void swapContentsToProtocolEndianness();
} __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 validateCrc32() const;
} __attribute__((packed));
} // namespace comms
} // namespace livoxProto1
#endif // LIVOXPROTO1_PROTOCOL_H
@@ -0,0 +1,331 @@
#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
{
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()
{
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;
}
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 device in temporary 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,97 @@
#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>
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
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
+20
View File
@@ -0,0 +1,20 @@
option(ENABLE_LIB_xcbXorg "Enable XCB/Xorg Connection Manager backend lib" ON)
if(ENABLE_LIB_xcbXorg)
pkg_check_modules(XCB REQUIRED xcb)
if(NOT XCB_FOUND)
message(FATAL_ERROR "XCB library not found. XCB/Xorg requires the XCB dev headers and shlib.")
endif()
add_library(xcbXorg SHARED
xcbXorg.cpp
)
# Set config define for header generation
add_compile_definitions(CONFIG_LIB_XCBXORG_ENABLED)
target_include_directories(xcbXorg PUBLIC ${XCB_INCLUDE_DIRS})
target_link_libraries(xcbXorg ${XCB_LIBRARIES} attachmentSupport)
# Install rules
install(TARGETS xcbXorg DESTINATION lib)
endif()
+292
View File
@@ -0,0 +1,292 @@
#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <memory>
#include <vector>
#include <sstream>
#include <map>
#include <atomic>
#include <xcb/xcb.h>
#include "xcbXorg.h"
namespace xcb_xorg {
// Static member initialization
std::map<ConnectionIdentifier, std::shared_ptr<XcbConnection>>
ConnectionManager::connections;
std::string ConnectionIdentifier::stringify() const
{
std::ostringstream os;
os << "display=" << display << ", screen=" << screen;
return os.str();
}
XcbConnection::XcbConnection(const ConnectionIdentifier& id)
: connection(nullptr, &xcb_disconnect),
connectionIdentifier(id), refCount(0)
{
// Convert to X display string format (e.g., ":0.1")
std::string displayString = ":" + std::to_string(id.display)
+ "." + std::to_string(id.screen);
int screenNum;
connection.reset(xcb_connect(displayString.c_str(), &screenNum));
if (xcb_connection_has_error(connection.get()))
{
throw std::runtime_error(
std::string(__func__) + ": Failed to connect to X server "
+ connectionIdentifier.stringify());
}
// Verify we got the screen we asked for
if (screenNum != id.screen)
{
throw std::runtime_error(
std::string(__func__) + ": Connected to wrong screen. "
"Requested " + connectionIdentifier.stringify()
+ " but got screen " + std::to_string(screenNum));
}
}
std::shared_ptr<XcbConnection> ConnectionManager::getOrCreateConnection(
const ConnectionIdentifier& id)
{
auto it = connections.find(id);
if (it != connections.end()) {
return it->second;
}
auto conn = std::make_shared<XcbConnection>(id);
connections.emplace(id, conn);
return conn;
}
void ConnectionManager::cleanupAllConnections()
{
connections.clear();
}
size_t ConnectionManager::getConnectionCount()
{
return connections.size();
}
/**
* @brief Remove a specific connection from the manager
* @param id Connection identifier to remove
*/
void ConnectionManager::removeConnection(const ConnectionIdentifier& id)
{
auto it = connections.find(id);
if (it != connections.end()) {
connections.erase(it);
}
}
namespace window_search {
struct XcbReplyDeleter {
void operator()(xcb_query_tree_reply_t* p) { free(p); }
void operator()(xcb_get_property_reply_t* p) { free(p); }
};
xcb_window_t findById(
xcb_connection_t* conn, xcb_window_t root, uint32_t targetId)
{
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
xcb_query_tree_reply(conn, cookie, nullptr));
if (!reply) return 0;
if (root == targetId) return root;
xcb_window_t* children = xcb_query_tree_children(reply.get());
int num_children = xcb_query_tree_children_length(reply.get());
for (int i = 0; i < num_children; ++i)
{
if (children[i] == targetId) {
return children[i];
}
// Recursively search this child's subtree
if (xcb_window_t result = findById(conn, children[i], targetId)) {
return result;
}
}
return 0;
}
xcb_window_t findByName(
xcb_connection_t* conn, xcb_window_t root,
const std::string& targetName, std::string& outWindowName,
MatchType matchType)
{
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
xcb_query_tree_reply(conn, cookie, nullptr));
if (!reply) return 0;
// First check current window
xcb_get_property_cookie_t prop_cookie = xcb_get_property(
conn, 0, root, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1024);
std::unique_ptr<xcb_get_property_reply_t, XcbReplyDeleter> prop_reply(
xcb_get_property_reply(conn, prop_cookie, nullptr));
if (prop_reply)
{
int len = xcb_get_property_value_length(prop_reply.get());
char* name = static_cast<char*>(
xcb_get_property_value(prop_reply.get()));
if (len > 0)
{
std::string windowName(name, len);
if ((matchType == MatchType::EXACT
&& windowName == targetName)
|| (matchType == MatchType::SUBSTRING
&& windowName.find(targetName) != std::string::npos))
{
outWindowName = windowName;
return root;
}
}
}
// Then check all children
xcb_window_t* children = xcb_query_tree_children(reply.get());
int num_children = xcb_query_tree_children_length(reply.get());
for (int i = 0; i < num_children; ++i)
{
prop_cookie = xcb_get_property(
conn, 0, children[i],
XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1024);
prop_reply.reset(xcb_get_property_reply(conn, prop_cookie, nullptr));
if (prop_reply)
{
int len = xcb_get_property_value_length(prop_reply.get());
char* name = static_cast<char*>(xcb_get_property_value(
prop_reply.get()));
if (len > 0)
{
std::string windowName(name, len);
if ((matchType == MatchType::EXACT
&& windowName == targetName)
|| (matchType == MatchType::SUBSTRING
&& windowName.find(targetName) != std::string::npos))
{
outWindowName = windowName;
return children[i];
}
}
}
// Recursively search this child's subtree
if (xcb_window_t result = findByName(
conn, children[i], targetName, outWindowName, matchType))
{
return result;
}
}
return 0;
}
} // namespace window_search
} // namespace xcb_xorg
// Export functions for external libraries
/**
* @brief Get or create a connection to the X server
* @param display Display number
* @param screen Screen number
* @return Shared pointer to connection (as void* for C compatibility)
*/
extern "C" get_or_create_connection_fn xcb_xorg_get_or_create_connection;
std::shared_ptr<xcb_xorg::XcbConnection> xcb_xorg_get_or_create_connection(
int display, int screen)
{
xcb_xorg::ConnectionIdentifier id{display, screen};
auto conn = xcb_xorg::ConnectionManager::getOrCreateConnection(id);
conn->incrementRefCount();
return conn;
}
/**
* @brief Clean up all connections
*/
extern "C" cleanup_connections_fn xcb_xorg_cleanup_connections;
void xcb_xorg_cleanup_connections()
{
xcb_xorg::ConnectionManager::cleanupAllConnections();
}
/**
* @brief Get the number of active connections
* @return Number of active connections
*/
size_t xcb_xorg_get_connection_count()
{
return xcb_xorg::ConnectionManager::getConnectionCount();
}
/**
* @brief Find window by ID
* @param conn Connection pointer (from xcb_xorg_get_connection)
* @param root Root window
* @param targetId Target window ID
* @return Window ID if found, 0 if not found
*/
extern "C" find_window_by_id_fn xcb_xorg_find_window_by_id;
xcb_window_t xcb_xorg_find_window_by_id(void* conn, xcb_window_t root, uint32_t targetId)
{
if (!conn) return 0;
auto connection = static_cast<xcb_xorg::XcbConnection*>(conn);
return xcb_xorg::window_search::findById(
connection->getConnection(), root, targetId);
}
/**
* @brief Find window by name
* @param conn Connection pointer (from xcb_xorg_get_connection)
* @param root Root window
* @param targetName Target window name
* @param outWindowName Output parameter for actual window name
* @param matchType Type of name matching
* @return Window ID if found, 0 if not found
*/
extern "C" find_window_by_name_fn xcb_xorg_find_window_by_name;
xcb_window_t xcb_xorg_find_window_by_name(void* conn, xcb_window_t root,
const std::string& targetName, std::string& outWindowName,
xcb_xorg::window_search::MatchType matchType)
{
if (!conn) return 0;
auto connection = static_cast<xcb_xorg::XcbConnection*>(conn);
return xcb_xorg::window_search::findByName(
connection->getConnection(), root, targetName, outWindowName,
matchType);
}
/**
* @brief Dereference a connection (decrements ref count and closes if zero)
* @param conn Shared pointer to the connection to dereference
*/
extern "C" dereference_connection_fn xcb_xorg_dereference_connection;
void xcb_xorg_dereference_connection(std::shared_ptr<xcb_xorg::XcbConnection> conn)
{
if (!conn) return;
int newRefCount = conn->decrementRefCount();
// Remove from connection manager if ref count reaches zero
if (newRefCount <= 0) {
xcb_xorg::ConnectionManager::removeConnection(conn->getIdentifier());
}
}
+184
View File
@@ -0,0 +1,184 @@
#ifndef XCB_XORG_API_H
#define XCB_XORG_API_H
#include <string>
#include <vector>
#include <memory>
#include <atomic>
#include <map>
#include <xcb/xcb.h>
namespace xcb_xorg {
/**
* @brief Connection identifier for X server connections
*/
struct ConnectionIdentifier
{
int display;
int screen;
bool operator<(const ConnectionIdentifier& other) const
{
if (display != other.display) return display < other.display;
return screen < other.screen;
}
std::string stringify() const;
};
/**
* @brief Represents a single X server connection using XCB
*
* This class manages a single connection to the X server. It provides
* RAII management for the connection and maintains a reference count
* for shared usage.
*/
class XcbConnection
{
public:
/**
* @brief Constructor for creating a new connection
* @param id Connection identifier specifying display and screen
* @throws std::runtime_error if connection fails
*/
explicit XcbConnection(const ConnectionIdentifier& id);
// Delete copy/move operations - we'll manage instances through pointers
XcbConnection(const XcbConnection&) = delete;
XcbConnection& operator=(const XcbConnection&) = delete;
XcbConnection(XcbConnection&&) = delete;
XcbConnection& operator=(XcbConnection&&) = delete;
/**
* @brief Destructor - automatically closes the connection
*/
~XcbConnection() = default;
/**
* @brief Get the underlying XCB connection
* @return Raw XCB connection pointer
*/
xcb_connection_t* getConnection() const { return connection.get(); }
/**
* @brief Get the connection identifier
* @return Connection identifier
*/
const ConnectionIdentifier& getIdentifier() const { return connectionIdentifier; }
/**
* @brief Increment reference count
*/
void incrementRefCount() { refCount++; }
/**
* @brief Decrement reference count
* @return New reference count
*/
int decrementRefCount() { return --refCount; }
/**
* @brief Get current reference count
* @return Current reference count
*/
int getRefCount() const { return refCount; }
/**
* @brief Check if connection is valid
* @return true if connection is valid, false otherwise
*/
bool isValid() const { return connection && !xcb_connection_has_error(connection.get()); }
private:
std::unique_ptr<xcb_connection_t, decltype(&xcb_disconnect)> connection;
ConnectionIdentifier connectionIdentifier;
std::atomic<int> refCount;
};
/**
* @brief Manages multiple X server connections
*
* This class manages a collection of XcbConnection instances. It ensures
* that each unique display and screen combination has a single connection,
* and provides RAII management for these connections.
*/
class ConnectionManager
{
public:
/**
* @brief Get or create a connection to the X server
* @param id Connection identifier specifying display and screen
* @return Shared pointer to the connection
* @throws std::runtime_error if connection fails
*/
static std::shared_ptr<XcbConnection> getOrCreateConnection(
const ConnectionIdentifier& id);
/**
* @brief Clean up all connections
*/
static void cleanupAllConnections();
/**
* @brief Get the number of active connections
* @return Number of active connections
*/
static size_t getConnectionCount();
/**
* @brief Remove a specific connection from the manager
* @param id Connection identifier to remove
*/
static void removeConnection(const ConnectionIdentifier& id);
private:
static std::map<ConnectionIdentifier, std::shared_ptr<XcbConnection>> connections;
};
/**
* @brief Window search functionality
*/
namespace window_search {
enum class MatchType { SUBSTRING, EXACT, ID };
/**
* @brief Find window by ID
* @param conn XCB connection
* @param root Root window
* @param targetId Target window ID
* @return Window ID if found, 0 if not found
*/
xcb_window_t findById(xcb_connection_t* conn, xcb_window_t root, uint32_t targetId);
/**
* @brief Find window by name
* @param conn XCB connection
* @param root Root window
* @param targetName Target window name
* @param outWindowName Output parameter for actual window name
* @param matchType Type of name matching to perform
* @return Window ID if found, 0 if not found
*/
xcb_window_t findByName(xcb_connection_t* conn, xcb_window_t root,
const std::string& targetName, std::string& outWindowName,
MatchType matchType);
} // namespace window_search
} // namespace xcb_xorg
// Function signature types for dynamic loading of libxcbXorg
typedef std::shared_ptr<xcb_xorg::XcbConnection> get_or_create_connection_fn(
int display, int screen);
typedef void cleanup_connections_fn();
typedef void dereference_connection_fn(
std::shared_ptr<xcb_xorg::XcbConnection> conn);
typedef xcb_window_t find_window_by_id_fn(
void* conn, xcb_window_t root, uint32_t targetId);
typedef xcb_window_t find_window_by_name_fn(void* conn, xcb_window_t root,
const std::string& targetName, std::string& outWindowName,
xcb_xorg::window_search::MatchType matchType);
#endif // XCB_XORG_API_H
+19
View File
@@ -0,0 +1,19 @@
if(COMPILE_CL_CHECKS)
find_package(OpenCL REQUIRED)
add_executable(clshmemlatency clshmemlatency.cpp)
target_include_directories(clshmemlatency
PUBLIC ${OpenCL_INCLUDE_DIRS})
target_link_libraries(clshmemlatency
${OpenCL_LIBRARY})
add_executable(clshmemcheck clshmemcheck.cpp)
target_include_directories(clshmemcheck
PUBLIC ${OpenCL_INCLUDE_DIRS})
target_link_libraries(clshmemcheck
${OpenCL_LIBRARY})
add_executable(clzerocopycheck clzerocopycheck.cpp)
target_include_directories(clzerocopycheck
PUBLIC ${OpenCL_INCLUDE_DIRS})
target_link_libraries(clzerocopycheck
${OpenCL_LIBRARY})
endif()
+90
View File
@@ -0,0 +1,90 @@
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <cstring>
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_command_queue q = clCreateCommandQueue(ctx, devices[d], 0, &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);
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;
}
+181
View File
@@ -0,0 +1,181 @@
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <cstring>
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_command_queue q = clCreateCommandQueue(ctx, devices[d], 0, &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 < 5; ++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;
}
+115
View File
@@ -0,0 +1,115 @@
#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_command_queue q = clCreateCommandQueue(ctx, dev, 0, &err);
CHECK(err, "clCreateCommandQueue");
// 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] != 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;
}
-65
View File
@@ -1,65 +0,0 @@
AC_INIT([Harriman-Peikoff Project], [0.00.000],
[latentprion@gmail.com],
[harikoff],
[github.com/latentprion/harikoff])
AC_CONFIG_SRCDIR([hcore/mind.cpp])
AC_CONFIG_AUX_DIR([autotools-aux])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([foreign -Wall -Werror])
# Set the mind's quantized virtual oscillator period in milliseconds.
# Default value is 33 ms, user override via MIND_VOSCILLATOR_PERIOD_MS.
# Check if MIND_VOSCILLATOR_PERIOD_MS is a valid positive integer
AC_ARG_VAR([MIND_VOSCILLATOR_PERIOD_MS], m4_normalize([
Mind's virtual osc clock rate. Must be a positive integer, default value 33
]))
AS_IF([test -z "${MIND_VOSCILLATOR_PERIOD_MS}"], [MIND_VOSCILLATOR_PERIOD_MS=33])
AS_IF([! test "${MIND_VOSCILLATOR_PERIOD_MS}" -eq "${MIND_VOSCILLATOR_PERIOD_MS}" 2>/dev/null ||
test "${MIND_VOSCILLATOR_PERIOD_MS}" -le 0 2>/dev/null], [
AC_MSG_ERROR([MIND_VOSCILLATOR_PERIOD_MS must be a positive integer > 0.])
])
AC_DEFINE_UNQUOTED([CONFIG_MIND_VOSCILLATOR_PERIOD_MS],
[${MIND_VOSCILLATOR_PERIOD_MS}],
[Period of the mind virtual oscillator in milliseconds])
AC_COMPUTE_INT(
[MIND_VOSCILLATOR_FREQ_MS], [1000 / ${MIND_VOSCILLATOR_PERIOD_MS}], [],
[AC_MSG_ERROR([Failed to compute the mind's virtual oscillator frequency.])])
AC_DEFINE_UNQUOTED([CONFIG_MIND_VOSCILLATOR_FREQ_MS],
[${MIND_VOSCILLATOR_FREQ_MS}],
[Frequency of the mind virtual oscillator in milliseconds])
m4_include([m4/ac_prog_flex.m4])
m4_include([m4/ac_prog_bison.m4])
AC_PROG_CC
AC_PROG_CXX
AC_PROG_RANLIB
AM_PROG_AR
AC_PROG_LEX(noyywrap)
AC_PROG_YACC
AS_IF([test -z "${LEX}" || test -z "${YACC}"], [
AC_MSG_ERROR([LEX and YACC must both be available in PATH.])
])
AM_CPPFLAGS=m4_normalize(["-I\"\$(top_srcdir)/include\""])
AC_SUBST([AM_CPPFLAGS])
AC_SUBST([YACC])
AC_SUBST([LEX])
AC_CONFIG_HEADERS([include/config.h])
AC_CONFIG_FILES([
Makefile hcore/Makefile
hcore/deviceManager/Makefile
])
AC_CONFIG_COMMANDS_POST([
AC_MSG_NOTICE([${PACKAGE_NAME} ${PACKAGE_VERSION} configuration:])
AC_MSG_NOTICE(m4_normalize([* MIND_VOSCILLATOR_PERIOD_MS:
${MIND_VOSCILLATOR_PERIOD_MS} ms
(freq: ${MIND_VOSCILLATOR_FREQ_MS} Hz)]))
])
AC_OUTPUT
+13
View File
@@ -0,0 +1,13 @@
add_subdirectory(bodies)
add_daps_target(all_device_specs
SOURCES
avia0.dapss
win0.dapss
)
# Register this target for later dependency addition from main CMakeLists.txt
register_daps_target(all_device_specs)
# Make the DAPSS target part of the ALL target for this subdirectory
# This ensures DAPSS targets are built when building just this subdirectory
set_property(TARGET all_device_specs PROPERTY FOLDER "devices")
+1
View File
@@ -0,0 +1 @@
+edev|avia0|structural-qualeiface()|livoxGen1()|livoxProto1()|3JEDK380010Z39
+17
View File
@@ -0,0 +1,17 @@
add_daps_target(body_rpi5_persys
SOURCES
rpi5-persys.dapss
)
add_daps_target(body_dell_laptop
SOURCES
dell-laptop.dapss
)
# Register this target for later dependency addition from main CMakeLists.txt
register_daps_target(body_rpi5_persys)
register_daps_target(body_dell_laptop)
# Make the DAPSS target part of the ALL target for this subdirectory
# This ensures DAPSS targets are built when building just this subdirectory
set_property(TARGET body_rpi5_persys PROPERTY FOLDER "devices/bodies")
set_property(TARGET body_dell_laptop PROPERTY FOLDER "devices/bodies")
+3
View File
@@ -0,0 +1,3 @@
#include "../win0.dapss"
||
#include "../avia0.dapss"
+3
View File
@@ -0,0 +1,3 @@
#include "../win0.dapss"
||
#include "../avia0.dapss"
+1
View File
@@ -0,0 +1 @@
+edev|win0|visual-qualeiface()|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
direct capture of frames at the kernel so it works for both Linux and Wayland.
There's a program known as GPU Screen Recorder that is able to use this to
capture specific windows on X11, but the window-specific capture doesn't work
with Wayland. Irrespective, whole-screen capture works on both GFX servers.
The stim-buff-api is Linux DRM/KMS. The provider is Linux itself. This provider
enables direct capture of frames at the kernel level, so it works for both X11
and Wayland. There is a program called GPU Screen Recorder that can use this
API to capture specific windows on X11, but window-specific capture does not
work with Wayland. However, whole-screen capture works on both graphics
servers.
Notes:
* `modetest` utility in ubuntu package `libdrm-tests` is relevant.
-5
View File
@@ -1,5 +0,0 @@
# DeviceSpec: API `x11`, server `xorg`:
* `xwininfo` is relevant.
+122
View File
@@ -0,0 +1,122 @@
# DAP Spec: stim-buff-api `xcb`, provider `xorg`
## Overview
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
or entire screens managed by the Xorg server.
## DAP Spec Format
The general format of a DAP spec for the `xcb` stim-buff-api with the `xorg` provider
is:
```
sensor-type|dev-identifier|quale-iface-api|xcb(stim-buff-api-params)|xorg(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.
* `quale-iface-api` is the name of the StimIface library that should be used to
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
must always be written out.
* `provider` is `xorg` in this case, and the `provider-params` in parentheses
may be omitted, in which case the parentheses will be empty, but the
parentheses must always be written out.
* `deviceSelector` is the idiosyncratic label/name used by the `provider` to
identify the specific window or screen you want to access via that
`provider`.
## `stim-buff-api-params` and `provider-params`
### `stim-buff-api-params`
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
ID. The ID can be specified in decimal or hexadecimal format.
* `dev-string`, `dev-str`, `devstr`, or `devstring`: Specifies that the
`deviceSelector` is an exact window name.
* `dev-substring`, `dev-substr`, `devsubstr`, or `devsubstring`: Specifies
that the `deviceSelector` is a substring match (default).
Example:
```
xcb(dev-id)|123456
xcb(dev-id)|0x1e240
xcb(dev-string)|exact-window-name
xcb(dev-substring)|window-name
```
### `provider-params`
The `provider-params` for the `xorg` provider can include the following:
* `display`: The display number (e.g., `0` for `:0`).
* `screen`: The screen number within the display (e.g., `0` for the first
screen).
Example:
```
xorg(display=0|screen=0)
```
## `deviceSelector`
The `deviceSelector` can be one of the following:
* A numeric window ID.
* A window name (exact match or substring match).
### Matching Types
* By default, the `deviceSelector` is treated as a substring match.
* To specify an exact match, use the `dev-string` parameter.
* To specify a numeric window ID, use the `dev-id` parameter.
Example:
```
xcb(dev-substring)|window-name
xcb(dev-string)|exact-window-name
xcb(dev-id)|123456
```
## Escaping Whitespace
In `deviceSelector` and other string tokens, whitespace can be included by
prefixing it with a backslash (`\`). The backslash will be discarded during
parsing.
Example:
```
xcb(dev-string)|My\ Exact\ Window\ Name
```
## Examples
### To attach a specific window by name (substring match):
```
+edev|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.
### To attach a specific window by exact 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".
### To attach a specific window by numeric ID:
```
+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`.
### To attach the entire screen:
```
+edev|my-screen|visual-qualeiface|xcb()|xorg(display=0|screen=0)|all
```
This will attach to the entire screen `0` of display `0`.
+67
View File
@@ -0,0 +1,67 @@
Ok: I realized I may be able to bridge async sequences without needing needing to make a bunch of functions async. The basic thing is:
```
funcThatCallsAsyncFuncsButWhoseSignatureIsItselfSync(ComponentThread &ct)
{
std::atomic continuationCondition(false);
async_call(ct, [] {
// Do stuff that will enqueue events on ct.
continuationCondition.store(true);
ComponentThread::getSelf()->getIoService()
.post([]{});
});
for (;;)
{
/* We, the thread actually executing this sequence
* here, may not actually be the thread that owns
* ct, in which case, ct's owner will dequeue the stuff
* that async_call() sent it to do, then conclude its
* processing. It may or may not send a message back
* to the thread executing this sync sequence here.
*
* If we are the same thread as ct, then wonderful:
* we will dequeue the messsage ourself and process
* it, then conclude the processing. We may or may
* not send a message back to ourself at the end of
* the processing but it doesn't matter since we'll
* have to check the condvar in the loop post-
* conditions before the next iteration.
*
* The problem is the first case where ct is a foreign
* thread which may not send us a wakeup message
* when condvar has been modified, and we may
* hypothetically never get another signal from any
* other thread.
* This can be solved by just ensuring that this thread
* always gives a callback to async_call() which first
* modifies condvar, and then sends an empty message
* to itself.
*/
ct.run_one();
if (continuationCondition.load()==true
||!ct.isRunnable())
{
break;
}
}
/* We've now bridged the async calls into this
* sync function's body without losing any
* event responsiveness for the main loop.
*/
// Continue executing normally...
}
```
This should be a complete solution for async bridging.
Now let's try it first in the sendHandshake sequence. We'll try to use non-blocking socket api calls to send the heartbeat and wait for a response asynchronously. Don't use boost:asio:socket functions because they cause a segfault due to a bug (see /CMakelists.txt). To wait for events on the socket, setup boost to wait on the socket or bind FD.
Async_call is OBVIOUSLY NOT a function you're expected to implement. It's merely a placeholder for any async sequence we call inside of the sync function.
You're expected to get rid of the io_context auto-scope object inside of executeHandshake, and use the stored Device.componentThread's io_context instead. You are expected to refrain from associating Device.componentThread.io_service with any boost::asio::socket stuff. You will have to write manual posix socket api code, and use a boost::file_descriptor to catch wakeup events on the socket from the UDP events like recv-data-ready.
You're basically going to break up executeHandshake into a series of lambdas, but use the pattern I described above to keep it as one synchronous function by bridging it. executeHandshake is the synchronous function that must remain synchronous to its caller. Internally, you must split up executeHandshake into several lambdas, and then at the end you set the condvar and post a message back to CompnentThread::getSelf()->io_service. Then, since executeHandshake() has a bridging sequence that loops and calls run_one() until the condvar is set, executeHandshake will resume executing after that loop exits.
Makes sense?
+116
View File
@@ -0,0 +1,116 @@
# Negtrin path reasoning:
This file is intended to enable me to trace the path of negtrin through a mind. This path of reasoning is important because it allows me to link my way from the understanding of the genesis of implexing, to understanding an implementation path for implexing.
## Motivation:
We already understand that implexing begins with an intrin. The intrin prompts a search for the body surface which is experiencing the intrin. That surface is first implexed and stored away. Then subsequently, the cause of the intrin is sought out by implexing things that are relating/interacting with that body surface.
Since it turns out that intrins are what motivate tabula rasa implexing, we can't move forward in designing our implexor code until we understand how the intrin path will work, since the implexor code will be invoked only as a consequence of, and in service to, the intrin processing code. We could implement something on the implexing side, but it would be a blind implementation.
## What exactly are we trying to figure out?
When intrins occur they occur at some device. Smo doesn't need a software-level representation of body parts, surfaces, or spots. The director can implex these and then build a proximity map for each intrin location by checking the causal relationships between the intrins when they occur. Intrins that co-occur in time and have a through-line of connecting co-occuring intrins are close enough to be proximally related.
This is how the director can build damage map volitionally without requiring us to force it to.
The design question we're pursuing here isn't the top-level notion of how to design a body map, but rather how to implement, at the software level, intrin implexing and delivery to the director.
* What information precisely do we have to deliver to the director for it to successfully implex body surfaces?
* At what point in the interoceptor data IRQ handling have we done enough work to enable the director to take over from that point?
The answers to these questions will, as a side effect, also tell us how to design the implexor model for interoceptors, at least for intrin interoceptors anyway.
* Should they be designed as NMI IRQs that the director can't ignore?
* Should they be prioritized normal IRQs that the director can ignore?
* Should they be IRQs at all? Maybe they should just be added to prioQ that the director polls.
* Should they be asychronously processed at all? Maybe the director should have to poll the device service routines manually.
Which of these implexor event interface designs is correct for the behavioural model?
Also, can this model that we're designing for interoceptors also work for extrospectors? Do intrin percept events work the same as nontrin percept events?
We still haven't fully crystallized the exact essential question we're trying to answer, but we have a list of questions. It appears that the fundamental question is what the correct behavioural model is for intrins and intrin processing. As a side-effect, we're also trying to figure out what the correct interface model should be between the interoceptor devices and the director. Finally, we're trying to figure out whether the design we come up with for interoceptors will be reusable for extrospectors.
## Suspicions:
### A hidden, implicit new subsystem:
I suspect that part of the reason why I can't easily figure out the operations that need to be performed to properly process intrins is that the current set of subsystems I'm using to model the processing is insufficient. There may be additional, new functions that need to be performed by a new logically distinct subsystem altogether, or a new role to be assigned to a current subsystem.
### Alternatively: my understanding of negtrins is simply inadequate:
It could also be that the reason I can't understand how to implement the negtrin path is because I just don't fully understand what negtrins are, implementation-wise. I.e: I still don't understand how to make negtrins be experienced by the director as intrinsically undesirable.
We do fully understand how to make postrins intrinsically desirable from an implementation perspective. But we don't understand fully how to make negtrins undesirable. Perhaps when we understand fully how to make the director __volitionally__ consider negtrins undesirable, the model will reveal itself.
## Intrinsically undesirable negtrin model design:
Postrins are represented as intrinsically desirable because the director will experience them as stupefactors. When a postrin occurs, the director will experience that as a stupifying experience, and will enter a HLT state, broken only by the suspicion that a greater state of stupefaction is possible.
### Our implicit assumption: negtrins have an implementation meaning on their own terms:
When the director dequeues a negtrin, what does it do? Perhaps our entire thought patterns around the default state of the director is simply wrong. We currently model the mind as having an idle loop, and then it can choose what to contemplate and pursue. Because of this thought model, we consider the question of how to treat negtrins to be a valid question: we treat negtrins as having their own intrinsic meaning.
### What if negtrins only have meaning as frustrators relative to postrins?
Consider what follows if we bias the entire implementation positively toward the direct pursuit of postrins. We treat the entire consciousness as a program whose fundamental goal is to enter a stupefaction loop on best postrin known to it. What would negtrins represent, in implementation terms? They would represent a frustrator. They would represent a dysvalue, automatically -- they would represent something that forces the mind to stop pursuing its highest known postrin and to attend to something cumbersome.
In other words: negtrins don't represent a distinct fact from postrins. The damage map that negtrins help us to build isn't meaningful on its own terms. It's only meaningful in relation to the stupefaction loop pursuit that it forces us to turn away from.
In this model, the stupefaction loop has intrinsic implementation meaning while the negtrin event has no intrinsic meaning: its meaning is merely that it diverts attention away from the pursuit and enjoyment of stupefaction.
#### Supporting evidence:
* A tabula rasa mind that has never experienced postrins literally has no desire to live. It has no positive goals.
#### Detracting evidence:
* A tabula rasa mind that has only experienced negtrins won't pursue any self-directed goals or desire to live, but it also will respond to negtrins. So even though this mind has never experienced a postrin, it will have a response (implying meaning and interpretation) to negtrins. If negtrins only have meaning relative to postrins, then this shouldn't be the case.
* Rejoinder: The tabula rasa mind which has never experienced a postrin will sit apoplectic in complete stasis, essentially in a "powered off" state. Is this not the same as a stupefied state? It seems that stupefaction is the default pursued state. To support the "default stupefaction pursuit" orientation, you'd only need to update your model of stupefaction to include net neutral states within your definition of postrins.
### Stupefactor-Frustrator model:
In this model, the mind doesn't receive nontrins as IRQs. Rather, it polls for them actively by "paying attention". Intrins are qualitatively different because they trigger IRQs. The mind is by default a procedural, polling system that volitionally polls its sensors. IRQs are intrins being injected.
Intrins are fundamentally different because they force attention, and a neurological response. When a sufficiently intense postrin happens, you automatically stupefy. I have never been forcibly postrin-injected while trying to conduct some other activity, so I can't confirm this model. I've never had the indignity of being forcibly made to experience a postrin when I didn't volitionally choose to participate in allowing it. An ideal experiment would be to somehow have say, a coregasm while working out and not have chosen it, and see whether I can ignore it. This is the closest I can conceive of as a barriered-off experiment.
For negtrins, I already know that a negtrin forcibly hijacks attention.
## The fundamental problem is the canvas-director evaluation function:
The fundamental unsolved problem is the contemplative evaluation whereby the canvas and director work together to produce a value judgment that considers intrins to be intrinsically [un]desirable.
### Who does comparison? Canvas or Director?
How does evaluation occur? Who does the comparison? We previously thought that the canvas does the comparisons but today we noticed that Director may be able to do them. In a sense, it makes more sense for Director to be the comparator because:
#### Director as comparator:
* If canvas merely helps director to transform mentents (we've finally found a good name), then director has more of an understanding of what specific instructions it's supposed to be giving to canvas.
* Our previous model of having canvas hold both goals and mentents made it difficult to really understand how director could ever "know" what instructions to send to canvas.
* With director holding goals, it can choose the types of scenes that need to be rendered in order to make comparisons and causal evaluations.
* This makes the canvas implexing make more sense: the comparison is being done within director, but the canvas is making the comparison possible by rendering the mentents in orientations that enable canvas to compare them.
* In a sense, this also makes it make more sense how compartmentalization occurs. A lot of the compartmentalization isn't within canvas, but rather it's between the director and canvas.
#### Canvas as comparator:
In a sense, we can think of it as director partitioning the canvas, and finding some way to refer to the mentents within different partitions. Director can then implex the rendered output from canvas and compare that way. Perhaps the director only compares, and the canvas holds both the mentents and the goals that the mentents are being compared against.
But how can the director know whether the goal currently loaded into the "goal partition" of the canvas is indeed the goal that it desired to compare against? The canvas is a distinct mentent space from the director's comparison stage. The director-comparator model has the advantage of ensuring that the director can be certain that its current goal matches whatever is being rendered by the canvas for it to implex from.
What we can do is synthesize a bit:
* Have a special shared region between the canvas and director called the "goalspace". This is where the director stores its current goal mentents and the canvas can read from this space directly. Some synchronization will be necessary between the two, but overall, it should be fine. Canvas will only ever need to read from it so we can prolly synchronize it with an RW spinlock, avoiding the overhead of sleeplocks (mutex, sem, etc).
I think we settle on the director as comparator, canvas as orienter model, with a shared goalspace between them.
### Where are introspective/contemplative qualia experienced?
In introspection/contemplation, canvas renders the scene into the buffers. Director implexes from the buffers. So in that sense, contemplative qualia are experienced when director implexes them from the buffers.
For intrins, canvas renders them into the sense buffers and then director implexes them and "experiences" them. I don't know whether canvas' rendering act triggers IRQs -- probably not. Since director is paying attention to what canvas is rendering, there's no need to forcibly direct director's attention toward canvas' rendered output.
### How does director's comparison yield the notion of intrinsic [un]desirability?
Imagine director asking canvas to simulate the effects of some interaction that would bring about direct pleasure.
Could it simply be that the director holds the goal of postrins as an innate comparison goal, and so when a comparison yields an intrin, it ends the need to compare, and the director just commits to action from then on?
* Prolly not because we don't only figure out that an interaction will yield an intrin. With postrins for example, we figure out that they'll yield a postrin and then we spend a long time elaborately contemplating that postrin in self-indulgent scenarios.
+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
+603
View File
@@ -0,0 +1,603 @@
I just realized that my spinqueueing mechanism is highly power inefficient if a
lock needs to be held across a "true async wait"—where the async sequence
actually waits on a hardware bottleneck. In this case, the thread acquires the
spinlock, then goes to sleep in the kernel schedq until some hardware event
occurs, and is then awakened—all while still holding the spinlock.
Meanwhile, other sequences running on other threads and contending for that lock
will be Qspinning. This is acceptable if all I care about is maximum throughput:
the Qspinning just re-posts the sequences back into the Q, and eventually
they'll acquire the LockSet and proceed.
Importantly, since the thread itself isn't slept in the kschedQ, it will be
deQing and processing other sequences that aren't bottlenecked on the lock held
by the sequence waiting for the hardware response. Throughput is indeed
maximized.
However, I just realized that if kernel mutexes expose FD events, I can apply
this same logic to sleeplocks: I can wait on the sleeplocks asynchronously
instead of synchronously. If I can make my asio::io_service wait on all the
mutex FDs requested by sequences on the current thread, then in theory I can put
the thread to sleep and know that when the mutex becomes available, I'll be
awakened again.
Hence, I can get the best of both worlds: maximum throughput and power saving.
Instead of spinqueueing, we just add the lock FDs to an FD set to be waited on
by asio. If any of those locks become available, the kernel scheduler will
awaken our asioQ thread, and we can then awaken and retry the lock.
## Boost asio queue-based sleep locking:
Instead of using FDs, we can also try to use a fifo Q based mechanism: each lock
is a spinlock and a fifo queue.
Acquire:
```
lock(spinlock);
q.push_back(self);
head = q.peek_front();
if (head == self) {
// We acquired the lock.
unlock(spinlock);
return;
}
unlock(spinlock);
```
release:
```
lock(spinlock);
// Should get back ourself.
q.pop_front();
// Wake up the next request in the q.
head = q.peek_front();
if (head == NULL) {
// Nobody was waiting.
unlock(spinlock);
return;
}
head.thread_to_wake.getIoService().post([]{
// This lambda causes thread_to_wake to check this lock's
// Q and then proceed to execute since it now owns the lock.
});
unlock(spinlock);
```
Something like this: it causes the entire thing to be, at least ostensibly,
in userspace -- though idk how Boost handles its queues internally.
## Priortizing LockSets:
One problem we have with a FIFO-based sleeping system is that it makes it very
unlikely that LockSets will ever acquire all of their locks, if there are
contenders for those same locks who only need to acquire one of the locks in
that LockSet.
We could theoretically give locksets an advantage by not making them backout
if they fail to acquire all locks in their set. I.e: if they get 2/3, then they
hold those 2 and then wait for the 3rd. This is problematic because it leaves
room open for deadlocks in the form of T1 and T2 needing both LockA and LockB,
but they acquire them in reverse order. I.e: T1 takes LockA and now waits for
LockB; and T2 takes LockB and now waits for LockA. This will now happen among
the LockSets if we don't impose backing out. It may be possible to avoid this
using very careful lock ordering and dependency analysis but this project is
asynchronous the locking is done in the async sequences and not in the sync
accessor functions. So this kind of analysis is almost impossible to do.
We need to think of a way to make the FIFOs biased toward LockSets so that they
have an advantage over single-lock acquirers. Or else LockSet sequences will be
starved.
### Timed backoff:
We could have Locksets be greedy and try to hold on to the locks they've
acquired (say, 2/3 and then wait for the 3rd) but then be forced to backoff
after a timeout.
This introduces async event complexity and also the timeout we choose is almost
guaranteed to be arbitrary.
### Fractionally inserted FIFOs:
We insert sequences with a LockSet.size() of 1, at the back.
We insert all other sequences (>1) into first 1/LockSet.size()th position in the
Queue.
So a Lockset of size 2 will be inserted at the end of the first half of the
items in the queue.
A Lockset of size 3 will be inserted at the end of the first 33% of items.
A lockset of size 4 will be inserted at the end of the first 25% of items.
And so on.
This ensures that higher LockSet.size()s will be prioritized ever higher, and
at the same time they don't completely hog everything. Those single-lock
sequences that have already naturally progressed past the fraction-mark of a
given LockSet size will continue making progress toward the front.
For queueing sequences with Locksets>1, we can enQ them on the FIFO of the first
lock in their set. They'll back off each time anyway, so they'll always be
re-trying from the first lock in their set each time.
#### Impl details:
We'd like to use std::unordered_set because insertion will require lots of
moving items around, but we'll have to use std::vector because we need direct
access to insert at arbitrary fractional indexes. It's unlikely the number of
items in any lock's Q will ever be large enough to require lots of displacement,
but welp there's no reason not to plan for scaling. Although if we end up
needing scaling that's a symptom of a bigger problem...with scaling itself lol.
There shouldn't be enough items blocked on a lock that we have to design the
lock's queue to be scalable.
### Inverted Fractionally acquired locksets:
The previous ideas of fractionally inserted lockQs was okay, but the acquisition
algo required that the async seq be at the front of a locks queue to
successfully acquire that lock. That makes it almost impossible for Locksets>1
to ever acquire all of their locks. If we add backoff to that, it basically
means no lockset will ever acquire all of its locks.
Instead what we now do is always insert at the rear (push_back()) and then when
acquiring, we check to see if the sequence is in the first
1/(1/(LockSet.size())), and if so, it successfully acquires the lock. I.e: if
the sequence item isn't in the LAST 1/(LockSet.size()) items, then it succeeds.
* For a lockset of size=1: It must be at the front of the queue.
* Lockset.size=2: it must be in the first 50% of items.
* Lockset.size=3: it must be in the first 66% of items.
* Lockset.size=4: It must be in the first 75% of items.
So this way larger LockSets are favoured, but 1-size locksets make progress.
For performance:
* We obv can just scan the smaller tail percentage for the item instead of
scanning the larger front percentage.
* If we use a doubly-linked list, we can prolly keep the insertion iterator
and this way we won't have to actually find the item in the lockQ when we wish
to eventually remove it from the lockQ when releasing the lock.
## Total overall design:
### Asio queues and Lockvokers:
Lockvokers are initially enqueued on a CompThread's queue. When the lockvoker
first runs, it checks a flag to see if it has been "registered" into the queues
for all locks in its set. If not, then it "registers" itself in each lock's
ticketQ and then attempts to acquire each lock. Registration and acquisition
are logically separate operations; and locks will often attempt acquisition
many times after first registering, without needing to register again. Ideally
we can implement a LockSet::registerAndTryAcquireAll() method, but that's for
us to think about later.
```
/* We'll need to rename current class LockSpec to LockSet. */
class LockSet
{
/* Add this either inside of LockSet or outside of it -- depends on whether
* it's we can get it to compile because I'm seeing some potential circular
* definition dependencies.
*/
typedef std::pair<Qutex, LockerAndInvokerList::iterator>
LockUsageDesc;
/* Find a LockUsageDesc -- useful below */
LockUsageDesc &getLockUsageDesc(Qutex &criterionLock)
{
for (auto &reqLock: requiredLocks) {
if (reqLock.first == &criterionLock) { return reqLock; }
}
// Should never happen.
throw;
}
};
LockSet::register(LockerAndInvoker &lockvoker)
{
for (auto &lock: lockset.locks) {
// Register the Lockvoker object in each lock's ticketQ.
lock.second = lock.first.register(lockvoker);
}
registered = true;
}
bool LockSet::tryAcquire(LockerAndInvoker &lockvoker)
{
if (!registered) {
// Should never happen.
throw ...;
}
int nLocksAcquired=0,
nLocksInSet = lockset.size();
for (auto &lock: lockset.locks) {
if (!lock.first.tryAcquire(nLocksInSet)) {
break;
}
nLocksAcquired++;
}
if (nLocksAcquired == nLocksInSet) {
// Success
return true;
}
for (int i=0; i<nLocksAcquired; i++) {
// Backoff does different stuff from release();
locks[i].first.backoff(lockvoker);
}
}
LockSet::release()
{
for (auto &lock: requiredLocks) {
lock.release();
}
}
```
Now, the Qutex class is what we'll use for synchronization. It's just a
combination of a SpinLock, a sh_ptr<LockerAndInvoker> and a std::list.
```
class SpinLock
{
/* Modify to add methods acquire() and release() which busy-wait.
*/
void acquire();
void release();
};
class LockSet
{
/* Modify the std::vector of SpinLock to instead be:
* std::vector<LockUsageDesc> locks;
*/
std::vector<LockUsageDesc> locks;
}
bool LockerAndInvoker::operator==(const LockerAndInvoker &other)
{
/* Compare by the address of the continuation objects. Why?
* Because there's no guarantee that the lockvoker object that was
* passed in by the io_service invocation is the same object as that
* which is in the qutexQs. Especially because we make_shared() a
* copy when registerInQutexQueues()ing.
*
* Generally when we "wake" a lockvoker by enqueuing it, boost's
* io_service::post will copy the lockvoker object.
*/
return &this->serializedContinuation == &other.serializedContinuation;
}
bool LockerAndInvoker::operator !=(const LockerAndInvoker &other)
{
return &this->serializedContinuation != &other.serializedContinuation;
}
class Qutex
{
public:
typedef std::list<LockerAndInvoker> LockerAndInvokerList;
LockerAndInvokerList::iterator register(const LockerAndInvoker &lockvoker)
{
/** EXPLANATION:
* Just insert the lockvoker into the rear of the list.
*
* Then, since we want to store the
*/
LockerAndInvokerList::iterator it;
lock.acquire();
queue.push_back(lockvoker);
it = queue.end();
--it;
lock.release();
return it;
}
void unregister(LockerAndInvokerList::iterator it, bool shouldLock=1)
{
if (shouldLock)
{
lock.acquire();
queue.erase(it);
lock.release();
}
else{
queue.erase(it);
}
}
bool tryAcquire(LockerAndInvoker &tryingLockvoker)
{
const nRequiredLocks = tryingLockvoker.serializedContinuation
.requiredLocks.size();
lock.acquire();
const qNItems = queue.size();
if (qNItems < 1) {
lock.release();
/** EXPLANATION:
* requiredLocks before ever trying to tryAcquire() them, so if
* tryAcquire is being called, that must mean that queue.size() > 0.
*
* Ergo this should never happen.
*/
throw;
}
if (!!currentOwner) {
lock.release();
return false;
}
/** EXPLANATION:
* From here:
* if qNItems == 1 the we are the only one in the ticketQ and we have
* successfully acquired the lock.
* If qNitems / nRequiredLocks == 0, then we acquire by default since
* the number of items in the ticketQ guarantees that we are in the top
* X% for that nRequiredLocks.
* If qNItems / nRequiredLocks >= 1, then we must do the normal algo:
* Check the last (qNItems/nRequiredLocks) items, and if the item isn't
* in those items, then it must be in the earlier ones (obviously).
* Hence this Lockvoker acquisition should be considered successful.
*
* EXPLANATION 2:
* You'll notice that we don't do actual percentages but rather we just
* do discrete fractions -- this makes the algo more deterministic
* and much easier to reason about. I.e:
* If nRequiredLocks is 6 and qNItems==3:
* we don't actually calculate that the Lockvoker item must be in
* the top (100-17%), and then try to calculate whether we ought to
* consider the 3rd item to be in the last 17-percentile. We just
* do a fractional count and assume complete discreteness.
*/
const int nRearItemsToScan = qNItems / nRequiredLocks;
if (qNItems == 1 || nRearItemsToScan < 1) {
currOwner = tryingLockvoker;
lock.release();
return true;
}
/** EXPLANATION:
* For lockvokers that only have 1 requiredLock, they must be at the
* front of the queue to successfully acquire.
*/
if (nRequiredLocks == 1)
{
bool ret=false;
if (tryingLockvoker == &queue.front())
{
currOwner = tryingLockvoker;
ret = true;
}
ret = false;
lock.release();
return ret;
}
auto rIt = queue.rbegin();
auto rEndIt = queue.rend();
bool foundInRear = false;
for (int i=0; i<nRearItemsToScan && rIt != rEndIt; rIt++, i++)
{
if (*rIt != tryingLockvoker) { continue; }
foundInRear = true;
break;
}
if (foundInRear) {
lock.release();
return false;
}
/* Not found in rear: this means the item is in the top X%. That means
* it should be allowed to claim the lock.
*/
currOwner = tryingLockvoker;
lock.release();
return true;
}
backoff(LockerAndInvoker &failedAcquirer)
{
lock.acquire();
const int nQItems = queue.size();
// Rotate queue members if failedAcquirer is at front of queue.
LockerAndInvoker &currFront = queue.front();
if (currFront == failedAcquirer && nQItems > 1)
{
/** EXPLANATION:
* Rotate the top LockSet.size() items in the queue by moving
* the failedAcquirer to the last position in the top
* LockSet.size() items within the queue.
*
* I.e: if queue.size()==20, and lockSet.size()==5, then move
* failedAcquirer from the front the 5th position in the queue,
* which should push the other 4 items forward.
* If queue.size==3 and LockSet.size()==5, then just
* push_back(failedAcquirer).
*
* It is impossible for a Qutex queue to have only one
* item in it, yet for that Lockvoker item to have failed to
* acquire the Qutex. Being the only item in the ticketQ
* means that you must succeed at acquiring the Qutex.
*/
int indexOfItemToInsertCurrFrontBehind = min(
nQItems - 1,
failedAcquirer.serializedContinuation.requiredLocks.size() - 1);
/* EXPLANATION:
* Rotate them here.
*
* The reason why we do this rotation is to avoid a particular kind
* of deadlock wherein a grid of async requests is perfectly
* configured so as to guarantee that none of them can make any
* forward progress unless they get reordered.
*
* Consider 2 different locks with 2 different items in them
* each, both of which come from 2 particular requests:
* Qutex1: Lockvoker1, Lv2
* Qutex2: Lv2, Lv1
*
* Moreover, both of these lockvokers have requiredLocks.size()==2,
* and the particular 2 locks that each one requires are indeed
* Qutex1 and Qutex2.
*
* This particular setup basically means that in TL1's queue, Lv1
* will wakeup since it's at the front of TL1. It'll successfully
* acquire TL1 (since it's at the front), and then it'll try to
* acquire TL2. But since Lv1 isn't in the top 50% of items in TL2's
* queue, Lv1 will fail to acquire TL2.
*
* Then similarly, in TL2's queue, Lv2 will wakeup since it's at
* the front. Again, it'll successfully acquire TL2 since it's at
* the front of TL2's queue. But then it'll try to acquire TL1.
* Since it's not in the top 50% of TL1's enqueued items, it'll fail
* to acquire TL1.
*
* N.B: This type of perfectly ordered deadlock can occur in any
* kind of NxN situation where ticketQ.size()==requiredLocks.size().
* That could be 4x4, 5x5, 6x6, etc. It doesn't happen in 1x1
* because a Lockvoker that only requires one lock will always just
* succeed if it's at the front of its queue.
*
* This state of affairs is stable and will persist unless these
* queues are reordered in some way. Hence: that's why we rotate the
* items in a QutexQ after backing off of it. Backing off means
* Not necessarily that the calling LockVoker failed to acquire
* THIS PARTICULAR Qutex, but rather than it failed to acquire
* ALL of its required locks.
*
* Hence, if we are backing out, we should also rotate the items
* in the queue if the current front item is the failed acquirer.
* So that's why we do this rotation here.
*/
// The first arg (the iterator) is a ref in case it must be updated.
rotate(
currFront.serializedContinuation.requiredLocks.getLockDesc(
*this).second,
indexOfItemToInsertCurrFrontBehind);
}
currOwner.release();
LockerAndInvoker &newFront = queue.front();
lock.release();
/** EXPLANATION:
* Why should this never happen? Well, if we were at the front of the queue
* and we failed to acquire the lock, we should have been rotated away from
* the front. On the other hand, if we were not at the front of the queue
* and we failed to acquire the lock, then we weren't at the front of the
* queue to begin with.
* The exception is if the queue has only one item in it.
*
* Hence there ought to be no way for the failedAcquirer to be at the front
* of the queue at this point UNLESS the queue has only one item in it.
*/
if (newFront == failedAcquirer && nQItems > 1)
{
throw;
}
/** EXPLANATION:
* We should always awaken whoever is at the front of the queue, even if
* we didn't rotate. Why? Consider this scenario:
*
* Lv1 has LockSet.size==1. Lv2 has LockSet.size==3.
* Lv1's required lock overlaps with Lv2's set of 3 required locks.
* Lv1 registers itself in its 1 qutex's queue.
* Lv2 registers itself in all 3 of its qutexes' queues.
* Lv2 acquires the lock that it needs in common with Lv1.
* (Assume that Lv2 was not at the front of the common qutex's
* internal queue -- it only needed to be in the top 66%.)
* Lv1 tries to acquire the common lock and fails. It gets taken off of
* its io_service. It's now asleep until it gets
* re-added into an io_service.
* Lv2 fails to acquire the other 2 locks it needs and backoff()s from
* the common lock it shares with Lv1.
*
* If Lv2 does NOT awaken the item at the front of the common lock's
* queue (aka: Lv1), then Lv1 is doomed to never wake up again.
*
* Hence: backout() callers should always wake up the lockvoker at the
* front of their queue before leaving.
*
* The exception is if the item at the front is the backout() caller
* itself. This can happen if, for example a multi-locking lockvoker
* is backing off of a qutex within which it's the only waiter.
*/
if (nQItems > 1) {
wakeUp(newFront);
}
}
void release()
{
lock.acquire();
/* Get the saved iterator and use it to unregister.
* Don't acquire lock because we already acquired it in this function.
*/
unregister(currOwner->serializedContinuation.requiredLocks
.getLockUsageDesc(*this).second, false);
currOwner.release();
/** EXPLANATION:
* It would be nice to be able to optimize by only awakening if the
* release()ing lockvoker was at the front of the qutexQ, but if we
* don't unconditionally wakeup() the front item, we could get lost
* wakeups. Consider:
*
* Lv1 only has 1 requiredLock.
* Lv2 has 3 requiredLocks. One of its requiredLocks overlaps with
* Lv1's single requiredLock. So they both share a common lock.
* Lv3's currently owns Lv1 & Lv2's common requiredLock.
* Lv3 release()s that common lock.
* Lv1 happens to be next in queue after Lv3 unregisters itself.
* Lv3 wakes up Lv1.
* Just before Lv1 can acquire the common lock, Lv2 acquires it now,
* because it only needs to be in the top 66% to succeed.
* Lv1 checks the currOwner and sees that it's owned. Lv1 is now
* dequeued from its io_service. It won't be awakened until someone
* awakens it.
* Lv2 finishes its critical section and releas()es the common lock.
* Lv2 was not at the front of the qutexQ, so it does NOT awaken the
* current item at the front.
*
* Thus, Lv1 never gets awakened again. The end.
* This also means that no LockSet.size()==1 lockvoker will ever be able
* to run again since they can only run if they are at the front of the
* qutexQ.
*
* Therefore we must always awaken the front item when releas()ing.
*/
LockerAndInvoker &front = queue.front();
lock.release();
wakeUp(front);
}
public:
SpinLock lock;
std::shared_ptr<LockerAndInvoker> currOwner;
LockerAndInvokerList queue;
};
```
+59
View File
@@ -0,0 +1,59 @@
# Spinqueueing: A new locking method that only blocks requests and not threads.
The idea is that instead of using sleeplocks like mutexes, we instead only spin
particular request objects by re-posting them to the queue.
Particular requests may need a given shared resource. Instead of sleeping a
whole thread while that particular request waits for the resource, we instead
sleep the request itself by re-posting it into the thread's queue. This
basically implements a kind of spinlock without busy-waiting. The underlying
thread is never blocked unless it has no requests that can make forward
progress.
Forward progress through requests is only halted when an external resource is
actually being waited on. Generally this will be an actual hardware event that
is being waited on. No software bottlenecks will be slept on.
All locks in the program are simple spinlocks, but the algorithm to spin on them
is:
## Each async call has a "locker and invoker":
int funcThatCallsAnAsyncFunc(...)
{
// Do preparatory stuff ...
// Post the lockvoker to the target thread.
targetThread.io_service.post(
[targetThread, /* args to asyncOperationReq captured here */]()
{
int nAcquired;
for (nAcquired=0; nAcquired<nLocksRequired; nAcquired++)
{
if (!requiredLocks->tryAcquire()) {
break;
}
}
if (nAcquired < nLocksRequired)
{
for (int i=0; i<nAcquired; i++) {
requiredLocks->release();
}
/* Unsure how to recapture the lambda object and re-enqueue it.
* Dunno if that's even possible. But this is the essence of the
* queue-spin system. We re-enqueue the lockvoker until it
* gets all locks required. Then it will invoke the async
* frontend.
*/
targetThead.io_service.post(this?);
}
managerObject.asyncOperationReq(
/* args to asyncOperationReq passed here */);
}
);
}
## Idk how to encapsulate lockvokers into a terse, reusable idiom.
+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
```
-159
View File
@@ -1,159 +0,0 @@
# Device manager: attaching sensors and actuators.
## Attaching sensors:
Sensors are input devices to Harikoff. Harikoff will perceive them as
perceptual inputs -- like your own sense organs. For example, if you attach a
camera as a sensor, harikoff will experience it in the same way that you
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 spec:
The general format of a device-spec for a sensor is:
```
sensor-type|implexor|api(api-params)|provider(provider-params)|deviceselector
```
* `sensor-type` is always either '`+idev`' (interoceptor), '`+edev`'
(extrospector), or '`+adev`' (actuator).
* `implexor` is the name of the implexor algorithm that should be used with
the data that is provided by the `provider` via the `api`.
* `api` is the interface that the provider uses to export perceptual data for
harikoff to read. Harikoff 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 access 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
```
Some examples follow:
### To attach a particular window from a window manager:
```
+edev|visual-implexor|wayland()|wayland(server-socket)|window0
```
Connect to the Wayland server that's listening on `server-socket`, using the
`wayland` api. Ask that Wayland server to give harikoff read-access to all of
the frames composited into the window buffer for `window0`. Use harikoff's
`visual-implexor` to implex from that `window0`'s compositor data.
### To attach a window manager's entire rendered desktop:
```
+edev|visual-implexor|wayland()|wayland(listen-socket)|all
```
In most cases, this is basically the same as attempting to attach all of the
underlying GFX server's output.
Connect to the Wayland server that's listening on `listen-socket`, using the
`wayland` api. Ask that Wayland server to give harikoff read-access to the
entire compositor framebuffer. Use harikoff's `visual-implexor` to implex from
that Wayland server's compositor data.
### To attach all of an Xorg server's gfx output to all screens:
```
+edev|visual-implexor|x11()|xorg(listen-socket)|all
```
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
api. Ask that Xorg server to let Harikoff read out all of the frames written
out to all screens. Use harikoff's `visual-implexor` to implex from the
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|visual-implexor|x11()|xorg(listen-socket)|:0
```
Connect to the Xorg server that's listening on `listen-socket`, using the `x11`
api. Ask that Xorg server to let Harikoff read out all of the frames written
out to display `:0`. Use harikoff's `visual-implexor` to implex from display
`: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|visual-implexor|v4l()|linux()|/dev/video0
```
We specify that we want to use the `linux` kernel's loaded driver to connect
to communicate with `/dev/video0`, via the `Video4Linux` API. We want harikoff
to use the `visual-implexor` algorithm to implex from `/dev/video0`'s data.
If `/dev/video0` is already consumed by another process, this may likely fail.
### To attach a microphone that's managed by ALSA server:
```
+edev|audio-implexor|alsa(shmem)|alsa()|cardname
```
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|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 Harikoff's way of enacting changes in the external world.
They're like your libs, or your mouth. Actuators enable harikoff to write
outputs to the world outside.
### Wilzors:
Actuator devices are analogous to your body's limbs. Harikoff controls these
by using `wilzor` algorithms. Wilzor is a contraction of **Wil**lpower
Actuat**Or** but with a 'Z' in the middle to make it sound cooler. Different
types of devices will require different wilzor algorithms. You need to know
what type of wilzor algorithm needs to be used to enable harikoff to control
your actuator device.
The general format for an actuator's device spec is:
```
WIP: TBD.
```
## Device specification files:
Inside of a device spec file, you can list any number of device specs.
Separate individual device specs with two consecutive h-bar characters (`||`),
like this:
```
+edev|visual-implexor|wayland()|wayland(server-socket)|window0
|| +edev|visual-implexor|x11()|xorg(listen-socket)|all
|| +idev|thermal-implexor|thermal-zone()|linux()|/sys/class/thermal_zone0
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

+229
View File
@@ -0,0 +1,229 @@
# LivoxGen1Lidar Device Attachment Pipeline (DAP) Specification
## Overview
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.
## Stim-Buff-API Structure
The LivoxGen1Lidar DAP uses multiple stim-buff-api names, one for each stim feature it presents:
* `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)
**Purpose**: Provides light intensity/reflectivity data from the LiDAR point cloud.
**Syntax**:
```
+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
```
**Stim-Buff-API**: `livoxGen1-pcloudIntensity`
**Quale-Iface-API**: `pcloudIntensity` - Processes intensity/reflectivity data from point clouds
### 2. Point Cloud Coordinate Data Device (Extrospector)
**Purpose**: Provides spatial coordinate data from the LiDAR point cloud.
**Syntax**:
```
+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
```
**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)
- `spherical`: Raw spherical coordinates
- `spherical-cartesian`: Spherical coordinates converted to Cartesian
- `dual-cartesian`: Dual Cartesian coordinate system
- `dual-spherical`: Dual spherical coordinate system
**Alternative Format Parameter Names** (synonymous):
- `format` or `fmt`
### 3. IMU Gyroscope Data Device (Interoceptor)
**Purpose**: Provides gyroscope data from the LiDAR's internal IMU.
**Syntax**:
```
+idev | avia0 | gyro | 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
```
**Stim-Buff-API**: `livoxGen1-gyro`
**Quale-Iface-API**: `gyro` - Processes gyroscope angular velocity data
### 4. IMU Accelerometer Data Device (Interoceptor)
**Purpose**: Provides accelerometer data from the LiDAR's internal IMU.
**Syntax**:
```
+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
```
**Stim-Buff-API**: `livoxGen1-accel`
**Quale-Iface-API**: `accel` - Processes accelerometer linear acceleration data
## Provider Parameters
### livoxProto1 Provider
The `livoxProto1` provider accepts the following parameters:
**command-timeout-ms** / **cmd-timeout-ms** (optional, synonyms):
- Specifies the timeout for command operations when communicating with devices
- Value: Integer number of milliseconds
- Example: `command-timeout-ms=1000` or `cmd-timeout-ms=1000` (1 second timeout)
- Default: 1000ms if not specified
**retry-delay-ms** (optional):
- Specifies how long to wait for broadcast messages to arrive after attempting an initial direct connection
- Value: Integer number of milliseconds
- Example: `retry-delay-ms=3000` (wait 3 seconds)
- Default: 3000ms if not specified
**subnet** (optional):
- Specifies the IP subnet for device IP address calculation
- Value: IP address in the form X.X.0.0 where non-subnet bits must be 0
- Example: `subnet=10.42.0.0` (use 10.42.x.x subnet)
- Default: 0.0.0.0 (use default 192.168.1.x subnet)
**data-port** (optional):
- Specifies the UDP port for receiving point cloud data from the device
- Value: Integer port number
- Example: `data-port=56000`
- Default: 56000 if not specified
**cmd-port** (optional):
- Specifies the UDP port for receiving command responses from the device
- Value: Integer port number
- Example: `cmd-port=56001`
- Default: 56001 if not specified
**imu-port** (optional):
- Specifies the UDP port for receiving IMU data from the device
- Value: Integer port number
- Example: `imu-port=56002`
- Default: 56002 if not specified
## Parameter Summary
### Stim-Buff-API Names
| Stim Feature | Stim-Buff-API | Quale-Iface-API | Description |
|--------------|---------------|----------------|-------------|
| Point Cloud Intensity | `livoxGen1-pcloudIntensity` | `pcloudIntensity` | Light intensity/reflectivity data |
| Point Cloud Coordinates | `livoxGen1-pcloud` | `pcloud` | Spatial coordinate data |
| Gyroscope | `livoxGen1-gyro` | `gyro` | Angular velocity measurements |
| Accelerometer | `livoxGen1-accel` | `accel` | Linear acceleration measurements |
### Stim-Buff-API Parameters
Each stim-buff-api accepts device-specific parameters:
| Parameter | Description | Example |
|-----------|-------------|---------|
| `data-rate-hz` | Data sampling rate in Hz | `data-rate-hz=10` |
### Quale-Iface-API Parameters
The `pcloud` quale-iface-api accepts format parameters:
| Format | Description |
|--------|-------------|
| `xyz` | Standard Cartesian coordinates (X, Y, Z) |
| `spherical` | Raw spherical coordinates (range, azimuth, elevation) |
| `spherical-cartesian` | Spherical coordinates converted to Cartesian |
| `dual-cartesian` | Dual Cartesian coordinate system |
| `dual-spherical` | Dual spherical coordinate system |
## Device Discovery and Connection
The specification uses a retry-based connection strategy with two different approaches:
### Connection Methods
**1. Broadcast-Based Connection (connectToKnownDeviceReq)**
- Uses device IP addresses discovered from broadcast advertisements
- **smo-ip parameter**: Optional - if omitted, driver auto-detects the appropriate interface
- **smo-subnet-nbits parameter**: Optional - used for validation if smo-ip is provided
- **When to use**: When devices are actively broadcasting their presence
**2. Heuristic Connection (connectByDeviceIdentifierReq)**
- Generates device IP addresses from serial numbers using network prefix
- **smo-ip parameter**: **Required** - needed to determine network prefix for IP generation
- **smo-subnet-nbits parameter**: **Required** - needed to calculate valid device IP addresses
- **When to use**: When devices are not broadcasting or for initial setup
### Connection Strategy
1. **Initial Check**: Check if device is already known from broadcasts
2. **Direct Connect**: Attempt direct connection based on calculated IP address
3. **Retry Wait**: If direct connect fails, wait for `retry-delay-ms` for broadcast messages
4. **Final Check**: Check known devices again after retry delay
5. **Report Result**: Success or failure based on final check
## Data Formats
### Point Cloud Coordinate Formats
1. **XYZ Format**: Standard 3D Cartesian coordinates
- X, Y, Z in meters
- Standard coordinate system orientation
2. **Spherical Format**: Raw spherical coordinates
- Range (distance) in meters
- Azimuth angle in degrees/radians
- Elevation angle in degrees/radians
3. **Spherical-Cartesian Format**: Spherical coordinates converted to Cartesian
- Range, azimuth, elevation converted to X, Y, Z
- Maintains spherical measurement precision
4. **Dual Formats**: Support for dual-coordinate systems
- Useful for devices with multiple measurement modes
- Provides redundancy and validation capabilities
### Intensity Data
- Reflectivity values typically in the range 0-255
- Normalized intensity measurements
- Calibrated for material reflectivity analysis
### IMU Data
- **Gyroscope**: Angular velocity measurements (rad/s)
- **Accelerometer**: Linear acceleration measurements (m/s²)
- Timestamped data synchronized with point cloud measurements
## Error Handling
The specification includes comprehensive error handling for:
- Network connectivity issues
- Device communication timeouts
- Invalid coordinate format requests
- IMU data stream interruptions
- Device discovery failures
- Connection retry timeouts
+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`
-5
View File
@@ -1,5 +0,0 @@
SUBDIRS = deviceManager
AM_CPPFLAGS+= -I"$(top_srcdir)/hcore/include"
noinst_LIBRARIES = libhcore.a
libhcore_a_SOURCES = mind.cpp opts.cpp
-18
View File
@@ -1,18 +0,0 @@
AM_CPPFLAGS+= -I"$(top_srcdir)/hcore/include"
AM_YFLAGS = -d
noinst_LIBRARIES = libdeviceManager.a
libdeviceManager_a_SOURCES = deviceSpecp.yy deviceSpecl.ll \
deviceManager.cpp deviceSpecParser.cpp
deviceSpecl.cc: deviceSpecl.ll
deviceSpecl.o: AM_LFLAGS += --header-file=deviceSpecl.hh \
-o deviceSpecl.cc
deviceSpecp.cc deviceSpecp.hh: deviceSpecp.yy
deviceSpecp.o: AM_YFLAGS += -p deviceSpecp \
--header=deviceSpecp.hh -o deviceSpecp.cc
deviceSpecParser.o: AM_CXXFLAGS+=-Wno-ignored-attributes
CLEANFILES=deviceSpecp.cc deviceSpecp.hh \
deviceSpecl.cc deviceSpecl.hh
-46
View File
@@ -1,46 +0,0 @@
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <sstream>
#include <opts.h>
#include <deviceManager/deviceManager.h>
std::vector<DeviceManager::InteroceptorDeviceSpec>
DeviceManager::interoceptorDeviceSpecs;
std::vector<DeviceManager::ExtrospectorDeviceSpec>
DeviceManager::extrospectorDeviceSpecs;
std::ostream& operator<<(
std::ostream& os, const DeviceManager::SensorDeviceSpec& spec)
{
os << "Device: " << spec.sensorType << ", Implexor: "
<< spec.implexor << ", API: " << spec.api
<< ", API Params: (";
for (auto it = spec.apiParams.begin(); it != spec.apiParams.end(); ++it) {
os << *it << (it + 1 == spec.apiParams.end() ? "" : " ");
}
os << "), Provider: " << spec.provider << ", Provider Params: (";
for (auto it = spec.providerParams.begin(); it != spec.providerParams.end(); ++it) {
os << *it << (it + 1 == spec.providerParams.end() ? "" : " ");
}
os << "), Device Selector: " << spec.deviceSelector << std::endl;
return os;
}
const std::string DeviceManager::printDeviceSpecs(void)
{
std::ostringstream oss;
for (const auto& spec : DeviceManager::interoceptorDeviceSpecs) {
oss << "Interoceptor " << spec;
}
for (const auto& spec : DeviceManager::extrospectorDeviceSpecs) {
oss << "Extrospector " << spec;
}
return oss.str();
}
-56
View File
@@ -1,56 +0,0 @@
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <sstream>
#include <memory>
#include <cstdio>
#include <deviceManager/deviceManager.h>
#include "deviceSpecp.hh"
#include "deviceSpecl.hh"
std::string DeviceManager::readDeviceFile(const std::string& filename)
{
std::ifstream file(filename);
if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + filename);
}
std::string content(
(std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
return std::move(content);
}
void DeviceManager::collateAllDeviceSpecs(const OptionParser& options)
{
allDeviceSpecs = options.deviceSpecs;
for (const auto& file : options.deviceSpecFiles)
{
std::string fileContent = readDeviceFile(file);
if (!allDeviceSpecs.empty()) {
allDeviceSpecs += "||";
}
allDeviceSpecs += fileContent;
}
}
void DeviceManager::parseAllDeviceSpecs(void)
{
std::unique_ptr<FILE, decltype(&fclose)> input(
fmemopen((void*)allDeviceSpecs.c_str(),
allDeviceSpecs.size(), "r"),
&fclose);
if (!input) {
throw std::runtime_error("Failed to open memory as file");
}
deviceSpeclin = input.get();
if (deviceSpecpparse()) {
throw std::runtime_error("Failed to parse device specs");
}
}
-34
View File
@@ -1,34 +0,0 @@
%option prefix="deviceSpecl"
%{
#include <vector>
#include <deviceManager/deviceManager.h>
#include "deviceSpecp.hh"
%}
%%
"+adev" {
deviceSpecplval.chr = yytext[1];
return KEYWORD_SPECTYPE_ACTUATOR;
}
"+edev" {
deviceSpecplval.chr = yytext[1];
return KEYWORD_SPECTYPE_EXTROSPECTOR;
}
"+idev" {
deviceSpecplval.chr = yytext[1];
return KEYWORD_SPECTYPE_INTEROSPECTOR;
}
"||" { return DOUBLE_PIPE; }
"|" { return PIPE; }
"(" { return LPAREN; }
")" { return RPAREN; }
[^\|\(\) \t\r\n]+ { deviceSpecplval.str = strdup(yytext); return STRING; }
\r?\n { /* ignore newlines */ }
[ \t]+ { /* ignore whitespace */ }
. { return yytext[0]; }
%%
int deviceSpeclwrap(void)
{
return 1; // Indicate end of input
}
-118
View File
@@ -1,118 +0,0 @@
%{
#include <vector>
#include <utility>
#include <string>
#include <cstring>
#include <cstdlib>
#include <stdexcept>
#include <deviceManager/deviceManager.h>
#ifndef yylex
/* We use different prefixes for the lexer and parser.
* * Our lexer's prefix is deviceSpecl.
* * Our parser's prefix is deviceSpecp.
*
* Yacc and Bison don't have a way to handle the scenario where the lexer has
* a different prefix from the parser that they generate. They assume that the
* lexer must have the same prefix as the parser they generate. So we just use
* this #define below to override yacc/bison's presumed prefix for the lexer.
*/
#error "Yacc should have defined yylex, and we need to override it to tell yacc the name of our lex function."
#endif
#undef yylex
#define yylex deviceSpecllex
// Declare the symbols that our lexer will export.
int yylex(void);
void yyerror(const char *message)
{
throw std::runtime_error(
std::string("deviceSpec parser error: ")
+ std::string(message));
}
%}
%union {
char* str;
char chr;
DeviceManager::SensorDeviceSpec* sensorSpec;
DeviceManager::InteroceptorDeviceSpec* interoceptorSpec;
DeviceManager::ExtrospectorDeviceSpec* extrospectorSpec;
std::vector<std::string>* stringVector;
}
%token <str> STRING
%token PIPE DOUBLE_PIPE LPAREN RPAREN
%token <chr> KEYWORD_SPECTYPE_ACTUATOR
%token <chr> KEYWORD_SPECTYPE_EXTROSPECTOR KEYWORD_SPECTYPE_INTEROSPECTOR
%type <stringVector> params opt_params
%type <sensorSpec> spec_body
%type <interoceptorSpec> interoceptor_spec
%type <extrospectorSpec> extrospector_spec
%%
file: /* NOTHING */
| sensor_specs
;
sensor_specs:
sensor_spec
| sensor_specs DOUBLE_PIPE sensor_spec
;
sensor_spec:
interoceptor_spec
| extrospector_spec
;
interoceptor_spec:
KEYWORD_SPECTYPE_INTEROSPECTOR PIPE spec_body {
DeviceManager::InteroceptorDeviceSpec *spec =
static_cast<DeviceManager::InteroceptorDeviceSpec *>($3);
spec->sensorType = $1;
DeviceManager::interoceptorDeviceSpecs.push_back(*spec);
delete spec;
}
;
extrospector_spec:
KEYWORD_SPECTYPE_EXTROSPECTOR PIPE spec_body {
DeviceManager::ExtrospectorDeviceSpec *spec =
static_cast<DeviceManager::ExtrospectorDeviceSpec *>($3);
spec->sensorType = $1;
DeviceManager::extrospectorDeviceSpecs.push_back(*spec);
delete spec;
}
;
spec_body:
STRING PIPE STRING LPAREN opt_params RPAREN PIPE STRING LPAREN opt_params RPAREN PIPE STRING {
$$ = new DeviceManager::SensorDeviceSpec();
$$->sensorType = '\0';
$$->implexor = std::string($1);
$$->api = std::string($3);
$$->apiParams = std::move(*$5);
$$->provider = std::string($8);
$$->providerParams = std::move(*$10);
$$->deviceSelector = std::string($13);
delete $5;
delete $10;
}
;
opt_params:
params
| /* empty */ { $$ = new std::vector<std::string>(); }
;
params:
STRING { $$ = new std::vector<std::string>{ $1 }; }
| params PIPE STRING { $$ = $1; $$->push_back($3); }
;
%%
-17
View File
@@ -1,17 +0,0 @@
#ifndef _BODY_MAP_H
#define _BODY_MAP_H
#include <set>
#include <cstdint>
#include <body/limb.h>
class BodyMap {
public:
BodyMap() = default;
~BodyMap() = default;
std::set<uint32_t, BodyLimb> limbs;
};
#endif // _BODY_MAP_H
-64
View File
@@ -1,64 +0,0 @@
#ifndef BODY_MESSAGE_H
#define BODY_MESSAGE_H
#include <vector>
#include <cstdint>
#include <body/limb.h>
#include <body/bodyPart.h>
class BodyMessage
{
public:
BodyMessage() = default;
~BodyMessage() = default;
};
class BodySpotImpactEntry
{
public:
enum class ReportType
{
PRESSURE,
PAIN,
PLEASURE,
HEAT,
COLD
};
BodySpotImpactEntry(uint32_t _spot, ReportType _type, uint32_t _value)
: spot(_spot), type(_type), value(_value)
{}
~BodySpotImpactEntry() = default;
public:
uint32_t spot;
ReportType type;
uint32_t value;
};
class BodySpotImpactInd
: public BodyMessage
{
public:
BodySpotImpactInd(BodyPart &_part) : part(_part) {}
~BodySpotImpactInd() = default;
public:
BodyPart &part;
std::vector<BodySpotImpactEntry> entries;
};
class BodyPartMsg
: public BodyMessage
{
public:
BodyPartMsg(const BodyPart& _part)
:part(_part)
{}
public:
const BodyPart& part;
};
#endif // BODY_MESSAGE_H
-28
View File
@@ -1,28 +0,0 @@
#ifndef BODY_LIMB_H
#define BODY_LIMB_H
#include <string>
#include <set>
#include <cstdint>
#include <body/bodyPart.h>
class BodyLimb
{
public:
BodyLimb(uint32_t _id) : id(_id) {}
BodyLimb(uint32_t _id,
const std::string& _name, const std::string& _desc,
const std::string& _loc)
: id(_id), name(_name), description(_desc), location(_loc)
{}
~BodyLimb() = default;
public:
uint32_t id;
std::string name, description, location;
std::set<uint32_t, BodyPart> parts;
};
#endif // BODY_LIMB_H
-30
View File
@@ -1,30 +0,0 @@
#ifndef _CHRONOMENON_H
#define _CHRONOMENON_H
#include <vector>
#include <qualeBundle.h>
#include <mentalEntity.h>
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
-11
View File
@@ -1,11 +0,0 @@
#ifndef _CONCEPT_H
#define _CONCEPT_H
#include <mentalEntity.h>
class Concept
: public MentalEntity
{
};
#endif
@@ -1,58 +0,0 @@
#ifndef DEVICEMANAGER_H
#define DEVICEMANAGER_H
#include <vector>
#include <string>
#include <opts.h>
#include <utility>
#include <iostream>
class DeviceManager
{
public:
struct SensorDeviceSpec
{
char sensorType;
std::string implexor;
std::string api;
std::vector<std::string> apiParams;
std::string provider;
std::vector<std::string> providerParams;
std::string deviceSelector;
friend std::ostream& operator<<(
std::ostream& os, const SensorDeviceSpec& spec);
};
struct InteroceptorDeviceSpec : public SensorDeviceSpec
{
};
struct ExtrospectorDeviceSpec : public SensorDeviceSpec
{
};
static DeviceManager& getInstance()
{
static DeviceManager instance;
return instance;
}
std::string readDeviceFile(const std::string& filename);
void collateAllDeviceSpecs(const OptionParser& options);
void parseAllDeviceSpecs(void);
static const std::string printDeviceSpecs();
private:
DeviceManager() = default;
~DeviceManager() = default;
DeviceManager(const DeviceManager&) = delete;
DeviceManager& operator=(const DeviceManager&) = delete;
public:
std::string allDeviceSpecs;
static std::vector<InteroceptorDeviceSpec> interoceptorDeviceSpecs;
static std::vector<ExtrospectorDeviceSpec> extrospectorDeviceSpecs;
};
#endif // DEVICEMANAGER_H
-21
View File
@@ -1,21 +0,0 @@
#ifndef DIRECTOR_H
#define DIRECTOR_H
#include <config.h>
#include <goal.h>
namespace hk {
namespace director {
class Director {
public:
Director() = default;
~Director() = default;
Goal purpose;
};
} // namespace director
} // namespace hk
#endif // DIRECTOR_H
-17
View File
@@ -1,17 +0,0 @@
#ifndef _GOAL_H
#define _GOAL_H
#include <simulator/scene.h>
namespace hk {
class Goal
: public simulator::Scene {
public:
Goal() = default;
~Goal() = default;
};
} // namespace hk
#endif
-14
View File
@@ -1,14 +0,0 @@
#ifndef _MENTAL_ENTITY_H
#define _MENTAL_ENTITY_H
namespace hk {
class MentalEntity
{
public:
using Id = uint32_t;
};
} // namespace hk
#endif
-28
View File
@@ -1,28 +0,0 @@
#ifndef _MIND_H
#define _MIND_H
#include <config.h>
#include <thread>
#include <director/director.h>
#include <simulator/simulator.h>
namespace hk {
class Mind
{
public:
void execute(void);
public:
std::thread directorThread;
std::thread simulatorThread;
std::thread subconsciousThread;
director::Director director;
simulator::Simulator canvas;
};
} // namespace hk
#endif
-27
View File
@@ -1,27 +0,0 @@
#ifndef OPTS_H
#define OPTS_H
#include <vector>
#include <string>
#include <getopt.h>
// Define a class to hold the options and parse arguments
class OptionParser
{
public:
OptionParser() : verbose(false), printUsage(false) {}
~OptionParser() = default;
void parseArguments(int argc, char *argv[]);
void dumpOptions() const;
std::string getUsage() const;
public:
std::string deviceSpecs;
std::vector<std::string> deviceSpecFiles;
bool verbose, printUsage;
static struct option longOptions[];
};
#endif // OPTS_H
-39
View File
@@ -1,39 +0,0 @@
#ifndef _QUALE_H
#define _QUALE_H
#include <cstdint>
#include <attentionTrigger.h>
class Quale
{
public:
enum class Type
{
NEUTRAL,
/* Bounding refers to qualia such as tactile pressure which
* are mostly neutral but disclose information about the limits
* of the body.
**/
BOUNDING,
PAINFUL,
PLEASURABLE
} type;
int32_t intensity;
};
class NeutralQuale
: public Quale
{
};
class NonNeutralQuale
: public Quale, public AttentionTrigger
{
public:
virtual void eventInd(void);
public:
};
#endif
-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
-2
View File
@@ -1,2 +0,0 @@
#include <mind.h>
-71
View File
@@ -1,71 +0,0 @@
#include <opts.h>
#include <iostream>
#include <stdexcept>
#include <getopt.h>
#include <string>
#include <vector>
struct option OptionParser::longOptions[] = {
{"devicespec", required_argument, 0, 's'},
{"spec", required_argument, 0, 's'},
{"devspec", required_argument, 0, 's'},
{"devfile", required_argument, 0, 'd'},
{"devicefile", required_argument, 0, 'd'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, '?'},
{0, 0, 0, 0}
};
void OptionParser::parseArguments(int argc, char *argv[])
{
int opt;
int optionIndex = 0;
optind = 1; // Reset optind to 1 before parsing
while ((opt = getopt_long(
argc, argv, "s:d:v?", longOptions, &optionIndex)) != -1)
{
switch (opt)
{
case 's':
if (!deviceSpecs.empty()) {
deviceSpecs += "||";
}
deviceSpecs += std::string(optarg);
break;
case 'd':
deviceSpecFiles.push_back(optarg);
break;
case 'v':
verbose = true;
break;
case '?':
printUsage = true;
return;
default:
throw std::invalid_argument("Invalid argument encountered: " + std::string(argv[optind - 1]));
}
}
}
std::string OptionParser::getUsage() const
{
return "Usage: program [-s|--devicespec|--spec|--devspec <device_spec>] "
"[-d|--devfile|--devicefile <filename>] "
"[-v|--verbose] "
"[-?|--help]";
}
void OptionParser::dumpOptions() const
{
if (verbose) {
std::cout << "Verbose mode is on" << std::endl;
}
std::cout << "Device Specs: " << deviceSpecs << std::endl;
std::cout << "Device Spec Files: ";
for (const auto& file : deviceSpecFiles) {
std::cout << file << " ";
}
std::cout << std::endl;
}
-2
View File
@@ -1,2 +0,0 @@
#include <nonNeutralQualia.h>
+58
View File
@@ -0,0 +1,58 @@
#ifndef ASYNCHRONOUS_BRIDGE_H
#define ASYNCHRONOUS_BRIDGE_H
#include <boostAsioLinkageFix.h>
#include <atomic>
#include <boost/asio/io_service.hpp>
namespace smo {
class AsynchronousBridge
{
public:
AsynchronousBridge(boost::asio::io_service &io_service)
: isAsyncOperationComplete(false), io_service(io_service)
{}
void setAsyncOperationComplete(void)
{
/** EXPLANATION:
* This empty post()ed message is necessary to ensure that the thread
* that's waiting on the io_service is signaled to wake up and check
* the io_service's queue.
*/
isAsyncOperationComplete.store(true);
io_service.post([]{});
}
void waitForAsyncOperationCompleteOrIoServiceStopped(void)
{
for (;;)
{
io_service.run_one();
if (isAsyncOperationComplete.load() || io_service.stopped())
{ break; }
/** 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.
*/
}
}
bool exitedBecauseIoServiceStopped(void) const
{ return io_service.stopped(); }
private:
std::atomic<bool> isAsyncOperationComplete;
boost::asio::io_service &io_service;
};
} // namespace smo
#endif // ASYNCHRONOUS_BRIDGE_H
+158
View File
@@ -0,0 +1,158 @@
#ifndef ASYNCHRONOUS_CONTINUATION_H
#define ASYNCHRONOUS_CONTINUATION_H
#include <functional>
#include <memory>
#include <exception>
#include <componentThread.h>
#include <callback.h>
#include <callableTracer.h>
#include <asynchronousContinuationChainLink.h>
namespace smo {
/**
* AsynchronousContinuation - Template base class for async sequence management
*
* This template provides a common pattern for managing asynchronous operations
* that need to maintain object lifetime through a sequence of callbacks.
*
* The template parameter OriginalCbFnT represents the signature of the original
* callback that will be invoked when the async sequence completes.
*/
template <class OriginalCbFnT>
class AsynchronousContinuation
: public AsynchronousContinuationChainLink
{
public:
explicit AsynchronousContinuation(Callback<OriginalCbFnT> originalCb)
: originalCallback(std::move(originalCb))
{}
/** EXPLANATION:
* Each numbered segmented sequence persists the lifetime of the
* continuation object by taking a copy of its shared_ptr.
*/
typedef void (SegmentFn)(
std::shared_ptr<AsynchronousContinuation<OriginalCbFnT>>
lifetimePreservingConveyance);
/** EXPLANATION:
* When an exception is thrown in a an async callee, which pertains to an
* error in the data given by the caller, we ought not to throw the
* exception within the callee. Instead, we should store the exception
* in the continuation object and return it to the caller.
*
* The caller should then call checkException() to rethrow it on its
* own stack.
*
* This macro should be used by the caller to bubble the exception to the
* caller.
*/
#define CALLEE_SETEXC(continuation, type, exc_obj) \
(continuation)->exception = std::make_exception_ptr<type>(exc_obj)
#define CALLEE_SETEXC_CALLCB(continuation, type, exc_obj) \
do { \
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; \
} while(0)
// Call this in the caller to rethrow the exception.
void checkException()
{
if (exception)
{ std::rethrow_exception(exception); }
}
// Implement the virtual method from AsynchronousContinuationChainLink
virtual std::shared_ptr<AsynchronousContinuationChainLink>
getCallersContinuationShPtr() const override
{ return originalCallback.callerContinuation; }
public:
Callback<OriginalCbFnT> originalCallback;
std::exception_ptr exception;
};
/**
* NonPostedAsynchronousContinuation - For continuations that don't post
* callbacks
*
* Note: We intentionally do not create a
* LockedNonPostedAsynchronousContinuation because the only way to implement
* non-posted locking would be via busy-spinning or sleeplocks. This would
* eliminate the throughput advantage from our Qspinning mechanism, which
* relies on re-posting to the io_service queue when locks are unavailable.
*/
template <class OriginalCbFnT>
class NonPostedAsynchronousContinuation
: public AsynchronousContinuation<OriginalCbFnT>
{
public:
explicit NonPostedAsynchronousContinuation(
Callback<OriginalCbFnT> originalCb)
: AsynchronousContinuation<OriginalCbFnT>(originalCb)
{}
/**
* @brief Call the original callback with perfect forwarding
* (immediate execution)
*
* This implementation calls the original callback immediately without
* posting to any thread or queue. Used for non-posted continuations.
*
* @param args Arguments to forward to the original callback
*/
template<typename... Args>
void callOriginalCb(Args&&... args)
{
if (AsynchronousContinuation<OriginalCbFnT>::originalCallback
.callbackFn)
{
AsynchronousContinuation<OriginalCbFnT>::originalCallback
.callbackFn(std::forward<Args>(args)...);
}
}
};
template <class OriginalCbFnT>
class PostedAsynchronousContinuation
: public AsynchronousContinuation<OriginalCbFnT>
{
public:
PostedAsynchronousContinuation(
const std::shared_ptr<ComponentThread> &caller,
Callback<OriginalCbFnT> originalCbFn)
: AsynchronousContinuation<OriginalCbFnT>(originalCbFn),
caller(caller)
{}
template<typename... Args>
void callOriginalCb(Args&&... args)
{
if (AsynchronousContinuation<OriginalCbFnT>::originalCallback
.callbackFn)
{
caller->getIoService().post(
STC(std::bind(
AsynchronousContinuation<OriginalCbFnT>::originalCallback
.callbackFn,
std::forward<Args>(args)...)));
}
}
public:
std::shared_ptr<ComponentThread> caller;
};
} // namespace smo
#endif // ASYNCHRONOUS_CONTINUATION_H
@@ -0,0 +1,32 @@
#ifndef ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H
#define ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H
#include <memory>
namespace smo {
/**
* @brief Base class for all asynchronous continuation chain links
*
* This non-template base class provides type erasure for the continuation
* chain, allowing RTTI and dynamic casting when walking the chain.
*
* The chain walking logic can use dynamic_cast to determine the most
* derived type and perform appropriate operations.
*
* Inherits from enable_shared_from_this to allow objects to obtain a
* shared_ptr to themselves, which is useful for gridlock detection tracking.
*/
class AsynchronousContinuationChainLink
: public std::enable_shared_from_this<AsynchronousContinuationChainLink>
{
public:
virtual ~AsynchronousContinuationChainLink() = default;
virtual std::shared_ptr<AsynchronousContinuationChainLink>
getCallersContinuationShPtr() const = 0;
};
} // namespace smo
#endif // ASYNCHRONOUS_CONTINUATION_CHAIN_LINK_H
+64
View File
@@ -0,0 +1,64 @@
#ifndef ASYNCHRONOUS_LOOP_H
#define ASYNCHRONOUS_LOOP_H
#include <atomic>
namespace smo {
class AsynchronousLoop
{
public:
AsynchronousLoop(
const unsigned int nTotal,
unsigned int nSucceeded=0, unsigned int nFailed=0)
: nTotal(nTotal), nSucceeded(nSucceeded), nFailed(nFailed)
{}
AsynchronousLoop(const AsynchronousLoop& other)
: nTotal(other.nTotal),
nSucceeded(other.nSucceeded.load()), nFailed(other.nFailed.load())
{}
AsynchronousLoop& operator=(const AsynchronousLoop& other)
{
if (this != &other)
{
nTotal = other.nTotal;
nSucceeded.store(other.nSucceeded.load());
nFailed.store(other.nFailed.load());
}
return *this;
}
bool isComplete(void) const
{
return nSucceeded + nFailed == nTotal;
}
void incrementSuccessOrFailureDueTo(bool success)
{
if (success)
{ ++nSucceeded; }
else
{ ++nFailed; }
}
bool incrementSuccessOrFailureAndTestForCompletionDueTo(bool success)
{
incrementSuccessOrFailureDueTo(success);
return isComplete();
}
bool nTotalIsZero(void) const
{
return nTotal == 0;
}
public:
unsigned int nTotal;
std::atomic<unsigned int> nSucceeded, nFailed;
};
} // namespace smo
#endif // ASYNCHRONOUS_LOOP_H
+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
+31
View File
@@ -0,0 +1,31 @@
#ifndef CALLBACK_H
#define CALLBACK_H
#include <memory>
namespace smo {
// Forward declaration
class AsynchronousContinuationChainLink;
/**
* @brief Callback class that wraps a function and its caller continuation
*
* This class provides a way to pass both a callback function and the
* caller's continuation in a single object, enabling deadlock detection
* by walking the chain of continuations.
*
* Usage: Callback<CbFnT>{context, std::bind(...)}
*/
template<typename CbFnT>
class Callback
{
public:
// Aggregate initialization allows: Callback<CbFnT>{context, std::bind(...)}
std::shared_ptr<AsynchronousContinuationChainLink> callerContinuation;
CbFnT callbackFn;
};
} // namespace smo
#endif // CALLBACK_H
+51
View File
@@ -0,0 +1,51 @@
#ifndef _CONFIG_H
#define _CONFIG_H
/* Package information */
#define PACKAGE_NAME "@PROJECT_NAME@"
#define PACKAGE_VERSION "@PROJECT_VERSION@"
/* Mind oscillator configuration */
#define CONFIG_MIND_VOSCILLATOR_PERIOD_MS @MIND_VOSCILLATOR_PERIOD_MS@
#define CONFIG_MIND_VOSCILLATOR_FREQ_MS @MIND_VOSCILLATOR_FREQ_MS@
/* Device manager reattacher configuration */
#define CONFIG_MRNTT_DEVMGR_REATTACHER_PERIOD_MS @MRNTT_DEVMGR_REATTACHER_PERIOD_MS@
/* 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 */
#cmakedefine CONFIG_WORLD_USE_BODY_THREAD
/* Debug locking configuration */
#cmakedefine CONFIG_ENABLE_DEBUG_LOCKS
#cmakedefine CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS @DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS@
/* Debug callable tracing configuration */
#cmakedefine CONFIG_DEBUG_TRACE_CALLABLES
/* Cross-compilation configuration */
#cmakedefine CMAKE_CROSSCOMPILING
/* Common Libraries */
#cmakedefine CONFIG_LIB_XCBXORG_ENABLED
#cmakedefine CONFIG_LIB_ALSA_ENABLED
/* Stim Buff APIs */
#cmakedefine CONFIG_STIMBUFFAPI_XCBWINDOW_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_LIVOXGEN1_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_V4L_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_ALSAMIC_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_LIVOX_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_R3LIVE_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_FASTLIO2_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_ADALIO2_ENABLED
#cmakedefine CONFIG_STIMBUFFAPI_DEEPLIO2_ENABLED
/* Wilzor APIs */
#cmakedefine CONFIG_WILZORAPI_XCBMOUSE_ENABLED
#cmakedefine CONFIG_WILZORAPI_XCBKEYBOARD_ENABLED
#cmakedefine CONFIG_WILZORAPI_ALSAVOICE_ENABLED
#endif /* _CONFIG_H */
+85
View File
@@ -0,0 +1,85 @@
#ifndef DEPENDENCY_GRAPH_H
#define DEPENDENCY_GRAPH_H
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <memory>
namespace smo {
// Forward declarations
class AsynchronousContinuationChainLink;
/**
* @brief DependencyGraph - Represents a directed graph for lock dependency analysis
*
* This graph represents dependencies between continuations (lockvokers) where
* an edge from A to B means that continuation A wants a lock that is held by
* continuation B. This is used to detect circular dependencies (gridlocks).
*/
class DependencyGraph
{
public:
typedef std::shared_ptr<AsynchronousContinuationChainLink> Node;
// Each node maps to a set of nodes it depends on
typedef std::unordered_map<Node, std::unordered_set<Node>> AdjacencyList;
public:
void addNode(const Node& node);
/**
* @brief Add a directed edge from source to target
* @param source The continuation that wants a lock
* @param target The continuation that holds the wanted lock
*/
void addEdge(const Node& source, const Node& target);
/**
* @brief Find all cycles in the graph using DFS
* @return Vector of cycles, where each cycle is a vector of nodes
*/
std::vector<std::vector<Node>> findCycles() const;
/**
* @brief Check if there are any cycles in the graph
* @return true if cycles exist, false otherwise
*/
bool hasCycles() const;
/**
* @brief Get the number of nodes in the graph
* @return Number of nodes
*/
size_t getNodeCount() const;
/**
* @brief Get the adjacency list for debugging
* @return Reference to the adjacency list
*/
const AdjacencyList& getAdjacencyList() const { return adjacencyList; }
private:
/**
* @brief DFS helper for cycle detection
* @param node Current node being visited
* @param visited Set of nodes that have been fully processed
* @param recursionStack Set of nodes currently in the recursion stack
* @param path Current path being explored
* @param cycles Vector to store found cycles
*/
void dfsCycleDetection(
const Node& node,
std::unordered_set<Node>& visited,
std::unordered_set<Node>& recursionStack,
std::vector<Node>& path,
std::vector<std::vector<Node>>& cycles)
const;
private:
AdjacencyList adjacencyList;
};
} // namespace smo
#endif // DEPENDENCY_GRAPH_H
+260
View File
@@ -0,0 +1,260 @@
#ifndef LOCK_SET_H
#define LOCK_SET_H
#include <vector>
#include <stdexcept>
#include <utility>
#include <memory>
#include <optional>
#include <qutex.h>
#include <lockerAndInvokerBase.h>
namespace smo {
// Forward declarations
template <class OriginalCbFnT>
class SerializedAsynchronousContinuation;
class Qutex;
/**
* @brief LockSet - Manages a collection of locks for acquisition/release
*/
template <class OriginalCbFnT>
class LockSet
{
public:
/** EXPLANATION:
* Tracks both the Qutex that must be acquired, as well as the parent
* LockerAndInvoker that this LockSet has registered into that Qutex's
* queue.
*/
struct LockUsageDesc
{
std::reference_wrapper<Qutex> qutex;
typename LockerAndInvokerBase::List::iterator iterator;
bool hasBeenReleased = false;
LockUsageDesc(std::reference_wrapper<Qutex> qutexRef,
typename LockerAndInvokerBase::List::iterator iter)
: qutex(qutexRef), iterator(iter), hasBeenReleased(false) {}
};
typedef std::vector<std::reference_wrapper<Qutex>> Set;
public:
/**
* @brief Constructor
* @param parentContinuation Reference to the parent
* SerializedAsynchronousContinuation
* @param qutexes Vector of Qutex references that must be acquired
*/
LockSet(
SerializedAsynchronousContinuation<OriginalCbFnT> &parentContinuation,
std::vector<std::reference_wrapper<Qutex>> qutexes = {})
: parentContinuation(parentContinuation), allLocksAcquired(false),
registeredInQutexQueues(false)
{
/* Convert Qutex references to LockUsageDesc (iterators will be filled
* in during registration)
*/
locks.reserve(qutexes.size());
for (auto& qutexRef : qutexes)
{
locks.emplace_back(
qutexRef,
typename LockerAndInvokerBase::List::iterator{});
}
}
/**
* @brief Register the LockSet with all its Qutex locks
* @param lockvoker The LockerAndInvoker to register with each Qutex
*
* EXPLANATION:
* I'm not sure an unregisterFromQutexQueues() method is needed.
* Why? Because if an async sequence can't acquire all locks, it will
* simply never leave the qutexQ until it eventually does. The only other
* time it will leave the qutexQ is when the program terminates.
*
* I'm not sure we'll actually cancal all in-flight async sequences --
* and especially not all those that aren't even in any io_service queues.
* To whatever extent these objects get cleaned up, they'll probably be
* cleaned up in the qutexQ's std::list destructor -- and that won't
* execute any fancy cleanup logic. It'll just clear() out the list.
*/
void registerInQutexQueues(
const std::shared_ptr<LockerAndInvokerBase> &lockvoker
)
{
/** EXPLANATION:
* Register the lockvoker with each Qutex and store the returned
* iterator to its place within each Qutex's queue. We store the
* iterator so that we can quickly move the lockvoker around within
* the queue, and eventually, erase() it when we acquire all the
* locks.
*/
for (auto& lockUsageDesc : locks)
{
lockUsageDesc.iterator = lockUsageDesc.qutex.get().registerInQueue(
lockvoker);
}
registeredInQutexQueues = true;
}
void unregisterFromQutexQueues()
{
if (!registeredInQutexQueues)
{
throw std::runtime_error(
std::string(__func__) +
": LockSet::unregisterFromQutexQueues() called but not "
"registered in Qutex queues");
}
// Unregister from all qutex queues
for (auto& lockUsageDesc : locks)
{
auto it = lockUsageDesc.iterator;
lockUsageDesc.qutex.get().unregisterFromQueue(it);
}
}
/**
* @brief Try to acquire all locks in order; back off if acquisition fails
* @param lockvoker The LockerAndInvoker attempting to acquire the locks
* @param firstFailedQutex Output parameter to receive the first Qutex that
* failed acquisition (can be nullptr)
* @return true if all locks were acquired, false otherwise
*/
bool tryAcquireOrBackOff(
LockerAndInvokerBase &lockvoker,
std::optional<std::reference_wrapper<Qutex>> &firstFailedQutex
= std::nullopt
)
{
if (!registeredInQutexQueues)
{
throw std::runtime_error(
std::string(__func__) +
": LockSet::tryAcquireOrBackOff() called but not registered in "
"Qutex queues");
}
if (allLocksAcquired)
{
throw std::runtime_error(
std::string(__func__) +
": LockSet::tryAcquireOrBackOff() called but allLocksAcquired "
"is already true");
}
// Try to acquire all required locks
int nAcquired = 0;
const int nRequiredLocks = static_cast<int>(locks.size());
for (auto& lockUsageDesc : locks)
{
if (!lockUsageDesc.qutex.get().tryAcquire(
lockvoker, nRequiredLocks))
{
// Set the first failed qutex for debugging
firstFailedQutex = std::ref(lockUsageDesc.qutex.get());
break;
}
nAcquired++;
}
if (nAcquired < nRequiredLocks)
{
// Release any locks we managed to acquire
for (int i = 0; i < nAcquired; i++) {
locks[i].qutex.get().backoff(lockvoker, nRequiredLocks);
}
return false;
}
allLocksAcquired = true;
return true;
}
// @brief Release all locks
void release()
{
if (!registeredInQutexQueues)
{
throw std::runtime_error(
std::string(__func__) +
": LockSet::release() called but not registered in Qutex "
"queues");
}
if (!allLocksAcquired)
{
throw std::runtime_error(
std::string(__func__) +
": LockSet::release() called but allLocksAcquired is false");
}
for (auto& lockUsageDesc : locks)
{
if (lockUsageDesc.hasBeenReleased) { continue; }
lockUsageDesc.qutex.get().release();
}
allLocksAcquired = false;
}
const LockUsageDesc &getLockUsageDesc(const Qutex &criterionLock) const
{
for (auto& lockUsageDesc : locks)
{
if (&lockUsageDesc.qutex.get() == &criterionLock) {
return lockUsageDesc;
}
}
// Should never happen if the LockSet is properly constructed
throw std::runtime_error(
std::string(__func__) +
": Qutex not found in this LockSet");
}
/**
* @brief Release a specific qutex early and mark it as released
* @param qutex The qutex to release early
*/
void releaseQutexEarly(Qutex &qutex)
{
if (!allLocksAcquired)
{
throw std::runtime_error(
std::string(__func__) +
": LockSet::releaseQutexEarly() called but allLocksAcquired is false");
}
auto& lockUsageDesc = const_cast<LockUsageDesc&>(
getLockUsageDesc(qutex));
if (!lockUsageDesc.hasBeenReleased)
{
lockUsageDesc.qutex.get().release();
lockUsageDesc.hasBeenReleased = true;
}
return;
}
public:
std::vector<LockUsageDesc> locks;
private:
SerializedAsynchronousContinuation<OriginalCbFnT> &parentContinuation;
bool allLocksAcquired, registeredInQutexQueues;
};
} // namespace smo
#endif // LOCK_SET_H
+87
View File
@@ -0,0 +1,87 @@
#ifndef LOCKER_AND_INVOKER_BASE_H
#define LOCKER_AND_INVOKER_BASE_H
#include <list>
#include <memory>
namespace smo {
// Forward declaration
class Qutex;
/**
* @brief LockerAndInvokerBase - Base class for lockvoking mechanism
*
* This base class contains the common functionality needed by Qutex,
* including the serialized continuation reference and comparison operators.
*/
class LockerAndInvokerBase
{
public:
/**
* @brief Constructor
* @param serializedContinuationVaddr Raw pointer to the serialized continuation
*/
explicit LockerAndInvokerBase(const void* serializedContinuationVaddr)
: serializedContinuationVaddr(serializedContinuationVaddr)
{}
/**
* @brief Typedef for list of LockerAndInvokerBase shared pointers
*/
typedef std::list<std::shared_ptr<LockerAndInvokerBase>> List;
/**
* @brief Get the iterator for this lockvoker in the specified Qutex's queue
* @param qutex The Qutex to get the iterator for
* @return Iterator pointing to this lockvoker in the Qutex's queue
*/
virtual List::iterator getLockvokerIteratorForQutex(Qutex& qutex) const = 0;
/**
* @brief Awaken this lockvoker by posting it to its io_service
* @param forceAwaken If true, post even if already awake
*/
virtual void awaken(bool forceAwaken = false) = 0;
/* These two are ued to iterate through the lockset of a Lockvoker in a
* template-erased manner. We use them in the gridlock detection algorithm.
*/
virtual size_t getLockSetSize() const = 0;
virtual Qutex& getLockAt(size_t index) const = 0;
/**
* @brief Equality operator
*
* Compare by the address of the continuation objects. Why?
* Because there's no guarantee that the lockvoker object that was
* passed in by the io_service invocation is the same object as that
* which is in the qutexQs. Especially because we make_shared() a
* copy when registerInQutexQueues()ing.
*
* Generally when we "wake" a lockvoker by enqueuing it, boost's
* io_service::post will copy the lockvoker object.
*/
bool operator==(const LockerAndInvokerBase &other) const
{
return serializedContinuationVaddr == other.serializedContinuationVaddr;
}
/**
* @brief Inequality operator
*/
bool operator!=(const LockerAndInvokerBase &other) const
{
return serializedContinuationVaddr != other.serializedContinuationVaddr;
}
protected:
/* Never let this monstrosity be seen beyond this class's scope.
* Remember what I've taught you, quasi-modo?
*/
const void* serializedContinuationVaddr;
};
} // namespace smo
#endif // LOCKER_AND_INVOKER_BASE_H
+11
View File
@@ -0,0 +1,11 @@
#ifndef SMO_PREPROCESSOR_H
#define SMO_PREPROCESSOR_H
#define SMO_Q(x) #x
#define SMO_QUOTE(x) SMO_Q(x)
#define SMO_CONCAT(a, b) a ## b
#define SMO_UNMANGLED "C"
#endif // SMO_PREPROCESSOR_H
+107
View File
@@ -0,0 +1,107 @@
#ifndef QUTEX_H
#define QUTEX_H
#include <config.h>
#include <list>
#include <memory>
#include <string>
#include <spinLock.h>
#include <lockerAndInvokerBase.h>
namespace smo {
/**
* @brief Qutex - Queue-based mutex for asynchronous lock management
*
* A Qutex combines a spinlock, an ownership flag, and a queue of waiting
* lockvokers to provide efficient asynchronous lock management with
* priority-based acquisition for LockSets.
*/
class Qutex
{
public:
/**
* @brief Constructor
*/
Qutex([[maybe_unused]] const std::string &_name)
:
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
name(_name), currOwner(nullptr),
#endif
isOwned(false)
{}
/**
* @brief Register a lockvoker in the queue
* @param lockvoker The lockvoker to register
* @return Iterator pointing to the registered lockvoker in the queue
*/
LockerAndInvokerBase::List::iterator registerInQueue(
const std::shared_ptr<LockerAndInvokerBase> &lockvoker
)
{
lock.acquire();
auto it = queue.insert(queue.end(), lockvoker);
lock.release();
return it;
}
/**
* @brief Unregister a lockvoker from the queue
* @param it Iterator pointing to the lockvoker to unregister
* @param shouldLock Whether to acquire the spinlock before erasing (default: true)
*/
void unregisterFromQueue(
LockerAndInvokerBase::List::iterator it, bool shouldLock = true
)
{
if (shouldLock)
{
lock.acquire();
queue.erase(it);
lock.release();
}
else {
queue.erase(it);
}
}
/**
* @brief Try to acquire the lock for a lockvoker
* @param tryingLockvoker The lockvoker attempting to acquire the lock
* @param nRequiredLocks Number of locks required by the lockvoker's LockSet
* @return true if the lock was successfully acquired, false otherwise
*/
bool tryAcquire(
const LockerAndInvokerBase &tryingLockvoker, int nRequiredLocks);
/**
* @brief Handle backoff when a lockvoker fails to acquire all required locks
* @param failedAcquirer The lockvoker that failed to acquire all locks
* @param nRequiredLocks Number of locks required by the lockvoker's LockSet
*/
void backoff(const LockerAndInvokerBase &failedAcquirer, int nRequiredLocks);
/**
* @brief Release the lock and wake up the next waiting lockvoker
*/
void release();
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
std::shared_ptr<LockerAndInvokerBase> getCurrOwner() const
{ return currOwner; }
#endif
public:
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
std::string name;
std::shared_ptr<LockerAndInvokerBase> currOwner;
#endif
SpinLock lock;
LockerAndInvokerBase::List queue;
bool isOwned;
};
} // namespace smo
#endif // QUTEX_H
+164
View File
@@ -0,0 +1,164 @@
#ifndef QUTEX_ACQUISITION_HISTORY_TRACKER_H
#define QUTEX_ACQUISITION_HISTORY_TRACKER_H
#include <unordered_map>
#include <memory>
#include <forward_list>
#include <functional>
#include "spinLock.h"
namespace smo {
// Forward declarations
class Qutex;
class AsynchronousContinuationChainLink;
class DependencyGraph;
/**
* @brief QutexAcquisitionHistoryTracker - Tracks acquisition history for
* gridlock detection
*
* This class maintains a central acquisition history to track all lockvokers
* suspected of being gridlocked. It stores information about what locks each
* timed-out lockvoker wants and what locks they hold in their continuation
* history.
*/
class QutexAcquisitionHistoryTracker
{
public:
/**
* @brief Type definition for the acquisition history entry
*
* pair.first: The firstFailedQutex that this lockvoker WANTS but can't
* acquire
* pair.second: A unique_ptr to a list of all acquired Qutexes in this
* lockvoker's continuation history
*/
typedef std::pair<
std::reference_wrapper<Qutex>,
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
> AcquisitionHistoryEntry;
/**
* @brief Type definition for the acquisition history map
*
* Key: std::shared_ptr<AsynchronousContinuationChainLink>
* (the continuation that contains the timed-out lockvoker)
* Value: AcquisitionHistoryEntry
* (its wanted lock (aka: firstFailedQutex/pair.first) + held locks)
*/
typedef std::unordered_map<
std::shared_ptr<AsynchronousContinuationChainLink>,
AcquisitionHistoryEntry
> AcquisitionHistoryMap;
public:
static QutexAcquisitionHistoryTracker& getInstance()
{
static QutexAcquisitionHistoryTracker instance;
return instance;
}
/**
* @brief Add a continuation to the acquisition history if it doesn't
* already exist
* @param continuation Shared pointer to the
* AsynchronousContinuationChainLink
* @param wantedLock The lock that this continuation wants but can't
* acquire
* @param heldLocks Unique pointer to list of locks held in this
* continuation's history (will be moved)
*/
void addIfNotExists(
std::shared_ptr<AsynchronousContinuationChainLink> &continuation,
Qutex& wantedLock,
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
heldLocks
)
{
acquisitionHistoryLock.acquire();
auto it = acquisitionHistory.find(continuation);
// If a continuation already exists, don't add it again
if (it != acquisitionHistory.end())
{
acquisitionHistoryLock.release();
return;
}
acquisitionHistory.emplace(continuation, std::make_pair(
std::ref(wantedLock), std::move(heldLocks)));
acquisitionHistoryLock.release();
}
/**
* @brief Remove a continuation from the acquisition history
*
* @param continuation Shared pointer to the
* AsynchronousContinuationChainLink to remove
* @return true if the continuation was found and removed, false if not found
*/
bool remove(
std::shared_ptr<AsynchronousContinuationChainLink> &continuation
)
{
acquisitionHistoryLock.acquire();
auto it = acquisitionHistory.find(continuation);
if (it != acquisitionHistory.end())
{
acquisitionHistory.erase(it);
acquisitionHistoryLock.release();
return true;
}
acquisitionHistoryLock.release();
return false;
}
bool heuristicallyTraceContinuationHistoryForGridlockOn(
Qutex &firstFailedQutex,
std::shared_ptr<AsynchronousContinuationChainLink>&
currentContinuation);
bool completelyTraceContinuationHistoryForGridlockOn(
Qutex &firstFailedQutex);
/**
* @brief Generates a dependency graph among known continuations, based on
* the currently known acquisition history. There may well be a cyclical
* dependency which hasn't been reported to the history tracker yet.
* @param dontAcquireLock If true, skips acquiring the internal spinlock
* (assumes caller already holds it)
*/
[[nodiscard]] std::unique_ptr<DependencyGraph> generateGraph(
bool dontAcquireLock = false);
// Disable copy constructor and assignment operator
QutexAcquisitionHistoryTracker(
const QutexAcquisitionHistoryTracker&) = delete;
QutexAcquisitionHistoryTracker& operator=(
const QutexAcquisitionHistoryTracker&) = delete;
private:
QutexAcquisitionHistoryTracker() = default;
~QutexAcquisitionHistoryTracker() = default;
private:
/** EXPLANATION:
* We use a SpinLock here instead of a Qutex because this acquisition
* history tracker is invoked within the LockerAndInvoker.
* Since LockerAndInvoker is too tightly coupled with Qutex workings, using
* a Qutex here would create a circular dependency or deadlock situation.
* Therefore, it's best to use a SpinLock on the history class to avoid
* these coupling issues.
*/
SpinLock acquisitionHistoryLock;
AcquisitionHistoryMap acquisitionHistory;
};
} // namespace smo
#endif // QUTEX_ACQUISITION_HISTORY_TRACKER_H
@@ -0,0 +1,588 @@
#ifndef SERIALIZED_ASYNCHRONOUS_CONTINUATION_H
#define SERIALIZED_ASYNCHRONOUS_CONTINUATION_H
#include <config.h>
#include <memory>
#include <atomic>
#include <chrono>
#include <iostream>
#include <optional>
#include <componentThread.h>
#include <lockSet.h>
#include <asynchronousContinuation.h>
#include <lockerAndInvokerBase.h>
#include <callback.h>
#include <qutexAcquisitionHistoryTracker.h>
namespace smo {
template <class OriginalCbFnT>
class SerializedAsynchronousContinuation
: public PostedAsynchronousContinuation<OriginalCbFnT>
{
public:
SerializedAsynchronousContinuation(
const std::shared_ptr<ComponentThread> &caller,
Callback<OriginalCbFnT> originalCbFn,
std::vector<std::reference_wrapper<Qutex>> requiredLocks)
: PostedAsynchronousContinuation<OriginalCbFnT>(caller, originalCbFn),
requiredLocks(*this, std::move(requiredLocks))
{}
template<typename... Args>
void callOriginalCb(Args&&... args)
{
requiredLocks.release();
PostedAsynchronousContinuation<OriginalCbFnT>::callOriginalCb(
std::forward<Args>(args)...);
}
// Return list of all qutexes in predecessors' LockSets; excludes self.
[[nodiscard]]
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
getAcquiredQutexHistory() const;
/**
* @brief Release a specific qutex early
* @param qutex The qutex to release early
*/
void releaseQutexEarly(Qutex &qutex)
{ requiredLocks.releaseQutexEarly(qutex); }
public:
LockSet<OriginalCbFnT> requiredLocks;
std::atomic<bool> isAwakeOrBeingAwakened{false};
/**
* @brief LockerAndInvoker - Template class for lockvoking mechanism
*
* This class wraps a std::bind result and provides locking functionality.
* When locks cannot be acquired, the object re-posts itself to the io_service
* queue, implementing the "spinqueueing" pattern.
*/
template <class InvocationTargetT>
class LockerAndInvoker
: public LockerAndInvokerBase
{
public:
/**
* @brief Constructor that immediately posts to io_service
* @param serializedContinuation Reference to the serialized continuation
* containing LockSet and target io_service
* @param target The ComponentThread whose io_service to post to
* @param invocationTarget The std::bind result to invoke when locks are acquired
*/
LockerAndInvoker(
SerializedAsynchronousContinuation<OriginalCbFnT>
&serializedContinuation,
const std::shared_ptr<ComponentThread>& target,
InvocationTargetT invocationTarget)
: LockerAndInvokerBase(&serializedContinuation),
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
creationTimestamp(std::chrono::steady_clock::now()),
#endif
serializedContinuation(serializedContinuation),
target(target),
invocationTarget(std::move(invocationTarget))
{
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
std::optional<std::reference_wrapper<Qutex>> firstDuplicatedQutex =
traceContinuationHistoryForDeadlock();
if (firstDuplicatedQutex.has_value())
{
handleDeadlock(firstDuplicatedQutex.value().get());
throw std::runtime_error(
"LockerAndInvoker::LockerAndInvoker(): Deadlock detected");
}
#endif // CONFIG_ENABLE_DEBUG_LOCKS
firstWake();
}
/**
* @brief Function call operator - tries to acquire locks and either
* invokes the target or returns (already registered in qutex queues)
*/
void operator()();
/**
* @brief Get the iterator for this lockvoker in the specified Qutex's queue
* @param qutex The Qutex to get the iterator for
* @return Iterator pointing to this lockvoker in the Qutex's queue
*/
LockerAndInvokerBase::List::iterator
getLockvokerIteratorForQutex(Qutex& qutex) const override
{
return serializedContinuation.requiredLocks.getLockUsageDesc(
qutex).iterator;
}
/**
* @brief Awaken this lockvoker by posting it to its io_service
* @param forceAwaken If true, post even if already awake
*/
void awaken(bool forceAwaken = false) override
{
bool prevVal = serializedContinuation.isAwakeOrBeingAwakened
.exchange(true);
if (prevVal == true && !forceAwaken)
{ return; }
target->getIoService().post(*this);
}
size_t getLockSetSize() const override
{ return serializedContinuation.requiredLocks.locks.size(); }
Qutex& getLockAt(size_t index) const override
{
return serializedContinuation.requiredLocks.locks[index]
.qutex.get();
}
private:
// Allow awakening by resetting the awake flag
void allowAwakening()
{ serializedContinuation.isAwakeOrBeingAwakened.store(false); }
/** EXPLANATION:
* We create a copy of the Lockvoker and then give sh_ptrs to that
* *COPY*, to each Qutex's internal queue. This enables us to keep
* the AsyncContinuation sh_ptr (which the Lockvoker contains within
* itself) alive without wasting too much memory.
*
* This way the io_service objects can remove the lockvoker from
* their queues and there'll be a copy of the lockvoker in each
* Qutex's queue.
*
* For non-serialized, posted continuations, they won't be removed
* from the io_service queue until they're executed, so there's no
* need to create copies of them. Lockvokers are removed from their
* io_service, potentially without being executed if they fail to
* acquire all locks.
*/
void registerInLockSet()
{
auto sharedLockvoker = std::make_shared<
LockerAndInvoker<InvocationTargetT>>(*this);
serializedContinuation.requiredLocks.registerInQutexQueues(
sharedLockvoker);
}
/**
* @brief First wake - register in queues and awaken
*
* Sets isAwake=true before calling awaken with forceAwaken to ensure
* that none of the locks we just registered with awaken()s a duplicate
* copy of this lockvoker on the io_service.
*/
void firstWake()
{
serializedContinuation.isAwakeOrBeingAwakened.store(true);
registerInLockSet();
// Force awaken since we just set the flag above
awaken(true);
}
// Has CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS elapsed since creation?
bool isDeadlockLikely() const
{
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - creationTimestamp);
return elapsed.count() >= CONFIG_DEBUG_QUTEX_DEADLOCK_TIMEOUT_MS;
#else
return false;
#endif
}
// Wrapper around isDeadlockLikely for gridlock detection
bool isGridlockLikely() const
{ return isDeadlockLikely(); }
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
struct obsolete {
bool traceContinuationHistoryForGridlockOn(Qutex &firstFailedQutex);
};
bool traceContinuationHistoryForDeadlockOn(Qutex &firstFailedQutex);
std::optional<std::reference_wrapper<Qutex>>
traceContinuationHistoryForDeadlock(void)
{
for (auto& lockUsageDesc
: serializedContinuation.requiredLocks.locks)
{
if (traceContinuationHistoryForDeadlockOn(
lockUsageDesc.qutex.get()))
{
return std::ref(lockUsageDesc.qutex.get());
}
}
return std::nullopt;
}
/**
* @brief Handle a likely deadlock situation by logging debug information
* @param firstFailedQutex The first qutex that failed acquisition
*/
void handleDeadlock(const Qutex &firstFailedQutex)
{
std::cerr << __func__ << ": Deadlock: "
<< "Lockvoker has been waiting for "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - this->creationTimestamp)
.count()
<< "ms, failed on qutex @" << &firstFailedQutex
<< " (" << firstFailedQutex.name << ")" << std::endl;
}
void handleGridlock(const Qutex &firstFailedQutex)
{
std::cerr << __func__ << ": Gridlock: "
<< "Lockvoker has been waiting for "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - this->creationTimestamp)
.count()
<< "ms, failed on qutex @" << &firstFailedQutex
<< " (" << firstFailedQutex.name << ")" << std::endl;
}
#endif
private:
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
std::chrono::steady_clock::time_point creationTimestamp;
#endif
SerializedAsynchronousContinuation<OriginalCbFnT>
&serializedContinuation;
std::shared_ptr<ComponentThread> target;
InvocationTargetT invocationTarget;
};
};
/******************************************************************************/
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
template <class OriginalCbFnT>
std::unique_ptr<std::forward_list<std::reference_wrapper<Qutex>>>
SerializedAsynchronousContinuation<OriginalCbFnT>::getAcquiredQutexHistory()
const
{
auto heldLocks = std::make_unique<
std::forward_list<std::reference_wrapper<Qutex>>>();
/** EXPLANATION:
* Walk through the continuation chain to collect all acquired locks
*
* We don't add the current continuation's locks because it's the one
* failing to acquire locks and backing off. So we start from the previous
* continuation.
*/
for (std::shared_ptr<AsynchronousContinuationChainLink> currContin =
this->getCallersContinuationShPtr();
currContin != nullptr;
currContin = currContin->getCallersContinuationShPtr())
{
auto serializedCont = std::dynamic_pointer_cast<
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin);
if (serializedCont == nullptr) { continue; }
// Add this continuation's locks to the held locks list
for (size_t i = 0; i < serializedCont->requiredLocks.locks.size(); ++i)
{
heldLocks->push_front(serializedCont->requiredLocks.locks[i].qutex);
}
}
return heldLocks;
}
template <class OriginalCbFnT>
template <class InvocationTargetT>
bool
SerializedAsynchronousContinuation<OriginalCbFnT>
::LockerAndInvoker<InvocationTargetT>
::traceContinuationHistoryForDeadlockOn(Qutex& firstFailedQutex)
{
/** EXPLANATION:
* In this function we will trace through the chain of continuations that
* led up to this Lockvoker's continuation. For each continuation which is
* a SerializedAsynchronousContinuation, we check through its LockSet to see
* if it contains the lock that failed acquisition. If it does, we have a
* deadlock.
*/
/* We can't start with the continuation directly referenced by this starting
* Lockvoker as it would contain the all locks we're currently trying to
* acquire...and rightly so because it's the continuation for this current
* lockvoker.
*/
for (std::shared_ptr<AsynchronousContinuationChainLink> currContin =
this->serializedContinuation.getCallersContinuationShPtr();
currContin != nullptr;
currContin = currContin->getCallersContinuationShPtr())
{
auto serializedCont = std::dynamic_pointer_cast<
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin);
if (serializedCont == nullptr) { continue; }
// Check if the firstFailedQutex is in this continuation's LockSet
try {
serializedCont->requiredLocks.getLockUsageDesc(firstFailedQutex);
} catch (const std::runtime_error& e) {
std::cerr << __func__ << ": " << e.what() << std::endl;
continue;
}
std::cout << __func__ << ":Deadlock detected: Found "
<< "firstFailedQutex @" << &firstFailedQutex
<< " (" << firstFailedQutex.name << ") in LockSet of "
<< "SerializedAsynchronousContinuation @"
<< serializedCont.get() << std::endl;
return true;
}
return false;
}
template <class OriginalCbFnT>
template <class InvocationTargetT>
bool
SerializedAsynchronousContinuation<OriginalCbFnT>
::LockerAndInvoker<InvocationTargetT>
::obsolete::traceContinuationHistoryForGridlockOn(Qutex &firstFailedQutex)
{
/** EXPLANATION:
* In this function we check for gridlocks which are slightly different
* from deadlocks. In a gridlock, two requests are waiting for locks that
* are held by the other. I.e:
*
* R1 holds LockA and is waiting for LockB.
* R2 holds LockB and is waiting for LockA.
*
* This differs from deadlocks because it's not a single request which is
* attempting to re-acquire a lock that it already holds.
*
* To detect this condition, we wait until the acquisition timeout has
* expired. Then: we extract the current owner of the first lock we're
* failing to acquire.
*
* From there, we go through each of the locks in the foreign owner's
* current (i.e: immediate, most recent continuation's) required LockSet.
* For each of the locks in the foreign owner's most immediate required
* LockSet, we trace backward in our *OWN* history to see if any of *OUR*
* continuations (excluding our most immediate continuation) contains that
* lock.
*
* If we find a match, that means that we're holding a lock that the foreign
* owner is waiting for. And we already know that the foreign owner is
* holding a lock that we're waiting for (when we extracted the current
* owner of the first failed lock in our most immediate Lockset).
*
* Hence, we have a gridlock.
*/
std::shared_ptr<LockerAndInvokerBase> foreignOwnerShPtr =
firstFailedQutex.getCurrOwner();
// If no current owner, can't be a gridlock
if (foreignOwnerShPtr == nullptr)
{ return false; }
// Use reference for the rest of the function for safety.
LockerAndInvokerBase &foreignOwner = *foreignOwnerShPtr;
/* For each lock in the foreign owner's LockSet, check if we hold it
* in any of our previous continuations (excluding our most immediate one)
*/
for (size_t i = 0; i < foreignOwner.getLockSetSize(); ++i)
{
Qutex& foreignLock = foreignOwner.getLockAt(i);
/* Skip the firstFailedQutex since we already know the foreign owner
* holds it -- hence it's impossible for any of our previous
* continuations to hold it.
*/
if (&foreignLock == &firstFailedQutex)
{ continue; }
/** EXPLANATION:
* Trace backward through our continuation history (excluding our most
* immediate continuation).
*
* The reason we exclude our most immediate continuation is because the
* LockSet acquisition algorithm backs off if it fails to acquire ALL
* locks in the set. So if the lock that the foreign owner is waiting
* for is in our most immediate continuation, and NOT in one of our
* previous continuations, then we will back off and the foreign owner
* should eventually be able to acquire that lock.
*/
for (std::shared_ptr<AsynchronousContinuationChainLink> currContin =
this->serializedContinuation.getCallersContinuationShPtr();
currContin != nullptr;
currContin = currContin->getCallersContinuationShPtr())
{
auto serializedCont = std::dynamic_pointer_cast<
SerializedAsynchronousContinuation<OriginalCbFnT>>(currContin);
if (serializedCont == nullptr) { continue; }
// Check if this continuation holds the foreign lock
try {
const auto& lockUsageDesc = serializedCont->requiredLocks
.getLockUsageDesc(foreignLock);
// Matched! We hold a lock that the foreign owner is waiting for
std::cout << __func__ << ": Gridlock detected: We hold lock @"
<< &foreignLock << " (" << foreignLock.name << ") in "
"continuation @" << serializedCont.get()
<< ", while foreign owner @" << &foreignOwner
<< " holds lock @" << &firstFailedQutex << " ("
<< firstFailedQutex.name << ") that we're waiting for"
<< std::endl;
return true;
} catch (const std::runtime_error& e) {
// This continuation doesn't hold the foreign lock. Continue.
continue;
}
}
}
return false;
}
#endif // CONFIG_ENABLE_DEBUG_LOCKS
template <class OriginalCbFnT>
template <class InvocationTargetT>
void SerializedAsynchronousContinuation<OriginalCbFnT>
::LockerAndInvoker<InvocationTargetT>::operator()()
{
if (ComponentThread::getSelf() != target)
{
throw std::runtime_error(
"LockerAndInvoker::operator(): Thread safety violation - "
"executing on wrong ComponentThread");
}
std::optional<std::reference_wrapper<Qutex>> firstFailedQutexRet;
bool deadlockLikely = isDeadlockLikely();
bool gridlockLikely = isGridlockLikely();
if (!serializedContinuation.requiredLocks.tryAcquireOrBackOff(
*this, firstFailedQutexRet))
{
// Just allow this lockvoker to be dropped from its io_service.
allowAwakening();
if (!deadlockLikely && !gridlockLikely)
{ return; }
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
Qutex &firstFailedQutex = firstFailedQutexRet.value().get();
bool isDeadlock = traceContinuationHistoryForDeadlockOn(
firstFailedQutex);
bool gridlockIsHeuristicallyLikely = false;
bool gridlockIsAlgorithmicallyLikely = false;
if (gridlockLikely)
{
auto& tracker = QutexAcquisitionHistoryTracker
::getInstance();
auto heldLocks = serializedContinuation
.getAcquiredQutexHistory();
// Add this continuation to the tracker
auto currentContinuationShPtr = serializedContinuation
.shared_from_this();
tracker.addIfNotExists(
currentContinuationShPtr,
firstFailedQutex, std::move(heldLocks));
gridlockIsHeuristicallyLikely = tracker
.heuristicallyTraceContinuationHistoryForGridlockOn(
firstFailedQutex, currentContinuationShPtr);
if (gridlockIsHeuristicallyLikely)
{
gridlockIsAlgorithmicallyLikely = tracker
.completelyTraceContinuationHistoryForGridlockOn(
firstFailedQutex);
}
}
bool isGridlock = (gridlockIsHeuristicallyLikely
|| gridlockIsAlgorithmicallyLikely);
if (!isDeadlock && !isGridlock)
{ return; }
if (isDeadlock) { handleDeadlock(firstFailedQutex); }
if (isGridlock) { handleGridlock(firstFailedQutex); }
#endif
return;
}
/** EXPLANATION:
* Successfully acquired all locks, so unregister from qutex queues.
* We do this here so that we can free up queue slots in the qutex
* queues for other lockvokers that may be waiting to acquire the
* locks. The size of the qutex queues does matter for other
* contending lockvokers; and so also does their position in the
* queues.
*
* The alternative is to leave ourself in the queues until we
* eventually release all locks; and given that we may hold locks
* even across true async hardware bottlenecks, this could take a
* long time.
*
* Granted, the fact that we own the locks means that even though
* we've removed ourselves from the queues, other lockvokers still
* can't acquire the locks anyway.
*/
serializedContinuation.requiredLocks.unregisterFromQutexQueues();
#ifdef CONFIG_ENABLE_DEBUG_LOCKS
/** EXPLANATION:
* If we were being tracked for gridlock detection but successfully
* acquired all locks, it was a false positive due to timed delay,
* long-running operation, or I/O delay
*/
if (gridlockLikely)
{
std::shared_ptr<AsynchronousContinuationChainLink>
currentContinuationShPtr =
serializedContinuation.shared_from_this();
bool removed = QutexAcquisitionHistoryTracker::getInstance()
.remove(currentContinuationShPtr);
if (removed)
{
std::cerr
<< "LockerAndInvoker::operator(): False positive "
"gridlock detection - continuation @"
<< &serializedContinuation
<< " was being tracked but successfully acquired all "
"locks. This was likely due to timed delay, "
"long-running operation, or I/O delay."
<< std::endl;
}
}
#endif
invocationTarget();
}
} // namespace smo
#endif // SERIALIZED_ASYNCHRONOUS_CONTINUATION_H

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