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