如何在EDT面前管理游戏状态?

aka*_*okd 8 java swing event-dispatch-thread

我正在Java平台上开发一个实时策略游戏克隆,我有一些关于放置位置和如何管理游戏状态的概念性问题.游戏使用Swing/Java2D作为渲染.在当前的开发阶段,不存在模拟,也不存在AI,只有用户能够改变游戏的状态(例如,建造/拆除建筑物,添加 - 移除生产线,组装车队和设备).因此,可以在事件调度线程中执行游戏状态操作而无需任何渲染查找.游戏状态还用于向用户显示各种聚合信息.

但是,由于我需要引入模拟(例如,构建进度,人口变化,车队运动,制造过程等),在Timer和EDT中更改游戏状态肯定会降低渲染速度.

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

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

游戏状态模型似乎过于复杂,无法在任何地方使用不可变值类.

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

更新:(根据我给出的评论)游戏使用13个AI控制的玩家,1个人类玩家,并拥有大约10000个游戏对象(行星,建筑物,设备,研究等).例如,游戏对象具有以下属性:

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

在一个场景中,用户在这个星球上建造一座新建筑.这是在EDT中执行的,因为需要更改地图和建筑物集合.与此同时,每隔500毫秒进行一次模拟,以计算所有游戏行星上建筑物的能量分配,这需要遍历建筑物集合以进行统计收集.如果计算分配,则将其提交给EDT并且分配每个建筑物的能量场.

只有人类玩家互动具有此属性,因为AI计算的结果无论如何都应用于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));            
    }
}
Run Code Online (Sandbox Code Playgroud)

因此重叠在onAddBuildingClicked()和distributePower()之间.现在想象一下你在游戏模型的各个部分之间有50种这种重叠的情况.

Ati*_*axi 3

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

玩家是客户端——交互和渲染发生在客户端。因此,玩家按下按钮,请求就会发送到服务器。服务器返回回复,玩家状态更新。在这些事情发生之间的任何时刻,屏幕都可以重新绘制,它反映了客户端当前所知的游戏状态。

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

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

最后,在客户端,您可以通过执行一些快速近似计算并显示结果(因此满足即时需求)来防止按下按钮和查看结果之间的延迟,然后在服务器绕过时显示更正确的结果和你说话。

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

或者,您可以将在模拟过程中保持数据一致性的责任放在数据库上,因为它们在构建时已经考虑了锁定和一致性。像 sqlite 这样的东西可以作为非网络解决方案的一部分。