全局状态
1)这是处理事情的好方法吗?意思是使用“全局” Gamestate 类是个好主意吗?有没有更好的方法呢?
对于游戏而言,与一些可重用的代码相比,我认为全局状态就足够了。您甚至可以避免传递游戏状态指针,而是真正将其设为全局变量。
同步
2) 我的意图是让 Gamestate 在 getter/setter 中有互斥锁,但它不适用于读取,因为我无法在对象仍处于锁定状态时返回它,这意味着我必须将同步放在 getter 之外/setters 并公开互斥锁。这也意味着我将为所有不同的资源拥有大量的互斥锁。解决这个问题的最优雅的方法是什么?
我会尝试从交易的角度来考虑这一点。将每个状态更改包装到自己的互斥锁代码中不仅会影响性能,而且如果代码获取一个状态元素,对其执行一些计算并稍后设置值,而其他一些代码修改了之间的相同元素。所以我会尝试构建LogicManager
和Renderer
以这样的方式与游戏状态的所有交互都捆绑在几个地方。在该交互的持续时间内,线程应该在状态上持有一个互斥锁。
如果您想强制使用互斥锁,那么您可以创建一些至少有两个类的构造。让我们称它们为GameStateData
and GameStateAccess
。GameStateData
将包含所有状态,但不提供对它的公共访问。GameStateAccess
将成为其私人数据的朋友GameStateData
并提供对其私人数据的访问权限。的构造函数GameStateAccess
将获取指向 的引用或指针,GameStateData
并将锁定该数据的互斥锁。析构函数将释放互斥锁。这样,您用于操作状态的代码将被简单地编写为GameStateAccess
对象在范围内的块。
但是,仍然存在一个漏洞:如果从此类返回的对象GameStateAccess
是指针或对可变对象的引用,那么此设置不会阻止您的代码将此类指针带出受互斥体保护的范围。为了防止这种情况,要么注意你是如何写东西的,要么使用一些自定义的类似指针的模板类,一旦GameStateAccess
超出范围就可以清除,或者确保你只通过值而不是引用传递东西。
例子
使用C++11,上述锁管理思想可以实现如下:
class GameStateData {
private:
std::mutex _mtx;
int _val;
friend class GameStateAccess;
};
GameStateData global_state;
class GameStateAccess {
private:
GameStateData& _data;
std::lock_guard<std::mutex> _lock;
public:
GameStateAccess(GameStateData& data)
: _data(data), _lock(data._mtx) {}
int getValue() const { return _data._val; }
void setValue(int val) { _data._val = val; }
};
void LogicManager::performStateUpdate {
int valueIncrement = computeValueIncrement(); // No lock for this computation
{ GameStateAccess gs(global_state); // Lock will be held during this scope
int oldValue = gs.getValue();
int newValue = oldValue + valueIncrement;
gs.setValue(newValue); // still in the same transaction
} // free lock on global state
cleanup(); // No lock held here either
}
循环终止指示器
3)我让所有线程访问“bool run”以检查是否继续它们的循环
while(gs->run){
....
}
如果我在 EventManager 中收到退出消息,run 会设置为 false。我是否需要同步该变量?我会将其设置为易失性吗?
对于这个应用程序,一个易失但不同步的变量应该没问题。您必须将其声明为 volatile 以防止编译器生成缓存该值的代码,从而隐藏另一个线程的修改。
作为替代方案,您可能希望为此使用std::atomic
变量。
指针间接开销
4)不断取消引用指针等是否会对性能产生影响?例如gs->objects->entitylist.at(2)->move();
,所有这些都会->
导致.
任何重大放缓吗?
这取决于替代品。在许多情况下,如果重复使用,编译器将能够保留gs->objects->entitylist.at(2)
上述代码中 eg 的值,而不必一遍又一遍地计算它。一般来说,我认为由于所有这些指针间接导致的性能损失是次要问题,但这很难确定。