-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[skip ci] Add EventEmitter C++ bridging type (#44808)
Summary: Adds an `AsyncEventEmitter` class which can be used as a property of currently C++ only Turbo Modules to send type safe data back to JavaScript. Adding support for ObjC / Java Turbo Modules is possible, straight forward and can be added as an afterthought. It implements this interface ``` export type EventEmitter<T> = { addListener(handler: (T) => mixed): EventSubscription, }; ``` ## Hybrid It is a 'hybrid' object. 1.) You `addListener(handler: (T) => mixed)` in JavaScript for emitted events (coming from C++, native code) 2.) You `emit(...Arg)` events in C++, native code (getting sent to JavaScript) ## Changelog: [General] [Added] - Add EventEmitter C++ bridging type ## Facebook: Apps usually create custom functionality to achieve this kind of behavior - e.g. https://www.internalfb.com/code/fbsource/[e72bd42a028a]/arvr/js/apps/RemoteDesktopCompanion/shared/turbo_modules/TMSubscription.h Differential Revision: D57424391
- Loading branch information
1 parent
ce10ce4
commit 1066db8
Showing
5 changed files
with
237 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
packages/react-native/ReactCommon/react/bridging/EventEmitter.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <react/bridging/Function.h> | ||
#include <functional> | ||
#include <memory> | ||
#include <mutex> | ||
#include <unordered_map> | ||
|
||
#define FRIEND_TEST(test_case_name, test_name) \ | ||
friend class test_case_name##_##test_name##_Test | ||
|
||
namespace facebook::react { | ||
|
||
class EventSubscription { | ||
public: | ||
explicit EventSubscription(std::function<void()> remove) | ||
: remove_(std::move(remove)) {} | ||
~EventSubscription() = default; | ||
EventSubscription(EventSubscription&&) noexcept = default; | ||
EventSubscription& operator=(EventSubscription&&) noexcept = default; | ||
EventSubscription(const EventSubscription&) = delete; | ||
EventSubscription& operator=(const EventSubscription&) = delete; | ||
|
||
private: | ||
friend Bridging<EventSubscription>; | ||
|
||
std::function<void()> remove_; | ||
}; | ||
|
||
template <> | ||
struct Bridging<EventSubscription> { | ||
static jsi::Object toJs( | ||
jsi::Runtime& rt, | ||
const EventSubscription& eventSubscription, | ||
const std::shared_ptr<CallInvoker>& jsInvoker) { | ||
auto result = jsi::Object(rt); | ||
result.setProperty( | ||
rt, "remove", bridging::toJs(rt, eventSubscription.remove_, jsInvoker)); | ||
return result; | ||
} | ||
}; | ||
|
||
class IAsyncEventEmitter { | ||
public: | ||
IAsyncEventEmitter() noexcept = default; | ||
virtual ~IAsyncEventEmitter() noexcept = default; | ||
IAsyncEventEmitter(IAsyncEventEmitter&&) noexcept = default; | ||
IAsyncEventEmitter& operator=(IAsyncEventEmitter&&) noexcept = default; | ||
IAsyncEventEmitter(const IAsyncEventEmitter&) = delete; | ||
IAsyncEventEmitter& operator=(const IAsyncEventEmitter&) = delete; | ||
|
||
virtual jsi::Object get( | ||
jsi::Runtime& rt, | ||
const std::shared_ptr<CallInvoker>& jsInvoker) const = 0; | ||
}; | ||
|
||
template <typename... Args> | ||
class AsyncEventEmitter : public IAsyncEventEmitter { | ||
static_assert( | ||
sizeof...(Args) <= 1, | ||
"AsyncEventEmitter must have at most one argument"); | ||
|
||
public: | ||
AsyncEventEmitter() : state_(std::make_shared<SharedState>()) { | ||
listen_ = [state = state_](AsyncCallback<Args...> listener) { | ||
std::lock_guard<std::mutex> lock(state->mutex); | ||
auto listenerId = state->listenerId++; | ||
state->listeners.emplace(listenerId, std::move(listener)); | ||
return EventSubscription([state, listenerId]() { | ||
std::lock_guard<std::mutex> innerLock(state->mutex); | ||
state->listeners.erase(listenerId); | ||
}); | ||
}; | ||
} | ||
~AsyncEventEmitter() override = default; | ||
AsyncEventEmitter(AsyncEventEmitter&&) noexcept = default; | ||
AsyncEventEmitter& operator=(AsyncEventEmitter&&) noexcept = default; | ||
AsyncEventEmitter(const AsyncEventEmitter&) = delete; | ||
AsyncEventEmitter& operator=(const AsyncEventEmitter&) = delete; | ||
|
||
void emit(Args... value) { | ||
std::lock_guard<std::mutex> lock(state_->mutex); | ||
for (const auto& [_, listener] : state_->listeners) { | ||
listener.call(static_cast<Args>(value)...); | ||
} | ||
} | ||
|
||
jsi::Object get( | ||
jsi::Runtime& rt, | ||
const std::shared_ptr<CallInvoker>& jsInvoker) const override { | ||
auto result = jsi::Object(rt); | ||
result.setProperty( | ||
rt, "addListener", bridging::toJs(rt, listen_, jsInvoker)); | ||
return result; | ||
} | ||
|
||
private: | ||
friend Bridging<AsyncEventEmitter>; | ||
FRIEND_TEST(BridgingTest, eventEmitterTest); | ||
|
||
struct SharedState { | ||
std::mutex mutex; | ||
std::unordered_map<size_t, AsyncCallback<Args...>> listeners; | ||
size_t listenerId{}; | ||
}; | ||
|
||
std::function<EventSubscription(AsyncCallback<Args...>)> listen_; | ||
std::shared_ptr<SharedState> state_; | ||
}; | ||
|
||
template <typename... Args> | ||
struct Bridging<AsyncEventEmitter<Args...>> { | ||
static jsi::Object toJs( | ||
jsi::Runtime& rt, | ||
const AsyncEventEmitter<Args...>& eventEmitter, | ||
const std::shared_ptr<CallInvoker>& jsInvoker) { | ||
return eventEmitter.get(rt, jsInvoker); | ||
} | ||
}; | ||
|
||
} // namespace facebook::react |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters