创建密集的动态数组(值数组)作为库

时间:2016-06-03 02:33:17

标签: c++ data-structures

我尝试将压缩数组创建为游戏引擎的数据结构,如下所述: - http://experilous.com/1/blog/post/dense-dynamic-arrays-with-stable-handles-part-1
diagram

简而言之,结构存储值而不是指针。

这是草稿。

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>) ...
}

原型按照我的意愿工作,但现在我关注旧代码的美妙。

例如,我总是创建一个这样的新对象: -

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.

以下是问题:

  1. 新版本的代码更难看 我应该避免吗?怎么避免呢?

  2. 如何避免/减少程序员对上述修改的工作?
    当它们中有许多散布在周围时,这是非常繁琐的。

  3. (编辑)最可怕的部分是类型声明的变化,例如

    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.
    

    此更改(插入单词&#34; Id&lt;&gt;&#34;)表示游戏逻辑必须知道用于存储Bullet的基础数据结构。

    如果将来再次更改基础数据结构,我将不得不再次手动重构它们(从Id&lt;&gt;到其他东西),即较低的可维护性。

    1. (可选)此数据结构/技术的名称是什么?

    2. 作为一个库,Id是否有一个PackArray *字段来启用访问底层对象(例如Bullet *),而没有manager()?

      Bullet * bullet = someId-&gt; getUnderlyingObject();

1 个答案:

答案 0 :(得分:1)

id的这种行为听起来像handles,因为您没有提供有关存储方法的信息,但只要句柄有效就保证访问权限。在后面的方面,句柄的行为类似于原始指针:你将无法判断它是否有效(至少没有管理器)并且句柄可能在某些时候被重用。

如果从原始指针更改为句柄产生更丑陋的代码这个问题是非常自以为是的,我宁愿保持这个目标:在可读显式和太多输入之间存在平衡 - 每个人都在这里绘制自己的限制。让调用站点指定getManager也有好处:也许这些管理器有多个可能的实例,可能需要锁定管理器以及只想锁定一次的多个操作。 (除了我在下面提供的内容之外,你可以支持这两种情况。)

让我们使用指针/迭代器表示法通过我们的句柄访问对象,减少必要的代码更改量。使用std::make_uniquestd::make_shared作为参考,让我们定义make_handle以将创建分派给正确的经理。我已经调整了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;
    }
};

你甚至可以使所有这些const-correct(与实现自定义迭代器的技术相同)。

您甚至可能希望将details<T>扩展为完整特征类型......这是在泛化和复杂性之间的平衡。