From e712f073f156f7a2a9d3c57d36866284eccd8709 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Thu, 13 Nov 2025 19:12:01 -0800 Subject: [PATCH 1/7] Add PerfTrace API Add some more tracing in XmlHttpRequest --- CMakeLists.txt | 4 +- Core/Foundation/CMakeLists.txt | 13 +++- Core/Foundation/Include/Babylon/PerfTrace.h | 39 ++++++++++ Core/Foundation/Source/PerfTrace.cpp | 74 +++++++++++++++++++ .../XMLHttpRequest/Source/XMLHttpRequest.cpp | 14 +++- .../XMLHttpRequest/Source/XMLHttpRequest.h | 1 + 6 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 Core/Foundation/Include/Babylon/PerfTrace.h create mode 100644 Core/Foundation/Source/PerfTrace.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f475332..e2f57da2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ include(FetchContent) # Declarations # -------------------------------------------------- FetchContent_Declare(arcana.cpp - GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git - GIT_TAG c726dbe58713eda65bfb139c257093c43479b894) + GIT_REPOSITORY https://github.com/ryantrem/arcana.cpp.git + GIT_TAG c117bfda6fc855e8e63f6bfb8cf1b66d51026dcb) FetchContent_Declare(AndroidExtensions GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git GIT_TAG f7ed149b5360cc8a4908fece66607c5ce1e6095b) diff --git a/Core/Foundation/CMakeLists.txt b/Core/Foundation/CMakeLists.txt index 5c4fbf7a..d089d429 100644 --- a/Core/Foundation/CMakeLists.txt +++ b/Core/Foundation/CMakeLists.txt @@ -1,11 +1,20 @@ set(SOURCES "Include/Babylon/Api.h" "Include/Babylon/DebugTrace.h" - "Source/DebugTrace.cpp") + "Include/Babylon/PerfTrace.h" + "Source/DebugTrace.cpp" + "Source/PerfTrace.cpp") add_library(Foundation ${SOURCES}) -target_include_directories(Foundation INTERFACE "Include") +target_include_directories(Foundation + PRIVATE "Include/Babylon" + INTERFACE "Include") + +target_link_libraries(Foundation + PUBLIC napi + PRIVATE napi-extensions + PRIVATE arcana) set_property(TARGET Foundation PROPERTY FOLDER Core) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) diff --git a/Core/Foundation/Include/Babylon/PerfTrace.h b/Core/Foundation/Include/Babylon/PerfTrace.h new file mode 100644 index 00000000..23280e58 --- /dev/null +++ b/Core/Foundation/Include/Babylon/PerfTrace.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include + + +namespace Babylon +{ + namespace PerfTrace + { + enum class Level + { + None, + Mark, + Log, + }; + + class Handle + { + public: + ~Handle(); + Handle(const Handle&) = delete; + Handle& operator=(const Handle&) = delete; + Handle(Handle&&) noexcept; + Handle& operator=(Handle&&) noexcept; + + static Napi::Value ToNapi(Napi::Env env, Handle handle); + static Handle FromNapi(Napi::Value napiValue); + + private: + friend Handle Trace(const char* name); + Handle(const char* name); + struct Impl; + std::unique_ptr m_impl; + }; + + void SetLevel(Level level); + Handle Trace(const char* name); + } +} diff --git a/Core/Foundation/Source/PerfTrace.cpp b/Core/Foundation/Source/PerfTrace.cpp new file mode 100644 index 00000000..5534a627 --- /dev/null +++ b/Core/Foundation/Source/PerfTrace.cpp @@ -0,0 +1,74 @@ +#include "PerfTrace.h" +#include +#include + +namespace +{ + std::unordered_map g_traceHandles; + std::uint32_t g_nextTraceId = 0; +} + +namespace Babylon +{ + namespace PerfTrace + { + void SetLevel(Level level) + { + switch(level) + { + case Level::None: + arcana::trace_region::disable(); + break; + case Level::Mark: + arcana::trace_region::enable(arcana::trace_level::mark); + break; + case Level::Log: + arcana::trace_region::enable(arcana::trace_level::log); + break; + } + } + + // Private implementation to hide arcana::trace_region + struct Handle::Impl + { + arcana::trace_region region; + + Impl(const char* name) : region(name) {} + }; + + Handle::Handle(const char* name) + : m_impl(std::make_unique(name)) + { + } + + Handle::~Handle() = default; + Handle::Handle(Handle&&) noexcept = default; + Handle& Handle::operator=(Handle&&) noexcept = default; + + Handle Trace(const char* name) + { + return Handle(name); + } + + Napi::Value Handle::ToNapi(Napi::Env env, Handle traceHandle) + { + g_nextTraceId++; + g_traceHandles.emplace(g_nextTraceId, std::move(traceHandle)); + return Napi::Value::From(env, g_nextTraceId); + } + + Handle Handle::FromNapi(Napi::Value napiValue) + { + const std::uint32_t traceId = napiValue.As().Uint32Value(); + auto it = g_traceHandles.find(traceId); + if (it == g_traceHandles.end()) + { + throw std::runtime_error("Invalid TraceHandle ID"); + } + + Handle traceHandle = std::move(it->second); + g_traceHandles.erase(it); + return traceHandle; + } + } +} diff --git a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp index cff2371e..e355c951 100644 --- a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp +++ b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp @@ -1,6 +1,8 @@ #include "XMLHttpRequest.h" #include #include +#include +#include namespace Babylon::Polyfills::Internal { @@ -216,11 +218,11 @@ namespace Babylon::Polyfills::Internal void XMLHttpRequest::Open(const Napi::CallbackInfo& info) { - const auto inputURL = info[1].As(); + m_url = info[1].As(); try { - m_request.Open(MethodType::StringToEnum(info[0].As().Utf8Value()), inputURL); + m_request.Open(MethodType::StringToEnum(info[0].As().Utf8Value()), m_url); } catch (const std::exception& e) { @@ -254,7 +256,11 @@ namespace Babylon::Polyfills::Internal } } - m_request.SendAsync().then(m_runtimeScheduler, arcana::cancellation::none(), [this]() { + std::string traceName = (std::ostringstream{} << "XMLHttpRequest::Send [" << m_url << "]").str(); + arcana::trace_region sendRegion{traceName.c_str()}; + m_request.SendAsync().then(arcana::inline_scheduler, arcana::cancellation::none(), [sendRegion{std::make_optional(std::move(sendRegion))}]() mutable { + sendRegion.reset(); + }).then(m_runtimeScheduler, arcana::cancellation::none(), [this]() { SetReadyState(ReadyState::Done); RaiseEvent(EventType::LoadEnd); @@ -277,6 +283,8 @@ namespace Babylon::Polyfills::Internal void XMLHttpRequest::RaiseEvent(const char* eventType) { + std::string traceName = (std::ostringstream{} << "XMLHttpRequest::RaiseEvent [" << eventType << "] [" << m_url << "]").str(); + arcana::trace_region raiseEventRegion{traceName.c_str()}; const auto it = m_eventHandlerRefs.find(eventType); if (it != m_eventHandlerRefs.end()) { diff --git a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h index e59ae879..24139bda 100644 --- a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h +++ b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h @@ -44,6 +44,7 @@ namespace Babylon::Polyfills::Internal void SetReadyState(ReadyState readyState); void RaiseEvent(const char* eventType); + std::string m_url{}; UrlLib::UrlRequest m_request{}; JsRuntimeScheduler m_runtimeScheduler; ReadyState m_readyState{ReadyState::Unsent}; From 476b464efc2df8b6b03ed856f2fa4bc703dcde75 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Thu, 13 Nov 2025 19:47:49 -0800 Subject: [PATCH 2/7] Add missing include --- Core/Foundation/Source/PerfTrace.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/Foundation/Source/PerfTrace.cpp b/Core/Foundation/Source/PerfTrace.cpp index 5534a627..b6efb98d 100644 --- a/Core/Foundation/Source/PerfTrace.cpp +++ b/Core/Foundation/Source/PerfTrace.cpp @@ -1,5 +1,6 @@ #include "PerfTrace.h" #include +#include #include namespace From 090c6c1cf1ebe43aa186ef236f2ed30194c61a0d Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 14 Nov 2025 09:11:01 -0800 Subject: [PATCH 3/7] Update arcana to merged commit --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2f57da2..1eec7ffb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,11 +10,11 @@ include(FetchContent) # Declarations # -------------------------------------------------- FetchContent_Declare(arcana.cpp - GIT_REPOSITORY https://github.com/ryantrem/arcana.cpp.git - GIT_TAG c117bfda6fc855e8e63f6bfb8cf1b66d51026dcb) + GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git + GIT_TAG c02527c32d51b6b3ffeda36f8cf140d2e1c60bb9) FetchContent_Declare(AndroidExtensions GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git - GIT_TAG f7ed149b5360cc8a4908fece66607c5ce1e6095b) + GIT_TAG f7ed149b5360cc8a4908fece66607c5ce1e6095b) FetchContent_Declare(asio GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git GIT_TAG f693a3eb7fe72a5f19b975289afc4f437d373d9c) From 49be36bf75f2155cd615fd870e7cd9b344f0b70f Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 14 Nov 2025 09:27:18 -0800 Subject: [PATCH 4/7] Minor cleanup and comments --- Core/Foundation/Include/Babylon/PerfTrace.h | 12 +++++++++++- Core/Foundation/Source/PerfTrace.cpp | 9 +++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Core/Foundation/Include/Babylon/PerfTrace.h b/Core/Foundation/Include/Babylon/PerfTrace.h index 23280e58..33067bd2 100644 --- a/Core/Foundation/Include/Babylon/PerfTrace.h +++ b/Core/Foundation/Include/Babylon/PerfTrace.h @@ -9,11 +9,15 @@ namespace Babylon { enum class Level { + // Tracing is a no-op. None, + // Traces time intervals with platform specific profiling APIs. Mark, + // Traces time intervals and also performas platform specific logging. Log, }; + // Controls a perf trace interval. When the handle is destructed, the perf interval ends. class Handle { public: @@ -23,17 +27,23 @@ namespace Babylon Handle(Handle&&) noexcept; Handle& operator=(Handle&&) noexcept; + // Transfers ownership of the handle to a napi value. After this call, destructing the passed in handle has no effect. static Napi::Value ToNapi(Napi::Env env, Handle handle); + + // Transfers ownership of the handle from a napi value. After this call, destructing the returned handle will end the perf trace interval. static Handle FromNapi(Napi::Value napiValue); private: friend Handle Trace(const char* name); Handle(const char* name); - struct Impl; + class Impl; std::unique_ptr m_impl; }; + // Sets the trace level. It is None by default. void SetLevel(Level level); + + // Starts a perf trace interval. Destructing the returned handle ends the perf trace interval. Handle Trace(const char* name); } } diff --git a/Core/Foundation/Source/PerfTrace.cpp b/Core/Foundation/Source/PerfTrace.cpp index b6efb98d..157e9d3f 100644 --- a/Core/Foundation/Source/PerfTrace.cpp +++ b/Core/Foundation/Source/PerfTrace.cpp @@ -30,11 +30,12 @@ namespace Babylon } // Private implementation to hide arcana::trace_region - struct Handle::Impl + class Handle::Impl { - arcana::trace_region region; - - Impl(const char* name) : region(name) {} + public: + Impl(const char* name) : m_region(name) {} + private: + arcana::trace_region m_region; }; Handle::Handle(const char* name) From 597b8d07f2785973b61fa6483b9e7d5651bf6dd6 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 14 Nov 2025 09:35:06 -0800 Subject: [PATCH 5/7] PR feedback --- Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp index e355c951..86a4700e 100644 --- a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp +++ b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp @@ -257,8 +257,8 @@ namespace Babylon::Polyfills::Internal } std::string traceName = (std::ostringstream{} << "XMLHttpRequest::Send [" << m_url << "]").str(); - arcana::trace_region sendRegion{traceName.c_str()}; - m_request.SendAsync().then(arcana::inline_scheduler, arcana::cancellation::none(), [sendRegion{std::make_optional(std::move(sendRegion))}]() mutable { + auto sendRegion = std::make_optional(traceName.c_str()); + m_request.SendAsync().then(arcana::inline_scheduler, arcana::cancellation::none(), [sendRegion{std::move(sendRegion)}]() mutable { sendRegion.reset(); }).then(m_runtimeScheduler, arcana::cancellation::none(), [this]() { SetReadyState(ReadyState::Done); From 597018d313c1c85f4fcc3ebe0e5aa885de9823df Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 14 Nov 2025 09:45:51 -0800 Subject: [PATCH 6/7] Format in VS as requested! --- .../XMLHttpRequest/Source/XMLHttpRequest.cpp | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp index 86a4700e..bdec8689 100644 --- a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp +++ b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp @@ -259,20 +259,22 @@ namespace Babylon::Polyfills::Internal std::string traceName = (std::ostringstream{} << "XMLHttpRequest::Send [" << m_url << "]").str(); auto sendRegion = std::make_optional(traceName.c_str()); m_request.SendAsync().then(arcana::inline_scheduler, arcana::cancellation::none(), [sendRegion{std::move(sendRegion)}]() mutable { - sendRegion.reset(); - }).then(m_runtimeScheduler, arcana::cancellation::none(), [this]() { - SetReadyState(ReadyState::Done); - RaiseEvent(EventType::LoadEnd); - - // Assume the XMLHttpRequest will only be used for a single request and clear the event handlers. - // Single use seems to be the standard pattern, and we need to release our strong refs to event handlers. - m_eventHandlerRefs.clear(); - }).then(arcana::inline_scheduler, arcana::cancellation::none(), [env = info.Env()](arcana::expected result) { - if (result.has_error()) - { - Napi::Error::New(env, result.error()).ThrowAsJavaScriptException(); - } - }); + sendRegion.reset(); + }) + .then(m_runtimeScheduler, arcana::cancellation::none(), [this]() { + SetReadyState(ReadyState::Done); + RaiseEvent(EventType::LoadEnd); + + // Assume the XMLHttpRequest will only be used for a single request and clear the event handlers. + // Single use seems to be the standard pattern, and we need to release our strong refs to event handlers. + m_eventHandlerRefs.clear(); + }) + .then(arcana::inline_scheduler, arcana::cancellation::none(), [env = info.Env()](arcana::expected result) { + if (result.has_error()) + { + Napi::Error::New(env, result.error()).ThrowAsJavaScriptException(); + } + }); } void XMLHttpRequest::SetReadyState(ReadyState readyState) From 3c088ee6c38876a0008df7022ec113228eaa4045 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 14 Nov 2025 09:54:38 -0800 Subject: [PATCH 7/7] Line break --- Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp index bdec8689..344169a8 100644 --- a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp +++ b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp @@ -258,9 +258,10 @@ namespace Babylon::Polyfills::Internal std::string traceName = (std::ostringstream{} << "XMLHttpRequest::Send [" << m_url << "]").str(); auto sendRegion = std::make_optional(traceName.c_str()); - m_request.SendAsync().then(arcana::inline_scheduler, arcana::cancellation::none(), [sendRegion{std::move(sendRegion)}]() mutable { - sendRegion.reset(); - }) + m_request.SendAsync() + .then(arcana::inline_scheduler, arcana::cancellation::none(), [sendRegion{std::move(sendRegion)}]() mutable { + sendRegion.reset(); + }) .then(m_runtimeScheduler, arcana::cancellation::none(), [this]() { SetReadyState(ReadyState::Done); RaiseEvent(EventType::LoadEnd);