react-native-wgpu 0.1.13 → 0.1.15
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/android/CMakeLists.txt +7 -0
- package/android/build.gradle +4 -4
- package/android/cpp/cpp-adapter.cpp +10 -4
- package/android/cpp/platform/ThreadUtils.cpp +41 -0
- package/apple/platform/ThreadUtils.cpp +33 -0
- package/cpp/jsi/RNFJSIConverter.h +47 -28
- package/cpp/platform/ThreadUtils.h +30 -0
- package/cpp/rnwgpu/RNWebGPUManager.cpp +8 -0
- package/cpp/rnwgpu/SurfaceRegistry.h +12 -8
- package/cpp/rnwgpu/api/Convertors.h +13 -5
- package/cpp/rnwgpu/api/GPU.cpp +4 -4
- package/cpp/rnwgpu/api/GPUAdapter.cpp +15 -14
- package/cpp/rnwgpu/api/GPUAdapterInfo.h +25 -4
- package/cpp/rnwgpu/api/GPUCanvasContext.cpp +3 -2
- package/cpp/rnwgpu/api/GPUDevice.cpp +5 -5
- package/cpp/rnwgpu/api/GPUDevice.h +7 -1
- package/cpp/rnwgpu/api/GPUFeatures.h +4 -4
- package/cpp/rnwgpu/api/GPUShaderModule.cpp +2 -1
- package/cpp/rnwgpu/api/descriptors/GPUCanvasConfiguration.h +3 -1
- package/cpp/rnwgpu/api/descriptors/Unions.h +30 -0
- package/cpp/threading/CallInvokerDispatcher.h +37 -0
- package/cpp/threading/Dispatcher.cpp +54 -0
- package/cpp/threading/Dispatcher.h +93 -0
- package/cpp/threading/ThreadPool.cpp +86 -0
- package/cpp/threading/ThreadPool.h +53 -0
- package/cpp/webgpu/webgpu.h +762 -758
- package/cpp/webgpu/webgpu_cpp.h +1827 -1626
- package/cpp/webgpu/webgpu_cpp_chained_struct.h +2 -0
- package/lib/commonjs/hooks.js +4 -2
- package/lib/commonjs/hooks.js.map +1 -1
- package/lib/module/hooks.js +4 -2
- package/lib/module/hooks.js.map +1 -1
- package/lib/typescript/lib/commonjs/hooks.d.ts.map +1 -1
- package/lib/typescript/lib/module/hooks.d.ts.map +1 -1
- package/lib/typescript/src/hooks.d.ts.map +1 -1
- package/libs/android/arm64-v8a/libwebgpu_dawn.so +0 -0
- package/libs/android/armeabi-v7a/libwebgpu_dawn.so +0 -0
- package/libs/android/x86/libwebgpu_dawn.so +0 -0
- package/libs/android/x86_64/libwebgpu_dawn.so +0 -0
- package/libs/apple/arm64_iphoneos/libwebgpu_dawn.a +0 -0
- package/libs/apple/arm64_iphonesimulator/libwebgpu_dawn.a +0 -0
- package/libs/apple/arm64_xros/libwebgpu_dawn.a +0 -0
- package/libs/apple/arm64_xrsimulator/libwebgpu_dawn.a +0 -0
- package/libs/apple/iphonesimulator/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/Info.plist +10 -10
- package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64_x86_64-simulator/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/xros-arm64/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/xros-arm64-simulator/libwebgpu_dawn.a +0 -0
- package/libs/apple/universal_macosx/libwebgpu_dawn.a +0 -0
- package/libs/apple/x86_64_iphonesimulator/libwebgpu_dawn.a +0 -0
- package/libs/dawn.json +270 -251
- package/package.json +2 -2
- package/src/__tests__/Device.spec.ts +31 -0
- package/src/hooks.tsx +3 -2
|
@@ -67,6 +67,14 @@ static void convertJSUnionToEnum(const std::string &inUnion,
|
|
|
67
67
|
*outEnum = wgpu::BlendFactor::Constant;
|
|
68
68
|
} else if (inUnion == "one-minus-constant") {
|
|
69
69
|
*outEnum = wgpu::BlendFactor::OneMinusConstant;
|
|
70
|
+
} else if (inUnion == "src1") {
|
|
71
|
+
*outEnum = wgpu::BlendFactor::Src1;
|
|
72
|
+
} else if (inUnion == "one-minus-src1") {
|
|
73
|
+
*outEnum = wgpu::BlendFactor::OneMinusSrc1;
|
|
74
|
+
} else if (inUnion == "src1-alpha") {
|
|
75
|
+
*outEnum = wgpu::BlendFactor::Src1Alpha;
|
|
76
|
+
} else if (inUnion == "one-minus-src1-alpha") {
|
|
77
|
+
*outEnum = wgpu::BlendFactor::OneMinusSrc1Alpha;
|
|
70
78
|
} else {
|
|
71
79
|
throw invalidUnion(inUnion);
|
|
72
80
|
}
|
|
@@ -114,6 +122,18 @@ static void convertEnumToJSUnion(wgpu::BlendFactor inEnum,
|
|
|
114
122
|
case wgpu::BlendFactor::OneMinusConstant:
|
|
115
123
|
*outUnion = "one-minus-constant";
|
|
116
124
|
break;
|
|
125
|
+
case wgpu::BlendFactor::Src1:
|
|
126
|
+
*outUnion = "src1";
|
|
127
|
+
break;
|
|
128
|
+
case wgpu::BlendFactor::OneMinusSrc1:
|
|
129
|
+
*outUnion = "one-minus-src1";
|
|
130
|
+
break;
|
|
131
|
+
case wgpu::BlendFactor::Src1Alpha:
|
|
132
|
+
*outUnion = "src1-alpha";
|
|
133
|
+
break;
|
|
134
|
+
case wgpu::BlendFactor::OneMinusSrc1Alpha:
|
|
135
|
+
*outUnion = "one-minus-src1-alpha";
|
|
136
|
+
break;
|
|
117
137
|
default:
|
|
118
138
|
throw invalidEnum(inEnum);
|
|
119
139
|
}
|
|
@@ -412,6 +432,10 @@ static void convertJSUnionToEnum(const std::string &inUnion,
|
|
|
412
432
|
*outEnum = wgpu::FeatureName::BGRA8UnormStorage;
|
|
413
433
|
} else if (inUnion == "float32-filterable") {
|
|
414
434
|
*outEnum = wgpu::FeatureName::Float32Filterable;
|
|
435
|
+
} else if (inUnion == "clip-distances") {
|
|
436
|
+
*outEnum = wgpu::FeatureName::ClipDistances;
|
|
437
|
+
} else if (inUnion == "dual-source-blending") {
|
|
438
|
+
*outEnum = wgpu::FeatureName::DualSourceBlending;
|
|
415
439
|
} else {
|
|
416
440
|
throw invalidUnion(inUnion);
|
|
417
441
|
}
|
|
@@ -453,6 +477,12 @@ static void convertEnumToJSUnion(wgpu::FeatureName inEnum,
|
|
|
453
477
|
case wgpu::FeatureName::Float32Filterable:
|
|
454
478
|
*outUnion = "float32-filterable";
|
|
455
479
|
break;
|
|
480
|
+
case wgpu::FeatureName::ClipDistances:
|
|
481
|
+
*outUnion = "clip-distances";
|
|
482
|
+
break;
|
|
483
|
+
case wgpu::FeatureName::DualSourceBlending:
|
|
484
|
+
*outUnion = "dual-source-blending";
|
|
485
|
+
break;
|
|
456
486
|
default:
|
|
457
487
|
throw invalidEnum(inEnum);
|
|
458
488
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
//
|
|
3
|
+
// Created by Marc Rousavy on 27.03.24.
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#include "Dispatcher.h"
|
|
9
|
+
|
|
10
|
+
#include <utility>
|
|
11
|
+
#include <memory>
|
|
12
|
+
#include <ReactCommon/CallInvoker.h>
|
|
13
|
+
|
|
14
|
+
namespace margelo {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A Dispatcher that uses react::CallInvoker for it's implementation
|
|
18
|
+
*/
|
|
19
|
+
class CallInvokerDispatcher final : public Dispatcher {
|
|
20
|
+
public:
|
|
21
|
+
explicit CallInvokerDispatcher(
|
|
22
|
+
std::shared_ptr<react::CallInvoker> callInvoker)
|
|
23
|
+
: _callInvoker(callInvoker) {}
|
|
24
|
+
|
|
25
|
+
void runAsync(std::function<void()> &&function) override {
|
|
26
|
+
_callInvoker->invokeAsync(std::move(function));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void runSync(std::function<void()> &&function) override {
|
|
30
|
+
_callInvoker->invokeSync(std::move(function));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private:
|
|
34
|
+
std::shared_ptr<react::CallInvoker> _callInvoker;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
} // namespace margelo
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Created by Marc Rousavy on 12.03.24.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#include "Dispatcher.h"
|
|
6
|
+
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include "RNFJSIHelper.h"
|
|
9
|
+
|
|
10
|
+
namespace margelo {
|
|
11
|
+
|
|
12
|
+
namespace jsi = facebook::jsi;
|
|
13
|
+
|
|
14
|
+
static constexpr auto GLOBAL_DISPATCHER_HOLDER_NAME = "__nitroDispatcher";
|
|
15
|
+
|
|
16
|
+
std::unordered_map<jsi::Runtime *, std::weak_ptr<Dispatcher>>
|
|
17
|
+
Dispatcher::_globalCache;
|
|
18
|
+
|
|
19
|
+
void Dispatcher::installRuntimeGlobalDispatcher(
|
|
20
|
+
jsi::Runtime &runtime, std::shared_ptr<Dispatcher> dispatcher) {
|
|
21
|
+
|
|
22
|
+
// Store a weak reference in global cache
|
|
23
|
+
_globalCache[&runtime] = std::weak_ptr<Dispatcher>(dispatcher);
|
|
24
|
+
|
|
25
|
+
// Inject the dispatcher into Runtime global (runtime will hold a strong
|
|
26
|
+
// reference)
|
|
27
|
+
jsi::Object dispatcherHolder(runtime);
|
|
28
|
+
dispatcherHolder.setNativeState(runtime, dispatcher);
|
|
29
|
+
runtime.global().setProperty(runtime, GLOBAL_DISPATCHER_HOLDER_NAME,
|
|
30
|
+
dispatcherHolder);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
std::shared_ptr<Dispatcher>
|
|
34
|
+
Dispatcher::getRuntimeGlobalDispatcher(jsi::Runtime &runtime) {
|
|
35
|
+
if (auto search = _globalCache.find(&runtime); search != _globalCache.end()) {
|
|
36
|
+
// the runtime is known - we have something in cache
|
|
37
|
+
std::weak_ptr<Dispatcher> weakDispatcher = _globalCache[&runtime];
|
|
38
|
+
std::shared_ptr<Dispatcher> strongDispatcher = weakDispatcher.lock();
|
|
39
|
+
if (strongDispatcher) {
|
|
40
|
+
// the weak reference we cached is still valid - return it!
|
|
41
|
+
return strongDispatcher;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
jsi::Value dispatcherHolderValue = getRuntimeGlobalDispatcherHolder(runtime);
|
|
46
|
+
jsi::Object dispatcherHolder = dispatcherHolderValue.getObject(runtime);
|
|
47
|
+
return dispatcherHolder.getNativeState<Dispatcher>(runtime);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
jsi::Value Dispatcher::getRuntimeGlobalDispatcherHolder(jsi::Runtime &runtime) {
|
|
51
|
+
return runtime.global().getProperty(runtime, GLOBAL_DISPATCHER_HOLDER_NAME);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} // namespace margelo
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Created by Marc Rousavy on 12.03.24.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#pragma once
|
|
6
|
+
|
|
7
|
+
#include <functional>
|
|
8
|
+
#include <future>
|
|
9
|
+
#include <jsi/jsi.h>
|
|
10
|
+
#include <queue>
|
|
11
|
+
#include <unordered_map>
|
|
12
|
+
#include <utility>
|
|
13
|
+
#include <memory>
|
|
14
|
+
|
|
15
|
+
namespace margelo {
|
|
16
|
+
|
|
17
|
+
namespace jsi = facebook::jsi;
|
|
18
|
+
|
|
19
|
+
class Dispatcher : public jsi::NativeState {
|
|
20
|
+
public:
|
|
21
|
+
/**
|
|
22
|
+
Installs the Dispatcher into the given Runtime.
|
|
23
|
+
It can be accessed using `getRuntimeGlobalDispatcher` later.
|
|
24
|
+
*/
|
|
25
|
+
static void
|
|
26
|
+
installRuntimeGlobalDispatcher(jsi::Runtime &runtime,
|
|
27
|
+
std::shared_ptr<Dispatcher> dispatcher);
|
|
28
|
+
/**
|
|
29
|
+
Gets the global Dispatcher in the given Runtime, or throws an error if not
|
|
30
|
+
found.
|
|
31
|
+
*/
|
|
32
|
+
static std::shared_ptr<Dispatcher>
|
|
33
|
+
getRuntimeGlobalDispatcher(jsi::Runtime &runtime);
|
|
34
|
+
|
|
35
|
+
private:
|
|
36
|
+
static jsi::Value getRuntimeGlobalDispatcherHolder(jsi::Runtime &runtime);
|
|
37
|
+
|
|
38
|
+
public:
|
|
39
|
+
/**
|
|
40
|
+
* Run the given void function synchronously on the Thread this Dispatcher is
|
|
41
|
+
* managing.
|
|
42
|
+
*/
|
|
43
|
+
virtual void runSync(std::function<void()> &&function) = 0;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run the given void function asynchronously on the Thread this Dispatcher is
|
|
47
|
+
* managing.
|
|
48
|
+
*/
|
|
49
|
+
virtual void runAsync(std::function<void()> &&function) = 0;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Run the given function asynchronously on the Thread this Dispatcher is
|
|
53
|
+
* managing, and return a future that will hold the result of the function.
|
|
54
|
+
*/
|
|
55
|
+
template <typename T>
|
|
56
|
+
std::future<T> runAsyncAwaitable(std::function<T()> &&function) {
|
|
57
|
+
// 1. Create Promise that can be shared between this and dispatcher thread
|
|
58
|
+
auto promise = std::make_shared<std::promise<T>>();
|
|
59
|
+
std::future<T> future = promise->get_future();
|
|
60
|
+
|
|
61
|
+
runAsync([function = std::move(function), promise]() {
|
|
62
|
+
try {
|
|
63
|
+
if constexpr (std::is_void_v<T>) {
|
|
64
|
+
// 4. Call the actual function on the new Thread
|
|
65
|
+
function();
|
|
66
|
+
// 5.a. Resolve the Promise if we succeeded
|
|
67
|
+
promise->set_value();
|
|
68
|
+
} else {
|
|
69
|
+
// 4. Call the actual function on the new Thread
|
|
70
|
+
T result = function();
|
|
71
|
+
// 5.a. Resolve the Promise if we succeeded
|
|
72
|
+
promise->set_value(std::move(result));
|
|
73
|
+
}
|
|
74
|
+
} catch (...) {
|
|
75
|
+
// 5.b. Reject the Promise if the call failed
|
|
76
|
+
promise->set_exception(std::current_exception());
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// 3. Return an open future that gets resolved later by the dispatcher
|
|
81
|
+
// Thread
|
|
82
|
+
return future;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private:
|
|
86
|
+
static std::unordered_map<jsi::Runtime *, std::weak_ptr<Dispatcher>>
|
|
87
|
+
_globalCache;
|
|
88
|
+
|
|
89
|
+
private:
|
|
90
|
+
static constexpr auto TAG = "Dispatcher";
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
} // namespace margelo
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ThreadPool.cpp
|
|
3
|
+
// NitroModules
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 21.06.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#include "ThreadPool.h"
|
|
9
|
+
#include "ThreadUtils.h"
|
|
10
|
+
|
|
11
|
+
#include <utility>
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
|
|
14
|
+
namespace margelo {
|
|
15
|
+
|
|
16
|
+
ThreadPool::ThreadPool(const char *name, size_t numThreads)
|
|
17
|
+
: _isAlive(true), _name(name) {
|
|
18
|
+
for (size_t i = 0; i < numThreads; ++i) {
|
|
19
|
+
std::string threadName = std::string(name) + "-" + std::to_string(i + 1);
|
|
20
|
+
_workers.emplace_back([this, threadName] {
|
|
21
|
+
// Set the Thread's name
|
|
22
|
+
ThreadUtils::setThreadName(threadName);
|
|
23
|
+
|
|
24
|
+
// Start the run-loop
|
|
25
|
+
while (true) {
|
|
26
|
+
std::function<void()> task;
|
|
27
|
+
{
|
|
28
|
+
// Lock on the mutex so only one Worker receives the condition signal
|
|
29
|
+
// at a time
|
|
30
|
+
std::unique_lock<std::mutex> lock(_queueMutex);
|
|
31
|
+
this->_condition.wait(
|
|
32
|
+
lock, [this] { return !_isAlive || !_tasks.empty(); });
|
|
33
|
+
if (!_isAlive && _tasks.empty()) {
|
|
34
|
+
// ThreadPool is dead - stop run-loop.
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Schedule the oldest task
|
|
38
|
+
task = std::move(_tasks.front());
|
|
39
|
+
_tasks.pop();
|
|
40
|
+
}
|
|
41
|
+
// Run it (outside of the mutex so others can run in parallel)
|
|
42
|
+
task();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void ThreadPool::run(std::function<void()> &&task) {
|
|
49
|
+
{
|
|
50
|
+
// lock on the mutex - we want to emplace the task back in the queue
|
|
51
|
+
std::unique_lock<std::mutex> lock(_queueMutex);
|
|
52
|
+
if (!_isAlive) {
|
|
53
|
+
throw std::runtime_error("Cannot queue the given task - the ThreadPool "
|
|
54
|
+
"has already been stopped!");
|
|
55
|
+
}
|
|
56
|
+
_tasks.emplace(std::move(task));
|
|
57
|
+
}
|
|
58
|
+
// Notify about a new task - one of the workers will pick it up
|
|
59
|
+
_condition.notify_one();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
ThreadPool::~ThreadPool() {
|
|
63
|
+
{
|
|
64
|
+
// Lock and set `_isAlive` to false.
|
|
65
|
+
std::unique_lock<std::mutex> lock(_queueMutex);
|
|
66
|
+
_isAlive = false;
|
|
67
|
+
}
|
|
68
|
+
// Notify all workers - they will stop the work since `_isAlive` is false.
|
|
69
|
+
_condition.notify_all();
|
|
70
|
+
for (std::thread &worker : _workers) {
|
|
71
|
+
// Wait for each worker to exit.
|
|
72
|
+
worker.join();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
std::shared_ptr<ThreadPool> ThreadPool::getSharedPool() {
|
|
77
|
+
static std::shared_ptr<ThreadPool> shared;
|
|
78
|
+
if (shared == nullptr) {
|
|
79
|
+
int availableThreads = std::thread::hardware_concurrency();
|
|
80
|
+
auto numThreads = std::min(availableThreads, 3);
|
|
81
|
+
shared = std::make_shared<ThreadPool>("nitro-thread", numThreads);
|
|
82
|
+
}
|
|
83
|
+
return shared;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
} // namespace margelo
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ThreadPool.hpp
|
|
3
|
+
// NitroModules
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 21.06.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <atomic>
|
|
11
|
+
#include <condition_variable>
|
|
12
|
+
#include <functional>
|
|
13
|
+
#include <memory>
|
|
14
|
+
#include <mutex>
|
|
15
|
+
#include <queue>
|
|
16
|
+
#include <string>
|
|
17
|
+
#include <thread>
|
|
18
|
+
#include <vector>
|
|
19
|
+
|
|
20
|
+
namespace margelo {
|
|
21
|
+
|
|
22
|
+
class ThreadPool final {
|
|
23
|
+
public:
|
|
24
|
+
/**
|
|
25
|
+
* Create a new ThreadPool with the given number of fixed workers/threads.
|
|
26
|
+
*/
|
|
27
|
+
explicit ThreadPool(const char *const name, size_t numThreads);
|
|
28
|
+
~ThreadPool();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Schedules the given task asynchronously on the ThreadPool.
|
|
32
|
+
* It will run once a worker is available.
|
|
33
|
+
*/
|
|
34
|
+
void run(std::function<void()> &&task);
|
|
35
|
+
|
|
36
|
+
public:
|
|
37
|
+
/**
|
|
38
|
+
* Get a static singleton instance - a shared ThreadPool.
|
|
39
|
+
* The shared ThreadPool has 3 threads.
|
|
40
|
+
*/
|
|
41
|
+
static std::shared_ptr<ThreadPool> getSharedPool();
|
|
42
|
+
|
|
43
|
+
private:
|
|
44
|
+
std::vector<std::thread> _workers;
|
|
45
|
+
std::queue<std::function<void()>> _tasks;
|
|
46
|
+
std::mutex _queueMutex;
|
|
47
|
+
std::condition_variable _condition;
|
|
48
|
+
std::atomic<bool> _isAlive;
|
|
49
|
+
const char *_name;
|
|
50
|
+
static constexpr auto TAG = "ThreadPool";
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
} // namespace margelo
|