|
12 | 12 | #include <pybricks/util_pb/pb_error.h>
|
13 | 13 |
|
14 | 14 | /**
|
15 |
| - * Cancels the iterable so it will stop awaiting. |
| 15 | + * Makes the iterable exhaust the next time it is iterated. |
16 | 16 | *
|
17 | 17 | * This will not call close(). Safe to call even if iter is NULL or if it is
|
18 | 18 | * already complete.
|
19 | 19 | *
|
| 20 | + * This is useful when all we need is for the ongoing awaitable to stop, with |
| 21 | + * the newly created iterable taking care of the hardware. For example, if the |
| 22 | + * new operation takes over the speaker, the old one only has to stop iterating, |
| 23 | + * not stop the speaker as it would do with close(). |
| 24 | + * |
20 | 25 | * @param [in] iter The awaitable object.
|
21 | 26 | */
|
22 |
| -void pb_type_async_schedule_cancel(pb_type_async_t *iter) { |
| 27 | +void pb_type_async_schedule_stop_iteration(pb_type_async_t *iter) { |
23 | 28 | if (!iter || iter->parent_obj == MP_OBJ_NULL) {
|
24 | 29 | // Don't schedule if already complete.
|
25 | 30 | return;
|
@@ -58,7 +63,7 @@ static mp_obj_t pb_type_async_iternext(mp_obj_t iter_in) {
|
58 | 63 |
|
59 | 64 | // Special case without iterator means yield exactly once and then complete.
|
60 | 65 | if (!iter->iter_once) {
|
61 |
| - pb_type_async_schedule_cancel(iter); |
| 66 | + pb_type_async_schedule_stop_iteration(iter); |
62 | 67 | return mp_const_none;
|
63 | 68 | }
|
64 | 69 |
|
@@ -98,22 +103,35 @@ MP_DEFINE_CONST_OBJ_TYPE(pb_type_async,
|
98 | 103 | * Returns an awaitable operation if the runloop is active, or awaits the
|
99 | 104 | * operation here and now.
|
100 | 105 | *
|
101 |
| - * @param [in] config Configuration of the operation |
102 |
| - * @param [in] prev Candidate iterable object that might be re-used. |
| 106 | + * @param [in] config Configuration of the operation |
| 107 | + * @param [in, out] prev Candidate iterable object that might be re-used, otherwise assigned newly allocated object. |
| 108 | + * @param [in] stop_prev Whether to stop ongoing awaitable if it is active. |
103 | 109 | * @returns An awaitable if the runloop is active, otherwise the mapped return value.
|
104 | 110 | */
|
105 |
| -mp_obj_t pb_type_async_wait_or_await(pb_type_async_t *config, pb_type_async_t **prev) { |
| 111 | +mp_obj_t pb_type_async_wait_or_await(pb_type_async_t *config, pb_type_async_t **prev, bool stop_prev) { |
106 | 112 |
|
107 | 113 | config->base.type = &pb_type_async;
|
108 | 114 |
|
109 | 115 | // Return allocated awaitable if runloop active.
|
110 | 116 | if (pb_module_tools_run_loop_is_active()) {
|
| 117 | + |
| 118 | + // Optionally schedule ongoing awaitable to stop (next time) if busy. |
| 119 | + if (prev && stop_prev) { |
| 120 | + pb_type_async_schedule_stop_iteration(*prev); |
| 121 | + } |
| 122 | + |
111 | 123 | // Re-use existing awaitable if exists and is free, otherwise allocate
|
112 | 124 | // another one. This allows many resources with one concurrent physical
|
113 | 125 | // operation like a motor to operate without re-allocation.
|
114 | 126 | pb_type_async_t *iter = (prev && *prev && (*prev)->parent_obj == MP_OBJ_NULL) ?
|
115 | 127 | *prev : (pb_type_async_t *)m_malloc(sizeof(pb_type_async_t));
|
| 128 | + |
| 129 | + // Copy the confuration to the object on heap so it lives on. |
116 | 130 | *iter = *config;
|
| 131 | + |
| 132 | + // Attaches newly defined awaitable (or no-op if reused) to the parent |
| 133 | + // object. The object that was here before is detached, so we no longer |
| 134 | + // prevent it from being garbage collected. |
117 | 135 | if (prev) {
|
118 | 136 | *prev = iter;
|
119 | 137 | }
|
|
0 commit comments