Skip to content

Commit 1b4f560

Browse files
authored
Add support for turbomodule eventemitters, and codegen (#13555)
* Add support for turbomodule eventemitters, and codegen * Change files * fix * fix * fix * Minor review feedback * format
1 parent a8bc907 commit 1b4f560

19 files changed

+506
-33
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Add support for turbomodule eventemitters, and codegen",
4+
"packageName": "@react-native-windows/codegen",
5+
"email": "30809111+acoates-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Add support for turbomodule eventemitters, and codegen",
4+
"packageName": "react-native-windows",
5+
"email": "30809111+acoates-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/@react-native-windows/codegen/src/generators/GenerateNM2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ export function createNM2Generator({
118118
});
119119
let tuples = `
120120
static constexpr auto methods = std::tuple{
121-
${methods[0]}
121+
${methods.traversedPropertyTuples}${methods.traversedEventEmitterTuples ? '\n' : ''}${methods.traversedEventEmitterTuples}
122122
};`;
123123
let checks = `
124124
constexpr auto methodCheckResults = CheckMethods<TModule, ::_MODULE_NAME_::Spec>();`;
125-
let errors = methods[1];
125+
let errors = methods.traversedProperties + (methods.traversedEventEmitters ? '\n' : '') + methods.traversedEventEmitters;
126126

127127
// prepare constants
128128
const constants = generateValidateConstants(nativeModule, aliases);

packages/@react-native-windows/codegen/src/generators/ParamTypes.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import type {
1010
NamedShape,
1111
NativeModuleArrayTypeAnnotation,
1212
NativeModuleBaseTypeAnnotation,
13+
NativeModuleEventEmitterBaseTypeAnnotation,
14+
NativeModuleEventEmitterTypeAnnotation,
1315
NativeModuleEnumDeclaration,
1416
NativeModuleFunctionTypeAnnotation,
1517
NativeModuleParamTypeAnnotation,
@@ -141,6 +143,39 @@ function translateArray(
141143
}
142144
}
143145

146+
// Hopefully eventually NativeModuleEventEmitterTypeAnnotation will align better with NativeModuleParamTypeAnnotation
147+
// and this method can be merged / replaced with translateArray
148+
function translateEventEmitterArray(
149+
param: {
150+
readonly type: 'ArrayTypeAnnotation';
151+
readonly elementType: NativeModuleEventEmitterBaseTypeAnnotation | {type: string};
152+
},
153+
aliases: AliasMap,
154+
baseAliasName: string,
155+
target: ParamTarget,
156+
options: CppCodegenOptions,
157+
): string {
158+
switch (target) {
159+
case 'spec':
160+
case 'template':
161+
return `std::vector<${translateEventEmitterParam(
162+
param.elementType as NativeModuleEventEmitterBaseTypeAnnotation,
163+
aliases,
164+
`${baseAliasName}_element`,
165+
'template',
166+
options,
167+
)}>`;
168+
default:
169+
return `std::vector<${translateEventEmitterParam(
170+
param.elementType as NativeModuleEventEmitterBaseTypeAnnotation,
171+
aliases,
172+
`${baseAliasName}_element`,
173+
'template',
174+
options,
175+
)}> const &`;
176+
}
177+
}
178+
144179
function translateParam(
145180
param: NativeModuleParamTypeAnnotation,
146181
aliases: AliasMap,
@@ -193,6 +228,39 @@ function translateParam(
193228
}
194229
}
195230

231+
// Hopefully eventually NativeModuleEventEmitterTypeAnnotation will align better with NativeModuleParamTypeAnnotation
232+
// and this method can be merged / replaced with translateParam
233+
function translateEventEmitterParam(
234+
param: NativeModuleEventEmitterTypeAnnotation,
235+
aliases: AliasMap,
236+
baseAliasName: string,
237+
target: ParamTarget,
238+
options: CppCodegenOptions,
239+
): string {
240+
// avoid: Property 'type' does not exist on type 'never'
241+
const paramType = param.type;
242+
switch (param.type) {
243+
case 'StringTypeAnnotation':
244+
return options.cppStringType;
245+
case 'NumberTypeAnnotation':
246+
case 'FloatTypeAnnotation':
247+
case 'DoubleTypeAnnotation':
248+
return 'double';
249+
case 'Int32TypeAnnotation':
250+
return 'int';
251+
case 'BooleanTypeAnnotation':
252+
return 'bool';
253+
case 'ArrayTypeAnnotation':
254+
return translateEventEmitterArray(param, aliases, baseAliasName, target, options);
255+
case 'TypeAliasTypeAnnotation':
256+
return decorateType(getAliasCppName(param.name), target);
257+
case 'VoidTypeAnnotation':
258+
return 'void';
259+
default:
260+
throw new Error(`Unhandled type in translateParam: ${paramType}`);
261+
}
262+
}
263+
196264
function translateNullableParamType(
197265
paramType: Nullable<NativeModuleParamTypeAnnotation>,
198266
aliases: AliasMap,
@@ -280,6 +348,22 @@ export function translateSpecArgs(
280348
});
281349
}
282350

351+
export function translateEventEmitterArgs(
352+
params: NativeModuleEventEmitterTypeAnnotation,
353+
aliases: AliasMap,
354+
baseAliasName: string,
355+
options: CppCodegenOptions,
356+
) {
357+
const translatedParam = translateEventEmitterParam(
358+
params,
359+
aliases,
360+
baseAliasName,
361+
'spec',
362+
options,
363+
);
364+
return `${translatedParam}`;
365+
}
366+
283367
export function translateArgs(
284368
params: ReadonlyArray<NativeModuleParamShape>,
285369
aliases: AliasMap,

packages/@react-native-windows/codegen/src/generators/ValidateMethods.ts

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
'use strict';
88

99
import type {
10+
NativeModuleEventEmitterShape,
1011
NativeModuleFunctionTypeAnnotation,
1112
NativeModulePropertyShape,
1213
NativeModuleSchema,
1314
} from '@react-native/codegen/lib/CodegenSchema';
1415
import {AliasMap} from './AliasManaging';
1516
import type {CppCodegenOptions} from './ObjectTypes';
16-
import {translateArgs, translateSpecArgs} from './ParamTypes';
17+
import {translateArgs, translateSpecArgs, translateEventEmitterArgs} from './ParamTypes';
1718
import {translateImplReturnType, translateSpecReturnType} from './ReturnTypes';
1819

1920
function isMethodSync(funcType: NativeModuleFunctionTypeAnnotation) {
@@ -91,9 +92,9 @@ function renderProperties(
9192
aliases: AliasMap,
9293
tuple: boolean,
9394
options: CppCodegenOptions,
94-
): string {
95+
): { code: string, numberOfProperties: number } {
9596
// TODO: generate code for constants
96-
return methods
97+
const properties = methods
9798
.filter(prop => prop.name !== 'getConstants')
9899
.map((prop, index) => {
99100
// TODO: prop.optional === true
@@ -151,6 +152,66 @@ function renderProperties(
151152
options,
152153
)});`;
153154
}
155+
});
156+
157+
return {code: properties.join('\n'), numberOfProperties: properties.length};
158+
}
159+
160+
function getPossibleEventEmitterSignatures(
161+
eventEmitter: NativeModuleEventEmitterShape,
162+
aliases: AliasMap,
163+
options: CppCodegenOptions): string[] {
164+
165+
const traversedArgs = translateEventEmitterArgs(
166+
eventEmitter.typeAnnotation.typeAnnotation,
167+
aliases,
168+
eventEmitter.name,
169+
options,
170+
);
171+
return [`REACT_EVENT(${eventEmitter.name}) std::function<void(${traversedArgs})> ${eventEmitter.name};`]
172+
}
173+
174+
function translatePossibleEventSignatures(
175+
eventEmitter: NativeModuleEventEmitterShape,
176+
aliases: AliasMap,
177+
options: CppCodegenOptions): string {
178+
return getPossibleEventEmitterSignatures(
179+
eventEmitter,
180+
aliases,
181+
options
182+
)
183+
.map(sig => `" ${sig}\\n"`)
184+
.join('\n ');
185+
}
186+
187+
function renderEventEmitters(
188+
eventEmitters: ReadonlyArray<NativeModuleEventEmitterShape>,
189+
indexOffset: number,
190+
aliases: AliasMap,
191+
tuple: boolean,
192+
options: CppCodegenOptions,
193+
): string {
194+
return eventEmitters
195+
.map((eventEmitter, index) => {
196+
const traversedArgs = translateEventEmitterArgs(
197+
eventEmitter.typeAnnotation.typeAnnotation,
198+
aliases,
199+
eventEmitter.name,
200+
options,
201+
);
202+
203+
if (tuple) {
204+
return ` EventEmitter<void(${traversedArgs})>{${index + indexOffset}, L"${eventEmitter.name}"},`;
205+
} else {
206+
return ` REACT_SHOW_EVENTEMITTER_SPEC_ERRORS(
207+
${index + indexOffset},
208+
"${eventEmitter.name}",
209+
${translatePossibleEventSignatures(
210+
eventEmitter,
211+
aliases,
212+
options,
213+
)});`;
214+
}
154215
})
155216
.join('\n');
156217
}
@@ -159,19 +220,39 @@ export function generateValidateMethods(
159220
nativeModule: NativeModuleSchema,
160221
aliases: AliasMap,
161222
options: CppCodegenOptions,
162-
): [string, string] {
223+
): {
224+
traversedProperties: string,
225+
traversedEventEmitters: string,
226+
traversedPropertyTuples: string,
227+
traversedEventEmitterTuples: string,
228+
} {
163229
const methods = nativeModule.spec.methods;
230+
const eventEmitters = nativeModule.spec.eventEmitters;
164231
const traversedProperties = renderProperties(
165232
methods,
166233
aliases,
167234
false,
168235
options,
169236
);
237+
const traversedEventEmitters = renderEventEmitters(
238+
eventEmitters,
239+
traversedProperties.numberOfProperties,
240+
aliases,
241+
false,
242+
options,
243+
);
170244
const traversedPropertyTuples = renderProperties(
171245
methods,
172246
aliases,
173247
true,
174248
options,
175249
);
176-
return [traversedPropertyTuples, traversedProperties];
250+
const traversedEventEmitterTuples = renderEventEmitters(
251+
eventEmitters,
252+
traversedProperties.numberOfProperties,
253+
aliases,
254+
true,
255+
options,
256+
);
257+
return {traversedPropertyTuples: traversedPropertyTuples.code, traversedEventEmitterTuples, traversedProperties: traversedProperties.code, traversedEventEmitters};
177258
}

packages/@react-native-windows/tester/src/js/examples/TurboModule/SampleTurboModuleExample.windows.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,6 @@ class SampleTurboModuleExample extends React.Component<{||}, State> {
225225
Windows]
226226
*/
227227

228-
/*
229-
[Windows Wee need to implement both a proper SampleTurboModule (issue #13531) and add EventEmitter support (issue #13532)
230228
this.eventSubscriptions.push(
231229
NativeSampleTurboModule.onPress(value => console.log('onPress: ()')),
232230
);
@@ -245,8 +243,6 @@ class SampleTurboModuleExample extends React.Component<{||}, State> {
245243
console.log(`onSubmit: (${JSON.stringify(value)})`),
246244
),
247245
);
248-
Windows]
249-
*/
250246
}
251247

252248
componentWillUnmount() {

vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ void ReactModuleBuilderMock::AddSyncMethod(hstring const &name, SyncMethodDelega
8484
m_syncMethods.emplace(name, method);
8585
}
8686

87+
void ReactModuleBuilderMock::AddEventEmitter(hstring const &, EventEmitterInitializerDelegate const &) noexcept {}
88+
8789
JSValueObject ReactModuleBuilderMock::GetConstants() noexcept {
8890
auto constantWriter = MakeJSValueTreeWriter();
8991
constantWriter.WriteObjectBegin();

vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ struct ReactModuleBuilderMock {
7373
void AddConstantProvider(ConstantProviderDelegate const &constantProvider) noexcept;
7474
void AddMethod(hstring const &name, MethodReturnType returnType, MethodDelegate const &method) noexcept;
7575
void AddSyncMethod(hstring const &name, SyncMethodDelegate const &method) noexcept;
76+
void AddEventEmitter(hstring const &name, EventEmitterInitializerDelegate const &emitter) noexcept;
7677

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

222224
private:
223225
ReactModuleBuilderMock &m_mock;
@@ -352,4 +354,10 @@ inline void ReactModuleBuilderImpl::AddSyncMethod(hstring const &name, SyncMetho
352354
m_mock.AddSyncMethod(name, method);
353355
}
354356

357+
inline void ReactModuleBuilderImpl::AddEventEmitter(
358+
hstring const &name,
359+
EventEmitterInitializerDelegate const &emitter) noexcept {
360+
m_mock.AddEventEmitter(name, emitter);
361+
}
362+
355363
} // namespace winrt::Microsoft::ReactNative

vnext/Microsoft.ReactNative.Cxx/ModuleRegistration.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ ModuleRegistration::ModuleRegistration(wchar_t const *moduleName) noexcept : m_m
1717

1818
void AddAttributedModules(IReactPackageBuilder const &packageBuilder, bool useTurboModules) noexcept {
1919
for (auto const *reg = ModuleRegistration::Head(); reg != nullptr; reg = reg->Next()) {
20-
if (useTurboModules)
20+
if (useTurboModules || reg->ShouldRegisterAsTurboModule())
2121
packageBuilder.AddTurboModule(reg->ModuleName(), reg->MakeModuleProvider());
2222
else
2323
packageBuilder.AddModule(reg->ModuleName(), reg->MakeModuleProvider());
@@ -30,7 +30,7 @@ bool TryAddAttributedModule(
3030
bool useTurboModule) noexcept {
3131
for (auto const *reg = ModuleRegistration::Head(); reg != nullptr; reg = reg->Next()) {
3232
if (moduleName == reg->ModuleName()) {
33-
if (useTurboModule) {
33+
if (useTurboModule || reg->ShouldRegisterAsTurboModule()) {
3434
packageBuilder.AddTurboModule(moduleName, reg->MakeModuleProvider());
3535
} else {
3636
packageBuilder.AddModule(moduleName, reg->MakeModuleProvider());

0 commit comments

Comments
 (0)