Skip to content

Plugin Refactor - #157

Merged
syntheticmagus merged 43 commits into
BabylonJS:masterfrom
syntheticmagus:pluginRefactor
Feb 25, 2020
Merged

Plugin Refactor#157
syntheticmagus merged 43 commits into
BabylonJS:masterfrom
syntheticmagus:pluginRefactor

Conversation

@syntheticmagus

Copy link
Copy Markdown
Contributor

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).

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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@syntheticmagus syntheticmagus Feb 20, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Core/AppRuntime/Source/TaskChain.h Outdated

namespace Babylon
{
class TaskChain

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider naming this WorkQueue (or JSWorkQueue) (I feel like that is a more standard name for this concept).

Comment thread Core/AppRuntime/Source/TaskChain.cpp Outdated

namespace Babylon
{
TaskChain::TaskChain(std::function<void()> threadProcedure)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused why the windowPtr, width, and height need to be passed to both NativeWindow and NativeEngine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 bghgary left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good overall. I like the separation of the plugins.

Comment thread Apps/ValidationTests/Win32/App.cpp Outdated
WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
std::unique_ptr<Babylon::RuntimeWin32> runtime{};
std::unique_ptr<Babylon::AppRuntime> runtime{};
std::string rootUrl{};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this global now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Core/AppRuntime/CMakeLists.txt Outdated
AppRuntime(const char* rootUrl = nullptr);
~AppRuntime();

const std::string& GetRootUrl() const;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should talk about the convention for getters in our code.

Suggested change
const std::string& GetRootUrl() const;
const std::string& RootUrl() const;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's const anyway, maybe we should just make the member public.

Comment on lines +23 to +28
#if WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
assert(SUCCEEDED(hr));
auto coInitScopeGuard = gsl::finally([] { CoUninitialize(); });
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it should be outside of this class and in AppRuntimeWin32 or something.

Comment thread Core/AppRuntime/Source/TaskChain.h Outdated
std::mutex m_blockingTickMutex{};
std::mutex m_suspendMutex{};
std::condition_variable m_suspendConditionVariable{};
bool m_suspended{ false };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: might consider auto formatting all the files

Comment thread Core/JsRuntime/Source/JsRuntime.cpp Outdated
static constexpr auto JS_WINDOW_NAME = "window";
}

JsRuntime::JsRuntime(DispatchFunctionT&& dispatchFunction)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be a &&. There is no reason to force people to move the dispatch function into the constructor.

Suggested change
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe TicketedCollection.h to be consistent in naming schemes?


add_library(BabylonNativeUtils ${SOURCES})

target_include_directories(BabylonNativeUtils PRIVATE "Include/Babylon")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that arcana is already added?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Apps/Playground/Win32/App.cpp Outdated
// Initialize XMLHttpRequest plugin.
runtime->Dispatch([rootUrl](Napi::Env env)
{
Babylon::XMLHttpRequest::Initialize(env, rootUrl.data());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@syntheticmagus
syntheticmagus marked this pull request as ready for review February 22, 2020 00:39

@syntheticmagus syntheticmagus left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?
  • ScriptLoader cannot live in Dependencies because it depends on JsRuntime. 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 on JsRuntime.
  • 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 also XMLHttpRequest.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 as Babylon/NativeEngine.h while the other can only be accessed as NativeEngine.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 ScriptLoader at 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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go ahead and do as part of this PR?

Comment thread Apps/Playground/Win32/App.cpp Outdated
InputManager::Initialize(*runtime, *inputBuffer);

Babylon::ScriptLoader loader{ *runtime, rootUrl };
Babylon::ScriptLoader loader{ *runtime, runtime->RootUrl };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We chatted offline and it would be better as a getter.

Comment thread Core/AppRuntime/Source/AppRuntime.cpp Outdated

AppRuntime::~AppRuntime()
AppRuntime::AppRuntime(const char* rootUrl, std::unique_ptr<WorkQueue> workQueue)
: JsRuntime([& workQueue = *workQueue](auto func) { workQueue.Append(std::move(func)); })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: workQueue = workQueue.get()

constexpr auto JS_ROOT_URL_NAME = "RootUrl";
}

void InitializeXMLHttpRequest(JsRuntime& runtime, const char* rootUrl)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void InitializeXMLHttpRequest(JsRuntime& runtime, const char* rootUrl)
void InitializeXMLHttpRequest(JsRuntime& runtime, std::string rootUrl)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants