diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f475332..1eec7ffb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,10 @@ include(FetchContent) # -------------------------------------------------- FetchContent_Declare(arcana.cpp GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git - GIT_TAG c726dbe58713eda65bfb139c257093c43479b894) + 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) 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..33067bd2 --- /dev/null +++ b/Core/Foundation/Include/Babylon/PerfTrace.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include + + +namespace Babylon +{ + namespace PerfTrace + { + 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: + ~Handle(); + Handle(const Handle&) = delete; + Handle& operator=(const Handle&) = delete; + 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); + 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 new file mode 100644 index 00000000..157e9d3f --- /dev/null +++ b/Core/Foundation/Source/PerfTrace.cpp @@ -0,0 +1,76 @@ +#include "PerfTrace.h" +#include +#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 + class Handle::Impl + { + public: + Impl(const char* name) : m_region(name) {} + private: + arcana::trace_region m_region; + }; + + 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..344169a8 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,19 +256,26 @@ namespace Babylon::Polyfills::Internal } } - m_request.SendAsync().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(); - } - }); + 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(); + } + }); } void XMLHttpRequest::SetReadyState(ReadyState readyState) @@ -277,6 +286,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};