Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -84,6 +84,8 @@ void ReactModuleBuilderMock::AddSyncMethod(hstring const &name, SyncMethodDelega
m_syncMethods.emplace(name, method);
}

void ReactModuleBuilderMock::AddEventEmitter(hstring const &, EventEmitterInitializerDelegate const &) noexcept {}

JSValueObject ReactModuleBuilderMock::GetConstants() noexcept {
auto constantWriter = MakeJSValueTreeWriter();
constantWriter.WriteObjectBegin();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ struct ReactModuleBuilderMock {
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
void AddEventEmitter(hstring const &name, EventEmitterInitializerDelegate const &emitter) noexcept;

private:
MethodDelegate GetMethod0(std::wstring const &methodName) const noexcept;
Expand Down Expand Up @@ -218,6 +219,7 @@ struct ReactModuleBuilderImpl : implements<ReactModuleBuilderImpl, IReactModuleB
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
void AddEventEmitter(hstring const &name, EventEmitterInitializerDelegate const &emitter) noexcept;

private:
ReactModuleBuilderMock &m_mock;
Expand Down Expand Up @@ -352,4 +354,10 @@ inline void ReactModuleBuilderImpl::AddSyncMethod(hstring const &name, SyncMetho
m_mock.AddSyncMethod(name, method);
}

inline void ReactModuleBuilderImpl::AddEventEmitter(
hstring const &name,
EventEmitterInitializerDelegate const &emitter) noexcept {
m_mock.AddEventEmitter(name, emitter);
}

} // namespace winrt::Microsoft::ReactNative
58 changes: 57 additions & 1 deletion vnext/Microsoft.ReactNative.Cxx/NativeModules.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eventName

Do we have the event name or not? I see it is not documented below.

// 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.
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -898,6 +937,7 @@ enum class ReactMemberKind {
ConstantStrongTypedMethod,
ConstantField,
EventField,
EventEmitterField,
FunctionField,
};

Expand All @@ -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>
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_moduleBuilder

We must not have two calls here. There must be only one of them.

}

template <class TField>
void RegisterFunctionField(TField field, std::wstring_view name, std::wstring_view moduleName = L"") noexcept {
auto functionInitializer = ModuleFunctionFieldInfo<TField>::GetFunctionInitializer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ public void EmitJSEvent(string eventEmitterName, string eventName, JSValueArgWri
writer.WriteArrayEnd();
m_jsEventHandler(eventEmitterName, eventName, writer.TakeValue());
}

public void AddEventEmitter(string name, EventEmitterInitializerDelegate emitter) { }
}

class ReactSettingsSnapshot : IReactSettingsSnapshot
Expand Down
4 changes: 4 additions & 0 deletions vnext/Microsoft.ReactNative/IReactModuleBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddEventEmitter

Why do we need this method then? See the event implementation - we only need the AddInitializer call.

Copy link
Contributor

Choose a reason for hiding this comment

The 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 {
Expand Down
1 change: 1 addition & 0 deletions vnext/Microsoft.ReactNative/IReactModuleBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ReactModuleBuilder : winrt::implements<ReactModuleBuilder, IReactModuleBu
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
void AddEventEmitter(hstring const &name, EventEmitterInitializerDelegate const &emitter) noexcept;

public:
std::unique_ptr<facebook::xplat::module::CxxModule> MakeCxxModule(
Expand Down
9 changes: 9 additions & 0 deletions vnext/Microsoft.ReactNative/IReactModuleBuilder.idl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ namespace Microsoft.ReactNative
MethodResultCallback resolve,
MethodResultCallback reject);

DOC_STRING("A delegate gets the arguments for an event emit on a EventEmitter.")
delegate void EmitEventSetterDelegate(JSValueArgWriter argWriter);

DOC_STRING("A delegate to call a turbo module EventEmitter.")
delegate void EventEmitterInitializerDelegate(EmitEventSetterDelegate emitter);

DOC_STRING("A delegate to call native synchronous method.")
delegate void SyncMethodDelegate(IJSValueReader inputReader, IJSValueWriter outputWriter);

Expand Down Expand Up @@ -62,5 +68,8 @@ namespace Microsoft.ReactNative

DOC_STRING("Adds a synchronous method to the native module. See @SyncMethodDelegate.")
void AddSyncMethod(String name, SyncMethodDelegate method);

DOC_STRING("Adds an EventEmitter to the turbo module. See @EventEmitterInitializerDelegate.")
void AddEventEmitter(String name, EventEmitterInitializerDelegate emitter);
};
} // namespace Microsoft.ReactNative
129 changes: 129 additions & 0 deletions vnext/Microsoft.ReactNative/Modules/SampleTurboModule.cpp
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
Loading