ruble 0.0.3.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.gdbinit +21 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +96 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +6 -0
  7. data/CMakeLists.txt +4 -0
  8. data/CODE_OF_CONDUCT.md +84 -0
  9. data/Gemfile +21 -0
  10. data/Gemfile.lock +98 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +63 -0
  13. data/Rakefile +41 -0
  14. data/ext/ruble/.gitignore +5 -0
  15. data/ext/ruble/CMakeLists.txt +157 -0
  16. data/ext/ruble/RuBLEHelpers.cmake +240 -0
  17. data/ext/ruble/bindings/Adapter.cpp +193 -0
  18. data/ext/ruble/bindings/Adapter.hpp +85 -0
  19. data/ext/ruble/bindings/Characteristic.cpp +171 -0
  20. data/ext/ruble/bindings/Characteristic.hpp +132 -0
  21. data/ext/ruble/bindings/Descriptor.cpp +34 -0
  22. data/ext/ruble/bindings/Descriptor.hpp +69 -0
  23. data/ext/ruble/bindings/Peripheral.cpp +212 -0
  24. data/ext/ruble/bindings/Peripheral.hpp +108 -0
  25. data/ext/ruble/bindings/RuBLE.cpp +115 -0
  26. data/ext/ruble/bindings/Service.cpp +112 -0
  27. data/ext/ruble/bindings/Service.hpp +61 -0
  28. data/ext/ruble/bindings/common.hpp +48 -0
  29. data/ext/ruble/bindings/globals.cpp +43 -0
  30. data/ext/ruble/cmake.mk +62 -0
  31. data/ext/ruble/concerns/CharacteristicValueTracker.cpp +18 -0
  32. data/ext/ruble/concerns/CharacteristicValueTracker.hpp +40 -0
  33. data/ext/ruble/concerns/Rubyable.cpp +4 -0
  34. data/ext/ruble/concerns/Rubyable.hpp +46 -0
  35. data/ext/ruble/config.h.in +25 -0
  36. data/ext/ruble/containers/ByteArray.cpp +64 -0
  37. data/ext/ruble/containers/ByteArray.hpp +161 -0
  38. data/ext/ruble/containers/Callback.hpp +52 -0
  39. data/ext/ruble/containers/NamedBitSet.hpp +140 -0
  40. data/ext/ruble/containers/NamedBitSet.ipp +71 -0
  41. data/ext/ruble/extconf.rb +30 -0
  42. data/ext/ruble/management/Registry.cpp +63 -0
  43. data/ext/ruble/management/Registry.hpp +170 -0
  44. data/ext/ruble/management/RegistryFactory.hpp +113 -0
  45. data/ext/ruble/management/RubyQueue.cpp +152 -0
  46. data/ext/ruble/management/RubyQueue.hpp +69 -0
  47. data/ext/ruble/modularize.diff +28 -0
  48. data/ext/ruble/types/SimpleBLE.hpp +21 -0
  49. data/ext/ruble/types/declarations.hpp +91 -0
  50. data/ext/ruble/types/helpers.hpp +12 -0
  51. data/ext/ruble/types/ruby.hpp +36 -0
  52. data/ext/ruble/types/stl.hpp +41 -0
  53. data/ext/ruble/utils/RubyCallbackTraits.cpp +28 -0
  54. data/ext/ruble/utils/RubyCallbackTraits.hpp +48 -0
  55. data/ext/ruble/utils/async.cpp +10 -0
  56. data/ext/ruble/utils/async.hpp +76 -0
  57. data/ext/ruble/utils/containers.hpp +41 -0
  58. data/ext/ruble/utils/exception_handling.cpp +50 -0
  59. data/ext/ruble/utils/exception_handling.hpp +53 -0
  60. data/ext/ruble/utils/garbage_collection.cpp +82 -0
  61. data/ext/ruble/utils/garbage_collection.hpp +22 -0
  62. data/ext/ruble/utils/hash.cpp +83 -0
  63. data/ext/ruble/utils/hash.hpp +52 -0
  64. data/ext/ruble/utils/hexadecimal.hpp +116 -0
  65. data/ext/ruble/utils/human_type_names.hpp +38 -0
  66. data/ext/ruble/utils/inspection.cpp +24 -0
  67. data/ext/ruble/utils/inspection.hpp +108 -0
  68. data/ext/ruble/utils/ruby.hpp +103 -0
  69. data/ext/ruble/utils/ruby_context.hpp +73 -0
  70. data/lib/ruble/build/.rubocop.yml +19 -0
  71. data/lib/ruble/build/boost.rb +34 -0
  72. data/lib/ruble/build/cmake.rb +134 -0
  73. data/lib/ruble/build/core_ext.rb +5 -0
  74. data/lib/ruble/build/data/bundler.rb +24 -0
  75. data/lib/ruble/build/data/extension.rb +101 -0
  76. data/lib/ruble/build/data/os.rb +21 -0
  77. data/lib/ruble/build/data/rice.rb +24 -0
  78. data/lib/ruble/build/data.rb +22 -0
  79. data/lib/ruble/build/extconf.rb +76 -0
  80. data/lib/ruble/build/github_repo.rb +129 -0
  81. data/lib/ruble/build/simpleble.rb +56 -0
  82. data/lib/ruble/build.rb +28 -0
  83. data/lib/ruble/version.rb +7 -0
  84. data/lib/ruble.rb +46 -0
  85. data/lib/tasks/dev/dev_tasks.rb +130 -0
  86. data/lib/tasks/dev/pager.rb +218 -0
  87. data/lib/tasks/dev/paths.rb +30 -0
  88. data/lib/tasks/dev/state_hash.rb +65 -0
  89. data/lib/tasks/dev.rake +41 -0
  90. data/lib/tasks/simpleble.rake +29 -0
  91. data/sig/rubble.rbs +4 -0
  92. metadata +263 -0
@@ -0,0 +1,170 @@
1
+ #pragma once
2
+
3
+ #include <memory>
4
+ #include <iostream>
5
+ #include <ranges>
6
+
7
+ #include "types/declarations.hpp"
8
+ #include "types/helpers.hpp"
9
+ #include "types/ruby.hpp"
10
+ #include "utils/ruby_context.hpp"
11
+ #include "utils/human_type_names.hpp"
12
+ #include "utils/garbage_collection.hpp"
13
+
14
+ namespace ranges = std::ranges;
15
+ namespace views = ranges::views;
16
+
17
+ namespace RuBLE {
18
+ template<typename Key, class ProxyClass, class Value>
19
+ class Registry {
20
+ VALUE _self = Qnil;
21
+ std::shared_ptr<std::mutex> _mtx = nullptr;
22
+ public:
23
+ using Self [[maybe_unused]] = Registry<Key, ProxyClass, Value>;
24
+ using Owner = ProxyClass::Owner;
25
+ using Factory = RegistryFactory<Self>;
26
+ using KeyType = Key;
27
+ using ProxyClassType = ProxyClass;
28
+ using ProxyRubyObjType = ToRubyObjectResult<ProxyClass>;
29
+ using ValueType = Value;
30
+ using DataObject = Data_Object<ProxyClass>;
31
+ using Registry_DO = Data_Object<Self>;
32
+ using ProxyPtr = std::shared_ptr<ProxyClass>;
33
+ // using ProxyRefVector = std::vector<ProxyRef>;
34
+ using ProxyPtrVector = std::vector<ProxyPtr>;
35
+ using Collection = std::map<Key, ProxyPtr>;
36
+
37
+ using ForEachFn = std::function<void(typename Collection::mapped_type &)>;
38
+ using ForEachFnConst = std::function<void(typename Collection::mapped_type &)>;
39
+ static constexpr const bool is_owned = !std::is_same_v<Owner, nullptr_t>;
40
+
41
+ void ruby_mark() const {
42
+ rb_gc_mark(_self);
43
+ Rice::ruby_mark<Collection>(_registry.get());
44
+ for_each([](const ProxyPtr &ref) -> void {
45
+ Rice::ruby_mark<ProxyClass>(ref.get());
46
+ });
47
+ }
48
+
49
+ private:
50
+ Owner *_owner;
51
+ mutable std::shared_ptr<Collection> _registry;
52
+ protected:
53
+ explicit Registry(Owner *owner) :
54
+ _owner(owner),
55
+ _registry(std::make_shared<Collection>()),
56
+ _mtx(std::make_shared<std::mutex>()),
57
+ _self(Registry_DO(*this)) {
58
+ // std::cout << "Constructing new " << RuBLE::human_type_name<ProxyClass>() << " registry at address ";
59
+ // std::cout << to_hex_addr(this);
60
+ // if constexpr (is_owned) std::cout << " owned by " << human_type_name(*_owner); // _owner->to_s();
61
+ // std::cout << std::endl;
62
+ }
63
+ // FIXME: why am I getting a warning that _self is uninitialized with the constructor below?
64
+ Registry() requires std::is_same_v<Owner, nullptr_t> : Registry(nullptr) {}
65
+
66
+ Object self() const { return _self; }
67
+ constexpr const Owner *owner() const { return _owner; }
68
+ constexpr Owner *owner() { return _owner; }
69
+
70
+ [[maybe_unused]] Key key_from_value(const Value &) const {
71
+ std::cerr << "Type of class: " << RuBLE::Utils::human_type_name(*this) << std::endl;
72
+ throw std::invalid_argument(
73
+ "key_from_value not implemented without .address() returning a BluetoothAddress");
74
+ }
75
+
76
+ [[maybe_unused]] BluetoothAddress key_from_value(const Value &v) const requires BluetoothAddressable<Value> {
77
+ return const_cast<Value &>(v).address();
78
+ }
79
+
80
+ [[maybe_unused]] BluetoothUUID key_from_value(const Value &v) const requires UUIDable<Value> {
81
+ return const_cast<Value &>(v).uuid();
82
+ }
83
+
84
+ public:
85
+ Collection::size_type size() const { return _registry->size(); }
86
+ bool contains(const auto &key) const { return _registry->contains(key); }
87
+ ProxyPtr &operator[](const Key &addr) { return _registry->at(addr); }
88
+ const ProxyPtr &operator[](const Key &addr) const { return _registry->at(addr); }
89
+ const ProxyPtr &at(const Key &addr) const { return _registry->at(addr); }
90
+ ProxyPtr &at(const Key &addr) { return _registry->at(addr); }
91
+ constexpr const Collection &data() const;
92
+
93
+ ProxyPtr fetch(const Value &value) const {
94
+ const std::lock_guard<std::mutex> lock(*_mtx); // needed to avoid race condition
95
+
96
+ Key key = key_from_value(value);
97
+ typename decltype(_registry)::element_type::iterator found = _registry->find(key);
98
+ if (found != _registry->end()) return found->second;
99
+
100
+ if (rb_during_gc()) {
101
+ std::ostringstream oss;
102
+ oss << "Warning: attempt to create new " << Utils::human_type_name<ProxyClass>();
103
+ oss << " at key " << key << " during garbage collection.";
104
+ throw std::runtime_error(oss.str());
105
+ }
106
+
107
+ if constexpr (is_owned) {
108
+ _registry->emplace(key, new ProxyClass(value, _owner));
109
+ } else {
110
+ _registry->emplace(key, new ProxyClass(value));
111
+ }
112
+ return _registry->at(key);
113
+ };
114
+
115
+ void clear() {
116
+ const std::lock_guard<std::mutex> lock(*_mtx); // needed to avoid race condition
117
+ _registry->clear();
118
+ }
119
+
120
+ ProxyPtrVector map_to_objects(const std::vector<Value> &unwrappedValues) const {
121
+ ProxyPtrVector result{};
122
+ std::ranges::transform(unwrappedValues, back_inserter(result), [this](const Value &v) -> ProxyPtr {
123
+ return this->fetch(v);
124
+ });
125
+ return result;
126
+ }
127
+
128
+ Rice::Array map_to_ruby_objects(const auto &arg) const requires HasRubyObject<ProxyClass> {
129
+ std::vector<ProxyPtr> proxyVec = map_to_objects(arg);
130
+ auto xform = [](const ProxyPtr &obj) -> Rice::Object { return obj->self(); };
131
+ auto objs = proxyVec | views::transform(xform);
132
+ Rice::Array result(objs.begin(), objs.end());
133
+ return result;
134
+ }
135
+
136
+ void for_each(auto fn) {
137
+ std::ranges::for_each(*_registry, [&fn](typename Collection::value_type &pair) -> void {
138
+ auto &[key, value] = pair;
139
+ fn(value);
140
+ });
141
+ }
142
+
143
+ void for_each(auto fn) const {
144
+ std::ranges::for_each(*_registry, [&fn](const typename Collection::value_type &pair) -> void {
145
+ const auto &[key, value] = pair;
146
+ fn(value);
147
+ });
148
+ }
149
+
150
+ [[nodiscard]] std::string to_s() const {
151
+ std::ostringstream os;
152
+ os << "Registry for " << Utils::human_type_name<ProxyClass>() << " (";
153
+ os << Utils::human_type_name(*this) << "): "; // << getSelf().inspect();
154
+ if constexpr (is_owned) os << " owned by " << _owner->to_s();
155
+ return os.str();
156
+ }
157
+
158
+ friend void Init_Registries();
159
+ template<typename> friend class RegistryFactory;
160
+
161
+ friend class std::hash<Registry<Key, ProxyClass, Value>>;
162
+ };
163
+
164
+ template<typename Key, class ProxyClass, class Value>
165
+ constexpr const Registry<Key, ProxyClass, Value>::Collection &Registry<Key, ProxyClass, Value>::data() const { return *_registry; }
166
+
167
+ extern std::shared_ptr<AdapterRegistry> adapterRegistry;
168
+ }
169
+
170
+
@@ -0,0 +1,113 @@
1
+ #pragma once
2
+
3
+ #include "types/declarations.hpp"
4
+ #include "types/ruby.hpp"
5
+ #include "types/helpers.hpp"
6
+ #include "utils/hash.hpp"
7
+ #include "utils/garbage_collection.hpp"
8
+ #include <unordered_map>
9
+
10
+
11
+ namespace RuBLE {
12
+ // there is always a single singleton instance of this class,
13
+ // but each instance holds a registry of registries (so to speak)
14
+ // the keys of the meta-registry are hashes of Owner
15
+ template<class RegistryType>
16
+ class RegistryFactory {
17
+ public:
18
+ using Self [[maybe_unused]] = RegistryFactory<RegistryType>;
19
+
20
+ using RegistryOwner = RegistryType::Owner;
21
+ using RegistryMapKey = HashResultType<RegistryOwner>;
22
+ using RegistryMapValue = std::shared_ptr<RegistryType>;
23
+ using RegistryMapType = std::unordered_map<RegistryMapKey, RegistryMapValue>;
24
+
25
+ // no moving. only copying
26
+ RegistryFactory();
27
+ RegistryFactory(RegistryFactory &&) = delete;
28
+ RegistryFactory &operator=(RegistryFactory &&) = delete;
29
+ ~RegistryFactory() = default;
30
+
31
+ protected:
32
+ static constexpr HasherType<RegistryOwner> RegistryKeyHasher {};
33
+ inline static std::shared_ptr<Self> _instance {};
34
+ const std::shared_ptr<RegistryMapType> _registries {};
35
+
36
+ public:
37
+ static const std::shared_ptr<RegistryFactory> &instance();
38
+
39
+ const std::shared_ptr<RegistryType> &registry(RegistryOwner *owner) const;
40
+
41
+ const std::shared_ptr<RegistryType> &registry() const requires std::same_as<RegistryOwner, nullptr_t>;
42
+
43
+ template<class... Types>
44
+ [[maybe_unused]] static const std::shared_ptr<RegistryType> &getRegistry(const Types &... args);
45
+
46
+ void ruby_mark() const;
47
+ };
48
+
49
+ template<class RegistryType>
50
+ void RegistryFactory<RegistryType>::ruby_mark() const {
51
+ Rice::ruby_mark(_registries.get());
52
+ static constexpr auto mark_each_value = [](const RegistryMapValue &value) {
53
+ Rice::ruby_mark<RegistryType>(value.get());
54
+ rb_gc_mark(value->_self);
55
+ };
56
+ for (const auto &pair : *_registries) mark_each_value(pair.second);
57
+ }
58
+
59
+ template<class RegistryType>
60
+ template<class... Types>
61
+ const std::shared_ptr<RegistryType> &RegistryFactory<RegistryType>::getRegistry(const Types &... args) {
62
+ return instance()->registry(args...);
63
+ }
64
+
65
+ template<class RegistryType>
66
+ RegistryFactory<RegistryType>::RegistryFactory() : _registries(std::make_shared<RegistryMapType>()) {}
67
+
68
+ template<class RegistryType>
69
+ const std::shared_ptr<RegistryType> &
70
+ RegistryFactory<RegistryType>::registry() const requires std::same_as<RegistryOwner, nullptr_t> {
71
+ return this->registry(nullptr);
72
+ }
73
+
74
+ template<class RegistryType>
75
+ const std::shared_ptr<RegistryType> &RegistryFactory<RegistryType>::registry(RegistryOwner *owner) const {
76
+ RegistryMapKey index;
77
+ if constexpr (RegistryType::is_owned) {
78
+ index = RegistryKeyHasher(*owner);
79
+ } else {
80
+ index = RegistryKeyHasher(nullptr);
81
+ }
82
+ // happy path is registry at index already exists
83
+ if (_registries->contains(index)) return (*_registries)[index];
84
+
85
+ // using new RegistryType instead of std::make_shared allows keeping the constructor private
86
+ // will go out of scope later if try_emplace fails due to race condition
87
+ std::shared_ptr<RegistryType> newRegistry;
88
+ if constexpr (RegistryType::is_owned) {
89
+ newRegistry = std::shared_ptr<RegistryType>(new RegistryType(owner));
90
+ } else {
91
+ newRegistry = std::shared_ptr<RegistryType>(new RegistryType());
92
+ }
93
+ const auto &[registry_it, inserted] = _registries->try_emplace(index, std::move(newRegistry));
94
+ return registry_it->second;
95
+ }
96
+
97
+ template<class RegistryType>
98
+ const std::shared_ptr<RegistryFactory<RegistryType>> &RegistryFactory<RegistryType>::instance() {
99
+ static std::mutex _static_mtx;
100
+ if (!_instance) {
101
+ std::lock_guard<std::mutex> _lock(_static_mtx);
102
+ if (!_instance) _instance = std::shared_ptr<Self>(new Self());
103
+ }
104
+ return _instance;
105
+ }
106
+
107
+ extern std::shared_ptr<AdapterRegistryFactory> adapterRegistryFactory;
108
+ extern std::shared_ptr<PeripheralRegistryFactory> peripheralRegistryFactory;
109
+ extern std::shared_ptr<ServiceRegistryFactory> serviceRegistryFactory;
110
+ extern std::shared_ptr<CharacteristicRegistryFactory> characteristicRegistryFactory;
111
+ }
112
+
113
+
@@ -0,0 +1,152 @@
1
+ #include "management/RubyQueue.hpp"
2
+ #include "utils/ruby_context.hpp"
3
+ #include "utils/async.hpp"
4
+ #include <ruby/thread.h>
5
+
6
+ using namespace std::chrono_literals;
7
+ namespace RuBLE {
8
+ constinit std::shared_ptr<RubyQueue> RubyQueue::_instance {};
9
+
10
+ const std::shared_ptr<RubyQueue> &RubyQueue::instance() {
11
+ if (!_instance) _instance = std::shared_ptr<RubyQueue>(new RubyQueue());
12
+ return _instance;
13
+ }
14
+
15
+ Rice::Object RubyQueue::rb_thread() const {
16
+ return { _rb_thread.load() };
17
+ }
18
+
19
+ void RubyQueue::start() {
20
+ if (!_starting.test_and_set()) {
21
+ // if constexpr (DEBUG) std::cerr << "Starting RubyQueue" << std::endl;
22
+ RbThreadCreateFn<void> runThread = [](void *rubyQueuePtr) -> VALUE {
23
+ reinterpret_cast<RubyQueue*>(rubyQueuePtr)->run();
24
+ return Qnil;
25
+ };
26
+ // TODO: figure out how/if to handle case where thread creation fails
27
+ // TODO: wrap this in rice's `protect` fn
28
+ RubyThreadId tid = rb_thread_create(runThread, this);
29
+ if constexpr (DEBUG) std::cerr << "RubyQueue thread ID: " << tid << std::endl;
30
+ _rb_thread.store(tid);
31
+ }
32
+ }
33
+ void RubyQueue::ensure_started() {
34
+ if (Utils::wait_until([&]{ return _startup_latch.try_acquire(); }, 15s)) {
35
+ // std::cout << "RubyQueue started." << std::endl;
36
+ // std::cerr << "RuBLE queue start ensured." << std::endl;
37
+ return;
38
+ }
39
+
40
+ std::cerr << "RuBLE queue failed to start. Killing it." << std::endl;
41
+ kill();
42
+ throw std::runtime_error("RuBLE queue failed to start. Aborting.");
43
+ }
44
+
45
+ VALUE RubyQueue::run() {
46
+ // TODO: add thread unblocking handlers
47
+ auto loopFn = [](void *rqPtr) -> void * {
48
+ // std::cout << "About to call RubyQueue::loop" << std::endl;
49
+ reinterpret_cast<RubyQueue*>(rqPtr)->loop();
50
+ return nullptr;
51
+ };
52
+ auto ubf = [](void *rqPtr) -> void {
53
+ reinterpret_cast<RubyQueue*>(rqPtr)->stop();
54
+ };
55
+ rb_thread_call_without_gvl(loopFn, this, ubf, this);
56
+ return Qnil;
57
+ }
58
+
59
+ void RubyQueue::loop() {
60
+ std::lock_guard lck(_run_mtx);
61
+
62
+ _thread_id.store(std::this_thread::get_id());
63
+ _startup_latch.release();
64
+ _running.test_and_set();
65
+ // std::cerr << "RuBLE::RubyQueue has started." << std::endl;
66
+
67
+ QueueItemType item;
68
+ while (!_stopping.test()) {
69
+ _sem.acquire();
70
+ if (_stopping.test()) break;
71
+
72
+ {
73
+ std::lock_guard<std::mutex> lock(_mtx);
74
+ if constexpr (DEBUG) assert(!_q->empty());
75
+ item = std::move(_q->back());
76
+ _q->pop_back();
77
+ }
78
+
79
+ rb_thread_call_with_gvl([](void *queueItem) -> void * {
80
+ auto in_ruby_guard = in_ruby.assert_in_ruby_guard();
81
+ (*reinterpret_cast<QueueItemType*>(queueItem))();
82
+ return nullptr;
83
+ }, &item);
84
+ }
85
+ _running.clear();
86
+ // std::cout << "RubyQueue::loop exiting" << std::endl;
87
+ }
88
+
89
+ void RubyQueue::push(QueueItemType fn) {
90
+ if (_stopping.test()) {
91
+ if (DEBUG) {
92
+ std::cerr << "Warning: attempted to push to RuBLE callback queue, " <<
93
+ "but queue has been stopped. Refusing to push." << std::endl;
94
+ std::cerr << "C++ Backtrace:" << std::endl << boost::stacktrace::stacktrace() << std::endl;
95
+ return;
96
+ }
97
+ }
98
+
99
+ {
100
+ std::lock_guard<std::mutex> lck(_mtx);
101
+ _q->emplace_back(std::move(fn));
102
+ }
103
+ _sem.release();
104
+ }
105
+
106
+ void RubyQueue::stop() {
107
+ if (!_stopping.test_and_set()) {
108
+ // std::cerr << "Signalling RubyQueue to stop" << std::endl;
109
+ _sem.release(); // if stop flag wasn't set before, notify cv
110
+ }
111
+ std::this_thread::yield();
112
+ }
113
+
114
+ // This is ensured to be accurate because it only returns true if
115
+ // the _running std::atomic_flag is set AND the _run_mtx can't be acquired
116
+ bool RubyQueue::running() const {
117
+ if (!_running.test()) return false;
118
+
119
+ // using a shared lock here means we don't have to worry about
120
+ // multiple simultaneous calls to RubyQueue::running causing issues
121
+ std::shared_lock<std::shared_mutex> lck(_run_mtx, std::defer_lock);
122
+ // if we were are able to acquire the shared lock, obviously the queue isn't holding it
123
+ // if so, we clear _running, because this means the queue previously set _running,
124
+ // but isn't any longer
125
+ if (lck.try_lock()) _running.clear();
126
+ return !lck;
127
+ }
128
+
129
+ // returns if _stopping was requested AND we're not running
130
+ bool RubyQueue::stopped() const {
131
+ return _stopping.test() && !running();
132
+ }
133
+
134
+ void RubyQueue::stop_on_exit(VALUE) {
135
+ std::shared_ptr<RubyQueue> rq = instance();
136
+
137
+ rq->stop();
138
+ if (rq->stopped()) return;
139
+
140
+ if (Utils::wait_until([&]{ return !rq->running(); }, 1s)) return;
141
+
142
+ std::cerr << "RuBLE::RubyQueue won't exit. Forcing it.." << std::endl;
143
+ rq->kill();
144
+ }
145
+
146
+ void RubyQueue::kill() {
147
+ stop();
148
+ if (rb_thread().test()) rb_thread_kill(rb_thread());
149
+ // TODO: is there any way to join threads? ruby's thread join functions
150
+ // don't seem to be exported (I guess we can use Rice::Object#call)
151
+ }
152
+ }
@@ -0,0 +1,69 @@
1
+ #pragma once
2
+
3
+ #include "containers/Callback.hpp"
4
+ #include "types/stl.hpp"
5
+ #include <deque>
6
+ #include <mutex>
7
+ #include <chrono>
8
+ #include <thread>
9
+ #include <shared_mutex>
10
+
11
+
12
+ namespace RuBLE {
13
+ // All ruby calls must take place in thread known to ruby (i.e. where rb_get_execution_context works)
14
+ // Since SimpleBLE creates its own thread from which callbacks are invoked,
15
+ // we have that (non-ruby) thread add a function to RubyQueue,
16
+ // which is monitored from a ruby thread and invoked immediately invoked with a FIFO ordering
17
+ class RubyQueue {
18
+ public:
19
+ using FnType = std::function<void(void)>;
20
+ using QueueItemType = FnType;
21
+ // TODO: perhaps switch to https://www.boost.org/doc/libs/1_83_0/doc/html/boost/lockfree/queue.html
22
+ // to reduce the need for atomics/etc?
23
+ using QueueType = std::deque<QueueItemType>;
24
+
25
+ private:
26
+ static std::shared_ptr<RubyQueue> _instance;
27
+ std::shared_ptr<QueueType> _q = std::shared_ptr<QueueType>(new QueueType());
28
+
29
+ std::mutex _mtx;
30
+ Semaphore _sem { 0 };
31
+ std::binary_semaphore _startup_latch { 0 };
32
+
33
+ mutable std::shared_mutex _run_mtx;
34
+ mutable std::atomic_flag _running;
35
+
36
+ std::atomic<CppThreadId> _thread_id;
37
+ std::atomic<RubyThreadId> _rb_thread;
38
+ std::atomic_flag _starting, _stopping;
39
+
40
+ constexpr RubyQueue() = default;
41
+ VALUE run();
42
+ void loop();
43
+ public:
44
+ ~RubyQueue() = default;
45
+ RubyQueue(const RubyQueue&) = delete;
46
+ RubyQueue(RubyQueue&&) = delete;
47
+ RubyQueue &operator=(RubyQueue&&) = delete;
48
+ RubyQueue &operator=(const RubyQueue&) = delete;
49
+ // TODO: add a function that waits for queue to be settled
50
+
51
+ static const std::shared_ptr<RubyQueue> &instance();
52
+ [[nodiscard]] Object rb_thread() const;
53
+
54
+ void push(QueueItemType fn);
55
+
56
+ void start();
57
+ void ensure_started();
58
+ [[nodiscard]] bool running() const;
59
+
60
+ void stop();
61
+ [[nodiscard]] bool stopped() const;
62
+ static void stop_on_exit(VALUE); // Argument is unused, but rb_set_end_proc requires it
63
+ void kill();
64
+ };
65
+
66
+ // using RubyQueue_DT = Data_Type<RubyQueue>;
67
+ // using RubyQueue_DO = Data_Object<RubyQueue>;
68
+ // static inline RubyQueue_DT rb_cRubyQueue;
69
+ }
@@ -0,0 +1,28 @@
1
+ 64c64,68
2
+ < include(FetchContent)
3
+ ---
4
+ > set(BOOST_REPO "https://github.com/boostorg/boost")
5
+ > set(BOOST_TAG boost-1.83.0)
6
+ > cmake_path(SET BOOST_CACHE_LOCATION NORMALIZE "${CMAKE_SOURCE_DIR}/tmp/${BOOST_TAG}")
7
+ > set(BOOST_INCLUDE_LIBRARIES "stacktrace;exception;headers;endian;algorithm;core;describe")
8
+ > 
9
+ 70d73
10
+ < set(BOOST_INCLUDE_LIBRARIES "stacktrace;exception;headers;endian;algorithm;core;describe")
11
+ 78a82,83
12
+ > 
13
+ > include(FetchContent)
14
+ 81,84c86,88
15
+ < # TODO: put this somewhere outside the build tree so it stays cached
16
+ < SOURCE_DIR ../../tmp/boost-git-cache
17
+ < GIT_REPOSITORY "https://github.com/boostorg/boost" # TODO: would an archive make more sense(?)
18
+ < GIT_TAG boost-1.83.0 # TODO: set this in build-config.cmake
19
+ ---
20
+ > SOURCE_DIR "${BOOST_CACHE_LOCATION}"
21
+ > GIT_REPOSITORY ${BOOST_REPO} # TODO: would an archive make more sense(?)
22
+ > GIT_TAG ${BOOST_TAG} # TODO: set this in build-config.cmake
23
+ 103,104c107,108
24
+ < target_link_libraries(${build_target} INTERFACE Boost::headers Boost::exception Boost::describe)
25
+ < target_link_libraries(${build_target} PRIVATE Boost::algorithm Boost::core)
26
+ ---
27
+ > target_link_libraries(${build_target} INTERFACE Boost::headers Boost::exception Boost::describe Boost::endian)
28
+ > target_link_libraries(${build_target} PRIVATE Boost::algorithm Boost::core Boost::endian)
@@ -0,0 +1,21 @@
1
+ #pragma once
2
+
3
+ #include <simpleble/SimpleBLE.h>
4
+ #include <simpleble/Adapter.h>
5
+ #include <simpleble/Characteristic.h>
6
+
7
+ namespace RuBLE {
8
+ using BluetoothAddress = SimpleBLE::BluetoothAddress;
9
+ using BluetoothAddressType = SimpleBLE::BluetoothAddressType;
10
+ using BluetoothUUID = SimpleBLE::BluetoothUUID;
11
+
12
+ template<typename T>
13
+ concept BluetoothAddressable = requires(T t) {
14
+ { t.address() } -> std::same_as<SimpleBLE::BluetoothAddress>;
15
+ };
16
+
17
+ template<typename T>
18
+ concept UUIDable = requires(T t) {
19
+ { t.uuid() } -> std::same_as<SimpleBLE::BluetoothUUID>;
20
+ };
21
+ }
@@ -0,0 +1,91 @@
1
+ #pragma once
2
+
3
+ #include "config.h"
4
+ #ifdef HAVE_VALGRIND
5
+ #include <valgrind/valgrind.h>
6
+ #endif
7
+
8
+ // from https://stackoverflow.com/a/6713727
9
+ // #define STRINGIFICATOR(X) #X
10
+ #include "types/SimpleBLE.hpp"
11
+
12
+ namespace Rice {
13
+ class String;
14
+ class Array;
15
+ class Object;
16
+ template<typename T>
17
+ class Data_Type;
18
+ template<typename T>
19
+ class Data_Object;
20
+ template<typename Enum_T>
21
+ class Enum;
22
+ }
23
+
24
+ namespace RuBLE {
25
+ #ifdef RUBLE_DEBUG
26
+ static constexpr const bool DEBUG = true;
27
+ #else
28
+ static constexpr const bool DEBUG = false;
29
+ #endif
30
+
31
+ template<const auto &FIELD_NAMES>
32
+ class NamedBitSet;
33
+
34
+ using str_char_type [[maybe_unused]] = std::string::value_type;
35
+
36
+ class RubyQueue;
37
+
38
+ class Callback;
39
+ template<typename Key, class ProxyClass, class Value>
40
+ class Registry;
41
+
42
+ template<class RegistryType>
43
+ class RegistryFactory;
44
+
45
+ using BluetoothAddressType_DT = Rice::Enum<SimpleBLE::BluetoothAddressType>;
46
+
47
+ class ByteArray;
48
+ using ByteArray_DT = Rice::Data_Type<ByteArray>;
49
+ using ByteArray_DO = Rice::Data_Object<ByteArray>;
50
+
51
+ class Adapter;
52
+ using Adapter_DT = Rice::Data_Type<Adapter>;
53
+ using Adapter_DO = Rice::Data_Object<Adapter>;
54
+ using AdapterRegistry = Registry<SimpleBLE::BluetoothAddress, Adapter, SimpleBLE::Adapter>;
55
+ using AdapterRegistry_DT = Rice::Data_Type<AdapterRegistry>;
56
+ using AdapterRegistry_DO [[maybe_unused]] = Rice::Data_Object<AdapterRegistry>;
57
+ using AdapterRegistryFactory = RegistryFactory<AdapterRegistry>;
58
+
59
+ class Peripheral;
60
+ using Peripheral_DT = Rice::Data_Type<Peripheral>;
61
+ using Peripheral_DO = Rice::Data_Object<Peripheral>;
62
+ using PeripheralRegistry = Registry<SimpleBLE::BluetoothAddress, Peripheral, SimpleBLE::Peripheral>;
63
+ using PeripheralRegistry_DT = Rice::Data_Type<PeripheralRegistry>;
64
+ using PeripheralRegistry_DO [[maybe_unused]] = Rice::Data_Object<PeripheralRegistry>;
65
+ using PeripheralRegistryFactory = RegistryFactory<PeripheralRegistry>;
66
+
67
+ class Service;
68
+ using Service_DT = Rice::Data_Type<Service>;
69
+ using Service_DO [[maybe_unused]] = Rice::Data_Object<Service>;
70
+ using ServiceRegistry = Registry<SimpleBLE::BluetoothUUID, Service, SimpleBLE::Service>;
71
+ using ServiceRegistry_DT = Rice::Data_Type<ServiceRegistry>;
72
+ using ServiceRegistry_DO [[maybe_unused]] = Rice::Data_Object<ServiceRegistry>;
73
+ using ServiceRegistryFactory = RegistryFactory<ServiceRegistry>;
74
+
75
+ class Characteristic;
76
+ using Characteristic_DT = Rice::Data_Type<Characteristic>;
77
+ using Characteristic_DO [[maybe_unused]] = Rice::Data_Object<Characteristic>;
78
+ using CharacteristicRegistry = Registry<SimpleBLE::BluetoothUUID, Characteristic, SimpleBLE::Characteristic>;
79
+ using CharacteristicRegistry_DT = Rice::Data_Type<CharacteristicRegistry>;
80
+ using CharacteristicRegistry_DO [[maybe_unused]] = Rice::Data_Object<CharacteristicRegistry>;
81
+ using CharacteristicRegistryFactory = RegistryFactory<CharacteristicRegistry>;
82
+
83
+ enum class CharacteristicCapabilityType : std::size_t;
84
+ using CharacteristicCapabilityType_DT = Rice::Enum<CharacteristicCapabilityType>;
85
+ using CharacteristicCapabilityType_DO [[maybe_unused]] = Rice::Data_Object<CharacteristicCapabilityType>;
86
+
87
+ class Descriptor;
88
+ using Descriptor_DT = Rice::Data_Type<Descriptor>;
89
+ using Descriptor_DO = Rice::Data_Object<Descriptor>;
90
+
91
+ }
@@ -0,0 +1,12 @@
1
+ #pragma once
2
+ #include <concepts>
3
+
4
+ namespace std {
5
+ struct nullopt_t; // NOLINT(*-dcl58-cpp)
6
+ }
7
+
8
+ namespace RuBLE::Utils {
9
+ template<typename T>
10
+ constexpr bool is_nullopt = std::same_as<std::remove_cvref_t<T>, std::nullopt_t>;
11
+ }
12
+