我尝试创建打包数组作为游戏引擎的数据结构,如下所述:-
http://experilous.com/1/blog/post/dense-dynamic-arrays-with-stable-handles-part-1
简而言之,结构存储的是值,而不是指针。
这是草稿。
template<class T> class Id{
int id;
}
template<class T> class PackArray{
std::vector <int>indirection ; //promote indirection here
std::vector <T>data;
//... some fields for pooling (for recycling instance of T)
Id<T> create(){
data.push_back(T());
//.... update indirection ...
return Id( .... index , usually = indirection.size()-1 .... )
}
T* get(Id<T> id){
return &data[indirection[id.id]];
//the return result is not stable, caller can't hold it very long
}
//... others function e.g. destroy(Id<T>) ...
}
原型(prototype)如我所愿,但现在我关心的是旧代码的美感。
例如,我总是像这样创建一个新对象:-
Bullet* bullet = new Bullet(gameEngine,velocity);
现在我必须打电话:-
Id<Bullet> bullet = getManager()->create()->ini(velocity);
// getManager() usually return PackArray<Bullet>*
// For this data structure,
// if I want to hold the object for a long time, I have to cache it as Id.
以下是问题:-
新版代码更丑了。
我应该避免它吗?如何避免?如何避免/减少程序员对上述修改的工作量?
这是非常乏味的,因为它们散落在周围。
(编辑)最可怕的部分是类型声明的变化,例如
class Rocket{
std::vector<Bullet*> bullets;
//-> std::vector<Id<Bullet>> bullets;
void somefunction(){
Bullet* bullet = someQuery();
//-> Id<Bullet> bullet
}
}//These changes scatter around many places in many files.
此更改(插入单词“Id<>”)意味着游戏逻辑必须知道用于存储 Bullet 的底层数据结构。
如果以后底层的数据结构又要变了,我又得手动一个一个重构(从Id<>改成别的),可维护性比较差。
(可选)这个数据结构/技术的名称是什么?
作为一个库,Id 是否应该有一个 PackArray* 字段以允许在没有 manager() 的情况下访问底层对象(例如 Bullet*)?
Bullet* bullet = someId->getUnderlyingObject();
最佳答案
id
的这种行为听起来像 handles
,因为您不提供有关存储方法的信息,但只要句柄有效就保证访问。在后面的方面,句柄的行为类似于原始指针:您将无法判断它是否有效(至少没有管理器)并且句柄可能在某些时候被重用。
如果从原始指针更改为句柄会产生更丑陋的代码,这个问题是非常自以为是的,我宁愿保持这个目标:在可读性明确和过多输入之间存在平衡 - 每个人都在这里画出自己的限制。让调用站点指定 getManager
也有好处:也许这些管理器有多个可能的实例,也许获取管理器需要锁定并且对于多个操作你只想锁定一次。 (除了我在下面介绍的内容之外,您还可以支持这两种情况。)
让我们使用指针/迭代器表示法通过我们的句柄访问对象,减少必要的代码更改量。使用 std::make_unique
和 std::make_shared
作为引用,让我们定义 make_handle
将创作分派(dispatch)给合适的经理。我调整了PackArray::create
稍微使以下示例更紧凑:
template<class T> class Handle;
template<class T> class PackArray;
template<class T, class... Args> Handle<T> make_handle(Args&&... args);
template<class T>
struct details {
friend class Handle<T>;
template<class U, class... Args> friend Handle<U> make_handle(Args&&... args);
private:
// tight control over who get's to access the underlying storage
static PackArray<T>& getManager();
};
template<class T>
class Handle {
friend class PackArray<T>;
size_t id;
public:
// accessors (via the manager)
T& operator*();
T* operator->() { return &*(*this); }
};
template<class T>
class PackArray {
std::vector<size_t> idx;
std::vector<T> data;
public:
template<class... Args>
Handle<T> create(Args&&... args) {
Handle<T> handle;
handle.id = data.size();
idx.push_back(data.size());
// enables non-default constructable types
data.emplace_back(std::forward<Args>(args)...);
return handle;
}
// access using the handle
T& get(Handle<T> handle) {
return data[idx[handle.id]];
}
};
template<class T, class... Args>
Handle<T> make_handle(Args&&... args) {
Handle<T> handle = details<T>::getManager().create(std::forward<Args>(args)...);
return handle;
}
template<class T>
T& Handle<T>::operator*() {
return details<T>::getManager().get(*this);
}
使用代码如下:
Handle<int> hIntA = make_handle<int>();
Handle<int> hIntB = make_handle<int>(13);
Handle<float> hFloatA = make_handle<float>(13.37f);
Handle<Bullet> hBulletA = make_handle<Bullet>();
// Accesses through the respective managers
*hIntA = 42; // assignment
std::cout << *hIntB; // prints 13
float foo = (*hFloatA + 12.26f) * 0.01;
applyDamage(hBulletA->GetDmgValue());
每种类型都需要一个管理器,也就是说,如果你没有定义一个默认值,你会得到一个编译器错误。或者,您可以提供一个通用实现(注意:instance
的初始化不是线程安全的!):
template<class T>
PackArray<T>& details<T>::getManager() {
static PackArray<T> instance;
return instance;
}
您可以通过模板特化获得特殊行为。您甚至可以通过模板特化替换管理器类型,让您轻松比较存储策略(例如 SOA 与 AOS)。
template<>
struct details<Bullet> {
friend class Handle<Bullet>;
template<class U, class... Args> friend Handle<U> make_handle(Args&&... args);
private:
static MyBulletManager& getManager() {
static MyBulletManager instance;
std::cout << "special bullet store" << std::endl;
return instance;
}
};
您甚至可以使所有这些常量正确(与实现自定义迭代器相同的技术适用)。
您甚至可能想要扩展 details<T>
到一个完整的特征类型......这是泛化和复杂性之间的平衡。
关于c++ - 创建密集的动态数组(值数组)作为库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37605056/