OSCR

Thalamus: a real-time system for synchronized, closed-loop multimodal behavioral and electrophysiological data capture.

Code ↔ Paper

7 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 7 matches
  1. [1] § Methods › Methods: timestamp latency ↔ src/thalamus/node_graph_impl.cpp, lines 1966 to 2022 · score 0.69 · Wave Generator Node, Pulse Node, Storage Node, NIDAQ, channel · Report a mismatch
  2. [2] § Methods › Methods: Performance Trace ↔ src/thalamus/node_graph_impl.cpp, lines 1966 to 2022 · score 0.64 · VideoNode, Algebra Node, Storage Node · Report a mismatch
  3. [3] § Methods › Synchrony › Timestamp logic ↔ src/thalamus/genicam_node.cpp, lines 86 to 147 · score 0.61 · GENICAM nodes, GenTL, ASIO, Boost, interfaces, threads · Report a mismatch
  4. [4] § Methods › Customizable user interface ↔ thalamus/task_controller/controller.py, lines 173 to 311 · score 0.55 · Add Task Cluster, Add Task button, UI, queue · Report a mismatch
  5. [5] § Methods › Real-time capabilities and closed-loop via gRPC ↔ thalamus/pipeline/main_impl.py, lines 93 to 236 · score 0.55 · native.exe, gRPC, client, loop, Thalamus · Report a mismatch
  6. [6] § Methods › Dependency management ↔ thalamus/build.py, lines 152 to 209 · score 0.53 · win amd64, py3, whl, Thalamus · Report a mismatch
  7. [7] § Methods › Real-time capabilities and closed-loop via gRPC ↔ thalamus/task_controller/main_impl.py, lines 169 to 235 · score 0.51 · native.exe, gRPC, client, Thalamus · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

C++ · 2,439 lines · 94 KB · GPL-3.0 · 2 matches

  1. #include <cstddef>
  2. #include <limits>
  3. #include <thalamus/tracing.hpp>
  4. #include <chrono>
  5. #include <thalamus/algebra_node.hpp>
  6. #include <thalamus/nidaq_node.hpp>
  7. #include <thalamus/storage_node.hpp>
  8. #include <thalamus/xsens_node.hpp>
  9. #include <thalamus/analog_node.hpp>
  10. #include <thalamus/aruco_node.hpp>
  11. #include <thalamus/alpha_omega_node.hpp>
  12. #include <thalamus/channel_picker_node.hpp>
  13. #include <thalamus/chessboard_node.hpp>
  14. #include <thalamus/distortion_node.hpp>
  15. #include <thalamus/genicam_node.hpp>
  16. #include <thalamus/hexascope_node.hpp>
  17. #include <thalamus/image_node.hpp>
  18. #include <thalamus/intan_node.hpp>
  19. #include <thalamus/log_node.hpp>
  20. #include <thalamus/lua_node.hpp>
  21. #include <thalamus/node_graph_impl.hpp>
  22. #include <thalamus/grpc_tls.hpp>
  23. #include <thalamus/normalize_node.hpp>
  24. #include <thalamus/oculomatic_node.hpp>
  25. #include <thalamus/ophanim_node.hpp>
  26. #include <thalamus/pupil_node.hpp>
  27. #include <thalamus/remote_node.hpp>
  28. #include <thalamus/remotelog_node.hpp>
  29. #include <thalamus/serialtouchscreen_node.hpp>
  30. #include <thalamus/joystick_node.hpp>
  31. #include <variant>
  32. #ifndef _WIN32
  33. #include <thalamus/ros2_node.hpp>
  34. #endif
  35. #ifdef _WIN32
  36. #include <thalamus/brainproducts_node.hpp>
  37. #endif
  38. #include <thalamus/run_node.hpp>
  39. #include <thalamus/run2_node.hpp>
  40. #include <thalamus/spikeglx_node.hpp>
  41. #include <thalamus/stim_printer_node.hpp>
  42. #include <thalamus/sync_node.hpp>
  43. #include <thalamus/storage2_node.hpp>
  44. #include <thalamus/task_controller_node.hpp>
  45. #include <thalamus_config.h>
  46. #include <thalamus/thread_pool.hpp>
  47. #include <thalamus/touchscreen_node.hpp>
  48. #include <thalamus/video_node.hpp>
  49. #include <thalamus/test_pulse_node.hpp>
  50. #include <thalamus/wallclock_node.hpp>
  51. #include <thalamus/delsys_node.hpp>
  52. #include <thalamus/ceci_node.hpp>
  53. #include <thalamus/frequency_node.hpp>
  54. #include <thalamus/samplemonitor_node.hpp>
  55. #include <thalamus/plugin.h>
  56. #include <thalamus/modalities_util.hpp>
  57. #include <thalamus/node_util.hpp>
  58. #ifdef __clang__
  59. #pragma clang diagnostic push
  60. #pragma clang diagnostic ignored "-Weverything"
  61. #endif
  62. #include "boost/signals2/connection.hpp"
  63. #include <grpcpp/create_channel.h>
  64. #include <thalamus.grpc.pb.h>
  65. #include <SDL3/SDL.h>
  66. #include <SDL3/SDL_vulkan.h>
  67. #ifdef __clang__
  68. #pragma clang diagnostic pop
  69. #endif
  70. #include <thalamus/image_viewer.hpp>
  71. struct ThalamusState {
  72. int count;
  73. thalamus::ObservableCollection::Value value;
  74. };
  75. struct ThalamusStateIter {
  76. thalamus::ObservableCollection::Value value;
  77. std::variant<thalamus::ObservableCollection::VectorIteratorWrapper,
  78. thalamus::ObservableCollection::MapIteratorWrapper> begin;
  79. std::variant<thalamus::ObservableCollection::VectorIteratorWrapper,
  80. thalamus::ObservableCollection::MapIteratorWrapper> pos;
  81. std::variant<thalamus::ObservableCollection::VectorIteratorWrapper,
  82. thalamus::ObservableCollection::MapIteratorWrapper> end;
  83. bool started;
  84. };
  85. struct ThalamusIoContext {
  86. boost::asio::io_context& io_context;
  87. ThalamusIoContext(boost::asio::io_context& _io_context) : io_context(_io_context) {}
  88. };
  89. struct ThalamusNodeGraph {
  90. thalamus::NodeGraph* graph;
  91. ThalamusNodeGraph(thalamus::NodeGraph* _graph) : graph(_graph) {}
  92. };
  93. struct ThalamusVkQueueLock {
  94. std::unique_lock<std::mutex> lock;
  95. };
  96. struct ThalamusStateConnection {
  97. boost::signals2::scoped_connection connection;
  98. ThalamusStateConnection(boost::signals2::connection _connection) : connection(_connection) {}
  99. };
  100. struct ThalamusTimer {
  101. boost::asio::steady_timer timer;
  102. ThalamusTimer(boost::asio::io_context& _io_context) : timer(_io_context) {}
  103. };
  104. struct ThalamusErrorCode {
  105. const boost::system::error_code *error;
  106. ThalamusErrorCode() : error(nullptr) {}
  107. ThalamusErrorCode(const boost::system::error_code &_error) : error(&_error) {}
  108. };
  109. struct ThalamusSerialPort {
  110. boost::asio::serial_port port;
  111. boost::system::error_code error;
  112. ThalamusErrorCode error_wrapper;
  113. };
  114. struct ThalamusStreamBuf {
  115. boost::asio::streambuf buffer;
  116. };
  117. struct ThalamusJson {
  118. size_t refs;
  119. boost::json::value value;
  120. };
  121. struct ThalamusRequestHandle {
  122. std::function<void(const boost::json::value &)> callback;
  123. };
  124. struct ThalamusNodeGetConnection {
  125. boost::signals2::scoped_connection connection;
  126. };
  127. struct ThalamusNodeReadyConnection {
  128. boost::signals2::scoped_connection connection;
  129. ThalamusNode* node;
  130. };
  131. struct ThalamusOffMainSignaler {
  132. thalamus::node::OffMainSignaler signaler;
  133. };
  134. static std::string to_string(const ThalamusCharSpan& span) {
  135. return std::string(span.data, span.size);
  136. }
  137. static std::string_view to_string_view(const ThalamusCharSpan& span) {
  138. return std::string_view(span.data, span.size);
  139. }
  140. inline std::ostream& operator<<(std::ostream& os, const ThalamusCharSpan& span) {
  141. return os.write(span.data, std::streamsize(span.size));
  142. }
  143. namespace thalamus {
  144. using namespace std::chrono_literals;
  145. struct INodeFactory {
  146. virtual ~INodeFactory();
  147. virtual Node *create(ObservableDictPtr state,
  148. boost::asio::io_context &io_context,
  149. NodeGraph *graph) = 0;
  150. virtual bool prepare() = 0;
  151. virtual void cleanup() = 0;
  152. virtual std::string type_name() = 0;
  153. };
  154. INodeFactory::~INodeFactory() {}
  155. template <typename T> struct NodeFactory : public INodeFactory {
  156. Node *create(ObservableDictPtr state, boost::asio::io_context &io_context,
  157. NodeGraph *graph) override {
  158. return new T(state, io_context, graph);
  159. }
  160. bool prepare() override {
  161. constexpr bool has_prepare = requires { T::prepare(); };
  162. if constexpr (has_prepare) {
  163. return T::prepare();
  164. } else {
  165. return true;
  166. }
  167. }
  168. void cleanup() override {
  169. constexpr bool has_cleanup = requires { T::cleanup(); };
  170. if constexpr (has_cleanup) {
  171. T::cleanup();
  172. }
  173. }
  174. std::string type_name() override { return T::type_name(); }
  175. };
  176. static void string_to_span(struct ThalamusCharSpan* output, const std::string& input) {
  177. auto data = new char[input.size()];
  178. std::copy(input.begin(), input.end(), data);
  179. output->data = data;
  180. output->size = input.size();
  181. output->owns_data = 1;
  182. }
  183. struct ExtNode : public Node, public AnalogNode, public ImageNode, public MotionCaptureNode, public TextNode {
  184. ThalamusNode* node;
  185. ThalamusNodeFactory *factory;
  186. ThalamusAPI *api;
  187. std::function<void()> drop_ready;
  188. ExtNode(ThalamusNode *_node, ThalamusNodeFactory *_factory, ThalamusAPI *_api)
  189. : node(_node), factory(_factory), api(_api) {
  190. if(node && node->signals_offmain) {
  191. ready_multithreaded.emplace();
  192. }
  193. }
  194. ~ExtNode() override;
  195. void predrop(std::function<void()> on_drop_ready) override {
  196. THALAMUS_LOG(info) << "*ext* ExtNode::predrop";
  197. drop_ready = on_drop_ready;
  198. node->predrop(node);
  199. }
  200. size_t modalities() const override {
  201. size_t result = 0;
  202. result |= node->analog != nullptr ? THALAMUS_MODALITY_ANALOG : 0;
  203. result |= node->mocap != nullptr ? THALAMUS_MODALITY_MOCAP : 0;
  204. result |= node->image != nullptr ? THALAMUS_MODALITY_IMAGE : 0;
  205. result |= node->text != nullptr ? THALAMUS_MODALITY_TEXT : 0;
  206. return result;
  207. }
  208. static std::string type_name() {
  209. THALAMUS_ABORT("Unimplemented");
  210. }
  211. std::span<const double> data(int channel) const override {
  212. ThalamusDoubleSpan temp;
  213. node->analog->data(&temp, node, channel);
  214. return std::span<const double>(temp.data, temp.data+temp.size);
  215. }
  216. std::span<const short> short_data(int channel) const override {
  217. ThalamusShortSpan temp;
  218. node->analog->short_data(&temp, node, channel);
  219. return std::span<const short>(temp.data, temp.data+temp.size);
  220. }
  221. std::span<const int> int_data(int channel) const override {
  222. ThalamusIntSpan temp;
  223. node->analog->int_data(&temp, node, channel);
  224. return std::span<const int>(temp.data, temp.data+temp.size);
  225. }
  226. std::span<const uint64_t> ulong_data(int channel) const override {
  227. ThalamusULongSpan temp;
  228. node->analog->ulong_data(&temp, node, channel);
  229. return std::span<const uint64_t>(temp.data, temp.data+temp.size);
  230. }
  231. int num_channels() const override {
  232. return node->analog->num_channels(node);
  233. }
  234. std::chrono::nanoseconds sample_interval(int channel) const override {
  235. return std::chrono::nanoseconds(node->analog->sample_interval_ns(node, channel));
  236. }
  237. std::chrono::nanoseconds time() const override {
  238. return std::chrono::nanoseconds(node->time_ns(node));
  239. }
  240. std::chrono::nanoseconds remote_time() const override {
  241. return 0ns;
  242. }
  243. std::string_view name(int channel) const override {
  244. ThalamusCharSpan temp2;
  245. node->analog->name(&temp2, node, channel);
  246. return std::string_view(temp2.data, temp2.data + temp2.size);
  247. }
  248. void inject(const thalamus::vector<std::span<double const>> &,
  249. const thalamus::vector<std::chrono::nanoseconds> &,
  250. const thalamus::vector<std::string_view> &) override {
  251. THALAMUS_ABORT("Unimplemented");
  252. }
  253. bool has_analog_data() const override {
  254. return node->analog->has_analog_data(node);
  255. }
  256. bool is_short_data() const override {
  257. return node->analog->is_short_data ? node->analog->is_short_data(node) : false;
  258. }
  259. bool is_int_data() const override {
  260. return node->analog->is_int_data ? node->analog->is_int_data(node) : false;
  261. }
  262. bool is_ulong_data() const override {
  263. return node->analog->is_ulong_data ? node->analog->is_ulong_data(node) : false;
  264. }
  265. bool is_transformed() const override {
  266. return node->analog->is_transformed ? node->analog->is_transformed(node) : false;
  267. }
  268. double scale(int channel) const override {
  269. return node->analog->scale(node, channel);
  270. }
  271. double offset(int channel) const override {
  272. return node->analog->offset(node, channel);
  273. }
  274. Plane plane(int i) const override {
  275. ThalamusByteSpan temp;
  276. node->image->plane(&temp, node, i);
  277. return std::span<const uint8_t>(temp.data, temp.data+temp.size);
  278. }
  279. size_t num_planes() const override {
  280. return node->image->num_planes(node);
  281. }
  282. Format format() const override {
  283. auto format = node->image->format(node);
  284. switch(format) {
  285. case ThalamusImageFormat_Gray:
  286. return ImageNode::Format::Gray;
  287. case ThalamusImageFormat_RGB:
  288. return ImageNode::Format::RGB;
  289. case ThalamusImageFormat_YUV420P:
  290. return ImageNode::Format::YUV420P;
  291. case ThalamusImageFormat_YUYV422:
  292. return ImageNode::Format::YUYV422;
  293. case ThalamusImageFormat_YUVJ420P:
  294. return ImageNode::Format::YUVJ420P;
  295. case ThalamusImageFormat_NV12:
  296. return ImageNode::Format::NV12;
  297. case ThalamusImageFormat_BGR:
  298. return ImageNode::Format::BGR;
  299. case ThalamusImageFormat_MJPEG:
  300. return ImageNode::Format::MJPEG;
  301. case ThalamusImageFormat_Gray16:
  302. return ImageNode::Format::Gray16;
  303. case ThalamusImageFormat_RGB16:
  304. return ImageNode::Format::RGB16;
  305. case ThalamusImageFormat_MPEG4:
  306. return ImageNode::Format::MPEG4;
  307. case ThalamusImageFormat_MPEG1:
  308. THALAMUS_ABORT("MPEG1 is no longer supported");
  309. }
  310. }
  311. size_t width() const override {
  312. return node->image->width(node);
  313. }
  314. size_t height() const override {
  315. return node->image->height(node);
  316. }
  317. std::chrono::nanoseconds frame_interval() const override {
  318. return std::chrono::nanoseconds(node->image->frame_interval_ns(node));
  319. }
  320. void inject(const thalamus_grpc::Image &) override {
  321. THALAMUS_ABORT("Unimplemented");
  322. }
  323. bool has_image_data() const override {
  324. return node->image->has_image_data(node);
  325. }
  326. std::span<MotionCaptureNode::Segment const> segments() const override {
  327. ThalamusMocapSegmentSpan temp;
  328. node->mocap->segments(&temp, node);
  329. return std::span<MotionCaptureNode::Segment const>(temp.data, temp.data+temp.size);
  330. }
  331. const std::string_view pose_name() const override {
  332. ThalamusCharSpan temp;
  333. node->mocap->pose_name(&temp, node);
  334. return std::string_view(temp.data, temp.data+temp.size);
  335. }
  336. void inject(const std::span<MotionCaptureNode::Segment const> &) override {
  337. THALAMUS_ABORT("Unimplemented");
  338. }
  339. bool has_motion_data() const override {
  340. return node->mocap->has_motion_data(node);
  341. }
  342. std::string_view text() const override {
  343. ThalamusCharSpan span;
  344. node->text->text(&span, node);
  345. return to_string_view(span);
  346. }
  347. bool has_text_data() const override {
  348. return node->text->has_text_data(node);
  349. }
  350. void process(const boost::json::value & request, std::function<void(const boost::json::value &)> callback) override {
  351. if(node->process == nullptr) {
  352. callback(boost::json::value());
  353. return;
  354. }
  355. auto json = new ThalamusJson{1, request};
  356. auto handle = new ThalamusRequestHandle {callback};
  357. node->process(node, handle, json);
  358. api->json_dec_ref(json);
  359. }
  360. };
  361. ExtNode::~ExtNode() {
  362. factory->destroy(factory, node);
  363. }
  364. struct Interfaces {
  365. Node* node = nullptr;
  366. AnalogNode* analog = nullptr;
  367. ImageNode* image = nullptr;
  368. MotionCaptureNode* mocap = nullptr;
  369. std::atomic_int safe = 0;
  370. std::atomic_int count = 0;
  371. };
  372. #define ASSERT_SAFE() do { if(!interfaces->safe) [[unlikely]] { THALAMUS_ABORT("Node should only be accessed in get_node or ready callback"); } } while(0)
  373. static uint64_t plugin_analog_time_ns(struct ThalamusNode* node) {
  374. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  375. ASSERT_SAFE();
  376. std::chrono::nanoseconds t = interfaces->analog->time();
  377. return uint64_t(t.count());
  378. }
  379. static uint64_t plugin_image_time_ns(struct ThalamusNode* node) {
  380. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  381. ASSERT_SAFE();
  382. std::chrono::nanoseconds t = interfaces->image->time();
  383. return uint64_t(t.count());
  384. }
  385. static uint64_t plugin_mocap_time_ns(struct ThalamusNode* node) {
  386. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  387. ASSERT_SAFE();
  388. std::chrono::nanoseconds t = interfaces->mocap->time();
  389. return uint64_t(t.count());
  390. }
  391. static void plugin_node_process(struct ThalamusNode* node, struct ThalamusRequestHandle* handle, struct ThalamusJson* request) {
  392. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  393. ASSERT_SAFE();
  394. interfaces->node->process(request->value, [handle](const boost::json::value & response) {
  395. handle->callback(response);
  396. delete handle;
  397. });
  398. }
  399. static void plugin_analog_data(struct ThalamusDoubleSpan* output, struct ThalamusNode* node, int channel) {
  400. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  401. ASSERT_SAFE();
  402. auto span = interfaces->analog->data(channel);
  403. output->data = span.data();
  404. output->size = span.size();
  405. }
  406. static void plugin_analog_short_data(struct ThalamusShortSpan* output, struct ThalamusNode* node, int channel) {
  407. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  408. ASSERT_SAFE();
  409. auto span = interfaces->analog->short_data(channel);
  410. output->data = span.data();
  411. output->size = span.size();
  412. }
  413. static void plugin_analog_int_data(struct ThalamusIntSpan* output, struct ThalamusNode* node, int channel) {
  414. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  415. ASSERT_SAFE();
  416. auto span = interfaces->analog->int_data(channel);
  417. output->data = span.data();
  418. output->size = span.size();
  419. }
  420. static void plugin_analog_ulong_data(struct ThalamusULongSpan* output, struct ThalamusNode* node, int channel) {
  421. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  422. ASSERT_SAFE();
  423. auto span = interfaces->analog->ulong_data(channel);
  424. output->data = span.data();
  425. output->size = span.size();
  426. }
  427. static int plugin_analog_num_channels(struct ThalamusNode* node) {
  428. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  429. ASSERT_SAFE();
  430. return interfaces->analog->num_channels();
  431. }
  432. static uint64_t plugin_analog_sample_interval_ns(struct ThalamusNode* node, int channel) {
  433. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  434. ASSERT_SAFE();
  435. std::chrono::nanoseconds ns = interfaces->analog->sample_interval(channel);
  436. return uint64_t(ns.count());
  437. }
  438. static void plugin_analog_name(ThalamusCharSpan* output, struct ThalamusNode* node, int channel) {
  439. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  440. ASSERT_SAFE();
  441. auto name = interfaces->analog->name(channel);
  442. output->data = name.data();
  443. output->size = name.size();
  444. }
  445. static char plugin_analog_has_analog_data(struct ThalamusNode* node) {
  446. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  447. ASSERT_SAFE();
  448. return interfaces->analog->has_analog_data() ? 1 : 0;
  449. }
  450. static char plugin_analog_is_short_data(struct ThalamusNode* node) {
  451. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  452. ASSERT_SAFE();
  453. return interfaces->analog->is_short_data() ? 1 : 0;
  454. }
  455. static char plugin_analog_is_int_data(struct ThalamusNode* node) {
  456. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  457. ASSERT_SAFE();
  458. return interfaces->analog->is_int_data() ? 1 : 0;
  459. }
  460. static char plugin_analog_is_ulong_data(struct ThalamusNode* node) {
  461. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  462. ASSERT_SAFE();
  463. return interfaces->analog->is_ulong_data() ? 1 : 0;
  464. }
  465. static char plugin_analog_is_transformed(struct ThalamusNode* node) {
  466. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  467. ASSERT_SAFE();
  468. return interfaces->analog->is_transformed() ? 1 : 0;
  469. }
  470. static double plugin_analog_scale(struct ThalamusNode* node, int channel) {
  471. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  472. ASSERT_SAFE();
  473. return interfaces->analog->scale(channel);
  474. }
  475. static double plugin_analog_offset(struct ThalamusNode* node, int channel) {
  476. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  477. ASSERT_SAFE();
  478. return interfaces->analog->offset(channel);
  479. }
  480. static void plugin_image_plane(struct ThalamusByteSpan* output, struct ThalamusNode* node, int channel) {
  481. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  482. ASSERT_SAFE();
  483. auto span = interfaces->image->plane(channel);
  484. output->data = span.data();
  485. output->size = span.size();
  486. }
  487. static uint64_t plugin_image_num_planes(struct ThalamusNode* node) {
  488. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  489. ASSERT_SAFE();
  490. return interfaces->image->num_planes();
  491. }
  492. static ThalamusImageFormat plugin_image_format(struct ThalamusNode* node) {
  493. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  494. ASSERT_SAFE();
  495. switch(interfaces->image->format()) {
  496. case ImageNode::Format::Gray:
  497. return ThalamusImageFormat_Gray;
  498. case ImageNode::Format::RGB:
  499. return ThalamusImageFormat_RGB;
  500. case ImageNode::Format::YUYV422:
  501. return ThalamusImageFormat_YUYV422;
  502. case ImageNode::Format::YUV420P:
  503. return ThalamusImageFormat_YUV420P;
  504. case ImageNode::Format::YUVJ420P:
  505. return ThalamusImageFormat_YUVJ420P;
  506. case ImageNode::Format::NV12:
  507. return ThalamusImageFormat_NV12;
  508. case ImageNode::Format::BGR:
  509. return ThalamusImageFormat_BGR;
  510. case ImageNode::Format::MJPEG:
  511. return ThalamusImageFormat_MJPEG;
  512. case ImageNode::Format::Gray16:
  513. return ThalamusImageFormat_Gray16;
  514. case ImageNode::Format::RGB16:
  515. return ThalamusImageFormat_RGB16;
  516. case ImageNode::Format::MPEG4:
  517. return ThalamusImageFormat_MPEG4;
  518. }
  519. }
  520. static uint64_t plugin_image_width(struct ThalamusNode* node) {
  521. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  522. ASSERT_SAFE();
  523. return interfaces->image->width();
  524. }
  525. static uint64_t plugin_image_height(struct ThalamusNode* node) {
  526. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  527. ASSERT_SAFE();
  528. return interfaces->image->height();
  529. }
  530. static uint64_t plugin_image_frame_interval_ns(struct ThalamusNode* node) {
  531. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  532. ASSERT_SAFE();
  533. std::chrono::nanoseconds ns = interfaces->image->frame_interval();
  534. return uint64_t(ns.count());
  535. }
  536. static char plugin_image_has_image_data(struct ThalamusNode* node) {
  537. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  538. ASSERT_SAFE();
  539. return interfaces->image->has_image_data() ? 1 : 0;
  540. }
  541. static void plugin_mocap_segments(ThalamusMocapSegmentSpan* output, struct ThalamusNode* node) {
  542. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  543. ASSERT_SAFE();
  544. auto segments = interfaces->mocap->segments();
  545. output->data = segments.data();
  546. output->size = segments.size();
  547. }
  548. static void plugin_mocap_pose_name(ThalamusCharSpan* output, struct ThalamusNode* node) {
  549. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  550. ASSERT_SAFE();
  551. auto name = interfaces->mocap->pose_name();
  552. output->data = name.data();
  553. output->size = name.size();
  554. }
  555. static char plugin_mocap_has_motion_data(struct ThalamusNode* node) {
  556. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  557. ASSERT_SAFE();
  558. return interfaces->mocap->has_motion_data() ? 1 : 0;
  559. }
  560. struct NodeGuard {
  561. Interfaces* interfaces;
  562. NodeGuard(ThalamusNode* node) : interfaces(reinterpret_cast<Interfaces*>(node->impl)) {
  563. ++interfaces->safe;
  564. }
  565. ~NodeGuard() {
  566. --interfaces->safe;
  567. }
  568. };
  569. static std::function<void()> to_closure(ThalamusPostCallback callback, void* data) {
  570. if(callback) {
  571. return [callback, data] { callback(data); };
  572. } else {
  573. return []{};
  574. }
  575. }
  576. struct ThalamusAPIImpl {
  577. static std::map<ObservableCollection::Value, ThalamusState*>* cpp_to_c;
  578. static std::map<ThalamusState*, ObservableCollection::Value>* c_to_cpp;
  579. static std::mutex* mutex;
  580. static std::map<Node*, ThalamusNode*>* node_cpp_to_c;
  581. static std::map<ThalamusNode*, Node*>* node_c_to_cpp;
  582. static boost::asio::io_context* io_context;
  583. static NodeGraphImpl* node_graph;
  584. static ThalamusNode* wrap_node(Node* node) {
  585. auto result = new ThalamusNode();
  586. memset(result, 0, sizeof(ThalamusNode));
  587. auto interfaces = new Interfaces();
  588. interfaces->node = node;
  589. interfaces->count = 1;
  590. result->impl = interfaces;
  591. result->process = plugin_node_process;
  592. auto analog = node_cast<AnalogNode *>(node);
  593. auto image = node_cast<ImageNode *>(node);
  594. auto mocap = node_cast<MotionCaptureNode *>(node);
  595. if(analog) {
  596. interfaces->analog = analog;
  597. result->time_ns = result->time_ns ? result->time_ns : plugin_analog_time_ns;
  598. result->analog = new ThalamusAnalogNode();
  599. result->analog->data = plugin_analog_data;
  600. result->analog->short_data = plugin_analog_short_data;
  601. result->analog->int_data = plugin_analog_int_data;
  602. result->analog->ulong_data = plugin_analog_ulong_data;
  603. result->analog->num_channels = plugin_analog_num_channels;
  604. result->analog->sample_interval_ns = plugin_analog_sample_interval_ns;
  605. result->analog->name = plugin_analog_name;
  606. result->analog->has_analog_data = plugin_analog_has_analog_data;
  607. result->analog->is_short_data = plugin_analog_is_short_data;
  608. result->analog->is_int_data = plugin_analog_is_int_data;
  609. result->analog->is_ulong_data = plugin_analog_is_ulong_data;
  610. result->analog->is_transformed = plugin_analog_is_transformed;
  611. result->analog->scale = plugin_analog_scale;
  612. result->analog->offset = plugin_analog_offset;
  613. }
  614. if (image) {
  615. interfaces->image = image;
  616. result->time_ns = result->time_ns ? result->time_ns : plugin_image_time_ns;
  617. result->image = new ThalamusImageNode();
  618. result->image->plane = plugin_image_plane;
  619. result->image->num_planes = plugin_image_num_planes;
  620. result->image->format = plugin_image_format;
  621. result->image->width = plugin_image_width;
  622. result->image->height = plugin_image_height;
  623. result->image->frame_interval_ns = plugin_image_frame_interval_ns;
  624. result->image->has_image_data = plugin_image_has_image_data;
  625. }
  626. if (mocap) {
  627. interfaces->mocap = mocap;
  628. result->time_ns = result->time_ns ? result->time_ns : plugin_mocap_time_ns;
  629. result->mocap = new ThalamusMocapNode();
  630. result->mocap->segments = plugin_mocap_segments;
  631. result->mocap->pose_name = plugin_mocap_pose_name;
  632. result->mocap->has_motion_data = plugin_mocap_has_motion_data;
  633. }
  634. return result;
  635. }
  636. static ThalamusNode* get_node_ref(Node* val) {
  637. std::lock_guard<std::mutex> lock(*mutex);
  638. auto i = node_cpp_to_c->find(val);
  639. if(i == node_cpp_to_c->end()) {
  640. auto new_ptr = wrap_node(val);
  641. //THALAMUS_LOG(info) << "new_state " << get_path(new_ptr->value) << " " << new_ptr->count;
  642. (*node_cpp_to_c)[val] = new_ptr;
  643. (*node_c_to_cpp)[new_ptr] = val;
  644. return new_ptr;
  645. } else {
  646. node_inc_ref(i->second);
  647. return i->second;
  648. }
  649. }
  650. static ObservableCollection::Value key_to_value(ObservableCollection::Key key) {
  651. if(std::holds_alternative<std::monostate>(key)) {
  652. return ObservableCollection::Value(std::get<std::monostate>(key));
  653. } else if (std::holds_alternative<int64_t>(key)) {
  654. return ObservableCollection::Value(std::get<int64_t>(key));
  655. } else if (std::holds_alternative<bool>(key)) {
  656. return ObservableCollection::Value(std::get<bool>(key));
  657. } else if (std::holds_alternative<std::string>(key)) {
  658. return ObservableCollection::Value(std::get<std::string>(key));
  659. }
  660. THALAMUS_ABORT("Unexpected type");
  661. }
  662. static ThalamusState* get_state_ref(ObservableCollection::Key key) {
  663. if(std::holds_alternative<std::monostate>(key)) {
  664. return get_state_ref(ObservableCollection::Value(std::get<std::monostate>(key)));
  665. } else if (std::holds_alternative<int64_t>(key)) {
  666. return get_state_ref(ObservableCollection::Value(std::get<int64_t>(key)));
  667. } else if (std::holds_alternative<bool>(key)) {
  668. return get_state_ref(ObservableCollection::Value(std::get<bool>(key)));
  669. } else if (std::holds_alternative<std::string>(key)) {
  670. return get_state_ref(ObservableCollection::Value(std::get<std::string>(key)));
  671. }
  672. THALAMUS_ABORT("Unexpected type");
  673. }
  674. static std::string get_path(ObservableCollection::Value state) {
  675. if(std::holds_alternative<ObservableDictPtr>(state)) {
  676. return std::get<ObservableDictPtr>(state)->address();
  677. } else if(std::holds_alternative<ObservableListPtr>(state)) {
  678. return std::get<ObservableListPtr>(state)->address();
  679. } else {
  680. return "";
  681. }
  682. }
  683. static ThalamusState* get_state_ref(ObservableCollection::Value val) {
  684. auto i = cpp_to_c->find(val);
  685. if(i == cpp_to_c->end()) {
  686. auto new_ptr = new ThalamusState{1, val};
  687. THALAMUS_LOG(trace) << "new_state " << get_path(new_ptr->value) << " " << new_ptr->count;
  688. (*cpp_to_c)[val] = new_ptr;
  689. (*c_to_cpp)[new_ptr] = val;
  690. return new_ptr;
  691. } else {
  692. state_inc_ref(i->second);
  693. return i->second;
  694. }
  695. }
  696. static void state_inc_ref(ThalamusState* state) {
  697. ++state->count;
  698. THALAMUS_LOG(trace) << "state_inc_ref " << get_path(state->value) << " " << state->count;
  699. }
  700. static void state_dec_ref(ThalamusState* state) {
  701. --state->count;
  702. THALAMUS_LOG(trace) << "state_dec_ref " << get_path(state->value) << " " << state->count;
  703. if(state->count == 0) {
  704. c_to_cpp->erase(state);
  705. cpp_to_c->erase(state->value);
  706. delete state;
  707. }
  708. }
  709. static char state_is_dict(ThalamusState* value) {
  710. return std::holds_alternative<ObservableDictPtr>(value->value) ? 1 : 0;
  711. }
  712. static char state_is_list(ThalamusState* value) {
  713. return std::holds_alternative<ObservableListPtr>(value->value) ? 1 : 0;
  714. }
  715. static char state_is_string(ThalamusState* value) {
  716. return std::holds_alternative<std::string>(value->value) ? 1 : 0;
  717. }
  718. static char state_is_int(ThalamusState* value) {
  719. return std::holds_alternative<int64_t>(value->value) ? 1 : 0;
  720. }
  721. static char state_is_float(ThalamusState* value) {
  722. return std::holds_alternative<double>(value->value) ? 1 : 0;
  723. }
  724. static char state_is_null(ThalamusState* value) {
  725. return std::holds_alternative<std::monostate>(value->value) ? 1 : 0;
  726. }
  727. static char state_is_bool(ThalamusState* value) {
  728. return std::holds_alternative<bool>(value->value) ? 1 : 0;
  729. }
  730. static void state_get_string(ThalamusCharSpan* span, ThalamusState* state) {
  731. const auto& s = std::get<std::string>(state->value);
  732. span->data = s.data();
  733. span->size = s.size();
  734. span->owns_data = 0;
  735. }
  736. static int64_t state_get_int(ThalamusState* state) {
  737. return std::get<int64_t>(state->value);
  738. }
  739. static double state_get_float(ThalamusState* state) {
  740. return std::get<double>(state->value);
  741. }
  742. static char state_get_bool(ThalamusState* state) {
  743. return std::get<bool>(state->value);
  744. }
  745. static ThalamusState* state_get_at_name(ThalamusState* state, const ThalamusCharSpan* key) {
  746. std::string key_str(key->data, key->size);
  747. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  748. auto dict = std::get<ObservableDictPtr>(state->value);
  749. if(dict->contains(key_str)) {
  750. return get_state_ref((*dict)[key_str]);
  751. } else {
  752. return nullptr;
  753. }
  754. }
  755. THALAMUS_ABORT("State does not contain dict");
  756. }
  757. static ThalamusState* state_get_at_index(ThalamusState* state, uint64_t key) {
  758. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  759. auto dict = std::get<ObservableDictPtr>(state->value);
  760. if(dict->contains(int64_t(key))) {
  761. return get_state_ref((*dict)[int64_t(key)]);
  762. } else {
  763. return nullptr;
  764. }
  765. } else if (std::holds_alternative<ObservableListPtr>(state->value)) {
  766. auto list = std::get<ObservableListPtr>(state->value);
  767. if(key < list->size()) {
  768. return get_state_ref((*list)[key]);
  769. } else {
  770. return nullptr;
  771. }
  772. }
  773. THALAMUS_ABORT("State does not contain dict or list");
  774. }
  775. static ThalamusStateConnection* state_recursive_change_connect(ThalamusState* state, ThalamusStateRecursiveCallback callback, void* data) {
  776. auto handler = [callback,data](ObservableCollection* source,
  777. ObservableCollection::Action action,
  778. ObservableCollection::Key key,
  779. ObservableCollection::Value value) {
  780. ObservableDict* as_dict;
  781. ObservableList* as_list;
  782. ThalamusState* source_wrapper;
  783. if((as_dict = dynamic_cast<ObservableDict*>(source)) != nullptr) {
  784. source_wrapper = get_state_ref(as_dict->shared_from_this());
  785. } else if ((as_list = dynamic_cast<ObservableList*>(source)) != nullptr) {
  786. source_wrapper = get_state_ref(as_list->shared_from_this());
  787. } else {
  788. THALAMUS_ABORT("state is not a collection");
  789. }
  790. auto action_wrapper = action == ObservableCollection::Action::Set ? ThalamusStateAction_Set : ThalamusStateAction_Delete;
  791. auto wrapped_key = get_state_ref(key);
  792. auto wrapped_value = get_state_ref(value);
  793. callback(source_wrapper, action_wrapper, wrapped_key, wrapped_value, data);
  794. state_dec_ref(source_wrapper);
  795. state_dec_ref(wrapped_key);
  796. state_dec_ref(wrapped_value);
  797. };
  798. boost::signals2::connection connection;
  799. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  800. connection = std::get<ObservableDictPtr>(state->value)->recursive_changed.connect(handler);
  801. } else {
  802. connection = std::get<ObservableListPtr>(state->value)->recursive_changed.connect(handler);
  803. }
  804. return new ThalamusStateConnection(connection);
  805. }
  806. static void state_recursive_change_disconnect(ThalamusStateConnection *connection) {
  807. delete connection;
  808. }
  809. static ThalamusTimer *timer_create() {
  810. return new ThalamusTimer(*io_context);
  811. }
  812. static void timer_destroy(ThalamusTimer *timer) {
  813. delete timer;
  814. }
  815. static void timer_expire_after_ns(ThalamusTimer *timer, uint64_t ns) {
  816. timer->timer.expires_after(std::chrono::nanoseconds(ns));
  817. }
  818. static void timer_async_wait(ThalamusTimer *timer,
  819. ThalamusTimerCallback callback, void *data) {
  820. timer->timer.async_wait([callback, data](const boost::system::error_code& error) {
  821. ThalamusErrorCode error_wrapper(error);
  822. callback(&error_wrapper, data);
  823. });
  824. }
  825. static int error_code_value(ThalamusErrorCode *error) {
  826. return error->error->value();
  827. }
  828. static void node_ready(ThalamusNode* node) {
  829. auto ext_node = reinterpret_cast<ExtNode*>(node->impl);
  830. ext_node->ready(ext_node);
  831. }
  832. static void node_ready_offmain(ThalamusNode* node) {
  833. auto ext_node = reinterpret_cast<ExtNode*>(node->impl);
  834. node::signal_ready_offmain(ext_node, *io_context);
  835. }
  836. static void node_predrop_ready(ThalamusNode* node) {
  837. THALAMUS_LOG(info) << "*ext* node_predrop_ready";
  838. auto ext_node = reinterpret_cast<ExtNode*>(node->impl);
  839. THALAMUS_ASSERT(ext_node->drop_ready, "No predrop callback available");
  840. ext_node->drop_ready();
  841. }
  842. static uint64_t time_ns() {
  843. std::chrono::nanoseconds ns = std::chrono::steady_clock::now().time_since_epoch();
  844. return uint64_t(ns.count());
  845. }
  846. static int error_code_operation_aborted() {
  847. return boost::asio::error::operation_aborted;
  848. }
  849. static void state_recap(ThalamusState* state) {
  850. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  851. std::get<ObservableDictPtr>(state->value)->recap();
  852. } else if(std::holds_alternative<ObservableListPtr>(state->value)) {
  853. std::get<ObservableListPtr>(state->value)->recap();
  854. } else {
  855. THALAMUS_ABORT("Attempt to recap a value that is neither a dict or list");
  856. }
  857. }
  858. static void state_recap_with(ThalamusState* state, ThalamusStateRecursiveCallback callback, void* data) {
  859. auto handler = [state,callback,data](ObservableCollection::Action action,
  860. ObservableCollection::Key key,
  861. ObservableCollection::Value value) {
  862. auto action_wrapper = action == ObservableCollection::Action::Set ? ThalamusStateAction_Set : ThalamusStateAction_Delete;
  863. auto wrapped_key = get_state_ref(key);
  864. auto wrapped_value = get_state_ref(value);
  865. callback(state, action_wrapper, wrapped_key, wrapped_value, data);
  866. state_dec_ref(wrapped_key);
  867. state_dec_ref(wrapped_value);
  868. };
  869. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  870. std::get<ObservableDictPtr>(state->value)->recap(handler);
  871. } else if(std::holds_alternative<ObservableListPtr>(state->value)) {
  872. std::get<ObservableListPtr>(state->value)->recap(handler);
  873. } else {
  874. THALAMUS_ABORT("Attempt to recap a value that is neither a dict or list");
  875. }
  876. }
  877. template <typename T1, typename T2> static void assign_state(struct ThalamusState* state, T1 key, T2 value, std::function<void()> callback = nullptr) {
  878. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  879. auto coll = std::get<ObservableDictPtr>(state->value);
  880. (*coll)[key].assign(value, callback);
  881. } else if(std::holds_alternative<ObservableListPtr>(state->value)) {
  882. if constexpr (std::is_integral<T1>::value) {
  883. auto coll = std::get<ObservableListPtr>(state->value);
  884. if(size_t(key) < coll->size()) {
  885. (*coll)[size_t(key)].assign(value, callback);
  886. } else {
  887. THALAMUS_ABORT("Index out of bounds");
  888. }
  889. } else {
  890. THALAMUS_ABORT("Can only index list with integer");
  891. }
  892. } else {
  893. THALAMUS_ABORT("Attempt to recap a value that is neither a dict or list");
  894. }
  895. }
  896. template <typename T1> static void erase_state(struct ThalamusState* state, T1 key, std::function<void()> callback = nullptr) {
  897. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  898. auto coll = std::get<ObservableDictPtr>(state->value);
  899. coll->erase(key, [callback] (auto) {
  900. callback();
  901. });
  902. } else if(std::holds_alternative<ObservableListPtr>(state->value)) {
  903. if constexpr (std::is_integral<T1>::value) {
  904. auto coll = std::get<ObservableListPtr>(state->value);
  905. if(size_t(key) < coll->size()) {
  906. coll->erase(size_t(key), [callback] (auto) {
  907. callback();
  908. });
  909. } else {
  910. THALAMUS_ABORT("Index out of bounds");
  911. }
  912. } else {
  913. THALAMUS_ABORT("Can only index list with integer");
  914. }
  915. } else {
  916. THALAMUS_ABORT("Attempt to recap a value that is neither a dict or list");
  917. }
  918. }
  919. template <typename T1> static void push_state(struct ThalamusState* state, T1 value, std::function<void()> callback) {
  920. if(std::holds_alternative<ObservableListPtr>(state->value)) {
  921. auto coll = std::get<ObservableListPtr>(state->value);
  922. coll->push_back(value, callback);
  923. } else {
  924. THALAMUS_ABORT("Attempt to recap a value that is neither a dict or list");
  925. }
  926. }
  927. static void state_set_at_name_state(struct ThalamusState* state, const ThalamusCharSpan* key, struct ThalamusState* value) {
  928. assign_state(state, std::string(key->data, key->size), value->value);
  929. }
  930. static void state_set_at_name_string(struct ThalamusState* state, const ThalamusCharSpan* key, const ThalamusCharSpan* value) {
  931. assign_state(state, std::string(key->data, key->size), std::string(value->data, value->size));
  932. }
  933. static void state_set_at_name_int(struct ThalamusState* state, const ThalamusCharSpan* key, int64_t value) {
  934. assign_state(state, std::string(key->data, key->size), value);
  935. }
  936. static void state_set_at_name_float(struct ThalamusState* state, const ThalamusCharSpan* key, double value) {
  937. assign_state(state, std::string(key->data, key->size), value);
  938. }
  939. static void state_set_at_name_null(struct ThalamusState* state, const ThalamusCharSpan* key) {
  940. assign_state(state, std::string(key->data, key->size), std::monostate());
  941. }
  942. static void state_set_at_name_bool(struct ThalamusState* state, const ThalamusCharSpan* key, char value) {
  943. assign_state(state, std::string(key->data, key->size), value != 0);
  944. }
  945. static void state_set_at_index_state(struct ThalamusState* state, int64_t key, struct ThalamusState* value) {
  946. assign_state(state, key, value->value);
  947. }
  948. static void state_set_at_index_string(struct ThalamusState* state, int64_t key, const ThalamusCharSpan* value) {
  949. assign_state(state, key, std::string(value->data, value->size));
  950. }
  951. static void state_set_at_index_int(struct ThalamusState* state, int64_t key, int64_t value) {
  952. assign_state(state, key, value);
  953. }
  954. static void state_set_at_index_float(struct ThalamusState* state, int64_t key, double value) {
  955. assign_state(state, key, value);
  956. }
  957. static void state_set_at_index_null(struct ThalamusState* state, int64_t key) {
  958. assign_state(state, key, std::monostate());
  959. }
  960. static void state_set_at_index_bool(struct ThalamusState* state, int64_t key, char value) {
  961. assign_state(state, key, value != 0);
  962. }
  963. static void state_set_at_name_state_with_callback(struct ThalamusState* state, const ThalamusCharSpan* key, struct ThalamusState* value, ThalamusPostCallback callback, void* data) {
  964. auto closure = to_closure(callback, data);
  965. assign_state(state, std::string(key->data, key->size), value->value, closure);
  966. }
  967. static void state_set_at_name_string_with_callback(struct ThalamusState* state, const ThalamusCharSpan* key, const ThalamusCharSpan* value, ThalamusPostCallback callback, void* data) {
  968. auto closure = to_closure(callback, data);
  969. assign_state(state, std::string(key->data, key->size), std::string(value->data, value->size), closure);
  970. }
  971. static void state_set_at_name_int_with_callback(struct ThalamusState* state, const ThalamusCharSpan* key, int64_t value, ThalamusPostCallback callback, void* data) {
  972. auto closure = to_closure(callback, data);
  973. assign_state(state, std::string(key->data, key->size), value, closure);
  974. }
  975. static void state_set_at_name_float_with_callback(struct ThalamusState* state, const ThalamusCharSpan* key, double value, ThalamusPostCallback callback, void* data) {
  976. auto closure = to_closure(callback, data);
  977. assign_state(state, std::string(key->data, key->size), value, closure);
  978. }
  979. static void state_set_at_name_null_with_callback(struct ThalamusState* state, const ThalamusCharSpan* key, ThalamusPostCallback callback, void* data) {
  980. auto closure = to_closure(callback, data);
  981. assign_state(state, std::string(key->data, key->size), std::monostate(), closure);
  982. }
  983. static void state_set_at_name_bool_with_callback(struct ThalamusState* state, const ThalamusCharSpan* key, char value, ThalamusPostCallback callback, void* data) {
  984. auto closure = to_closure(callback, data);
  985. assign_state(state, std::string(key->data, key->size), value != 0, closure);
  986. }
  987. static void state_set_at_index_state_with_callback(struct ThalamusState* state, int64_t key, struct ThalamusState* value, ThalamusPostCallback callback, void* data) {
  988. auto closure = to_closure(callback, data);
  989. assign_state(state, key, value->value, closure);
  990. }
  991. static void state_set_at_index_string_with_callback(struct ThalamusState* state, int64_t key, const ThalamusCharSpan* value, ThalamusPostCallback callback, void* data) {
  992. auto closure = to_closure(callback, data);
  993. assign_state(state, key, std::string(value->data, value->size), closure);
  994. }
  995. static void state_set_at_index_int_with_callback(struct ThalamusState* state, int64_t key, int64_t value, ThalamusPostCallback callback, void* data) {
  996. auto closure = to_closure(callback, data);
  997. assign_state(state, key, value, closure);
  998. }
  999. static void state_set_at_index_float_with_callback(struct ThalamusState* state, int64_t key, double value, ThalamusPostCallback callback, void* data) {
  1000. auto closure = to_closure(callback, data);
  1001. assign_state(state, key, value, closure);
  1002. }
  1003. static void state_set_at_index_null_with_callback(struct ThalamusState* state, int64_t key, ThalamusPostCallback callback, void* data) {
  1004. auto closure = to_closure(callback, data);
  1005. assign_state(state, key, std::monostate(), closure);
  1006. }
  1007. static void state_set_at_index_bool_with_callback(struct ThalamusState* state, int64_t key, char value, ThalamusPostCallback callback, void* data) {
  1008. auto closure = to_closure(callback, data);
  1009. assign_state(state, key, value != 0, closure);
  1010. }
  1011. static void state_push_state_with_callback(struct ThalamusState* state, struct ThalamusState* value, ThalamusPostCallback callback, void* data) {
  1012. auto closure = to_closure(callback, data);
  1013. push_state(state, value->value, closure);
  1014. }
  1015. static void state_push_string_with_callback(struct ThalamusState* state, const ThalamusCharSpan* value, ThalamusPostCallback callback, void* data) {
  1016. auto closure = to_closure(callback, data);
  1017. push_state(state, std::string(value->data, value->size), closure);
  1018. }
  1019. static void state_push_int_with_callback(struct ThalamusState* state, int64_t value, ThalamusPostCallback callback, void* data) {
  1020. auto closure = to_closure(callback, data);
  1021. push_state(state, value, closure);
  1022. }
  1023. static void state_push_float_with_callback(struct ThalamusState* state, double value, ThalamusPostCallback callback, void* data) {
  1024. auto closure = to_closure(callback, data);
  1025. push_state(state, value, closure);
  1026. }
  1027. static void state_push_null_with_callback(struct ThalamusState* state, ThalamusPostCallback callback, void* data) {
  1028. auto closure = to_closure(callback, data);
  1029. push_state(state, std::monostate(), closure);
  1030. }
  1031. static void state_push_bool_with_callback(struct ThalamusState* state, char value, ThalamusPostCallback callback, void* data) {
  1032. auto closure = to_closure(callback, data);
  1033. push_state(state, value != 0, closure);
  1034. }
  1035. static struct ThalamusState* state_parent(struct ThalamusState* state) {
  1036. ObservableCollection* parent = nullptr;
  1037. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  1038. parent = std::get<ObservableDictPtr>(state->value)->parent;
  1039. } else if (std::holds_alternative<ObservableListPtr>(state->value)) {
  1040. parent = std::get<ObservableListPtr>(state->value)->parent;
  1041. } else {
  1042. THALAMUS_ABORT("Attempt to recap a value that is neither a dict or list");
  1043. }
  1044. if (parent == nullptr) {
  1045. return nullptr;
  1046. }
  1047. ObservableList* list;
  1048. ObservableDict* dict;
  1049. ObservableCollection::Value parent_value;
  1050. if((list = parent->as_list())) {
  1051. parent_value = ObservableCollection::Value(list->shared_from_this());
  1052. } else if ((dict = parent->as_dict())) {
  1053. parent_value = ObservableCollection::Value(dict->shared_from_this());
  1054. } else {
  1055. return nullptr;
  1056. }
  1057. return get_state_ref(parent_value);
  1058. }
  1059. static struct ThalamusStateIter* state_iter_create(struct ThalamusState* state) {
  1060. if(std::holds_alternative<ObservableDictPtr>(state->value)) {
  1061. auto value = std::get<ObservableDictPtr>(state->value);
  1062. return new ThalamusStateIter{state->value, value->begin(), value->begin(), value->end(), false};
  1063. } else if (std::holds_alternative<ObservableListPtr>(state->value)) {
  1064. auto value = std::get<ObservableListPtr>(state->value);
  1065. return new ThalamusStateIter{state->value, value->begin(), value->begin(), value->end(), false};
  1066. }
  1067. THALAMUS_ABORT("Attempt to iterate a value that is neither a dict or list");
  1068. }
  1069. static uint8_t state_iter_next(struct ThalamusStateIter* iter) {
  1070. if (iter->pos == iter->end) {
  1071. return 0;
  1072. }
  1073. if(std::holds_alternative<thalamus::ObservableCollection::VectorIteratorWrapper>(iter->begin)) {
  1074. auto& pos = std::get<thalamus::ObservableCollection::VectorIteratorWrapper>(iter->pos);
  1075. auto end = std::get<thalamus::ObservableCollection::VectorIteratorWrapper>(iter->pos);
  1076. if(iter->started) {
  1077. ++pos;
  1078. } else {
  1079. iter->started = true;
  1080. }
  1081. return iter->pos == iter->end ? 0 : 1;
  1082. } else if (std::holds_alternative<thalamus::ObservableCollection::MapIteratorWrapper>(iter->begin)) {
  1083. auto& pos = std::get<thalamus::ObservableCollection::MapIteratorWrapper>(iter->pos);
  1084. auto end = std::get<thalamus::ObservableCollection::MapIteratorWrapper>(iter->pos);
  1085. if(iter->started) {
  1086. ++pos;
  1087. } else {
  1088. iter->started = true;
  1089. }
  1090. return iter->pos == iter->end ? 0 : 1;
  1091. }
  1092. THALAMUS_ABORT("Bad Iterator");
  1093. }
  1094. static struct ThalamusState* state_iter_key(struct ThalamusStateIter* iter) {
  1095. THALAMUS_ASSERT(iter->pos != iter->end, "Attempted to read finished iterator");
  1096. thalamus::ObservableCollection::Value result;
  1097. if(std::holds_alternative<thalamus::ObservableCollection::VectorIteratorWrapper>(iter->begin)) {
  1098. auto begin = std::get<thalamus::ObservableCollection::VectorIteratorWrapper>(iter->begin);
  1099. auto pos = std::get<thalamus::ObservableCollection::VectorIteratorWrapper>(iter->pos);
  1100. result = int64_t(std::distance(begin, pos));
  1101. } else if (std::holds_alternative<thalamus::ObservableCollection::MapIteratorWrapper>(iter->begin)) {
  1102. auto pos = std::get<thalamus::ObservableCollection::MapIteratorWrapper>(iter->pos);
  1103. result = key_to_value(pos->first);
  1104. }
  1105. return get_state_ref(result);
  1106. }
  1107. static struct ThalamusState* state_iter_value(struct ThalamusStateIter* iter) {
  1108. THALAMUS_ASSERT(iter->pos != iter->end, "Attempted to read finished iterator");
  1109. thalamus::ObservableCollection::Value result;
  1110. if(std::holds_alternative<thalamus::ObservableCollection::VectorIteratorWrapper>(iter->begin)) {
  1111. auto pos = std::get<thalamus::ObservableCollection::VectorIteratorWrapper>(iter->pos);
  1112. result = *pos;
  1113. } else if (std::holds_alternative<thalamus::ObservableCollection::MapIteratorWrapper>(iter->begin)) {
  1114. auto pos = std::get<thalamus::ObservableCollection::MapIteratorWrapper>(iter->pos);
  1115. result = pos->second;
  1116. }
  1117. return get_state_ref(result);
  1118. }
  1119. static struct ThalamusState* state_key_of(struct ThalamusState* parent, struct ThalamusState* child) {
  1120. ObservableCollection* parent_col = nullptr;
  1121. ObservableCollection* child_col = nullptr;
  1122. if(std::holds_alternative<ObservableDictPtr>(parent->value)) {
  1123. parent_col = std::get<ObservableDictPtr>(parent->value).get();
  1124. } else if (std::holds_alternative<ObservableListPtr>(parent->value)) {
  1125. parent_col = std::get<ObservableListPtr>(parent->value).get();
  1126. } else {
  1127. THALAMUS_ABORT("Attempt to iterate a value that is neither a dict or list");
  1128. }
  1129. if(std::holds_alternative<ObservableDictPtr>(child->value)) {
  1130. child_col = std::get<ObservableDictPtr>(child->value).get();
  1131. } else if (std::holds_alternative<ObservableListPtr>(child->value)) {
  1132. child_col = std::get<ObservableListPtr>(child->value).get();
  1133. } else {
  1134. THALAMUS_ABORT("Attempt to iterate a value that is neither a dict or list");
  1135. }
  1136. auto key = parent_col->key_of(*child_col);
  1137. return key ? get_state_ref(*key) : nullptr;
  1138. }
  1139. static void state_iter_destroy(struct ThalamusStateIter* iter) {
  1140. delete iter;
  1141. }
  1142. static void io_context_post(ThalamusPostCallback callback, void* data) {
  1143. boost::asio::post(*io_context, [callback, data] {
  1144. callback(data);
  1145. });
  1146. }
  1147. static void threadpool_post(ThalamusPostCallback callback, void* data) {
  1148. node_graph->get_thread_pool().push([callback, data] {
  1149. callback(data);
  1150. });
  1151. }
  1152. static void trace_event_begin(const ThalamusCharSpan* name) {
  1153. TRACE_EVENT_BEGIN("plugin", perfetto::DynamicString(name->data, name->size));
  1154. }
  1155. static void trace_event_end() {
  1156. TRACE_EVENT_END("plugin");
  1157. }
  1158. static ThalamusSerialPort* serial_port_create() {
  1159. auto result = new ThalamusSerialPort{
  1160. boost::asio::serial_port(*io_context),
  1161. boost::system::error_code(),
  1162. ThalamusErrorCode()
  1163. };
  1164. result->error_wrapper.error = &result->error;
  1165. return result;
  1166. }
  1167. static void serial_port_destroy(ThalamusSerialPort* port) {
  1168. delete port;
  1169. }
  1170. static void serial_set_baud_rate(ThalamusSerialPort* port, uint32_t rate) {
  1171. port->port.set_option(boost::asio::serial_port_base::baud_rate(rate));
  1172. }
  1173. static void serial_port_open(ThalamusSerialPort* port, const ThalamusCharSpan* name) {
  1174. port->port.open(std::string(name->data, name->size), port->error);
  1175. }
  1176. static ThalamusErrorCode* serial_port_error(ThalamusSerialPort* port) {
  1177. return &port->error_wrapper;
  1178. }
  1179. static void serial_port_read_until(ThalamusSerialPort* port, ThalamusStreamBuf* buffer, const ThalamusCharSpan* delimiter, ThalamusIOCallback callback, void* data) {
  1180. auto view = boost::asio::string_view(delimiter->data, delimiter->size);
  1181. boost::asio::async_read_until(port->port, buffer->buffer, view, [callback, data] (auto ec, auto count) {
  1182. ThalamusErrorCode err{ec};
  1183. callback(&err, count, data);
  1184. });
  1185. }
  1186. static void serial_port_read_some(ThalamusSerialPort* port, ThalamusMutableByteSpan* span, ThalamusIOCallback callback, void* data) {
  1187. port->port.async_read_some(boost::asio::buffer(span->data, span->size), [callback, data] (auto ec, auto count) {
  1188. ThalamusErrorCode err{ec};
  1189. callback(&err, count, data);
  1190. });
  1191. }
  1192. static void serial_port_read(ThalamusSerialPort* port, ThalamusMutableByteSpan* span, ThalamusIOCallback callback, void* data) {
  1193. boost::asio::async_read(port->port, boost::asio::buffer(span->data, span->size), [callback, data] (auto ec, auto count) {
  1194. ThalamusErrorCode err{ec};
  1195. callback(&err, count, data);
  1196. });
  1197. }
  1198. static void serial_port_write(ThalamusSerialPort* port, ThalamusByteSpan* span, ThalamusIOCallback callback, void* data) {
  1199. boost::asio::async_write(port->port, boost::asio::buffer(span->data, span->size), [callback, data] (auto ec, auto count) {
  1200. ThalamusErrorCode err{ec};
  1201. callback(&err, count, data);
  1202. });
  1203. }
  1204. static ThalamusStreamBuf* streambuf_create() {
  1205. auto result = new ThalamusStreamBuf{
  1206. boost::asio::streambuf()
  1207. };
  1208. return result;
  1209. }
  1210. static void streambuf_destroy(ThalamusStreamBuf* port) {
  1211. delete port;
  1212. }
  1213. static void streambuf_to_span(struct ThalamusCharSpan* result, ThalamusStreamBuf* buffer) {
  1214. std::istream stream(&buffer->buffer);
  1215. auto size = buffer->buffer.size();
  1216. char* data = new char[size];
  1217. std::copy_n(std::istreambuf_iterator<char>(&buffer->buffer), buffer->buffer.size(), data);
  1218. result->data = data;
  1219. result->size = size;
  1220. result->owns_data = true;
  1221. }
  1222. static void streambuf_consume(ThalamusStreamBuf* buffer, uint64_t count) {
  1223. buffer->buffer.consume(count);
  1224. }
  1225. static uint64_t streambuf_size(ThalamusStreamBuf* buffer) {
  1226. return buffer->buffer.size();
  1227. }
  1228. static void charspan_release(ThalamusCharSpan* span) {
  1229. if(span->owns_data) {
  1230. delete span->data;
  1231. }
  1232. }
  1233. static void error_code_message(struct ThalamusCharSpan* result, ThalamusErrorCode *error) {
  1234. auto message = error->error->message();
  1235. char* data = new char[message.size()];
  1236. std::copy_n(message.begin(), message.size(), data);
  1237. result->data = data;
  1238. result->size = message.size();
  1239. result->owns_data = true;
  1240. }
  1241. static void json_to_string(struct ThalamusCharSpan* output, const struct ThalamusJson* input) {
  1242. auto serialized = boost::json::serialize(input->value);
  1243. string_to_span(output, serialized);
  1244. }
  1245. static struct ThalamusJson* json_from_string(const struct ThalamusCharSpan* input) {
  1246. return new ThalamusJson{1, boost::json::parse(std::string_view(input->data, input->size))};
  1247. }
  1248. static void json_inc_ref(struct ThalamusJson* input) {
  1249. ++input->refs;
  1250. }
  1251. static void json_dec_ref(struct ThalamusJson* input) {
  1252. if(--input->refs == 0) {
  1253. delete input;
  1254. }
  1255. }
  1256. static void request_respond(struct ThalamusRequestHandle* handle, const struct ThalamusJson* response) {
  1257. handle->callback(response->value);
  1258. delete handle;
  1259. }
  1260. static ThalamusNodeGetConnection* node_get_node(struct ThalamusNodeSelector* c_selector, ThalamusNodeGetCallback callback, void* data) {
  1261. thalamus_grpc::NodeSelector selector;
  1262. if(c_selector->name.data) {
  1263. selector.set_name(std::string(c_selector->name.data, c_selector->name.size));
  1264. }
  1265. if(c_selector->type.data) {
  1266. selector.set_type(std::string(c_selector->type.data, c_selector->type.size));
  1267. }
  1268. auto result = new ThalamusNodeGetConnection();
  1269. result->connection = node_graph->get_node_scoped(selector, [callback, data] (auto node) {
  1270. auto ref = get_node_ref(node.lock().get());
  1271. {
  1272. NodeGuard lock(ref);
  1273. callback(ref, data);
  1274. }
  1275. node_dec_ref(ref);
  1276. });
  1277. return result;
  1278. }
  1279. static void node_inc_ref(struct ThalamusNode* node) {
  1280. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  1281. ++interfaces->count;
  1282. }
  1283. static void node_dec_ref(struct ThalamusNode* node) {
  1284. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  1285. if(--interfaces->count == 0) {
  1286. std::lock_guard<std::mutex> lock(*mutex);
  1287. node_c_to_cpp->erase(node);
  1288. node_cpp_to_c->erase(interfaces->node);
  1289. delete node;
  1290. }
  1291. }
  1292. static ThalamusNodeReadyConnection* node_ready_connect(struct ThalamusNode* node, ThalamusNodeReadyCallback callback, void* data) {
  1293. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  1294. auto result = new ThalamusNodeReadyConnection();
  1295. result->node = node;
  1296. node_inc_ref(node);
  1297. result->connection = interfaces->node->ready.connect([node, callback, data] (auto) {
  1298. NodeGuard lock(node);
  1299. callback(node, data);
  1300. });
  1301. return result;
  1302. }
  1303. static ThalamusNodeReadyConnection* node_ready_multithreaded_connect(struct ThalamusNode* node, ThalamusNodeReadyCallback callback, void* data) {
  1304. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  1305. auto result = new ThalamusNodeReadyConnection();
  1306. result->node = node;
  1307. node_inc_ref(node);
  1308. result->connection = node::connect_ready_multithreaded(interfaces->node, [node, callback, data] (auto) {
  1309. NodeGuard lock(node);
  1310. callback(node, data);
  1311. });
  1312. return result;
  1313. }
  1314. static void node_get_node_disconnect(struct ThalamusNodeGetConnection* conn) {
  1315. delete conn;
  1316. }
  1317. static void node_ready_disconnect(struct ThalamusNodeReadyConnection* conn) {
  1318. node_dec_ref(conn->node);
  1319. delete conn;
  1320. }
  1321. static void node_channels_changed(struct ThalamusNode* node) {
  1322. auto ext_node = reinterpret_cast<ExtNode*>(node->impl);
  1323. ext_node->channels_changed(ext_node);
  1324. }
  1325. static ThalamusNodeReadyConnection* node_channels_changed_connect(struct ThalamusNode* node, ThalamusNodeReadyCallback callback, void* data) {
  1326. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  1327. auto result = new ThalamusNodeReadyConnection();
  1328. result->node = node;
  1329. node_inc_ref(node);
  1330. if(interfaces->analog) {
  1331. result->connection = interfaces->analog->channels_changed.connect([node, callback, data] (auto) {
  1332. NodeGuard lock(node);
  1333. callback(node, data);
  1334. });
  1335. }
  1336. return result;
  1337. }
  1338. static void node_channels_changed_disconnect(struct ThalamusNodeReadyConnection* conn) {
  1339. node_dec_ref(conn->node);
  1340. delete conn;
  1341. }
  1342. static ThalamusState* node_get_state(struct ThalamusNode* node) {
  1343. auto interfaces = reinterpret_cast<Interfaces*>(node->impl);
  1344. ASSERT_SAFE();
  1345. return get_state_ref(node_graph->get_node_state(interfaces->node));
  1346. }
  1347. static VkInstance get_vulkan_instance() {
  1348. return node_graph->get_vulkan_instance();
  1349. }
  1350. static VkDevice get_vulkan_device() {
  1351. return node_graph->get_vulkan_device();
  1352. }
  1353. static VkPhysicalDevice get_vulkan_physical_device() {
  1354. return node_graph->get_vulkan_physical_device();
  1355. }
  1356. static VkQueue get_vulkan_queue() {
  1357. return node_graph->get_vulkan_queue();
  1358. }
  1359. static VkCommandPool create_vulkan_command_pool() {
  1360. return node_graph->create_vulkan_command_pool();
  1361. }
  1362. static ThalamusVkQueueLock* lock_vulkan_queue() {
  1363. return new ThalamusVkQueueLock{node_graph->lock_vulkan_queue()};
  1364. }
  1365. static void unlock_vulkan_queue(ThalamusVkQueueLock* lock) {
  1366. delete lock;
  1367. }
  1368. static struct THALAMUS_SDL_Window* sdl_create_window(struct ThalamusCharSpan* name, int32_t width, int32_t height, uint64_t flags) {
  1369. auto window = SDL_CreateWindow(std::string(name->data, name->size).c_str(), width, height, flags);
  1370. return reinterpret_cast<struct THALAMUS_SDL_Window*>(window);
  1371. }
  1372. static void sdl_destroy_window(struct THALAMUS_SDL_Window* window) {
  1373. SDL_DestroyWindow(reinterpret_cast<SDL_Window*>(window));
  1374. }
  1375. static void sdl_set_window_position(struct THALAMUS_SDL_Window* window, int32_t x, int32_t y) {
  1376. SDL_SetWindowPosition(reinterpret_cast<SDL_Window*>(window), x, y);
  1377. }
  1378. static void sdl_set_window_size(struct THALAMUS_SDL_Window* window, int32_t w, int32_t h) {
  1379. SDL_SetWindowSize(reinterpret_cast<SDL_Window*>(window), w, h);
  1380. }
  1381. static void sdl_set_window_title(struct THALAMUS_SDL_Window* window, struct ThalamusCharSpan* title) {
  1382. SDL_SetWindowTitle(reinterpret_cast<SDL_Window*>(window), std::string(title->data, title->size).c_str());
  1383. }
  1384. static void sdl_get_error(struct ThalamusCharSpan* error) {
  1385. const char* message = SDL_GetError();
  1386. error->data = message;
  1387. error->size = message ? strlen(message) : 0;
  1388. error->owns_data = 0;
  1389. }
  1390. static uint8_t sdl_vulkan_create_surface(struct THALAMUS_SDL_Window* window, VkInstance instance, const struct VkAllocationCallbacks* allocator, VkSurfaceKHR* surface) {
  1391. return SDL_Vulkan_CreateSurface(reinterpret_cast<SDL_Window*>(window), instance, allocator, surface) ? 1 : 0;
  1392. }
  1393. static void sdl_get_window_size_in_pixels(struct THALAMUS_SDL_Window* window, int32_t* w, int32_t* h) {
  1394. SDL_GetWindowSizeInPixels(reinterpret_cast<SDL_Window*>(window), w, h);
  1395. }
  1396. static uint32_t sdl_get_window_id(struct THALAMUS_SDL_Window* window) {
  1397. return SDL_GetWindowID(reinterpret_cast<SDL_Window*>(window));
  1398. }
  1399. static void sdl_get_window_position(struct THALAMUS_SDL_Window* window, int32_t* x, int32_t* y) {
  1400. SDL_GetWindowPosition(reinterpret_cast<SDL_Window*>(window), x, y);
  1401. }
  1402. static void sdl_get_window_size(struct THALAMUS_SDL_Window* window, int32_t* w, int32_t* h) {
  1403. SDL_GetWindowSize(reinterpret_cast<SDL_Window*>(window), w, h);
  1404. }
  1405. static struct THALAMUS_SDL_EventSubscription* sdl_events_subscribe(THALAMUS_SDL_EventCallback callback, void* data) {
  1406. return ImageViewer::subscribe([callback, data](THALAMUS_SDL_Event* event) {
  1407. callback(event, data);
  1408. });
  1409. }
  1410. static void sdl_events_unsubscribe(struct THALAMUS_SDL_EventSubscription* subscription) {
  1411. ImageViewer::unsubscribe(subscription);
  1412. }
  1413. static void sdl_get_clipboard_text(struct ThalamusCharSpan* result) {
  1414. char* text = SDL_GetClipboardText();
  1415. string_to_span(result, text ? std::string(text) : std::string());
  1416. SDL_free(text);
  1417. }
  1418. static uint8_t sdl_set_clipboard_text(const struct ThalamusCharSpan* text) {
  1419. return SDL_SetClipboardText(std::string(text->data, text->size).c_str()) ? 1 : 0;
  1420. }
  1421. static struct THALAMUS_SDL_Cursor* sdl_create_system_cursor(int32_t id) {
  1422. return reinterpret_cast<struct THALAMUS_SDL_Cursor*>(SDL_CreateSystemCursor(static_cast<SDL_SystemCursor>(id)));
  1423. }
  1424. static uint8_t sdl_set_cursor(struct THALAMUS_SDL_Cursor* cursor) {
  1425. return SDL_SetCursor(reinterpret_cast<SDL_Cursor*>(cursor)) ? 1 : 0;
  1426. }
  1427. static void sdl_destroy_cursor(struct THALAMUS_SDL_Cursor* cursor) {
  1428. SDL_DestroyCursor(reinterpret_cast<SDL_Cursor*>(cursor));
  1429. }
  1430. static uint8_t sdl_show_cursor() {
  1431. return SDL_ShowCursor() ? 1 : 0;
  1432. }
  1433. static uint8_t sdl_hide_cursor() {
  1434. return SDL_HideCursor() ? 1 : 0;
  1435. }
  1436. static struct ThalamusState* state_make_dict() {
  1437. auto dict = ObservableCollection::Value(std::make_shared<ObservableDict>());
  1438. auto ref = get_state_ref(dict);
  1439. return ref;
  1440. }
  1441. static struct ThalamusState* state_make_list() {
  1442. auto dict = ObservableCollection::Value(std::make_shared<ObservableList>());
  1443. auto ref = get_state_ref(dict);
  1444. return ref;
  1445. }
  1446. static struct ThalamusOffMainSignaler* node_offmain_signaler_create(struct ThalamusNode* node) {
  1447. auto ext_node = reinterpret_cast<ExtNode*>(node->impl);
  1448. return new ThalamusOffMainSignaler{node::OffMainSignaler(*ext_node, *io_context)};
  1449. }
  1450. static void node_offmain_signaler_destroy(struct ThalamusOffMainSignaler* signaler) {
  1451. delete signaler;
  1452. }
  1453. static void node_offmain_signaler_block(struct ThalamusOffMainSignaler* signaler) {
  1454. signaler->signaler.block();
  1455. }
  1456. static void node_offmain_signaler_unblock(struct ThalamusOffMainSignaler* signaler) {
  1457. signaler->signaler.unblock();
  1458. }
  1459. static uint8_t node_offmain_signaler_ready(struct ThalamusOffMainSignaler* signaler) {
  1460. return signaler->signaler.ready() ? 1 : 0;
  1461. }
  1462. static void dialog_show(struct ThalamusCharSpan* title, struct ThalamusCharSpan* message, ThalamusDialogType type) {
  1463. thalamus_grpc::Dialog dialog;
  1464. dialog.set_title(to_string(*title));
  1465. dialog.set_message(to_string(*message));
  1466. switch(type) {
  1467. case ThalamusDialogType_Info:
  1468. dialog.set_type(thalamus_grpc::Dialog::Type::Dialog_Type_INFO);
  1469. break;
  1470. case ThalamusDialogType_Warn:
  1471. dialog.set_type(thalamus_grpc::Dialog::Type::Dialog_Type_WARN);
  1472. break;
  1473. case ThalamusDialogType_Error:
  1474. dialog.set_type(thalamus_grpc::Dialog::Type::Dialog_Type_ERROR);
  1475. break;
  1476. case ThalamusDialogType_Fatal:
  1477. dialog.set_type(thalamus_grpc::Dialog::Type::Dialog_Type_FATAL);
  1478. break;
  1479. }
  1480. node_graph->dialog(dialog);
  1481. }
  1482. static void state_remove_at_name(struct ThalamusState* state, const struct ThalamusCharSpan* key, ThalamusPostCallback callback, void* data) {
  1483. auto closure = to_closure(callback, data);
  1484. erase_state(state, std::string(key->data, key->size), closure);
  1485. }
  1486. static void state_remove_at_index(struct ThalamusState* state, int64_t key, ThalamusPostCallback callback, void* data) {
  1487. auto closure = to_closure(callback, data);
  1488. erase_state(state, key, closure);
  1489. }
  1490. };
  1491. std::map<ObservableCollection::Value, ThalamusState*>* ThalamusAPIImpl::cpp_to_c = nullptr;
  1492. std::map<ThalamusState*, ObservableCollection::Value>* ThalamusAPIImpl::c_to_cpp = nullptr;
  1493. boost::asio::io_context* ThalamusAPIImpl::io_context = nullptr;
  1494. NodeGraphImpl* ThalamusAPIImpl::node_graph = nullptr;
  1495. std::mutex* ThalamusAPIImpl::mutex = nullptr;
  1496. std::map<Node*, ThalamusNode*>* ThalamusAPIImpl::node_cpp_to_c = nullptr;
  1497. std::map<ThalamusNode*, Node*>* ThalamusAPIImpl::node_c_to_cpp = nullptr;
  1498. struct ExtNodeFactory : public INodeFactory {
  1499. int version;
  1500. ThalamusNodeFactory* underlying;
  1501. ThalamusIoContext io_context;
  1502. ThalamusNodeGraph node_graph;
  1503. ThalamusAPI* api;
  1504. ExtNodeFactory(int _version, ThalamusNodeFactory* _underlying, boost::asio::io_context &_io_context, NodeGraph *graph, ThalamusAPI* _api)
  1505. : version(_version), underlying(_underlying), io_context(_io_context), node_graph(graph), api(_api) {}
  1506. Node *create(ObservableDictPtr state, boost::asio::io_context &,
  1507. NodeGraph *) override {
  1508. auto state_wrapper = ThalamusAPIImpl::get_state_ref(state);
  1509. auto result = new ExtNode(nullptr, underlying, api);
  1510. ThalamusNode* node;
  1511. if (version < 1) {
  1512. node = underlying->create(underlying, state_wrapper, &io_context, &node_graph);
  1513. } else {
  1514. node = underlying->create2(underlying, state_wrapper, &io_context, &node_graph, result);
  1515. }
  1516. result->node = node;
  1517. if(node->signals_offmain) {
  1518. result->ready_multithreaded.emplace();
  1519. }
  1520. ThalamusAPIImpl::state_dec_ref(state_wrapper);
  1521. return result;
  1522. }
  1523. bool prepare() override {
  1524. if(underlying->prepare != nullptr) {
  1525. return underlying->prepare(underlying);
  1526. } else {
  1527. return true;
  1528. }
  1529. }
  1530. void cleanup() override {
  1531. if(underlying->cleanup != nullptr) {
  1532. return underlying->cleanup(underlying);
  1533. }
  1534. }
  1535. std::string type_name() override;
  1536. };
  1537. std::string ExtNodeFactory::type_name() { return std::string("*EXT* ") + to_string(underlying->type); }
  1538. struct NodeGraphImpl::Impl {
  1539. ObservableListPtr nodes;
  1540. std::vector<std::string> node_next_type;
  1541. std::vector<std::shared_ptr<Node>> node_impls;
  1542. std::vector<std::string> node_types;
  1543. std::vector<ObservableDictPtr> node_configs;
  1544. size_t num_nodes;
  1545. boost::asio::io_context &io_context;
  1546. std::optional<Service *> service;
  1547. std::vector<std::pair<thalamus_grpc::NodeSelector,
  1548. std::function<void(std::weak_ptr<Node>)>>>
  1549. callbacks;
  1550. std::vector<std::pair<thalamus_grpc::NodeSelector,
  1551. boost::signals2::signal<void(std::weak_ptr<Node>)>>>
  1552. signals;
  1553. NodeGraphImpl *outer;
  1554. thalamus::map<std::string, std::weak_ptr<grpc::Channel>> channels;
  1555. thalamus::map<std::string, std::unique_ptr<thalamus_grpc::Thalamus::Stub>> stubs;
  1556. std::chrono::system_clock::time_point system_time;
  1557. std::chrono::steady_clock::time_point steady_time;
  1558. ThreadPool thread_pool;
  1559. thalamus_grpc::Thalamus::Stub* stub;
  1560. std::vector<SharedLibrary>& extension;
  1561. std::mutex vulkan_mutex;
  1562. Vulkan vulkan;
  1563. std::map<std::string, INodeFactory *> node_factories;
  1564. ThalamusAPIImpl thalamus_api_impl;
  1565. ThalamusAPI thalamus_api;
  1566. int creating_index = -1;
  1567. boost::signals2::scoped_connection nodes_connection;
  1568. std::vector<boost::signals2::scoped_connection> node_connections;
  1569. public:
  1570. Impl(ObservableListPtr _nodes, boost::asio::io_context &_io_context,
  1571. NodeGraphImpl *_outer,
  1572. std::chrono::system_clock::time_point _system_time,
  1573. std::chrono::steady_clock::time_point _steady_time,
  1574. thalamus_grpc::Thalamus::Stub* _stub,
  1575. std::vector<SharedLibrary>& _extension, Vulkan _vulkan)
  1576. : nodes(_nodes), num_nodes(nodes->size()), io_context(_io_context),
  1577. outer(_outer), system_time(_system_time), steady_time(_steady_time),
  1578. thread_pool("ThreadPool"), stub(_stub), extension(_extension), vulkan(_vulkan) {
  1579. ThalamusAPIImpl::cpp_to_c = new std::map<ObservableCollection::Value, ThalamusState*>();
  1580. ThalamusAPIImpl::c_to_cpp = new std::map<ThalamusState*, ObservableCollection::Value>();
  1581. ThalamusAPIImpl::mutex = new std::mutex();
  1582. ThalamusAPIImpl::node_cpp_to_c = new std::map<Node*, ThalamusNode*>();
  1583. ThalamusAPIImpl::node_c_to_cpp = new std::map<ThalamusNode*, Node*>();
  1584. ThalamusAPIImpl::io_context = &io_context;
  1585. ThalamusAPIImpl::node_graph = _outer;
  1586. thalamus_api.state_is_dict = ThalamusAPIImpl::state_is_dict;
  1587. thalamus_api.state_is_list = ThalamusAPIImpl::state_is_list;
  1588. thalamus_api.state_is_string = ThalamusAPIImpl::state_is_string;
  1589. thalamus_api.state_is_int = ThalamusAPIImpl::state_is_int;
  1590. thalamus_api.state_is_float = ThalamusAPIImpl::state_is_float;
  1591. thalamus_api.state_is_null = ThalamusAPIImpl::state_is_null;
  1592. thalamus_api.state_is_bool = ThalamusAPIImpl::state_is_bool;
  1593. thalamus_api.state_get_string = ThalamusAPIImpl::state_get_string;
  1594. thalamus_api.state_get_int = ThalamusAPIImpl::state_get_int;
  1595. thalamus_api.state_get_float = ThalamusAPIImpl::state_get_float;
  1596. thalamus_api.state_get_bool = ThalamusAPIImpl::state_get_bool;
  1597. thalamus_api.state_get_at_name = ThalamusAPIImpl::state_get_at_name;
  1598. thalamus_api.state_get_at_index = ThalamusAPIImpl::state_get_at_index;
  1599. thalamus_api.state_dec_ref = ThalamusAPIImpl::state_dec_ref;
  1600. thalamus_api.state_inc_ref = ThalamusAPIImpl::state_inc_ref;
  1601. thalamus_api.state_recursive_change_connect = ThalamusAPIImpl::state_recursive_change_connect;
  1602. thalamus_api.state_recursive_change_disconnect = ThalamusAPIImpl::state_recursive_change_disconnect;
  1603. thalamus_api.timer_create = ThalamusAPIImpl::timer_create;
  1604. thalamus_api.timer_destroy = ThalamusAPIImpl::timer_destroy;
  1605. thalamus_api.timer_expire_after_ns = ThalamusAPIImpl::timer_expire_after_ns;
  1606. thalamus_api.timer_async_wait = ThalamusAPIImpl::timer_async_wait;
  1607. thalamus_api.error_code_value = ThalamusAPIImpl::error_code_value;
  1608. thalamus_api.node_ready = ThalamusAPIImpl::node_ready;
  1609. thalamus_api.time_ns = ThalamusAPIImpl::time_ns;
  1610. thalamus_api.error_code_operation_aborted = ThalamusAPIImpl::error_code_operation_aborted;
  1611. thalamus_api.state_recap = ThalamusAPIImpl::state_recap;
  1612. thalamus_api.state_set_at_name_state = ThalamusAPIImpl::state_set_at_name_state;
  1613. thalamus_api.state_set_at_name_string = ThalamusAPIImpl::state_set_at_name_string;
  1614. thalamus_api.state_set_at_name_int = ThalamusAPIImpl::state_set_at_name_int;
  1615. thalamus_api.state_set_at_name_float = ThalamusAPIImpl::state_set_at_name_float;
  1616. thalamus_api.state_set_at_name_null = ThalamusAPIImpl::state_set_at_name_null;
  1617. thalamus_api.state_set_at_name_bool = ThalamusAPIImpl::state_set_at_name_bool;
  1618. thalamus_api.state_set_at_index_state = ThalamusAPIImpl::state_set_at_index_state;
  1619. thalamus_api.state_set_at_index_string = ThalamusAPIImpl::state_set_at_index_string;
  1620. thalamus_api.state_set_at_index_int = ThalamusAPIImpl::state_set_at_index_int;
  1621. thalamus_api.state_set_at_index_float = ThalamusAPIImpl::state_set_at_index_float;
  1622. thalamus_api.state_set_at_index_null = ThalamusAPIImpl::state_set_at_index_null;
  1623. thalamus_api.state_set_at_index_bool = ThalamusAPIImpl::state_set_at_index_bool;
  1624. thalamus_api.io_context_post = ThalamusAPIImpl::io_context_post;
  1625. thalamus_api.trace_event_begin = ThalamusAPIImpl::trace_event_begin;
  1626. thalamus_api.trace_event_end = ThalamusAPIImpl::trace_event_end;
  1627. thalamus_api.serial_port_create = ThalamusAPIImpl::serial_port_create;
  1628. thalamus_api.serial_port_destroy = ThalamusAPIImpl::serial_port_destroy;
  1629. thalamus_api.serial_set_baud_rate = ThalamusAPIImpl::serial_set_baud_rate;
  1630. thalamus_api.serial_port_open = ThalamusAPIImpl::serial_port_open;
  1631. thalamus_api.serial_port_error = ThalamusAPIImpl::serial_port_error;
  1632. thalamus_api.serial_port_read_until = ThalamusAPIImpl::serial_port_read_until;
  1633. thalamus_api.serial_port_read_some = ThalamusAPIImpl::serial_port_read_some;
  1634. thalamus_api.serial_port_read = ThalamusAPIImpl::serial_port_read;
  1635. thalamus_api.serial_port_write = ThalamusAPIImpl::serial_port_write;
  1636. thalamus_api.streambuf_create = ThalamusAPIImpl::streambuf_create;
  1637. thalamus_api.streambuf_destroy = ThalamusAPIImpl::streambuf_destroy;
  1638. thalamus_api.streambuf_to_span = ThalamusAPIImpl::streambuf_to_span;
  1639. thalamus_api.streambuf_consume = ThalamusAPIImpl::streambuf_consume;
  1640. thalamus_api.streambuf_size = ThalamusAPIImpl::streambuf_size;
  1641. thalamus_api.charspan_release = ThalamusAPIImpl::charspan_release;
  1642. thalamus_api.error_code_message = ThalamusAPIImpl::error_code_message;
  1643. thalamus_api.json_to_string = ThalamusAPIImpl::json_to_string;
  1644. thalamus_api.json_from_string = ThalamusAPIImpl::json_from_string;
  1645. thalamus_api.request_respond = ThalamusAPIImpl::request_respond;
  1646. thalamus_api.json_inc_ref = ThalamusAPIImpl::json_inc_ref;
  1647. thalamus_api.json_dec_ref = ThalamusAPIImpl::json_dec_ref;
  1648. thalamus_api.node_get_node = ThalamusAPIImpl::node_get_node;
  1649. thalamus_api.node_ready_connect = ThalamusAPIImpl::node_ready_connect;
  1650. thalamus_api.node_get_node_disconnect = ThalamusAPIImpl::node_get_node_disconnect;
  1651. thalamus_api.node_ready_disconnect = ThalamusAPIImpl::node_ready_disconnect;
  1652. thalamus_api.node_channels_changed = ThalamusAPIImpl::node_channels_changed;
  1653. thalamus_api.node_channels_changed_connect = ThalamusAPIImpl::node_channels_changed_connect;
  1654. thalamus_api.node_channels_changed_disconnect = ThalamusAPIImpl::node_channels_changed_disconnect;
  1655. thalamus_api.node_inc_ref = ThalamusAPIImpl::node_inc_ref;
  1656. thalamus_api.node_dec_ref = ThalamusAPIImpl::node_dec_ref;
  1657. thalamus_api.state_parent = ThalamusAPIImpl::state_parent;
  1658. thalamus_api.state_iter_create = ThalamusAPIImpl::state_iter_create;
  1659. thalamus_api.state_iter_next = ThalamusAPIImpl::state_iter_next;
  1660. thalamus_api.state_iter_key = ThalamusAPIImpl::state_iter_key;
  1661. thalamus_api.state_iter_value = ThalamusAPIImpl::state_iter_value;
  1662. thalamus_api.state_iter_destroy = ThalamusAPIImpl::state_iter_destroy;
  1663. thalamus_api.state_key_of = ThalamusAPIImpl::state_key_of;
  1664. thalamus_api.state_recap_with = ThalamusAPIImpl::state_recap_with;
  1665. thalamus_api.node_get_state = ThalamusAPIImpl::node_get_state;
  1666. thalamus_api.threadpool_post = ThalamusAPIImpl::threadpool_post;
  1667. thalamus_api.node_ready_multithreaded_connect = ThalamusAPIImpl::node_ready_multithreaded_connect;
  1668. thalamus_api.node_ready_offmain = ThalamusAPIImpl::node_ready_offmain;
  1669. thalamus_api.node_predrop_ready = ThalamusAPIImpl::node_predrop_ready;
  1670. thalamus_api.get_vulkan_instance = ThalamusAPIImpl::get_vulkan_instance;
  1671. thalamus_api.get_vulkan_device = ThalamusAPIImpl::get_vulkan_device;
  1672. thalamus_api.get_vulkan_physical_device = ThalamusAPIImpl::get_vulkan_physical_device;
  1673. thalamus_api.get_vulkan_queue = ThalamusAPIImpl::get_vulkan_queue;
  1674. thalamus_api.create_vulkan_command_pool = ThalamusAPIImpl::create_vulkan_command_pool;
  1675. thalamus_api.lock_vulkan_queue = ThalamusAPIImpl::lock_vulkan_queue;
  1676. thalamus_api.unlock_vulkan_queue = ThalamusAPIImpl::unlock_vulkan_queue;
  1677. thalamus_api.sdl_create_window = ThalamusAPIImpl::sdl_create_window;
  1678. thalamus_api.sdl_destroy_window = ThalamusAPIImpl::sdl_destroy_window;
  1679. thalamus_api.sdl_set_window_position = ThalamusAPIImpl::sdl_set_window_position;
  1680. thalamus_api.sdl_set_window_size = ThalamusAPIImpl::sdl_set_window_size;
  1681. thalamus_api.sdl_set_window_title = ThalamusAPIImpl::sdl_set_window_title;
  1682. thalamus_api.sdl_get_error = ThalamusAPIImpl::sdl_get_error;
  1683. thalamus_api.sdl_vulkan_create_surface = ThalamusAPIImpl::sdl_vulkan_create_surface;
  1684. thalamus_api.sdl_get_window_size_in_pixels = ThalamusAPIImpl::sdl_get_window_size_in_pixels;
  1685. thalamus_api.sdl_get_window_id = ThalamusAPIImpl::sdl_get_window_id;
  1686. thalamus_api.sdl_get_window_position = ThalamusAPIImpl::sdl_get_window_position;
  1687. thalamus_api.sdl_get_window_size = ThalamusAPIImpl::sdl_get_window_size;
  1688. thalamus_api.sdl_events_subscribe = ThalamusAPIImpl::sdl_events_subscribe;
  1689. thalamus_api.sdl_events_unsubscribe = ThalamusAPIImpl::sdl_events_unsubscribe;
  1690. thalamus_api.sdl_get_clipboard_text = ThalamusAPIImpl::sdl_get_clipboard_text;
  1691. thalamus_api.sdl_set_clipboard_text = ThalamusAPIImpl::sdl_set_clipboard_text;
  1692. thalamus_api.sdl_create_system_cursor = ThalamusAPIImpl::sdl_create_system_cursor;
  1693. thalamus_api.sdl_set_cursor = ThalamusAPIImpl::sdl_set_cursor;
  1694. thalamus_api.sdl_destroy_cursor = ThalamusAPIImpl::sdl_destroy_cursor;
  1695. thalamus_api.sdl_show_cursor = ThalamusAPIImpl::sdl_show_cursor;
  1696. thalamus_api.sdl_hide_cursor = ThalamusAPIImpl::sdl_hide_cursor;
  1697. thalamus_api.state_make_dict = ThalamusAPIImpl::state_make_dict;
  1698. thalamus_api.state_make_list = ThalamusAPIImpl::state_make_list;
  1699. thalamus_api.state_set_at_name_state_with_callback = ThalamusAPIImpl::state_set_at_name_state_with_callback;
  1700. thalamus_api.state_set_at_name_string_with_callback = ThalamusAPIImpl::state_set_at_name_string_with_callback;
  1701. thalamus_api.state_set_at_name_int_with_callback = ThalamusAPIImpl::state_set_at_name_int_with_callback;
  1702. thalamus_api.state_set_at_name_float_with_callback = ThalamusAPIImpl::state_set_at_name_float_with_callback;
  1703. thalamus_api.state_set_at_name_null_with_callback = ThalamusAPIImpl::state_set_at_name_null_with_callback;
  1704. thalamus_api.state_set_at_name_bool_with_callback = ThalamusAPIImpl::state_set_at_name_bool_with_callback;
  1705. thalamus_api.state_set_at_index_state_with_callback = ThalamusAPIImpl::state_set_at_index_state_with_callback;
  1706. thalamus_api.state_set_at_index_string_with_callback = ThalamusAPIImpl::state_set_at_index_string_with_callback;
  1707. thalamus_api.state_set_at_index_int_with_callback = ThalamusAPIImpl::state_set_at_index_int_with_callback;
  1708. thalamus_api.state_set_at_index_float_with_callback = ThalamusAPIImpl::state_set_at_index_float_with_callback;
  1709. thalamus_api.state_set_at_index_null_with_callback = ThalamusAPIImpl::state_set_at_index_null_with_callback;
  1710. thalamus_api.state_set_at_index_bool_with_callback = ThalamusAPIImpl::state_set_at_index_bool_with_callback;
  1711. thalamus_api.state_push_state_with_callback = ThalamusAPIImpl::state_push_state_with_callback;
  1712. thalamus_api.state_push_string_with_callback = ThalamusAPIImpl::state_push_string_with_callback;
  1713. thalamus_api.state_push_int_with_callback = ThalamusAPIImpl::state_push_int_with_callback;
  1714. thalamus_api.state_push_float_with_callback = ThalamusAPIImpl::state_push_float_with_callback;
  1715. thalamus_api.state_push_null_with_callback = ThalamusAPIImpl::state_push_null_with_callback;
  1716. thalamus_api.state_push_bool_with_callback = ThalamusAPIImpl::state_push_bool_with_callback;
  1717. thalamus_api.node_offmain_signaler_create = ThalamusAPIImpl::node_offmain_signaler_create;
  1718. thalamus_api.node_offmain_signaler_destroy = ThalamusAPIImpl::node_offmain_signaler_destroy;
  1719. thalamus_api.node_offmain_signaler_block = ThalamusAPIImpl::node_offmain_signaler_block;
  1720. thalamus_api.node_offmain_signaler_unblock = ThalamusAPIImpl::node_offmain_signaler_unblock;
  1721. thalamus_api.node_offmain_signaler_ready = ThalamusAPIImpl::node_offmain_signaler_ready;
  1722. thalamus_api.dialog_show = ThalamusAPIImpl::dialog_show;
  1723. thalamus_api.state_remove_at_name = ThalamusAPIImpl::state_remove_at_name;
  1724. thalamus_api.state_remove_at_index = ThalamusAPIImpl::state_remove_at_index;
  1725. thalamus_api.version = 139;
  1726. node_factories = {
  1727. {"NONE", new NodeFactory<NoneNode>()},
  1728. {"NIDAQ", new NodeFactory<NidaqNode>()},
  1729. {"NIDAQ_OUT", new NodeFactory<NidaqOutputNode>()},
  1730. {"ALPHA_OMEGA", new NodeFactory<AlphaOmegaNode>()},
  1731. {"TOGGLE", new NodeFactory<ToggleNode>()},
  1732. {"XSENS", new NodeFactory<XsensNode>()},
  1733. {"MOCAP", new NodeFactory<MotionCaptureNodeImpl>()},
  1734. {"HAND_ENGINE", new NodeFactory<HandEngineNode>()},
  1735. {"WAVE", new NodeFactory<WaveGeneratorNode>()},
  1736. {"STORAGE", new NodeFactory<StorageNode>()},
  1737. {"STORAGE2", new NodeFactory<Storage2Node>()},
  1738. {"RUNNER", new NodeFactory<RunNode>()},
  1739. {"RUNNER2", new NodeFactory<Run2Node>()},
  1740. {"OPHANIM", new NodeFactory<OphanimNode>()},
  1741. {"TASK_CONTROLLER", new NodeFactory<TaskControllerNode>()},
  1742. {"ANALOG", new NodeFactory<AnalogNodeImpl>()},
  1743. {"FFMPEG", new NodeFactory<FfmpegNode>()},
  1744. {"VIDEO", new NodeFactory<VideoNode>()},
  1745. {"OCULOMATIC", new NodeFactory<OculomaticNode>()},
  1746. {"DISTORTION", new NodeFactory<DistortionNode>()},
  1747. {"GENICAM", new NodeFactory<GenicamNode>()},
  1748. {"THREAD_POOL", new NodeFactory<ThreadPoolNode>()},
  1749. {"CHANNEL_PICKER", new NodeFactory<ChannelPickerNode>()},
  1750. {"NORMALIZE", new NodeFactory<NormalizeNode>()},
  1751. {"ALGEBRA", new NodeFactory<AlgebraNode>()},
  1752. {"LUA", new NodeFactory<LuaNode>()},
  1753. #if !defined(_WIN32) && !defined(__APPLE__)
  1754. {"ROS2", new NodeFactory<Ros2Node>()},
  1755. #endif
  1756. #ifdef _WIN32
  1757. {"BRAINPRODUCTS", new NodeFactory<BrainProductsNode>()},
  1758. #endif
  1759. {"REMOTE", new NodeFactory<RemoteNode>()},
  1760. {"REMOTE_LOG", new NodeFactory<RemoteLogNode>()},
  1761. {"CHESSBOARD", new NodeFactory<ChessBoardNode>()},
  1762. {"PUPIL", new NodeFactory<PupilNode>()},
  1763. {"LOG", new NodeFactory<LogNode>()},
  1764. {"INTAN", new NodeFactory<IntanNode>()},
  1765. {"SPIKEGLX", new NodeFactory<SpikeGlxNode>()},
  1766. {"SYNC", new NodeFactory<SyncNode>()},
  1767. {"TOUCH_SCREEN", new NodeFactory<TouchScreenNode>()},
  1768. {"STIM_PRINTER", new NodeFactory<StimPrinterNode>()},
  1769. {"TEST_PULSE_NODE", new NodeFactory<TestPulseNode>()},
  1770. {"WALLCLOCK", new NodeFactory<WallClockNode>()},
  1771. {"CECI", new NodeFactory<CeciNode>()},
  1772. //{"HEXASCOPE", new NodeFactory<HexascopeNode>()},
  1773. {"DELSYS", new NodeFactory<DelsysNode>()},
  1774. {"FREQUENCY", new NodeFactory<FrequencyNode>()},
  1775. {"SAMPLE_MONITOR", new NodeFactory<SampleMonitorNode>()},
  1776. {"JOYSTICK", new NodeFactory<JoystickNode>()},
  1777. {"SERIAL_TOUCH_SCREEN", new NodeFactory<SerialTouchScreenNode>()},
  1778. {"ARUCO", new NodeFactory<ArucoNode>()}};
  1779. for(auto& ext : extension) {
  1780. THALAMUS_LOG(info) << "Loading " << ext.name();
  1781. //auto library_handle = LoadLibrary("C:\\Thalamus\\ext.dll");
  1782. //auto get_node_factories = reinterpret_cast<get_node_factories_fun>(
  1783. // ::GetProcAddress(library_handle, "get_node_factories"));
  1784. auto get_node_factory_version = ext.load<thalamus_get_node_factory_version_t>("thalamus_get_node_factory_version");
  1785. int32_t version;
  1786. if(get_node_factory_version != nullptr) {
  1787. version = get_node_factory_version();
  1788. } else {
  1789. version = 0;
  1790. }
  1791. auto get_node_factories = ext.load<thalamus_get_node_factories_t>("thalamus_get_node_factories");
  1792. THALAMUS_ASSERT(get_node_factories, "thalamus_get_node_factories not found in extension");
  1793. auto factory = get_node_factories(&thalamus_api);
  1794. while(*factory != nullptr) {
  1795. THALAMUS_LOG(info) << "Found " << (*factory)->type;
  1796. auto type_name = to_string((*factory)->type);
  1797. node_factories[type_name] = new ExtNodeFactory(version, *factory, io_context, outer, &thalamus_api);
  1798. ++factory;
  1799. }
  1800. }
  1801. using namespace std::placeholders;
  1802. auto i = node_factories.begin();
  1803. while (i != node_factories.end()) {
  1804. if (!i->second->prepare()) {
  1805. i = node_factories.erase(i);
  1806. } else {
  1807. ++i;
  1808. }
  1809. }
  1810. nodes_connection = nodes->changed.connect(std::bind(&Impl::on_nodes, this, _1, _2, _3));
  1811. }
  1812. ~Impl() {
  1813. nodes_connection.disconnect();
  1814. node_connections.clear();
  1815. node_impls.clear();
  1816. auto i = node_factories.begin();
  1817. while (i != node_factories.end()) {
  1818. i->second->cleanup();
  1819. delete i->second;
  1820. ++i;
  1821. }
  1822. for(auto& ext : extension) {
  1823. auto thalamus_teardown = ext.load<thalamus_teardown_t>("thalamus_teardown");
  1824. if(thalamus_teardown) {
  1825. thalamus_teardown();
  1826. }
  1827. }
  1828. delete ThalamusAPIImpl::cpp_to_c;
  1829. delete ThalamusAPIImpl::c_to_cpp;
  1830. }
  1831. void clean_signals() {
  1832. for (auto i = signals.begin(); i != signals.end();) {
  1833. if (i->second.empty()) {
  1834. i = signals.erase(i);
  1835. } else {
  1836. ++i;
  1837. }
  1838. }
  1839. }
  1840. void on_nodes(ObservableCollection::Action a,
  1841. const ObservableCollection::Key &k,
  1842. const ObservableCollection::Value &v) {
  1843. using namespace std::placeholders;
  1844. if (a == ObservableCollection::Action::Set) {
  1845. auto index = std::get<int64_t>(k);
  1846. ObservableDictPtr node = std::get<ObservableDictPtr>(v);
  1847. boost::signals2::scoped_connection conn = node->changed.connect(
  1848. std::bind(&Impl::on_node, this, node.get(), _1, _2, _3));
  1849. std::string type_str = node->at("type");
  1850. auto factory = node_factories.at(type_str);
  1851. creating_index = int32_t(index);
  1852. auto node_impl =
  1853. std::shared_ptr<Node>(factory->create(node, io_context, outer));
  1854. creating_index = -1;
  1855. node_connections.insert(node_connections.begin() + index, std::move(conn));
  1856. node_next_type.insert(node_next_type.begin() + index, "");
  1857. node_impls.insert(node_impls.begin() + index, node_impl);
  1858. node_types.insert(node_types.begin() + index, type_str);
  1859. node_configs.insert(node_configs.begin() + index, node);
  1860. node->recap(std::bind(&Impl::on_node, this, node.get(), _1, _2, _3));
  1861. } else {
  1862. auto index = std::get<int64_t>(k);
  1863. node_connections.erase(node_connections.begin() + index);
  1864. node_next_type.erase(node_next_type.begin() + index);
  1865. node_impls.erase(node_impls.begin() + index);
  1866. node_types.erase(node_types.begin() + index);
  1867. node_configs.erase(node_configs.begin() + index);
  1868. }
  1869. }
  1870. void notify(std::function<bool(const thalamus_grpc::NodeSelector &)> selector,
  1871. std::weak_ptr<Node> node_impl) {
  1872. for (auto i = callbacks.begin(); i != callbacks.end();) {
  1873. if (selector(i->first)) {
  1874. i->second(std::weak_ptr<Node>(node_impl));
  1875. i = callbacks.erase(i);
  1876. } else {
  1877. ++i;
  1878. }
  1879. }
  1880. for (auto i = signals.begin(); i != signals.end();) {
  1881. if (selector(i->first)) {
  1882. i->second(std::weak_ptr<Node>(node_impl));
  1883. i = signals.erase(i);
  1884. } else if (i->second.empty()) {
  1885. i = signals.erase(i);
  1886. } else {
  1887. ++i;
  1888. }
  1889. }
  1890. }
  1891. void on_node(ObservableDict *node, ObservableCollection::Action a,
  1892. const ObservableCollection::Key &k,
  1893. const ObservableCollection::Value &v) {
  1894. if (a == ObservableCollection::Action::Set) {
  1895. auto node_index = 0;
  1896. ObservableDictPtr shared_node;
  1897. for (auto i = 0u; i < nodes->size(); ++i) {
  1898. shared_node = nodes->at(i);
  1899. if (node == shared_node.get()) {
  1900. node_index = int(i);
  1901. break;
  1902. }
  1903. }
  1904. if(creating_index == node_index) {
  1905. return;
  1906. }
  1907. auto key_str = std::get<std::string>(k);
  1908. if (key_str == "type") {
  1909. auto value_str = std::get<std::string>(v);
  1910. if (value_str != node_types.at(size_t(node_index))) {
  1911. auto current_node = node_impls.at(size_t(node_index));
  1912. auto current_next_type = node_next_type[size_t(node_index)];
  1913. if(!current_next_type.empty()) {
  1914. node_next_type[size_t(node_index)] = value_str;
  1915. } else {
  1916. node_next_type[size_t(node_index)] = value_str;
  1917. current_node->predrop([this,current_node] {
  1918. boost::asio::post(io_context, [this, current_node] {
  1919. size_t new_node_index = std::numeric_limits<size_t>::max();
  1920. for(size_t i = 0;i < node_impls.size();++i) {
  1921. if(node_impls[i] == current_node) {
  1922. new_node_index = i;
  1923. break;
  1924. }
  1925. }
  1926. THALAMUS_ASSERT(new_node_index != std::numeric_limits<size_t>::max(), "Node is missing");
  1927. auto node_config = node_configs[new_node_index];
  1928. auto type_str = node_next_type[new_node_index];
  1929. auto factory = node_factories.at(type_str);
  1930. node_types.at(new_node_index) = type_str;
  1931. creating_index = int(new_node_index);
  1932. node_impls.at(new_node_index)
  1933. .reset(factory->create(node_config, io_context, outer));
  1934. creating_index = -1;
  1935. node_next_type[new_node_index] = "";
  1936. auto node_impl = node_impls.at(new_node_index);
  1937. notify([&type_str](
  1938. auto &selector) { return selector.type() == type_str; },
  1939. node_impl);
  1940. });
  1941. });
  1942. }
  1943. } else {
  1944. auto node_impl = node_impls.at(size_t(node_index));
  1945. notify([&value_str](
  1946. auto &selector) { return selector.type() == value_str; },
  1947. node_impl);
  1948. }
  1949. } else if (key_str == "name") {
  1950. auto node_impl = node_impls.at(size_t(node_index));
  1951. auto value_str = std::get<std::string>(v);
  1952. notify([&value_str](
  1953. auto &selector) { return selector.name() == value_str; },
  1954. node_impl);
  1955. }
  1956. }
  1957. }
  1958. };
  1959. NodeGraphImpl::NodeGraphImpl(ObservableListPtr nodes,
  1960. boost::asio::io_context &io_context,
  1961. std::chrono::system_clock::time_point system_time,
  1962. std::chrono::steady_clock::time_point steady_time,
  1963. thalamus_grpc::Thalamus::Stub* stub,
  1964. std::vector<SharedLibrary>& extension,
  1965. Vulkan vulkan,
  1966. std::optional<int> thread_policy,
  1967. std::optional<int> thread_priority)
  1968. : impl(new Impl(nodes, io_context, this, system_time, steady_time, stub, extension, vulkan)) {
  1969. impl->nodes->recap();
  1970. impl->thread_pool.start(thread_policy, thread_priority);
  1971. }
  1972. NodeGraphImpl::~NodeGraphImpl() {}
  1973. std::optional<std::string>
  1974. NodeGraphImpl::get_type_name(const std::string &type) {
  1975. auto i = impl->node_factories.find(type);
  1976. if (i != impl->node_factories.end()) {
  1977. return i->second->type_name();
  1978. } else {
  1979. return std::nullopt;
  1980. }
  1981. }
  1982. void NodeGraphImpl::set_service(Service *service) { impl->service = service; }
  1983. Service &NodeGraphImpl::get_service() { return **impl->service; }
  1984. std::weak_ptr<Node> NodeGraphImpl::get_node(const std::string &query_name) {
  1985. thalamus_grpc::NodeSelector selector;
  1986. selector.set_name(query_name);
  1987. return get_node(selector);
  1988. }
  1989. std::weak_ptr<Node>
  1990. NodeGraphImpl::get_node(const thalamus_grpc::NodeSelector &query_name) {
  1991. std::string key;
  1992. std::string query;
  1993. if (!query_name.name().empty()) {
  1994. key = "name";
  1995. query = query_name.name();
  1996. } else {
  1997. key = "type";
  1998. query = query_name.type();
  1999. }
  2000. for (auto i = 0u; i < impl->nodes->size(); ++i) {
  2001. ObservableDictPtr node = impl->nodes->at(i);
  2002. std::string value = node->at(key);
  2003. if (query == value) {
  2004. return std::weak_ptr<Node>(impl->node_impls.at(i));
  2005. }
  2006. }
  2007. return std::weak_ptr<Node>();
  2008. }
  2009. void NodeGraphImpl::get_node(
  2010. const std::string &query_name,
  2011. std::function<void(std::weak_ptr<Node>)> callback) {
  2012. thalamus_grpc::NodeSelector selector;
  2013. selector.set_name(query_name);
  2014. return get_node(selector, callback);
  2015. }
  2016. void NodeGraphImpl::get_node(
  2017. const thalamus_grpc::NodeSelector &query_name,
  2018. std::function<void(std::weak_ptr<Node>)> callback) {
  2019. auto value = get_node(query_name);
  2020. if (!value.lock()) {
  2021. impl->callbacks.emplace_back(query_name, callback);
  2022. } else {
  2023. impl->io_context.post([callback,value] {
  2024. callback(value);
  2025. });
  2026. }
  2027. }
  2028. NodeGraph::NodeConnection NodeGraphImpl::get_node_scoped(
  2029. const std::string &name,
  2030. std::function<void(std::weak_ptr<Node>)> callback) {
  2031. thalamus_grpc::NodeSelector selector;
  2032. selector.set_name(name);
  2033. return get_node_scoped(selector, callback);
  2034. }
  2035. NodeGraph::NodeConnection NodeGraphImpl::get_node_scoped(
  2036. const thalamus_grpc::NodeSelector &selector,
  2037. std::function<void(std::weak_ptr<Node>)> callback) {
  2038. auto value = get_node(selector);
  2039. if (!value.lock()) {
  2040. impl->signals.emplace_back(
  2041. selector, boost::signals2::signal<void(std::weak_ptr<Node>)>());
  2042. boost::signals2::scoped_connection connection(
  2043. impl->signals.back().second.connect(callback));
  2044. return connection;
  2045. } else {
  2046. impl->io_context.post([callback,value] {
  2047. callback(value);
  2048. });
  2049. return NodeConnection();
  2050. }
  2051. }
  2052. std::shared_ptr<grpc::Channel>
  2053. NodeGraphImpl::get_channel(const std::string &url) {
  2054. std::vector<std::string> tokens = absl::StrSplit(url, ':');
  2055. int port;
  2056. bool parsed = absl::SimpleAtoi(tokens.back(), &port);
  2057. if(!parsed) {
  2058. return get_channel(url + ":50050");
  2059. }
  2060. if (!impl->channels.contains(url) || !impl->channels[url].lock()) {
  2061. auto channel = create_grpc_channel(url);
  2062. impl->channels[url] = channel;
  2063. return channel;
  2064. }
  2065. return impl->channels[url].lock();
  2066. }
  2067. thalamus_grpc::Thalamus::Stub*
  2068. NodeGraphImpl::get_thalamus_stub(const std::string &url) {
  2069. if (!impl->stubs.contains(url)) {
  2070. auto channel = get_channel(url);
  2071. auto stub = thalamus_grpc::Thalamus::NewStub(channel);
  2072. impl->stubs[url] = std::move(stub);
  2073. }
  2074. return impl->stubs[url].get();
  2075. }
  2076. std::chrono::system_clock::time_point
  2077. NodeGraphImpl::get_system_clock_at_start() {
  2078. return impl->system_time;
  2079. }
  2080. std::chrono::steady_clock::time_point
  2081. NodeGraphImpl::get_steady_clock_at_start() {
  2082. return impl->steady_time;
  2083. }
  2084. ThreadPool &NodeGraphImpl::get_thread_pool() { return impl->thread_pool; }
  2085. void NodeGraphImpl::dialog(const thalamus_grpc::Dialog &dialog) {
  2086. auto context = std::make_shared<grpc::ClientContext>();
  2087. auto request = std::make_shared<thalamus_grpc::Dialog>(dialog);
  2088. auto response = std::make_shared<thalamus_grpc::Empty>();
  2089. impl->stub->async()->dialog(context.get(), request.get(), response.get(),
  2090. [moved_context=context,moved_request=request,moved_response=response](grpc::Status s) {
  2091. THALAMUS_LOG(info) << "Dialog complete " << s.error_message();
  2092. });
  2093. }
  2094. void NodeGraphImpl::log(const thalamus_grpc::Text & text) {
  2095. (*impl->service)->log_signal(text);
  2096. }
  2097. ObservableDictPtr NodeGraphImpl::get_node_state(Node* node) {
  2098. for(size_t i = 0;i < impl->node_impls.size();++i) {
  2099. if(impl->node_impls[i].get() == node) {
  2100. return impl->node_configs[i];
  2101. }
  2102. }
  2103. THALAMUS_ABORT("Failed to find node.");
  2104. }
  2105. VkDevice NodeGraphImpl::get_vulkan_device() {
  2106. return impl->vulkan.device;
  2107. }
  2108. VkInstance NodeGraphImpl::get_vulkan_instance() {
  2109. return impl->vulkan.instance;
  2110. }
  2111. VkPhysicalDevice NodeGraphImpl::get_vulkan_physical_device() {
  2112. return impl->vulkan.physical_device;
  2113. }
  2114. VkQueue NodeGraphImpl::get_vulkan_queue() {
  2115. return impl->vulkan.queue;
  2116. }
  2117. std::unique_lock<std::mutex> NodeGraphImpl::lock_vulkan_queue() {
  2118. return std::unique_lock<std::mutex>(impl->vulkan_mutex);
  2119. }
  2120. VkCommandPool NodeGraphImpl::create_vulkan_command_pool() {
  2121. VkCommandPoolCreateInfo cp_ci{};
  2122. cp_ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
  2123. cp_ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
  2124. cp_ci.queueFamilyIndex = impl->vulkan.queue_family_index;
  2125. VkCommandPool result;
  2126. auto success = vkCreateCommandPool(impl->vulkan.device, &cp_ci, nullptr, &result);
  2127. THALAMUS_ASSERT(success == VK_SUCCESS, "vkCreateCommandPool failed");
  2128. return result;
  2129. }
  2130. void NodeGraphImpl::predrop(std::function<void()> ready) {
  2131. auto drop_count = std::make_shared<size_t>(impl->node_impls.size());
  2132. if (*drop_count == 0) {
  2133. ready();
  2134. return;
  2135. }
  2136. for(auto& node : impl->node_impls) {
  2137. node->predrop([ready,drop_count,this] {
  2138. boost::asio::post(impl->io_context, [ready,drop_count] {
  2139. --*drop_count;
  2140. if(*drop_count == 0) {
  2141. ready();
  2142. }
  2143. });
  2144. });
  2145. }
  2146. }
  2147. } // namespace thalamus

node_graph_impl.cpp at commit 4c0bcd7, under GPL-3.0 · at the source

Overview

Authors: Jarl Haggerty1, Qasim Qureshi1, Ellie D Gabriel1, Pedro GLB Borges1, Pierce Davis1, Katie Wingel1, Jerry Cai1, Krishna Sargur2, Min Jae Kim1, Agrita Dubey1, Indie Garwood1, Alex Vaz1, Andrew G Richardson1, Han-Chiao Isaac Chen1,3, Lauren H Hammer2,4, Joshua Gold5, Brian Litt1,2,4, Daniel Yoshor1, Michael Beauchamp1, Casey Halpern1,3, Bijan Pesaran1,2,5, Iahn Cajigas1,2
  1. Department of Neurosurgery, Perelman School of Medicine, University of Pennsylvania, Philadelphia, PA USA
  2. Department of Bioengineering, University of Pennsylvania, Philadelphia, PA USA
  3. Department of Surgery, Corporal Michael J. Crescenz Veterans Affairs Medical Center, Philadelphia, PA USA
  4. Department of Neurology, University of Pennsylvania, Philadelphia, PA USA
  5. Department of Neuroscience, University of Pennsylvania, Philadelphia, PA USA
Institutions: University of Pennsylvania (United States); Philadelphia VA Medical Center (United States)
Journal: Communications engineering, volume 5, issue 1, article 93
Dates: received 1 October 2024; accepted 11 March 2026; published online 26 March 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s44172-026-00646-z · PMID 41882345 · PMCID PMC13184004 · OpenAlex W7140605488
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Methods: Spectral & time-frequency, Connectivity, Preprocessing, Statistics, Machine learning
Keywords: Biomedical engineering, Physiology, Neuronal physiology
Topic: EEG and Brain-Computer Interfaces (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: U.S. Department of Health &amp; Human Services | National Institutes of Health (5K12NS129164-02); U.S. Department of Health & Human Services | National Institutes of Health (NIH) (5K12NS129164-02); NINDS NIH HHS (K23 NS138692)
Citations: not cited yet (Europe PMC); 56 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

Its files are read in the Code ↔ Paper reader above, with 7 matches between paragraphs and lines of code.

cajigaslab/Thalamus

License: GPL-3.0
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 4c0bcd7e6b9a9a5b209e97973960d8909a75bc79, 29 September 2026
Languages: Python (128), C++ (77), C/C++ (15), MATLAB (10), Shell (6), Java (2), JavaScript (2), NEURON (1), Go (1)
Size: 568 files, 242 scripts
Software Heritage: not archived
Found in: the text, “Dependency management”
Holds: README, license file, environment (pyproject.toml, requirements-ci.txt, requirements-no-native.txt, requirements-ubuntu-18.04.txt, requirements.txt, docker/ubuntu-18.04.dockerfile, docker/uploader.dockerfile, docker/windows.dockerfile), tests, continuous integration, documentation, 2 notebooks
Not found: CITATION.cff
Tools: NumPy (37 files), Matplotlib (6 files), SciPy (6 files), OpenCV (2 files), pandas (2 files), h5py (1 file), Signal Processing Toolbox (1 file), Pillow (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
244 files

cajigaslab

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: the link answers
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 30 September 2026: the link answers (HTTP 200)
  • 30 September 2026: the link answers (HTTP 200)
At the source: github.com/cajigaslab

Code availability statement

The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s44172-026-00646-z.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 242 scripts, each with its path and the digest of its content;
  • 7 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data availability statement

The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

  • no repository, dataset or request procedure was recognized in it

Read it in the paper: doi.org/10.1038/s44172-026-00646-z.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 22 authors, 3 keywords, 3 funders, 23 references.

Cite

This paper

Haggerty, J., Qureshi, Q., Gabriel, E. D., Borges, P. G., Davis, P., Wingel, K., Cai, J., Sargur, K., Kim, M. J., Dubey, A., Garwood, I., Vaz, A., Richardson, A. G., Chen, H.-C. I., Hammer, L. H., Gold, J., Litt, B., Yoshor, D., Beauchamp, M., . . . Cajigas, I. (2026). Thalamus: a real-time system for synchronized, closed-loop multimodal behavioral and electrophysiological data capture. Communications engineering, 5(1), 93. https://doi.org/10.1038/s44172-026-00646-z

BibTeX

@article{haggerty2026thalamus,
author = {Haggerty, Jarl and Qureshi, Qasim and Gabriel, Ellie D and Borges, Pedro GLB and Davis, Pierce and Wingel, Katie and Cai, Jerry and Sargur, Krishna and Kim, Min Jae and Dubey, Agrita and Garwood, Indie and Vaz, Alex and Richardson, Andrew G and Chen, Han-Chiao Isaac and Hammer, Lauren H and Gold, Joshua and Litt, Brian and Yoshor, Daniel and Beauchamp, Michael and Halpern, Casey and Pesaran, Bijan and Cajigas, Iahn},
title = {{Thalamus: a real-time system for synchronized, closed-loop multimodal behavioral and electrophysiological data capture}},
journal = {Communications engineering},
year = {2026},
month = mar,
volume = {5},
number = {1},
pages = {93},
publisher = {Springer Science+Business Media},
issn = {2731-3395},
doi = {10.1038/s44172-026-00646-z},
url = {https://doi.org/10.1038/s44172-026-00646-z},
pmid = {41882345},
pmcid = {PMC13184004}
}

RIS

TY - JOUR
AU - Haggerty, Jarl
AU - Qureshi, Qasim
AU - Gabriel, Ellie D
AU - Borges, Pedro GLB
AU - Davis, Pierce
AU - Wingel, Katie
AU - Cai, Jerry
AU - Sargur, Krishna
AU - Kim, Min Jae
AU - Dubey, Agrita
AU - Garwood, Indie
AU - Vaz, Alex
AU - Richardson, Andrew G
AU - Chen, Han-Chiao Isaac
AU - Hammer, Lauren H
AU - Gold, Joshua
AU - Litt, Brian
AU - Yoshor, Daniel
AU - Beauchamp, Michael
AU - Halpern, Casey
AU - Pesaran, Bijan
AU - Cajigas, Iahn
TI - Thalamus: a real-time system for synchronized, closed-loop multimodal behavioral and electrophysiological data capture
T2 - Communications engineering
J2 - Commun Eng
PY - 2026
DA - 2026/03/26
VL - 5
IS - 1
SP - 93
SN - 2731-3395
PB - Springer Science+Business Media
DO - 10.1038/s44172-026-00646-z
UR - https://doi.org/10.1038/s44172-026-00646-z
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s44172-026-00646-z",
"type": "article-journal",
"title": "Thalamus: a real-time system for synchronized, closed-loop multimodal behavioral and electrophysiological data capture",
"container-title": "Communications engineering",
"author": [
{
"family": "Haggerty",
"given": "Jarl"
},
{
"family": "Qureshi",
"given": "Qasim"
},
{
"family": "Gabriel",
"given": "Ellie D"
},
{
"family": "Borges",
"given": "Pedro GLB"
},
{
"family": "Davis",
"given": "Pierce"
},
{
"family": "Wingel",
"given": "Katie"
},
{
"family": "Cai",
"given": "Jerry"
},
{
"family": "Sargur",
"given": "Krishna"
},
{
"family": "Kim",
"given": "Min Jae"
},
{
"family": "Dubey",
"given": "Agrita"
},
{
"family": "Garwood",
"given": "Indie"
},
{
"family": "Vaz",
"given": "Alex"
},
{
"family": "Richardson",
"given": "Andrew G"
},
{
"family": "Chen",
"given": "Han-Chiao Isaac"
},
{
"family": "Hammer",
"given": "Lauren H"
},
{
"family": "Gold",
"given": "Joshua"
},
{
"family": "Litt",
"given": "Brian"
},
{
"family": "Yoshor",
"given": "Daniel"
},
{
"family": "Beauchamp",
"given": "Michael"
},
{
"family": "Halpern",
"given": "Casey"
},
{
"family": "Pesaran",
"given": "Bijan"
},
{
"family": "Cajigas",
"given": "Iahn"
}
],
"container-title-short": "Commun Eng",
"volume": "5",
"issue": "1",
"page": "93",
"DOI": "10.1038/s44172-026-00646-z",
"PMID": "41882345",
"PMCID": "PMC13184004",
"ISSN": "2731-3395",
"publisher": "Springer Science+Business Media",
"URL": "https://doi.org/10.1038/s44172-026-00646-z",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
26
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s42003-025-07516-y [code]
Human response times are governed by dual anticipatory processes with distinct neural signatures.
Journal: Communications biology
In common: pandas, SciPy, Matplotlib, 1 other tool, 2 authors
[2] doi:10.1038/s41591-025-04035-5 [code]
Brain activity associated with breakthrough food preoccupation in an individual on tirzepatide.
Journal: Nature medicine
In common: Signal Processing Toolbox, 2 authors
[3] doi:10.1093/braincomms/fcae320 [code]
The sixth sense: how much does interictal intracranial EEG add to determining the focality of epileptic networks?
Journal: Brain communications
In common: pandas, SciPy, Matplotlib, 1 other tool, author Iahn Cajigas
[4] doi:10.1371/journal.pone.0344600 [code]
Robust disease prognosis via diagnostic knowledge preservation: A sequential learning approach.
Journal: PloS one
In common: OpenCV, h5py, Pillow, 4 other tools, 1 reference
[5] doi: [code]
Real-time closed-loop feedback system for mouse mesoscale cortical signal and movement control
Journal: eLife
In common: OpenCV, h5py, Pillow, 4 other tools, 1 reference
[6] doi:10.7554/elife.108663 [code]
The Crunchometer, a low-cost, open-source acoustic analysis of feeding microstructure.
Journal: eLife
In common: OpenCV, h5py, Signal Processing Toolbox, 4 other tools, 1 reference
[7] doi:10.7554/elife.100258 [code]
Normative evidence weighing and accumulation in correlated environments.
Journal: eLife
In common: pandas, SciPy, Matplotlib, 1 other tool, author Joshua I Gold
[8] doi:10.1038/s41598-025-14693-7 [code]
RpiBeh offers a versatile open source solution for rodent behavior tracking and closed loop interventions.
Journal: Scientific reports
In common: OpenCV, h5py, Pillow, 3 other tools, 1 reference
[9] doi:10.1016/j.ynirp.2023.100164 [code]
Neural processing of goal and non-goal-directed movements on the smartphone.
Journal: Neuroimage. Reports
In common: h5py, Signal Processing Toolbox, pandas, 2 other tools, 2 references
[10] doi:10.1038/s41597-023-02614-0 [code]
A comparison of neuroelectrophysiology databases.
Journal: Scientific data
In common: SciPy, NumPy, author Michael Beauchamp

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1038/s44172-026-00646-z.svg)](https://openscicode.org/badge/doi/10.1038/s44172-026-00646-z)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1038/s44172-026-00646-z.svg
   :target: https://openscicode.org/badge/doi/10.1038/s44172-026-00646-z
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1038/s44172-026-00646-z"><img src="https://openscicode.org/badge/doi/10.1038/s44172-026-00646-z.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.