Я занимаюсь разработкой клона стратегии в реальном времени на платформе Java, и у меня есть несколько принципиальных вопросов о том, где разместить и как управлять состоянием игры. В качестве рендеринга игра использует Swing / Java2D. На текущем этапе разработки симуляция и ИИ отсутствуют, и только пользователь может изменить состояние игры (например, построить / снести здание, добавить или удалить производственные линии, собрать флот и оборудование). Следовательно, манипулирование состоянием игры может выполняться в потоке диспетчеризации событий без какого-либо просмотра рендеринга. Состояние игры также используется для отображения различной агрегированной информации пользователю.
Однако, поскольку мне нужно ввести симуляцию (например, прогресс строительства, изменения численности населения, перемещения флота, производственный процесс и т. Д.), Изменение состояния игры в таймере и EDT наверняка замедлит рендеринг.
Допустим, операция симуляции / AI выполняется каждые 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. Также гарантируется, что новый шаг симуляции / 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 () и distribtePower (). Теперь представьте случай, когда у вас есть 50 таких перекрытий между различными частями игровой модели.