Skip to content

Commit 9434d86

Browse files
authored
Fix O(N²) mount/unmount scans in Session.patch_control (#6651)
* Fix O(N²) mount/unmount scans in Session.patch_control patch_control checked every removed control against all added controls (and vice versa) with linear any() scans, making each patch O(N²) in the number of added/removed controls. When a @ft.component re-renders, the frozen diff reports every matched control as both removed and re-added, so components rendering large lists pay this cost on every re-render, blocking the asyncio event loop for the duration. Precompute added/removed ID sets once so each membership check is O(1), reducing the whole function to O(N). Behaviour is unchanged: will_unmount/did_mount still fire only for controls not present in both sets, and index management, logging, and message sending are untouched. * Updating CHANGELOG.md for #6651
1 parent f259b95 commit 9434d86

2 files changed

Lines changed: 5 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
* Fix `flet.Router`'s default `on_view_pop` navigating to the wrong URL when an `outlet=True` layout sits between two views in `manage_views=True` mode. Popping such a view now targets the previous view entry's resolved URL — skipping outlet layouts and componentless grouping routes — instead of `chain[-2]`, which could equal the current view's URL and strand the page route, making the next navigation to it a no-op ([#6533](https://github.com/flet-dev/flet/pull/6533)) by @FeodorFitsner.
7070
* Fix `flet-audio.Audio.play()`/`seek()` timing out when replaying after playback had completed: under the default `ReleaseMode.RELEASE` the source is freed on completion and is now re-prepared on replay ([#6536](https://github.com/flet-dev/flet/issues/6536), [#6538](https://github.com/flet-dev/flet/pull/6538)) by @ndonkoHenri.
7171
* Fix `ft.run(view=ft.AppView.FLET_APP_HIDDEN)` briefly flashing the native window in the top-left corner during Windows desktop startup. The Windows runner now respects `FLET_HIDE_WINDOW_ON_START` and skips the first-frame `Show()` call so the window stays hidden until `page.window.visible = True`, matching the Linux desktop behavior; the same fix is applied to the `flet build windows` template runner so generated apps behave consistently. On Linux, pre-show window placement actions (`page.window.center()`, `page.window.alignment`) are now deferred until the window first becomes visible to avoid an analogous flash, and the window's `focused` state is preserved when a `prevent_close` handler cancels a close attempt ([#5897](https://github.com/flet-dev/flet/issues/5897), [#5914](https://github.com/flet-dev/flet/issues/5914), [#6527](https://github.com/flet-dev/flet/pull/6527)) by @ihmily.
72+
* Improve performance of checking added/removed controls in Session.patch_control from O(N²) to O(N) ([#6651](https://github.com/flet-dev/flet/pull/6651)) by @davidlawson.
7273

7374
## 0.85.2
7475

sdk/python/packages/flet/src/flet/messaging/session.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,9 @@ def patch_control(
247247
for c in removed_controls:
248248
patch_logger.debug(" %s", c)
249249

250+
added_ids = {added_control._i for added_control in added_controls}
250251
for removed_control in removed_controls:
251-
if not any(added._i == removed_control._i for added in added_controls):
252+
if removed_control._i not in added_ids:
252253
removed_control.will_unmount()
253254
self.__index.pop(removed_control._i, None)
254255

@@ -264,9 +265,10 @@ def patch_control(
264265
for ac in added_controls:
265266
patch_logger.debug(" %s", ac)
266267

268+
removed_ids = {removed_control._i for removed_control in removed_controls}
267269
for added_control in added_controls:
268270
self.__index[added_control._i] = added_control
269-
if not any(removed._i == added_control._i for removed in removed_controls):
271+
if added_control._i not in removed_ids:
270272
added_control.did_mount()
271273

272274
def apply_patch(self, control_id: int, patch: dict[str, Any]):

0 commit comments

Comments
 (0)