Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Async + shutdown #1397

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
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
101 changes: 64 additions & 37 deletions Apps/UnitTests/Shared/Shared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,43 +98,6 @@ TEST(JavaScript, All)
EXPECT_EQ(exitCode, 0);
}

/*
This test does a serie of initialization and shutdowns.
It needs the shutdown PR to be merged before running properly.
TEST(NativeAPI, LifeCycle)
{
for (int cycle = 0; cycle < 20; cycle++)
{
Babylon::Graphics::Device device{deviceConfig};
std::optional<Babylon::Polyfills::Canvas> nativeCanvas;

Babylon::AppRuntime runtime{};
runtime.Dispatch([&device, &nativeCanvas](Napi::Env env) {
device.AddToJavaScript(env);

Babylon::Polyfills::XMLHttpRequest::Initialize(env);
Babylon::Polyfills::Console::Initialize(env, [](const char* message, auto) {
printf("%s", message);
fflush(stdout);
});
Babylon::Polyfills::Window::Initialize(env);
nativeCanvas.emplace(Babylon::Polyfills::Canvas::Initialize(env));
Babylon::Plugins::NativeEngine::Initialize(env);
});

Babylon::ScriptLoader loader{runtime};
loader.LoadScript("app:///Scripts/babylon.max.js");
loader.LoadScript("app:///Scripts/babylonjs.materials.js");

for (int frame = 0; frame < 10; frame++)
{
device.StartRenderingCurrentFrame();
device.FinishRenderingCurrentFrame();
}
}
}
*/

TEST(Performance, Spheres)
{
// create a bunch of sphere, does the rendering for a number of frames, log time it took
Expand Down Expand Up @@ -212,6 +175,70 @@ TEST(Performance, Spheres)
std::cout.flush();
}

TEST(Shutdown, AsyncShaderCompilation)
{
std::promise<int32_t> exitCodePromise;
Babylon::Graphics::Device device{ deviceConfig };
auto update{ device.GetUpdate("update") };
std::optional<Babylon::Polyfills::Canvas> nativeCanvas;
Babylon::AppRuntime runtime{};

// OpenGL/Linux needs to render 1 frame to set bgfx caps. More infos here : https://github.com/BabylonJS/BabylonNative/issues/1163
device.StartRenderingCurrentFrame();
update.Start();
update.Finish();
device.FinishRenderingCurrentFrame();

runtime.Dispatch([&device, &nativeCanvas, &exitCodePromise](Napi::Env env) {
device.AddToJavaScript(env);
Babylon::Polyfills::XMLHttpRequest::Initialize(env);
Babylon::Polyfills::Console::Initialize(env, [](const char* message, auto) {
std::cout << message << std::endl;
std::cout.flush();
});
Babylon::Polyfills::Window::Initialize(env);
nativeCanvas.emplace(Babylon::Polyfills::Canvas::Initialize(env));
Babylon::Plugins::NativeEngine::Initialize(env);
auto setExitCodeCallback = Napi::Function::New(
env, [&exitCodePromise](const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
exitCodePromise.set_value(info[0].As<Napi::Number>().Int32Value());
}, "setExitCode");
env.Global().Set("setExitCode", setExitCodeCallback);
});
Babylon::ScriptLoader loader{ runtime };
loader.LoadScript("app:///Scripts/babylon.max.js");
loader.LoadScript("app:///Scripts/babylonjs.materials.js");
loader.Eval(R"(
function CreateBoxAsync(scene) {
BABYLON.Mesh.CreateBox("box1", 0.2, scene);
return Promise.resolve();
}
var engine = new BABYLON.NativeEngine();
var scene = new BABYLON.Scene(engine);
CreateBoxAsync(scene).then(function () {
var disc = BABYLON.Mesh.CreateDisc("disc", 3, 60, scene);
scene.createDefaultCamera(true, true, true);
scene.activeCamera.alpha += Math.PI;
scene.createDefaultLight(true);
var mainMaterial = new BABYLON.PBRMaterial("main", scene);
disc.material = mainMaterial;
engine.runRenderLoop(function () {
scene.render();
});
console.log("Ready to shutdown.");
setExitCode(1);
}, function (ex) {
console.log(ex.message, ex.stack);
});
)", "script");
auto exitCode{ exitCodePromise.get_future().get() };
device.StartRenderingCurrentFrame();
update.Start();
update.Finish();
device.FinishRenderingCurrentFrame();
}

int RunTests(const Babylon::Graphics::Configuration& config)
{
deviceConfig = config;
Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ FetchContent_Declare(ios-cmake
GIT_REPOSITORY https://github.com/leetal/ios-cmake.git
GIT_TAG 4.4.1)
FetchContent_Declare(JsRuntimeHost
GIT_REPOSITORY https://github.com/BabylonJS/JsRuntimeHost.git
GIT_TAG 9ccb9477a97501525f3f9ca0e00e6971934037ec)
GIT_REPOSITORY https://github.com/cedricguillemet/JsRuntimeHost.git
GIT_TAG 05105aed423084a3581ad6ea8cfaa3b2c2d1641f)
FetchContent_Declare(OpenXR-MixedReality
GIT_REPOSITORY https://github.com/microsoft/OpenXR-MixedReality.git
GIT_TAG 67424511525b96a36847f2a96d689d99e5879503)
Expand Down
10 changes: 10 additions & 0 deletions Plugins/NativeCamera/Source/CameraDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
#include <gsl/gsl>
#include "Capability.h"

#ifdef _MSC_VER
#define DISABLE_UNREACHABLE_CODE_WARNINGS \
__pragma(warning(push)) \
__pragma(warning(disable : 4702))
#define ENABLE_UNREACHABLE_CODE_WARNINGS __pragma(warning(pop))
#else
#define DISABLE_UNREACHABLE_CODE_WARNINGS
#define ENABLE_UNREACHABLE_CODE_WARNINGS
#endif

namespace Babylon::Plugins
{
enum class RedEyeReduction
Expand Down
12 changes: 1 addition & 11 deletions Plugins/NativeCamera/Source/ImageCapture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@
#include <Babylon/JsRuntime.h>
#include <Babylon/JsRuntimeScheduler.h>

#ifdef _MSC_VER
#define DISABLE_UNREACHABLE_CODE_WARNINGS \
__pragma(warning(push)) \
__pragma(warning(disable : 4702))
#define ENABLE_UNREACHABLE_CODE_WARNINGS __pragma(warning(pop))
#else
#define DISABLE_UNREACHABLE_CODE_WARNINGS
#define ENABLE_UNREACHABLE_CODE_WARNINGS
#endif

namespace Babylon::Plugins::Internal
{
namespace
Expand Down Expand Up @@ -169,7 +159,7 @@ DISABLE_UNREACHABLE_CODE_WARNINGS
bytes.resize(result.size());
std::memcpy(bytes.data(), result.data(), result.size());
return bytes;
}).then(m_runtimeScheduler, arcana::cancellation::none(), [env, deferred](const arcana::expected<std::vector<uint8_t>, std::exception_ptr>& result) {
}).then(m_runtimeScheduler.Get(), arcana::cancellation::none(), [env, deferred](const arcana::expected<std::vector<uint8_t>, std::exception_ptr>& result) {
if (result.has_error())
{
deferred.Reject(Napi::Error::New(env, result.error()).Value());
Expand Down
7 changes: 3 additions & 4 deletions Plugins/NativeCamera/Source/MediaDevices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ namespace Babylon::Plugins::Internal
videoConstraints = constraints.Get("video").As<Napi::Object>();
}
}

auto runtimeScheduler{std::make_unique<JsRuntimeScheduler>(JsRuntime::GetFromJavaScript(env))};
MediaStream::NewAsync(env, videoConstraints).then(*runtimeScheduler, arcana::cancellation::none(), [runtimeScheduler = std::move(runtimeScheduler), env, deferred](const arcana::expected<Napi::Object, std::exception_ptr>& result) {
DISABLE_UNREACHABLE_CODE_WARNINGS
MediaStream::NewAsync(env, videoConstraints).then(arcana::inline_scheduler, arcana::cancellation::none(), [env, deferred](const arcana::expected<Napi::Object, std::exception_ptr>& result) {
if (result.has_error())
{
deferred.Reject(Napi::Error::New(env, result.error()).Value());
Expand All @@ -48,7 +47,7 @@ namespace Babylon::Plugins::Internal

deferred.Resolve(result.value());
});

ENABLE_UNREACHABLE_CODE_WARNINGS
return std::move(promise);
}

Expand Down
11 changes: 8 additions & 3 deletions Plugins/NativeCamera/Source/MediaStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Babylon::Plugins
auto mediaStreamObject{Napi::Persistent(GetConstructor(env).New({}))};
auto mediaStream{MediaStream::Unwrap(mediaStreamObject.Value())};

return mediaStream->ApplyInitialConstraintsAsync(constraints).then(mediaStream->m_runtimeScheduler, arcana::cancellation::none(), [mediaStreamObject{std::move(mediaStreamObject)}]() {
return mediaStream->ApplyInitialConstraintsAsync(constraints).then(arcana::inline_scheduler, arcana::cancellation::none(), [mediaStreamObject{ std::move(mediaStreamObject) }]() {
return mediaStreamObject.Value();
});
}
Expand Down Expand Up @@ -58,13 +58,18 @@ namespace Babylon::Plugins

MediaStream::~MediaStream()
{
m_cancellationSource.cancel();
// HACK: This is a hack to make sure the camera device is destroyed on the JS thread.
// The napi-jsi adapter currently calls the destructors of JS objects possibly on the wrong thread.
// Once this is fixed, this hack will no longer be needed.
if (m_cameraDevice != nullptr)
{
// The cameraDevice should be destroyed on the JS thread as it may need to access main thread resources
// move ownership of the cameraDevice to a lambda and dispatch it with the runtimeScheduler so the destructor
// is called from that thread.
m_runtimeScheduler([cameraDevice = std::move(m_cameraDevice)]() {});
m_runtimeScheduler.Get()([cameraDevice = std::move(m_cameraDevice)]() {});
}
m_runtimeScheduler.Rundown();
}

Napi::Value MediaStream::GetVideoTracks(const Napi::CallbackInfo& info)
Expand Down Expand Up @@ -120,7 +125,7 @@ namespace Babylon::Plugins
// Create a persistent ref to the constraints object so it isn't destructed during our async work
auto constraintsRef{Napi::Persistent(constraints)};

return m_cameraDevice->OpenAsync(bestCamera.value().second).then(m_runtimeScheduler, arcana::cancellation::none(), [this, constraintsRef{std::move(constraintsRef)}](CameraDevice::CameraDimensions cameraDimensions) {
return m_cameraDevice->OpenAsync(bestCamera.value().second).then(m_runtimeScheduler.Get(), arcana::cancellation::none(), [this, constraintsRef{std::move(constraintsRef)}](CameraDevice::CameraDimensions cameraDimensions) {
this->Width = cameraDimensions.width;
this->Height = cameraDimensions.height;

Expand Down
1 change: 1 addition & 0 deletions Plugins/NativeCamera/Source/MediaStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ namespace Babylon::Plugins
JsRuntimeScheduler m_runtimeScheduler;

Napi::ObjectReference m_currentConstraints{};
arcana::cancellation_source m_cancellationSource;
};
}
14 changes: 11 additions & 3 deletions Plugins/NativeCamera/Source/NativeVideo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,18 @@ namespace Babylon::Plugins

NativeVideo::NativeVideo(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<NativeVideo>{info}
, m_runtimeScheduler{ JsRuntime::GetFromJavaScript(info.Env()) }
{
}

NativeVideo::~NativeVideo()
{
m_cancellationSource.cancel();

// Wait for async operations to complete.
m_runtimeScheduler.Rundown();
}

Napi::Value NativeVideo::GetVideoWidth(const Napi::CallbackInfo& /*info*/)
{
if (!m_streamObject.Value().IsNull() && !m_streamObject.Value().IsUndefined())
Expand Down Expand Up @@ -141,16 +150,15 @@ namespace Babylon::Plugins

Napi::Value NativeVideo::Play(const Napi::CallbackInfo& info)
{
auto env{info.Env()};
auto deferred{Napi::Promise::Deferred::New(env)};
auto deferred = Napi::Promise::Deferred::New(info.Env());

if (!m_IsPlaying && !m_streamObject.Value().IsNull() && !m_streamObject.Value().IsUndefined())
{
m_IsPlaying = true;
RaiseEvent("playing");
}

deferred.Resolve(env.Undefined());
deferred.Resolve(info.Env().Undefined());

return deferred.Promise();
}
Expand Down
5 changes: 4 additions & 1 deletion Plugins/NativeCamera/Source/NativeVideo.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Babylon::Plugins
static void Initialize(Napi::Env& env);
static Napi::Object New(const Napi::CallbackInfo& info);
NativeVideo(const Napi::CallbackInfo& info);
~NativeVideo() = default;
~NativeVideo();

void UpdateTexture(bgfx::TextureHandle textureHandle);

Expand All @@ -37,6 +37,9 @@ namespace Babylon::Plugins
Napi::Value GetSrcObject(const Napi::CallbackInfo& info);
void SetSrcObject(const Napi::CallbackInfo& info, const Napi::Value& value);

arcana::cancellation_source m_cancellationSource;
JsRuntimeScheduler m_runtimeScheduler;

std::unordered_map<std::string, std::vector<Napi::FunctionReference>> m_eventHandlerRefs{};

bool m_isReady{false};
Expand Down
Loading
Loading