Есть ли лучший способ настроить систему событий?

9

Системы событий удивительны, они делают чрезвычайно громоздкий код ручным и действительно позволяют динамически создавать игры с помощью простого взаимодействия объектов и игрового цикла. Я испытываю трудности с эффективностью моей текущей реализации. В настоящее время моя небольшая оптимизация разделения списков объектов на события, на которые они отвечают, сделала чудеса, но я должен сделать больше.

В настоящее время у меня есть два метода:

  1. Проще всего : все объекты добавляются в вектор при отправке события, все объекты отправляются событием через метод handle_event ()

  2. Более сложный: у меня есть карта со строкой в ​​качестве ключа и int в качестве значения. Когда тип события добавляется, он добавляется к этой карте, при этом int просто увеличивается (должен быть лучший способ),
    чтобы вектор векторов объектов затем возвращал новый вектор для обработки этого типа события.
    Когда событие вызывается, оно просто вызывает соответствующее int в сопоставлении eventTypes с типом внутри вектора векторов объектов и отправляет это событие каждому объекту, обрабатывающему этот тип события.

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

Есть ли более быстрый (мудрый способ) способ? Есть ли более быстрый способ найти int из строкового типа? (Изначально у меня был enum, но он не допускал пользовательских типов, которые необходимы из-за желаемого уровня динамизма.)

ultifinitus
источник
1
Это то, для чего предназначены Hash Maps (или Hash Table), строка вычисляется до хеш-числа, которое затем используется для поиска непосредственно в массиве. en.wikipedia.org/wiki/Hash_table
Патрик Хьюз
Хороший вопрос: действительно ли это узкое место в производительности, или вы просто преждевременно переживаете?
Яри ​​Комппа
Это уже реализовано, и существует узкое место, когда используется много объектов. Мой предыдущий комментарий об отсутствии узкого места был не в самом приложении, а на самом деле разница в скорости между двумя реализациями выше, где фактически обрабатывалось одинаковое количество объектов. Я надеялся на другие методы создания систем событий ... Однако некоторые идеи в этом потоке немного увеличат скорость, особенно в загрузке системы событий (время загрузки 11-25 полных секунд с 100000 объектов)
ultifinitus
100K объектов звучит ужасно высоко для игры. Это сервер или клиентское приложение для конечного пользователя?
Патрик Хьюз
Это мой двигатель, поэтому я действительно стремлюсь к гибкости. Он будет использоваться в серверных приложениях и приложениях для конечных пользователей (реже - первый, оптимизация)
ultifinitus

Ответы:

5

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

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

Натан Рид
источник
Узкое место не велико (для 100 000 объектов оно теряет .0000076 мс / объект), но я думаю, что ваша идея - отличная идея! Я думаю, что на самом деле я сделаю однократный поиск идентификатора, и eventID будет храниться как int, а не как исходные строковые данные. И я на самом деле не думал о связанных списках, хорошая идея.
ультифинит
1
+1 За предварительную обработку идентификаторов. Вы также можете сделать это лениво, имея тип EventType, который каждый модуль может получить через что-то вроде EventType takeDamageEvent = EventSystem::CacheEventType("takeDamageEvent");. Сделайте его статическим членом классов, и у вас будет только одна копия в каждом классе, который в этом нуждается.
Майкл Бартнетт
2
Обратите внимание, что хранение идентификаторов вместо строк затруднит отладку. Всегда полезно видеть текст, указывающий, откуда пришел объект. Вы можете пойти наполовину, сохранив и идентификатор, и строку, которую вы храните в целях отладки (возможно, даже удаляется в сборках релиза).
Никол Болас
2

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

Время поиска в a mapосновано на двух вещах: количестве элементов на карте и времени, которое требуется для сравнения двух ключей (игнорируя проблемы с кэшем). Если время поиска является проблемой, одним из способов его обработки является изменение функции сравнения путем изменения типа строки.

Давайте предположим, что вы используете std::stringи operator<для сравнения. Это крайне неэффективно; это делает побайтовое сравнение. Вы не заботитесь о реальной строке меньше, чем сравнение; вам просто нужно какое-то сравнение, которое дает строго-слабый порядок (потому mapчто иначе не работает).

Поэтому вы должны использовать 32-байтовую строку фиксированной длины вместо std::string. Я использую их для идентификаторов. Тесты сравнения для этих фиксированных строк не выполняют побайтных сравнений; вместо этого они выполняют 32-битные (или 64-битные) сравнения. Он принимает каждые 4 байта как целое число без знака и сравнивает его с соответствующими 4 байтами другой строки. Таким образом, сравнение занимает максимум 8 сравнений. Это обеспечивает строго-слабое упорядочение, хотя упорядочение не имеет ничего общего с данными в виде символов.

При хранении строк длиннее 31 байта (требуется символ NULL) строка усекается (но не с конца, а с середины. Я считаю, что энтропия имеет тенденцию быть наибольшей в начале и в конце). И строки, которые короче этого, дополняют оставшиеся символы \0.

Теперь вы можете mapполностью отказаться от использования хеш-таблицы. Если у вас действительно есть более 100 000 различных типов событий, это может быть хорошей идеей. Но я не знаю игры, где это было бы разумно.

Николь Болас
источник
Поэтому вы должны использовать 32-байтовую строку фиксированной длины вместо std :: string. Фантастический! Я не думал менять тип строки.
ультифинит
0

Чтобы ответить на общий вопрос:

Лучший способ настроить систему событий

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

Я реализовал и попытался обобщить тонны систем событий и пришел к такому выводу. Поскольку tradoff действительно разные, если вы делаете это во время компиляции или во время выполнения, если вы используете иерархии объектов или классные доски и т. Д.

Лучший способ - изучить различные виды систем событий, и тогда вы поймете, какую из них использовать в каком случае.

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

Klaim
источник