Skip to content

Commit f5a7848

Browse files
committed
Added RuntimeNodeApiAsync class
1 parent 8557768 commit f5a7848

File tree

3 files changed

+211
-0
lines changed

3 files changed

+211
-0
lines changed

packages/host/android/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ add_library(node-api-host SHARED
2222
../cpp/WeakNodeApiInjector.cpp
2323
../cpp/RuntimeNodeApi.cpp
2424
../cpp/RuntimeNodeApi.hpp
25+
../cpp/RuntimeNodeApiAsync.cpp
26+
../cpp/RuntimeNodeApiAsync.hpp
2527
)
2628

2729
target_include_directories(node-api-host PRIVATE
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#include "RuntimeNodeApiAsync.hpp"
2+
#include <ReactCommon/CallInvoker.h>
3+
#include "Logger.hpp"
4+
5+
struct AsyncJob {
6+
using IdType = uint64_t;
7+
enum State { Created, Queued, Completed, Cancelled, Deleted };
8+
9+
IdType id{};
10+
State state{};
11+
napi_env env;
12+
napi_value async_resource;
13+
napi_value async_resource_name;
14+
napi_async_execute_callback execute;
15+
napi_async_complete_callback complete;
16+
void* data{nullptr};
17+
18+
static AsyncJob* fromWork(napi_async_work work) {
19+
return reinterpret_cast<AsyncJob*>(work);
20+
}
21+
static napi_async_work toWork(AsyncJob* job) {
22+
return reinterpret_cast<napi_async_work>(job);
23+
}
24+
};
25+
26+
class AsyncWorkRegistry {
27+
public:
28+
using IdType = AsyncJob::IdType;
29+
30+
std::shared_ptr<AsyncJob> create(napi_env env,
31+
napi_value async_resource,
32+
napi_value async_resource_name,
33+
napi_async_execute_callback execute,
34+
napi_async_complete_callback complete,
35+
void* data) {
36+
const auto job = std::shared_ptr<AsyncJob>(new AsyncJob{
37+
.id = next_id(),
38+
.state = AsyncJob::State::Created,
39+
.env = env,
40+
.async_resource = async_resource,
41+
.async_resource_name = async_resource_name,
42+
.execute = execute,
43+
.complete = complete,
44+
.data = data,
45+
});
46+
47+
jobs_[job->id] = job;
48+
return job;
49+
}
50+
51+
std::shared_ptr<AsyncJob> get(napi_async_work work) const {
52+
const auto job = AsyncJob::fromWork(work);
53+
if (!job) {
54+
return {};
55+
}
56+
if (const auto it = jobs_.find(job->id); it != jobs_.end()) {
57+
return it->second;
58+
}
59+
return {};
60+
}
61+
62+
bool release(IdType id) {
63+
if (const auto it = jobs_.find(id); it != jobs_.end()) {
64+
it->second->state = AsyncJob::State::Deleted;
65+
jobs_.erase(it);
66+
return true;
67+
}
68+
return false;
69+
}
70+
71+
private:
72+
IdType next_id() {
73+
if (current_id_ == std::numeric_limits<IdType>::max()) [[unlikely]] {
74+
current_id_ = 0;
75+
}
76+
return ++current_id_;
77+
}
78+
79+
IdType current_id_{0};
80+
std::unordered_map<IdType, std::shared_ptr<AsyncJob>> jobs_;
81+
};
82+
83+
static std::weak_ptr<facebook::react::CallInvoker> callInvoker;
84+
static AsyncWorkRegistry asyncWorkRegistry;
85+
86+
namespace callstack::nodeapihost {
87+
88+
void setCallInvoker(
89+
const std::shared_ptr<facebook::react::CallInvoker>& invoker) {
90+
callInvoker = invoker;
91+
}
92+
93+
napi_status napi_create_async_work(napi_env env,
94+
napi_value async_resource,
95+
napi_value async_resource_name,
96+
napi_async_execute_callback execute,
97+
napi_async_complete_callback complete,
98+
void* data,
99+
napi_async_work* result) {
100+
const auto job = asyncWorkRegistry.create(
101+
env, async_resource, async_resource_name, execute, complete, data);
102+
if (!job) {
103+
log_debug("Error: Failed to create async work job");
104+
return napi_generic_failure;
105+
}
106+
107+
*result = AsyncJob::toWork(job.get());
108+
return napi_ok;
109+
}
110+
111+
napi_status napi_queue_async_work(
112+
node_api_basic_env env, napi_async_work work) {
113+
const auto job = asyncWorkRegistry.get(work);
114+
if (!job) {
115+
log_debug("Error: Received null job in napi_queue_async_work");
116+
return napi_invalid_arg;
117+
}
118+
119+
const auto invoker = callInvoker.lock();
120+
if (!invoker) {
121+
log_debug("Error: No CallInvoker available for async work");
122+
return napi_invalid_arg;
123+
}
124+
125+
invoker->invokeAsync([env, weakJob = std::weak_ptr{job}]() {
126+
const auto job = weakJob.lock();
127+
if (!job) {
128+
log_debug("Error: Async job has been deleted before execution");
129+
return;
130+
}
131+
if (job->state == AsyncJob::State::Queued) {
132+
job->execute(job->env, job->data);
133+
}
134+
135+
job->complete(env,
136+
job->state == AsyncJob::State::Cancelled ? napi_cancelled : napi_ok,
137+
job->data);
138+
job->state = AsyncJob::State::Completed;
139+
});
140+
141+
job->state = AsyncJob::State::Queued;
142+
return napi_ok;
143+
}
144+
145+
napi_status napi_delete_async_work(
146+
node_api_basic_env env, napi_async_work work) {
147+
const auto job = asyncWorkRegistry.get(work);
148+
if (!job) {
149+
log_debug("Error: Received non-existent job in napi_delete_async_work");
150+
return napi_invalid_arg;
151+
}
152+
153+
if (!asyncWorkRegistry.release(job->id)) {
154+
log_debug("Error: Failed to release async work job");
155+
return napi_generic_failure;
156+
}
157+
158+
return napi_ok;
159+
}
160+
161+
napi_status napi_cancel_async_work(
162+
node_api_basic_env env, napi_async_work work) {
163+
const auto job = asyncWorkRegistry.get(work);
164+
if (!job) {
165+
log_debug("Error: Received null job in napi_cancel_async_work");
166+
return napi_invalid_arg;
167+
}
168+
switch (job->state) {
169+
case AsyncJob::State::Completed:
170+
log_debug("Error: Cannot cancel async work that is already completed");
171+
return napi_generic_failure;
172+
case AsyncJob::State::Deleted:
173+
log_debug("Warning: Async work job is already deleted");
174+
return napi_generic_failure;
175+
case AsyncJob::State::Cancelled:
176+
log_debug("Warning: Async work job is already cancelled");
177+
return napi_ok;
178+
}
179+
180+
job->state = AsyncJob::State::Cancelled;
181+
return napi_ok;
182+
}
183+
} // namespace callstack::nodeapihost
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#pragma once
2+
3+
#include <ReactCommon/CallInvoker.h>
4+
#include <memory>
5+
#include "node_api.h"
6+
7+
namespace callstack::nodeapihost {
8+
void setCallInvoker(
9+
const std::shared_ptr<facebook::react::CallInvoker>& invoker);
10+
11+
napi_status napi_create_async_work(napi_env env,
12+
napi_value async_resource,
13+
napi_value async_resource_name,
14+
napi_async_execute_callback execute,
15+
napi_async_complete_callback complete,
16+
void* data,
17+
napi_async_work* result);
18+
19+
napi_status napi_queue_async_work(node_api_basic_env env, napi_async_work work);
20+
21+
napi_status napi_delete_async_work(
22+
node_api_basic_env env, napi_async_work work);
23+
24+
napi_status napi_cancel_async_work(
25+
node_api_basic_env env, napi_async_work work);
26+
} // namespace callstack::nodeapihost

0 commit comments

Comments
 (0)