Skip to content

Commit 0e5b020

Browse files
committed
pybricks.tools: Include stop option when making object.
Almost everywhere we use pb_type_async_wait_or_await we stop the ongoing iteration first, so include that functionality as a bool. It is still also available separately for methods that want to stop ongoing awaitables without spawning another one. Also rename pb_type_async_schedule_cancel to pb_type_async_schedule_stop_iteration since cancel isn't quite the right word and this makes it more explicit what it does.
1 parent 9522809 commit 0e5b020

File tree

10 files changed

+47
-38
lines changed

10 files changed

+47
-38
lines changed

pybricks/common/pb_type_device.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ mp_obj_t pb_type_device_method_call(mp_obj_t self_in, size_t n_args, size_t n_kw
9797
.parent_obj = sensor_in,
9898
.return_map = method->get_values,
9999
};
100-
return pb_type_async_wait_or_await(&config, &sensor->last_awaitable);
100+
return pb_type_async_wait_or_await(&config, &sensor->last_awaitable, false);
101101
}
102102

103103
/**
@@ -126,7 +126,7 @@ mp_obj_t pb_type_device_set_data(pb_type_device_obj_base_t *sensor, uint8_t mode
126126
.iter_once = pb_pup_device_iter_once,
127127
.parent_obj = MP_OBJ_FROM_PTR(sensor),
128128
};
129-
return pb_type_async_wait_or_await(&config, &sensor->last_awaitable);
129+
return pb_type_async_wait_or_await(&config, &sensor->last_awaitable, false);
130130
}
131131

132132
void pb_device_set_lego_mode(pbio_port_t *port) {

pybricks/common/pb_type_lightmatrix.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,7 @@ static mp_obj_t common_LightMatrix_text(size_t n_args, const mp_obj_t *pos_args,
337337
.iter_once = pb_type_lightmatrix_text_iterate_once,
338338
};
339339
// New operation always wins; ongoing animation is cancelled.
340-
pb_type_async_schedule_cancel(self->text_iter);
341-
return pb_type_async_wait_or_await(&config, &self->text_iter);
340+
return pb_type_async_wait_or_await(&config, &self->text_iter, true);
342341
}
343342
static MP_DEFINE_CONST_FUN_OBJ_KW(common_LightMatrix_text_obj, 1, common_LightMatrix_text);
344343

pybricks/common/pb_type_motor.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ static mp_obj_t pb_type_Motor_reset_angle(size_t n_args, const mp_obj_t *pos_arg
243243

244244
// Set the new angle
245245
pb_assert(pbio_servo_reset_angle(self->srv, reset_angle, reset_to_abs));
246-
pb_type_async_schedule_cancel(self->last_awaitable);
246+
pb_type_async_schedule_stop_iteration(self->last_awaitable);
247247
return mp_const_none;
248248
}
249249
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Motor_reset_angle_obj, 1, pb_type_Motor_reset_angle);
@@ -268,7 +268,7 @@ static mp_obj_t pb_type_Motor_run(size_t n_args, const mp_obj_t *pos_args, mp_ma
268268

269269
mp_int_t speed = pb_obj_get_int(speed_in);
270270
pb_assert(pbio_servo_run_forever(self->srv, speed));
271-
pb_type_async_schedule_cancel(self->last_awaitable);
271+
pb_type_async_schedule_stop_iteration(self->last_awaitable);
272272
return mp_const_none;
273273
}
274274
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Motor_run_obj, 1, pb_type_Motor_run);
@@ -277,7 +277,7 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Motor_run_obj, 1, pb_type_Motor_run);
277277
static mp_obj_t pb_type_Motor_hold(mp_obj_t self_in) {
278278
pb_type_Motor_obj_t *self = MP_OBJ_TO_PTR(self_in);
279279
pb_assert(pbio_servo_stop(self->srv, PBIO_CONTROL_ON_COMPLETION_HOLD));
280-
pb_type_async_schedule_cancel(self->last_awaitable);
280+
pb_type_async_schedule_stop_iteration(self->last_awaitable);
281281
return mp_const_none;
282282
}
283283
static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_Motor_hold_obj, pb_type_Motor_hold);
@@ -313,8 +313,7 @@ static mp_obj_t pb_type_motor_wait_or_await(pb_type_Motor_obj_t *self, bool retu
313313
.return_map = return_final_angle ? pb_type_motor_get_final_angle : NULL,
314314
};
315315
// New operation always wins; ongoing awaitable motor motion is cancelled.
316-
pb_type_async_schedule_cancel(self->last_awaitable);
317-
return pb_type_async_wait_or_await(&config, &self->last_awaitable);
316+
return pb_type_async_wait_or_await(&config, &self->last_awaitable, true);
318317
}
319318

320319
// pybricks.common.Motor.run_time
@@ -429,7 +428,7 @@ static mp_obj_t pb_type_Motor_track_target(size_t n_args, const mp_obj_t *pos_ar
429428

430429
mp_int_t target_angle = pb_obj_get_int(target_angle_in);
431430
pb_assert(pbio_servo_track_target(self->srv, target_angle));
432-
pb_type_async_schedule_cancel(self->last_awaitable);
431+
pb_type_async_schedule_stop_iteration(self->last_awaitable);
433432
return mp_const_none;
434433
}
435434
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Motor_track_target_obj, 1, pb_type_Motor_track_target);

pybricks/common/pb_type_speaker.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ static mp_obj_t pb_type_Speaker_beep(size_t n_args, const mp_obj_t *pos_args, mp
113113
.close = pb_type_Speaker_close,
114114
};
115115
// New operation always wins; ongoing sound awaitable is cancelled.
116-
pb_type_async_schedule_cancel(self->iter);
117-
return pb_type_async_wait_or_await(&config, &self->iter);
116+
return pb_type_async_wait_or_await(&config, &self->iter, true);
118117

119118
}
120119
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Speaker_beep_obj, 1, pb_type_Speaker_beep);
@@ -325,8 +324,7 @@ static mp_obj_t pb_type_Speaker_play_notes(size_t n_args, const mp_obj_t *pos_ar
325324
.close = pb_type_Speaker_close,
326325
};
327326
// New operation always wins; ongoing sound awaitable is cancelled.
328-
pb_type_async_schedule_cancel(self->iter);
329-
return pb_type_async_wait_or_await(&config, &self->iter);
327+
return pb_type_async_wait_or_await(&config, &self->iter, true);
330328
}
331329
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Speaker_play_notes_obj, 1, pb_type_Speaker_play_notes);
332330

pybricks/iodevices/pb_type_i2c_device.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,7 @@ mp_obj_t pb_type_i2c_device_start_operation(mp_obj_t i2c_device_obj, const uint8
188188
.return_map = return_map ? pb_type_i2c_device_return_generic : NULL,
189189
};
190190
// New operation always wins; ongoing sound awaitable is cancelled.
191-
pb_type_async_schedule_cancel(device->iter);
192-
return pb_type_async_wait_or_await(&config, &device->iter);
191+
return pb_type_async_wait_or_await(&config, &device->iter, true);
193192
}
194193

195194
/**

pybricks/iodevices/pb_type_uart_device.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ static mp_obj_t pb_type_uart_device_write(size_t n_args, const mp_obj_t *pos_arg
103103
.parent_obj = MP_OBJ_FROM_PTR(self),
104104
.return_map = pb_type_uart_device_write_return_map,
105105
};
106-
pb_type_async_schedule_cancel(self->write_iter);
107-
return pb_type_async_wait_or_await(&config, &self->write_iter);
106+
return pb_type_async_wait_or_await(&config, &self->write_iter, true);
108107
}
109108
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_uart_device_write_obj, 1, pb_type_uart_device_write);
110109

@@ -146,8 +145,7 @@ static mp_obj_t pb_type_uart_device_read(size_t n_args, const mp_obj_t *pos_args
146145
.parent_obj = MP_OBJ_FROM_PTR(self),
147146
.return_map = pb_type_uart_device_read_return_map,
148147
};
149-
pb_type_async_schedule_cancel(self->read_iter);
150-
return pb_type_async_wait_or_await(&config, &self->read_iter);
148+
return pb_type_async_wait_or_await(&config, &self->read_iter, true);
151149
}
152150
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_uart_device_read_obj, 1, pb_type_uart_device_read);
153151

pybricks/robotics/pb_type_drivebase.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ static mp_obj_t pb_type_DriveBase_stop(mp_obj_t self_in) {
100100

101101
// Cancel awaitables.
102102
pb_type_DriveBase_obj_t *self = MP_OBJ_TO_PTR(self_in);
103-
pb_type_async_schedule_cancel(self->last_awaitable);
103+
pb_type_async_schedule_stop_iteration(self->last_awaitable);
104104

105105
// Stop hardware.
106106
pb_assert(pbio_drivebase_stop(self->db, PBIO_CONTROL_ON_COMPLETION_COAST));
@@ -119,8 +119,7 @@ static mp_obj_t await_or_wait(pb_type_DriveBase_obj_t *self) {
119119
.close = pb_type_DriveBase_stop,
120120
};
121121
// New operation always wins; ongoing awaitable motion is cancelled.
122-
pb_type_async_schedule_cancel(self->last_awaitable);
123-
return pb_type_async_wait_or_await(&config, &self->last_awaitable);
122+
return pb_type_async_wait_or_await(&config, &self->last_awaitable, true);
124123
}
125124

126125
// pybricks.robotics.DriveBase.straight
@@ -237,7 +236,7 @@ static mp_obj_t pb_type_DriveBase_drive(size_t n_args, const mp_obj_t *pos_args,
237236
mp_int_t turn_rate = pb_obj_get_int(turn_rate_in);
238237

239238
// Cancel awaitables but not hardware. Drive forever will handle this.
240-
pb_type_async_schedule_cancel(self->last_awaitable);
239+
pb_type_async_schedule_stop_iteration(self->last_awaitable);
241240

242241
pb_assert(pbio_drivebase_drive_forever(self->db, speed, turn_rate));
243242
return mp_const_none;
@@ -249,7 +248,7 @@ static mp_obj_t pb_type_DriveBase_brake(mp_obj_t self_in) {
249248

250249
// Cancel awaitables.
251250
pb_type_DriveBase_obj_t *self = MP_OBJ_TO_PTR(self_in);
252-
pb_type_async_schedule_cancel(self->last_awaitable);
251+
pb_type_async_schedule_stop_iteration(self->last_awaitable);
253252

254253
// Stop hardware.
255254
pb_assert(pbio_drivebase_stop(self->db, PBIO_CONTROL_ON_COMPLETION_BRAKE));

pybricks/tools/pb_module_tools.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ static mp_obj_t pb_module_tools_wait(size_t n_args, const mp_obj_t *pos_args, mp
9696
.state = pbdrv_clock_get_ms() + (uint32_t)time,
9797
};
9898

99-
return pb_type_async_wait_or_await(&config, &reuse);
99+
return pb_type_async_wait_or_await(&config, &reuse, false);
100100
}
101101
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_module_tools_wait_obj, 0, pb_module_tools_wait);
102102

@@ -158,7 +158,7 @@ mp_obj_t pb_module_tools_pbio_task_wait_or_await(pbio_task_t *task) {
158158

159159
// REVISIT: pbio tasks will be deprecated. Instead, protothreads can now
160160
// be safely awaited.
161-
return pb_type_async_wait_or_await(&config, NULL);
161+
return pb_type_async_wait_or_await(&config, NULL, false);
162162
}
163163

164164
/**

pybricks/tools/pb_type_async.c

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,19 @@
1212
#include <pybricks/util_pb/pb_error.h>
1313

1414
/**
15-
* Cancels the iterable so it will stop awaiting.
15+
* Makes the iterable exhaust the next time it is iterated.
1616
*
1717
* This will not call close(). Safe to call even if iter is NULL or if it is
1818
* already complete.
1919
*
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+
*
2025
* @param [in] iter The awaitable object.
2126
*/
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) {
2328
if (!iter || iter->parent_obj == MP_OBJ_NULL) {
2429
// Don't schedule if already complete.
2530
return;
@@ -58,7 +63,7 @@ static mp_obj_t pb_type_async_iternext(mp_obj_t iter_in) {
5863

5964
// Special case without iterator means yield exactly once and then complete.
6065
if (!iter->iter_once) {
61-
pb_type_async_schedule_cancel(iter);
66+
pb_type_async_schedule_stop_iteration(iter);
6267
return mp_const_none;
6368
}
6469

@@ -98,22 +103,35 @@ MP_DEFINE_CONST_OBJ_TYPE(pb_type_async,
98103
* Returns an awaitable operation if the runloop is active, or awaits the
99104
* operation here and now.
100105
*
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.
103109
* @returns An awaitable if the runloop is active, otherwise the mapped return value.
104110
*/
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) {
106112

107113
config->base.type = &pb_type_async;
108114

109115
// Return allocated awaitable if runloop active.
110116
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+
111123
// Re-use existing awaitable if exists and is free, otherwise allocate
112124
// another one. This allows many resources with one concurrent physical
113125
// operation like a motor to operate without re-allocation.
114126
pb_type_async_t *iter = (prev && *prev && (*prev)->parent_obj == MP_OBJ_NULL) ?
115127
*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.
116130
*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.
117135
if (prev) {
118136
*prev = iter;
119137
}

pybricks/tools/pb_type_async.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ typedef mp_obj_t (*pb_type_async_return_map_t)(mp_obj_t parent_obj);
4040
*/
4141
typedef pbio_error_t (*pb_type_async_iterate_once_t)(pbio_os_state_t *state, mp_obj_t parent_obj);
4242

43-
// Object representing the iterable that is returned by an awaitable operation.
43+
/** Object representing the iterable that is returned by an awaitable operation. */
4444
typedef struct {
4545
mp_obj_base_t base;
4646
/**
@@ -49,8 +49,7 @@ typedef struct {
4949
*
5050
* Special values:
5151
* MP_OBJ_NULL: This iterable has been fully exhausted and can be reused.
52-
* MP_OBJ_SENTINEL: This iterable is cancelled and will exhaust
53-
* when it is iterated again.
52+
* MP_OBJ_SENTINEL: This iterable is will raise StopIteration when it is iterated again.
5453
*/
5554
mp_obj_t parent_obj;
5655
/**
@@ -74,8 +73,8 @@ typedef struct {
7473
pbio_os_state_t state;
7574
} pb_type_async_t;
7675

77-
mp_obj_t pb_type_async_wait_or_await(pb_type_async_t *config, pb_type_async_t **prev);
76+
mp_obj_t pb_type_async_wait_or_await(pb_type_async_t *config, pb_type_async_t **prev, bool stop_prev);
7877

79-
void pb_type_async_schedule_cancel(pb_type_async_t *iter);
78+
void pb_type_async_schedule_stop_iteration(pb_type_async_t *iter);
8079

8180
#endif // PYBRICKS_INCLUDED_ASYNC_H

0 commit comments

Comments
 (0)