Существует два основных подхода к организации логики программы: опрос в суперцикле (superloop polling) и событийно-ориентированный подход (event-based, event-driven).
Polling #
Опрос - это когда мы вручную опрашиваем в суперцикле какие-то функции или методы объектов и ожидаем от них результат. Например - опрос кнопки или доступность Serial
для чтения:
void loop() {
if (buttonClick()) {
// обработка клика по кнопке
}
if (Serial.available()) {
// чтение данных
}
}
Это самый простой вариант, у него очень компактная реализация - нет лишнего кода и лишних действий в программе. В то же время он визуально занимает много места - если писать всю программу в loop
таким образом - она превратится в нечитаемое полотно из опросов и реакций на их результат.
Функция-обработчик #
Одно из решений - вынести всю логику обработки в отдельную функцию, которую потом можно будет перенести в отдельный файл для удобства:
void processClick() {
// обработка клика по кнопке
}
void loop() {
if (buttonClick()) processClick();
}
Получилась функция для обработки события (event) - клика по кнопке, функция будет вызвана, когда произойдёт событие. Такая функция называется обработчиком.
Callback #
Здесь мы всё ещё вручную опрашиваем кнопку и вручную вызываем обработчик, но во многих библиотеках предусмотрено подключение обработчика: можно указать, какую функцию нужно вызвать при наступлении события. Такая функция-обработчик, переданная объекту, называется callback - объект запоминает её к себе в память и вызывает при наступлении события.
Существует также термин event listener - это тоже функция-обработчик события, но событие в данном случае рассылается всем, кто его ожидает. Грубо говоря, callback - это когда начальник попросил подчинённого лично сообщить ему о выполненной работе. А event listener - когда работник сделал работу и написал об этом публично, и все кому это интересно - прочитают данную информацию, "подпишутся на рассылку"
Это может выглядеть примерно так (используется выдуманный класс кнопки, но мы напишем его позже в другом уроке):
// создание кнопки
Button btn;
void processClick() {
// обработка клика по кнопке
}
void setup() {
// подключение обработчика
btn.onClick(processClick);
}
void loop() {
// опрос кнопки, может отсутствовать
btn.tick();
}
Тикер #
В примере выше btn.tick()
- некий системный метод, который занимается обработкой кнопки, он должен вызываться в суперцикле как можно чаще - такие штуки часто называют тикерами. Это неблокирующая функция, которая занимается обработкой данных, переключением таймеров и машин состояний внутри себя и обеспечивает работу объекта. Тикера может и не быть - если опрос кнопки реализован на прерываниях (о них - в следующих уроках). Во многих библиотеках используется подобный механизм "тикер + обработчики" - в суперцикл помещается тикер, подключаются обработчики и оно работает.
Это удобно - позволяет разгрузить суперцикл и легко структурировать программу, сделать её более читаемой. Но есть и недостатки - на вызов функции тратится дополнительное время, а также нужно хранить указатель на обработчик в оперативной памяти
Некоторые языки, например JavaScript, изначально событийно-ориентированные - в нём нет суперцикла и не нужно его делать: всё работает на событиях и обработчиках. Но "под капотом" цикл всё же имеется - в нём точно так же проверяются события и вызываются подключенные обработчики, просто это полностью скрыто от программиста.