nitrogen 0.34.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,6 +17,7 @@ export function createHybridObjectIntializer() {
17
17
  const autolinkedHybridObjects = NitroConfig.current.getAutolinkedHybridObjects();
18
18
  const cppHybridObjectImports = [];
19
19
  const cppRegistrations = [];
20
+ const cppDefinitions = [];
20
21
  for (const hybridObjectName of Object.keys(autolinkedHybridObjects)) {
21
22
  const config = autolinkedHybridObjects[hybridObjectName];
22
23
  if (config?.cpp != null) {
@@ -30,11 +31,12 @@ export function createHybridObjectIntializer() {
30
31
  }
31
32
  if (config?.kotlin != null) {
32
33
  // Autolink a Kotlin HybridObject through JNI/C++!
33
- const { cppCode, requiredImports } = createJNIHybridObjectRegistration({
34
+ const { cppCode, cppDefinition, requiredImports } = createJNIHybridObjectRegistration({
34
35
  hybridObjectName: hybridObjectName,
35
36
  jniClassName: config.kotlin,
36
37
  });
37
38
  cppHybridObjectImports.push(...requiredImports);
39
+ cppDefinitions.push(cppDefinition);
38
40
  cppRegistrations.push(cppCode);
39
41
  }
40
42
  }
@@ -100,6 +102,8 @@ int initialize(JavaVM* vm) {
100
102
  });
101
103
  }
102
104
 
105
+ ${cppDefinitions.join('\n')}
106
+
103
107
  void registerAllNatives() {
104
108
  using namespace margelo::nitro;
105
109
  using namespace ${cxxNamespace};
@@ -1,4 +1,4 @@
1
- import { createIndentation, indent } from '../../utils.js';
1
+ import { indent } from '../../utils.js';
2
2
  import { getForwardDeclaration } from '../c++/getForwardDeclaration.js';
3
3
  import { includeHeader } from '../c++/includeNitroHeader.js';
4
4
  import { getAllTypes } from '../getAllTypes.js';
@@ -25,19 +25,29 @@ export function createFbjniHybridObject(spec) {
25
25
  .map((p) => p.getCode('c++', { override: true }))
26
26
  .join('\n');
27
27
  const jniClassDescriptor = spec.config.getAndroidPackage('c++/jni', name.HybridTSpec);
28
+ const cxxPartJniClassDescriptor = `${jniClassDescriptor}$CxxPart`;
28
29
  const cxxNamespace = spec.config.getCxxNamespace('c++');
29
- const spaces = createIndentation(name.JHybridTSpec.length);
30
- let cppBase = 'JHybridObject';
30
+ let cppBaseClass = 'JHybridObject';
31
+ let javaPartBaseClass = 'JHybridObject::JavaPart';
32
+ let cxxPartBaseClass = 'JHybridObject::CxxPart';
33
+ const constructorCalls = [
34
+ `HybridObject(${name.HybridTSpec}::TAG)`,
35
+ `JHybridObject(javaPart)`,
36
+ ];
31
37
  if (spec.baseTypes.length > 0) {
32
38
  if (spec.baseTypes.length > 1) {
33
39
  throw new Error(`${name.T}: Inheriting from multiple HybridObject bases is not yet supported on Kotlin!`);
34
40
  }
35
41
  const base = spec.baseTypes[0];
36
- cppBase = getHybridObjectName(base.name).JHybridTSpec;
42
+ let baseTypename = getHybridObjectName(base.name).JHybridTSpec;
37
43
  if (base.config.isExternalConfig) {
38
44
  // It's an external type we inherit from - we have to prefix the namespace
39
- cppBase = base.config.getCxxNamespace('c++', cppBase);
45
+ baseTypename = base.config.getCxxNamespace('c++', baseTypename);
40
46
  }
47
+ cppBaseClass = baseTypename;
48
+ javaPartBaseClass = `${baseTypename}::JavaPart`;
49
+ cxxPartBaseClass = `${baseTypename}::CxxPart`;
50
+ constructorCalls.push(`${baseTypename}(javaPart)`);
41
51
  }
42
52
  const cppImports = [];
43
53
  for (const base of spec.baseTypes) {
@@ -71,34 +81,32 @@ namespace ${cxxNamespace} {
71
81
 
72
82
  using namespace facebook;
73
83
 
74
- class ${name.JHybridTSpec}: public jni::HybridClass<${name.JHybridTSpec}, ${cppBase}>,
75
- ${spaces} public virtual ${name.HybridTSpec} {
84
+ class ${name.JHybridTSpec}: public virtual ${name.HybridTSpec}, public virtual ${cppBaseClass} {
76
85
  public:
77
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
78
- static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
79
- static void registerNatives();
80
-
81
- protected:
82
- // C++ constructor (called from Java via \`initHybrid()\`)
83
- explicit ${name.JHybridTSpec}(jni::alias_ref<jhybridobject> jThis) :
84
- HybridObject(${name.HybridTSpec}::TAG),
85
- HybridBase(jThis),
86
- _javaPart(jni::make_global(jThis)) {}
86
+ struct JavaPart: public jni::JavaClass<JavaPart, ${javaPartBaseClass}> {
87
+ static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
88
+ std::shared_ptr<${name.JHybridTSpec}> get${name.JHybridTSpec}();
89
+ };
90
+ struct CxxPart: public jni::HybridClass<CxxPart, ${cxxPartBaseClass}> {
91
+ static auto constexpr kJavaDescriptor = "L${cxxPartJniClassDescriptor};";
92
+ static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
93
+ static void registerNatives();
94
+ using HybridBase::HybridBase;
95
+ protected:
96
+ std::shared_ptr<JHybridObject> createHybridObject(const jni::local_ref<JHybridObject::JavaPart>& javaPart) override;
97
+ };
87
98
 
88
99
  public:
100
+ explicit ${name.JHybridTSpec}(const jni::local_ref<${name.JHybridTSpec}::JavaPart>& javaPart):
101
+ ${indent(constructorCalls.join(',\n'), ' ')},
102
+ _javaPart(jni::make_global(javaPart)) {}
89
103
  ~${name.JHybridTSpec}() override {
90
104
  // Hermes GC can destroy JS objects on a non-JNI Thread.
91
105
  jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); });
92
106
  }
93
107
 
94
108
  public:
95
- size_t getExternalMemorySize() noexcept override;
96
- bool equals(const std::shared_ptr<HybridObject>& other) override;
97
- void dispose() noexcept override;
98
- std::string toString() override;
99
-
100
- public:
101
- inline const jni::global_ref<${name.JHybridTSpec}::javaobject>& getJavaPart() const noexcept {
109
+ inline const jni::global_ref<${name.JHybridTSpec}::JavaPart>& getJavaPart() const noexcept {
102
110
  return _javaPart;
103
111
  }
104
112
 
@@ -111,9 +119,7 @@ ${spaces} public virtual ${name.HybridTSpec} {
111
119
  ${indent(methodsDecl, ' ')}
112
120
 
113
121
  private:
114
- friend HybridBase;
115
- using HybridBase::HybridBase;
116
- jni::global_ref<${name.JHybridTSpec}::javaobject> _javaPart;
122
+ jni::global_ref<${name.JHybridTSpec}::JavaPart> _javaPart;
117
123
  };
118
124
 
119
125
  } // namespace ${cxxNamespace}
@@ -121,7 +127,7 @@ ${spaces} public virtual ${name.HybridTSpec} {
121
127
  // Make sure we register all native JNI methods on app startup
122
128
  addJNINativeRegistration({
123
129
  namespace: cxxNamespace,
124
- className: `${name.JHybridTSpec}`,
130
+ className: `${name.JHybridTSpec}::CxxPart`,
125
131
  import: {
126
132
  name: `${name.JHybridTSpec}.hpp`,
127
133
  space: 'user',
@@ -158,37 +164,31 @@ ${cppIncludes.join('\n')}
158
164
 
159
165
  namespace ${cxxNamespace} {
160
166
 
161
- jni::local_ref<${name.JHybridTSpec}::jhybriddata> ${name.JHybridTSpec}::initHybrid(jni::alias_ref<jhybridobject> jThis) {
162
- return makeCxxInstance(jThis);
163
- }
164
-
165
- void ${name.JHybridTSpec}::registerNatives() {
166
- registerHybrid({
167
- makeNativeMethod("initHybrid", ${name.JHybridTSpec}::initHybrid),
168
- });
167
+ std::shared_ptr<${name.JHybridTSpec}> ${name.JHybridTSpec}::JavaPart::get${name.JHybridTSpec}() {
168
+ auto hybridObject = JHybridObject::JavaPart::getJHybridObject();
169
+ auto castHybridObject = std::dynamic_pointer_cast<${name.JHybridTSpec}>(hybridObject);
170
+ if (castHybridObject == nullptr) [[unlikely]] {
171
+ throw std::runtime_error("Failed to downcast JHybridObject to ${name.JHybridTSpec}!");
172
+ }
173
+ return castHybridObject;
169
174
  }
170
175
 
171
- size_t ${name.JHybridTSpec}::getExternalMemorySize() noexcept {
172
- static const auto method = javaClassStatic()->getMethod<jlong()>("getMemorySize");
173
- return method(_javaPart);
176
+ jni::local_ref<${name.JHybridTSpec}::CxxPart::jhybriddata> ${name.JHybridTSpec}::CxxPart::initHybrid(jni::alias_ref<jhybridobject> jThis) {
177
+ return makeCxxInstance(jThis);
174
178
  }
175
179
 
176
- bool ${name.JHybridTSpec}::equals(const std::shared_ptr<HybridObject>& other) {
177
- if (auto otherCast = std::dynamic_pointer_cast<${name.JHybridTSpec}>(other)) {
178
- return _javaPart == otherCast->_javaPart;
180
+ std::shared_ptr<JHybridObject> ${name.JHybridTSpec}::CxxPart::createHybridObject(const jni::local_ref<JHybridObject::JavaPart>& javaPart) {
181
+ auto castJavaPart = jni::dynamic_ref_cast<${name.JHybridTSpec}::JavaPart>(javaPart);
182
+ if (castJavaPart == nullptr) [[unlikely]] {
183
+ throw std::runtime_error("Failed to cast JHybridObject::JavaPart to ${name.JHybridTSpec}::JavaPart!");
179
184
  }
180
- return false;
185
+ return std::make_shared<${name.JHybridTSpec}>(castJavaPart);
181
186
  }
182
187
 
183
- void ${name.JHybridTSpec}::dispose() noexcept {
184
- static const auto method = javaClassStatic()->getMethod<void()>("dispose");
185
- method(_javaPart);
186
- }
187
-
188
- std::string ${name.JHybridTSpec}::toString() {
189
- static const auto method = javaClassStatic()->getMethod<jni::JString()>("toString");
190
- auto javaString = method(_javaPart);
191
- return javaString->toStdString();
188
+ void ${name.JHybridTSpec}::CxxPart::registerNatives() {
189
+ registerHybrid({
190
+ makeNativeMethod("initHybrid", ${name.JHybridTSpec}::CxxPart::initHybrid),
191
+ });
192
192
  }
193
193
 
194
194
  // Properties
@@ -242,7 +242,7 @@ function getFbjniMethodForwardImplementation(spec, method, jniMethodName) {
242
242
  if (returnJNI.hasType) {
243
243
  // return something - we need to parse it
244
244
  body = `
245
- static const auto method = javaClassStatic()->getMethod<${cxxSignature}>("${methodName}");
245
+ static const auto method = _javaPart->javaClassStatic()->getMethod<${cxxSignature}>("${methodName}");
246
246
  auto __result = method(${paramsForward.join(', ')});
247
247
  return ${returnJNI.parse('__result', 'kotlin', 'c++', 'c++')};
248
248
  `;
@@ -250,7 +250,7 @@ return ${returnJNI.parse('__result', 'kotlin', 'c++', 'c++')};
250
250
  else {
251
251
  // void method. no return
252
252
  body = `
253
- static const auto method = javaClassStatic()->getMethod<${cxxSignature}>("${methodName}");
253
+ static const auto method = _javaPart->javaClassStatic()->getMethod<${cxxSignature}>("${methodName}");
254
254
  method(${paramsForward.join(', ')});
255
255
  `;
256
256
  }
@@ -343,7 +343,7 @@ export class KotlinCxxBridgedType {
343
343
  case 'c++':
344
344
  const hybridObjectType = getTypeAs(this.type, HybridObjectType);
345
345
  const fullName = this.getFullJHybridObjectName(hybridObjectType);
346
- return `${fullName}::javaobject`;
346
+ return `${fullName}::JavaPart`;
347
347
  default:
348
348
  return this.type.getCode(language);
349
349
  }
@@ -445,7 +445,6 @@ export class KotlinCxxBridgedType {
445
445
  // any jni::HybridClass needs to be dereferenced to jobject with .get()
446
446
  case 'array-buffer':
447
447
  case 'function':
448
- case 'hybrid-object':
449
448
  case 'hybrid-object-base':
450
449
  case 'map':
451
450
  case 'promise':
@@ -805,9 +804,9 @@ export class KotlinCxxBridgedType {
805
804
  case 'hybrid-object': {
806
805
  switch (language) {
807
806
  case 'c++':
808
- const hybrid = getTypeAs(this.type, HybridObjectType);
809
- const fullName = this.getFullJHybridObjectName(hybrid);
810
- return `${parameterName}->cthis()->shared_cast<${fullName}>()`;
807
+ const hybridObject = getTypeAs(this.type, HybridObjectType);
808
+ const { JHybridTSpec } = getHybridObjectName(hybridObject.hybridObjectName);
809
+ return `${parameterName}->get${JHybridTSpec}()`;
811
810
  default:
812
811
  return parameterName;
813
812
  }
@@ -20,6 +20,11 @@ export function createKotlinHybridObject(spec) {
20
20
  ...spec.properties.flatMap((p) => p.getRequiredImports('kotlin')),
21
21
  ...spec.methods.flatMap((m) => m.getRequiredImports('kotlin')),
22
22
  ...spec.baseTypes.flatMap((b) => new HybridObjectType(b).getRequiredImports('kotlin')),
23
+ {
24
+ name: 'com.margelo.nitro.core.HybridObject',
25
+ space: 'system',
26
+ language: 'kotlin',
27
+ },
23
28
  ];
24
29
  if (spec.isHybridView) {
25
30
  extraImports.push({
@@ -28,14 +33,8 @@ export function createKotlinHybridObject(spec) {
28
33
  language: 'kotlin',
29
34
  });
30
35
  }
31
- else {
32
- extraImports.push({
33
- name: 'com.margelo.nitro.core.HybridObject',
34
- space: 'system',
35
- language: 'kotlin',
36
- });
37
- }
38
36
  let kotlinBase = spec.isHybridView ? 'HybridView' : 'HybridObject';
37
+ let cxxPartBase = 'HybridObject.CxxPart';
39
38
  if (spec.baseTypes.length > 0) {
40
39
  if (spec.baseTypes.length > 1) {
41
40
  throw new Error(`${name.T}: Inheriting from multiple HybridObject bases is not yet supported in Kotlin!`);
@@ -43,6 +42,7 @@ export function createKotlinHybridObject(spec) {
43
42
  const base = spec.baseTypes[0];
44
43
  const baseHybrid = new HybridObjectType(base);
45
44
  kotlinBase = baseHybrid.getCode('kotlin');
45
+ cxxPartBase = `${kotlinBase}.CxxPart`;
46
46
  }
47
47
  const imports = extraImports
48
48
  .map((i) => `import ${i.name}`)
@@ -71,30 +71,27 @@ ${imports.join('\n')}
71
71
  "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName"
72
72
  )
73
73
  abstract class ${name.HybridTSpec}: ${kotlinBase}() {
74
- @DoNotStrip
75
- private var mHybridData: HybridData = initHybrid()
76
-
77
- init {
78
- super.updateNative(mHybridData)
79
- }
74
+ // Properties
75
+ ${indent(properties, ' ')}
80
76
 
81
- override fun updateNative(hybridData: HybridData) {
82
- mHybridData = hybridData
83
- super.updateNative(hybridData)
84
- }
77
+ // Methods
78
+ ${indent(methods, ' ')}
85
79
 
86
80
  // Default implementation of \`HybridObject.toString()\`
87
81
  override fun toString(): String {
88
82
  return "[HybridObject ${name.T}]"
89
83
  }
90
84
 
91
- // Properties
92
- ${indent(properties, ' ')}
93
-
94
- // Methods
95
- ${indent(methods, ' ')}
96
-
97
- private external fun initHybrid(): HybridData
85
+ // C++ backing class
86
+ @DoNotStrip
87
+ @Keep
88
+ protected open class CxxPart(javaPart: ${name.HybridTSpec}): ${cxxPartBase}(javaPart) {
89
+ // C++ ${name.JHybridTSpec}::CxxPart::initHybrid(...)
90
+ external override fun initHybrid(): HybridData
91
+ }
92
+ override fun createCxxPart(): CxxPart {
93
+ return CxxPart(this)
94
+ }
98
95
 
99
96
  companion object {
100
97
  protected const val TAG = "${name.HybridTSpec}"
@@ -11,6 +11,7 @@ interface Props {
11
11
  }
12
12
  interface JNIHybridObjectRegistration {
13
13
  cppCode: string;
14
+ cppDefinition: string;
14
15
  requiredImports: SourceImport[];
15
16
  }
16
17
  export declare function createJNIHybridObjectRegistration({ hybridObjectName, jniClassName, }: Props): JNIHybridObjectRegistration;
@@ -12,13 +12,21 @@ export function createJNIHybridObjectRegistration({ hybridObjectName, jniClassNa
12
12
  space: 'system',
13
13
  },
14
14
  ],
15
+ cppDefinition: `
16
+ struct ${JHybridTSpec}Impl: public jni::JavaClass<${JHybridTSpec}Impl, ${JHybridTSpec}::JavaPart> {
17
+ static auto constexpr kJavaDescriptor = "L${jniNamespace};";
18
+ static std::shared_ptr<${JHybridTSpec}> create() {
19
+ static auto constructorFn = javaClassStatic()->getConstructor<${JHybridTSpec}Impl::javaobject()>();
20
+ jni::local_ref<${JHybridTSpec}::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
21
+ return javaPart->get${JHybridTSpec}();
22
+ }
23
+ };
24
+ `.trim(),
15
25
  cppCode: `
16
26
  HybridObjectRegistry::registerHybridObjectConstructor(
17
27
  "${hybridObjectName}",
18
28
  []() -> std::shared_ptr<HybridObject> {
19
- static DefaultConstructableObject<${JHybridTSpec}::javaobject> object("${jniNamespace}");
20
- auto instance = object.create();
21
- return instance->cthis()->shared();
29
+ return ${JHybridTSpec}Impl::create();
22
30
  }
23
31
  );
24
32
  `.trim(),
@@ -22,10 +22,6 @@ data class ${innerName}(@DoNotStrip val value: ${bridge.getTypeCode('kotlin')}):
22
22
  `.trim();
23
23
  });
24
24
  const packageName = NitroConfig.current.getAndroidPackage('java/kotlin');
25
- const getterCases = variant.cases.map(([label]) => {
26
- const innerName = capitalizeName(label);
27
- return `is ${innerName} -> value as? T`;
28
- });
29
25
  const isFunctions = variant.cases.map(([label]) => {
30
26
  const innerName = capitalizeName(label);
31
27
  return `
@@ -83,11 +79,6 @@ ${extraImports.join('\n')}
83
79
  sealed class ${kotlinName} {
84
80
  ${indent(innerClasses.join('\n'), ' ')}
85
81
 
86
- @Deprecated("getAs() is not type-safe. Use fold/asFirstOrNull/asSecondOrNull instead.", level = DeprecationLevel.ERROR)
87
- inline fun <reified T> getAs(): T? = when (this) {
88
- ${indent(getterCases.join('\n'), ' ')}
89
- }
90
-
91
82
  ${indent(isFunctions.join('\n'), ' ')}
92
83
 
93
84
  ${indent(asFunctions.join('\n'), ' ')}
@@ -132,7 +132,7 @@ public:
132
132
 
133
133
  public:
134
134
  static void updateViewProps(jni::alias_ref<jni::JClass> /* class */,
135
- jni::alias_ref<${JHybridTSpec}::javaobject> view,
135
+ jni::alias_ref<${JHybridTSpec}::JavaPart> view,
136
136
  jni::alias_ref<JStateWrapper::javaobject> stateWrapperInterface);
137
137
 
138
138
  public:
@@ -155,7 +155,7 @@ public:
155
155
  const setter = p.getSetterName('other');
156
156
  return `
157
157
  if (props->${name}.isDirty) {
158
- view->${setter}(props->${name}.value);
158
+ hybridView->${setter}(props->${name}.value);
159
159
  props->${name}.isDirty = false;
160
160
  }
161
161
  `.trim();
@@ -174,9 +174,9 @@ using namespace facebook;
174
174
  using ConcreteStateData = react::ConcreteState<${stateClassName}>;
175
175
 
176
176
  void J${stateUpdaterName}::updateViewProps(jni::alias_ref<jni::JClass> /* class */,
177
- jni::alias_ref<${JHybridTSpec}::javaobject> javaView,
177
+ jni::alias_ref<${JHybridTSpec}::JavaPart> javaView,
178
178
  jni::alias_ref<JStateWrapper::javaobject> stateWrapperInterface) {
179
- ${JHybridTSpec}* view = javaView->cthis();
179
+ std::shared_ptr<${JHybridTSpec}> hybridView = javaView->get${JHybridTSpec}();
180
180
 
181
181
  // Get concrete StateWrapperImpl from passed StateWrapper interface object
182
182
  jobject rawStateWrapper = stateWrapperInterface.get();
@@ -202,8 +202,7 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref<jni::JClass> /* class
202
202
  // hybridRef changed - call it with new this
203
203
  const auto& maybeFunc = props->hybridRef.value;
204
204
  if (maybeFunc.has_value()) {
205
- std::shared_ptr<${JHybridTSpec}> shared = javaView->cthis()->shared_cast<${JHybridTSpec}>();
206
- maybeFunc.value()(shared);
205
+ maybeFunc.value()(hybridView);
207
206
  }
208
207
  props->hybridRef.isDirty = false;
209
208
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitrogen",
3
- "version": "0.34.1",
3
+ "version": "0.35.0",
4
4
  "description": "The code-generator for react-native-nitro-modules.",
5
5
  "main": "lib/index",
6
6
  "types": "lib/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "chalk": "^5.3.0",
38
- "react-native-nitro-modules": "^0.34.1",
38
+ "react-native-nitro-modules": "^0.35.0",
39
39
  "ts-morph": "^27.0.0",
40
40
  "yargs": "^18.0.0",
41
41
  "zod": "^4.0.5"
@@ -26,6 +26,7 @@ export function createHybridObjectIntializer(): SourceFile[] {
26
26
 
27
27
  const cppHybridObjectImports: SourceImport[] = []
28
28
  const cppRegistrations: string[] = []
29
+ const cppDefinitions: string[] = []
29
30
  for (const hybridObjectName of Object.keys(autolinkedHybridObjects)) {
30
31
  const config = autolinkedHybridObjects[hybridObjectName]
31
32
 
@@ -40,11 +41,13 @@ export function createHybridObjectIntializer(): SourceFile[] {
40
41
  }
41
42
  if (config?.kotlin != null) {
42
43
  // Autolink a Kotlin HybridObject through JNI/C++!
43
- const { cppCode, requiredImports } = createJNIHybridObjectRegistration({
44
- hybridObjectName: hybridObjectName,
45
- jniClassName: config.kotlin,
46
- })
44
+ const { cppCode, cppDefinition, requiredImports } =
45
+ createJNIHybridObjectRegistration({
46
+ hybridObjectName: hybridObjectName,
47
+ jniClassName: config.kotlin,
48
+ })
47
49
  cppHybridObjectImports.push(...requiredImports)
50
+ cppDefinitions.push(cppDefinition)
48
51
  cppRegistrations.push(cppCode)
49
52
  }
50
53
  }
@@ -113,6 +116,8 @@ int initialize(JavaVM* vm) {
113
116
  });
114
117
  }
115
118
 
119
+ ${cppDefinitions.join('\n')}
120
+
116
121
  void registerAllNatives() {
117
122
  using namespace margelo::nitro;
118
123
  using namespace ${cxxNamespace};
@@ -1,4 +1,4 @@
1
- import { createIndentation, indent } from '../../utils.js'
1
+ import { indent } from '../../utils.js'
2
2
  import { getForwardDeclaration } from '../c++/getForwardDeclaration.js'
3
3
  import { includeHeader } from '../c++/includeNitroHeader.js'
4
4
  import { getAllTypes } from '../getAllTypes.js'
@@ -35,10 +35,16 @@ export function createFbjniHybridObject(spec: HybridObjectSpec): SourceFile[] {
35
35
  'c++/jni',
36
36
  name.HybridTSpec
37
37
  )
38
+ const cxxPartJniClassDescriptor = `${jniClassDescriptor}$CxxPart`
38
39
  const cxxNamespace = spec.config.getCxxNamespace('c++')
39
- const spaces = createIndentation(name.JHybridTSpec.length)
40
40
 
41
- let cppBase = 'JHybridObject'
41
+ let cppBaseClass = 'JHybridObject'
42
+ let javaPartBaseClass = 'JHybridObject::JavaPart'
43
+ let cxxPartBaseClass = 'JHybridObject::CxxPart'
44
+ const constructorCalls = [
45
+ `HybridObject(${name.HybridTSpec}::TAG)`,
46
+ `JHybridObject(javaPart)`,
47
+ ]
42
48
  if (spec.baseTypes.length > 0) {
43
49
  if (spec.baseTypes.length > 1) {
44
50
  throw new Error(
@@ -46,11 +52,15 @@ export function createFbjniHybridObject(spec: HybridObjectSpec): SourceFile[] {
46
52
  )
47
53
  }
48
54
  const base = spec.baseTypes[0]!
49
- cppBase = getHybridObjectName(base.name).JHybridTSpec
55
+ let baseTypename = getHybridObjectName(base.name).JHybridTSpec
50
56
  if (base.config.isExternalConfig) {
51
57
  // It's an external type we inherit from - we have to prefix the namespace
52
- cppBase = base.config.getCxxNamespace('c++', cppBase)
58
+ baseTypename = base.config.getCxxNamespace('c++', baseTypename)
53
59
  }
60
+ cppBaseClass = baseTypename
61
+ javaPartBaseClass = `${baseTypename}::JavaPart`
62
+ cxxPartBaseClass = `${baseTypename}::CxxPart`
63
+ constructorCalls.push(`${baseTypename}(javaPart)`)
54
64
  }
55
65
  const cppImports: SourceImport[] = []
56
66
  for (const base of spec.baseTypes) {
@@ -90,34 +100,32 @@ namespace ${cxxNamespace} {
90
100
 
91
101
  using namespace facebook;
92
102
 
93
- class ${name.JHybridTSpec}: public jni::HybridClass<${name.JHybridTSpec}, ${cppBase}>,
94
- ${spaces} public virtual ${name.HybridTSpec} {
103
+ class ${name.JHybridTSpec}: public virtual ${name.HybridTSpec}, public virtual ${cppBaseClass} {
95
104
  public:
96
- static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
97
- static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
98
- static void registerNatives();
99
-
100
- protected:
101
- // C++ constructor (called from Java via \`initHybrid()\`)
102
- explicit ${name.JHybridTSpec}(jni::alias_ref<jhybridobject> jThis) :
103
- HybridObject(${name.HybridTSpec}::TAG),
104
- HybridBase(jThis),
105
- _javaPart(jni::make_global(jThis)) {}
105
+ struct JavaPart: public jni::JavaClass<JavaPart, ${javaPartBaseClass}> {
106
+ static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};";
107
+ std::shared_ptr<${name.JHybridTSpec}> get${name.JHybridTSpec}();
108
+ };
109
+ struct CxxPart: public jni::HybridClass<CxxPart, ${cxxPartBaseClass}> {
110
+ static auto constexpr kJavaDescriptor = "L${cxxPartJniClassDescriptor};";
111
+ static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
112
+ static void registerNatives();
113
+ using HybridBase::HybridBase;
114
+ protected:
115
+ std::shared_ptr<JHybridObject> createHybridObject(const jni::local_ref<JHybridObject::JavaPart>& javaPart) override;
116
+ };
106
117
 
107
118
  public:
119
+ explicit ${name.JHybridTSpec}(const jni::local_ref<${name.JHybridTSpec}::JavaPart>& javaPart):
120
+ ${indent(constructorCalls.join(',\n'), ' ')},
121
+ _javaPart(jni::make_global(javaPart)) {}
108
122
  ~${name.JHybridTSpec}() override {
109
123
  // Hermes GC can destroy JS objects on a non-JNI Thread.
110
124
  jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); });
111
125
  }
112
126
 
113
127
  public:
114
- size_t getExternalMemorySize() noexcept override;
115
- bool equals(const std::shared_ptr<HybridObject>& other) override;
116
- void dispose() noexcept override;
117
- std::string toString() override;
118
-
119
- public:
120
- inline const jni::global_ref<${name.JHybridTSpec}::javaobject>& getJavaPart() const noexcept {
128
+ inline const jni::global_ref<${name.JHybridTSpec}::JavaPart>& getJavaPart() const noexcept {
121
129
  return _javaPart;
122
130
  }
123
131
 
@@ -130,9 +138,7 @@ ${spaces} public virtual ${name.HybridTSpec} {
130
138
  ${indent(methodsDecl, ' ')}
131
139
 
132
140
  private:
133
- friend HybridBase;
134
- using HybridBase::HybridBase;
135
- jni::global_ref<${name.JHybridTSpec}::javaobject> _javaPart;
141
+ jni::global_ref<${name.JHybridTSpec}::JavaPart> _javaPart;
136
142
  };
137
143
 
138
144
  } // namespace ${cxxNamespace}
@@ -141,7 +147,7 @@ ${spaces} public virtual ${name.HybridTSpec} {
141
147
  // Make sure we register all native JNI methods on app startup
142
148
  addJNINativeRegistration({
143
149
  namespace: cxxNamespace,
144
- className: `${name.JHybridTSpec}`,
150
+ className: `${name.JHybridTSpec}::CxxPart`,
145
151
  import: {
146
152
  name: `${name.JHybridTSpec}.hpp`,
147
153
  space: 'user',
@@ -180,37 +186,31 @@ ${cppIncludes.join('\n')}
180
186
 
181
187
  namespace ${cxxNamespace} {
182
188
 
183
- jni::local_ref<${name.JHybridTSpec}::jhybriddata> ${name.JHybridTSpec}::initHybrid(jni::alias_ref<jhybridobject> jThis) {
184
- return makeCxxInstance(jThis);
185
- }
186
-
187
- void ${name.JHybridTSpec}::registerNatives() {
188
- registerHybrid({
189
- makeNativeMethod("initHybrid", ${name.JHybridTSpec}::initHybrid),
190
- });
189
+ std::shared_ptr<${name.JHybridTSpec}> ${name.JHybridTSpec}::JavaPart::get${name.JHybridTSpec}() {
190
+ auto hybridObject = JHybridObject::JavaPart::getJHybridObject();
191
+ auto castHybridObject = std::dynamic_pointer_cast<${name.JHybridTSpec}>(hybridObject);
192
+ if (castHybridObject == nullptr) [[unlikely]] {
193
+ throw std::runtime_error("Failed to downcast JHybridObject to ${name.JHybridTSpec}!");
194
+ }
195
+ return castHybridObject;
191
196
  }
192
197
 
193
- size_t ${name.JHybridTSpec}::getExternalMemorySize() noexcept {
194
- static const auto method = javaClassStatic()->getMethod<jlong()>("getMemorySize");
195
- return method(_javaPart);
198
+ jni::local_ref<${name.JHybridTSpec}::CxxPart::jhybriddata> ${name.JHybridTSpec}::CxxPart::initHybrid(jni::alias_ref<jhybridobject> jThis) {
199
+ return makeCxxInstance(jThis);
196
200
  }
197
201
 
198
- bool ${name.JHybridTSpec}::equals(const std::shared_ptr<HybridObject>& other) {
199
- if (auto otherCast = std::dynamic_pointer_cast<${name.JHybridTSpec}>(other)) {
200
- return _javaPart == otherCast->_javaPart;
202
+ std::shared_ptr<JHybridObject> ${name.JHybridTSpec}::CxxPart::createHybridObject(const jni::local_ref<JHybridObject::JavaPart>& javaPart) {
203
+ auto castJavaPart = jni::dynamic_ref_cast<${name.JHybridTSpec}::JavaPart>(javaPart);
204
+ if (castJavaPart == nullptr) [[unlikely]] {
205
+ throw std::runtime_error("Failed to cast JHybridObject::JavaPart to ${name.JHybridTSpec}::JavaPart!");
201
206
  }
202
- return false;
207
+ return std::make_shared<${name.JHybridTSpec}>(castJavaPart);
203
208
  }
204
209
 
205
- void ${name.JHybridTSpec}::dispose() noexcept {
206
- static const auto method = javaClassStatic()->getMethod<void()>("dispose");
207
- method(_javaPart);
208
- }
209
-
210
- std::string ${name.JHybridTSpec}::toString() {
211
- static const auto method = javaClassStatic()->getMethod<jni::JString()>("toString");
212
- auto javaString = method(_javaPart);
213
- return javaString->toStdString();
210
+ void ${name.JHybridTSpec}::CxxPart::registerNatives() {
211
+ registerHybrid({
212
+ makeNativeMethod("initHybrid", ${name.JHybridTSpec}::CxxPart::initHybrid),
213
+ });
214
214
  }
215
215
 
216
216
  // Properties
@@ -275,14 +275,14 @@ function getFbjniMethodForwardImplementation(
275
275
  if (returnJNI.hasType) {
276
276
  // return something - we need to parse it
277
277
  body = `
278
- static const auto method = javaClassStatic()->getMethod<${cxxSignature}>("${methodName}");
278
+ static const auto method = _javaPart->javaClassStatic()->getMethod<${cxxSignature}>("${methodName}");
279
279
  auto __result = method(${paramsForward.join(', ')});
280
280
  return ${returnJNI.parse('__result', 'kotlin', 'c++', 'c++')};
281
281
  `
282
282
  } else {
283
283
  // void method. no return
284
284
  body = `
285
- static const auto method = javaClassStatic()->getMethod<${cxxSignature}>("${methodName}");
285
+ static const auto method = _javaPart->javaClassStatic()->getMethod<${cxxSignature}>("${methodName}");
286
286
  method(${paramsForward.join(', ')});
287
287
  `
288
288
  }
@@ -363,7 +363,7 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
363
363
  case 'c++':
364
364
  const hybridObjectType = getTypeAs(this.type, HybridObjectType)
365
365
  const fullName = this.getFullJHybridObjectName(hybridObjectType)
366
- return `${fullName}::javaobject`
366
+ return `${fullName}::JavaPart`
367
367
  default:
368
368
  return this.type.getCode(language)
369
369
  }
@@ -472,7 +472,6 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
472
472
  // any jni::HybridClass needs to be dereferenced to jobject with .get()
473
473
  case 'array-buffer':
474
474
  case 'function':
475
- case 'hybrid-object':
476
475
  case 'hybrid-object-base':
477
476
  case 'map':
478
477
  case 'promise':
@@ -849,9 +848,11 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
849
848
  case 'hybrid-object': {
850
849
  switch (language) {
851
850
  case 'c++':
852
- const hybrid = getTypeAs(this.type, HybridObjectType)
853
- const fullName = this.getFullJHybridObjectName(hybrid)
854
- return `${parameterName}->cthis()->shared_cast<${fullName}>()`
851
+ const hybridObject = getTypeAs(this.type, HybridObjectType)
852
+ const { JHybridTSpec } = getHybridObjectName(
853
+ hybridObject.hybridObjectName
854
+ )
855
+ return `${parameterName}->get${JHybridTSpec}()`
855
856
  default:
856
857
  return parameterName
857
858
  }
@@ -26,6 +26,11 @@ export function createKotlinHybridObject(spec: HybridObjectSpec): SourceFile[] {
26
26
  ...spec.baseTypes.flatMap((b) =>
27
27
  new HybridObjectType(b).getRequiredImports('kotlin')
28
28
  ),
29
+ {
30
+ name: 'com.margelo.nitro.core.HybridObject',
31
+ space: 'system',
32
+ language: 'kotlin',
33
+ },
29
34
  ]
30
35
  if (spec.isHybridView) {
31
36
  extraImports.push({
@@ -33,15 +38,10 @@ export function createKotlinHybridObject(spec: HybridObjectSpec): SourceFile[] {
33
38
  space: 'system',
34
39
  language: 'kotlin',
35
40
  })
36
- } else {
37
- extraImports.push({
38
- name: 'com.margelo.nitro.core.HybridObject',
39
- space: 'system',
40
- language: 'kotlin',
41
- })
42
41
  }
43
42
 
44
43
  let kotlinBase = spec.isHybridView ? 'HybridView' : 'HybridObject'
44
+ let cxxPartBase = 'HybridObject.CxxPart'
45
45
  if (spec.baseTypes.length > 0) {
46
46
  if (spec.baseTypes.length > 1) {
47
47
  throw new Error(
@@ -51,6 +51,7 @@ export function createKotlinHybridObject(spec: HybridObjectSpec): SourceFile[] {
51
51
  const base = spec.baseTypes[0]!
52
52
  const baseHybrid = new HybridObjectType(base)
53
53
  kotlinBase = baseHybrid.getCode('kotlin')
54
+ cxxPartBase = `${kotlinBase}.CxxPart`
54
55
  }
55
56
 
56
57
  const imports = extraImports
@@ -82,30 +83,27 @@ ${imports.join('\n')}
82
83
  "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName"
83
84
  )
84
85
  abstract class ${name.HybridTSpec}: ${kotlinBase}() {
85
- @DoNotStrip
86
- private var mHybridData: HybridData = initHybrid()
87
-
88
- init {
89
- super.updateNative(mHybridData)
90
- }
86
+ // Properties
87
+ ${indent(properties, ' ')}
91
88
 
92
- override fun updateNative(hybridData: HybridData) {
93
- mHybridData = hybridData
94
- super.updateNative(hybridData)
95
- }
89
+ // Methods
90
+ ${indent(methods, ' ')}
96
91
 
97
92
  // Default implementation of \`HybridObject.toString()\`
98
93
  override fun toString(): String {
99
94
  return "[HybridObject ${name.T}]"
100
95
  }
101
96
 
102
- // Properties
103
- ${indent(properties, ' ')}
104
-
105
- // Methods
106
- ${indent(methods, ' ')}
107
-
108
- private external fun initHybrid(): HybridData
97
+ // C++ backing class
98
+ @DoNotStrip
99
+ @Keep
100
+ protected open class CxxPart(javaPart: ${name.HybridTSpec}): ${cxxPartBase}(javaPart) {
101
+ // C++ ${name.JHybridTSpec}::CxxPart::initHybrid(...)
102
+ external override fun initHybrid(): HybridData
103
+ }
104
+ override fun createCxxPart(): CxxPart {
105
+ return CxxPart(this)
106
+ }
109
107
 
110
108
  companion object {
111
109
  protected const val TAG = "${name.HybridTSpec}"
@@ -15,6 +15,7 @@ interface Props {
15
15
 
16
16
  interface JNIHybridObjectRegistration {
17
17
  cppCode: string
18
+ cppDefinition: string
18
19
  requiredImports: SourceImport[]
19
20
  }
20
21
 
@@ -37,13 +38,21 @@ export function createJNIHybridObjectRegistration({
37
38
  space: 'system',
38
39
  },
39
40
  ],
41
+ cppDefinition: `
42
+ struct ${JHybridTSpec}Impl: public jni::JavaClass<${JHybridTSpec}Impl, ${JHybridTSpec}::JavaPart> {
43
+ static auto constexpr kJavaDescriptor = "L${jniNamespace};";
44
+ static std::shared_ptr<${JHybridTSpec}> create() {
45
+ static auto constructorFn = javaClassStatic()->getConstructor<${JHybridTSpec}Impl::javaobject()>();
46
+ jni::local_ref<${JHybridTSpec}::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
47
+ return javaPart->get${JHybridTSpec}();
48
+ }
49
+ };
50
+ `.trim(),
40
51
  cppCode: `
41
52
  HybridObjectRegistry::registerHybridObjectConstructor(
42
53
  "${hybridObjectName}",
43
54
  []() -> std::shared_ptr<HybridObject> {
44
- static DefaultConstructableObject<${JHybridTSpec}::javaobject> object("${jniNamespace}");
45
- auto instance = object.create();
46
- return instance->cthis()->shared();
55
+ return ${JHybridTSpec}Impl::create();
47
56
  }
48
57
  );
49
58
  `.trim(),
@@ -30,10 +30,6 @@ data class ${innerName}(@DoNotStrip val value: ${bridge.getTypeCode('kotlin')}):
30
30
  })
31
31
 
32
32
  const packageName = NitroConfig.current.getAndroidPackage('java/kotlin')
33
- const getterCases = variant.cases.map(([label]) => {
34
- const innerName = capitalizeName(label)
35
- return `is ${innerName} -> value as? T`
36
- })
37
33
  const isFunctions = variant.cases.map(([label]) => {
38
34
  const innerName = capitalizeName(label)
39
35
  return `
@@ -94,11 +90,6 @@ ${extraImports.join('\n')}
94
90
  sealed class ${kotlinName} {
95
91
  ${indent(innerClasses.join('\n'), ' ')}
96
92
 
97
- @Deprecated("getAs() is not type-safe. Use fold/asFirstOrNull/asSecondOrNull instead.", level = DeprecationLevel.ERROR)
98
- inline fun <reified T> getAs(): T? = when (this) {
99
- ${indent(getterCases.join('\n'), ' ')}
100
- }
101
-
102
93
  ${indent(isFunctions.join('\n'), ' ')}
103
94
 
104
95
  ${indent(asFunctions.join('\n'), ' ')}
@@ -158,7 +158,7 @@ public:
158
158
 
159
159
  public:
160
160
  static void updateViewProps(jni::alias_ref<jni::JClass> /* class */,
161
- jni::alias_ref<${JHybridTSpec}::javaobject> view,
161
+ jni::alias_ref<${JHybridTSpec}::JavaPart> view,
162
162
  jni::alias_ref<JStateWrapper::javaobject> stateWrapperInterface);
163
163
 
164
164
  public:
@@ -182,7 +182,7 @@ public:
182
182
  const setter = p.getSetterName('other')
183
183
  return `
184
184
  if (props->${name}.isDirty) {
185
- view->${setter}(props->${name}.value);
185
+ hybridView->${setter}(props->${name}.value);
186
186
  props->${name}.isDirty = false;
187
187
  }
188
188
  `.trim()
@@ -201,9 +201,9 @@ using namespace facebook;
201
201
  using ConcreteStateData = react::ConcreteState<${stateClassName}>;
202
202
 
203
203
  void J${stateUpdaterName}::updateViewProps(jni::alias_ref<jni::JClass> /* class */,
204
- jni::alias_ref<${JHybridTSpec}::javaobject> javaView,
204
+ jni::alias_ref<${JHybridTSpec}::JavaPart> javaView,
205
205
  jni::alias_ref<JStateWrapper::javaobject> stateWrapperInterface) {
206
- ${JHybridTSpec}* view = javaView->cthis();
206
+ std::shared_ptr<${JHybridTSpec}> hybridView = javaView->get${JHybridTSpec}();
207
207
 
208
208
  // Get concrete StateWrapperImpl from passed StateWrapper interface object
209
209
  jobject rawStateWrapper = stateWrapperInterface.get();
@@ -229,8 +229,7 @@ void J${stateUpdaterName}::updateViewProps(jni::alias_ref<jni::JClass> /* class
229
229
  // hybridRef changed - call it with new this
230
230
  const auto& maybeFunc = props->hybridRef.value;
231
231
  if (maybeFunc.has_value()) {
232
- std::shared_ptr<${JHybridTSpec}> shared = javaView->cthis()->shared_cast<${JHybridTSpec}>();
233
- maybeFunc.value()(shared);
232
+ maybeFunc.value()(hybridView);
234
233
  }
235
234
  props->hybridRef.isDirty = false;
236
235
  }