java - 面对 EDT 如何管理游戏状态?

标签 java swing event-dispatch-thread

我正在 Java 平台上开发一个实时战略游戏克隆,我有一些概念性的问题关于放置在哪里以及如何管理游戏状态。游戏使用Swing/Java2D作为渲染。在目前的开发阶段,没有模拟,也没有人工智能,只有用户可以改变游戏的状态(例如, build /拆除建筑物、增减生产线、组装车队和设备)。因此,游戏状态操作可以在事件分派(dispatch)线程中执行,无需任何渲染查找。游戏状态还用于向用户显示各种聚合信息。

但是,由于我需要引入模拟(例如,建筑进度、人口变化、舰队移动、制造过程等),在 Timer 和 EDT 中更改游戏状态肯定会减慢渲染速度。

假设模拟/AI 操作每 500 毫秒执行一次,我使用 SwingWorker 进行大约 250 毫秒的计算。我如何确保在模拟和可能的用户交互之间没有关于游戏状态读取的竞争条件?

我知道模拟的结果(少量数据)可以通过 SwingUtilities.invokeLater() 调用有效地移回 EDT。

游戏状态模型似乎过于复杂以至于无法在所有地方使用不可变值类。

是否有相对正确的方法来消除这种读取竞争条件?也许在每个计时器滴答上进行完整/部分游戏状态克隆,或者将游戏状态的生存空间从 EDT 更改为其他线程?

更新:(来 self 给出的评论) 该游戏由 13 名 AI 控制的玩家、1 名人类玩家运营,拥有约 10000 个游戏对象(行星、建筑物、设备、研究等)。例如,游戏对象具有以下属性:

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

In a scenario, the user builds a new building onto this planet. This is performed in EDT as the map and buildings collection needs to be changed. Parallel to this, a simulation is run on every 500ms to compute the energy allocation to the buildings on all game planets, which needs to traverse the buildings collection for statistics gathering. If the allocation is computed, it is submitted to the EDT and each building's energy field gets assigned.

Only human player interactions have this property, because the results of the AI computation are applied to the structures in EDT anyway.

In general, 75% of the object attributes are static and used only for rendering. The rest of it is changeable either via user interaction or simulation/AI decision. It is also ensured, that no new simulation/AI step is started until the previous one has written back all changes.

My objectives are:

  • Avoid delaying the user interaction, e.g. user places the building onto the planet and only after 0.5s gets the visual feedback
  • Avoid blocking the EDT with computation, lock wait, etc.
  • Avoid concurrency issues with collection traversal and modification, attribute changes

Options:

  • Fine grained object locking
  • Immutable collections
  • Volatile fields
  • Partial snapshot

All of these have advantages, disadvantages and causes to the model and the game.

Update 2: I'm talking about this game. My clone is here. The screenshots might help to imagine the rendering and data model interactions.

Update 3:

I'll try to give a small code sample for clarify my problem as it seems from the comments it is misunderstood:

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 个这样的重叠。

最佳答案

这听起来像是可以从客户端/服务器方法中获益:

播放器是客户端 - 交互和渲染发生在这一端。所以玩家按下一个按钮,请求就会发送到服务器。服务器回复回来,玩家状态更新。在这些事情发生之间的任何时候,屏幕都可以重新绘制,它反射(reflect)了客户端当前知道的游戏状态。

人工智能同样是一个客户端——它相当于一个机器人。

模拟就是服务器。它在不同时间从其客户端获取更新并更新世界状态,然后适本地将这些更新发送给每个人。这是它与您的情况相关的地方:模拟/AI 需要一个静态世界,许多事情同时发生。服务器可以简单地排队更改请求并在将更新发送回客户端之前应用它们。因此,就服务器而言,游戏世界实际上并没有实时变化,它会在服务器决定实时变化时发生变化。

最后,在客户端,您可以通过进行一些快速的近似计算并显示结果(满足即时需求),然后在按下按钮时显示更正确的结果,从而避免按下按钮和看到结果之间的延迟服务器开始与您交谈。

请注意,这实际上不必以互联网上的 TCP/IP 方式实现,只是有助于从这些方面考虑它。

或者,您可以将在模拟期间保持数据一致性的责任放在数据库上,因为它们在构建时已经考虑到了锁定和一致性。 sqlite 之类的东西可以作为非联网解决方案的一部分。

关于java - 面对 EDT 如何管理游戏状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/981920/

相关文章:

java swing形式挂起

java - 如何刷新jFrame组件(jPanels)?

java - 添加面板到面板

java - 进度条更新程序占用 CPU

java - 无法在 Jframe/标签内重新绘制

java - 使用线程更新jlabel的内容

java - 什么是 int ai = a[i], j;做?

java - 在spring拦截器中获取请求映射对象,获取实际的url字符串模式

java - 似乎无法打印充满对象/枚举的数组列表

java - 如何同时更改JLabel文本和JButton位置