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

StackOverflow https://stackoverflow.com/questions/981920

Вопрос

Я разрабатываю клон стратегической игры в реальном времени на платформе 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% атрибутов объекта являются статическими и используются только для рендеринга.Остальная часть может быть изменена либо с помощью взаимодействия с пользователем, либо с помощью моделирования / решения искусственного интеллекта.Также гарантируется, что ни один новый этап моделирования / искусственного интеллекта не будет запущен до тех пор, пока предыдущий не вернет все изменения.

Мои цели заключаются в следующем:

  • Избегайте задержки взаимодействия с пользователем, напримерпользователь помещает здание на планету и только через 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 подобных перекрытий между различными частями игровой модели.

Это было полезно?

Решение

Это звучит так, как будто он мог бы извлечь выгоду из клиент-серверного подхода:

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

Искусственный интеллект также является клиентом - это эквивалент бота.

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

При каждом тике таймера новое динамическое состояние мира вычисляется на основе всех входных данных (время также является входным значением) и Копировать вставляется в первую трубу.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top