[WIP] WebWorker Demo (to improve search performance) #47432
Closed
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixed Issues
$#47627
Details
This PR shows how web workers can be used to offload computational heavy work to another thread.
This PR is probably going to be closed and split into multiple smaller PRs, but this is the PoC implementation.
Demo
Screen.Recording.2024-08-14.at.16.59.47.mov
How the new API works in the expensify code
We can simply replace our
useMemo
s that do computational heavy work withuseWorkerMemo
:The difference is that the function we want to invoke in our
useWorkerMemo
needs to be defined in a separate file (to conform to how web workers work on web). This function here is calledworkerFilterOptions
and is defined like this:createWorkerFactory
is basically a helper function that creates a new worker, which is loaded from the URL provided (which points to a new file in our code which exposes the worker function).Lets have a look at how
exampleWorker.ts
is implemented:A
worker
is exported that uses ourexpose
API.expose
is used to define a function. That is the function ouruseWorkerMemo
"will call" (though web worker APIs).It basically just forwards a call to a library utility of ours for filtering the search results.
Note
I choose this API as it:
useMemo
to an offloaded oneNow, as our computation happens on another thread, the data retrieval is asynchronous. Thus
useWorkerMemo
can't directly return the data. Instead it returns an object that is either:while we are requesting data or its:
There is also an error state in case the worker function had any problems.
Using this data we can display a loading UI while we wait for the data.
How it can work on mobile
Web Workers is an API only available on the web/desktop. For mobile we'll need a different implementation. The idea is to still use the same API on mobile, but the implementation details will be different.
Running code on a separate thread is something we already do in expensify: we use
"worklet"
functions from reanimated to run on the UI thread.However, for those extra computations we don't won't to run them on the UI thread, but a special separate thread. Additionally reanimated worklets lack some functionality such as being able to return a value from the worklet function.
At Margelo we developed react-native-worklets-core, which works very similar to the
worklets
we know from reanimated.However, with
react-native-worklets-core
we can run on different threads and return values from our worklets. Additionally inreact-native-worklets-core
we can support loading code from custom bundles (which reanimated worklets can't). This way we can implement the "workers" on mobile as follows using worklets:We simply add the worklet directive to our worker:
// .. import {expose} from '@libs/Worker'; const worker = expose((options: Options, searchInputValue: string, config?: FilterOptionsConfig) => { + "worklet"; return FilterListUtils.filterOptions(options, searchInputValue, config); });
We'd construct our worker factory differently on mobile:
react-native-worklets-core
will taking care of creating a separate bundle that can be imported at runtime.Internally in
useWorkerMemo
instead of using the message pattern from the web workers, we will simply call our worklet function:Currently the library doesn't support
await import
which we'd need. We reckon that implementing this behaviour would take between 3-5 days.Next steps
useWebWorker
,expose
,createWorkerFactory
, etc APIfilterOptions
functionfilterOption
functions lives inOptionListUtils
which is huge and imports API code that imports pusher, which depends onwindow.
, which is not available in workers. It would be a waste to include so much code in our worker anyway.FilterListUtils
utility, as it contains thefilterOptions
function isolated.filterOptions
withuseWorkerMemo
OptionsListUtils.getSearchOptions
with useWorkerMemo (thats also quite heavy and blocking the search page)Fixed Issues
$ #46591
PROPOSAL: https://expensify.slack.com/archives/C05LX9D6E07/p1723454316012939?thread_ts=1723209631.007999&cid=C05LX9D6E07
Tests
Offline tests
QA Steps
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.src/languages/*
files and using the translation methodSTYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)Design
label and/or tagged@Expensify/design
so the design team can review the changes.ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
MacOS: Desktop