Как управлять состоянием игры перед лицом EDT?

Я разрабатываю клон игры-стратегии в реальном времени на платформе Java, и у меня есть несколько концептуальных вопросов о том, где разместить и как управлять состоянием игры. В качестве рендеринга игра использует Swing / Java2D. На текущем этапе разработки нет моделирования и искусственного интеллекта, и только пользователь может изменить состояние игры (например, построить / снести здание, добавить-удалить производственные линии, собрать флот и оборудование). Следовательно, манипуляции с игровым состоянием могут выполняться в потоке отправки событий без какого-либо поиска отрисовки. Состояние игры также используется для отображения пользователю различной агрегированной информации.

Однако, поскольку мне нужно ввести моделирование (например, прогресса строительства, изменения населения, движения флота, производственного процесса и т. Д.), Изменение состояния игры в таймере и EDT, несомненно, замедлит рендеринг.

Допустим, операция моделирования / ИИ выполняется каждые 500 мс, и я использую SwingWorker для вычислений длиной около 250 мс. Как я могу гарантировать отсутствие состояния гонки в отношении чтения состояния игры между симуляцией и возможным взаимодействием с пользователем?

Я знаю, что результат моделирования (который представляет собой небольшой объем данных) можно эффективно перенести обратно в EDT с помощью вызова SwingUtilities.invokeLater ().

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

Есть ли относительно правильный подход к устранению этого состояния гонки чтения? Возможно, выполнение полного / частичного клонирования игрового состояния при каждом такте таймера или изменение жизненного пространства игрового состояния из EDT в какой-либо другой поток?

Обновление: (из комментариев, которые я дал) В игре участвуют 13 игроков, управляемых ИИ, 1 игрок-человек и около 10000 игровых объектов (планеты, здания, оборудование, исследования и т. д.). Например, игровой объект имеет следующие атрибуты:

World (Planets, Players, Fleets, ...)
Planet (location, owner, population, type, 
    map, buildings, taxation, allocation, ...)
Building (location, enabled, energy, worker, health, ...)

В сценарии пользователь строит новое здание на этой планете. Это выполняется в EDT, так как карту и коллекцию зданий необходимо изменить. Параллельно с этим каждые 500 мс запускается моделирование для вычисления распределения энергии зданиям на всех игровых планетах, которое необходимо для прохождения коллекции зданий для сбора статистики. Если распределение вычислено, оно передается в EDT, и ему присваивается энергетическое поле каждого здания.

Этим свойством обладают только взаимодействия с игроком-людьми, потому что результаты вычислений ИИ в любом случае применяются к структурам в EDT.

Как правило, 75% атрибутов объекта статичны и используются только для визуализации. Остальное можно изменить либо посредством взаимодействия с пользователем, либо путем моделирования / искусственного интеллекта. Также гарантируется, что никакой новый этап моделирования / AI не запускается, пока предыдущий не записал все изменения.

Мои цели:

  • Избегайте откладывания взаимодействия с пользователем, например пользователь помещает здание на планету и только через 0,5 с получает визуальную обратную связь
  • Избегайте блокировки EDT с помощью вычислений, ожидания блокировки и т. Д.
  • Избегайте проблем параллелизма с обходом и модификацией коллекции, изменениями атрибутов

Опции:

  • Блокировка мелкозернистых объектов
  • Неизменяемые коллекции
  • Неустойчивые поля
  • Частичный снимок

Все они имеют преимущества, недостатки и недостатки для модели и игры.

Обновление 2: я говорю об этой игре. Мой клон находится здесь. Снимки экрана могут помочь представить взаимодействие рендеринга и модели данных.

Обновление 3:

Я постараюсь привести небольшой пример кода, чтобы прояснить мою проблему, поскольку из комментариев кажется, что он неправильно понят:

List<GameObject> largeListOfGameObjects = ...
List<Building> preFilteredListOfBuildings = ...
// In EDT
public void onAddBuildingClicked() {
    Building b = new Building(100 /* kW */);
    largeListOfGameObjects.add(b);
    preFilteredListOfBuildings.add(b);
}
// In EDT
public void paint(Graphics g) {
    int y = 0;
    for (Building b : preFilteredListOfBuildings) {
        g.drawString(Integer.toString(b.powerAssigned), 0, y);
        y += 20;
    }
}
// In EDT
public void assignPowerTo(Building b, int amount) {
    b.powerAssigned = amount;
}
// In simulation thread
public void distributePower() {
    int sum = 0;
    for (Building b : preFilteredListOfBuildings) {
        sum += b.powerRequired;
    }
    final int alloc = sum / (preFilteredListOfBuildings.size() + 1);
    for (final Building b : preFilteredListOfBuildings) {
        SwingUtilities.invokeLater(=> assignPowerTo(b, alloc));            
    }
}

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


person akarnokd    schedule 11.06.2009    source источник


Ответы (8)


Похоже, что подход клиент / сервер может выиграть:

Плеер - это клиент - с этой стороны происходит интерактивность и рендеринг. Итак, игрок нажимает кнопку, запрос отправляется на сервер. Ответ от сервера возвращается, и состояние игрока обновляется. В любой момент между этими событиями экран можно перерисовать, и он будет отражать состояние игры, известное клиенту в данный момент.

ИИ - это тоже клиент, он эквивалент бота.

Симуляция - это сервер. Он получает обновления от своих клиентов в разное время и обновляет состояние мира, а затем рассылает эти обновления всем по мере необходимости. Вот где это связано с вашей ситуацией: симуляция / ИИ требует статического мира, и многие вещи происходят одновременно. Сервер может просто поставить в очередь запросы на изменение и применить их перед отправкой обновлений обратно клиенту (ам). Итак, что касается сервера, игровой мир на самом деле не меняется в реальном времени, он меняется всякий раз, когда сервер чертовски хорошо это решает.

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

Обратите внимание, что на самом деле это не обязательно должно быть реализовано в виде TCP / IP через Интернет, просто это помогает думать об этом в этих терминах.

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

person Atiaxi    schedule 30.06.2009
comment
Спасибо, теперь я вижу более широкую картину. Вы действительно решили две из моих проблем, связанных с этой игрой (вторая примерно описана в моем другом вопросе: stackoverflow.com/questions/993635/). Локальная и сетевая игра должны быть спроектированы одинаково, поэтому практически нет различий между однопользовательским и многопользовательским «игровым движком». В конце концов, это просто передача сообщений. Дело твое. - person akarnokd; 30.06.2009

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

Создайте api, например SwingUtilities.invokeLater () и / или SwingUtilities.invokeAndWait (), для очереди на изменение состояния, чтобы обрабатывать запросы на изменение состояния.

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

person user101884    schedule 11.06.2009
comment
Нет, проблема не в части изменения состояния, а в том, чтобы избежать состояния гонки между изменением состояния, инициированным пользователем, и некоторым текущим оцениванием состояния. Истинным решением была бы программная транзакционная память, но она еще не доступна. - person akarnokd; 11.06.2009
comment
STM - это только программное обеспечение. Это вряд ли решит проблемы с производительностью. - person Tom Hawtin - tackline; 12.06.2009
comment
если все проверки состояния и изменения состояния были выполнены в одном потоке, это не должно быть проблемой. Поэтому добавьте оценку состояния в ту же очередь, что и изменение состояния. - person user101884; 12.06.2009

Самый простой способ - сделать симуляцию достаточно быстрой для запуска в EDT. Предпочитайте программы, которые работают!

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

Для обновления: в потоке моделирования заблокируйте модель рендеринга. Пройдите обновление модели рендеринга там, где все отличается от ожидаемого, обновите модель рендеринга. По завершении обхода разблокируйте модель рендеринга и запланируйте перерисовку. Обратите внимание, что в этом подходе вам не нужен миллиард слушателей.

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

person Tom Hawtin - tackline    schedule 11.06.2009
comment
К сожалению, количество игровых объектов (~ 10000) и AI-игроков (13) требует времени вычислений около 250-300 мс (в зависимости от системы). Для анимации пользовательского интерфейса и скорости отклика установлено значение 20 FPS = 50 мс минимум. Путь обновления ясен, потому что изменения модели, навязанные ИИ и симуляцией, влияют примерно на 500 игровых объектов, и большая часть из них - это простое присвоение значений. И я не хочу, чтобы действие пользователя и реакция пользовательского интерфейса находились слишком далеко (действия пользователя - это щелчки мыши и нажатия клавиш, которые появляются в EDT). - person akarnokd; 12.06.2009

Если изменение состояния игры происходит быстро (как только вы знаете, на что его изменить), вы можете рассматривать состояние игры, как другие модели Swing, и изменять или просматривать состояние только в EDT. Если изменение состояния игры происходит не быстро, то вы можете либо синхронизировать изменение состояния и сделать это в работнике / таймере качания (но не в EDT), либо вы можете сделать это в отдельном потоке, который вы обрабатываете аналогично EDT (в этот момент вы посмотрите, как использовать BlockingQueue для обработки запросов на изменение). Последнее более полезно, если пользовательскому интерфейсу никогда не требуется извлекать информацию из состояния игры, а вместо этого изменения рендеринга отправляются через слушателей или наблюдателей.

person Kathy Van Stone    schedule 11.06.2009
comment
Проблема заключается не в изменении состояния SwingWorker, а в потенциальном перекрытии действий пользователя и оценок, выполняемых в SwingWorker. Например, рабочий поток хочет вычислить общее потребление энергии и производство энергии зданиями игрока на планетах игрока, а затем назначить энергию различным зданиям на основе некоторых критериев. Но когда это агрегирование запускается, пользователь может вводить команды в EDT для добавления или удаления зданий на планете. Если модель состояния является общей, это приводит к проблемам с параллелизмом. Я ищу способы избежать этого. - person akarnokd; 12.06.2009
comment
Вам нужно будет синхронизировать агрегацию и изменения либо с помощью блокировки (возможно, ReadWriteLock), либо путем помещения всего кода в один поток (хотя тогда это должен быть поток, отличный от EDT). - person Kathy Van Stone; 12.06.2009
comment
Примерно 75% игровой модели используется только для рендеринга или чтения. Динамическая часть написана в EDT и читается в EDT / рабочих потоках. Записи никогда не перекрываются, но операции чтения / записи могут. Если модель не находится в EDT, рендеринг замедляется из-за увеличения числа конфликтов между граничными запросами / блокировками. Я думаю о том, чтобы сделать снимок 25% в подмодели и использовать его в качестве основы только для чтения для вычислений. Некоторые вычисления / агрегации в настоящее время выполняются в EDT на каждом проходе рендеринга, но в любом случае их нужно переместить в другое место. - person akarnokd; 12.06.2009

Можно ли постепенно обновлять состояние игры и при этом иметь согласованную модель? Например, пересчитайте для подмножества объектов планеты / игрока / флота между рендерами / обновлениями пользователя.

Если это так, вы можете запускать инкрементные обновления в EDT, которые вычисляют только небольшую часть состояния, прежде чем позволить EDT обрабатывать вводимые пользователем данные и визуализировать.

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

Это должно позволить вам избежать копирования или блокировки игровой модели, сохраняя при этом отзывчивость взаимодействия с пользователем.

person Aaron    schedule 14.06.2009
comment
Нравится круговой стиль? ИИ-игроки сменяют друг друга в EDT так же, как и человек? С точки зрения прогресса моделирования это могло бы быть возможным решением. (В текущей версии распределение ресурсов выполняется в EDT при каждой перерисовке (), но только для текущей выбранной планеты). Думаю сейчас в гибридной версии. - person akarnokd; 14.06.2009

Я думаю, вам не следует, чтобы World сохранял какие-либо данные или вносил изменения в какие-либо объекты, его следует использовать только для поддержки ссылки на объект, и когда этот объект необходимо изменить, попросите Player внести изменения напрямую. В этом случае единственное, что вам нужно сделать, - это синхронизировать каждый объект в игровом мире, чтобы, когда игрок вносит изменения, другой игрок не мог этого сделать. Вот пример того, о чем я думаю:

Игроку А нужно знать о планете, поэтому он запрашивает у мира эту планету (как это зависит от вашей реализации). World возвращает ссылку на объект Planet, который запрошен игроком A. Игрок А решает внести изменения, поэтому он это делает. Допустим, он добавляет здание. Метод добавления здания на планету синхронизирован, поэтому только один игрок может делать это одновременно. Здание будет отслеживать собственное время постройки (если оно есть), поэтому метод добавления здания Planet будет освобожден почти сразу. Таким образом, несколько игроков могут запрашивать информацию на одной и той же планете одновременно, не влияя друг на друга, и игроки могут добавлять здания почти одновременно, без особых задержек. Если два игрока ищут место для постройки (если это часть вашей игры), то проверка пригодности места будет запросом, а не изменением.

Извините, если это не ответ на ваш вопрос, я не уверен, правильно ли я понял.

person mnuzzo    schedule 19.06.2009
comment
Проблема в том, что статистика планеты, состояние здания используются для рендеринга (90%) и прогресса на основе моделирования (25%) с перекрытием между ними. Например, текущий полный процент наносится на изображение здания при каждой перерисовке (). Игроки довольно сильно разделены с точки зрения развития планеты и построек. Может, стоит сослаться на настоящую игру. - person akarnokd; 19.06.2009
comment
Просто чтобы пройти этот шаг за шагом, чтобы убедиться, что я ничего не пропускаю, как работает графический интерфейс? Я полагал, что вы используете шаблон Observer для визуализации графического интерфейса пользователя на основе информации, содержащейся в отображаемых объектах. Если графический интерфейс управляет строительством здания (чтобы использовать ваш пример), это может создать проблему. - person mnuzzo; 19.06.2009
comment
Графический интерфейс визуализируется на основе полей состояния: поле прогресса в объекте Building. Для прогресса от 0 до 10% покажите изображение 1, 10-20% изображение 2 и т. Д. Однако прогресс изменяется симуляцией. Каждые 500 мс состояние выполнения увеличивается. Однако игровой мир требует сотен изменений состояния, которые зависят от агрегированных свойств. Например, скорость строительства зависит от всех доступных заводов во всем королевстве. Это значение емкости требует, чтобы симуляция проходила по всем зданиям на всех планетах игрока для каждого игрока. - person akarnokd; 21.06.2009

Как насчет реализации архитектуры каналов и фильтров. Каналы соединяют фильтры вместе и ставят запросы в очередь, если фильтр работает недостаточно быстро. Обработка происходит внутри фильтров. Первый фильтр - это механизм AI, а механизм рендеринга реализуется набором последующих фильтров.

При каждом такте таймера новое динамическое мировое состояние вычисляется на основе всех входных данных (время также является входом) и копии, вставленной в первый канал.

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

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

Эта архитектура обеспечивает хороший параллелизм без большой синхронизации.

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

person Hans Malherbe    schedule 21.06.2009
comment
Проблема не в том, как я выплюну вычисления. Проблема в том, как разделить данные между вычислениями, рендерингом и взаимодействием с пользователем в реальном времени. Рендеринг и взаимодействие с пользователем происходят в EDT - никаких конфликтов там нет, но вычисления моделирования / AI требуют неизменяемого игрового мира в качестве входных данных во время их запуска. Вопрос в том, как я могу дешево поделиться состоянием без проблем с потоковой передачей - полная / частичная копия; летучие поля и т. д.? - person akarnokd; 21.06.2009
comment
В приведенном выше дизайне нет общего состояния между разными потоками. Первый фильтр создает копию вычисленного динамического состояния, которое передается по цепочке. Затем он начинает новое вычисление состояния мира и входных данных. Каждый последующий фильтр работает с этой копией. Копия помещается в трубу только после завершения работы фильтра. В конце конвейера копия отбрасывается. - person Hans Malherbe; 22.06.2009

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

person nojevive    schedule 27.06.2009
comment
Я не знаю, как сделать больший акцент на том факте, что вопрос НЕ в том, как я буду применять изменения к модели, потому что эта операция ясна и достаточно быстра. Вопрос в том, как я прохожу и модифицирую игровую модель практически одновременно с решением, имеющим наименьшее количество причин, связанных с потоками. Не обижайся. - person akarnokd; 27.06.2009