live13 (live13) wrote,
live13
live13

Categories:

Шаблоны игрового программирования. Очередь событий(2-я часть главы)

Очередь событий (Event Queue)



Первая часть главы
Оглавление


Архитектурные решения



Многие игры используют очередь событий в качестве ключевой части их коммуникационной структуры и вы также можете потратить кучу времени на проектирование различных типов сложных маршрутов и фильтрации сообщений. Но прежде чем вы решитесь построить нечто столь же громадное как телефонный коммутатор Лос Анджелеса. Рекомендую начать с простого. Вот несколько вопросов, над которыми нужно подумать:

Что происходит в очереди?



Я использовал до сих пор слова "событие" и "сообщение" как взаимозаменяемые, потому что разница практически не имела значения. На снижение связности и агрегацию это не влияет.


  • Если вы используете очередь событий:

    "Событие" или "уведомление" описывает нечто что уже случилось в духе "монстр помер". Вы помещаете их в очередь так что объекты смогут реагировать на события, примерно как в асинхронном шаблоне Наблюдатель( Observer)GoF.


    • Вы можете позволить себе несколько слушателей. Так как очередь содержит вещи, которые уже случились, отправителя мало беспокоит кто их получает. С этой точки зрения, события находятся в прошлом и уже забыты.

    • Область видимости очереди стремится к расширению. Очередь событий часто используется для широкополосного вещания (broadcast ) событий во все заинтересованные стороны. Чтобы обеспечить максимальную гибкость для всех заинтересованных частей, такую очередь стоит сделать глобальной.



  • Если вы используете очередь сообщений:

    "Сообщение" или "запрос" описывают действие, которое мы хотим чтобы случилось в будущем. Например, "проиграть звук". Вы можете думать об этом как об асинхронном API для сервиса.

      Еще одно название для "запроса" - это "команда", как в шаблоне Команда( Command)GoF и с ним тоже можно использовать очередь.



    • У вас скорее всего будет всего один слушатель. В нашем примере, помещенные в очередь сообщения - это запросы специально для аудио API для проигрывания звука. Если другие случайные части игрового движка начнут похищать сообщения из очереди, ни к чему хорошему это не приведет.

      Я говорю "скорее всего" потому что вы можете добавлять сообщения не беспокоясь о том какой код его обрабатывает до тех пор, пока он будет обрабатываться так как вы ожидали. В таком случае вы делаете нечто в духе Поиска сервиса (Service Locator).





Кто может читать из очереди?



В нашем примере очередь инкапсулируется и только Audio класс может из нее читать. В системе событий пользовательского интерфейса, вы можете регистрировать любых слушателей каких только ваша душа пожелает. Вы наверняка слышали два разных термина: "направленное вещание" и "широкополосное вещание". Полезны оба стиля.


  • Очередь с направленным вещанием:

    Такой подход естественен когда очередь является частью API класса. Как в нашем примере с аудио, с точки зрения вызывающего кода, видно только доступный для вызова метод playSound().


    • Очередь становится реализацией деталей читателя. Все что может отправитель - так это просто отправлять сообщения.

    • Очередь сильнее инкапсулирована. При прочих равных условиях, сильная инкапсуляция всегда лучше.

    • Вам не нужно беспокоиться о конкуренции между слушателями. Когда у вас есть несколько слушателей, вам нужно решить, будут ли они все получать каждый элемент (широкополосное вещание) или каждый элемент в очереди предназначен строго для конкретного слушателя (больше похоже на живую рабочую очередь).

      В любом случае, слушатели могут выполнять лишнюю работу или мешать друг другу и вам нужно тщательно продумывать какое у них должно быть поведение. Когда у вас всего один слушатель, никаких подобных сложностей не возникает.



  • Очередь с широкополосным вещанием:

    Именно так работает большинство систем "событий". Если у вас есть десять слушателей когда происходит событие, его увидят все десять слушателей.


    • События могут упасть на пол. Из предыдущего вывода следует что если у вас ноль слушателей, событие получат все ноль. В большинстве широкополосных систем, если по время обработки события у нас нет никаких слушателей, события просто пропадают.

    • Вам может понадобиться фильтровать события. Широкополосные очереди обычно видны большей части программы и в результате у вас получится куча слушателей. Перемножая множество событий на множество слушателей, мы получим огромное количество обработчиков событий.

      Чтобы снизить их количество, большинство систем широкополосного вещания позволяют слушателям ограничивать набор принимаемых событий. Например, они могут объявить, что принимают события мыши или события в определенной части пользовательского интерфейса.



  • Рабочая очередь:

    Как и в случае с широкополосной очередью, здесь у вас тоже будет множество слушателей. Разница в том что теперь каждое событие из очереди отправляется только одному слушателю. Этот шаблон обычно распределяет работу между пулом конкурентно работающих потоков.

    • Вам потребуется планировщик. Так как элементы передаются только одному слушателю, очереди нужна логика для определения наилучшего кандидата. Это может быть очевидное решение, случайный выбор или более сложная система приоритетов.





Кто может писать в очередь?



Это обратная сторона предыдущего архитектурного решения. Этот шаблон работает со всеми конфигурациями чтения/записи: один к одному, один к многим, многие к одному или многие к многим.

    Возможно вы слышали название " fan-in " для описания системы коммуникации многие к одному и " fan-out " для системы один ко многим.



  • Когда писатель один:

    Этот стиль больше всего похож на синхронный шаблон Наблюдатель(Observer ). Вам нужен один привилегированный объект, генерирующий события, которые будут получать другие.


    • Вы косвенно знаете откуда поступают события. Так как пополнять очередь может только один объект, все слушатели могут быть уверены что именно он является отправителем.

    • Обычно вы разрешаете существовать множеству читателей. Вы конечно можете создать очередь с одним отправителем и одним получателем, но тогда шаблон уже не будет похож сам на себя и превратится в подобие обычной структуры данных очередь.



  • Когда писателей много:

    Именно так работает пример с аудио. Так как playSound() публичный метод, любая часть кодовой базы может добавлять в очередь запросы. Именно так работает "глобальная" или "центральная" система событий.


    • Вам нужно быть осторожнее с циклами. Так как потенциально помещать запросы в очередь может кто угодно, очень просто отправить какой-то запрос прямо в середине обработчика события. И если вы не будете достаточно осторожны, то рискуете получить цикл обратных вызовов.

    • Вам может понадобиться ссылка на отправителя в самом событии. Когда слушатель получает событие, он не знает кто его послал, потому что это может быть кто угодно. И если вам нужно это знать, вам нужно добавить такую информацию в событие чтобы слушатель мог ею воспользоваться.





Какова продолжительность жизни событий в очереди?



В системах синхронных уведомлений, выполнение не возвращается обратно отправителю, пока получатель не закончит обработку сообщения. Это значит что сообщение может безопасно размещаться в локальной переменной или в стеке. Когда у нас появляется очередь, сообщение живет дольше чем породивший его вызов.

Если вы используете язык со сборщиком мусора, вам не нужно слишком об этом беспокоиться. Вы помещаете сообщение в очередь и оно будет там находиться до тех пор пока не перестанет быть нужным. В C или C++ вы сами должны позаботиться о том чтобы объект прожил достаточно долго.


  • Передача владения:

    Это традиционный способ работы при ручном управлении памятью. Когда сообщение попадает в очередь, очередь забирает его себе и отправитель им больше не владеет. После того как оно отправляется на обработку, его забирает себе получатель и теперь он отвечает за удаление сообщения.

      В C++ именно такую семантику предоставляет нам unique_ptr прямо из коробки.

  • Совместное владение:

    В наши дни, когда даже C++ программисты работают с достаточным комфортном со сборщиком мусора, все чаще применяется совместное владение. При этом сообщение остается живым пока на него хотя бы кто-то ссылается и автоматически удаляется, когда его все забыли.
      Анаголично в C++ для этого предусмотрен shared_ptr.


  • Принадлежность очереди:

    Еще одним вариантом является разрешение событию жить в очереди всегда. Вместо выделения памяти для события самостоятельно, отправитель запрашивает "свежее" из очереди. очередь возвращает ссылку на сообщение, которое уже выделено и находится в очереди, а отправитель его заполняет. Когда сообщение поступает на обработку, получатель обращается к тому же сообщению из очереди.

      Другим словами получается резервное хранилище для очереди как в Пуле объектов ( Object Pool).



Смотрите также




  • Я уже упоминал об этом несколько раз, но во многом этот шаблон является асинхронным родственником хорошо знакомого нам шаблона Наблюдатель( Observer)GoF.

  • Как и многие другие шаблоны, очередь событий имеет множество псевдонимов. Один из таких устоявшихся терминов - это "очередь событий". Он обычно ссылается на проявление более высокого уровня. Очередь событий обычно используется для общения внутри приложения, а очередь сообщений для общения между приложениями.

    Еще один термин "публикация/подписка(publish/subscribe)", часто сокращаемый до " pubsub". Как и очередь сообщений он обычно применяется для описания распределенных систем и в меньшей степени для скромных шаблонов программирования, которыми мы здесь занимаемся.

  • Конечный автомат, подобно шаблону Состояние(State)GoF от банды четырех требует потока ввода. Если вы хотите реагировать на него асинхронно, вам поможет применение очереди.

    Если у вас есть множество конечных автоматов, обменивающихся сообщениями, у каждого из которых есть небольшая очередь поступающих сигналов(называемая почтовым ящиком (mailbox)), значит вы переизобретаете актерскую модель общения.

  • В языке Go встроенный тип "канал" представляет из себя очередь сообщений или событий.

Tags: c++, design patterns, game programming, programming, игры, книги, перевод, программирование, шаблоны, шаблоны проектирования
Subscribe

  • Батя Русс

    Читаю пропущенные сборники и отдельные рассказы по Ереси прежде чем двигаться дальше по основному циклу. А еще посмотрел фильм Батя. И прямо…

  • Проклятый и душный Писос

    Ох и тягучая книга. Спейсмарины против динозавриков - это совсем не так весело как кажется. Косматые давиниты-культисты с плоскими звериными…

  • Сильно мстительный дух

    Эта книга МакНила занимает довольно важное место в серии. На самом деле это 1.5 книжки в одной. Основная часть - это история вторжения Хоруса на…

promo live13 may 11, 2014 17:58 46
Buy for 50 tokens
Примерно неделю назад я писал, что заинтересовался этой online-книжкой http://gameprogrammingpatterns.com/ и решил сделать ее перевод. Сам я мог бы ограничиться и английским вариантом, но думаю многим перевод пригодится. В прошлом я уже занимался переводом книг. Не как основной работой. Так,…
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 2 comments