-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Implement REACT_EVENT_EMITTER and add to the SampleTurboModule module #13533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "type": "prerelease", | ||
| "comment": "Implement SampleTurboModule", | ||
| "packageName": "react-native-windows", | ||
| "email": "[email protected]", | ||
| "dependentChangeType": "patch" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -113,6 +113,16 @@ | |
| #define REACT_EVENT(/* field, [opt] eventName, [opt] eventEmitterName */...) \ | ||
| INTERNAL_REACT_MEMBER(__VA_ARGS__)(EventField, __VA_ARGS__) | ||
|
|
||
| // REACT_EVENT_EMITTER(field, [opt] eventName, [opt] eventEmitterName) | ||
| // Arguments: | ||
| // - field (required) - the field name the macro is attached to. | ||
| // - eventEmitterName (optional) - the JavaScript eventEmitter name. Default is the field name. | ||
| // REACT_EVENT_EMITTER annotates a field that helps raise a JavaScript event. | ||
| // The field type can be any std::function like type. E.g. Func<void(Args...)>. | ||
| // It must be an instance field. | ||
| #define REACT_EVENT_EMITTER(/* field, [opt] eventEmitterName */...) \ | ||
| INTERNAL_REACT_MEMBER(__VA_ARGS__)(EventEmitterField, __VA_ARGS__) | ||
|
|
||
| // REACT_FUNCTION(field, [opt] functionName, [opt] moduleName) | ||
| // Arguments: | ||
| // - field (required) - the field name the macro is attached to. | ||
|
|
@@ -832,6 +842,35 @@ struct ModuleEventFieldInfo<TFunc<void(TArgs...)> TModule::*> { | |
| } | ||
| }; | ||
|
|
||
| template <class TField> | ||
| struct ModuleEventEmitterFieldInfo; | ||
|
|
||
| template <class TModule, template <class> class TFunc, class... TArgs> | ||
| struct ModuleEventEmitterFieldInfo<TFunc<void(TArgs...)> TModule::*> { | ||
| using ModuleType = TModule; | ||
| using EventType = TFunc<void(TArgs...)>; | ||
| using FieldType = EventType TModule::*; | ||
|
|
||
| static EventEmitterInitializerDelegate GetEventEmitterInitializer(void *module, FieldType field) noexcept { | ||
| return [module = static_cast<ModuleType *>(module), field](EmitEventSetterDelegate const &emitEventDelegate) { | ||
| module->*field = [emitEventDelegate](TArgs... args) noexcept { | ||
| emitEventDelegate([&args...]([[maybe_unused]] IJSValueWriter const &argWriter) noexcept { | ||
| (void)argWriter; // [[maybe_unused]] above does not work | ||
| (WriteValue(argWriter, args), ...); | ||
| }); | ||
| }; | ||
| }; | ||
| } | ||
|
|
||
| static InitializerDelegate GetEventEmitterEmptyInitializer(void *module, FieldType field) noexcept { | ||
| return [module = static_cast<ModuleType *>(module), field](IReactContext const &reactContext) noexcept { | ||
| // Default emitter will do nothing | ||
| // This will be replaced with a method that will call the jsi EventEmitter when JS requests the emitter | ||
| module->*field = [](TArgs... args) noexcept {}; | ||
| }; | ||
| } | ||
| }; | ||
|
|
||
| template <class TField> | ||
| struct ModuleFunctionFieldInfo; | ||
|
|
||
|
|
@@ -898,6 +937,7 @@ enum class ReactMemberKind { | |
| ConstantStrongTypedMethod, | ||
| ConstantField, | ||
| EventField, | ||
| EventEmitterField, | ||
| FunctionField, | ||
| }; | ||
|
|
||
|
|
@@ -917,6 +957,7 @@ using ReactConstantMethodAttribute = ReactMemberAttribute<ReactMemberKind::Const | |
| using ReactConstantStrongTypedMethodAttribute = ReactMemberAttribute<ReactMemberKind::ConstantStrongTypedMethod>; | ||
| using ReactConstantFieldAttribute = ReactMemberAttribute<ReactMemberKind::ConstantField>; | ||
| using ReactEventFieldAttribute = ReactMemberAttribute<ReactMemberKind::EventField>; | ||
| using ReactEventEmitterFieldAttribute = ReactMemberAttribute<ReactMemberKind::EventEmitterField>; | ||
| using ReactFunctionFieldAttribute = ReactMemberAttribute<ReactMemberKind::FunctionField>; | ||
|
|
||
| template <class T> | ||
|
|
@@ -941,7 +982,7 @@ struct ReactModuleBuilder { | |
| } | ||
|
|
||
| void CompleteRegistration() noexcept { | ||
| // Add REACT_INIT initializers after REACT_EVENT and REACT_FUNCTION initializers. | ||
| // Add REACT_INIT initializers after REACT_EVENT, REACT_EVENT_EMITTER and REACT_FUNCTION initializers. | ||
| // This way REACT_INIT method is invoked after event and function fields are initialized. | ||
| for (auto &initializer : m_initializers) { | ||
| m_moduleBuilder.AddInitializer(initializer); | ||
|
|
@@ -967,6 +1008,8 @@ struct ReactModuleBuilder { | |
| RegisterConstantField(member, attributeInfo.JSMemberName); | ||
| } else if constexpr (std::is_same_v<TAttribute, ReactEventFieldAttribute>) { | ||
| RegisterEventField(member, attributeInfo.JSMemberName, attributeInfo.JSModuleName); | ||
| } else if constexpr (std::is_same_v<TAttribute, ReactEventEmitterFieldAttribute>) { | ||
| RegisterEventEmitterField(member, attributeInfo.JSMemberName, attributeInfo.JSModuleName); | ||
| } else if constexpr (std::is_same_v<TAttribute, ReactFunctionFieldAttribute>) { | ||
| RegisterFunctionField(member, attributeInfo.JSMemberName, attributeInfo.JSModuleName); | ||
| } | ||
|
|
@@ -1017,6 +1060,19 @@ struct ReactModuleBuilder { | |
| m_moduleBuilder.AddInitializer(eventHandlerInitializer); | ||
| } | ||
|
|
||
| template <class TField> | ||
| void RegisterEventEmitterField( | ||
| TField field, | ||
| std::wstring_view eventName, | ||
| std::wstring_view eventEmitterName = L"") noexcept { | ||
| auto eventHandlerEmptyInitializer = | ||
| ModuleEventEmitterFieldInfo<TField>::GetEventEmitterEmptyInitializer(m_module, field); | ||
| auto eventEmitterInitializer = ModuleEventEmitterFieldInfo<TField>::GetEventEmitterInitializer(m_module, field); | ||
|
|
||
| m_moduleBuilder.AddInitializer(eventHandlerEmptyInitializer); | ||
| m_moduleBuilder.AddEventEmitter(eventName, eventEmitterInitializer); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| template <class TField> | ||
| void RegisterFunctionField(TField field, std::wstring_view name, std::wstring_view moduleName = L"") noexcept { | ||
| auto functionInitializer = ModuleFunctionFieldInfo<TField>::GetFunctionInitializer( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,6 +129,10 @@ void ReactModuleBuilder::AddSyncMethod(hstring const &name, SyncMethodDelegate c | |
| m_methods.push_back(std::move(cxxMethod)); | ||
| } | ||
|
|
||
| void ReactModuleBuilder::AddEventEmitter(hstring const &name, EventEmitterInitializerDelegate const &emitter) noexcept { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Event emitters are only supported on TurboModules. The ReactModuleBuilder is used when the module is running as a native module. We could choose to throw and not allow the module to run at all if it's being run as a NativeModule. I was leaning towards allowing at least the rest of the module to continue to work if its run in that mode. But I'd be ok making this throw. |
||
| // No-op. We only support auto generated EventEmitters in TurboModules | ||
| } | ||
|
|
||
| /*static*/ MethodResultCallback ReactModuleBuilder::MakeMethodResultCallback(CxxModule::Callback &&callback) noexcept { | ||
| if (callback) { | ||
| return [callback = std::move(callback)](const IJSValueWriter &outputWriter) noexcept { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #include "pch.h" | ||
| #include "SampleTurboModule.h" | ||
|
|
||
| namespace Microsoft::ReactNative { | ||
|
|
||
| void SampleTurboModule::Initialize(winrt::Microsoft::ReactNative::ReactContext const &reactContext) noexcept { | ||
| m_reactContext = reactContext; | ||
| } | ||
|
|
||
| ReactNativeSpecs::SampleTurboModuleSpec_Constants SampleTurboModule::GetConstants() noexcept { | ||
| ReactNativeSpecs::SampleTurboModuleSpec_Constants constants; | ||
| constants.const1 = true; | ||
| constants.const2 = 375; | ||
| constants.const3 = "something"; | ||
| return constants; | ||
| } | ||
|
|
||
| void SampleTurboModule::voidFunc() noexcept { | ||
| onPress(); | ||
| onClick("click"); | ||
|
|
||
| /* | ||
| SampleTurboModuleSpec_ObjectStruct will be added in a future version of SampleTurboModuleSpec | ||
| Uncomment below when we FI to that version | ||
| */ | ||
| /* | ||
| ReactNativeSpecs::SampleTurboModuleSpec_ObjectStruct obj; | ||
| obj.a = 1; | ||
| obj.b = "two"; | ||
| onChange(obj); | ||
|
|
||
| ReactNativeSpecs::SampleTurboModuleSpec_ObjectStruct obj2; | ||
| obj2.a = 3; | ||
| obj2.b = "four"; | ||
|
|
||
| auto writer = winrt::Microsoft::ReactNative::MakeJSValueTreeWriter(); | ||
| writer.WriteArrayBegin(); | ||
| winrt::Microsoft::ReactNative::WriteValue(writer, obj); | ||
| winrt::Microsoft::ReactNative::WriteValue(writer, obj2); | ||
| writer.WriteArrayEnd(); | ||
| onSubmit(winrt::Microsoft::ReactNative::TakeJSValue(writer).MoveArray()); | ||
| */ | ||
| } | ||
|
|
||
| bool SampleTurboModule::getBool(bool arg) noexcept { | ||
| return arg; | ||
| } | ||
|
|
||
| double SampleTurboModule::getEnum(double arg) noexcept { | ||
| return arg; | ||
| } | ||
|
|
||
| double SampleTurboModule::getNumber(double arg) noexcept { | ||
| return arg; | ||
| } | ||
|
|
||
| std::string SampleTurboModule::getString(std::string arg) noexcept { | ||
| return std::string(arg); | ||
| } | ||
|
|
||
| ::React::JSValueArray SampleTurboModule::getArray(::React::JSValueArray &&arg) noexcept { | ||
| return arg.Copy(); | ||
| } | ||
|
|
||
| ::React::JSValue SampleTurboModule::getObject(::React::JSValue &&arg) noexcept { | ||
| assert(arg.Type() == ::React::JSValueType::Object); | ||
| return arg.Copy(); | ||
| } | ||
|
|
||
| ::React::JSValue SampleTurboModule::getUnsafeObject(::React::JSValue &&arg) noexcept { | ||
| assert(arg.Type() == ::React::JSValueType::Object); | ||
| return arg.Copy(); | ||
| } | ||
|
|
||
| double SampleTurboModule::getRootTag(double arg) noexcept { | ||
| // TODO: Proper impl | ||
| return arg; | ||
| } | ||
|
|
||
| ::React::JSValue SampleTurboModule::getValue(double x, std::string y, ::React::JSValue &&z) noexcept { | ||
| return ::React::JSValueObject{ | ||
| {"x", x}, | ||
| {"y", y}, | ||
| {"z", z.Copy()}, | ||
| }; | ||
| } | ||
|
|
||
| void SampleTurboModule::getValueWithCallback(std::function<void(std::string)> const &callback) noexcept { | ||
| callback("value from callback!"); | ||
| } | ||
|
|
||
| void SampleTurboModule::getValueWithPromise(bool error, ::React::ReactPromise<std::string> &&result) noexcept { | ||
| if (error) { | ||
| result.Reject("intentional promise rejection"); | ||
| } else { | ||
| result.Resolve("result!"); | ||
| } | ||
| } | ||
|
|
||
| void SampleTurboModule::voidFuncThrows() noexcept { | ||
| // TODO: Proper impl | ||
| } | ||
|
|
||
| ::React::JSValue SampleTurboModule::getObjectThrows(::React::JSValue &&arg) noexcept { | ||
| // TODO: Proper impl | ||
| return nullptr; | ||
| } | ||
|
|
||
| void SampleTurboModule::promiseThrows(::React::ReactPromise<void> &&result) noexcept { | ||
| // TODO: Proper impl | ||
| } | ||
|
|
||
| void SampleTurboModule::voidFuncAssert() noexcept { | ||
| // TODO: Proper impl | ||
| } | ||
|
|
||
| ::React::JSValue SampleTurboModule::getObjectAssert(::React::JSValue &&arg) noexcept { | ||
| // TODO: Proper impl | ||
| return nullptr; | ||
| } | ||
|
|
||
| void SampleTurboModule::promiseAssert(::React::ReactPromise<void> &&result) noexcept { | ||
| // TODO: Proper impl | ||
| } | ||
|
|
||
| } // namespace Microsoft::ReactNative |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have the event name or not? I see it is not documented below.