Skip to content

Commit

Permalink
react-native code-gen > C++ TurboModules struct support (#35265)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #35265

This adds a templating layer for C++ TurboModules to automatically generate struct templates from TurboModule specs.

You have to define concrete types for those templates to use them in your C++ TurboModule.

E.g. for the JS flow type:
```
export type ObjectStruct = {|
  a: number,
  b: string,
  c?: ?string,
|};
```
code-gen will now generate the following template code:
```
#pragma mark - NativeCxxModuleExampleCxxBaseObjectStruct

template <typename P0, typename P1, typename P2>
struct NativeCxxModuleExampleCxxBaseObjectStruct {
  P0 a;
  P1 b;
  P2 c;
  bool operator==(const NativeCxxModuleExampleCxxBaseObjectStruct &other) const {
    return a == other.a && b == other.b && c == other.c;
  }
};

template <typename P0, typename P1, typename P2>
struct NativeCxxModuleExampleCxxBaseObjectStructBridging {
  static NativeCxxModuleExampleCxxBaseObjectStruct<P0, P1, P2> fromJs(
      jsi::Runtime &rt,
      const jsi::Object &value,
      const std::shared_ptr<CallInvoker> &jsInvoker) {
    NativeCxxModuleExampleCxxBaseObjectStruct<P0, P1, P2> result{
      bridging::fromJs<P0>(rt, value.getProperty(rt, "a"), jsInvoker),
      bridging::fromJs<P1>(rt, value.getProperty(rt, "b"), jsInvoker),
      bridging::fromJs<P2>(rt, value.getProperty(rt, "c"), jsInvoker)};
    return result;
  }

  static jsi::Object toJs(
      jsi::Runtime &rt,
      const NativeCxxModuleExampleCxxBaseObjectStruct<P0, P1, P2> &value) {
    auto result = facebook::jsi::Object(rt);
    result.setProperty(rt, "a", bridging::toJs(rt, value.a));
    result.setProperty(rt, "b", bridging::toJs(rt, value.b));
    if (value.c) {
      result.setProperty(rt, "c", bridging::toJs(rt, value.c.value()));
    }
    return result;
  }
};
```
and you can use it in our C++ TurboModule for example as:
```
using ObjectStruct = NativeCxxModuleExampleCxxBaseObjectStruct<
    int32_t,
    std::string,
    std::optional<std::string>>;

template <>
struct Bridging<ObjectStruct>
    : NativeCxxModuleExampleCxxBaseObjectStructBridging<
          int32_t,
          std::string,
          std::optional<std::string>> {};
```
or as
```
using ObjectStruct = NativeCxxModuleExampleCxxBaseObjectStruct<
    float,
    folly::StringPiece,
    std::optional<std::string>>;

template <>
struct Bridging<ObjectStruct>
    : NativeCxxModuleExampleCxxBaseObjectStructBridging<
          float,
          folly::StringPiece,
          std::optional<std::string>> {};
```
Or as
...

Changelog: [Internal]

Reviewed By: rshest

Differential Revision: D41133761

fbshipit-source-id: fdf36e51073cb46c5234f6121842c79a884899c7
  • Loading branch information
christophpurrer authored and facebook-github-bot committed Nov 9, 2022
1 parent 64ea7ce commit c0a06d2
Show file tree
Hide file tree
Showing 9 changed files with 493 additions and 223 deletions.
26 changes: 25 additions & 1 deletion Libraries/WebPerformance/NativePerformanceObserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,34 @@
#include <optional>
#include <string>
#include <vector>
#include "NativePerformanceObserver_RawPerformanceEntry.h"

namespace facebook::react {

#pragma mark - Structs

using RawPerformanceEntry = NativePerformanceObserverCxxBaseRawPerformanceEntry<
std::string,
int32_t,
double,
double,
// For "event" entries only:
std::optional<double>,
std::optional<double>,
std::optional<double>>;

template <>
struct Bridging<RawPerformanceEntry>
: NativePerformanceObserverCxxBaseRawPerformanceEntryBridging<
std::string,
int32_t,
double,
double,
std::optional<double>,
std::optional<double>,
std::optional<double>> {};

#pragma mark - implementation

class NativePerformanceObserver
: public NativePerformanceObserverCxxSpec<NativePerformanceObserver>,
std::enable_shared_from_this<NativePerformanceObserver> {
Expand Down
4 changes: 2 additions & 2 deletions Libraries/WebPerformance/NativePerformanceObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const RawPerformanceEntryTypeValues = {

export type RawPerformanceEntryType = number;

export type RawPerformanceEntry = $ReadOnly<{
export type RawPerformanceEntry = {|
name: string,
entryType: RawPerformanceEntryType,
startTime: number,
Expand All @@ -27,7 +27,7 @@ export type RawPerformanceEntry = $ReadOnly<{
processingStart?: number,
processingEnd?: number,
interactionId?: number,
}>;
|};

export interface Spec extends TurboModule {
+startReporting: (entryType: string) => void;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
NativeModuleTypeAnnotation,
NativeModuleFunctionTypeAnnotation,
NativeModulePropertyShape,
NativeModuleAliasMap,
} from '../../CodegenSchema';

import type {AliasResolver} from './Utils';
Expand All @@ -28,8 +29,13 @@ type FilesOutput = Map<string, string>;
const ModuleClassDeclarationTemplate = ({
hasteModuleName,
moduleProperties,
}: $ReadOnly<{hasteModuleName: string, moduleProperties: string[]}>) => {
return `class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
structs,
}: $ReadOnly<{
hasteModuleName: string,
moduleProperties: string[],
structs: string,
}>) => {
return `${structs}class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
protected:
${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
Expand Down Expand Up @@ -186,6 +192,76 @@ function translatePrimitiveJSTypeToCpp(
}
}

function createStructs(
moduleName: string,
aliasMap: NativeModuleAliasMap,
resolveAlias: AliasResolver,
): string {
return Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${moduleName}Base${alias}`;
const templateParameterWithTypename = value.properties
.map((v, i) => 'typename P' + i)
.join(', ');
const templateParameter = value.properties
.map((v, i) => 'P' + i)
.join(', ');
return `#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct ${structName} {
${value.properties.map((v, i) => ' P' + i + ' ' + v.name).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties
.map(v => `${v.name} == other.${v.name}`)
.join(' && ')};
}
};
template <${templateParameterWithTypename}>
struct ${structName}Bridging {
static ${structName}<${templateParameter}> fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
${structName}<${templateParameter}> result{
${value.properties
.map(
(v, i) =>
` bridging::fromJs<P${i}>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`,
)
.join(',\n')}};
return result;
}
static jsi::Object toJs(
jsi::Runtime &rt,
const ${structName}<${templateParameter}> &value) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map((v, i) => {
if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value()));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n');
}

function translatePropertyToCpp(
prop: NativeModulePropertyShape,
resolveAlias: AliasResolver,
Expand Down Expand Up @@ -251,13 +327,15 @@ module.exports = {
moduleNames: [moduleName],
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliases);
const structs = createStructs(moduleName, aliases, resolveAlias);

return [
ModuleClassDeclarationTemplate({
hasteModuleName,
moduleProperties: properties.map(prop =>
translatePropertyToCpp(prop, resolveAlias, true),
),
structs,
}),
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
Expand Down
Loading

0 comments on commit c0a06d2

Please sign in to comment.