ruble 0.0.3.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gdbinit +21 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +96 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +6 -0
- data/CMakeLists.txt +4 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +98 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +41 -0
- data/ext/ruble/.gitignore +5 -0
- data/ext/ruble/CMakeLists.txt +157 -0
- data/ext/ruble/RuBLEHelpers.cmake +240 -0
- data/ext/ruble/bindings/Adapter.cpp +193 -0
- data/ext/ruble/bindings/Adapter.hpp +85 -0
- data/ext/ruble/bindings/Characteristic.cpp +171 -0
- data/ext/ruble/bindings/Characteristic.hpp +132 -0
- data/ext/ruble/bindings/Descriptor.cpp +34 -0
- data/ext/ruble/bindings/Descriptor.hpp +69 -0
- data/ext/ruble/bindings/Peripheral.cpp +212 -0
- data/ext/ruble/bindings/Peripheral.hpp +108 -0
- data/ext/ruble/bindings/RuBLE.cpp +115 -0
- data/ext/ruble/bindings/Service.cpp +112 -0
- data/ext/ruble/bindings/Service.hpp +61 -0
- data/ext/ruble/bindings/common.hpp +48 -0
- data/ext/ruble/bindings/globals.cpp +43 -0
- data/ext/ruble/cmake.mk +62 -0
- data/ext/ruble/concerns/CharacteristicValueTracker.cpp +18 -0
- data/ext/ruble/concerns/CharacteristicValueTracker.hpp +40 -0
- data/ext/ruble/concerns/Rubyable.cpp +4 -0
- data/ext/ruble/concerns/Rubyable.hpp +46 -0
- data/ext/ruble/config.h.in +25 -0
- data/ext/ruble/containers/ByteArray.cpp +64 -0
- data/ext/ruble/containers/ByteArray.hpp +161 -0
- data/ext/ruble/containers/Callback.hpp +52 -0
- data/ext/ruble/containers/NamedBitSet.hpp +140 -0
- data/ext/ruble/containers/NamedBitSet.ipp +71 -0
- data/ext/ruble/extconf.rb +30 -0
- data/ext/ruble/management/Registry.cpp +63 -0
- data/ext/ruble/management/Registry.hpp +170 -0
- data/ext/ruble/management/RegistryFactory.hpp +113 -0
- data/ext/ruble/management/RubyQueue.cpp +152 -0
- data/ext/ruble/management/RubyQueue.hpp +69 -0
- data/ext/ruble/modularize.diff +28 -0
- data/ext/ruble/types/SimpleBLE.hpp +21 -0
- data/ext/ruble/types/declarations.hpp +91 -0
- data/ext/ruble/types/helpers.hpp +12 -0
- data/ext/ruble/types/ruby.hpp +36 -0
- data/ext/ruble/types/stl.hpp +41 -0
- data/ext/ruble/utils/RubyCallbackTraits.cpp +28 -0
- data/ext/ruble/utils/RubyCallbackTraits.hpp +48 -0
- data/ext/ruble/utils/async.cpp +10 -0
- data/ext/ruble/utils/async.hpp +76 -0
- data/ext/ruble/utils/containers.hpp +41 -0
- data/ext/ruble/utils/exception_handling.cpp +50 -0
- data/ext/ruble/utils/exception_handling.hpp +53 -0
- data/ext/ruble/utils/garbage_collection.cpp +82 -0
- data/ext/ruble/utils/garbage_collection.hpp +22 -0
- data/ext/ruble/utils/hash.cpp +83 -0
- data/ext/ruble/utils/hash.hpp +52 -0
- data/ext/ruble/utils/hexadecimal.hpp +116 -0
- data/ext/ruble/utils/human_type_names.hpp +38 -0
- data/ext/ruble/utils/inspection.cpp +24 -0
- data/ext/ruble/utils/inspection.hpp +108 -0
- data/ext/ruble/utils/ruby.hpp +103 -0
- data/ext/ruble/utils/ruby_context.hpp +73 -0
- data/lib/ruble/build/.rubocop.yml +19 -0
- data/lib/ruble/build/boost.rb +34 -0
- data/lib/ruble/build/cmake.rb +134 -0
- data/lib/ruble/build/core_ext.rb +5 -0
- data/lib/ruble/build/data/bundler.rb +24 -0
- data/lib/ruble/build/data/extension.rb +101 -0
- data/lib/ruble/build/data/os.rb +21 -0
- data/lib/ruble/build/data/rice.rb +24 -0
- data/lib/ruble/build/data.rb +22 -0
- data/lib/ruble/build/extconf.rb +76 -0
- data/lib/ruble/build/github_repo.rb +129 -0
- data/lib/ruble/build/simpleble.rb +56 -0
- data/lib/ruble/build.rb +28 -0
- data/lib/ruble/version.rb +7 -0
- data/lib/ruble.rb +46 -0
- data/lib/tasks/dev/dev_tasks.rb +130 -0
- data/lib/tasks/dev/pager.rb +218 -0
- data/lib/tasks/dev/paths.rb +30 -0
- data/lib/tasks/dev/state_hash.rb +65 -0
- data/lib/tasks/dev.rake +41 -0
- data/lib/tasks/simpleble.rake +29 -0
- data/sig/rubble.rbs +4 -0
- 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> ®istry(RegistryOwner *owner) const;
|
40
|
+
|
41
|
+
const std::shared_ptr<RegistryType> ®istry() 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
|
+
[0;36m64c64,68[0;0m
|
2
|
+
[0;31m< include(FetchContent)[0;0m
|
3
|
+
[0;0m---[0;0m
|
4
|
+
[0;32m> set(BOOST_REPO "https://github.com/boostorg/boost")[0;0m
|
5
|
+
[0;32m> set(BOOST_TAG boost-1.83.0)[0;0m
|
6
|
+
[0;32m> cmake_path(SET BOOST_CACHE_LOCATION NORMALIZE "${CMAKE_SOURCE_DIR}/tmp/${BOOST_TAG}")[0;0m
|
7
|
+
[0;32m> set(BOOST_INCLUDE_LIBRARIES "stacktrace;exception;headers;endian;algorithm;core;describe")[0;0m
|
8
|
+
[0;32m> [0;0m
|
9
|
+
[0;36m70d73[0;0m
|
10
|
+
[0;31m< set(BOOST_INCLUDE_LIBRARIES "stacktrace;exception;headers;endian;algorithm;core;describe")[0;0m
|
11
|
+
[0;36m78a82,83[0;0m
|
12
|
+
[0;32m> [0;0m
|
13
|
+
[0;32m> include(FetchContent)[0;0m
|
14
|
+
[0;36m81,84c86,88[0;0m
|
15
|
+
[0;31m< # TODO: put this somewhere outside the build tree so it stays cached[0;0m
|
16
|
+
[0;31m< SOURCE_DIR ../../tmp/boost-git-cache[0;0m
|
17
|
+
[0;31m< GIT_REPOSITORY "https://github.com/boostorg/boost" # TODO: would an archive make more sense(?)[0;0m
|
18
|
+
[0;31m< GIT_TAG boost-1.83.0 # TODO: set this in build-config.cmake[0;0m
|
19
|
+
[0;0m---[0;0m
|
20
|
+
[0;32m> SOURCE_DIR "${BOOST_CACHE_LOCATION}"[0;0m
|
21
|
+
[0;32m> GIT_REPOSITORY ${BOOST_REPO} # TODO: would an archive make more sense(?)[0;0m
|
22
|
+
[0;32m> GIT_TAG ${BOOST_TAG} # TODO: set this in build-config.cmake[0;0m
|
23
|
+
[0;36m103,104c107,108[0;0m
|
24
|
+
[0;31m< target_link_libraries(${build_target} INTERFACE Boost::headers Boost::exception Boost::describe)[0;0m
|
25
|
+
[0;31m< target_link_libraries(${build_target} PRIVATE Boost::algorithm Boost::core)[0;0m
|
26
|
+
[0;0m---[0;0m
|
27
|
+
[0;32m> target_link_libraries(${build_target} INTERFACE Boost::headers Boost::exception Boost::describe Boost::endian)[0;0m
|
28
|
+
[0;32m> target_link_libraries(${build_target} PRIVATE Boost::algorithm Boost::core Boost::endian)[0;0m
|
@@ -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
|
+
}
|