Skip to content

Commit e35ed9a

Browse files
committed
make effects run just before and after full render
1 parent 3775e8d commit e35ed9a

3 files changed

Lines changed: 73 additions & 19 deletions

File tree

docs/source/examples/snake_game.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,14 @@ async def animate():
108108
def use_async_effect(function):
109109
def ensure_effect_future():
110110
future = asyncio.ensure_future(function())
111-
return future.cancel
111+
112+
def cleanup():
113+
if future.done():
114+
future.result()
115+
else:
116+
future.cancel()
117+
118+
return cleanup
112119

113120
idom.hooks.use_effect(ensure_effect_future)
114121

@@ -120,7 +127,7 @@ async def interval():
120127
await asyncio.sleep(rate - (time.time() - usage_time.current))
121128
usage_time.current = time.time()
122129

123-
return interval()
130+
return asyncio.ensure_future(interval())
124131

125132

126133
def use_snake_food(grid_size, current_snake):

idom/core/hooks.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
overload,
1515
)
1616

17+
from loguru import logger
18+
1719
from idom.utils import Ref
1820

1921
from .element import AbstractElement
@@ -373,15 +375,24 @@ def element_will_render(self) -> None:
373375
self._is_rendering = True
374376

375377
for effect in self._event_effects.will_render:
376-
effect()
378+
try:
379+
effect()
380+
except Exception:
381+
msg = f"Pre-render effect {effect} failed for {self.element}"
382+
logger.exception(msg)
377383

378384
self._event_effects.will_render.clear()
379385
self._event_effects.will_unmount.clear()
380386

381387
def element_did_render(self) -> None:
382388
"""The element completed a render"""
383389
for effect in self._event_effects.did_render:
384-
effect()
390+
try:
391+
effect()
392+
except Exception:
393+
msg = f"Post-render effect {effect} failed for {self.element}"
394+
logger.exception(msg)
395+
385396
self._event_effects.did_render.clear()
386397

387398
self._is_rendering = False
@@ -393,7 +404,12 @@ def element_did_render(self) -> None:
393404
def element_will_unmount(self) -> None:
394405
"""The element is about to be removed from the layout"""
395406
for effect in self._event_effects.will_unmount:
396-
effect()
407+
try:
408+
effect()
409+
except Exception:
410+
msg = f"Pre-unmount effect {effect} failed for {self.element}"
411+
logger.exception(msg)
412+
397413
self._event_effects.will_unmount.clear()
398414

399415
def set_current(self) -> None:

idom/core/layout.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,22 +101,36 @@ async def _element_states(self) -> AsyncIterator[ElementState]:
101101
try:
102102
yield {self._root.id: root_element_state}
103103
finally:
104-
self._unmount_element_state(root_element_state)
104+
self._delete_element_state(root_element_state)
105105

106106
async def _create_layout_update(self, element: AbstractElement) -> LayoutUpdate:
107107
element_state = self._element_states[element.id]
108+
109+
element_state.life_cycle_hook.element_will_render()
110+
111+
for state in self._iter_element_states_from_root(
112+
element_state,
113+
include_root=False,
114+
):
115+
state.life_cycle_hook.element_will_unmount()
116+
117+
self._clear_element_state_event_handlers(element_state)
118+
self._delete_element_state_children(element_state)
119+
108120
old_model = element_state.model.copy() # we copy because it will be mutated
109121
new_model = await self._render_element(element_state)
110122
changes = make_patch(old_model, new_model).patch
123+
124+
for state in self._iter_element_states_from_root(
125+
element_state,
126+
include_root=True,
127+
):
128+
state.life_cycle_hook.element_did_render()
129+
111130
return LayoutUpdate(path=element_state.path, changes=changes)
112131

113132
async def _render_element(self, element_state: ElementState) -> Dict[str, Any]:
114133
try:
115-
element_state.life_cycle_hook.element_will_render()
116-
117-
self._clear_element_state_event_handlers(element_state)
118-
self._unmount_element_state_children(element_state)
119-
120134
# BUG: https://github.com/python/mypy/issues/9256
121135
raw_model = await _render_with_life_cycle_hook(element_state) # type: ignore
122136

@@ -126,8 +140,6 @@ async def _render_element(self, element_state: ElementState) -> Dict[str, Any]:
126140
resolved_model = await self._render_model(element_state, raw_model)
127141
element_state.model.clear()
128142
element_state.model.update(resolved_model)
129-
130-
element_state.life_cycle_hook.element_did_render()
131143
except Exception:
132144
logger.exception(f"Failed to render {element_state.element_obj}")
133145

@@ -206,24 +218,43 @@ def _create_element_state(
206218

207219
def _reset_element_state(self, element_state: ElementState) -> None:
208220
self._clear_element_state_event_handlers(element_state)
209-
self._unmount_element_state_children(element_state)
221+
self._delete_element_state_children(element_state)
210222

211-
def _unmount_element_state(self, element_state: ElementState) -> None:
212-
element_state.life_cycle_hook.element_will_unmount()
223+
def _delete_element_state(self, element_state: ElementState) -> None:
213224
self._clear_element_state_event_handlers(element_state)
214-
self._unmount_element_state_children(element_state)
225+
self._delete_element_state_children(element_state)
215226
del self._element_states[element_state.element_obj.id]
216227

217228
def _clear_element_state_event_handlers(self, element_state: ElementState) -> None:
218229
for handler_id in element_state.event_handler_ids:
219230
del self._event_handlers[handler_id]
220231
element_state.event_handler_ids.clear()
221232

222-
def _unmount_element_state_children(self, element_state: ElementState) -> None:
233+
def _delete_element_state_children(self, element_state: ElementState) -> None:
223234
for e_id in element_state.child_elements_ids:
224-
self._unmount_element_state(self._element_states[e_id])
235+
self._delete_element_state(self._element_states[e_id])
225236
element_state.child_elements_ids.clear()
226237

238+
def _iter_element_states_from_root(
239+
self,
240+
root_element_state: ElementState,
241+
include_root: bool,
242+
) -> Iterator[ElementState]:
243+
if include_root:
244+
pending = [root_element_state]
245+
else:
246+
pending = [
247+
self._element_states[i] for i in root_element_state.child_elements_ids
248+
]
249+
250+
while pending:
251+
visited_element_state = pending.pop(0)
252+
yield visited_element_state
253+
pending.extend(
254+
self._element_states[i]
255+
for i in visited_element_state.child_elements_ids
256+
)
257+
227258

228259
@coroutine
229260
def _render_with_life_cycle_hook(element_state: ElementState) -> Iterator[None]:

0 commit comments

Comments
 (0)