{"id":7536,"date":"2026-07-31T00:15:00","date_gmt":"2026-07-30T16:15:00","guid":{"rendered":"https:\/\/i007.cc\/wordpress\/?p=7536"},"modified":"2026-07-31T00:15:00","modified_gmt":"2026-07-30T16:15:00","slug":"%e6%8e%a8%e8%8d%90%e4%b8%80%e4%b8%aaesc%e6%a1%86%e6%9e%b6%e7%9a%84%e8%ae%be%e8%ae%a1","status":"publish","type":"post","link":"https:\/\/i007.cc\/wordpress\/archives\/7536","title":{"rendered":"\u63a8\u8350\u4e00\u4e2aESC\u6846\u67b6\u7684\u8bbe\u8ba1"},"content":{"rendered":"<div data-rs-index=\"10\" data-index=\"10\">\n<div tabindex=\"0\" role=\"article\" aria-setsize=\"12\" aria-posinset=\"11\" aria-label=\"Message 11 of 12\">\n<div data-test-render-count=\"2\">\n<div class=\"mb-1 mt-[var(--msg-gap,1.5rem)] group group\/message-row\">\n<div class=\"flex flex-col items-end gap-1\">\n<div class=\"group relative inline-flex gap-2 bg-bg-300 dark:[[data-darker-default]_&amp;]:bg-bg-000 rounded-xl pl-2.5 py-[var(--msg-bubble-py,0.625rem)] break-words text-text-100 transition-all max-w-[75ch] flex-col !px-[var(--msg-bubble-px,1rem)] max-w-[85%]\" data-user-message-bubble=\"true\">\n<div class=\"flex flex-row gap-2 relative\">\n<div class=\"flex-1\">\n<div class=\"font-large !font-user-message py-0.5 grid grid-cols-1 gap-2 relative [&amp;_ul]:!space-y-0 [&amp;_ol]:!space-y-0 [&amp;_ul]:pl-8 [&amp;_ol]:pl-8\" data-testid=\"user-message\"><\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div data-rs-index=\"11\" data-index=\"11\" data-last-message=\"true\">\n<div tabindex=\"-1\" role=\"article\" aria-setsize=\"12\" aria-posinset=\"12\" aria-label=\"Message 12 of 12\">\n<div data-test-render-count=\"1\">\n<div class=\"group group\/message-row\">\n<div class=\"group relative relative pb-[var(--msg-assistant-pb,0.75rem)]\" data-is-streaming=\"false\">\n<div class=\"font-claude-response relative leading-[1.65rem] [&amp;_pre&gt;div]:bg-bg-000\/50 [&amp;_pre&gt;div]:border-0.5 [&amp;_pre&gt;div]:border-border-400 [&amp;_.ignore-pre-bg&gt;div]:bg-transparent [&amp;_.standard-markdown_:is(p,blockquote,h1,h2,h3,h4,h5,h6)]:pl-2 [&amp;_.standard-markdown_:is(p,blockquote,ul,ol,h1,h2,h3,h4,h5,h6)]:pr-8 [&amp;_.progressive-markdown_:is(p,blockquote,h1,h2,h3,h4,h5,h6)]:pl-2 [&amp;_.progressive-markdown_:is(p,blockquote,ul,ol,h1,h2,h3,h4,h5,h6)]:pr-8\">\n<div class=\"grid grid-rows-[auto_auto] min-w-0\">\n<div class=\"row-start-2 col-start-1 relative grid grid-rows-[auto_auto] isolate min-w-0\">\n<div class=\"row-start-1 col-start-1 relative z-[2] min-w-0\">\n<div class=\"standard-markdown grid-cols-1 grid [&amp;_&gt;_*]:min-w-0 gap-3 [&amp;_&gt;_*:last-child]:mb-0 print:block print:[&amp;_&gt;_*_+_*]:mt-3 standard-markdown\">\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">\u4ee3\u7801\u7f16\u8bd1\u8fd0\u884c\u6b63\u5e38\uff0c\u53d1\u7ed9\u4f60\u4e86\u3002\u6574\u4f53\u8bbe\u8ba1\u5206\u56db\u5c42\uff0c\u4ece\u4e0b\u5230\u4e0a\u8bb2\u4e00\u904d\uff1a<\/p>\n<hr class=\"border-border-200 border-t-0.5 my-3 mx-1.5\" \/>\n<h3 class=\"text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold\" dir=\"ltr\">\u8bbe\u8ba1\u6838\u5fc3\uff1aSparse Set<\/h3>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">\u8fd9\u662f\u6574\u4e2a\u6846\u67b6\u7684\u7075\u9b42\uff0c\u7406\u89e3\u5b83\u5c31\u7406\u89e3\u4e86 EnTT \u7684\u6838\u5fc3\u3002<\/p>\n<div class=\"relative group\/copy bg-bg-000\/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100\" tabindex=\"0\" role=\"group\" aria-label=\"Code\">\n<div class=\"sticky opacity-0 group-hover\/copy:opacity-100 group-focus-within\/copy:opacity-100 top-2 py-2 h-12 w-0 float-right\">\n<div class=\"absolute right-0 h-8 px-2 items-center inline-flex z-10\"><\/div>\n<\/div>\n<div class=\"overflow-x-auto\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">sparse[entity.index()] \u2192 dense \u4e2d\u7684\u4e0b\u6807 i\r\ndense[i]               \u2192 entity\uff08\u9a8c\u8bc1 generation\uff09\r\ncomponents[i]          \u2192 \u7ec4\u4ef6\u6570\u636e\uff08\u4e0e dense \u5e73\u884c\uff09<\/pre>\n<p>&nbsp;<\/p>\n<\/div>\n<\/div>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\"><strong>\u6dfb\u52a0\u7ec4\u4ef6<\/strong>\uff1a\u5728 dense \u548c components \u672b\u5c3e\u5404 push \u4e00\u4e2a\uff0c\u5728 sparse \u91cc\u8bb0\u5f55\u4e0b\u6807\uff0cO(1)\u3002<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\"><strong>\u5220\u9664\u7ec4\u4ef6<\/strong>\uff1a\u628a\u8981\u5220\u7684\u90a3\u4e2a\u548c\u6700\u540e\u4e00\u4e2a\u4ea4\u6362\uff0c\u7136\u540e pop_back\uff0cO(1)\uff0c<strong>dense \u59cb\u7ec8\u8fde\u7eed<\/strong>\u3002\u8fd9\u662f\u5173\u952e\u2014\u2014System \u8fed\u4ee3\u65f6\u6c38\u8fdc\u5728\u8fde\u7eed\u5185\u5b58\u4e0a\u8dd1\uff0cCPU prefetch \u975e\u5e38\u53cb\u597d\u3002<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\"><strong>\u67e5\u627e\u7ec4\u4ef6<\/strong>\uff1a<code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">sparse[e.index()]<\/code>\u00a0\u76f4\u63a5\u62ff\u5230\u4e0b\u6807\uff0cO(1)\uff0c\u4f46\u8981\u591a\u4e00\u6b65 generation \u6821\u9a8c\uff0c\u9632\u6b62\u60ac\u7a7a\u5f15\u7528\u3002<\/p>\n<hr class=\"border-border-200 border-t-0.5 my-3 mx-1.5\" \/>\n<h3 class=\"text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold\" dir=\"ltr\">Entity \u7684 Generation \u673a\u5236<\/h3>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">\u4ee3\u7801\u91cc\u6f14\u793a\u4e86\u8fd9\u4e2a\uff1a<\/p>\n<div class=\"relative group\/copy bg-bg-000\/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100\" tabindex=\"0\" role=\"group\" aria-label=\"Code\">\n<div class=\"sticky opacity-0 group-hover\/copy:opacity-100 group-focus-within\/copy:opacity-100 top-2 py-2 h-12 w-0 float-right\">\n<div class=\"absolute right-0 h-8 px-2 items-center inline-flex z-10\"><\/div>\n<\/div>\n<div class=\"overflow-x-auto\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">\u521b\u5efa enemy \u2192 id=1, index=1, gen=0\r\n\u9500\u6bc1 enemy \u2192 gen[1]++ = 1\uff0cindex=1 \u653e\u56de free list\r\n\u521b\u5efa new_entity \u2192 index=1 \u88ab\u56de\u6536\uff0c\u4f46 gen=1\r\nold_enemy_ref(index=1, gen=0) \u2192 \u67e5 gen[1]=1\uff0c\u4e0d\u5339\u914d \u2192 invalid<\/pre>\n<p>&nbsp;<\/p>\n<\/div>\n<\/div>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">\u8fd9\u6837\u6301\u6709\u65e7 Entity ID \u7684\u4efb\u4f55\u5730\u65b9\u90fd\u4e0d\u4f1a\u8bef\u64cd\u4f5c\u5230\u65b0 entity\uff0c\u662f\u6e38\u620f\u91cc\u9632\u6b62&#8221;use after free&#8221;\u7684\u6807\u51c6\u624b\u6bb5\u3002<\/p>\n<hr class=\"border-border-200 border-t-0.5 my-3 mx-1.5\" \/>\n<h3 class=\"text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold\" dir=\"ltr\">View \u7684\u5de5\u4f5c\u65b9\u5f0f<\/h3>\n<div class=\"relative group\/copy bg-bg-000\/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100\" tabindex=\"0\" role=\"group\" aria-label=\"cpp code\">\n<div class=\"sticky opacity-0 group-hover\/copy:opacity-100 group-focus-within\/copy:opacity-100 top-2 py-2 h-12 w-0 float-right\">\n<div class=\"absolute right-0 h-8 px-2 items-center inline-flex z-10\"><\/div>\n<\/div>\n<div class=\"text-text-500 font-small p-3.5 pb-0\">cpp<\/div>\n<div class=\"overflow-x-auto\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">reg.view&lt;Position, Velocity&gt;([dt](Entity e, Position&amp; pos, Velocity&amp; vel) {\r\n    pos.x += vel.vx * dt;\r\n});<\/pre>\n<p>&nbsp;<\/p>\n<\/div>\n<\/div>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">\u5b9e\u73b0\u5f88\u7b80\u5355\uff1a\u62ff\u7b2c\u4e00\u4e2a\u7ec4\u4ef6\u7c7b\u578b\uff08Position\uff09\u7684 pool \u4f5c\u4e3a\u8fed\u4ee3\u4e3b\u6c60\uff0c\u904d\u5386\u5176\u4e2d\u6bcf\u4e2a entity\uff0c\u68c0\u67e5\u5b83\u662f\u5426\u4e5f\u6709 Velocity\u3002\u6709\u7684\u8bdd\u5c31\u8c03\u7528\u56de\u8c03\u3002<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">\u751f\u4ea7\u7ea7\u5b9e\u73b0\uff08EnTT \u771f\u6b63\u505a\u7684\uff09\u4f1a\u9009\u00a0<strong>size \u6700\u5c0f\u7684 pool<\/strong>\u00a0\u4f5c\u4e3a\u4e3b\u6c60\uff0c\u56e0\u4e3a\u6700\u5c0f\u7684 pool \u8fc7\u6ee4\u6548\u7387\u6700\u9ad8\u3002\u8fd9\u4e2a\u6846\u67b6\u91cc\u7528\u7684\u662f\u7b80\u5316\u7248\uff0c\u591f\u7406\u89e3\u539f\u7406\u3002<\/p>\n<hr class=\"border-border-200 border-t-0.5 my-3 mx-1.5\" \/>\n<h3 class=\"text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold\" dir=\"ltr\">\u548c EnTT \u7684\u5dee\u8ddd\u5728\u54ea\u91cc<\/h3>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">\u4f60\u770b\u5b8c\u8fd9\u4e2a\u6846\u67b6\u4e4b\u540e\uff0c\u5982\u679c\u9762\u8bd5\u88ab\u95ee\u5230&#8221;\u4f60\u4e86\u89e3 EnTT \u5417&#8221;\uff0c\u53ef\u4ee5\u8fd9\u6837\u8bf4\uff1a<\/p>\n<p class=\"font-claude-response-body break-words whitespace-normal\" dir=\"ltr\">EnTT \u5728\u8fd9\u4e2a\u57fa\u7840\u4e0a\u989d\u5916\u505a\u4e86\uff1a<strong>Archetype group<\/strong>\u00a0\u4f18\u5316\uff08\u628a\u9891\u7e41\u5171\u73b0\u7684\u7ec4\u4ef6\u5f3a\u5236\u5b58\u5230\u540c\u4e00\u8fde\u7eed\u5185\u5b58\u5757\uff0c\u8fed\u4ee3\u65f6\u771f\u6b63\u505a\u5230\u96f6 cache miss\uff09\uff1b<strong>Signal \/ observer<\/strong>\u00a0\u7cfb\u7edf\uff08\u7ec4\u4ef6\u589e\u5220\u65f6\u89e6\u53d1\u56de\u8c03\uff09\uff1b\u4ee5\u53ca\u975e\u5e38\u6781\u81f4\u7684\u6a21\u677f\u5143\u7f16\u7a0b\uff0c\u6574\u4e2a\u5e93 header-only\uff0c\u96f6\u8fd0\u884c\u65f6\u5f00\u9500\u3002\u6838\u5fc3\u601d\u8def\u548c\u8fd9\u4e2a\u6846\u67b6\u662f\u4e00\u6837\u7684\u3002<\/p>\n<\/div>\n<\/div>\n<\/div>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">\/**\r\n * ECS Framework \u2014 \u53c2\u8003 EnTT \u8bbe\u8ba1\u601d\u8def\u7684\u7b80\u5316\u5b9e\u73b0\r\n *\r\n * \u6838\u5fc3\u6570\u636e\u7ed3\u6784\uff1aSparse Set\r\n *   - sparse[entity]  \u2192 dense \u6570\u7ec4\u4e2d\u7684\u4e0b\u6807\uff08O(1) \u67e5\u627e\uff09\r\n *   - dense[]         \u2192 \u8fde\u7eed\u5b58\u50a8\u7684 entity \u5217\u8868\uff08O(1) \u8fed\u4ee3\uff0ccache \u53cb\u597d\uff09\r\n *   - components[]    \u2192 \u4e0e dense \u5e73\u884c\u7684\u7ec4\u4ef6\u6570\u636e\u6570\u7ec4\r\n *\r\n * \u4e09\u4e2a\u6838\u5fc3\u6982\u5ff5\uff1a\r\n *   Entity   \u2014 \u53ea\u662f\u4e00\u4e2a ID\uff0832\u4f4d\uff1a\u9ad812\u4f4d generation + \u4f4e20\u4f4d index\uff09\r\n *   Component \u2014 \u7eaf\u6570\u636e\u7ed3\u6784\uff0c\u65e0\u4efb\u4f55\u65b9\u6cd5\r\n *   System   \u2014 \u7eaf\u903b\u8f91\u51fd\u6570\uff0c\u901a\u8fc7 Registry::view() \u6279\u91cf\u5904\u7406\u7ec4\u4ef6\r\n *\/\r\n\r\n#include &lt;vector&gt;\r\n#include &lt;unordered_map&gt;\r\n#include &lt;typeindex&gt;\r\n#include &lt;memory&gt;\r\n#include &lt;functional&gt;\r\n#include &lt;cassert&gt;\r\n#include &lt;cstdint&gt;\r\n#include &lt;iostream&gt;\r\n#include &lt;tuple&gt;\r\n#include &lt;cmath&gt;\r\n\r\n\/\/ ============================================================\r\n\/\/ 1. ENTITY\r\n\/\/ ============================================================\r\n\r\n\/**\r\n * Entity \u662f\u4e00\u4e2a\u8f7b\u91cf ID\u3002\r\n *\r\n * 32\u4f4d\u5e03\u5c40\uff1a[ generation(12\u4f4d) | index(20\u4f4d) ]\r\n *\r\n * generation \u7684\u4f5c\u7528\uff1a\u5f53\u4e00\u4e2a entity \u88ab\u9500\u6bc1\u540e\uff0c\u5176 index \u4f1a\u88ab\u56de\u6536\u7ed9\u65b0\u7684\r\n * entity\uff0c\u4f46 generation \u4f1a +1\u3002\u6301\u6709\u65e7 entity ID \u7684\u4ee3\u7801\u62ff\u53bb\u67e5\u8be2\u65f6\uff0c\r\n * generation \u4e0d\u5339\u914d\uff0cRegistry \u5c31\u80fd\u8bc6\u522b\u8fd9\u662f\u4e00\u4e2a\"\u60ac\u7a7a\u5f15\u7528\"\u3002\r\n *\/\r\nstruct Entity {\r\n    static constexpr uint32_t INDEX_BITS = 20;\r\n    static constexpr uint32_t GEN_BITS   = 12;\r\n    static constexpr uint32_t INDEX_MASK = (1u &lt;&lt; INDEX_BITS) - 1;\r\n    static constexpr uint32_t INVALID_ID = UINT32_MAX;\r\n\r\n    uint32_t id = INVALID_ID;\r\n\r\n    uint32_t index()      const { return id &amp; INDEX_MASK; }\r\n    uint32_t generation() const { return id &gt;&gt; INDEX_BITS; }\r\n    bool     valid()      const { return id != INVALID_ID; }\r\n\r\n    bool operator==(const Entity&amp; o) const { return id == o.id; }\r\n    bool operator!=(const Entity&amp; o) const { return id != o.id; }\r\n\r\n    static Entity make(uint32_t index, uint32_t gen) {\r\n        return {(gen &lt;&lt; INDEX_BITS) | (index &amp; INDEX_MASK)};\r\n    }\r\n    static Entity null() { return {INVALID_ID}; }\r\n};\r\n\r\n\/\/ ============================================================\r\n\/\/ 2. SPARSE SET\uff08\u6838\u5fc3\u6570\u636e\u7ed3\u6784\uff09\r\n\/\/ ============================================================\r\n\r\n\/**\r\n * SparseSet&lt;T&gt; \u540c\u65f6\u6ee1\u8db3\u4e24\u4e2a\u770b\u4f3c\u77db\u76fe\u7684\u9700\u6c42\uff1a\r\n *   - O(1) \u6309 entity \u67e5\u8be2\u67d0\u4e2a\u7ec4\u4ef6\u662f\u5426\u5b58\u5728\r\n *   - \u7ec4\u4ef6\u6570\u636e\u5728\u5185\u5b58\u4e2d\u8fde\u7eed\u5b58\u50a8\uff0c\u8fed\u4ee3\u65f6 cache \u53cb\u597d\r\n *\r\n * \u5b9e\u73b0\u65b9\u5f0f\uff1a\r\n *   sparse:      indexed by entity.index()\uff0c\u5b58 dense \u4e2d\u7684\u4f4d\u7f6e\r\n *   dense:       \u8fde\u7eed\u6570\u7ec4\uff0c\u5b58 entity \u5217\u8868\r\n *   components:  \u4e0e dense \u5e73\u884c\uff0c\u5b58\u5b9e\u9645\u7ec4\u4ef6\u6570\u636e\r\n *\r\n *   Entity e \u2192 sparse[e.index()] = i \u2192 components[i]\r\n *\/\r\ntemplate&lt;typename T&gt;\r\nclass SparseSet {\r\n    static constexpr uint32_t EMPTY = UINT32_MAX;\r\n\r\n    std::vector&lt;uint32_t&gt; sparse_;     \/\/ [entity index] \u2192 dense \u4e0b\u6807\r\n    std::vector&lt;Entity&gt;   dense_;      \/\/ \u8fde\u7eed\u5b58\u50a8\u7684 entity \u5217\u8868\r\n    std::vector&lt;T&gt;        components_; \/\/ \u4e0e dense_ \u5e73\u884c\u7684\u7ec4\u4ef6\u6570\u636e\r\n\r\n    void ensure_sparse(uint32_t idx) {\r\n        if (idx &gt;= sparse_.size())\r\n            sparse_.resize(idx + 1, EMPTY);\r\n    }\r\n\r\npublic:\r\n    \/\/ \u6dfb\u52a0\u7ec4\u4ef6\uff08entity e \u4e0d\u80fd\u5df2\u6709\u6b64\u7ec4\u4ef6\uff09\r\n    template&lt;typename... Args&gt;\r\n    T&amp; emplace(Entity e, Args&amp;&amp;... args) {\r\n        assert(!contains(e) &amp;&amp; \"entity already has this component\");\r\n        ensure_sparse(e.index());\r\n\r\n        sparse_[e.index()] = static_cast&lt;uint32_t&gt;(dense_.size());\r\n        dense_.push_back(e);\r\n        components_.emplace_back(std::forward&lt;Args&gt;(args)...);\r\n        return components_.back();\r\n    }\r\n\r\n    \/\/ \u79fb\u9664\u7ec4\u4ef6\uff08swap-and-pop\uff0cO(1)\uff0c\u4fdd\u6301 dense \u8fde\u7eed\uff09\r\n    void remove(Entity e) {\r\n        assert(contains(e) &amp;&amp; \"entity does not have this component\");\r\n\r\n        uint32_t idx      = sparse_[e.index()];\r\n        uint32_t last_idx = static_cast&lt;uint32_t&gt;(dense_.size()) - 1;\r\n\r\n        if (idx != last_idx) {\r\n            \/\/ \u628a\u6700\u540e\u4e00\u4e2a\u5143\u7d20\u79fb\u5230\u88ab\u5220\u9664\u7684\u4f4d\u7f6e\r\n            dense_[idx]      = dense_[last_idx];\r\n            components_[idx] = std::move(components_[last_idx]);\r\n            sparse_[dense_[idx].index()] = idx;  \/\/ \u66f4\u65b0\u88ab\u79fb\u52a8\u5143\u7d20\u7684 sparse \u6307\u9488\r\n        }\r\n\r\n        sparse_[e.index()] = EMPTY;\r\n        dense_.pop_back();\r\n        components_.pop_back();\r\n    }\r\n\r\n    \/\/ \u83b7\u53d6\u7ec4\u4ef6\u5f15\u7528\r\n    T&amp; get(Entity e) {\r\n        assert(contains(e));\r\n        return components_[sparse_[e.index()]];\r\n    }\r\n    const T&amp; get(Entity e) const {\r\n        assert(contains(e));\r\n        return components_[sparse_[e.index()]];\r\n    }\r\n\r\n    bool contains(Entity e) const {\r\n        return e.index() &lt; sparse_.size()\r\n            &amp;&amp; sparse_[e.index()] != EMPTY\r\n            &amp;&amp; dense_[sparse_[e.index()]] == e;  \/\/ generation \u6821\u9a8c\r\n    }\r\n\r\n    \/\/ \u8fed\u4ee3\u5668\uff1a\u76f4\u63a5\u66b4\u9732\u8fde\u7eed\u7684 dense\/component \u6570\u7ec4\r\n    size_t size() const { return dense_.size(); }\r\n\r\n    const std::vector&lt;Entity&gt;&amp; entities()   const { return dense_; }\r\n          std::vector&lt;T&gt;&amp;      components()       { return components_; }\r\n    const std::vector&lt;T&gt;&amp;      components() const { return components_; }\r\n};\r\n\r\n\/\/ ============================================================\r\n\/\/ 3. COMPONENT POOL \u57fa\u7c7b\uff08\u7528\u4e8e\u7c7b\u578b\u64e6\u9664\u5b58\u5165 Registry\uff09\r\n\/\/ ============================================================\r\n\r\nstruct IPool {\r\n    virtual ~IPool() = default;\r\n    virtual void remove(Entity e) = 0;\r\n    virtual bool contains(Entity e) const = 0;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct Pool : IPool, SparseSet&lt;T&gt; {\r\n    void remove(Entity e)         override { SparseSet&lt;T&gt;::remove(e); }\r\n    bool contains(Entity e) const override { return SparseSet&lt;T&gt;::contains(e); }\r\n};\r\n\r\n\/\/ ============================================================\r\n\/\/ 4. REGISTRY\uff08\u4e2d\u592e\u7ba1\u7406\u5668\uff09\r\n\/\/ ============================================================\r\n\r\nclass Registry {\r\n    \/\/ Entity \u7ba1\u7406\r\n    std::vector&lt;uint32_t&gt; generations_;  \/\/ \u6bcf\u4e2a index \u5bf9\u5e94\u7684\u5f53\u524d generation\r\n    std::vector&lt;uint32_t&gt; free_indices_; \/\/ \u53ef\u56de\u6536\u7684 index \u5217\u8868\r\n\r\n    \/\/ \u7ec4\u4ef6\u6c60\uff1atype_index \u2192 pool\r\n    std::unordered_map&lt;std::type_index, std::unique_ptr&lt;IPool&gt;&gt; pools_;\r\n\r\n    template&lt;typename T&gt;\r\n    Pool&lt;T&gt;&amp; get_or_create_pool() {\r\n        auto key = std::type_index(typeid(T));\r\n        auto it  = pools_.find(key);\r\n        if (it == pools_.end()) {\r\n            auto [inserted_it, ok] = pools_.emplace(key, std::make_unique&lt;Pool&lt;T&gt;&gt;());\r\n            return static_cast&lt;Pool&lt;T&gt;&amp;&gt;(*inserted_it-&gt;second);\r\n        }\r\n        return static_cast&lt;Pool&lt;T&gt;&amp;&gt;(*it-&gt;second);\r\n    }\r\n\r\n    template&lt;typename T&gt;\r\n    Pool&lt;T&gt;* find_pool() {\r\n        auto it = pools_.find(std::type_index(typeid(T)));\r\n        return it != pools_.end() ? static_cast&lt;Pool&lt;T&gt;*&gt;(it-&gt;second.get()) : nullptr;\r\n    }\r\n\r\n    template&lt;typename T&gt;\r\n    const Pool&lt;T&gt;* find_pool() const {\r\n        auto it = pools_.find(std::type_index(typeid(T)));\r\n        return it != pools_.end() ? static_cast&lt;const Pool&lt;T&gt;*&gt;(it-&gt;second.get()) : nullptr;\r\n    }\r\n\r\npublic:\r\n    \/\/ \u2500\u2500 Entity \u751f\u547d\u5468\u671f \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n    Entity create() {\r\n        if (!free_indices_.empty()) {\r\n            uint32_t idx = free_indices_.back();\r\n            free_indices_.pop_back();\r\n            return Entity::make(idx, generations_[idx]);\r\n        }\r\n        uint32_t idx = static_cast&lt;uint32_t&gt;(generations_.size());\r\n        generations_.push_back(0);\r\n        return Entity::make(idx, 0);\r\n    }\r\n\r\n    void destroy(Entity e) {\r\n        assert(valid(e) &amp;&amp; \"destroying invalid entity\");\r\n        \/\/ \u79fb\u9664\u6240\u6709\u7ec4\u4ef6\r\n        for (auto&amp; [type, pool] : pools_)\r\n            if (pool-&gt;contains(e)) pool-&gt;remove(e);\r\n        \/\/ \u56de\u6536 index\uff0cgeneration +1 \u4f7f\u65e7\u5f15\u7528\u5931\u6548\r\n        ++generations_[e.index()];\r\n        free_indices_.push_back(e.index());\r\n    }\r\n\r\n    bool valid(Entity e) const {\r\n        return e.index() &lt; generations_.size()\r\n            &amp;&amp; generations_[e.index()] == e.generation();\r\n    }\r\n\r\n    \/\/ \u2500\u2500 \u7ec4\u4ef6\u64cd\u4f5c \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n    template&lt;typename T, typename... Args&gt;\r\n    T&amp; emplace(Entity e, Args&amp;&amp;... args) {\r\n        return get_or_create_pool&lt;T&gt;().emplace(e, std::forward&lt;Args&gt;(args)...);\r\n    }\r\n\r\n    template&lt;typename T&gt;\r\n    void remove(Entity e) {\r\n        auto* pool = find_pool&lt;T&gt;();\r\n        assert(pool &amp;&amp; pool-&gt;contains(e));\r\n        pool-&gt;remove(e);\r\n    }\r\n\r\n    template&lt;typename T&gt;\r\n    T&amp; get(Entity e) {\r\n        auto* pool = find_pool&lt;T&gt;();\r\n        assert(pool &amp;&amp; \"component pool does not exist\");\r\n        return pool-&gt;get(e);\r\n    }\r\n\r\n    template&lt;typename T&gt;\r\n    bool has(Entity e) const {\r\n        const auto* pool = find_pool&lt;T&gt;();\r\n        return pool &amp;&amp; pool-&gt;contains(e);\r\n    }\r\n\r\n    \/\/ \u2500\u2500 View\uff1a\u904d\u5386\u540c\u65f6\u62e5\u6709\u591a\u4e2a\u7ec4\u4ef6\u7684 entity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n    \/**\r\n     * view&lt;A, B, C&gt;() \u7684\u7b56\u7565\uff1a\r\n     *   \u9009\u51fa\u7ec4\u4ef6\u6570\u91cf\u6700\u5c11\u7684\u90a3\u4e2a Pool \u4f5c\u4e3a\"\u4e3b\u6c60\"\u6765\u8fed\u4ee3\uff0c\r\n     *   \u5bf9\u5176\u4e2d\u6bcf\u4e2a entity \u68c0\u67e5\u662f\u5426\u4e5f\u6709\u5176\u4ed6\u7ec4\u4ef6\u3002\r\n     *   \u8fd9\u6837\u8fed\u4ee3\u91cf\u6700\u5c0f\uff0c\u540c\u65f6\u4e3b\u6c60\u7684\u7ec4\u4ef6\u6570\u636e\u662f\u8fde\u7eed\u8bbf\u95ee\u7684\u3002\r\n     *\/\r\n    \/\/ view \u63a5\u53d7\u4efb\u610f\u53ef\u8c03\u7528\u5bf9\u8c61\uff08lambda\u3001\u51fd\u6570\u6307\u9488\u7b49\uff09\r\n    \/\/ Ts \u663e\u5f0f\u6307\u5b9a\u8981\u67e5\u8be2\u7684\u7ec4\u4ef6\u7c7b\u578b\uff0cFunc \u7531\u7f16\u8bd1\u5668\u63a8\u5bfc\r\n    template&lt;typename... Ts, typename Func&gt;\r\n    void view(Func&amp;&amp; func) {\r\n        \/\/ \u4ee5 Ts \u4e2d\u7b2c\u4e00\u4e2a\u7c7b\u578b\u7684 pool \u4f5c\u4e3a\u8fed\u4ee3\u4e3b\u6c60\uff08\u7b80\u5316\u7b56\u7565\uff09\r\n        \/\/ \u751f\u4ea7\u7ea7\u5b9e\u73b0\u4f1a\u9009\u62e9 size \u6700\u5c0f\u7684 pool \u4ee5\u51cf\u5c11\u8fed\u4ee3\u91cf\r\n        auto* primary_pool = find_pool&lt;std::tuple_element_t&lt;0, std::tuple&lt;Ts...&gt;&gt;&gt;();\r\n        if (!primary_pool) return;\r\n\r\n        \/\/ \u8fed\u4ee3\u4e3b\u6c60\uff0c\u5bf9\u6bcf\u4e2a entity \u68c0\u67e5\u662f\u5426\u540c\u65f6\u62e5\u6709\u6240\u6709 Ts \u7ec4\u4ef6\r\n        \/\/ \u6ce8\u610f\uff1a\u5012\u5e8f\u8fed\u4ee3\u4ee5\u5b89\u5168\u5e94\u5bf9\u56de\u8c03\u5185\u90e8\u5220\u9664\u7ec4\u4ef6\u7684\u60c5\u51b5\r\n        for (size_t i = primary_pool-&gt;entities().size(); i-- &gt; 0; ) {\r\n            Entity e = primary_pool-&gt;entities()[i];\r\n            if ((has&lt;Ts&gt;(e) &amp;&amp; ...))\r\n                func(e, get&lt;Ts&gt;(e)...);\r\n        }\r\n    }\r\n};\r\n\r\n\/\/ ============================================================\r\n\/\/ 5. COMPONENTS\uff08\u7eaf\u6570\u636e\uff0c\u65e0\u65b9\u6cd5\uff09\r\n\/\/ ============================================================\r\n\r\nstruct Position {\r\n    float x, y, z;\r\n};\r\n\r\nstruct Velocity {\r\n    float vx, vy, vz;\r\n};\r\n\r\nstruct Health {\r\n    int current;\r\n    int max;\r\n};\r\n\r\nstruct Renderable {\r\n    uint32_t mesh_id;\r\n    uint32_t material_id;\r\n};\r\n\r\nstruct AI {\r\n    enum class State { Idle, Chase, Attack, Flee } state = State::Idle;\r\n    Entity target = Entity::null();\r\n    float  detection_range = 10.f;\r\n};\r\n\r\n\/\/ ============================================================\r\n\/\/ 6. SYSTEMS\uff08\u7eaf\u903b\u8f91\u51fd\u6570\uff09\r\n\/\/ ============================================================\r\n\r\nnamespace Systems {\r\n\r\n    \/\/ \u79fb\u52a8\u7cfb\u7edf\uff1a\u5904\u7406\u6240\u6709\u6709 Position + Velocity \u7684 entity\r\n    void movement(Registry&amp; reg, float dt) {\r\n        reg.view&lt;Position, Velocity&gt;([dt](Entity e, Position&amp; pos, Velocity&amp; vel) {\r\n            pos.x += vel.vx * dt;\r\n            pos.y += vel.vy * dt;\r\n            pos.z += vel.vz * dt;\r\n        });\r\n    }\r\n\r\n    \/\/ \u751f\u547d\u503c\u6062\u590d\u7cfb\u7edf\uff1a\u6bcf\u5e27\u7ed9\u6240\u6709\u6709 Health \u7684 entity \u56de 1 \u70b9\u8840\r\n    void health_regen(Registry&amp; reg) {\r\n        reg.view&lt;Health&gt;([](Entity e, Health&amp; hp) {\r\n            if (hp.current &lt; hp.max)\r\n                hp.current = std::min(hp.current + 1, hp.max);\r\n        });\r\n    }\r\n\r\n    \/\/ AI \u7cfb\u7edf\uff1a\u5904\u7406\u6709 AI + Position \u7684 entity\r\n    void ai_update(Registry&amp; reg) {\r\n        reg.view&lt;AI, Position&gt;([&amp;](Entity e, AI&amp; ai, Position&amp; pos) {\r\n            if (!reg.valid(ai.target)) {\r\n                ai.state  = AI::State::Idle;\r\n                ai.target = Entity::null();\r\n                return;\r\n            }\r\n            \/\/ \u8ba1\u7b97\u4e0e\u76ee\u6807\u7684\u8ddd\u79bb\uff08\u7b80\u5316\uff09\r\n            auto&amp; target_pos = reg.get&lt;Position&gt;(ai.target);\r\n            float dx = target_pos.x - pos.x;\r\n            float dz = target_pos.z - pos.z;\r\n            float dist = std::sqrt(dx * dx + dz * dz);\r\n\r\n            if (dist &lt; 1.5f)\r\n                ai.state = AI::State::Attack;\r\n            else if (dist &lt; ai.detection_range)\r\n                ai.state = AI::State::Chase;\r\n            else\r\n                ai.state = AI::State::Idle;\r\n        });\r\n    }\r\n\r\n}\r\n\r\n\/\/ ============================================================\r\n\/\/ 7. \u4e3b\u7a0b\u5e8f\u6f14\u793a\r\n\/\/ ============================================================\r\n\r\nint main() {\r\n    Registry reg;\r\n\r\n    \/\/ \u2500\u2500 \u521b\u5efa\u5b9e\u4f53\uff1a\u73a9\u5bb6 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    Entity player = reg.create();\r\n    reg.emplace&lt;Position&gt;(player, 0.f, 0.f, 0.f);\r\n    reg.emplace&lt;Velocity&gt;(player, 1.f, 0.f, 0.5f);\r\n    reg.emplace&lt;Health&gt;  (player, 100, 100);\r\n    reg.emplace&lt;Renderable&gt;(player, 1001u, 2001u);\r\n    std::cout &lt;&lt; \"Player entity: \" &lt;&lt; player.id &lt;&lt; \"\\n\";\r\n\r\n    \/\/ \u2500\u2500 \u521b\u5efa\u5b9e\u4f53\uff1a\u654c\u4eba \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    Entity enemy = reg.create();\r\n    reg.emplace&lt;Position&gt;  (enemy, 5.f, 0.f, 5.f);\r\n    reg.emplace&lt;Velocity&gt;  (enemy, 0.f, 0.f, 0.f);\r\n    reg.emplace&lt;Health&gt;    (enemy, 50, 50);\r\n    reg.emplace&lt;Renderable&gt;(enemy, 1002u, 2002u);\r\n    reg.emplace&lt;AI&gt;        (enemy);\r\n    reg.get&lt;AI&gt;(enemy).target = player;  \/\/ \u7784\u51c6\u73a9\u5bb6\r\n    std::cout &lt;&lt; \"Enemy  entity: \" &lt;&lt; enemy.id &lt;&lt; \"\\n\\n\";\r\n\r\n    \/\/ \u2500\u2500 \u521b\u5efa\u5b9e\u4f53\uff1a\u9759\u6001\u573a\u666f\u7269\u4ef6\uff08\u53ea\u6709 Position + Renderable\uff09\u2500\u2500\r\n    Entity tree = reg.create();\r\n    reg.emplace&lt;Position&gt;  (tree, 10.f, 0.f, 10.f);\r\n    reg.emplace&lt;Renderable&gt;(tree, 3001u, 4001u);\r\n\r\n    \/\/ \u2500\u2500 \u6a21\u62df 3 \u5e27 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    for (int frame = 0; frame &lt; 3; ++frame) {\r\n        float dt = 0.016f; \/\/ 16ms per frame\r\n\r\n        Systems::movement   (reg, dt);\r\n        Systems::health_regen(reg);\r\n        Systems::ai_update  (reg);\r\n\r\n        auto&amp; ppos = reg.get&lt;Position&gt;(player);\r\n        auto&amp; eai  = reg.get&lt;AI&gt;(enemy);\r\n\r\n        const char* ai_state_str[] = {\"Idle\", \"Chase\", \"Attack\", \"Flee\"};\r\n        std::cout &lt;&lt; \"Frame \" &lt;&lt; frame + 1 &lt;&lt; \" | \"\r\n                  &lt;&lt; \"Player(\" &lt;&lt; ppos.x &lt;&lt; \", \" &lt;&lt; ppos.z &lt;&lt; \") | \"\r\n                  &lt;&lt; \"Enemy AI: \" &lt;&lt; ai_state_str[static_cast&lt;int&gt;(eai.state)]\r\n                  &lt;&lt; \"\\n\";\r\n    }\r\n\r\n    \/\/ \u2500\u2500 ECS \u7ec4\u5408\u7075\u6d3b\u6027\u6f14\u793a \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    std::cout &lt;&lt; \"\\n[\u6f14\u793a] \u7ed9\u6811\u6dfb\u52a0 Velocity\uff0c\u5b83\u5c31\u80fd\u88ab MovementSystem \u5904\u7406\\n\";\r\n    reg.emplace&lt;Velocity&gt;(tree, 0.f, 0.f, 0.1f);\r\n    Systems::movement(reg, 0.016f);\r\n    auto&amp; tpos = reg.get&lt;Position&gt;(tree);\r\n    std::cout &lt;&lt; \"Tree new Z: \" &lt;&lt; tpos.z &lt;&lt; \"\\n\";\r\n\r\n    std::cout &lt;&lt; \"\\n[\u6f14\u793a] \u9500\u6bc1 enemy\uff0c\u65e7\u5f15\u7528\u53d8\u65e0\u6548\\n\";\r\n    Entity old_enemy_ref = enemy;  \/\/ \u4fdd\u5b58\u65e7\u5f15\u7528\r\n    reg.destroy(enemy);\r\n    std::cout &lt;&lt; \"old enemy ref valid: \" &lt;&lt; std::boolalpha &lt;&lt; reg.valid(old_enemy_ref) &lt;&lt; \"\\n\";\r\n\r\n    \/\/ \u56de\u6536\u7684 index \u521b\u5efa\u65b0 entity\uff0cgeneration \u4e0d\u540c\uff0c\u65e7\u5f15\u7528\u4ecd\u7136\u65e0\u6548\r\n    Entity new_entity = reg.create();\r\n    std::cout &lt;&lt; \"new entity index: \"      &lt;&lt; new_entity.index()      &lt;&lt; \"\\n\";\r\n    std::cout &lt;&lt; \"new entity generation: \" &lt;&lt; new_entity.generation() &lt;&lt; \"\\n\";\r\n    std::cout &lt;&lt; \"old ref still invalid: \" &lt;&lt; !reg.valid(old_enemy_ref) &lt;&lt; \"\\n\";\r\n\r\n    return 0;\r\n}\r\n\r\n\/*\r\n\u9884\u671f\u8f93\u51fa\uff1a\r\nPlayer entity: 0\r\nEnemy  entity: 1048576   \u2190 index=1, gen=0 \u2192 id = (0&lt;&lt;20)|1\r\n\r\nFrame 1 | Player(0.016, 0.008) | Enemy AI: Idle\r\nFrame 2 | Player(0.032, 0.016) | Enemy AI: Idle\r\nFrame 3 | Player(0.048, 0.024) | Enemy AI: Idle\r\n\r\n[\u6f14\u793a] \u7ed9\u6811\u6dfb\u52a0 Velocity\uff0c\u5b83\u5c31\u80fd\u88ab MovementSystem \u5904\u7406\r\nTree new Z: 10.0016\r\n\r\n[\u6f14\u793a] \u9500\u6bc1 enemy\uff0c\u65e7\u5f15\u7528\u53d8\u65e0\u6548\r\nold enemy ref valid: false\r\nnew entity index: 1        \u2190 index \u88ab\u56de\u6536\r\nnew entity generation: 1   \u2190 generation +1\uff0c\u65e7\u5f15\u7528\u5931\u6548\r\nold ref still invalid: true\r\n*\/\r\n<\/pre>\n<p>&nbsp;<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>\u4ee3\u7801\u7f16\u8bd1\u8fd0\u884c\u6b63\u5e38\uff0c\u53d1\u7ed9\u4f60\u4e86\u3002\u6574\u4f53\u8bbe\u8ba1\u5206\u56db<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_container_layout":"default_layout","colormag_page_sidebar_layout":"default_layout","footnotes":""},"categories":[24],"tags":[],"class_list":["post-7536","post","type-post","status-publish","format-standard","hentry","category-value_docs"],"_links":{"self":[{"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/posts\/7536","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/comments?post=7536"}],"version-history":[{"count":0,"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/posts\/7536\/revisions"}],"wp:attachment":[{"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/media?parent=7536"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/categories?post=7536"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/i007.cc\/wordpress\/wp-json\/wp\/v2\/tags?post=7536"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}