Add EventEmitter code-gen support for C++ Turbo Modules (#44809)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/44809

Adding react-native-codegen parser support for a new `EventEmitter` property type on C++ Turbo Modules.

It is possible to later expand this feature to other languages (Java, ObjC).

## Characteristics

An `EventEmitter` must:
- be non null:
 `EventEmitter<string>` works, `?EventEmitter<string>` does NOT
- have a non null eventType:
  `EventEmitter<number>` works, `EventEmitter<?number>` does NOT
- have at most 1 eventType, `void` is possible as well:
  `EventEmitter<>` or `EventEmitter<MyObject>` work - `EventEmitter<number, string>` do NOT
- have a concrete eventType, `{}` is not allowed
  `EventEmitter<{}>` does NOT work
- be used in `Cxx` Turbo Modules only at this time

## Example

For these 4 eventEmitters in on an RN JS TM spec
```
  +onPress: EventEmitter<void>;
  +onClick: EventEmitter<string>;
  +onChange: EventEmitter<ObjectStruct>;
  +onSubmit: EventEmitter<ObjectStruct[]>;
```
We now generate this code:
1.) in the spec based header `{MyModuleName}CxxSpec` in the constructor:
```
      ... // existing code
      eventEmitterMap_["onPress"] = std::make_shared<AsyncEventEmitter<>>();
      eventEmitterMap_["onClick"] = std::make_shared<AsyncEventEmitter<OnClickType>>();
      eventEmitterMap_["onChange"] = std::make_shared<AsyncEventEmitter<OnChangeType>>();
      eventEmitterMap_["onSubmit"] = std::make_shared<AsyncEventEmitter<OnSubmitType>>();
```
2.) as `protected` functions
```
  void emitOnPress() {
      std::static_pointer_cast<AsyncEventEmitter<>>(delegate_.eventEmitterMap_["onPress"])->emit();
  }

  void emitOnClick(const OnClickType& value) {
      std::static_pointer_cast<AsyncEventEmitter<OnClickType>>(delegate_.eventEmitterMap_["onClick"])->emit(value);
  }

  void emitOnChange(const OnChangeType& value) {
      std::static_pointer_cast<AsyncEventEmitter<OnChangeType>>(delegate_.eventEmitterMap_["onChange"])->emit(value);
  }

  void emitOnSubmit(const OnSubmitType& value) {
      std::static_pointer_cast<AsyncEventEmitter<OnSubmitType>>(delegate_.eventEmitterMap_["onSubmit"])->emit(value);
  }
```

## Changelog:

[General] [Added] - Add EventEmitter code-gen support for C++ Turbo Modules

Reviewed By: javache

Differential Revision: D57407871

fbshipit-source-id: 2345cc6dacf0cb0d45f8a374ad9d4cbf8082f9d6
This commit is contained in:
Christoph Purrer
2024-06-11 19:19:22 -07:00
committed by Facebook GitHub Bot
parent bfb3b7008d
commit fd618819c7
17 changed files with 605 additions and 79 deletions
@@ -18,6 +18,7 @@ import type {
NativeModuleEnumMap,
NativeModuleEnumMembers,
NativeModuleEnumMemberType,
NativeModuleEventEmitterShape,
NativeModuleFunctionTypeAnnotation,
NativeModulePropertyShape,
NativeModuleTypeAnnotation,
@@ -28,7 +29,7 @@ import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Cxx');
const {getEnumName, toSafeCppString} = require('../Utils');
const {getEnumName, toPascalCase, toSafeCppString} = require('../Utils');
const {indent} = require('../Utils');
const {
createAliasResolver,
@@ -64,10 +65,12 @@ public:
const ModuleSpecClassDeclarationTemplate = ({
hasteModuleName,
moduleName,
moduleEventEmitters,
moduleProperties,
}: $ReadOnly<{
hasteModuleName: string,
moduleName: string,
moduleEventEmitters: EventEmitterCpp[],
moduleProperties: string[],
}>) => {
return `template <typename T>
@@ -83,16 +86,20 @@ protected:
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(std::string{${hasteModuleName}CxxSpec::kModuleName}, jsInvoker),
delegate_(reinterpret_cast<T*>(this), jsInvoker) {}
${moduleEventEmitters.map(e => e.emitFunction).join('\n')}
private:
class Delegate : public ${hasteModuleName}CxxSpecJSI {
public:
Delegate(T *instance, std::shared_ptr<CallInvoker> jsInvoker) :
${hasteModuleName}CxxSpecJSI(std::move(jsInvoker)), instance_(instance) {}
${hasteModuleName}CxxSpecJSI(std::move(jsInvoker)), instance_(instance) {
${moduleEventEmitters.map(e => e.registerEventEmitter).join('\n')}
}
${indent(moduleProperties.join('\n'), 4)}
private:
friend class ${hasteModuleName}CxxSpec;
T *instance_;
};
@@ -460,7 +467,7 @@ function translatePropertyToCpp(
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
abstract: boolean = false,
) {
): string {
const [propTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(prop.typeAnnotation);
@@ -511,6 +518,69 @@ function translatePropertyToCpp(
}`;
}
type EventEmitterCpp = {
isVoidTypeAnnotation: boolean,
templateName: string,
registerEventEmitter: string,
emitFunction: string,
};
function translateEventEmitterToCpp(
moduleName: string,
eventEmitter: NativeModuleEventEmitterShape,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): EventEmitterCpp {
const isVoidTypeAnnotation =
eventEmitter.typeAnnotation.typeAnnotation.type === 'VoidTypeAnnotation';
const templateName = `${toPascalCase(eventEmitter.name)}Type`;
const jsiType = translatePrimitiveJSTypeToCpp(
moduleName,
null,
eventEmitter.typeAnnotation.typeAnnotation,
false,
typeName =>
`Unsupported type for eventEmitter "${eventEmitter.name}" in ${moduleName}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
const isArray = jsiType === 'jsi::Array';
return {
isVoidTypeAnnotation: isVoidTypeAnnotation,
templateName: isVoidTypeAnnotation ? `/*${templateName}*/` : templateName,
registerEventEmitter: ` eventEmitterMap_["${
eventEmitter.name
}"] = std::make_shared<AsyncEventEmitter<${
isVoidTypeAnnotation ? '' : 'jsi::Value'
}>>();`,
emitFunction: `
${
isVoidTypeAnnotation ? '' : `template <typename ${templateName}> `
}void emit${toPascalCase(eventEmitter.name)}(${
isVoidTypeAnnotation
? ''
: `${isArray ? `std::vector<${templateName}>` : templateName} value`
}) {${
isVoidTypeAnnotation
? ''
: `
static_assert(bridging::supportsFromJs<${
isArray ? `std::vector<${templateName}>` : templateName
}, ${jsiType}>, "value cannnot be converted to ${jsiType}");`
}
std::static_pointer_cast<AsyncEventEmitter<${
isVoidTypeAnnotation ? '' : 'jsi::Value'
}>>(delegate_.eventEmitterMap_["${eventEmitter.name}"])->emit(${
isVoidTypeAnnotation
? ''
: `[jsInvoker = jsInvoker_, eventValue = value](jsi::Runtime& rt) -> jsi::Value {
return bridging::toJs(rt, eventValue, jsInvoker);
}`
});
}`,
};
}
module.exports = {
generate(
libraryName: string,
@@ -522,12 +592,8 @@ module.exports = {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
const {
aliasMap,
enumMap,
spec: {properties},
moduleName,
} = nativeModules[hasteModuleName];
const {aliasMap, enumMap, spec, moduleName} =
nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);
const structs = createStructsString(
hasteModuleName,
@@ -540,7 +606,7 @@ module.exports = {
return [
ModuleClassDeclarationTemplate({
hasteModuleName,
moduleProperties: properties.map(prop =>
moduleProperties: spec.properties.map(prop =>
translatePropertyToCpp(
hasteModuleName,
prop,
@@ -555,7 +621,15 @@ module.exports = {
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
moduleName,
moduleProperties: properties.map(prop =>
moduleEventEmitters: spec.eventEmitters.map(eventEmitter =>
translateEventEmitterToCpp(
moduleName,
eventEmitter,
resolveAlias,
enumMap,
),
),
moduleProperties: spec.properties.map(prop =>
translatePropertyToCpp(
hasteModuleName,
prop,