36
36
37
37
38
38
SendCoroutine = Callable [["VdomJsonPatch" ], Awaitable [None ]]
39
+ """Send model patches given by a dispatcher"""
40
+
39
41
RecvCoroutine = Callable [[], Awaitable [LayoutEvent ]]
42
+ """Called by a dispatcher to return a :class:`idom.core.layout.LayoutEvent`
43
+
44
+ The event will then trigger an :class:`idom.core.proto.EventHandlerType` in a layout.
45
+ """
46
+
47
+
48
+ class Stop (BaseException ):
49
+ """Stop dispatching changes and events
50
+
51
+ Raising this error will tell dispatchers to gracefully exit. Typically this is
52
+ called by code running inside a layout to tell it to stop rendering.
53
+ """
40
54
41
55
42
56
async def dispatch_single_view (
@@ -46,9 +60,12 @@ async def dispatch_single_view(
46
60
) -> None :
47
61
"""Run a dispatch loop for a single view instance"""
48
62
with layout :
49
- async with create_task_group () as task_group :
50
- task_group .start_soon (_single_outgoing_loop , layout , send )
51
- task_group .start_soon (_single_incoming_loop , layout , recv )
63
+ try :
64
+ async with create_task_group () as task_group :
65
+ task_group .start_soon (_single_outgoing_loop , layout , send )
66
+ task_group .start_soon (_single_incoming_loop , layout , recv )
67
+ except Stop :
68
+ logger .info ("Stopped dispatch task" )
52
69
53
70
54
71
SharedViewDispatcher = Callable [[SendCoroutine , RecvCoroutine ], Awaitable [None ]]
@@ -63,9 +80,8 @@ async def create_shared_view_dispatcher(
63
80
with layout :
64
81
(
65
82
dispatch_shared_view ,
66
- model_state ,
67
- all_patch_queues ,
68
- ) = await _make_shared_view_dispatcher (layout )
83
+ send_patch ,
84
+ ) = await _create_shared_view_dispatcher (layout )
69
85
70
86
dispatch_tasks : List [Future [None ]] = []
71
87
@@ -95,34 +111,35 @@ def dispatch_shared_view_soon(
95
111
else :
96
112
patch = VdomJsonPatch .create_from (update_future .result ())
97
113
98
- model_state .current = patch .apply_to (model_state .current )
99
- # push updates to all dispatcher callbacks
100
- for queue in all_patch_queues :
101
- queue .put_nowait (patch )
114
+ send_patch (patch )
102
115
103
116
104
117
def ensure_shared_view_dispatcher_future (
105
118
layout : LayoutType [LayoutUpdate , LayoutEvent ],
106
119
) -> Tuple [Future [None ], SharedViewDispatcher ]:
107
- """Ensure the future of a dispatcher created by :func:`create_shared_view_dispatcher`"""
120
+ """Ensure the future of a dispatcher made by :func:`create_shared_view_dispatcher`
121
+
122
+ This returns a future that can be awaited to block until all dispatch tasks have
123
+ completed as well as the dispatcher coroutine itself which is used to start dispatch
124
+ tasks.
125
+
126
+ This is required in situations where usage of the async context manager from
127
+ :func:`create_shared_view_dispatcher` is not possible. Typically this happens when
128
+ integrating IDOM with other frameworks, servers, or applications.
129
+ """
108
130
dispatcher_future : Future [SharedViewDispatcher ] = Future ()
109
131
110
132
async def dispatch_shared_view_forever () -> None :
111
133
with layout :
112
134
(
113
135
dispatch_shared_view ,
114
- model_state ,
115
- all_patch_queues ,
116
- ) = await _make_shared_view_dispatcher (layout )
136
+ send_patch ,
137
+ ) = await _create_shared_view_dispatcher (layout )
117
138
118
139
dispatcher_future .set_result (dispatch_shared_view )
119
140
120
141
while True :
121
- patch = await render_json_patch (layout )
122
- model_state .current = patch .apply_to (model_state .current )
123
- # push updates to all dispatcher callbacks
124
- for queue in all_patch_queues :
125
- queue .put_nowait (patch )
142
+ send_patch (await render_json_patch (layout ))
126
143
127
144
async def dispatch (send : SendCoroutine , recv : RecvCoroutine ) -> None :
128
145
await (await dispatcher_future )(send , recv )
@@ -159,28 +176,37 @@ def create_from(cls, update: LayoutUpdate) -> VdomJsonPatch:
159
176
return cls (update .path , make_patch (update .old or {}, update .new ).patch )
160
177
161
178
162
- async def _make_shared_view_dispatcher (
179
+ async def _create_shared_view_dispatcher (
163
180
layout : LayoutType [LayoutUpdate , LayoutEvent ],
164
- ) -> Tuple [SharedViewDispatcher , Ref [ Any ], WeakSet [ Queue [ VdomJsonPatch ]]]:
181
+ ) -> Tuple [SharedViewDispatcher , Callable [[ VdomJsonPatch ], None ]]:
165
182
update = await layout .render ()
166
183
model_state = Ref (update .new )
167
184
168
185
# We push updates to queues instead of pushing directly to send() callbacks in
169
- # order to isolate the render loop from any errors dispatch callbacks might
170
- # raise.
186
+ # order to isolate send_patch() from any errors send() callbacks might raise.
171
187
all_patch_queues : WeakSet [Queue [VdomJsonPatch ]] = WeakSet ()
172
188
173
189
async def dispatch_shared_view (send : SendCoroutine , recv : RecvCoroutine ) -> None :
174
190
patch_queue : Queue [VdomJsonPatch ] = Queue ()
175
- async with create_task_group () as inner_task_group :
176
- all_patch_queues .add (patch_queue )
177
- effective_update = LayoutUpdate ("" , None , model_state .current )
178
- await send (VdomJsonPatch .create_from (effective_update ))
179
- inner_task_group .start_soon (_single_incoming_loop , layout , recv )
180
- inner_task_group .start_soon (_shared_outgoing_loop , send , patch_queue )
191
+ try :
192
+ async with create_task_group () as inner_task_group :
193
+ all_patch_queues .add (patch_queue )
194
+ effective_update = LayoutUpdate ("" , None , model_state .current )
195
+ await send (VdomJsonPatch .create_from (effective_update ))
196
+ inner_task_group .start_soon (_single_incoming_loop , layout , recv )
197
+ inner_task_group .start_soon (_shared_outgoing_loop , send , patch_queue )
198
+ except Stop :
199
+ logger .info ("Stopped dispatch task" )
200
+ finally :
201
+ all_patch_queues .remove (patch_queue )
181
202
return None
182
203
183
- return dispatch_shared_view , model_state , all_patch_queues
204
+ def send_patch (patch : VdomJsonPatch ) -> None :
205
+ model_state .current = patch .apply_to (model_state .current )
206
+ for queue in all_patch_queues :
207
+ queue .put_nowait (patch )
208
+
209
+ return dispatch_shared_view , send_patch
184
210
185
211
186
212
async def _single_outgoing_loop (
0 commit comments