-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change StoreProtocol to implement transaction
#47
base: main
Are you sure you want to change the base?
Conversation
...rather than send. `send` becomes an asynchronous main actor isolated protocol extension that guarantees your changes will always happen on the main thread, but offers only a fire-and-forget interface.
@bfollington here's my take on #46. Not sure if the method rename will cause too much carnage in Subconscious. If so, we could instead do:
The tradeoff would be that we have to manually go change The constraints we're working with:
As a result:
|
Nice! This is very similar to (one of) the abstractions I tried, it looks good on paper:
BUT it seems the major performance penalty comes from the synchronous mutation of a binding. Comparing the attached code snippets, the first one has a noticeable hang (visible in profiling tools) on each tab transition and the second does not. A - has hangs TabView(
selection: Binding(
get: { store.state.selectedAppTab },
transact: store.transact,
tag: AppAction.setSelectedAppTab
)
) B - no hangs TabView(
selection: Binding(
get: { store.state.selectedAppTab },
transact: { action in Task { store.transact(action) } },
tag: AppAction.setSelectedAppTab
)
) Now, this violates a bunch of runtime guarantees so it's not acceptable. I think, if we want safe bindings + good performance, we might have to pull a similar trick to C - proxy binding @State var selectedTab: AppTab = .notebook
var body: some View {
TabView(
selection: $selectedTab
) { ... }
.onAppear {
selectedTab = store.state.selectedAppTab
}
.onChange(of: selectedTab) { v in
store.send(.setSelectedAppTab(selectedTab))
}
} This has the best performance I've seen yet in the profiler and suggests we should avoid ever directly calling I agree with your assessment that bindings seem to expect synchronous updates but we seem to be doing WAY too much work on each update to support that (because we touch so much of the tree on every change to every store). |
You can somewhat bundle this into a ViewModifier: @State var selectedTab: AppTab = .notebook
var body: some View {
TabView(selection: $selectedTab) { ... }
.modifier(
StoreProxyViewModifier(
state: $selectedTab,
get: { store.state.selectedAppTab },
send: store.send,
tag: AppAction.setSelectedAppTab
)
)
} I also had a go at writing a custom |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should go ahead and land this, along with subconsciousnetwork/subconscious#1124. They make a considerable improvement to performance, even if we know we need to go deeper on this problem in the long run.
...rather than send.
transact(:)
is a non-isolated method that performs a synchronous state transaction in response to an action.send(:)
becomes an asynchronous main actor isolated protocol extension that guarantees your changes will always happen on the main thread, but offers only a fire-and-forget interface.