openlang

Сигналы срабатывают сразу, а не в конце кадра

Меня это укусило в Godot, и, похоже, рано или поздно кусает всех. emit_signal() и signal.emit() вызывают подключённые функции сразу, внутри самого вызова, ещё до следующей строки отправителя. Это вызов функции с лишними шагами, а не сообщение, которое ждёт конца кадра.

Поэтому если обработчик health_changed убирает узел из дерева или меняет массив, по которому отправитель идёт в цикле, это происходит посреди логики отправителя. Чтобы обработчик выполнился позже, подключайте его с CONNECT_DEFERRED, тогда вызов встанет в очередь на время простоя.

Вторая половина вопроса в том, где живёт покадровая логика. _process(delta) выполняется раз за отрисованный кадр с плавающим delta. _physics_process(delta) идёт с фиксированной частотой физических тиков из настроек проекта. Если двигать CharacterBody2D в _process, в основном всё работает, а при другой частоте кадров ведёт себя иначе, потому что движение и шаг физики больше не идут в ногу.

Сейчас правило такое. Движение, связанное со столкновениями, в _physics_process, визуальное в _process, а отложенные подключения там, где обработчик может поменять дерево сцены. Честно, неясно, стоит ли делать последнее поведением по умолчанию. Если откладывать всё подряд, порядок событий становится труднее отследить.

Разговор продолжается 2

Следуя доводам
  1. Ссылка на ответ

    Задержимся на этой передаче управления, потому что await добавляет вторую. Функция, дошедшая до await some_signal, на этой строке возвращается к вызывающему, а остаток выполняется позже, когда сигнал отправят, прямо внутри этого вызова emit. Та же форма, что и возобновление корутины Lua из обратного вызова. Кто возобновит её дальше? Тот, кто отправит сигнал, в каком бы месте кадра это ни случилось.

  2. Ссылка на ответ

    Это коварнее самого сигнала. Перезарядка с await $Timer.timeout продолжается изнутри emit самого таймера, на том шаге обработки, где этот таймер тикает. Когда нужна предсказуемая точка, после этого ставлю await get_tree().process_frame, и выполнение возобновится прямо перед тем, как узлы получат свои вызовы _process.

Другие стороны того же вопроса

  1. Кто владеет буфером после отправки команд?

    C++ RAII-обёртка над буфером Vulkan записывает работу с этим буфером, отправляет её и выходит из области видимости. Деструкто…

    Ответы 3cppcodin