You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移除std::function开销:ECS框架TypedView模板优化问询

无std::function开销的ECS TypedView实现方案

核心原理

ecs.hpp框架消除std::function开销的核心思路是将类型擦除从运行期转移到编译期:通过模板元编程直接绑定用户的处理逻辑,利用编译器的参数推导和代码生成能力,避免std::function带来的虚调用、堆分配等运行时开销。

具体来说,它放弃了std::function的类型擦除机制,转而用模板化的方法接受任意可调用对象(lambda、函数指针、仿函数),在编译期直接生成适配当前可调用对象的遍历代码,完全不需要运行时的类型检查或间接调用。


简化实现代码

以下是一个最小化的可运行示例,核心实现TypedView的each方法,完全避免std::function:

#include <unordered_map>
#include <vector>
#include <cstdint>
#include <utility>
#include <tuple>
#include <cstdio>

// 定义示例组件
struct Transform { float x, y; };
struct Velocity { float dx, dy; };

// 实体管理器:负责实体创建和组件存储
class EntityManager {
public:
    using EntityId = uint32_t;

    EntityId create_entity() {
        return next_entity_id++;
    }

    // 给实体添加组件
    template<typename Comp>
    void add_component(EntityId e, Comp&& comp) {
        get_component_map<Comp>()[e] = std::forward<Comp>(comp);
    }

    // 获取实体的指定组件
    template<typename Comp>
    Comp& get_component(EntityId e) {
        return get_component_map<Comp>()[e];
    }

    // 检查实体是否拥有指定组件
    template<typename Comp>
    bool has_component(EntityId e) {
        auto& map = get_component_map<Comp>();
        return map.find(e) != map.end();
    }

    // 获取拥有所有指定组件的实体列表
    template<typename... Comps>
    std::vector<EntityId> get_matching_entities() {
        std::vector<EntityId> result;
        if constexpr (sizeof...(Comps) > 0) {
            // 取第一个组件的实体集合作为基础,过滤其他组件
            using FirstComp = std::tuple_element_t<0, std::tuple<Comps...>>;
            auto& first_map = get_component_map<FirstComp>();
            
            for (auto& [entity, _] : first_map) {
                // C++17折叠表达式:检查实体是否拥有所有组件
                if ((has_component<Comps>(entity) && ...)) {
                    result.push_back(entity);
                }
            }
        }
        return result;
    }

private:
    EntityId next_entity_id = 1;

    // 每个组件类型对应一个独立的存储容器(静态局部变量,确保单例)
    template<typename Comp>
    static std::unordered_map<EntityId, Comp>& get_component_map() {
        static std::unordered_map<EntityId, Comp> component_map;
        return component_map;
    }
};

// TypedView:筛选并处理拥有指定组件的实体
template<typename... Comps>
class TypedView {
public:
    explicit TypedView(EntityManager& em) : em_(em) {}

    // 核心方法:接受任意可调用对象,编译期直接生成调用逻辑
    template<typename Func>
    void each(Func&& func) {
        auto entities = em_.get_matching_entities<Comps...>();
        for (EntityId e : entities) {
            // 编译期展开组件参数,直接传递给处理函数
            func(em_.get_component<Comps>(e)...);
        }
    }

private:
    EntityManager& em_;
};

// 辅助函数:简化TypedView的创建
template<typename... Comps>
TypedView<Comps...> view(EntityManager& em) {
    return TypedView<Comps...>(em);
}

// 使用示例
int main() {
    EntityManager em;

    // 创建实体并添加组件
    auto e1 = em.create_entity();
    em.add_component(e1, Transform{10.f, 20.f});
    em.add_component(e1, Velocity{1.f, 0.5f});

    auto e2 = em.create_entity();
    em.add_component(e2, Transform{5.f, 5.f});
    em.add_component(e2, Velocity{-0.5f, 1.f});

    // 调用each处理实体,直接传入lambda,无std::function开销
    view<Transform, Velocity>(em).each([](Transform& t, Velocity& v) {
        t.x += v.dx;
        t.y += v.dy;
        printf("Entity position: (%.1f, %.1f)\n", t.x, t.y);
    });

    return 0;
}

关键细节解析

  1. 模板化的each方法
    each方法的参数Func是模板类型,编译器会根据传入的可调用对象(比如示例中的lambda)推导其具体类型,直接生成适配该类型的调用代码,完全没有运行时的类型擦除操作。

  2. 编译期参数展开
    利用C++17的折叠表达式(has_component<Comps>(entity) && ...)和模板参数展开func(em_.get_component<Comps>(e)...),在编译期处理任意数量的组件类型,不需要运行时的类型检查或动态参数传递。

  3. 组件存储的独立性
    每个组件类型对应独立的std::unordered_map,符合ECS的数据导向设计,缓存友好,同时避免了面向对象的封装带来的数据碎片化。

  4. 对比entityx的方案
    entityx用std::function做运行期类型擦除,是为了让TypedView的each方法可以接受不同类型的可调用对象,但代价是运行时开销;而本方案通过编译期模板推导,既保留了接受任意可调用对象的灵活性,又完全消除了std::function的开销。


内容的提问来源于stack exchange,提问作者user540983

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 20:47:44