Skip to content

Commit

Permalink
Add EventEmitter Code-gen support for Java and ObjC Turbo Modules (#4…
Browse files Browse the repository at this point in the history
…5119)

Summary:
Pull Request resolved: #45119

## Changelog:

[iOS][Added] - Add EventEmitter Code-gen support for Java and ObjC Turbo Modules

Reviewed By: RSNara

Differential Revision: D58929417

fbshipit-source-id: 5208ba5ecb5882d47c3827c2aa8e3a54a3d7f2b6
  • Loading branch information
christophpurrer authored and facebook-github-bot committed Jul 1, 2024
1 parent 9d30523 commit ad3df84
Show file tree
Hide file tree
Showing 14 changed files with 1,305 additions and 47 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import type {
NamedShape,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
Expand All @@ -23,6 +24,7 @@ import type {AliasResolver} from './Utils';

const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Java');
const {toPascalCase} = require('../Utils');
const {createAliasResolver, getModules} = require('./Utils');

type FilesOutput = Map<string, string>;
Expand All @@ -32,11 +34,13 @@ function FileTemplate(
packageName: string,
className: string,
jsName: string,
eventEmitters: string,
methods: string,
imports: string,
}>,
): string {
const {packageName, className, jsName, methods, imports} = config;
const {packageName, className, jsName, eventEmitters, methods, imports} =
config;
return `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
Expand Down Expand Up @@ -65,11 +69,28 @@ public abstract class ${className} extends ReactContextBaseJavaModule implements
return NAME;
}
${methods}
${eventEmitters}${eventEmitters.length > 0 ? '\n\n' : ''}${methods}
}
`;
}

function EventEmitterTemplate(
eventEmitter: NativeModuleEventEmitterShape,
imports: Set<string>,
): string {
return ` protected final void emit${toPascalCase(eventEmitter.name)}(${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? `${translateEventEmitterTypeToJavaType(eventEmitter, imports)} value`
: ''
}) {
mEventEmitterCallback.invoke("${eventEmitter.name}"${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? ', value'
: ''
});
}`;
}

function MethodTemplate(
config: $ReadOnly<{
abstract: boolean,
Expand Down Expand Up @@ -102,6 +123,34 @@ function MethodTemplate(
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function translateEventEmitterTypeToJavaType(
eventEmitter: NativeModuleEventEmitterShape,
imports: Set<string>,
): string {
switch (eventEmitter.typeAnnotation.typeAnnotation.type) {
case 'StringTypeAnnotation':
return 'String';
case 'NumberTypeAnnotation':
case 'FloatTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'Int32TypeAnnotation':
return 'double';
case 'BooleanTypeAnnotation':
return 'boolean';
case 'ObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return 'ReadableMap';
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return 'ReadableArray';
default:
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
}
}

function translateFunctionParamToJavaType(
param: Param,
createErrorMessage: (typeName: string) => string,
Expand Down Expand Up @@ -533,6 +582,9 @@ module.exports = {
packageName: normalizedPackageName,
className,
jsName: moduleName,
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterTemplate(eventEmitter, imports))
.join('\n\n'),
methods: methods.filter(Boolean).join('\n\n'),
imports: Array.from(imports)
.sort()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import type {
NamedShape,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
Expand Down Expand Up @@ -54,9 +55,11 @@ const HostFunctionTemplate = ({

const ModuleClassConstructorTemplate = ({
hasteModuleName,
eventEmitters,
methods,
}: $ReadOnly<{
hasteModuleName: string,
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
methods: $ReadOnlyArray<{
propertyName: string,
argCount: number,
Expand All @@ -69,7 +72,21 @@ ${methods
.map(({propertyName, argCount}) => {
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
})
.join('\n')}
.join('\n')}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<folly::dynamic>>();`;
})
.join('')
: ''
}${
eventEmitters.length > 0
? `
setEventEmitterCallback(params.instance);`
: ''
}
}`.trim();
};

Expand Down Expand Up @@ -438,7 +455,7 @@ module.exports = {
.map(hasteModuleName => {
const {
aliasMap,
spec: {methods},
spec: {eventEmitters, methods},
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);

Expand All @@ -457,6 +474,7 @@ module.exports = {
'\n\n' +
ModuleClassConstructorTemplate({
hasteModuleName,
eventEmitters,
methods: methods
.map(({name: propertyName, typeAnnotation}) => {
const [{returnTypeAnnotation, params}] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {MethodSerializationOutput} from './serializeMethod';

const {createAliasResolver, getModules} = require('../Utils');
const {serializeStruct} = require('./header/serializeStruct');
const {EventEmitterHeaderTemplate} = require('./serializeEventEmitter');
const {serializeMethod} = require('./serializeMethod');
const {serializeModuleSource} = require('./source/serializeModule');
const {StructCollector} = require('./StructCollector');
Expand All @@ -24,10 +25,12 @@ type FilesOutput = Map<string, string>;
const ModuleDeclarationTemplate = ({
hasteModuleName,
structDeclarations,
eventEmitters,
protocolMethods,
}: $ReadOnly<{
hasteModuleName: string,
structDeclarations: string,
eventEmitters: string,
protocolMethods: string,
}>) => `${structDeclarations}
@protocol ${hasteModuleName}Spec <RCTBridgeModule, RCTTurboModule>
Expand All @@ -36,7 +39,13 @@ ${protocolMethods}
@end
@interface ${hasteModuleName}SpecBase : NSObject
@interface ${hasteModuleName}SpecBase : NSObject {
@protected
facebook::react::EventEmitterCallback _eventEmitterCallback;
}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper;
${eventEmitters}
@end
namespace facebook::react {
Expand Down Expand Up @@ -191,6 +200,9 @@ module.exports = {
ModuleDeclarationTemplate({
hasteModuleName: hasteModuleName,
structDeclarations: structStrs.join('\n'),
eventEmitters: spec.eventEmitters
.map(eventEmitter => EventEmitterHeaderTemplate(eventEmitter))
.join('\n'),
protocolMethods: methodSerializations
.map(({protocolMethod}) => protocolMethod)
.join('\n'),
Expand All @@ -204,6 +216,7 @@ module.exports = {
hasteModuleName,
generatedStructs,
hasteModuleName,
spec.eventEmitters,
methodSerializations.filter(
({selector}) => selector !== '@selector(constantsToExport)',
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* 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.
*
* @flow strict
* @format
*/

import type {NativeModuleEventEmitterShape} from '../../../CodegenSchema';

const {toPascalCase} = require('../../Utils');

function getEventEmitterTypeObjCType(
eventEmitter: NativeModuleEventEmitterShape,
): string {
switch (eventEmitter.typeAnnotation.typeAnnotation.type) {
case 'StringTypeAnnotation':
return 'NSString *_Nonnull';
case 'NumberTypeAnnotation':
return 'NSNumber *_Nonnull';
case 'BooleanTypeAnnotation':
return 'BOOL';
case 'ObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation':
return 'NSDictionary *';
case 'ArrayTypeAnnotation':
return 'NSArray<id<NSObject>> *';
default:
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
}
}

function EventEmitterHeaderTemplate(
eventEmitter: NativeModuleEventEmitterShape,
): string {
return `- (void)emit${toPascalCase(eventEmitter.name)}${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? `:(${getEventEmitterTypeObjCType(eventEmitter)})value`
: ''
};`;
}

function EventEmitterImplementationTemplate(
eventEmitter: NativeModuleEventEmitterShape,
): string {
return `- (void)emit${toPascalCase(eventEmitter.name)}${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? `:(${getEventEmitterTypeObjCType(eventEmitter)})value`
: ''
}
{
_eventEmitterCallback("${eventEmitter.name}", ${
eventEmitter.typeAnnotation.typeAnnotation.type !== 'VoidTypeAnnotation'
? eventEmitter.typeAnnotation.typeAnnotation.type !==
'BooleanTypeAnnotation'
? 'value'
: '[NSNumber numberWithBool:value]'
: 'nil'
});
}`;
}

module.exports = {
EventEmitterHeaderTemplate,
EventEmitterImplementationTemplate,
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,39 @@

'use strict';

import type {NativeModuleEventEmitterShape} from '../../../../CodegenSchema';
import type {
MethodSerializationOutput,
StructParameterRecord,
} from '../serializeMethod';
import type {Struct} from '../StructCollector';

const {
EventEmitterImplementationTemplate,
} = require('./../serializeEventEmitter');

const ModuleTemplate = ({
hasteModuleName,
structs,
moduleName,
eventEmitters,
methodSerializationOutputs,
}: $ReadOnly<{
hasteModuleName: string,
structs: $ReadOnlyArray<Struct>,
moduleName: string,
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
methodSerializationOutputs: $ReadOnlyArray<MethodSerializationOutput>,
}>) => `
@implementation ${hasteModuleName}SpecBase
${eventEmitters
.map(eventEmitter => EventEmitterImplementationTemplate(eventEmitter))
.join('\n')}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper
{
_eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback);
}
@end
${structs
Expand Down Expand Up @@ -58,7 +73,23 @@ namespace facebook::react {
argCount,
}),
)
.join('\n' + ' '.repeat(8))}
.join('\n' + ' '.repeat(8))}${
eventEmitters.length > 0
? eventEmitters
.map(eventEmitter => {
return `
eventEmitterMap_["${eventEmitter.name}"] = std::make_shared<AsyncEventEmitter<id>>();`;
})
.join('')
: ''
}${
eventEmitters.length > 0
? `
setEventEmitterCallback([&](const std::string &name, id value) {
static_cast<AsyncEventEmitter<id> &>(*eventEmitterMap_[name]).emit(value);
});`
: ''
}
}
} // namespace facebook::react`;

Expand Down Expand Up @@ -112,12 +143,14 @@ function serializeModuleSource(
hasteModuleName: string,
structs: $ReadOnlyArray<Struct>,
moduleName: string,
eventEmitters: $ReadOnlyArray<NativeModuleEventEmitterShape>,
methodSerializationOutputs: $ReadOnlyArray<MethodSerializationOutput>,
): string {
return ModuleTemplate({
hasteModuleName,
structs: structs.filter(({context}) => context !== 'CONSTANTS'),
moduleName,
eventEmitters,
methodSerializationOutputs,
});
}
Expand Down
Loading

0 comments on commit ad3df84

Please sign in to comment.