ruble 0.0.3.alpha
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|