Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,30 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithMemoryBlock) {

auto sampleRate = args[1].getNumber();

auto promise = promiseVendor_->createPromise(
[data, size, sampleRate](std::shared_ptr<Promise> promise) {
std::thread([data, size, sampleRate, promise = std::move(promise)]() {
auto result =
AudioDecoder::decodeWithMemoryBlock(data, size, sampleRate);

if (!result) {
promise->reject("Failed to decode audio data.");
return;
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

promise->resolve([audioBufferHostObject = std::move(
audioBufferHostObject)](jsi::Runtime &runtime) {
auto jsiObject = jsi::Object::createFromHostObject(
runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
});
}).detach();
auto promise = promiseVendor_->createAsyncPromise(
[data, size, sampleRate]() -> PromiseResolver {
auto result =
AudioDecoder::decodeWithMemoryBlock(data, size, sampleRate);

if (!result) {
return [](jsi::Runtime &runtime)
-> std::variant<jsi::Value, std::string> {
return std::string("Failed to decode audio data.");
};
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

return [audioBufferHostObject =
std::move(audioBufferHostObject)](jsi::Runtime &runtime)
-> std::variant<jsi::Value, std::string> {
auto jsiObject =
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
};
});
return promise;
}
Expand All @@ -63,29 +64,29 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithFilePath) {
auto sourcePath = args[0].getString(runtime).utf8(runtime);
auto sampleRate = args[1].getNumber();

auto promise = promiseVendor_->createPromise(
[sourcePath, sampleRate](std::shared_ptr<Promise> promise) {
std::thread([sourcePath, sampleRate, promise = std::move(promise)]() {
auto result =
AudioDecoder::decodeWithFilePath(sourcePath, sampleRate);

if (!result) {
promise->reject("Failed to decode audio data source.");
return;
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

promise->resolve([audioBufferHostObject = std::move(
audioBufferHostObject)](jsi::Runtime &runtime) {
auto jsiObject = jsi::Object::createFromHostObject(
runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
});
}).detach();
auto promise = promiseVendor_->createAsyncPromise(
[sourcePath, sampleRate]() -> PromiseResolver {
auto result = AudioDecoder::decodeWithFilePath(sourcePath, sampleRate);

if (!result) {
return [](jsi::Runtime &runtime)
-> std::variant<jsi::Value, std::string> {
return std::string("Failed to decode audio data source.");
};
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

return [audioBufferHostObject =
std::move(audioBufferHostObject)](jsi::Runtime &runtime)
-> std::variant<jsi::Value, std::string> {
auto jsiObject =
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
};
});

return promise;
Expand All @@ -97,34 +98,31 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithPCMInBase64) {
auto inputChannelCount = args[2].getNumber();
auto interleaved = args[3].getBool();

auto promise = promiseVendor_->createPromise(
[b64, inputSampleRate, inputChannelCount, interleaved](
std::shared_ptr<Promise> promise) {
std::thread([b64,
inputSampleRate,
inputChannelCount,
interleaved,
promise = std::move(promise)]() {
auto result = AudioDecoder::decodeWithPCMInBase64(
b64, inputSampleRate, inputChannelCount, interleaved);

if (!result) {
promise->reject("Failed to decode audio data source.");
return;
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

promise->resolve([audioBufferHostObject = std::move(
audioBufferHostObject)](jsi::Runtime &runtime) {
auto jsiObject = jsi::Object::createFromHostObject(
runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
});
}).detach();
auto promise = promiseVendor_->createAsyncPromise(
[b64, inputSampleRate, inputChannelCount, interleaved]()
-> PromiseResolver {
auto result = AudioDecoder::decodeWithPCMInBase64(
b64, inputSampleRate, inputChannelCount, interleaved);

if (!result) {
return [](jsi::Runtime &runtime)
-> std::variant<jsi::Value, std::string> {
return std::string("Failed to decode audio data source.");
};
}

auto audioBufferHostObject =
std::make_shared<AudioBufferHostObject>(result);

return [audioBufferHostObject =
std::move(audioBufferHostObject)](jsi::Runtime &runtime)
-> std::variant<jsi::Value, std::string> {
auto jsiObject =
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
jsiObject.setExternalMemoryPressure(
runtime, audioBufferHostObject->getSizeInBytes());
return jsiObject;
};
});

return promise;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ jsi::Value PromiseVendor::createPromise(
}

jsi::Value PromiseVendor::createAsyncPromise(
std::function<std::variant<jsi::Value, std::string>(jsi::Runtime &)>
&&function) {
std::function<PromiseResolver()> &&function) {
auto &runtime = *runtime_;
auto callInvoker = callInvoker_;
auto threadPool = threadPool_;
Expand All @@ -74,32 +73,18 @@ jsi::Value PromiseVendor::createAsyncPromise(
jsi::Runtime &runtime,
const jsi::Value &thisValue,
const jsi::Value *arguments,
size_t count) -> jsi::Value {
size_t count) mutable -> jsi::Value {
auto resolveLocal = arguments[0].asObject(runtime).asFunction(runtime);
auto resolve = std::make_shared<jsi::Function>(std::move(resolveLocal));
auto rejectLocal = arguments[1].asObject(runtime).asFunction(runtime);
auto reject = std::make_shared<jsi::Function>(std::move(rejectLocal));

threadPool->schedule([callInvoker = std::move(callInvoker),
function = std::move(function),
resolve = std::move(resolve),
reject = std::move(reject),
&runtime]() {
auto result = function(runtime);
if (std::holds_alternative<jsi::Value>(result)) {
auto valueShared = std::make_shared<jsi::Value>(
std::move(std::get<jsi::Value>(result)));
callInvoker->invokeAsync([resolve, &runtime, valueShared]() -> void {
resolve->call(runtime, *valueShared);
});
} else {
auto errorMessage = std::get<std::string>(result);
callInvoker->invokeAsync([reject, &runtime, errorMessage]() -> void {
auto error = jsi::JSError(runtime, errorMessage);
reject->call(runtime, error.value());
});
}
});
threadPool->schedule(
&PromiseVendor::asyncPromiseJob,
std::move(callInvoker),
std::move(function),
std::move(resolve),
std::move(reject));
return jsi::Value::undefined();
};
auto promiseFunction = jsi::Function::createFromHostFunction(
Expand All @@ -110,4 +95,27 @@ jsi::Value PromiseVendor::createAsyncPromise(
return promiseCtor.callAsConstructor(runtime, std::move(promiseFunction));
}

void PromiseVendor::asyncPromiseJob(
std::shared_ptr<react::CallInvoker> callInvoker,
std::function<PromiseResolver()> &&function,
std::shared_ptr<jsi::Function> &&resolve,
std::shared_ptr<jsi::Function> &&reject) {
auto resolver = function();
callInvoker->invokeAsync(
[resolver = std::move(resolver),
reject = std::move(reject),
resolve = std::move(resolve)](jsi::Runtime &runtime) -> void {
auto result = resolver(runtime);
if (std::holds_alternative<jsi::Value>(result)) {
auto valueShared = std::make_shared<jsi::Value>(
std::move(std::get<jsi::Value>(result)));
resolve->call(runtime, *valueShared);
} else {
auto errorMessage = std::get<std::string>(result);
auto error = jsi::JSError(runtime, errorMessage);
reject->call(runtime, error.value());
}
});
}

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class Promise {
std::function<void(const std::string &)> reject_;
};

using PromiseResolver = std::function<std::variant<jsi::Value, std::string>(jsi::Runtime&)>;

class PromiseVendor {
public:
PromiseVendor(jsi::Runtime *runtime, const std::shared_ptr<react::CallInvoker> &callInvoker):
Expand All @@ -51,20 +53,30 @@ class PromiseVendor {
/// @example
/// ```cpp
/// auto promise = promiseVendor_->createAsyncPromise(
/// [](jsi::Runtime& rt) -> std::variant<jsi::Value, std::string> {
/// // Simulate some heavy work
/// []() -> PromiseResolver {
/// // Simulate some heavy work on a background thread
/// std::this_thread::sleep_for(std::chrono::seconds(2));
/// return jsi::String::createFromUtf8(rt, "Promise resolved successfully!");
/// return [](jsi::Runtime &rt) -> std::variant<jsi::Value, std::string> {
/// // Prepare and return the result on javascript thread
/// return jsi::String::createFromUtf8(rt, "Promise resolved successfully!");
/// };
/// }
/// );
///
/// return promise;
jsi::Value createAsyncPromise(std::function<std::variant<jsi::Value, std::string>(jsi::Runtime&)> &&function);
jsi::Value createAsyncPromise(std::function<PromiseResolver()> &&function);

private:
jsi::Runtime *runtime_;
std::shared_ptr<react::CallInvoker> callInvoker_;
std::shared_ptr<ThreadPool> threadPool_;

static void asyncPromiseJob(
std::shared_ptr<react::CallInvoker> callInvoker,
std::function<PromiseResolver()> &&function,
std::shared_ptr<jsi::Function> &&resolve,
std::shared_ptr<jsi::Function> &&reject
);
};

} // namespace audioapi
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#pragma once
#include <functional>

namespace audioapi {

/// @brief A forward declaration of a move-only function wrapper.
/// @note it is somehow required to have <R(Args...)> specialization below
/// @tparam Signature
template<typename Signature>
class move_only_function; // Forward declaration

/// @brief A move-only function wrapper similar to std::function but non-copyable.
/// @details This class allows you to store and invoke callable objects (like lambdas, function pointers, or functors)
/// that can be moved but not copied. It is useful for scenarios where you want to ensure that the callable
/// is unique and cannot be duplicated.
/// @note This implementation uses type erasure to store the callable object.
/// @note The callable object must be invocable with the specified arguments and return type.
/// @note IMPORTANT: This thing is implemented in C++23 standard and can be replaced with std::move_only_function once we switch to C++23.
/// @tparam R
/// @tparam ...Args
template<typename R, typename... Args>
class move_only_function<R(Args...)> {
/// @brief The base class for type erasure.
/// @note It gets optimized by Empty Base Optimization (EBO) when possible.
struct callable_base {
virtual ~callable_base() = default;
virtual R operator()(Args... args) = 0;
};

/// @brief The implementation of the callable object.
/// @tparam F
template<typename F>
struct callable_impl : callable_base {
/// @brief The stored callable object.
F f;

/// @brief Construct a new callable_impl object.
/// @tparam G
/// @param func
/// @note The enable_if_t ensures that F can be constructed from G&&.
template<typename G, typename = std::enable_if_t<std::is_constructible_v<F, G&&>>>
callable_impl(G&& func) : f(std::forward<G>(func)) {}

/// @brief Invoke the stored callable object with the given arguments.
/// @param args
/// @return R
/// @note The if constexpr is used to handle the case when R is void.
inline R operator()(Args... args) override {
if constexpr (std::is_void_v<R>) {
/// To avoid "warning: expression result unused" when R is void
f(std::forward<Args>(args)...);
} else {
return f(std::forward<Args>(args)...);
}
}
};

/// @brief The unique pointer to the base callable type.
std::unique_ptr<callable_base> impl_;

public:
move_only_function() = default;
move_only_function(std::nullptr_t) noexcept : impl_(nullptr) {}

template<typename F>
move_only_function(F&& f)
: impl_(std::make_unique<callable_impl<std::decay_t<F>>>(std::forward<F>(f))) {}

move_only_function(const move_only_function&) = delete;
move_only_function& operator=(const move_only_function&) = delete;

move_only_function(move_only_function&&) = default;
move_only_function& operator=(move_only_function&&) = default;

inline explicit operator bool() const noexcept {
return impl_ != nullptr;
}

inline R operator()(Args... args) {
/// We are unlikely to hit this case as we want to optimize for the common case.
if (impl_ == nullptr) [[ unlikely ]] {
throw std::bad_function_call{};
}
return (*impl_)(std::forward<Args>(args)...);
}

void swap(move_only_function& other) noexcept {
impl_.swap(other.impl_);
}
};
} // namespace audioapi
Loading