simplernble 1.2.1-dev132 → 1.2.1-dev136
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.
- package/native/VERSION +1 -1
- package/native/dependencies/internal/include/simplejni/ThreadRunner.hpp +166 -0
- package/native/dependencies/internal/include/simplejni/VM.hpp +19 -0
- package/package.json +1 -1
- package/prebuilt/release/SimpleBLE.xcframework/Info.plist +5 -5
- package/prebuilt/release/SimpleBLE.xcframework/ios-arm64/libsimpleble.a +0 -0
- package/prebuilt/release/SimpleBLE.xcframework/ios-arm64_x86_64-simulator/libsimpleble.a +0 -0
package/native/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.2.1-
|
|
1
|
+
1.2.1-dev136
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <simpleble/Logging.h>
|
|
4
|
+
|
|
5
|
+
#include <condition_variable>
|
|
6
|
+
#include <exception>
|
|
7
|
+
#include <functional>
|
|
8
|
+
#include <memory>
|
|
9
|
+
#include <mutex>
|
|
10
|
+
#include <queue>
|
|
11
|
+
#include <string>
|
|
12
|
+
#include <thread>
|
|
13
|
+
#include <utility>
|
|
14
|
+
|
|
15
|
+
#include "Common.hpp"
|
|
16
|
+
#include "VM.hpp"
|
|
17
|
+
|
|
18
|
+
namespace SimpleJNI {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Runs Java callbacks one at a time, in order, on a single thread attached to the JVM.
|
|
22
|
+
*
|
|
23
|
+
* Follows kvn::thread_runner: stop() runs the queued functions, functions enqueued afterwards are dropped, and the
|
|
24
|
+
* runner may be destroyed from one of its own functions. On top of that, it does what a JNI callback thread needs:
|
|
25
|
+
* - The thread starts on the first enqueue, since the JavaVM is only known after JNI_OnLoad.
|
|
26
|
+
* - The thread stays attached under the runner's name, which debuggers and ANR traces show, and detaches before it
|
|
27
|
+
* ends, as ART requires. It attaches as a daemon, so it does not keep a desktop JVM from exiting.
|
|
28
|
+
* - A function that throws, or that leaves a Java exception pending, is logged under the runner's name and the thread
|
|
29
|
+
* carries on.
|
|
30
|
+
*/
|
|
31
|
+
class ThreadRunner final {
|
|
32
|
+
public:
|
|
33
|
+
explicit ThreadRunner(std::string name) : _state(std::make_shared<state>()) { _state->name = std::move(name); }
|
|
34
|
+
|
|
35
|
+
// Remove copy constructor and copy assignment
|
|
36
|
+
ThreadRunner(const ThreadRunner&) = delete;
|
|
37
|
+
ThreadRunner& operator=(const ThreadRunner&) = delete;
|
|
38
|
+
|
|
39
|
+
~ThreadRunner() {
|
|
40
|
+
if (!on_own_thread()) {
|
|
41
|
+
stop();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Destroyed from one of its own functions: the thread cannot join itself. It keeps its own reference to the
|
|
46
|
+
// state, and detaches from the JVM and ends once the current function returns.
|
|
47
|
+
std::queue<std::function<void()>> dropped;
|
|
48
|
+
{
|
|
49
|
+
std::lock_guard<std::mutex> lock(_state->mutex);
|
|
50
|
+
_state->stop = true;
|
|
51
|
+
dropped.swap(_state->queue);
|
|
52
|
+
}
|
|
53
|
+
_state->cv.notify_one();
|
|
54
|
+
_thread.detach();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Functions enqueued after stop() are dropped.
|
|
59
|
+
*/
|
|
60
|
+
void enqueue(std::function<void()> func) {
|
|
61
|
+
{
|
|
62
|
+
std::lock_guard<std::mutex> lock(_state->mutex);
|
|
63
|
+
if (_state->stop) return;
|
|
64
|
+
if (!_thread.joinable()) {
|
|
65
|
+
_thread = std::thread(&ThreadRunner::thread_func, _state);
|
|
66
|
+
_thread_id = _thread.get_id();
|
|
67
|
+
}
|
|
68
|
+
_state->queue.push(std::move(func));
|
|
69
|
+
}
|
|
70
|
+
_state->cv.notify_one();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Run the functions already enqueued, then end the thread.
|
|
75
|
+
*
|
|
76
|
+
* Safe to call more than once. When called from one of the runner's own functions, it returns right away and the
|
|
77
|
+
* thread ends once the queue is empty.
|
|
78
|
+
*/
|
|
79
|
+
void stop() {
|
|
80
|
+
{
|
|
81
|
+
std::lock_guard<std::mutex> lock(_state->mutex);
|
|
82
|
+
_state->stop = true;
|
|
83
|
+
}
|
|
84
|
+
_state->cv.notify_one();
|
|
85
|
+
|
|
86
|
+
// Once stopped, enqueue() no longer starts the thread, so it is safe to read here.
|
|
87
|
+
if (on_own_thread()) return;
|
|
88
|
+
|
|
89
|
+
std::lock_guard<std::mutex> lock(_join_mutex);
|
|
90
|
+
if (_thread.joinable()) {
|
|
91
|
+
_thread.join();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private:
|
|
96
|
+
/** Shared with the thread, so the thread never touches the runner itself. */
|
|
97
|
+
struct state {
|
|
98
|
+
std::mutex mutex;
|
|
99
|
+
std::condition_variable cv;
|
|
100
|
+
std::queue<std::function<void()>> queue;
|
|
101
|
+
bool stop = false;
|
|
102
|
+
std::string name;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
static void log_error(const state& shared, const std::string& message) noexcept {
|
|
106
|
+
SimpleBLE::Logging::Logger::get()->log(SimpleBLE::Logging::Level::Error, shared.name, __FILE__, __LINE__,
|
|
107
|
+
__func__, message);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static void thread_func(std::shared_ptr<state> shared) noexcept {
|
|
111
|
+
try {
|
|
112
|
+
VM::attach_as_daemon(shared->name.c_str());
|
|
113
|
+
} catch (const std::exception& exception) {
|
|
114
|
+
log_error(*shared, std::string("Failed to attach callback thread: ") + exception.what());
|
|
115
|
+
|
|
116
|
+
// Nothing can run without the JVM, so drop the queued functions instead of letting the queue grow.
|
|
117
|
+
std::queue<std::function<void()>> dropped;
|
|
118
|
+
std::lock_guard<std::mutex> lock(shared->mutex);
|
|
119
|
+
shared->stop = true;
|
|
120
|
+
dropped.swap(shared->queue);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
while (true) {
|
|
125
|
+
std::function<void()> func;
|
|
126
|
+
{
|
|
127
|
+
std::unique_lock<std::mutex> lock(shared->mutex);
|
|
128
|
+
shared->cv.wait(lock, [&shared] { return shared->stop || !shared->queue.empty(); });
|
|
129
|
+
if (shared->stop && shared->queue.empty()) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
func = std::move(shared->queue.front());
|
|
133
|
+
shared->queue.pop();
|
|
134
|
+
}
|
|
135
|
+
run(*shared, func);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
VM::detach();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static void run(const state& shared, const std::function<void()>& func) noexcept {
|
|
142
|
+
try {
|
|
143
|
+
func();
|
|
144
|
+
} catch (const std::exception& exception) {
|
|
145
|
+
log_error(shared, std::string("Exception in callback thread: ") + exception.what());
|
|
146
|
+
} catch (...) {
|
|
147
|
+
log_error(shared, "Unknown exception in callback thread");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// A pending Java exception would break every later JNI call on this thread.
|
|
151
|
+
try {
|
|
152
|
+
Exception::check(VM::env());
|
|
153
|
+
} catch (const std::exception& exception) {
|
|
154
|
+
log_error(shared, std::string("Java exception left pending in callback thread: ") + exception.what());
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
bool on_own_thread() const { return std::this_thread::get_id() == _thread_id; }
|
|
159
|
+
|
|
160
|
+
std::shared_ptr<state> _state;
|
|
161
|
+
std::thread _thread;
|
|
162
|
+
std::thread::id _thread_id;
|
|
163
|
+
std::mutex _join_mutex;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
} // namespace SimpleJNI
|
|
@@ -87,6 +87,25 @@ class VM {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// Attaches the calling thread as a daemon, unless it is attached already, so a desktop JVM can exit while the
|
|
91
|
+
// thread is still running.
|
|
92
|
+
static void attach_as_daemon(const char* thread_name = nullptr) {
|
|
93
|
+
JavaVM* jvm = VM::jvm();
|
|
94
|
+
JNIEnv* env = nullptr;
|
|
95
|
+
if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) == JNI_OK) return;
|
|
96
|
+
|
|
97
|
+
// OpenJDK declares the name as char*, but the JVM only reads it.
|
|
98
|
+
JavaVMAttachArgs args{JNI_VERSION_1_6, const_cast<char*>(thread_name), nullptr};
|
|
99
|
+
#ifdef __ANDROID__
|
|
100
|
+
auto result = jvm->AttachCurrentThreadAsDaemon(&env, &args);
|
|
101
|
+
#else
|
|
102
|
+
auto result = jvm->AttachCurrentThreadAsDaemon(reinterpret_cast<void**>(&env), &args);
|
|
103
|
+
#endif
|
|
104
|
+
if (result != JNI_OK) {
|
|
105
|
+
throw std::runtime_error("Failed to attach the current thread to the JVM");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
static void detach() {
|
|
91
110
|
JavaVM* jvm = VM::jvm();
|
|
92
111
|
jvm->DetachCurrentThread();
|
package/package.json
CHANGED
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>libsimpleble.a</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>libsimpleble.a</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
+
<string>x86_64</string>
|
|
17
18
|
</array>
|
|
18
19
|
<key>SupportedPlatform</key>
|
|
19
20
|
<string>ios</string>
|
|
21
|
+
<key>SupportedPlatformVariant</key>
|
|
22
|
+
<string>simulator</string>
|
|
20
23
|
</dict>
|
|
21
24
|
<dict>
|
|
22
25
|
<key>BinaryPath</key>
|
|
23
26
|
<string>libsimpleble.a</string>
|
|
24
27
|
<key>LibraryIdentifier</key>
|
|
25
|
-
<string>ios-
|
|
28
|
+
<string>ios-arm64</string>
|
|
26
29
|
<key>LibraryPath</key>
|
|
27
30
|
<string>libsimpleble.a</string>
|
|
28
31
|
<key>SupportedArchitectures</key>
|
|
29
32
|
<array>
|
|
30
33
|
<string>arm64</string>
|
|
31
|
-
<string>x86_64</string>
|
|
32
34
|
</array>
|
|
33
35
|
<key>SupportedPlatform</key>
|
|
34
36
|
<string>ios</string>
|
|
35
|
-
<key>SupportedPlatformVariant</key>
|
|
36
|
-
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|
|
Binary file
|