Plugin Refactor - #157
Conversation
…here we want it long-term.
…Impl's responsibilities.
…Runtime's hideable behaviors ended up getting consumed by the TaskChain.
| auto* windowPtr = reinterpret_cast<ABI::Windows::UI::Core::ICoreWindow*>(CoreWindow::GetForCurrentThread()); | ||
| m_runtime->Dispatch([windowPtr, width, height](Napi::Env env) | ||
| { | ||
| Babylon::NativeWindow::Initialize(env, windowPtr, width, height); |
There was a problem hiding this comment.
Thinking about how this will work later with multi-view... maybe we would initialize the NativeWindow with just the env, and then call a function on it like RegisterView that takes the windowPtr, width, and height? Is it worth trying to get the right API now even if we only support one window/view?
There was a problem hiding this comment.
This is actually an analogue to the JavaScript concept of the Window, so this particular aspect of it likely won't change for multiview scenarios as I think that's a separate issue. This is one of the plugins we should probably consider renaming, though, to help make it more clear what exactly it is.
|
|
||
| namespace Babylon | ||
| { | ||
| class TaskChain |
There was a problem hiding this comment.
Consider naming this WorkQueue (or JSWorkQueue) (I feel like that is a more standard name for this concept).
|
|
||
| namespace Babylon | ||
| { | ||
| TaskChain::TaskChain(std::function<void()> threadProcedure) |
There was a problem hiding this comment.
It seems weird that this has to be passed in, and the assumption is that this function eventually calls RunTaskChain. Why can't TaskChain start it's own work queue thread, and the TaskChain consumer just call Append to get work injected into the thread?
There was a problem hiding this comment.
It might be possible to do it that way; I'll have to think about it more. This is a fairly direct port of the existing mechanism, and the main reasoning behind that mechanism is because of stack variables. In particular, V8 wants its initialization and state to be stored on the stack, and it goes to great lengths to make it difficult to do anything else. Thus, to use V8 correctly, we need to actually call TaskChain::RunTaskChain from inside the scope that contains the V8 state, rather than calling a task at the start to set up V8 and presumably calling another later to tear it down.
| }); | ||
|
|
||
| // Initialize NativeEngine plugin. | ||
| Babylon::InitializeNativeEngine(*m_runtime, windowPtr, width, height); |
There was a problem hiding this comment.
I'm a bit confused why the windowPtr, width, and height need to be passed to both NativeWindow and NativeEngine.
There was a problem hiding this comment.
This, in part, is just me porting over what the existing API was rather than making too many invasive API changes at this point in the refactor. However, the reason the API was that way is because of macOS and iOS. Those three values are required (in the current implementation) for the initialization of bgfx, and my understanding is that the Apple operating systems require bgfx to be initialized on the main thread, where it doesn't have access to the NativeWindow because the Napi::Env can't be accessed on that thread and may not even exist yet anyway.
To be clear, this is something I'd definitely like to think about revising, but I'm trying to keep cleanup work on the plugins themselves to a minimum in this change. I'm currently hoping to limit this PR as much as possible to restructuring, after which we can tackle the individual plugins in relative isolation.
bghgary
left a comment
There was a problem hiding this comment.
Looks pretty good overall. I like the separation of the plugins.
| WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name | ||
| std::unique_ptr<Babylon::RuntimeWin32> runtime{}; | ||
| std::unique_ptr<Babylon::AppRuntime> runtime{}; | ||
| std::string rootUrl{}; |
There was a problem hiding this comment.
Workaround for dangerous design decision that makes XMLHttpRequest::Initialize take a const char* that must outlive the JsRuntime. Touched on this in the comment I made in .../Win32/App.cpp. When that issue is fixed, this variable should also stop being global.
| AppRuntime(const char* rootUrl = nullptr); | ||
| ~AppRuntime(); | ||
|
|
||
| const std::string& GetRootUrl() const; |
There was a problem hiding this comment.
We should talk about the convention for getters in our code.
| const std::string& GetRootUrl() const; | |
| const std::string& RootUrl() const; |
There was a problem hiding this comment.
Since it's const anyway, maybe we should just make the member public.
| #if WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP | ||
| HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); | ||
| assert(SUCCEEDED(hr)); | ||
| auto coInitScopeGuard = gsl::finally([] { CoUninitialize(); }); | ||
| #endif | ||
|
|
There was a problem hiding this comment.
This seems like it should be outside of this class and in AppRuntimeWin32 or something.
| std::mutex m_blockingTickMutex{}; | ||
| std::mutex m_suspendMutex{}; | ||
| std::condition_variable m_suspendConditionVariable{}; | ||
| bool m_suspended{ false }; |
There was a problem hiding this comment.
nit: might consider auto formatting all the files
| static constexpr auto JS_WINDOW_NAME = "window"; | ||
| } | ||
|
|
||
| JsRuntime::JsRuntime(DispatchFunctionT&& dispatchFunction) |
There was a problem hiding this comment.
This shouldn't be a &&. There is no reason to force people to move the dispatch function into the constructor.
| JsRuntime::JsRuntime(DispatchFunctionT&& dispatchFunction) | |
| JsRuntime::JsRuntime(DispatchFunctionT dispatchFunction) |
Use std::move like you are doing in the other constructor to move. The calling code can choose whether to move or not.
There was a problem hiding this comment.
The reasoning here was that this is protected, and that in this case we actually do want to force the function to be moved in. I don't think there's ever a case where we want this function to be passed by copy because there's no reason for anybody else to have this lambda. In such a circumstance, is it better to still leave the choice up to the calling code?
| @@ -0,0 +1,131 @@ | |||
| set(CMAKE_CXX_STANDARD 17) | |||
There was a problem hiding this comment.
I'm not sure I understand the point of this file. Are we expecting that anyone who wants to use any dependencies add this as a subdirectory in CMakeLists.txt?
There was a problem hiding this comment.
Spoke offline. This file allows the Dependencies folder to participate in the CMake pattern shared by the other major folders (Core, Plugins, and Apps). Most of the code in here is currently just ported directly from the old build system, but hopefully we'll improve it as we go to make it as clean and airtight as possible over time.
| @@ -0,0 +1,13 @@ | |||
| set(SOURCES | |||
| "Include/Babylon/NetworkUtils.h" | |||
| "Include/Babylon/ticketed_collection.h" | |||
There was a problem hiding this comment.
Maybe TicketedCollection.h to be consistent in naming schemes?
|
|
||
| add_library(BabylonNativeUtils ${SOURCES}) | ||
|
|
||
| target_include_directories(BabylonNativeUtils PRIVATE "Include/Babylon") |
There was a problem hiding this comment.
Ugh, this naming scheme is killing me. The directory name is snake case but the library is upper camel case?
| target_include_directories(BabylonNativeUtils INTERFACE "Include") | ||
|
|
||
| target_link_libraries(BabylonNativeUtils | ||
| PUBLIC arcana |
There was a problem hiding this comment.
Is it expected that arcana is already added?
There was a problem hiding this comment.
Yes. The pattern being established here is that plugins (and modules following the plugin pattern) do not supply their own dependencies, but instead assume they exist as a part of a larger build system that bears the responsibility of making sure all dependencies are loaded in the right order. This allows the inclusions to scale horizontally and, when applicable, share dependencies amongst themselves.
| // Initialize XMLHttpRequest plugin. | ||
| runtime->Dispatch([rootUrl](Napi::Env env) | ||
| { | ||
| Babylon::XMLHttpRequest::Initialize(env, rootUrl.data()); |
There was a problem hiding this comment.
This is dangerous and probably doesn't work at all, must have dodged tests: the char* being passed to Initialize points to a copy of rootUrl local to this lambda, which will get cleaned up long before any XMLHttpRequest objects are created. XMLHttpRequest::Initialize should make a copy of the text and put it somewhere safe.
Co-Authored-By: Gary Hsu <bghgary@users.noreply.github.com>
syntheticmagus
left a comment
There was a problem hiding this comment.
Thanks for the feedback, Cedric! For some reason, I can't seem to reply to your comment directly, so here are some of the points you asked about. Short version: most of my responses resolve down to, "That's definitely something I want to fix, but in a separate PR." 😀
- "Helper," to me, implies there's an object, which seems misleading in this case. I have no particular attachment to "Utils," but it is relatively standard and unsurprising: people will know the purpose that folder is serving as soon as they read that name. Is there another name you'd prefer?
ScriptLoadercannot live inDependenciesbecause it depends onJsRuntime. I don't think we've formally defined it, but I tend to think of a plugin not as something that necessarily extends the functionality of the JS engine, but rather as any component that (1) is designed to be reused and (2) depends onJsRuntime.- Yes, this naming is something of an antipattern. This is one of the things I want to clean up as soon as possible. However, during this particular check-in, I'm trying to avoid changing the implementations of the plugins themselves in order to limit the scope of the check-in to structure as much as possible. Because of the amount of exposure in the "real"
NativeEngine.h(and alsoXMLHttpRequest.h, which I also moved to this pattern earlier today) was problematic, I created this two-header alternative as a temporary workaround. It should never be possible to confuse the headers -- one must always be referred to asBabylon/NativeEngine.hwhile the other can only be accessed asNativeEngine.h, but it's still something I want to remove as one of the first things I do to clean up the plugins. With that in mind, do you think it's fine for this temporary workaround to get checked in to avoid getting even more scope creep in this PR? - Probably. I hadn't really thought about it that way, and I think we'd need to change the way we're thinking about N-API to make that work, but it might be something explore. I think I'd rather we explore that in a separate PR, though, as it seems like it might go deep. Also, regarding using other JS engines, the current architecture still allows us to swap amongst them, it's just not done using the plugin mechanism.
- The
ScriptLoaderat this point is pretty directly just a port of some existing capabilities that were originally on the Runtime. We can modify it to be platform-adaptive, or make platform-specific versions of it, or something like that if we want to. For now, though, all it did was consume the Runtime's pre-existing dependency on the network utils. - Yep, the plugin initialization code is a mess. That's another artifact of my reluctance to change the plugins themselves any more than I have to as a part of this check-in. My hope is that, as soon as this change is in, I'll be able to embark on a sequence of PRs tackling the different plugins individually, one by one; getting the initialization code to all conform to a consistent pattern is one of the things I'd fix in those PRs. It's definitely a lot uglier and more complicated than it needs to be right now; I'm just not sure this particular PR is the right place to fix that.
| ANativeWindow *window = ANativeWindow_fromSurface(env, surface); | ||
| runtime->UpdateWindow(width, height, window); | ||
| // TODO: Add UpdateWindow for Android. | ||
| // runtime->UpdateWindow(width, height, window); |
There was a problem hiding this comment.
Don't forget to add this functionality back!
| std::string url = env->GetStringUTFChars(sourceURL, &iscopy); | ||
| runtime->Eval(env->GetStringUTFChars(source, &iscopy), url); | ||
| std::string src = env->GetStringUTFChars(source, &iscopy); | ||
| // TODO: Put this on ScriptLoader? |
There was a problem hiding this comment.
Go ahead and do as part of this PR?
| InputManager::Initialize(*runtime, *inputBuffer); | ||
|
|
||
| Babylon::ScriptLoader loader{ *runtime, rootUrl }; | ||
| Babylon::ScriptLoader loader{ *runtime, runtime->RootUrl }; |
There was a problem hiding this comment.
We chatted offline and it would be better as a getter.
|
|
||
| AppRuntime::~AppRuntime() | ||
| AppRuntime::AppRuntime(const char* rootUrl, std::unique_ptr<WorkQueue> workQueue) | ||
| : JsRuntime([& workQueue = *workQueue](auto func) { workQueue.Append(std::move(func)); }) |
There was a problem hiding this comment.
nit: workQueue = workQueue.get()
| constexpr auto JS_ROOT_URL_NAME = "RootUrl"; | ||
| } | ||
|
|
||
| void InitializeXMLHttpRequest(JsRuntime& runtime, const char* rootUrl) |
There was a problem hiding this comment.
| void InitializeXMLHttpRequest(JsRuntime& runtime, const char* rootUrl) | |
| void InitializeXMLHttpRequest(JsRuntime& runtime, std::string rootUrl) |
This is the refactor to convert Babylon Native to relying on plugins as its preeminent variety of component. This change is intended to improve scalability by "flattening" the structure of the repository, moving certain responsibilities outside the library and allowing a looser coupling of components. This change is by no means finished; but with the build system working and the apps of the four primary Windows builds confirmed to run, it is now at least ready to be looked at (and, if desired, downloaded and tried).