Skip to content

Commit

Permalink
fix: Make NativeNitroModules a code-gen'd TurboModule again
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed Sep 23, 2024
1 parent 98d1c15 commit ed941c6
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,133 +17,40 @@ namespace facebook::react {
using namespace margelo::nitro;

NativeNitroModules::NativeNitroModules(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(kModuleName, jsInvoker), _callInvoker(jsInvoker) {}
: NativeNitroModulesCxxSpec(jsInvoker), _callInvoker(jsInvoker) {}

NativeNitroModules::~NativeNitroModules() {}

jsi::Value NativeNitroModules::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
std::string name = propName.utf8(runtime);

if (name == "install") {
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forUtf8(runtime, "install"), 0,
[this](jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, size_t count) -> jsi::Value {
install(runtime);
return jsi::Value::undefined();
});
}
if (name == "createHybridObject") {
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forUtf8(runtime, "createHybridObject"), 2,
[this](jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, size_t count) -> jsi::Value {
#ifdef NITRO_DEBUG
if (count != 1 && count != 2) [[unlikely]] {
throw jsi::JSError(runtime, "NitroModules.createHybridObject(..) expects 1 or 2 arguments, but " + std::to_string(count) +
" were supplied!");
}
#endif
jsi::String objectName = args[0].asString(runtime);
std::optional<jsi::Object> optionalArgs = std::nullopt;
if (count > 1) {
optionalArgs = args[1].asObject(runtime);
}

return createHybridObject(runtime, objectName, optionalArgs);
});
}
if (name == "hasHybridObject") {
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forUtf8(runtime, "hasHybridObject"), 1,
[this](jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, size_t count) -> jsi::Value {
#ifdef NITRO_DEBUG
if (count != 1) [[unlikely]] {
throw jsi::JSError(runtime,
"NitroModules.hasHybridObject(..) expects 1 argument (name), but received " + std::to_string(count) + "!");
}
#endif
jsi::String objectName = args[0].asString(runtime);
return hasHybridObject(runtime, objectName);
});
}
if (name == "getAllHybridObjectNames") {
return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "getAllHybridObjectNames"), 0,
[this](jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args,
size_t count) -> jsi::Value { return getAllHybridObjectNames(runtime); });
}
if (name == "hasNativeState") {
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forUtf8(runtime, "hasNativeState"), 1,
[](jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, size_t count) -> jsi::Value {
jsi::Object object = args[0].asObject(runtime);
bool has = object.hasNativeState(runtime) && object.getNativeState(runtime) != nullptr;
return jsi::Value(has);
});
}
if (name == "removeNativeState") {
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forUtf8(runtime, "removeNativeState"), 1,
[](jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, size_t count) -> jsi::Value {
jsi::Object object = args[0].asObject(runtime);
object.setNativeState(runtime, nullptr);
return jsi::Value::undefined();
});
}
if (name == "box") {
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forUtf8(runtime, "box"), 1,
[](jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, size_t count) -> jsi::Value {
jsi::Object object = args[0].asObject(runtime);
#ifdef NITRO_DEBUG
if (!object.hasNativeState(runtime)) {
std::string stringified = args[0].toString(runtime).utf8(runtime);
throw std::runtime_error("Cannot box object " + stringified + " - it does not have a NativeState!");
}
#endif

std::shared_ptr<jsi::NativeState> nativeState = object.getNativeState(runtime);
std::shared_ptr<HybridObject> maybeHybridObject = std::dynamic_pointer_cast<HybridObject>(nativeState);
if (maybeHybridObject == nullptr) {
std::string stringified = args[0].toString(runtime).utf8(runtime);
throw std::runtime_error("Cannot box object " + stringified + " - it has a NativeState, but it's not a HybridObject!");
}
// Setup
void NativeNitroModules::install(jsi::Runtime& runtime) {
// Installs the global Dispatcher mechanism into this Runtime.
// This allows creating Promises and calling back to JS.
auto dispatcher = std::make_shared<CallInvokerDispatcher>(_callInvoker);
Dispatcher::installRuntimeGlobalDispatcher(runtime, dispatcher);
}

auto boxed = std::make_shared<BoxedHybridObject>(maybeHybridObject);
return jsi::Object::createFromHostObject(runtime, boxed);
});
}
if (name == "buildType") {
jsi::String NativeNitroModules::getBuildType(jsi::Runtime& runtime) {
#ifdef NITRO_DEBUG
return jsi::String::createFromAscii(runtime, "debug");
#else
return jsi::String::createFromAscii(runtime, "release");
#endif
}

return jsi::Value::undefined();
}

void NativeNitroModules::install(jsi::Runtime& runtime) {
// Installs the global Dispatcher mechanism into this Runtime.
// This allows creating Promises and calling back to JS.
auto dispatcher = std::make_shared<CallInvokerDispatcher>(_callInvoker);
Dispatcher::installRuntimeGlobalDispatcher(runtime, dispatcher);
}

jsi::Value NativeNitroModules::createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName,
const std::optional<jsi::Object>&) {
auto name = hybridObjectName.utf8(runtime);
// TODO: Pass args? Do we need that?
auto hybridObject = HybridObjectRegistry::createHybridObject(name.c_str());
return hybridObject->toObject(runtime);
// Creating Hybrid Objects
jsi::Object NativeNitroModules::createHybridObject(jsi::Runtime& runtime, jsi::String name) {
auto hybridObjectName = name.utf8(runtime);
auto hybridObject = HybridObjectRegistry::createHybridObject(hybridObjectName);
return hybridObject->toObject(runtime).getObject(runtime);
}

jsi::Value NativeNitroModules::hasHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName) {
std::string name = hybridObjectName.utf8(runtime);
bool exists = HybridObjectRegistry::hasHybridObject(name);
bool NativeNitroModules::hasHybridObject(jsi::Runtime& runtime, jsi::String name) {
std::string hybridObjectName = name.utf8(runtime);
bool exists = HybridObjectRegistry::hasHybridObject(hybridObjectName);
return exists;
}

jsi::Value NativeNitroModules::getAllHybridObjectNames(jsi::Runtime& runtime) {
jsi::Array NativeNitroModules::getAllHybridObjectNames(jsi::Runtime& runtime) {
std::vector<std::string> keys = HybridObjectRegistry::getAllHybridObjectNames();
jsi::Array array(runtime, keys.size());
for (size_t i = 0; i < keys.size(); i++) {
Expand All @@ -152,4 +59,34 @@ jsi::Value NativeNitroModules::getAllHybridObjectNames(jsi::Runtime& runtime) {
return array;
}

// Boxing
jsi::Object NativeNitroModules::box(jsi::Runtime& runtime, jsi::Object object) {
#ifdef NITRO_DEBUG
if (!object.hasNativeState(runtime)) {
std::string stringified = jsi::Value(runtime, object).toString(runtime).utf8(runtime);
throw std::runtime_error("Cannot box object " + stringified + " - it does not have a NativeState!");
}
#endif

std::shared_ptr<jsi::NativeState> nativeState = object.getNativeState(runtime);
std::shared_ptr<HybridObject> maybeHybridObject = std::dynamic_pointer_cast<HybridObject>(nativeState);
if (maybeHybridObject == nullptr) {
std::string stringified = jsi::Value(runtime, object).toString(runtime).utf8(runtime);
throw std::runtime_error("Cannot box object " + stringified + " - it has a NativeState, but it's not a HybridObject!");
}

auto boxed = std::make_shared<BoxedHybridObject>(maybeHybridObject);
return jsi::Object::createFromHostObject(runtime, boxed);
}

// NativeState Helpers
bool NativeNitroModules::hasNativeState(jsi::Runtime& runtime, jsi::Object object) {
bool has = object.hasNativeState(runtime) && object.getNativeState(runtime) != nullptr;
return has;
}

void NativeNitroModules::removeNativeState(jsi::Runtime& runtime, jsi::Object object) {
object.setNativeState(runtime, nullptr);
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,34 @@
#pragma once

#include <ReactCommon/TurboModule.h>
#include <ReactCodegen/NitroModulesJSI.h>

namespace facebook::react {

using namespace facebook;

// The base C++-based TurboModule. This is the entry point where all nitro modules get initialized.
class NativeNitroModules : public TurboModule {
class NativeNitroModules : public NativeNitroModulesCxxSpec<NativeNitroModules> {
public:
NativeNitroModules(std::shared_ptr<CallInvoker> jsInvoker);
~NativeNitroModules();

public:
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& propName) override;

// Setup
void install(jsi::Runtime& runtime);
// Hybrid Objects stuff
jsi::Value createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName, const std::optional<jsi::Object>& args);
jsi::Value hasHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName);
jsi::Value getAllHybridObjectNames(jsi::Runtime& runtime);
jsi::String getBuildType(jsi::Runtime &rt);

// Creating Hybrid Objects
jsi::Object createHybridObject(jsi::Runtime &rt, jsi::String name);
bool hasHybridObject(jsi::Runtime &rt, jsi::String name);
jsi::Array getAllHybridObjectNames(jsi::Runtime &rt);

// Boxing
jsi::Object box(jsi::Runtime &rt, jsi::Object obj);

// NativeState Helpers
bool hasNativeState(jsi::Runtime &rt, jsi::Object obj);
void removeNativeState(jsi::Runtime &rt, jsi::Object obj);

public:
constexpr static auto kModuleName = "NitroModulesCxx";
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native-nitro-modules/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,13 @@
}
]
]
},
"codegenConfig": {
"name": "NitroModules",
"type": "modules",
"jsSrcsDir": "src",
"android": {
"javaPackageName": "com.margelo.nitro"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,26 @@ import { TurboModuleRegistry } from 'react-native'
import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'
import { ModuleNotFoundError } from './ModuleNotFoundError'

// This TurboModule is *not* codegen'd.
// It's handwritten, because otherwise the app's CMakeLists wants to build it.
// Instead, we want to build it ourselves and have full control over the CMakeLists.
export interface NativeNitroSpec extends TurboModule {
export interface Spec extends TurboModule {
// Set up
install(): void
// Hybrid Objects stuff
createHybridObject(name: string, args?: UnsafeObject): UnsafeObject
createHybridObject(name: string): UnsafeObject
hasHybridObject(name: string): boolean
getAllHybridObjectNames(): string[]
// JSI Helpers
hasNativeState(obj: UnsafeObject): boolean
removeNativeState(obj: UnsafeObject): void
buildType: 'debug' | 'release'
getBuildType(): 'debug' | 'release'
box(obj: UnsafeObject): UnsafeObject
}

let turboModule: NativeNitroSpec | undefined
export function getNativeNitroModules(): NativeNitroSpec {
let turboModule: Spec | undefined
export function getNativeNitroModules(): Spec {
if (turboModule == null) {
try {
// 1. Get (and initialize) the C++ TurboModule
turboModule =
TurboModuleRegistry.getEnforcing<NativeNitroSpec>('NitroModulesCxx')
turboModule = TurboModuleRegistry.getEnforcing<Spec>('NitroModulesCxx')

// 2. Install Dispatcher and required bindings into the Runtime
turboModule.install()
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native-nitro-modules/src/NitroModules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getNativeNitroModules } from './NitroModulesTurboModule'
import { getNativeNitroModules } from './NativeNitroModules'
import type { HybridObject } from './HybridObject'

/**
Expand Down Expand Up @@ -76,7 +76,7 @@ export const NitroModules = {
*/
get buildType(): 'debug' | 'release' {
const nitro = getNativeNitroModules()
return nitro.buildType
return nitro.getBuildType()
},
/**
* Boxes the given {@linkcode hybridObject} into a {@linkcode BoxedHybridObject<T>}, which can
Expand Down

0 comments on commit ed941c6

Please sign in to comment.