创建密集的动态数组(数组的值)作为库 [英] create dense dynamic array (array of value) as library

查看:187
本文介绍了创建密集的动态数组(数组的值)作为库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图创建一个游戏引擎的数据结构的打包数组,如下所述:

I tried to create packed array as a data structure for a game engine as described here:- http://experilous.com/1/blog/post/dense-dynamic-arrays-with-stable-handles-part-1

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

In short, the structure stores values, instead of pointers.

这是一个草案。

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

,但现在我关心旧代码的美丽。

The prototype works as I wished, but now I concern the beauty of old code.

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

For example, I had always created a new object like this:-

Bullet* bullet = new Bullet(gameEngine,velocity);

现在我必须致电: -

Now I must call :-

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. 新版本的代码更丑陋。

    我应该避免吗?怎么避免呢?

  1. The new version of code is more ugly.
    Should I avoid it? How to avoid it?

如何避免/减少上述修改的程序员的工作?

这是非常繁琐的,当有很多散射

How to avoid / reduce programmer's-work of the above modification?
It is very tedious, when there are many of them scattering around.

(编辑)最可怕的部分是类型声明中的更改,例如

(Edit) The scariest part is change in the type declaration e.g.

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的基础数据结构。

This change (inserting the word "Id<>") means that the game logic has to know the underlying data structure that used to store Bullet.

如果基础数据结构将来会再次被更改,我将不得不再次手动重构它们(从Id到别的),即更低可维护性

If the underlying data structure would be changed again in future, I will have to manually refactor them one by one again (from Id<> to something else), i.e. lower maintainability.


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

  1. (optional) What is the name of this data structure / technique?

作为一个库,Id应该有一个PackArray *的字段来启用访问底层对象(如Bullet *),而没有manager()?

As a library, should Id has a field of PackArray* to enable accessing the underlying object (e.g. Bullet*), without manager()?

Bullet * bullet = someId-> getUnderlyingObject();

Bullet* bullet = someId->getUnderlyingObject();


推荐答案

id 的这种行为听起来像句柄,因为你不给出有关存储方法的信息,但只要句柄有效,保证访问。在后面的方面,句柄表现得像原始的指针:你将无法判断它是否有效(至少没有经理),并且句柄可能会在某些时候重复使用。

This behaviour of id sounds like handles, as in you don't give out information about the storage method, but guarantee access as long as the handle is valid. In the later respect handles behave like raw pointers: you won't be able to tell if it's valid (at least without the manager) and the handle might be reused at some point.

如果从原始指针更改为句柄,会产生较为糟糕的代码,这个问题是非常有意思的,我宁愿保持这个目标:在可读性和太多打字之间有一个平衡 - 每个人都在这里自己的限制。调用站点指定 getManager 还有一些优点:也许这些管理器有多个可能的实例,也许获得管理员需要锁定,并且对于只需要锁定一次的多个操作。 (您可以支持这两种情况除了下面的内容之外。)

The question if changing from raw pointers to handles produces uglier code is very opinionated and I'd rather keep this objective: there's a balance between readably explicit and too much typing - everyone draws their own limits here. There's also advantages to having the calling site specify getManager: maybe there are multiple possible instances of these managers, maybe getting the manager requires locking and for multiple operations you want to lock only once. (You can support both of these cases in addition to what I present below.)

我们使用指针/迭代器符号通过我们的句柄访问对象,减少了代码更改必要。使用 std :: make_unique std :: make_shared 作为参考,让我们定义 make_handle 将创建发送到正确的管理器。我已经调整了 PackArray :: create ,使以下示例更加紧​​凑:

Let's use pointer/iterator notation to access the objects through our handles, reducing the amount of code changes necessary. Using std::make_unique and std::make_shared for reference, let's define make_handle to dispatch the creation to the right manager. I've adjusted PackArray::create a bit to make the following example more compact:

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());

每个类型都需要一个经理,即如果你没有定义一个默认值,你会得到一个编译器错误。或者,您可以提供通用的实现(注意:初始化实例不是线程安全的!):

Every type needs a manager, i.e. if you don't define a default you'll get a compiler error. Alternatively you can provide a generic implementation (note: the initialisation of instance is not thread safe!):

template<class T>
PackArray<T>& details<T>::getManager() {
    static PackArray<T> instance;
    return instance;
}

通过模板专业化获得特殊行为。您甚至可以通过模板专业化来替换经理类型,从而可以轻松比较存储策略(例如SOA与AOS)。

You get special behaviour via template specialisation. You can even replace the manager type via template specialisation, allowing you to easily compare storage strategies (e.g. SOA vs. 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正确(与实现自定义迭代器相同的技巧)

And you can even make all of this const-correct (the same techniques as implementing custom iterators apply).

您甚至可以将详细信息< T> 扩展到完整的traits类型...全部泛化与复杂性之间的平衡。

You may even want to extend the details<T> to a full traits type... It's all a balance between generalisation and complexity.

这篇关于创建密集的动态数组(数组的值)作为库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆