diff options
author | 2022-04-24 17:01:25 +0200 | |
---|---|---|
committer | 2022-05-16 00:55:02 +0200 | |
commit | 0b632445a933644c7eb10015eb04b7c21d562900 (patch) | |
tree | 5bd1fe200cc747086220dbcba90097bfe2cc4cdc /packages/main/src/stores | |
parent | chore(deps): upgrade dependencies (diff) | |
download | sophie-0b632445a933644c7eb10015eb04b7c21d562900.tar.gz sophie-0b632445a933644c7eb10015eb04b7c21d562900.tar.zst sophie-0b632445a933644c7eb10015eb04b7c21d562900.zip |
refactor: reduce service switcher tearing
We render the location bar and notification banners separately for each
service and keep track of the BrowserView size separately for each
service to reduce the tearing that appears when people switch services.
The tearing cannot be eliminated completely, because it comes from the
separation between the main and renderer processes. But we can at least
try and reduce the IPC round-tripping and layout calculations required
to accurately position the services.
This approach has an overhead compared to the single
BrowserViewPlaceholder approach, because the renderer process has to
layout the location bar and notification for all services, not only the
selected one. The number of IPC messages during windows resize is also
increased. To compensate, we increase the throttle interval for resize
IPC messages and let electron itself resize the BrowserView between IPC
updates. (We must still keep pumping IPC messages during window resize,
because, e.g., changes in notification banner size due to re-layouting
will still affect the required BrowserView size).
If further reduction of IPC traffic is needed, we could implement
batching for resize IPC messages and more intelligent throttling via a
token bucker mechanism.
Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'packages/main/src/stores')
-rw-r--r-- | packages/main/src/stores/MainStore.ts | 19 | ||||
-rw-r--r-- | packages/main/src/stores/Service.ts | 26 |
2 files changed, 27 insertions, 18 deletions
diff --git a/packages/main/src/stores/MainStore.ts b/packages/main/src/stores/MainStore.ts index d717bed..9affbd0 100644 --- a/packages/main/src/stores/MainStore.ts +++ b/packages/main/src/stores/MainStore.ts | |||
@@ -18,9 +18,9 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import type { Action, BrowserViewBounds } from '@sophie/shared'; | 21 | import type { Action } from '@sophie/shared'; |
22 | import type { ResourceKey } from 'i18next'; | 22 | import type { ResourceKey } from 'i18next'; |
23 | import { applySnapshot, flow, Instance, types } from 'mobx-state-tree'; | 23 | import { flow, Instance, types } from 'mobx-state-tree'; |
24 | 24 | ||
25 | import type I18nStore from '../i18n/I18nStore'; | 25 | import type I18nStore from '../i18n/I18nStore'; |
26 | import type { UseTranslationResult } from '../i18n/I18nStore'; | 26 | import type { UseTranslationResult } from '../i18n/I18nStore'; |
@@ -37,15 +37,6 @@ const log = getLogger('MainStore'); | |||
37 | 37 | ||
38 | const MainStore = types | 38 | const MainStore = types |
39 | .model('MainStore', { | 39 | .model('MainStore', { |
40 | browserViewBounds: types.optional( | ||
41 | types.model('BrowserViewBounds', { | ||
42 | x: 0, | ||
43 | y: 0, | ||
44 | width: 0, | ||
45 | height: 0, | ||
46 | }), | ||
47 | {}, | ||
48 | ), | ||
49 | shared: types.optional(SharedStore, {}), | 40 | shared: types.optional(SharedStore, {}), |
50 | i18n: types.frozen<I18nStore | undefined>(), | 41 | i18n: types.frozen<I18nStore | undefined>(), |
51 | }) | 42 | }) |
@@ -83,9 +74,6 @@ const MainStore = types | |||
83 | }), | 74 | }), |
84 | ) | 75 | ) |
85 | .actions((self) => ({ | 76 | .actions((self) => ({ |
86 | setBrowserViewBounds(bounds: BrowserViewBounds): void { | ||
87 | applySnapshot(self.browserViewBounds, bounds); | ||
88 | }, | ||
89 | setMainWindow(mainWindow: MainWindow | undefined): void { | 77 | setMainWindow(mainWindow: MainWindow | undefined): void { |
90 | self.mainWindow = mainWindow; | 78 | self.mainWindow = mainWindow; |
91 | }, | 79 | }, |
@@ -121,9 +109,6 @@ const MainStore = types | |||
121 | .actions((self) => ({ | 109 | .actions((self) => ({ |
122 | dispatch(action: Action): void { | 110 | dispatch(action: Action): void { |
123 | switch (action.action) { | 111 | switch (action.action) { |
124 | case 'set-browser-view-bounds': | ||
125 | self.setBrowserViewBounds(action.browserViewBounds); | ||
126 | break; | ||
127 | case 'set-selected-service-id': | 112 | case 'set-selected-service-id': |
128 | self.settings.setSelectedServiceId(action.serviceId); | 113 | self.settings.setSelectedServiceId(action.serviceId); |
129 | break; | 114 | break; |
diff --git a/packages/main/src/stores/Service.ts b/packages/main/src/stores/Service.ts index 1d46dc9..8ba8098 100644 --- a/packages/main/src/stores/Service.ts +++ b/packages/main/src/stores/Service.ts | |||
@@ -25,6 +25,7 @@ import { | |||
25 | defineServiceModel, | 25 | defineServiceModel, |
26 | ServiceAction, | 26 | ServiceAction, |
27 | type ServiceStateSnapshotIn, | 27 | type ServiceStateSnapshotIn, |
28 | type BrowserViewBounds, | ||
28 | } from '@sophie/shared'; | 29 | } from '@sophie/shared'; |
29 | import { type Instance, getSnapshot, cast, flow } from 'mobx-state-tree'; | 30 | import { type Instance, getSnapshot, cast, flow } from 'mobx-state-tree'; |
30 | 31 | ||
@@ -41,8 +42,18 @@ const Service = defineServiceModel(ServiceSettings) | |||
41 | .volatile( | 42 | .volatile( |
42 | (): { | 43 | (): { |
43 | serviceView: ServiceView | undefined; | 44 | serviceView: ServiceView | undefined; |
45 | x: number; | ||
46 | y: number; | ||
47 | width: number; | ||
48 | height: number; | ||
49 | hasBounds: boolean; | ||
44 | } => ({ | 50 | } => ({ |
45 | serviceView: undefined, | 51 | serviceView: undefined, |
52 | x: 0, | ||
53 | y: 0, | ||
54 | width: 0, | ||
55 | height: 0, | ||
56 | hasBounds: false, | ||
46 | }), | 57 | }), |
47 | ) | 58 | ) |
48 | .views((self) => ({ | 59 | .views((self) => ({ |
@@ -59,10 +70,20 @@ const Service = defineServiceModel(ServiceSettings) | |||
59 | })) | 70 | })) |
60 | .views((self) => ({ | 71 | .views((self) => ({ |
61 | get shouldBeVisible(): boolean { | 72 | get shouldBeVisible(): boolean { |
62 | return self.shouldBeLoaded && !self.hasError; | 73 | // Do not attach service views for which we don't know the appropriate frame size, |
74 | // because they will just appear in a random location until the frame size is determined. | ||
75 | return self.shouldBeLoaded && !self.hasError && self.hasBounds; | ||
63 | }, | 76 | }, |
64 | })) | 77 | })) |
65 | .actions((self) => ({ | 78 | .actions((self) => ({ |
79 | setBrowserViewBounds(bounds: BrowserViewBounds): void { | ||
80 | self.x = bounds.x; | ||
81 | self.y = bounds.y; | ||
82 | self.width = bounds.width; | ||
83 | self.height = bounds.height; | ||
84 | self.hasBounds = true; | ||
85 | self.serviceView?.updateBounds(); | ||
86 | }, | ||
66 | setServiceView(serviceView: ServiceView | undefined): void { | 87 | setServiceView(serviceView: ServiceView | undefined): void { |
67 | self.serviceView = serviceView; | 88 | self.serviceView = serviceView; |
68 | }, | 89 | }, |
@@ -263,6 +284,9 @@ const Service = defineServiceModel(ServiceSettings) | |||
263 | .actions((self) => ({ | 284 | .actions((self) => ({ |
264 | dispatch(action: ServiceAction): void { | 285 | dispatch(action: ServiceAction): void { |
265 | switch (action.action) { | 286 | switch (action.action) { |
287 | case 'set-browser-view-bounds': | ||
288 | self.setBrowserViewBounds(action.browserViewBounds); | ||
289 | break; | ||
266 | case 'back': | 290 | case 'back': |
267 | self.goBack(); | 291 | self.goBack(); |
268 | break; | 292 | break; |