[{"content":"概要 個人サーバーを運用していると、「ちょっとした Webhook を受け取りたい」「定期的にシステム状態をロギングしたい」という場面がよくあります。\nDocker やヘビーなフレームワークを入れると、それだけで数十〜数百メガバイトのメモリを消費してしまいますが、Python 3.12 と FastAPI + Uvicorn を native systemd サービスとして動かすことで、わずか 50MB 程度の常駐メモリで本格的な API 基盤が手に入ります。\n主な特徴 自動ドキュメント生成: /api/docs にアクセスするだけで OpenAPI (Swagger UI) によるテストが可能。 Webhook トリガー: 外部からの POST リクエストを受け取り、ブログの自動再コンパイルや通知を実行。 systemd 監視: MemoryMax=80M のリソース制限を設け、メモリ暴走を確実に防止。 今後も少しずつ便利なエンドポイントを追加していく予定です。\n","permalink":"https://wuyukwi.duckdns.org/posts/second-post/","summary":"個人用サーバーでタスク自動化や Webhook 連携を行うためのミニマムな設計手法について。","title":"FastAPI による軽量マイクロサービスと Webhook 自動化"},{"content":"はじめに このブログは、Oracle Cloud の 1GB メモリ（物理 RAM 954MB、Swap 2GB）という制約の中で稼働しています。\nバックグラウンドではすでに NapCatQQ が約 350MB のメモリを消費しているため、残されたメモリは約 500MB 程度。この厳しいリソース条件下で、快適で高速、そしてメンテナンスが容易な個人ブログと API サービスを共存させるために、極限まで軽量化されたアーキテクチャを選定しました。\nアーキテクチャの選定 コンポーネント 採用技術 メモリ消費量 特徴 Web サーバー / リバースプロキシ Nginx ~2.5 MB 圧倒的な軽快さと安定性。HTTPS 自動リダイレクトとキャッシュ制御を担当。 ブログエンジン Hugo + PaperMod 0 MB（静的生成） 実行時常駐プロセスなし。Markdown を 300ms で HTML に静的コンパイル。 API サーバー Python 3.12 + FastAPI ~50 MB Swagger 自動ドキュメント、非同期タスク、Webhook 連携を最小構成で提供。 合計常駐メモリ 約 53 MB 空きメモリ 550MB 以上を余裕で確保。 コード例 FastAPI 側のヘルスチェック機能の実装例です。psutil を使ってリアルタイムなサーバーリソースを取得しています。\nimport psutil from fastapi import FastAPI app = FastAPI(title=\u0026#34;Micro API\u0026#34;, root_path=\u0026#34;/api\u0026#34;) @app.get(\u0026#34;/health\u0026#34;) def health_check(): vm = psutil.virtual_memory() return { \u0026#34;status\u0026#34;: \u0026#34;healthy\u0026#34;, \u0026#34;memory_used_mb\u0026#34;: round(vm.used / (1024 * 1024), 1), \u0026#34;available_mb\u0026#34;: round(vm.available / (1024 * 1024), 1), } おわりに 日常の記事執筆は、Markdown ファイルを配置してビルドスクリプトを実行するだけで完了します。 リソースを無駄遣いせず、シンプルで堅牢な環境を作ることができました。\n","permalink":"https://wuyukwi.duckdns.org/posts/first-post/","summary":"Oracle Cloud の 1GB メモリ VPS 上で、NapCatQQ を稼働させながら、常駐メモリほぼゼロの Hugo + PaperMod と FastAPI を共存させたミニマム構成の紹介。","title":"1GB の VPS に極小ブログと API サーバーを構築した記録"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nプログラマーを数年やっていると、おそらく全員が一度は同じことを考える。\n「これ、他のプロジェクトでも使える形にしたい」\n最初はただ動けばよかったコードが、だんだん気持ち悪く見えてくる。\nコピペも増える。責務も混ざる。依存も汚くなる。\nすると自然に、\n共通化したい 抽象化したい 再利用可能にしたい という欲求が出てくる。\nこれは悪いことではない。むしろ健全だと思う。\n“設計”を意識し始めた証拠だから。\nただ、この感覚はかなり危ない。\n一歩間違えると、\n簡単に「過剰設計」に転がり始める。\n「将来使うかもしれない」\nこの一言が、\n過剰設計の始まりになることはかなり多い。\n今回は、自分がゲーム開発をやる中で感じた、\nなぜ人は汎用化したくなるのか なぜそれが失敗しやすいのか 実際どう考えるとちょうどいいのか について書いてみる。\n汎用化したくなるのは、実はかなり自然なこと 例えば、最初の頃のコードってかなり局所的になる。\nif (isBoss) { hpBar.color = Color.red; } みたいな、「今必要だから書いたコード」が大量にある。\nでも経験を積むと、\nこういうコードを見るだけで“変更コスト”を想像するようになる。\n「これ別のボス出たらどうするんだろう」\n「色変えるたびに if 増えるの？」\n「そもそも UI が敵情報知ってるのおかしくない？」\nみたいなことを考え始める。\nつまり汎用化欲求の正体って、単なる意識高い設計趣味ではなく、\n“変更に弱いコードへの違和感”\nなんだと思う。\nだからある程度コードを書いた人ほど、自然と抽象化に向かう。\n実際、汎用化にはかなり強いメリットがある これは間違いない。\n1. 「依存」に敏感になる 再利用を意識すると、まずベタ結合が気になり始める。\n直接参照 Singleton ベタ依存 MonoBehaviour 神クラス Inspector 配線地獄 みたいなものを避けたくなる。\n結果として、\n責務分離 Interface 化 データ駆動 疎結合 みたいな方向へ進む。\nこういう意識がないと、\n人はかなり簡単に「今だけ動けばいいコード」を書く。\n実際、「他でも使えるようにしよう」という意識がなければ、人はかなり簡単に“その場しのぎ”を書く。\n2. 開発速度がある地点から急に変わる ある程度経験を積んだ人の開発速度が異常に速く見えることがある。\nあれ、単純にタイピングが速いわけじゃない。\n大抵の場合、\n「すでに持ってる」\nから速い。\n例えば、\nログ基盤 Pool 非同期 Utility UI 拡張 Editor 拡張 アニメーション補助 アセットロード周り みたいなものを、自分用に持っている。\nつまり毎回ゼロから作っていない。\nゲーム開発って結局、「毎回違うゲームを作ってるようで、毎回似た問題と戦ってる」ので、この差はかなり大きい。\nでも、ここから簡単に壊れ始める 問題はここ。\n汎用化思考って、あるラインを超えると急激にコストが増える。\n過剰設計の厄介なところは、\n作ってる本人がかなり気持ちよくなれること。\n綺麗に抽象化できると、\n「理解した感」が強く出る。\n設計してる時って楽しい。\n綺麗に抽象化できると「理解した感」がある。\nでも実際の開発で価値があるのは、\n“綺麗な構造”\nではなく、\n“予定通りに完成すること”\nだったりする。\nここを見失うと危ない。\n過剰設計が始まる瞬間 自分の経験上、一番危険なのは、\n「将来使うかもしれない」\nが設計理由になった瞬間。\n例えば本来必要なのが、\n「画像を横にスライドする UI」\nだけだったとする。\nでも設計好きな人間はここで止まらない。\n無限ループ対応 汎用アニメーション 任意方向 動的追加削除 仮想化 非同期ロード カスタムイベント 設定データ分離 みたいな方向へ行き始める。\nで、数日後に完成したものを見ると、\n今必要な機能、2割くらいしか使ってない\nみたいなことが普通に起きる。\nこれ、本当に危険なのは「頑張ってる感」があること。\n本人はサボってるわけじゃない。むしろ真面目。\nでも成果物として見ると、必要以上に重い。\nしかも「汎用化したコード」は意外と弱い ここもかなり重要。\n多くの人が最初誤解するけど、\n汎用コード = 強いコード\nではない。\nむしろ逆で、\n汎用コードほど、前提条件に弱い\nことが多い。\n例えば、自作の UI 基盤を作ったとする。\nでも次プロジェクトで、\nUI 構造が違う 運用フローが違う デザイナーの触り方が違う 演出思想が違う となった瞬間、急に噛み合わなくなる。\nするとどうなるか。\n頑張って「汎用化」したはずなのに、\n結局プロジェクト専用改造が大量発生する\nそして最終的に、\n「これ最初から作った方が早くない？」\nになる。\nこれはかなりある。\n個人的に一番重要だと思ってること ここ数年でかなり考え方が変わった。\n昔は「最初から綺麗に設計する」が正義だと思ってた。\nでも今はむしろ逆で、\n“最初は多少汚くていい”\nと思ってる。\nもちろん無秩序でいいわけじゃない。\nでも、最初から完成形を想像しすぎない方がいい。\nなぜなら、ゲーム開発って想像以上に仕様が変わるから。\n最初に考えていた「綺麗な構造」は、大抵途中で崩れる。\n重要なのは、\n「完璧な抽象化」ではなく\n「壊しやすさ」だと思っている。\n壊しやすさ\nなんだと思う。\n「3回目で抽象化する」はかなり合理的 有名な Rule of Three は、本当に実践的。\nhttps://www.ikemo3.com/dic/rule-of-three/#3度目の法則とは\n1回目は普通に作る。\n2回目で違和感を覚える。\n3回目で初めて抽象化する。\nこの順番を飛ばすと、\nかなりの確率で事故る。\nこれ、単なる経験則じゃなくてかなり理にかなってる。\n1回目では、「何が変化するか」が分からない。\nでも3回同じ問題に遭遇すると、\n本当に共通な部分 毎回違う部分 将来変わりやすい部分 が見えてくる。\nつまりその時点で初めて、“適切な抽象化”が可能になる。\n逆に、最初から作る汎用設計って、\nまだ存在していない問題に、\n先回りして答えを作っている\nことが多い。\nだから外れやすい。\n結局、「拡張しやすさ」が一番大事 最近は、\n“全部入り”\nより、\n“後から足しやすい”\nをかなり重視してる。\nつまり、\n責務が分かれている 依存方向が綺麗 置き換えやすい 変更範囲が閉じている みたいな状態。\n最初から全機能を想定するより、\n必要になった時に安全に伸ばせる\n方が、結果的に長生きする。\n特にゲーム開発は「完成前に仕様が変わる」のが普通なので、この思想の方がかなり現実的だと思う。\nおわりに プログラマーって、経験を積むほど「綺麗にしたい欲」が強くなる。\nそれ自体は悪くない。\nむしろ成長してる証拠だと思う。\nでも開発では、\n納期 仕様変更 チーム事情 運用コスト みたいな、“現実”も常に存在する。\nだから結局大事なのは、\n「未来のための設計」と\n「今作るべきもの」\nのバランスなんだと思う。\n最近は自分も、\n「未来を作り込みすぎない」\nことをかなり意識している。\n必要になったら伸ばす。\nでも、\n必要になる前には作り込まない。\n最近は、それくらいがちょうどいいと思っている。\n多分それくらいが、実際の開発ではちょうどいい。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-010f3a8e5af665/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/010f3a8e5af665\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eプログラマーを数年やっていると、おそらく全員が一度は同じことを考える。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e「これ、他のプロジェクトでも使える形にしたい」\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e最初はただ動けばよかったコードが、だんだん気持ち悪く見えてくる。\u003cbr\u003e\nコピペも増える。責務も混ざる。依存も汚くなる。\u003c/p\u003e\n\u003cp\u003eすると自然に、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e共通化したい\u003c/li\u003e\n\u003cli\u003e抽象化したい\u003c/li\u003e\n\u003cli\u003e再利用可能にしたい\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eという欲求が出てくる。\u003c/p\u003e\n\u003cp\u003eこれは悪いことではない。むしろ健全だと思う。\u003cbr\u003e\n“設計”を意識し始めた証拠だから。\u003c/p\u003e\n\u003cp\u003eただ、この感覚はかなり危ない。\u003c/p\u003e\n\u003cp\u003e一歩間違えると、\u003cbr\u003e\n簡単に「過剰設計」に転がり始める。\u003c/p\u003e\n\u003cp\u003e「将来使うかもしれない」\u003c/p\u003e\n\u003cp\u003eこの一言が、\u003cbr\u003e\n過剰設計の始まりになることはかなり多い。\u003c/p\u003e\n\u003cp\u003e今回は、自分がゲーム開発をやる中で感じた、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eなぜ人は汎用化したくなるのか\u003c/li\u003e\n\u003cli\u003eなぜそれが失敗しやすいのか\u003c/li\u003e\n\u003cli\u003e実際どう考えるとちょうどいいのか\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eについて書いてみる。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"汎用化したくなるのは実はかなり自然なこと\"\u003e汎用化したくなるのは、実はかなり自然なこと\u003c/h2\u003e\n\u003cp\u003e例えば、最初の頃のコードってかなり局所的になる。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eif (isBoss)\n{\n    hpBar.color = Color.red;\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eみたいな、「今必要だから書いたコード」が大量にある。\u003c/p\u003e\n\u003cp\u003eでも経験を積むと、\u003cbr\u003e\nこういうコードを見るだけで“変更コスト”を想像するようになる。\u003c/p\u003e\n\u003cp\u003e「これ別のボス出たらどうするんだろう」\u003cbr\u003e\n「色変えるたびに if 増えるの？」\u003cbr\u003e\n「そもそも UI が敵情報知ってるのおかしくない？」\u003c/p\u003e\n\u003cp\u003eみたいなことを考え始める。\u003c/p\u003e\n\u003cp\u003eつまり汎用化欲求の正体って、単なる意識高い設計趣味ではなく、\u003c/p\u003e\n\u003cp\u003e“変更に弱いコードへの違和感”\u003c/p\u003e\n\u003cp\u003eなんだと思う。\u003c/p\u003e\n\u003cp\u003eだからある程度コードを書いた人ほど、自然と抽象化に向かう。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"実際汎用化にはかなり強いメリットがある\"\u003e実際、汎用化にはかなり強いメリットがある\u003c/h2\u003e\n\u003cp\u003eこれは間違いない。\u003c/p\u003e\n\u003ch3 id=\"1-依存に敏感になる\"\u003e1. 「依存」に敏感になる\u003c/h3\u003e\n\u003cp\u003e再利用を意識すると、まずベタ結合が気になり始める。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e直接参照\u003c/li\u003e\n\u003cli\u003eSingleton ベタ依存\u003c/li\u003e\n\u003cli\u003eMonoBehaviour 神クラス\u003c/li\u003e\n\u003cli\u003eInspector 配線地獄\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eみたいなものを避けたくなる。\u003c/p\u003e\n\u003cp\u003e結果として、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e責務分離\u003c/li\u003e\n\u003cli\u003eInterface 化\u003c/li\u003e\n\u003cli\u003eデータ駆動\u003c/li\u003e\n\u003cli\u003e疎結合\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eみたいな方向へ進む。\u003c/p\u003e","title":"☝️ 「汎用化したい」という衝動と、開発の現実"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに 前回は、Job System + Burst を使って\n10万オブジェクトの回転処理（46ms → 3.5ms） まで最適化しました。\nただしその時点でも、フレーム全体としてはまだ重く、\nボトルネックはレンダリング側に残っていました。\nなぜJob Systemだけでは不十分だったのか 前回の時点で回転処理自体はかなり軽くなっていましたが、\nそれでもフレーム全体としては大きな負荷が残っていました。\n原因は、描画周りの処理です。\nたとえロジック（Job）が高速でも、メインスレッド側では依然として\n10万個の MeshRenderer を個別に処理する必要があります。\n具体的には以下のような処理です：\nCulling（可視判定） Bounding Volume（バウンディング更新） RenderQueue（描画順の整理） これらがすべて「オブジェクト単位」で実行されるため、\n結果として大きなCPU負荷になっていました。\nこの問題に対するアプローチが、Entities Graphicsです。\n目標：\n→ GameObjectベースのオブジェクトをEntityに変換する\n仕組み：\n→ 変換後は、これらの処理を Entities Graphics が内部的に管理し、\nBatchRendererGroup を利用して描画処理をまとめて実行します。\nその結果：\n数万〜数十万単位のオブジェクトでも、\nごく少数のバッチとして扱えるようになります。\n■ 結果 今回は DOTS（Entities + Entities Graphics）に移行した結果：\nフレーム時間：約120ms → 約9ms 110FPS前後で安定 ほぼ別物レベルまで改善しています。\n■ 比較 指標 改造前（GameObject） 改造後（Pure DOTS） 改善 Batches ~120,000 74 約1486倍削減 SetPass Calls 56 26 約2.1倍改善 Tris / Verts 不安定 安定 描画の一貫性向上 Frame Time 120ms（8FPS） 9ms（110FPS） 約13倍高速化 ■ Profilerを見ると何が起きているか Profilerを確認すると：\nJobカテゴリに大量のワーカースレッド（Worker 0〜18） CPU全コアがほぼ均等に使用されている つまり：\n完全に並列化された状態で処理が走っている\n■ DOTSのアプローチ DOTSでは考え方が変わります：\n個別オブジェクト管理\nデータをまとめて処理（データ指向）\n■ イメージ 従来：\n→ 10万の兵士がバラバラに動く DOTS：\n→ 1つの訓練された部隊として一括処理 ■ 実装 ■ データ定義 + Baker using UnityEngine; using Unity.Entities; using Unity.Mathematics; // Entityに持たせる設定データ（純粋なデータ構造） public struct SpawnerConfig : IComponentData { public Entity Prefab; public float3 SpawnPosition; } // 各エンティティの回転軸 public struct RotationAxis : IComponentData { public float3 Value; } public class SpawnerAuthoring : MonoBehaviour { public GameObject prefab; // Bakerは「GameObject → Entity」変換時に実行される class Baker : Baker\u0026lt;SpawnerAuthoring\u0026gt; { public override void Bake(SpawnerAuthoring authoring) { var entity = GetEntity(TransformUsageFlags.None); AddComponent(entity, new SpawnerConfig { // PrefabもEntityとして取得（重要） Prefab = GetEntity(authoring.prefab, TransformUsageFlags.Dynamic), SpawnPosition = authoring.transform.position }); } } } ポイント MonoBehaviour → Authoring Baker → Entity変換の入口 IComponentData → 純粋なデータ ロジックとデータを完全に分離するのがDOTSの基本思想\n■ Entity生成（ISystem） using Unity.Entities; using Unity.Transforms; using Unity.Mathematics; using Unity.Collections; using Unity.Burst; [BurstCompile] public partial struct SpawnSystem : ISystem { [BurstCompile] readonly public void OnUpdate(ref SystemState state) { // Singletonから設定を取得 if (!SystemAPI.TryGetSingleton\u0026lt;SpawnerConfig\u0026gt;(out var config)) return; // 一度だけ実行 state.Enabled = false; int count = 100000; // Entityを一括生成（ここがGameObjectとの大きな違い） var entities = state.EntityManager.Instantiate(config.Prefab, count, Allocator.Temp); var rand = new Unity.Mathematics.Random(123); foreach (var entity in entities) { // 球内ランダム配置（前回と同じ見た目を維持） float3 dir = rand.NextFloat3Direction(); float radius = 50f * math.pow(rand.NextFloat(), 1f / 3f); float3 pos = config.SpawnPosition + (dir * radius); state.EntityManager.SetComponentData(entity, LocalTransform.FromPosition(pos)); // 回転軸を付与（完全にデータとして保持） state.EntityManager.AddComponentData(entity, new RotationAxis { Value = rand.NextFloat3Direction() }); } } } ここが重要 一括生成 Instantiate(\u0026hellip;, count)\nGameObjectのループ生成とは別物（圧倒的に軽い）\n■ 回転処理（ISystem） using Unity.Burst; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; [BurstCompile] public partial struct RotateSystem : ISystem { [BurstCompile] readonly public void OnUpdate(ref SystemState state) { float speed = math.radians(100f); float dt = SystemAPI.Time.DeltaTime; // Componentをまとめて取得して並列処理 foreach (var (transform, axis) in SystemAPI.Query\u0026lt;RefRW\u0026lt;LocalTransform\u0026gt;, RefRO\u0026lt;RotationAxis\u0026gt;\u0026gt;()) { // クォータニオン計算（Burst最適化される） quaternion delta = quaternion.AxisAngle(axis.ValueRO.Value, speed * dt); // Transformではなく「構造体」を直接更新 transform.ValueRW.Rotation = math.mul(math.normalize(delta), transform.ValueRO.Rotation); } } } DOTSで速くなる理由 ① データが連続している（キャッシュ効率） LocalTransform は構造体 メモリ上に連続配置 CPUキャッシュに乗りやすい\n② 分岐が少ない GameObject：状態がバラバラ DOTS：同じデータ構造 SIMD最適化が効く\n③ バッチ処理 従来：10万回ループ + 個別処理 DOTS：まとめて処理 ここが一番大きい差\n④ Entities Graphics Culling DrawCall データ同期 すべてバッチ化\n■ まとめ Job Systemだけでは「計算」は速くなるが\n→ 描画側がボトルネックになる DOTSでは\n→ データ構造ごと変えることで根本解決 ■ 最終的な理解 今回一番大きかったのはこれです：\n「処理を速くする」のではなく\n「処理の形を変える」\n■ おわりに 正直ここまで差が出るとは思っていませんでした。\n単純な最適化というより、\n設計そのものを変えるとここまで変わる\nというのが一番の収穫でした。\n■ 追記：IJobEntity + ScheduleParallel への置き換え 公式ドキュメントを確認すると、\nComponent の取得は SystemState を通して扱うのが推奨されているようです。\nこれは、SystemがどのComponentにアクセスしているかを正しく追跡するためで、\nstate.Dependency を通じた Job の依存関係管理に重要な役割を持っています。\nこの方針に合わせて、回転処理も IJobEntity + ScheduleParallel を使う形に書き換えました：\nusing Unity.Burst; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; [BurstCompile] public partial struct RotateSystem : ISystem { [BurstCompile] readonly public void OnUpdate(ref SystemState state) { var rotateJob = new RotateJob2 { dt = SystemAPI.Time.DeltaTime, speed = math.radians(100f) }; // Jobを並列実行しつつ、依存関係を自動管理 state.Dependency = rotateJob.ScheduleParallel(state.Dependency); } } [BurstCompile] public partial struct RotateJob2 : IJobEntity { public float dt; public float speed; // ref = 書き込み、in = 読み取り専用 readonly public void Execute(ref LocalTransform transform, in RotationAxis axis) { quaternion delta = quaternion.AxisAngle(axis.Value, speed * dt); transform.Rotation = math.mul(math.normalize(delta), transform.Rotation); } } この書き方にすることで：\nSystemがアクセスするComponentが明示的にトラッキングされる Job間の依存関係が自動的に解決される よりDOTSの設計思想に沿った形になる 結果として、フレーム時間はさらにわずかに改善し、\n最終的には 約8ms前後 で安定するようになりました。\nパフォーマンスの差自体は大きくないものの、\nDOTSでは「書き方そのもの」がパフォーマンスに影響する\nという点が特に印象的でした。\n■ なぜ SystemAPI.Query より速くなったのか 今回の改善で重要なのは、APIの違いというよりも\n「実行方式の違い」です。\nSystemAPI.Query を使った foreach は、\nECSのデータ構造を利用しているものの、\n実行自体はメインスレッド上で逐次処理されます。\n一方で IJobEntity + ScheduleParallel は：\nデータをChunk単位で分割 ワーカースレッドに分散 並列実行 という形で処理されるため、\nCPUの全コアを活用することができます。\nつまり：\nSystemAPI.Query は「高速なforループ」、\nIJobEntity は「並列処理前提の仕組み」\nという違いになります。\n■ 補足：DeltaTimeの取得方法について SystemAPI.Time.DeltaTime と state.World.Time.DeltaTime は\nどちらも同じ値を取得できますが、\nDOTSの推奨としては SystemAPI.Time を使う形になります。\nSystemAPI 経由で取得することで、\nBurstコンパイル時に最適化されやすく、\n余計なアクセスコストを避けられる可能性があります。\n今回の検証でも、SystemAPI.Time を使った方が安定して高速でした。\nなぜ SystemAPI の方が高速なのか SystemAPI.Time が高速に動作する理由は、\n単にアクセスが短いからではなく、\nBurstコンパイル時に最適化されやすい形になっているためです。\nSystemAPI 経由のアクセスは、\nSystemの実行コンテキストに紐づいた「純粋なデータ」として扱われるため、\nインライン展開（inline） 定数伝播（constant propagation） SIMD最適化 といった最適化が効きやすくなります。\n一方で state.World を経由する場合は、\n内部的にマネージドオブジェクトへの参照を含むため、\nBurstから見ると「外部状態に依存する可能性のあるアクセス」として扱われます。\nその結果：\n毎回値を取得するコードが残る 最適化が制限される といった違いが生まれます。\n■ おまけ：ちょっとした可視化 せっかくなので、回転だけだと少し味気なかったため、\n各エンティティに色を持たせてみました。\n回転軸をそのまま色に変換することで、\nどの方向に回っているのかが視覚的に分かるようになります。\nsin関数と時間を使って色が変化するようにしています\n単純な処理ですが、こうして見ると\nDOTSで大量オブジェクトを動かす面白さが少し伝わる気がします。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-8577fbf1074840/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/8577fbf1074840\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://zenn.dev/wuyukwi/articles/88800f31f667f3\"\u003e前回\u003c/a\u003eは、Job System + Burst を使って\u003cbr\u003e\n\u003cstrong\u003e10万オブジェクトの回転処理（46ms → 3.5ms）\u003c/strong\u003e まで最適化しました。\u003c/p\u003e\n\u003cp\u003eただしその時点でも、フレーム全体としてはまだ重く、\u003cbr\u003e\nボトルネックはレンダリング側に残っていました。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"なぜjob-systemだけでは不十分だったのか\"\u003eなぜJob Systemだけでは不十分だったのか\u003c/h2\u003e\n\u003cp\u003e前回の時点で回転処理自体はかなり軽くなっていましたが、\u003cbr\u003e\nそれでもフレーム全体としては大きな負荷が残っていました。\u003c/p\u003e\n\u003cp\u003e原因は、描画周りの処理です。\u003c/p\u003e\n\u003cp\u003eたとえロジック（Job）が高速でも、メインスレッド側では依然として\u003cbr\u003e\n10万個の \u003ccode\u003eMeshRenderer\u003c/code\u003e を個別に処理する必要があります。\u003c/p\u003e\n\u003cp\u003e具体的には以下のような処理です：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eCulling（可視判定）\u003c/li\u003e\n\u003cli\u003eBounding Volume（バウンディング更新）\u003c/li\u003e\n\u003cli\u003eRenderQueue（描画順の整理）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eこれらがすべて「オブジェクト単位」で実行されるため、\u003cbr\u003e\n結果として大きなCPU負荷になっていました。\u003c/p\u003e\n\u003cp\u003eこの問題に対するアプローチが、Entities Graphicsです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e目標：\u003cbr\u003e\n→ GameObjectベースのオブジェクトをEntityに変換する\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e仕組み：\u003cbr\u003e\n→ 変換後は、これらの処理を \u003ccode\u003eEntities Graphics\u003c/code\u003e が内部的に管理し、\u003cbr\u003e\n\u003ccode\u003eBatchRendererGroup\u003c/code\u003e を利用して描画処理をまとめて実行します。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eその結果：\u003c/p\u003e\n\u003cp\u003e数万〜数十万単位のオブジェクトでも、\u003cbr\u003e\nごく少数のバッチとして扱えるようになります。\u003c/p\u003e\n\u003ch2 id=\"-結果\"\u003e■ 結果\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://static.zenn.studio/user-upload/8eac221c088d-20260401.gif\"\u003e\u003cbr\u003e\n今回は DOTS（Entities + Entities Graphics）に移行した結果：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eフレーム時間：約120ms → 約9ms\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e110FPS前後で安定\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eほぼ別物レベルまで改善しています。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"-比較\"\u003e■ 比較\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e指標\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e改造前（GameObject）\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e改造後（Pure DOTS）\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e改善\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eBatches\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e~120,000\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e74\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e約1486倍削減\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eSetPass Calls\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e56\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e26\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e約2.1倍改善\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eTris / Verts\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e不安定\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e安定\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e描画の一貫性向上\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eFrame Time\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e120ms（8FPS）\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e9ms（110FPS）\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e約13倍高速化\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003chr\u003e\n\u003ch2 id=\"-profilerを見ると何が起きているか\"\u003e■ Profilerを見ると何が起きているか\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://static.zenn.studio/user-upload/561808254336-20260401.png\"\u003e\u003cbr\u003e\n\u003cimg loading=\"lazy\" src=\"https://static.zenn.studio/user-upload/d2021f89924c-20260401.png\"\u003e\u003cbr\u003e\nProfilerを確認すると：\u003c/p\u003e","title":"🧊 【Unity】10万オブジェクトを回転させたらどこがボトルネックになるのか（後編：ISystemで一桁msへ）"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\n最近、DOTS（Data-Oriented Tech Stack）が実際どのくらい効果があるのか気になっていたので、\n簡単な検証を兼ねて触ってみることにしました。\nせっかくなので「大量のオブジェクトを動かした場合にどこがボトルネックになるのか？」も含めて、\n段階的に確認していこうと思います。\nこの記事ではまず、LateUpdateでの回転処理のボトルネックと、\nJob Systemによる最適化の効果にフォーカスします。\nレンダリング周り（CullingやDrawCall）については、次回以降で触れる予定です。\n■ 検証環境 CPU：i7-12700K GPU：RTX 3070 Unity：URPの新規プロジェクト オブジェクト数：10万（Cube） ■ テスト内容 シンプルに「10万個のCubeを生成して回転させるだけ」です。\nBoxColliderは削除\nランダム位置に配置\n各オブジェクトにランダムな回転軸\nvoid Start() { var root = new GameObject(\u0026ldquo;CubeRoot\u0026rdquo;).transform;\nfor (int i = 0; i \u0026lt; spawnCount; i++) { var go = Instantiate(prefab, Random.insideUnitSphere * 50f, Quaternion.identity, root); transforms.Add(go.transform); } }\nvoid LateUpdate() { float dt = Time.deltaTime;\nfor (int i = 0; i \u0026lt; transforms.Count; i++) { transforms[i].Rotate(rotationAxes[i], 100f * dt); } }\n■ 結果（最適化前） フレーム時間は約 120ms 前後。\n特に目立つのが：\nLateUpdate：約46ms ■ ボトルネックの正体 これはかなりシンプルで、\n10万回のTransform操作をメインスレッドで直列実行している\nというのが原因です。\nUnityの Transform は軽く見えて実はそこそこ重い処理なので、それを10万回ループすると普通に詰みます。\n■ レンダリング側の状況（軽く触れる） Profilerを見ると以下もかなり重いです：\nRenderLoop.Draw（約44ms） 主に Culling（可視判定） Transformとの同期コスト Gfx.WaitForGfxCommands（約42ms） Render Thread待ち（DrawCall地獄） ざっくり言うと：\n10万個のMeshRendererを1個ずつチェック＆送信してる\nこのあたりは次回の記事でちゃんとやります。\n■ レンダリングを止めてみる 全オブジェクトを SetActive(false) にすると：\nフレーム時間：約65ms 内訳：ほぼ LateUpdate（46ms） つまり、\n回転処理だけでも既に重い\nことが確認できます。\n■ Job Systemで最適化してみる ここからが本題です。\n「単純なループなら並列化できるよね」ということで、\nIJobParallelForTransform + Burst を使います。\n■ 実装 // Transform を Job で扱うための専用コンテナ private TransformAccessArray transformAccessArray; // 各オブジェクトごとの回転軸（Job から参照するため NativeArray を使用） private NativeArray\u0026lt;Vector3\u0026gt; rotationAxes; // スケジュールした Job のハンドル（依存関係管理用） private JobHandle jobHandle; void Start() { // NativeArray はマネージド外のメモリに確保されるため、明示的な解放が必要 rotationAxes = new NativeArray\u0026lt;Vector3\u0026gt;(spawnCount, Allocator.Persistent); for (int i = 0; i \u0026lt; spawnCount; i++) { rotationAxes[i] = Random.onUnitSphere; } // Transform 配列を Job 用にラップ // IJobParallelForTransform ではこれを通して Transform にアクセスする transformAccessArray = new TransformAccessArray(transformArray); } void Update() { // Job に渡すデータをセット var job = new RotateJob { DeltaTime = Time.deltaTime, Axes = rotationAxes }; // Job をスケジュール（非同期で実行開始） // この時点ではまだ処理は完了していない点に注意 jobHandle = job.Schedule(transformAccessArray); } void LateUpdate() { // Job の完了を待機 // ここで Complete することで、このフレーム内で結果を反映させる // ※ Complete を呼ばない場合： // - 次フレームまで処理が持ち越される可能性がある // - Transform の更新タイミングが不定になる // ※ LateUpdate で待つ理由： // - Update 直後に待つと並列化のメリットが薄れる // - できるだけギリギリまで Job を走らせておきたい jobHandle.Complete(); } void OnDestroy() { // Native メモリは自動解放されないため必ず Dispose する if (rotationAxes.IsCreated) rotationAxes.Dispose(); if (transformAccessArray.isCreated) transformAccessArray.Dispose(); } // Burst によってネイティブコードに最適化される [BurstCompile] struct RotateJob : IJobParallelForTransform { public float DeltaTime; // Job 内では読み取り専用データとして扱う（安全性＆最適化のため） [ReadOnly] public NativeArray\u0026lt;Vector3\u0026gt; Axes; public void Execute(int index, TransformAccess transform) { // Transform.Rotate は使用できないため、 // localRotation を直接更新する必要がある Quaternion currentRot = transform.localRotation; Quaternion delta = Quaternion.AngleAxis(100f * DeltaTime, Axes[index]); transform.localRotation = delta * currentRot; } } ■ 結果（最適化後） LateUpdate（相当処理）：約3.5ms ■ 何が起きたのか ポイントは2つです：\n① 並列化 10万回の処理 → 複数コアで分散 ② Burstコンパイル SIMD最適化 ネイティブコード化 キャッシュ効率改善 結果として：\n単なるforループが「CPUフル活用の並列処理」に変わった\nというのが本質です。\n■ まとめ 10万オブジェクトの回転は\n→ メインスレッドでは完全にボトルネックになる Job System + Burstで\n→ 46ms → 3.5msまで削減 ■ ここまでやってみて 今回の検証はあくまで「回転処理」に絞ったものでしたが、\n実際にはまだレンダリング側の負荷がかなり大きく残っています。\n一方で、このあと試しにDOTS（ISystem）ベースに置き換えてみたところ、\nフレーム時間自体はさらに大きく改善し、最終的には一桁ms台まで落とすことができました。\nただし、このあたりは少し話が長くなるので、\n詳細は次回の記事でまとめようと思います。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-88800f31f667f3/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/88800f31f667f3\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e最近、DOTS（Data-Oriented Tech Stack）が実際どのくらい効果があるのか気になっていたので、\u003cbr\u003e\n簡単な検証を兼ねて触ってみることにしました。\u003c/p\u003e\n\u003cp\u003eせっかくなので「大量のオブジェクトを動かした場合にどこがボトルネックになるのか？」も含めて、\u003cbr\u003e\n段階的に確認していこうと思います。\u003c/p\u003e\n\u003cp\u003eこの記事ではまず、LateUpdateでの回転処理のボトルネックと、\u003cbr\u003e\nJob Systemによる最適化の効果にフォーカスします。\u003c/p\u003e\n\u003cp\u003eレンダリング周り（CullingやDrawCall）については、次回以降で触れる予定です。\u003c/p\u003e\n\u003ch2 id=\"-検証環境\"\u003e■ 検証環境\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eCPU：i7-12700K\u003c/li\u003e\n\u003cli\u003eGPU：RTX 3070\u003c/li\u003e\n\u003cli\u003eUnity：URPの新規プロジェクト\u003c/li\u003e\n\u003cli\u003eオブジェクト数：10万（Cube）\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"-テスト内容\"\u003e■ テスト内容\u003c/h2\u003e\n\u003cp\u003eシンプルに「10万個のCubeを生成して回転させるだけ」です。\u003cbr\u003e\n\u003cimg loading=\"lazy\" src=\"https://static.zenn.studio/user-upload/96aecb2d5f9f-20260331.gif\"\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eBoxColliderは削除\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eランダム位置に配置\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e各オブジェクトにランダムな回転軸\u003c/p\u003e\n\u003cp\u003evoid Start()\n{\nvar root = new GameObject(\u0026ldquo;CubeRoot\u0026rdquo;).transform;\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efor (int i = 0; i \u0026lt; spawnCount; i++)\n{\n    var go = Instantiate(prefab, Random.insideUnitSphere * 50f, Quaternion.identity, root);\n    transforms.Add(go.transform);\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e}\u003c/p\u003e\n\u003cp\u003evoid LateUpdate()\n{\nfloat dt = Time.deltaTime;\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efor (int i = 0; i \u0026lt; transforms.Count; i++)\n{\n    transforms[i].Rotate(rotationAxes[i], 100f * dt);\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e}\u003c/p\u003e","title":"🧊 【Unity】10万オブジェクトを回転させたらどこがボトルネックになるのか（前編：LateUpdateとJob System）"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに ゲームエンジンの言語の話になると、よくこんな対比が出てきます。\nUnity は C#。\nUnreal は C++ と Blueprint。\nこの違い、ただの好みではありません。\nエンジンの設計思想そのものに関わっています。\nC# が優れているとか、C++ が古いとか、そういう単純な話ではない。\n両者はそもそも目指している開発体験が違うのです。\nUnity が辿った言語統一の歴史 今でこそ「Unity といえば C#」という印象がありますが、最初からそうだったわけではありません。\n初期の Unity では、開発者のバックグラウンドに合わせて複数の言語を選択できるのが特徴でした。\n具体的には次の3つです。\nC#\nUnityScript （JavaScript 風の独自言語）\nBoo （Python 風の言語）\n当時の Unity は「スクリプト言語の自由度」を売りの一つにしており、Web 開発者は UnityScript、Python 系に慣れている人は Boo、という具合に選べるようになっていました。\nしかし、この方針は長くは続きませんでした。\nUnity 2017 頃から段階的に非推奨となり、\nUnity 2019.1 を最後に UnityScript と Boo は完全に削除 されます。\n結果として、Unity のスクリプト言語は C# のみ に統一されました。\n参考\nhttps://discussions.unity.com/t/which-release-removed-support-unityscript-and-boo/1624299\nなぜこうなったのか。理由はいくつかあります。\nまず、複数言語の維持コストです。\nエンジン側はコンパイラ、ツールチェーン、デバッグ環境などをすべてサポートし続ける必要があります。\nさらに重要なのは、UnityScript と Boo の実態です。\nこれらは見た目こそ別言語でしたが、内部では Mono 上で動作するスクリプトでした。\nつまり、\n実行環境は共通（Mono）\n文法だけが違う\nという状態だったのです。\nその結果、コミュニティでも徐々に C# が主流になっていきました。\nエコシステム（ライブラリ・チュートリアル・Asset Store など）も C# 中心に発展していきます。\nこうして Unity は最終的に、\n「C# を第一言語とするエンジン」\nという現在の形に落ち着きました。\nUnity が C# を中心に据えた理由 Unity を少し触るとすぐ分かりますが、このエンジンはかなりツール寄りです。\nインスペクターで値を触りながら動作を見る。\nエディタ拡張を書く。\n小さく作ってすぐ動かす。\nこのワークフローと C# の相性は、正直かなりいい。\n実際に C# でツールを書いたことがある人なら分かると思いますが、\nReflection がとにかく便利です。\n例えば Unity では、\n[SerializeField]\n[Range]\nみたいな Attribute を付けるだけで、インスペクターの挙動が変わります。\nこれ、内部では Reflection で型情報を読んでいます。\nつまり Unity の設計は、\nコードにメタデータを書いて、ツール側がそれを読む\nというスタイルです。\nこの仕組みを C++ でやろうとするとかなり面倒になります。\nC# なら比較的素直に書ける。\nそれだけでも、エンジンの開発体験はかなり変わる。\nC# の「ちょうどよさ」 もう一つ大きいのが、言語としての扱いやすさです。\nGC があるのでメモリ管理はかなり楽。\nコンパイルも速い。\n例外も普通に使える。\nC++ でゲームロジックを書いていると、\nビルド待ちの時間にコーヒーをもう一杯入れたくなる瞬間があります。\nUnity のスクリプト開発は、そのストレスがかなり少ない。\nエンジンのコアはネイティブ。\nゲームロジックはマネージド。\nUnity はこの割り切りをかなり早い段階で決めました。\n結果として、C# が事実上の第一言語になったわけです。\nUnreal は最初から C++ の世界だった 一方の Unreal。\nこちらは最初からC++ が中心のエンジン です。\nしかも普通の C++ ではありません。\nUnreal のコードを見ると、こんなマクロが大量に出てきます。\nUCLASS()\nUPROPERTY()\nUFUNCTION()\n最初はただのマクロに見えます。\nでも実際には違う。\nこれらは UnrealHeaderTool（UHT） というツールで解析され、\nエンジンのリフレクション情報が生成されます。\nつまり Unreal は、\nC++ をベースにした独自のメタ言語環境\nと言った方が近い。\nこの構造の上に、\nGC\nシリアライズ\nエディタ連携\nが全部乗っています。\nUObject と GC の深い結びつき Unreal の GC は、普通の GC 言語とは少し違います。\nUPROPERTY を通じて参照関係を追跡し、\nUObject を回収します。\nつまり、\nUObject\nUPROPERTY\nGC\nこの3つは完全に一体です。\nここに C# を持ち込むとどうなるか。\nマネージド GC と UObject GC。\nGC が二重になる。\nオブジェクトの寿命管理はかなり複雑になるでしょう。\nエンジンの根っこをいじる話になります。\nもう一つの問題：ネイティブ境界 C# をエンジンに組み込む場合、必ず出てくる問題があります。\nネイティブ境界です。\nC# から C++ を呼ぶ。\nそのたびに P/Invoke やラッパーを通る。\n普通のアプリなら問題になりません。\nでもゲームエンジンは少し事情が違う。\n毎フレーム、何千回も API を呼びます。\nTransform を読む。\nコンポーネントを取得する。\nActor を生成する。\nこの境界コストは無視できません。\nAAA タイトルを想定した Unreal にとっては、\nかなり気になる部分だったはずです。\nC# からネイティブコードを呼ぶ場合、通常は P/Invoke などの仕組みを使います。\nhttps://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke\nUnreal がスクリプト言語を捨てた理由 実は Unreal にも昔はスクリプト言語がありました。\nUnrealScript です。\nUE3 までは、\nC++\nUnrealScript\nという2層構造でした。\nしかし UE4 で Epic はこの言語を捨てます。\n理由はいくつもありますが、\n一番大きいのはメンテナンスコスト です。\n言語を2つ維持するのは単純に大変です。\nコンパイラ\nデバッガ\nランタイム\nバインディング\n全部必要になります。\nそこで Epic が選んだのが、\nC++ + Blueprint\nという構成でした。\nBlueprint は「別の答え」 Blueprint はスクリプト言語ではありません。\nノードベースのビジュアルスクリプトです。\nただし中身は完全に C++ 側と連携しています。\nUFUNCTION を付けると Blueprint から呼べる。\nUPROPERTY を付けるとエディタに出る。\nつまり Blueprint は\nC++ メタシステムのフロントエンド\nです。\n新しい言語を増やしたわけではない。\nC++ の世界をツールで拡張しただけ。\nこれが Unreal の答えでした。\nそれでも C# を Unreal で使いたい人たち とはいえ、C# を Unreal で書きたいという人はずっといます。\n実際、コミュニティではいくつかのプロジェクトが作られています。\n例えば\nUnrealCLR\nや\nUnrealSharp\nどちらも .NET を Unreal に橋渡しする試みです。\n公式ではありませんが、長く続いているプロジェクトです。\nそれだけ C# の開発体験に魅力がある、ということでもあります。\n結局、言語選択はエンジンの思想 ここまで見ると、構図はわりとシンプルです。\nUnity は\nツールと開発速度を重視したエンジン。\nUnreal は\nネイティブ統合とパフォーマンスを突き詰めたエンジン。\nその結果として、\nUnity は C# を選び、\nUnreal は C++ に残った。\nどちらが正しいという話ではありません。\nエンジンの思想が違えば、\n選ばれる言語も変わる。\nそれだけのことなのです。\n追記：UnrealがC++中心になった「時代」と、Verseが生まれた理由 この記事に多くの反響をいただき、ありがとうございます。\nいくつか補足しておきたい点が出てきたため、追記します。\nまず一点目は、UnrealがC++を中心に設計された「時代背景」についてです。\n初代Unreal Engineが登場したのは1998年。\n当時はまだ .NET も C# もこの世に存在していませんでした。\n今の視点で見ると「なぜC#を採用しなかったのか」という議論になりがちですが、\n当時はゲームエンジン＝ネイティブコード（C++）が絶対的な前提だった時代です。\nUnity（2005年登場）のように、マネージド言語を前提とした設計は、\n当時のゲームエンジンではかなり珍しいアプローチでした。\nこの「出発点」の違いが、現在の両エンジンのアーキテクチャの根幹に、\n今も色濃く残っているのだと感じます。\nそしてもう一点、触れておきたいのが 新言語「Verse」 の存在です。\n「Game Developers Conference 2024（GDC 2024）」などの講演でも触れられていましたが、\nEpicは将来的にUE6世代でVerseをより深く統合していく方針を示しています。\nここで「それなら今からでもC#を採用すればよかったのでは？」という疑問も浮かびますが、\nVerseの設計思想を見ると、その狙いは単なる「スクリプト言語の置き換え」ではないことがわかります。\nVerseが目指している方向性として、\n公開されている講演や資料を見る限りでは、\n特に次のような言語的特徴が強調されているように見えます。\n失敗コンテキスト（failure context）：\nVerseでは処理の成功・失敗を言語レベルで扱う仕組みがあり、\n条件に応じて処理を安全に中断したり、\nゲームロジックの整合性を保ちやすくする設計になっていると説明されています。\n式ベースの言語設計：\nVerseは「式（expression）」を中心に構成された言語であり、\n多くの処理が値を返す式として記述できるよう設計されています。\nこれは従来の命令型言語とはやや異なるスタイルです。\nまた、言語レベルで並行処理を扱える点も、\n特徴の一つとして紹介されています。\nもちろん、これらの処理自体はC#のような汎用言語でも\n実装すること自体は可能です。\nしかしVerseでは、それらをライブラリやフレームワークではなく、\n言語機能として提供すること が特徴だと説明されています。\nそのため、従来のゲームスクリプト言語の延長というよりも、\n関数型や宣言的なアプローチなどを取り入れた、\n新しいプログラミングモデルを志向した言語設計になっているように見えます。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-97b70df23bd792/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/97b70df23bd792\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"はじめに\"\u003eはじめに\u003c/h1\u003e\n\u003cp\u003eゲームエンジンの言語の話になると、よくこんな対比が出てきます。\u003c/p\u003e\n\u003cp\u003eUnity は C#。\u003cbr\u003e\nUnreal は C++ と Blueprint。\u003c/p\u003e\n\u003cp\u003eこの違い、ただの好みではありません。\u003cbr\u003e\nエンジンの設計思想そのものに関わっています。\u003c/p\u003e\n\u003cp\u003eC# が優れているとか、C++ が古いとか、そういう単純な話ではない。\u003cbr\u003e\n両者はそもそも目指している開発体験が違うのです。\u003c/p\u003e\n\u003chr\u003e\n\u003ch1 id=\"unity-が辿った言語統一の歴史\"\u003eUnity が辿った言語統一の歴史\u003c/h1\u003e\n\u003cp\u003e今でこそ「Unity といえば C#」という印象がありますが、最初からそうだったわけではありません。\u003cbr\u003e\n初期の Unity では、開発者のバックグラウンドに合わせて複数の言語を選択できるのが特徴でした。\u003c/p\u003e\n\u003cp\u003e具体的には次の3つです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eC#\u003c/strong\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eUnityScript\u003c/strong\u003e （JavaScript 風の独自言語）\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eBoo\u003c/strong\u003e （Python 風の言語）\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e当時の Unity は「スクリプト言語の自由度」を売りの一つにしており、Web 開発者は UnityScript、Python 系に慣れている人は Boo、という具合に選べるようになっていました。\u003c/p\u003e\n\u003cp\u003eしかし、この方針は長くは続きませんでした。\u003c/p\u003e\n\u003cp\u003eUnity 2017 頃から段階的に非推奨となり、\u003cbr\u003e\n\u003cstrong\u003eUnity 2019.1 を最後に UnityScript と Boo は完全に削除\u003c/strong\u003e されます。\u003c/p\u003e\n\u003cp\u003e結果として、Unity のスクリプト言語は \u003cstrong\u003eC# のみ\u003c/strong\u003e に統一されました。\u003c/p\u003e\n\u003cp\u003e参考\u003cbr\u003e\n\u003ca href=\"https://discussions.unity.com/t/which-release-removed-support-unityscript-and-boo/1624299\"\u003ehttps://discussions.unity.com/t/which-release-removed-support-unityscript-and-boo/1624299\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eなぜこうなったのか。理由はいくつかあります。\u003c/p\u003e\n\u003cp\u003eまず、複数言語の維持コストです。\u003cbr\u003e\nエンジン側はコンパイラ、ツールチェーン、デバッグ環境などをすべてサポートし続ける必要があります。\u003c/p\u003e\n\u003cp\u003eさらに重要なのは、UnityScript と Boo の実態です。\u003cbr\u003e\nこれらは見た目こそ別言語でしたが、内部では Mono 上で動作するスクリプトでした。\u003c/p\u003e","title":"🙌 なぜ Unity は C# を選び、Unreal は選ばなかったのかについての個人の考察"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに Git を使っていて、こんな感覚になったことはありませんか？\nコマンドは打てるけど、何が起きているか分からない\nmerge と rebase の違いを、説明しろと言われると詰まる\nconflict が出ると、なぜ衝突したのか理解できない\n多くの Git 解説記事は、\n「何を打つか」 は丁寧に教えてくれますが、\n「なぜそうなるか」 までは踏み込んでくれません。\nその結果、\nなんとなく使えているが、仕組みはよく分からない\nという状態に陥りがちです。\nこの記事では、コマンドの説明はほぼ行いません。\n代わりに、Git の内部構造をデータ構造として捉え直す ことで、\n「あ、そういうことだったのか」\nと腑に落ちる理解を目指します。\n少しだけ グラフ理論 と ポインタの概念（C 言語） の素養があると、\nかなりスムーズに理解できると思います。\nGit の正体：木構造ではなく DAG（有向非巡回グラフ） 多くの人は、Git の履歴を「木構造」としてイメージしています。\nしかし、これは正確ではありません。\nGit の本質は 有向非巡回グラフです。\nCommit は「ノード」 すべての commit は、グラフ上の ノード です。\n各 commit は、\nファイルのスナップショット\n親 commit への参照（1 個または 2 個）\nを持っています。\n親が 2 個になる瞬間 = merge 通常の commit は親が 1 つですが、\nmerge commit は 親を 2 つ 持ちます。\nこの構造によって、Git の履歴は 木ではなく DAG になります。\nbranch と HEAD の正体は「ただのポインタ」 Git を難しく感じさせている最大の原因は、\nbranch という言葉のイメージです。\n多くの人は branch を「分岐した別ルート」だと考えますが、\n実際にはこうです：\nbranch は単なる commit へのポインタ\nbranch = 名前付きポインタ main -\u0026gt; C5\nこれは、\n「main という名前が commit C5 を指している」\nという意味でしかありません。\nbranch 自体には、履歴も構造も存在しません。\nHEAD は「ポインタへのポインタ」 さらに混乱を招く存在が HEAD です。\nHEAD は commit を直接指しているのではありません。\nHEAD -\u0026gt; branch -\u0026gt; commit\nつまり、\nHEAD は branch を指すポインタ\nです。\nたとえば：\nHEAD -\u0026gt; main -\u0026gt; C5\nこの状態で commit すると、\n新しい commit C6 が生成される\nmain が C6 を指す\nHEAD は引き続き main を指す\nという 極めて単純なポインタ操作 が行われています。\nGit の操作を「ポインタ操作」として捉える ここまで理解できると、Git の各コマンドは\nすべて ポインタの付け替え として説明できます。\ngit init 空の DAG を生成するだけ\nまだ commit は 1 つも存在しない\ngit clone 他人の DAG を丸ごとコピーしてくる\nローカルとリモートの構造は 完全に同一 です。\n唯一の違いは：\nリモート branch には origin/ という接頭辞が付く\nだけです。\ngit add ファイル差分を HEAD に記録する\ncommit ではありません。\nあくまで 「次の commit に含める差分を HEAD に積む」 操作です。\n※ 正確には index は次コミットのスナップショットであり、\ngit add は差分を積むというより、そのスナップショットを更新している。\ngit commit HEAD が保持している差分を使って、新しいノードを生成する\nそして branch をそのノードへ移動させる\ngit reset branch の指す commit を、別のノードへ付け替える\n履歴を書き換えたように見えるのは、\nポインタを過去へ動かしているだけ です。\ngit checkout HEAD が指す branch を切り替える\nつまり、\nHEAD -\u0026gt; main\nHEAD -\u0026gt; dev\nの付け替え操作に過ぎません。\ngit push / pull が分かりにくい理由 push / pull が分かりづらい原因も、\n「構造を見ていない」ことにあります。\n本質はこれだけ ローカルとリモートの branch が、\nどの commit を指しているかの問題\nfast-forward とは？ ローカルとリモートの commit が 同一の履歴線上 にある場合：\nA - B - C - D ↑ remote A - B - C - D - E - F ↑ local このとき push すると：\nE, F をリモートへコピー\nリモート branch を F へ移動\nこれが fast-forward です。\nmerge が発生する条件 両者の履歴が 分岐している場合 ：\nD - E (local) / A - B - C \\ F - G (remote) この場合、\nどちらの先に進むべきか自動で決められない\nため、merge commit を生成 します。\nrebase の正体：枝を「付け替える」だけ rebase は難しく感じられがちですが、\n構造としてはとても単純です。\nD - E (local) / A - B - C \\ F - G (remote) これを rebase すると：\nA - B - C - F - G - D - E\nやっていることは：\n共通祖先以降のローカル側の枝を切り落とし、\nそのまま相手の先端につなぎ直す\nだけです。\n※ 内部的には commit を付け替えるのではなく、差分を再適用した新しい commit を生成している。\nconflict の正体 conflict が発生する条件も非常に明快です。\n同一 commit 祖先から分岐し、\n同じファイルの同じ行を、別々に変更した\nこの場合、Git は：\n「どちらが正しいか判断できない」\nため、人間に決断を委ねる だけです。\n構造的には、\n「新しい commit を作る前に、人力で差分解決を要求されている」\nという状態です。\nすべては DAG とポインタ操作 ここまで見てきた通り、Git のすべての挙動は：\nDAG 構造\nポインタの付け替え\nこの 2 つで 完全に説明可能 です。\nコマンドを丸暗記するよりも、\n「今、どのポインタがどこを指しているのか」\nを意識する方が、\n圧倒的にトラブル対応力が上がります。\nまとめ：Git は難しくない、見えづらいだけ Git は決して複雑なツールではありません。\n難しく感じる理由はただ一つ：\n内部構造が可視化されていないから\nです。\nDAG とポインタ構造で理解できた瞬間、\nbranch\nHEAD\nmerge\nrebase\nreset\ncheckout\nこれらはすべて\n自然に説明できる単純操作 に変わります。\nもし今まで Git を「なんとなく」使っていたなら、\nぜひ一度、構造から考える視点 を持ってみてください。\nおそらく、\nGit に対するストレスが一段階、確実に下がるはずです。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-12736c19b679d6/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/12736c19b679d6\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"はじめに\"\u003eはじめに\u003c/h1\u003e\n\u003cp\u003eGit を使っていて、こんな感覚になったことはありませんか？\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eコマンドは打てるけど、何が起きているか分からない\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003emerge と rebase の違いを、説明しろと言われると詰まる\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003econflict が出ると、なぜ衝突したのか理解できない\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e多くの Git 解説記事は、\u003cbr\u003e\n\u003cstrong\u003e「何を打つか」\u003c/strong\u003e は丁寧に教えてくれますが、\u003cbr\u003e\n\u003cstrong\u003e「なぜそうなるか」\u003c/strong\u003e までは踏み込んでくれません。\u003c/p\u003e\n\u003cp\u003eその結果、\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eなんとなく使えているが、仕組みはよく分からない\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eという状態に陥りがちです。\u003c/p\u003e\n\u003cp\u003eこの記事では、コマンドの説明はほぼ行いません。\u003cbr\u003e\n代わりに、\u003cstrong\u003eGit の内部構造をデータ構造として捉え直す\u003c/strong\u003e ことで、\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e「あ、そういうことだったのか」\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eと腑に落ちる理解を目指します。\u003c/p\u003e\n\u003cp\u003e少しだけ \u003cstrong\u003eグラフ理論\u003c/strong\u003e と \u003cstrong\u003eポインタの概念（C 言語）\u003c/strong\u003e の素養があると、\u003cbr\u003e\nかなりスムーズに理解できると思います。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"git-の正体木構造ではなく-dag有向非巡回グラフ\"\u003eGit の正体：木構造ではなく DAG（有向非巡回グラフ）\u003c/h2\u003e\n\u003cp\u003e多くの人は、Git の履歴を「木構造」としてイメージしています。\u003c/p\u003e\n\u003cp\u003eしかし、これは正確ではありません。\u003c/p\u003e\n\u003cp\u003eGit の本質は \u003ca href=\"https://ja.wikipedia.org/wiki/%E6%9C%89%E5%90%91%E9%9D%9E%E5%B7%A1%E5%9B%9E%E3%82%B0%E3%83%A9%E3%83%95\"\u003e有向非巡回グラフ\u003c/a\u003eです。\u003c/p\u003e\n\u003ch3 id=\"commit-はノード\"\u003eCommit は「ノード」\u003c/h3\u003e\n\u003cp\u003eすべての commit は、グラフ上の \u003cstrong\u003eノード\u003c/strong\u003e です。\u003c/p\u003e\n\u003cp\u003e各 commit は、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eファイルのスナップショット\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e親 commit への参照（1 個または 2 個）\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eを持っています。\u003c/p\u003e\n\u003ch3 id=\"親が-2-個になる瞬間--merge\"\u003e親が 2 個になる瞬間 = merge\u003c/h3\u003e\n\u003cp\u003e通常の commit は親が 1 つですが、\u003cbr\u003e\nmerge commit は \u003cstrong\u003e親を 2 つ\u003c/strong\u003e 持ちます。\u003c/p\u003e","title":"🎉 Git を「完全に理解」するためのデータ構造の話"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに Unity で開発していると、\n実行中にパラメータを触りながら挙動を確認したい\nという場面は、ほぼ毎日のように出てきます。\n最初のうちは、Unity 標準の仕組みを使って\n簡単な Runtime Debug UI を用意すれば十分でした。\nただ、プロジェクトが進み、\nデバッグ項目が増える\n調整対象が増える\n複数人で同時に触る\nという状況になるにつれ、\n「その場しのぎ」で作ったデバッグ UI が、確実に足を引っ張る\nようになってきます。\nこの段階で欲しくなったのが、\n壊れにくく、拡張しやすく、\nプロジェクトと一緒に「育てていける」デバッグ基盤\nでした。\nこの記事では、\nそのために設計した 自作 Runtime Debug システム について、\n設計意図と実装の要点 を中心にまとめます。\n何が問題だったのか 問題の本質は、「UI をどう実装するか」 ではありませんでした。\n本当に困っていたのは、\nDebug 項目の追加・削除が面倒\nどこに何のデバッグ設定があるのか分からない\n不要になった項目が整理されず、蓄積していく\nという 運用面の破綻 です。\n特に後半になると、\n「デバッグ環境のせいで、デバッグそのものがやりにくい」\nという、本末転倒な状態になりがちでした。\n目指したゴール 今回の設計で目指したゴールは、かなり明確です。\nUI を書かずに Debug 項目を追加できる\n構造が自然に整理され続ける\n無効化・除去が簡単\nプロジェクト規模が大きくなっても破綻しない\nつまり、\n「運用しても壊れない設計」\nを最優先にしました。\n全体構成 全体の流れは、次のようになります。\n起動 ↓ RuntimeInitializeOnLoadMethod ↓ DebugManager 自動生成 ↓ DebugOption 自動登録 ↓ Reflection による項目収集 ↓ UI 自動生成 \u0026amp; バインディング ここで重視したのは、\n「シーンや手動設定に一切依存しない」\nという点です。\nデバッグ環境の初期化でミスる、というのは\n地味にストレスが溜まるため、\n起動時に必ず立ち上がる構成 にしています。\nDebugManager の自動生成 起動時に DebugManager を生成し、\nDontDestroyOnLoad で常駐させます。\n#if ENABLE_RUNTIME_DEBUG public static class DebugBootstrap { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] static void Initialize() { var go = new GameObject(\u0026quot;RuntimeDebugger\u0026quot;); Object.DontDestroyOnLoad(go); go.AddComponent\u0026lt;RuntimeDebugManager\u0026gt;(); } } #endif この形にしている理由は、\nシーン依存を完全に排除\n設定忘れを防止\nDefine 一発で無効化\nという 運用上の事故を防ぐため です。\nDebug 項目は「クラス」で定義する Debug 項目は、\nINotifyPropertyChanged を実装したクラスとして定義します。\npublic abstract class DebugOptionBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } } これにより、\n明示的な管理が不要\n項目単位で整理可能\nチーム開発でも衝突しにくい\nという構成になります。\n特に チーム規模が大きくなるほど効いてくる設計 だと感じています。\n実際の Debug クラス例 Debug クラスは、かなり素朴な形になります。\npublic partial class PlayerDebug : DebugOptionBase { [DisplayName(\u0026quot;移動速度\u0026quot;)] public float MoveSpeed { get =\u0026gt; _moveSpeed; set { _moveSpeed = value; OnPropertyChanged(); } } [DebugButton(\u0026quot;HP全回復\u0026quot;)] public void RecoverHP() { player.RecoverFullHP(); } private float _moveSpeed = 5f; } ここで書くのは、\n「何を触りたいか」だけ\nです。\nUI の構築やレイアウトは、\nすべてシステム側に任せています。\n自動登録による拡張性 各 DebugOption は、\n起動時に自動で DebugManager に登録されます。\n#if ENABLE_RUNTIME_DEBUG [RuntimeInitializeOnLoadMethod] static void Register() { RuntimeDebugManager.Register(new PlayerDebug()); } #endif Reflection + Attribute による UI 構築 DebugManager 側では Reflection を用いて、\nプロパティ\nメソッド\nAttribute\nを取得し、UI を自動生成します。\nvar props = type.GetProperties(); var methods = type.GetMethods(); ここに、\nDisplayName\nRange\nDebugButton\nといった Attribute を組み合わせることで、\n宣言的に UI の振る舞いを制御 できるようにしています。\nこの構成で意識したのは、\n「UI を作る」のではなく、「UI を記述する」設計に寄せること\nです。\n各 DebugModule 側では、\n「この値を触りたい」\n「この処理をボタンにしたい」\nという 意図だけをコード上に残し、\nUI の生成やレイアウトはすべてシステム側に委ねます。\nこれにより、\nUI 実装を一切意識せずに Debug 項目を追加できる\nUI の構造が自然に統一される\n不要になった項目の削除コストが極端に低い\nという、運用フェーズで効く設計 になりました。\n特に後半フェーズでは、\nデバッグ項目の増減が日常的に発生するため、\n「UI を書かなくていい」\nという一点だけでも、\n開発効率と精神的負担の両面で、かなり効いてきます。\n実装イメージ Reflection で取得した PropertyInfo から Attribute を読み取り、\n対応する UI を生成・バインドします。\nvoid CreateUI(object target, PropertyInfo prop, Transform parent) { var displayName = prop.GetCustomAttribute\u0026lt;DisplayNameAttribute\u0026gt;()?.DisplayName ?? prop.Name; var ui = Instantiate(_numberOptionPrefab, parent) .GetComponent\u0026lt;NumberOption\u0026gt;(); ui.Bind(target, prop, displayName); } UI 側では、INotifyPropertyChanged を利用して\n値の変更を双方向に同期 しています。\npublic abstract class OptionBase : MonoBehaviour { object _target; PropertyInfo _prop; public void Bind(object target, PropertyInfo prop, string name) { _target = target; _prop = prop; if (target is INotifyPropertyChanged notify) notify.PropertyChanged += OnPropertyChanged; RefreshUI(); } void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == _prop.Name) RefreshUI(); } protected abstract void RefreshUI(); } この構成により、\nUI → 値変更 → 即ゲーム反映\nゲーム → 値変更 → 即 UI 反映\nという リアルタイムな双方向同期 が、\n自然な形で成立します。\nこの構成にして感じた効果 1. Debug 項目を「育てられる」 項目追加・削除のコストが低いため、\n開発フェーズに合わせて 自然に進化していく\nデバッグ環境になりました。\n2. 変更コストが極端に低い 新しい Debug 項目は、\npublic int EnemyCount { get; set; } これだけで完了します。\nUI を一切意識しなくてよい、というのは\n想像以上に 精神的な負担が軽く なります。\n3. チーム開発との相性が良い 各担当が自分の DebugOption を持てるため、\n修正範囲が局所化\n衝突しにくい\n責務が明確\nという構造になります。\nまとめ 今回作った Runtime Debug 基盤は、\n自動生成\n自動登録\n宣言的設計\nリアルタイム同期\nを組み合わせることで、\n壊れにくく、育てやすいデバッグ環境\nを目指した設計です。\n即席のデバッグ UI は、短期的には便利ですが、\n中長期では 技術的負債 になります。\nもし、\nデバッグ環境そのものを、\nちゃんと「設計対象」として扱いたい\nと感じている方がいれば、\n一つの実践例として参考になれば嬉しいです。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-2d31ee3e52b29e/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/2d31ee3e52b29e\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"はじめに\"\u003eはじめに\u003c/h1\u003e\n\u003cp\u003eUnity で開発していると、\u003cbr\u003e\n\u003cstrong\u003e実行中にパラメータを触りながら挙動を確認したい\u003c/strong\u003e\u003cbr\u003e\nという場面は、ほぼ毎日のように出てきます。\u003c/p\u003e\n\u003cp\u003e最初のうちは、Unity 標準の仕組みを使って\u003cbr\u003e\n簡単な Runtime Debug UI を用意すれば十分でした。\u003c/p\u003e\n\u003cp\u003eただ、プロジェクトが進み、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eデバッグ項目が増える\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e調整対象が増える\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e複数人で同時に触る\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eという状況になるにつれ、\u003cbr\u003e\n\u003cstrong\u003e「その場しのぎ」で作ったデバッグ UI が、確実に足を引っ張る\u003c/strong\u003e\u003cbr\u003e\nようになってきます。\u003c/p\u003e\n\u003cp\u003eこの段階で欲しくなったのが、\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e壊れにくく、拡張しやすく、\u003cbr\u003e\nプロジェクトと一緒に「育てていける」デバッグ基盤\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eでした。\u003c/p\u003e\n\u003cp\u003eこの記事では、\u003cbr\u003e\nそのために設計した \u003cstrong\u003e自作 Runtime Debug システム\u003c/strong\u003e について、\u003cbr\u003e\n\u003cstrong\u003e設計意図と実装の要点\u003c/strong\u003e を中心にまとめます。\u003c/p\u003e\n\u003ch1 id=\"何が問題だったのか\"\u003e何が問題だったのか\u003c/h1\u003e\n\u003cp\u003e問題の本質は、\u003cstrong\u003e「UI をどう実装するか」\u003c/strong\u003e ではありませんでした。\u003c/p\u003e\n\u003cp\u003e本当に困っていたのは、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eDebug 項目の追加・削除が面倒\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eどこに何のデバッグ設定があるのか分からない\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e不要になった項目が整理されず、蓄積していく\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eという \u003cstrong\u003e運用面の破綻\u003c/strong\u003e です。\u003c/p\u003e\n\u003cp\u003e特に後半になると、\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e「デバッグ環境のせいで、デバッグそのものがやりにくい」\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eという、本末転倒な状態になりがちでした。\u003c/p\u003e\n\u003ch1 id=\"目指したゴール\"\u003e目指したゴール\u003c/h1\u003e\n\u003cp\u003e今回の設計で目指したゴールは、かなり明確です。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eUI を書かずに Debug 項目を追加できる\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e構造が自然に整理され続ける\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e無効化・除去が簡単\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eプロジェクト規模が大きくなっても破綻しない\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eつまり、\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e「運用しても壊れない設計」\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eを最優先にしました。\u003c/p\u003e\n\u003ch1 id=\"全体構成\"\u003e全体構成\u003c/h1\u003e\n\u003cp\u003e全体の流れは、次のようになります。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e起動\n ↓\nRuntimeInitializeOnLoadMethod\n ↓\nDebugManager 自動生成\n ↓\nDebugOption 自動登録\n ↓\nReflection による項目収集\n ↓\nUI 自動生成 \u0026amp; バインディング\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eここで重視したのは、\u003c/p\u003e","title":"☝️ デバッグ UI を書かずに Unity のデバッグ環境を成立させる設計と実装"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに PlayFab を使ったプロジェクトで、\nTitleId や DeveloperSecretKey をどう管理するかは、最初に一度は悩むポイントだと思います。\n自分も最初は深く考えずに、\nconst string TitleId = \u0026quot;XXXX\u0026quot;; const string DeveloperSecretKey = \u0026quot;YYYY\u0026quot;; のようにコードに直接書いていました。\nところが Android ビルド後に apk を確認してみると、\nstrings コマンドで 平文のままキーが抜ける ことに気づき、\n「これはさすがにまずいな……」となったのが今回のきっかけです。\n問題点：apk 内に平文で残る C# のコードに直接書いた文字列は、\nIL2CPP でも\n難読化をしていても\n最終的に 文字列リテラルとして apk 内に残ります 。\nつまり、\nstrings app.apk を叩くだけで、TitleId や SecretKey が見えてしまう状態でした。\nもちろん「完全に隠す」ことは不可能ですが、\n平文で置いておくのはリスクが高すぎる と判断しました。\n方針：コードには平文を置かない 今回の方針はシンプルです。\nソースコード内に 平文のキーを一切書かない\nビルド時に Editor 側で変換・生成する\nランタイムでは最小限の復号処理だけ行う\nいわゆる「秘匿」というより、\napk を覗かれたときに即バレしない状態を作る のが目的です。\n実装概要 実装は以下の流れにしました。\nEditor フォルダ内\nTitleId と DeveloperSecretKey を Json で管理 ビルド前処理で\nJson を読み込む\nXOR 変換した文字列を生成\nそれを保持する C# クラスを自動生成\nランタイムでは\nXOR で元の文字列に戻して使用 また、\nビルドターゲット変更時\nビルド実行時\nEditor 上での実行時\nのどのケースでも 常に最新のクラスが生成される ようにしています。\nビルドポストプロセスの仕組み Editor 側では以下のインターフェースを利用しました。\nIActiveBuildTargetChanged\nIPreprocessBuildWithReport\nこれにより、\nAndroid / iOS 切り替え時\nCI での自動ビルド時\nでも、確実に変換処理が走るようになります。\nさらに、\n[InitializeOnLoad]\nを付けることで、\nEditor 起動時や Play 実行時でもクラスが生成される ようにしています。\nこれにより、\nEditor 実行だけ例外になる\n生成忘れで古いキーを使ってしまう\nといった事故も防げます。\nランタイム側は最小限に ランタイム側では、\n生成されたクラスから文字列を取得\nXOR で復号\nPlayFab SDK に渡す\nだけです。\n複雑なことは一切せず、\nEditor に責務を寄せる 構成にしています。\nこの方法の限界と割り切り もちろん、この方法でも：\n本気で解析されれば復号は可能\n完全なセキュリティ対策ではない\nという前提は変わりません。\nただ、\nstrings 一発で見える状態\n平文がそのまま残っている状態\nに比べると、\n最低限のハードルは確実に上げられる と考えています。\n「やらないよりは明確にマシ」というラインです。\nおわりに 今回の対応は、\n小〜中規模プロジェクト\nクライアントのみで完結する構成\nを想定した現実的な落としどころです。\n本格的なセキュリティが必要な場合は\n当然サーバー側で管理すべきですが、\n「今のフェーズで、何もしないのは怖い」\nという場合には、一つの選択肢になると思います。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-6817fe00d739b4/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/6817fe00d739b4\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"はじめに\"\u003eはじめに\u003c/h3\u003e\n\u003cp\u003ePlayFab を使ったプロジェクトで、\u003cbr\u003e\n\u003ccode\u003eTitleId\u003c/code\u003e や \u003ccode\u003eDeveloperSecretKey\u003c/code\u003e をどう管理するかは、最初に一度は悩むポイントだと思います。\u003c/p\u003e\n\u003cp\u003e自分も最初は深く考えずに、\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003econst string TitleId = \u0026quot;XXXX\u0026quot;;\nconst string DeveloperSecretKey = \u0026quot;YYYY\u0026quot;;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eのようにコードに直接書いていました。\u003c/p\u003e\n\u003cp\u003eところが Android ビルド後に apk を確認してみると、\u003cbr\u003e\n\u003ccode\u003estrings\u003c/code\u003e コマンドで \u003cstrong\u003e平文のままキーが抜ける\u003c/strong\u003e ことに気づき、\u003cbr\u003e\n「これはさすがにまずいな……」となったのが今回のきっかけです。\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"問題点apk-内に平文で残る\"\u003e問題点：apk 内に平文で残る\u003c/h3\u003e\n\u003cp\u003eC# のコードに直接書いた文字列は、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eIL2CPP でも\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e難読化をしていても\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e最終的に \u003cstrong\u003e文字列リテラルとして apk 内に残ります\u003c/strong\u003e 。\u003c/p\u003e\n\u003cp\u003eつまり、\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003estrings app.apk\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eを叩くだけで、TitleId や SecretKey が見えてしまう状態でした。\u003c/p\u003e\n\u003cp\u003eもちろん「完全に隠す」ことは不可能ですが、\u003cbr\u003e\n\u003cstrong\u003e平文で置いておくのはリスクが高すぎる\u003c/strong\u003e と判断しました。\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"方針コードには平文を置かない\"\u003e方針：コードには平文を置かない\u003c/h3\u003e\n\u003cp\u003e今回の方針はシンプルです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eソースコード内に \u003cstrong\u003e平文のキーを一切書かない\u003c/strong\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eビルド時に Editor 側で変換・生成する\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eランタイムでは最小限の復号処理だけ行う\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eいわゆる「秘匿」というより、\u003cbr\u003e\n\u003cstrong\u003eapk を覗かれたときに即バレしない状態を作る\u003c/strong\u003e のが目的です。\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"実装概要\"\u003e実装概要\u003c/h3\u003e\n\u003cp\u003e実装は以下の流れにしました。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eEditor フォルダ内\u003c/strong\u003e\u003c/p\u003e","title":"🧠 AndroidビルドでPlayFabのSecretKeyが丸見えだった話と、ビルド時に自動変換する対策"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nUnity でエディタ拡張を書いていると、\n「この設定、どこに保存するのが正解なんだろう？」\nと悩む場面がよくあります。\nそのときにまず候補に上がるのが EditorPrefs です。\nbool isOpen = EditorPrefs.GetBool(\u0026quot;MyTool_Foldout\u0026quot;, false); とても手軽で便利ですが、使い方を間違えると\nチーム開発では地味に事故りやすい API でもあります。\nこの記事では、\nEditorPrefs は何のための仕組みなのか\nどんな用途に向いているのか\n逆に使ってはいけないケース\nを整理してみます。\nEditorPrefs とは何か EditorPrefs は、Unity エディタ専用の設定保存機構 です。\nプロジェクト単位ではない\nユーザー（マシン）単位で保存される\nバージョン管理の対象外\nという特徴があります。\nつまり、\n「この PC で、この Unity を使っている自分だけの設定」\nを保存するためのものです。\n保存場所はどこ？ EditorPrefs の中身は、OS ごとに以下の場所に保存されます。\nWindows：レジストリ\nmacOS：~/Library/Preferences\nLinux：~/.config\nこの時点で分かる通り、\nGit にも SVN にも乗りません 。\n向いているユースケース 1. エディタ UI の表示状態 例えば、\nツールウィンドウの開閉状態\n折りたたみ（Foldout）の ON / OFF\n最後に選択していたタブ\nこういった「見た目の状態」は、EditorPrefs と非常に相性が良いです。\nbool isOpen = EditorPrefs.GetBool(\u0026quot;MyTool_Foldout\u0026quot;, false);\nプロジェクトを切り替えても、\n「前回と同じ使い心地」を保てます。\n2. 個人向けの作業効率設定 デバッグ表示を有効にするか\n自動リロードを切るか\nログを詳細表示にするか\nといった 人によって好みが分かれる設定 も適しています。\nここを ScriptableObject で共有してしまうと、\n「誰かが変えた設定で全員の挙動が変わる」\nという事故が起きがちです。\n3. 一時的な作業フラグ 例えば、\nデバッグ用セーブデータを使うか\nテスト用モードで起動するか\nなど、\n正式な仕様ではないが、作業中は必要\nというフラグにも使えます。\n向いていないユースケース 1. チームで共有すべき設定 ビルド設定\nデバッグ機能の有効 / 無効\nゲーム挙動に影響するフラグ\nこれらを EditorPrefs に入れるのは危険です。\n理由は単純で、\n他人の EditorPrefs は見えないから 。\n「自分の環境では再現しない」という地獄が始まります。\n2. 実行時（Runtime）で使うデータ EditorPrefs は Editor 専用 です。\nビルド後のゲームでは一切使えません。\n実行時に必要なデータは、\nPlayerPrefs\nScriptableObject\nJSON / Binary ファイル\nなどを使うべきです。\nよくある設計ミス キー名を適当に書く EditorPrefs.SetInt(\u0026quot;Debug\u0026quot;, 1); これはかなり危険です。\n他のツールと衝突する\n何の設定か分からなくなる\n必ず ツール名・機能名を含めたキー にしましょう。\nプロジェクト固有だと勘違いする EditorPrefs は プロジェクトを跨いで共有 されます。\n同じキー名を使っていると、\n別プロジェクトの設定が混ざる\n意図しない初期値になる\nといった問題が起きます。\n必要であれば、\nApplication.productName などをキーに含めるのも一案です。\nScriptableObject との使い分け 用途 推奨 チームで共有 ScriptableObject 個人の作業環境 EditorPrefs 実行時データ PlayerPrefs / ファイル 一時的なUI状態 EditorPrefs 「誰が使う設定なのか？」\nを基準に考えると、判断しやすくなります。\nまとめ EditorPrefs は、\n小さく\n個人的で\nなくても困らないが、あると快適\nそんな情報を保存するための仕組みです。\n強力ですが、\n仕様データやチーム共通設定を入れ始めた瞬間に破綻 します。\nエディタ拡張を書くときは、\nこれは「自分だけの設定」か？\nと一度立ち止まって考えるだけで、\n後々のトラブルをかなり減らせるはずです。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-b2fdefc2c53908/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/b2fdefc2c53908\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eUnity でエディタ拡張を書いていると、\u003cbr\u003e\n「この設定、どこに保存するのが正解なんだろう？」\u003cbr\u003e\nと悩む場面がよくあります。\u003c/p\u003e\n\u003cp\u003eそのときにまず候補に上がるのが \u003ccode\u003eEditorPrefs\u003c/code\u003e です。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ebool isOpen = EditorPrefs.GetBool(\u0026quot;MyTool_Foldout\u0026quot;, false);\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eとても手軽で便利ですが、使い方を間違えると\u003cbr\u003e\n\u003cstrong\u003eチーム開発では地味に事故りやすい\u003c/strong\u003e API でもあります。\u003c/p\u003e\n\u003cp\u003eこの記事では、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eEditorPrefs は何のための仕組みなのか\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eどんな用途に向いているのか\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e逆に使ってはいけないケース\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eを整理してみます。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"editorprefs-とは何か\"\u003eEditorPrefs とは何か\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eEditorPrefs\u003c/code\u003e は、\u003cstrong\u003eUnity エディタ専用の設定保存機構\u003c/strong\u003e です。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eプロジェクト単位ではない\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eユーザー（マシン）単位で保存される\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eバージョン管理の対象外\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eという特徴があります。\u003c/p\u003e\n\u003cp\u003eつまり、\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e「この PC で、この Unity を使っている自分だけの設定」\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eを保存するためのものです。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"保存場所はどこ\"\u003e保存場所はどこ？\u003c/h2\u003e\n\u003cp\u003eEditorPrefs の中身は、OS ごとに以下の場所に保存されます。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eWindows：レジストリ\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003emacOS：\u003ccode\u003e~/Library/Preferences\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eLinux：\u003ccode\u003e~/.config\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eこの時点で分かる通り、\u003cbr\u003e\n\u003cstrong\u003eGit にも SVN にも乗りません\u003c/strong\u003e 。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"向いているユースケース\"\u003e向いているユースケース\u003c/h2\u003e\n\u003ch3 id=\"1-エディタ-ui-の表示状態\"\u003e1. エディタ UI の表示状態\u003c/h3\u003e\n\u003cp\u003e例えば、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eツールウィンドウの開閉状態\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e折りたたみ（Foldout）の ON / OFF\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e最後に選択していたタブ\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eこういった「見た目の状態」は、EditorPrefs と非常に相性が良いです。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ebool isOpen = EditorPrefs.GetBool(\u0026quot;MyTool_Foldout\u0026quot;, false);\u003c/code\u003e\u003c/p\u003e","title":"🐈 UnityのEditor拡張で「設定の保存先」に毎回悩む話"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\n――2026年のハードウェア前提で考えるデータ構造の現実 エンジニア向けの記事や SNS で、\n「リンクリストはもう終わった」\nという表現を見かけることがあります。\n最初にこの話を聞いたとき、正直かなり極端だなと思いました。\nただ、実務でパフォーマンスを意識した設計や実装を重ねていくと、この言い方が完全に間違っているとも言い切れない 、という感覚になってきます。\nこの記事では、「リンクリストがなぜ避けられるようになったのか」「それでも今なお使われ続けている理由」を、現代の CPU・メモリ構成を前提に 整理してみます。\n教科書的な理解と、現実のズレ 学生時代にデータ構造を学んだとき、ほとんどの人が次のように習ったはずです。\n配列：途中への挿入・削除は O(N)\nリンクリスト：挿入・削除は O(1)\n理論としては正しいです。\nただし、ここには重要な前提条件 があります。\nメモリアクセスのコストはすべて同じである\n現実のハードウェアでは、これは成立しません。\nキャッシュ階層という「無視できない現実」 現代の CPU では、メモリは階層構造になっています。\nL1 キャッシュ：数サイクル\nL2 キャッシュ：十数サイクル\nL3 キャッシュ：数十サイクル\nメインメモリ（RAM）：数百サイクル\nこの差は無視できるものではありません。\n特に問題になるのが、アクセスパターンが予測できない場合 です。\nリンクリストが不利になる理由 典型的なリンクリストは、ノードがメモリ上に散らばって配置されます。\nNode A -\u0026gt; Node B -\u0026gt; Node C\n見た目はシンプルですが、実際のアドレスは次のようになっているかもしれません。\n0x1000 -\u0026gt; 0x8F20 -\u0026gt; 0x3A10\nリンクリストを走査する場合、\n現在のノードを読み込む\nnext ポインタを読む\n次のアドレスが分かってから、次の読み込みを行う\nという強い依存関係のある処理 になります。\nCPU のプリフェッチ機構は、連続したアクセス（配列など）は得意ですが、\nリンクリストのようなポインタ追跡はほとんど先読みできません。\n結果として、キャッシュミスが頻発 します。\n配列（vector）が速い本当の理由 std::vector や配列を順番に処理しているとき、\n実際には CPU はすでに次のデータをキャッシュに読み込んでいます。\nつまり私たちは、\nメモリを読んでいるつもりでも\n実際にはキャッシュを読んでいる時間がほとんど\nという状態になります。\nこの差は、データ量が数千～数万程度でもはっきり現れます。\n「O(1) の挿入」は実務では罠になる リンクリストの O(1) 挿入は、すでに挿入位置のノードを持っている ことが前提です。\n現実のコードでは、\n挿入位置を探すために走査が必要\nノード生成のためにメモリ確保が発生\nアロケータのオーバーヘッド\nキャッシュのコールドスタート\nといったコストが重なります。\n実際、要素数がそれほど多くない場合は、\nvector の memmove のほうが速いケースは珍しくありません。\nそれでもリンクリストは「消えていない」 では、リンクリストは完全に不要になったのでしょうか。\n答えは No です。\nただし、使われるレイヤーが変わりました。\n侵入型リスト（Intrusive List） Linux カーネルなどで多用されているのが、侵入型リストです。\nこれは、\nノードを別途確保しない\n構造体の中に next / prev を直接持たせる\nという設計です。\nこの方式では、\n追加・削除でメモリ確保が発生しない\nオブジェクトが複数のリストに同時に属せる\nといった利点があります。\nカーネルや低レイヤーでは、今でも不可欠な構造です。\n並行処理・ロックフリー構造 マルチスレッド環境では、配列は扱いづらくなります。\n途中への挿入・削除では、\n広範囲の要素移動が必要\n大きなロックが必要\n一方、リンクリストは局所的な更新が可能です。\nCAS（Compare-And-Swap）命令を使えば、\nロックフリーキューやスタックを実装できます。\nMichael-Scott Queue など、実績のあるアルゴリズムの多くは\nリンク構造をベースにしています。\nメモリアロケータの内部でも使われている malloc の実装（glibc / jemalloc / tcmalloc）では、\n空きメモリ管理にリンクリストが使われています。\n空いているメモリ自身にポインタを持たせることで、\n追加の管理領域を不要にしているのです。\nこの用途では、リンクリストは非常に理にかなっています。\nデータ指向設計とリンクリスト 近年、ゲーム開発を中心に\nData-Oriented Design（DOD） が広く使われています。\nUnity の ECS などが典型例ですが、\nデータをオブジェクト単位でまとめない\n同種のデータを連続した配列で持つ\nという設計です。\nこの思想では、リンクリストは明確に不利です。\nキャッシュ効率を最優先するためです。\n配列でリンクを表現するという妥協点 最近よく使われる手法として、\nポインタではなくインデックスを使う\n大きな配列をオブジェクトプールとして使う\nという設計があります。\nこれは、\n物理的には配列（連続メモリ）\n論理的にはリンク構造\nという折衷案です。\nRust や Zig の文化とも相性が良く、\n高性能なシステムでよく見かける形です。\n結論：リンクリストは「役割が変わった」 アプリケーション層の汎用コンテナとしてのリンクリスト\n→ ほぼ使われなくなった\nシステム・低レイヤー・並行処理・メモリ管理\n→ 今も現役\n「リンクリストはもう使わない」という言葉は、\n無条件に否定すべきものではありません 。\nそれは、\n抽象だけを信じて、ハードウェアを無視するな\nという警告として受け取るのが、ちょうど良いと思います。\nこれからコードを書くとき、\n少しだけキャッシュラインやメモリアクセスを意識すると、\nデータ構造の選び方が変わってくるはずです。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-f01b1a0f7f2884/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/f01b1a0f7f2884\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"2026年のハードウェア前提で考えるデータ構造の現実\"\u003e――2026年のハードウェア前提で考えるデータ構造の現実\u003c/h2\u003e\n\u003cp\u003eエンジニア向けの記事や SNS で、\u003cbr\u003e\n「リンクリストはもう終わった」\u003cbr\u003e\nという表現を見かけることがあります。\u003c/p\u003e\n\u003cp\u003e最初にこの話を聞いたとき、正直かなり極端だなと思いました。\u003cbr\u003e\nただ、実務でパフォーマンスを意識した設計や実装を重ねていくと、この言い方が\u003cstrong\u003e完全に間違っているとも言い切れない\u003c/strong\u003e 、という感覚になってきます。\u003c/p\u003e\n\u003cp\u003eこの記事では、「リンクリストがなぜ避けられるようになったのか」「それでも今なお使われ続けている理由」を、\u003cstrong\u003e現代の CPU・メモリ構成を前提に\u003c/strong\u003e 整理してみます。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"教科書的な理解と現実のズレ\"\u003e教科書的な理解と、現実のズレ\u003c/h2\u003e\n\u003cp\u003e学生時代にデータ構造を学んだとき、ほとんどの人が次のように習ったはずです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e配列：途中への挿入・削除は O(N)\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eリンクリスト：挿入・削除は O(1)\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e理論としては正しいです。\u003cbr\u003e\nただし、ここには\u003cstrong\u003e重要な前提条件\u003c/strong\u003e があります。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eメモリアクセスのコストはすべて同じである\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e現実のハードウェアでは、これは成立しません。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"キャッシュ階層という無視できない現実\"\u003eキャッシュ階層という「無視できない現実」\u003c/h2\u003e\n\u003cp\u003e現代の CPU では、メモリは階層構造になっています。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eL1 キャッシュ：数サイクル\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eL2 キャッシュ：十数サイクル\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eL3 キャッシュ：数十サイクル\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eメインメモリ（RAM）：数百サイクル\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eこの差は無視できるものではありません。\u003cbr\u003e\n特に問題になるのが、\u003cstrong\u003eアクセスパターンが予測できない場合\u003c/strong\u003e です。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"リンクリストが不利になる理由\"\u003eリンクリストが不利になる理由\u003c/h2\u003e\n\u003cp\u003e典型的なリンクリストは、ノードがメモリ上に散らばって配置されます。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eNode A -\u0026gt; Node B -\u0026gt; Node C\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e見た目はシンプルですが、実際のアドレスは次のようになっているかもしれません。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e0x1000 -\u0026gt; 0x8F20 -\u0026gt; 0x3A10\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eリンクリストを走査する場合、\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e現在のノードを読み込む\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003enext ポインタを読む\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e次のアドレスが分かってから、次の読み込みを行う\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eという\u003cstrong\u003e強い依存関係のある処理\u003c/strong\u003e になります。\u003c/p\u003e\n\u003cp\u003eCPU のプリフェッチ機構は、連続したアクセス（配列など）は得意ですが、\u003cbr\u003e\nリンクリストのようなポインタ追跡はほとんど先読みできません。\u003c/p\u003e\n\u003cp\u003e結果として、\u003cstrong\u003eキャッシュミスが頻発\u003c/strong\u003e します。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"配列vectorが速い本当の理由\"\u003e配列（vector）が速い本当の理由\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003estd::vector\u003c/code\u003e や配列を順番に処理しているとき、\u003cbr\u003e\n実際には CPU はすでに次のデータをキャッシュに読み込んでいます。\u003c/p\u003e","title":"😎 「リンクリストはもう使われない」のか？"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nUE5 には Gameplay Ability System（GAS） や Game Features（GF） といった、拡張性と再利用性を強く意識した公式フレームワークが用意されています。\n最近では Epic Games もこれらの利用を積極的に推奨しており、\n「公式が用意しているのだから、最初から使うべきでは？」\nと感じる方も少なくないと思います。\nしかし、すべてのプロジェクト・すべての開発フェーズで導入すべきか というと、\n個人的には 明確にそうではない と考えています。\nこの記事では\nなぜプロジェクト初期で GAS / Game Features を導入すると負担になりやすいのか\nどの段階で導入すると真価を発揮するのか\nについて、自身の経験をもとに整理します。\n結論：GAS / Game Features は優秀だが「導入時期」が重要 まず前提として\nGAS は長年運用され、大規模タイトルで実績のあるシステム Game Features も 機能分離・モジュール化を重視した現代的な設計 であり、品質や思想そのものに問題があるわけではありません。\n問題になるのは 「いつ導入するか」 です。\nプロジェクト初期・プロトタイプ段階で起きやすい問題 ① フレームワークが過剰に重い GAS や Game Features は\n高い汎用性 拡張を前提とした設計 長期運用への耐性 を重視しています。\nその結果\nクラス構造が複雑 Ability / Attribute / Effect / GameplayTag など前提概念が多い 正しく使い始めるまでの学習コストが高い という特徴があります。\n仕様検証やプロトタイプ作成の段階 で、\nこの規模のフレームワークを扱う必要があるでしょうか。\n多くの場合、答えは NO です。\n② 仕様が未確定なのに構造だけが先に固まる 初期フェーズでは\nスキル構成が妥当か クールダウンやコスト設計が適切か ステータス体系が本当に必要か といった点が頻繁に変更されます。\nこの段階で GAS を導入すると\n「GAS 的に正しい設計」を意識しすぎる 小さな実験や変更の心理的コストが上がる 結果として仕様検討の自由度が下がる という状態に陥りやすくなります。\n設計がまだ流動的なのに、フレームワークの構造だけが先に固定されてしまう\nこれはプロジェクトにとってリスクです。\n③ 「公式フレームワークを使っている安心感」という落とし穴 これは特に注意が必要な点ですが\nGAS を使っているから問題ない Epic が用意した仕組みだから正しい という 思考停止 に陥りやすくなります。\nしかし本来重要なのは\nなぜこの抽象が必要なのか どの課題を解決したいのか 本当に今必要な仕組みなのか を理解したうえで使うことです。\n理解が伴わないまま導入すると、\n「巨大だが扱いきれないシステム」 になってしまいます。\nでは、いつ導入すべきか？ 仕様が一度安定した後 おすすめなのは\n基本仕様がある程度固まった 機能の増加や再利用が見えてきた 拡張性・保守性が課題になり始めた 再設計・リファクタリング段階 での導入です。\nこれは UE が RDG（Render Dependency Graph）を\n複数バージョンにわたって段階的に導入した流れ とよく似ています。\n一度シンプルな実装を自分で経験した後 興味深いのは\n自前で簡単なスキル管理やモジュール構成を実装した後に\nGAS / Game Features を見ると\n「これはこの問題を解決するための仕組みか」 「ここを汎用化しているのか」 と、設計意図が一気に理解できる点です。\n実際に\n「自分が欲しかった仕組みは、すでに UE に用意されていた」\nと感じたことがあります。\nこの状態で導入すると、\nGAS / Game Features は 負債ではなく強力な加速装置 になります。\nまとめ GAS / Game Features は非常に優れた仕組み ただし、プロジェクト初期ではコストが高すぎる場合が多い まずはシンプルに作り、課題を自分で体験する 必要性が明確になった段階で導入すると価値が最大化される というのが、現時点での結論です。\nフレームワークは思考の代替ではない\nしかし、正しいタイミングで使えば、思考と開発速度を大きく加速してくれる\nUE5 は選択肢が多い分、\n「何を使わないか」を判断することもエンジニアの重要な役割だと感じています。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-a3cb27df6b605c/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/a3cb27df6b605c\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eUE5 には \u003cstrong\u003eGameplay Ability System（GAS）\u003c/strong\u003e や \u003cstrong\u003eGame Features（GF）\u003c/strong\u003e といった、拡張性と再利用性を強く意識した公式フレームワークが用意されています。\u003c/p\u003e\n\u003cp\u003e最近では Epic Games もこれらの利用を積極的に推奨しており、\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e「公式が用意しているのだから、最初から使うべきでは？」\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eと感じる方も少なくないと思います。\u003c/p\u003e\n\u003cp\u003eしかし、\u003cstrong\u003eすべてのプロジェクト・すべての開発フェーズで導入すべきか\u003c/strong\u003e というと、\u003cbr\u003e\n個人的には \u003cstrong\u003e明確にそうではない\u003c/strong\u003e と考えています。\u003c/p\u003e\n\u003cp\u003eこの記事では\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eなぜプロジェクト初期で GAS / Game Features を導入すると負担になりやすいのか\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eどの段階で導入すると真価を発揮するのか\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eについて、自身の経験をもとに整理します。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"結論gas--game-features-は優秀だが導入時期が重要\"\u003e結論：GAS / Game Features は優秀だが「導入時期」が重要\u003c/h2\u003e\n\u003cp\u003eまず前提として\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGAS は長年運用され、\u003cstrong\u003e大規模タイトルで実績のあるシステム\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eGame Features も \u003cstrong\u003e機能分離・モジュール化を重視した現代的な設計\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eであり、品質や思想そのものに問題があるわけではありません。\u003c/p\u003e\n\u003cp\u003e問題になるのは \u003cstrong\u003e「いつ導入するか」\u003c/strong\u003e です。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"プロジェクト初期プロトタイプ段階で起きやすい問題\"\u003eプロジェクト初期・プロトタイプ段階で起きやすい問題\u003c/h2\u003e\n\u003ch3 id=\"-フレームワークが過剰に重い\"\u003e① フレームワークが過剰に重い\u003c/h3\u003e\n\u003cp\u003eGAS や Game Features は\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e高い汎用性\u003c/li\u003e\n\u003cli\u003e拡張を前提とした設計\u003c/li\u003e\n\u003cli\u003e長期運用への耐性\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eを重視しています。\u003c/p\u003e\n\u003cp\u003eその結果\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eクラス構造が複雑\u003c/li\u003e\n\u003cli\u003eAbility / Attribute / Effect / GameplayTag など前提概念が多い\u003c/li\u003e\n\u003cli\u003e正しく使い始めるまでの学習コストが高い\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eという特徴があります。\u003c/p\u003e","title":"📃 UE5 における Gameplay Ability System ＆ Game Features を「いつ使うべきか」という個人的な考察"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに：高速対戦ゲームにおけるネットコードの本質 ゲームプレイ（GamePlay）エンジニアにとって、最重要テーマのひとつが ネット同期（netcode） です。\n特に Overwatch のような高速リアルタイムアクションでは、「レスポンスの速さ（responsive）」 がゲーム性を決定づけます。\nそのため、プレイヤーの操作は サーバーの応答を待たずに即時反映 する必要があります。\nこれを実現するのが 予測（prediction / pre-presentation） です。\nただし、クライアントを信頼できない（チート対策）というFPSの前提は20年変わっていません。\nそのうえで「即時応答」と「サーバー権威」の両立を図るのがネットコード設計の核心になります。\n即時応答が必要な操作 移動\nスキル\n武器（発射や装填などのスキル付き武器）\n当たり判定（Hit Registration）\n共通原則はただ一つ：\n「プレイヤーがボタンを押したら即、見た目の挙動が起きること」\nどれほど ping が高くても、遅延を感じさせてはならない。\nしかし、予測を行う以上、避けられない問題があります──\nそれが 予測ミス（misprediction） です。\n予測ミスは、\n「クライアント側では成功したように見えた操作が、サーバー上では成立していない」\n状態を指します。\nOverwatch では、予測ミスが起きても「操作を遅延させる」のではなく、\nできる限り予測ミスを減らす設計（＝確定性 Determinism） が採用されています。\n予測ミスの例：サーバーとの食い違い ping 250ms。\nクライアントでは「ジャンプした」つもりでも、サーバー上では Mei の氷結を受けていた 。\nその結果、クライアントは一度ジャンプの予測を行った後、サーバーからの正しい状態（氷結）で「強制的に巻き戻される」。\nこのように、超高速応答を目指すほど、予測ミスが発生しやすくなります。\n▼ ここから：低ミス予測を支える「確定性（Determinism）」 以下では Overwatch が採用している ECS + Deterministic Simulation の要点を整理します。\n1. 時間の量子化：Command Frame 確定的なシミュレーションは、\n時間の同期\n固定更新周期\n値の量子化\nに依存します。\nサーバーとクライアントは、Command Frame（命令フレーム） 単位で同期を取ります。\n1フレーム = 16ms（大会時は 7ms） ECS では、プレイヤー入力を扱うすべての System は通常の Update ではなく\nUpdateFixed（固定フレーム用の更新）で動作します。\n2. クライアントは常にサーバーより「半RTT + 1フレーム」先を走る 例：RTT が 160ms の場合\n半分 → 80ms\n+1 フレーム（16ms）\nつまりクライアントは常に 約96ms 未来を走っている 状態です。\nなぜ？\nクライアントが即時にプレイヤー入力を反映し続けるには、現在時刻（NOW）に限りなく近く動作する必要があるからです。\n3. 予測 → サーバー確認 → 一致チェック クライアントは以下を行います：\n現在の入力を元に予測シミュレーション\nその結果をブラッシュアップしつつ、入力をサーバーへ送信\nサーバーが後から「この時刻の正しい状態」を送り返す\nクライアントは リングバッファに保存した過去の状態 と照合\n一致 → そのまま継続 / 不一致 → Reconciliation（巻き戻し＋再計算）\n一致していればスムーズに次へ進み、\n不一致なら「サーバーの状態へ巻き戻して」その後の入力を全部再適用します。\n4. Reconciliation：巻き戻しと再適用 予測ミスが起きた場合：\nサーバーから戻ってきた「正しい状態」を採用\nその時点から現在までの「入力ログ」を全て再生（リプレイ）\n現在時刻まで追いつく\n例：\nクライアントは Tracer が走っていると予測していたが、サーバー上ではMcCreeのフラッシュでスタン状態だった。\n巻き戻して再計算すると、挙動がサーバーと完全に揃う。\n5. ネットワークが不安定なとき：クライアントの「時間膨張」 クライアントの入力パケットはカスタム UDP（信頼制）で送られますが、\nそれでもパケットロスは起こる 。\nパケットロスが続くと、サーバーは「最後に届いた入力」を元に予測するしかありません。\nそこで、Overwatch が採用したのが：\nクライアントの Time Dilation（時間膨張） クライアントが、\n通常：1frame = 16ms\n膨張：1frame ≒ 15.2ms\nのように 僅かに高速でシミュレーション を行い、サーバーより先行幅を広げる技術です。\n結果：\n入力パケットが「より早く」サーバーに届く\nサーバー側の入力バッファが太くなる（余裕が出来る）\nパケットロスの穴を埋めやすくなる\nこれは、ping やパケットロスが揺れまくる環境（国際回線、宇宙ステーションなど）でも機能します。\n6. サーバーが「回復した」と判断したら ネットワークが安定すると、サーバーは：\n「OK、もう大丈夫」\nという合図を出します。\nクライアントは：\n時間膨張を解除（frame 時間を元に戻す）\n入力報告頻度を通常に戻す\nサーバーもバッファを縮小\nこれにより、遅延・丢包・予測ミスの影響を最小限に抑えつつレスポンス性を維持できます。\n7. 入力の「スライディングウィンドウ」送信 サーバーが最後に確認したフレームが例えば 4 で、\nクライアントが現在 19 フレームまで進んでいる場合：\nクライアントは 4〜19 のすべての入力ログをまとめて送る 。\nこれにより：\nパケットロスが発生しても次のパケットで穴埋め可能\nサーバーは入力の抜けをほぼ確実に補完できる\n移動予測は破綻しにくくなる\nこの手法は古い FPS（例：Quake）の時代から存在する伝統技術です。\n8. すべての要素を同時に起こした最悪ケースのデモ 以下の全要素が同時に発生：\nping 変動\nパケットロス\n時間膨張\n入力スライディングウィンドウ\n予測ミス\nサーバー側 correction\nそれでもゲームプレイは成立する──\nこれが Overwatch のネットコードの強さです。\nまとめ Overwatch のネットコードは次の技術の組み合わせで成り立っています：\nクライアント予測（Prediction）\nサーバー権威（Server Authoritative）\n巻き戻し（Reconciliation）\n確定的シミュレーション（Determinism）\nスライディングウィンドウ入力\nネットワーク不安定時の時間膨張（Time Dilation）\n結果として、低遅延・高レスポンス・高一貫性 を同時に実現しています。\n※このブログは GDC2017【Overwatch Gameplay Architecture andNetcode 】を参考にしました。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-ee81f79477d4f9/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/ee81f79477d4f9\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに高速対戦ゲームにおけるネットコードの本質\"\u003eはじめに：高速対戦ゲームにおけるネットコードの本質\u003c/h2\u003e\n\u003cp\u003eゲームプレイ（GamePlay）エンジニアにとって、最重要テーマのひとつが \u003cstrong\u003eネット同期（netcode）\u003c/strong\u003e です。\u003cbr\u003e\n特に Overwatch のような高速リアルタイムアクションでは、\u003cstrong\u003e「レスポンスの速さ（responsive）」\u003c/strong\u003e がゲーム性を決定づけます。\u003c/p\u003e\n\u003cp\u003eそのため、プレイヤーの操作は \u003cstrong\u003eサーバーの応答を待たずに即時反映\u003c/strong\u003e する必要があります。\u003cbr\u003e\nこれを実現するのが \u003cstrong\u003e予測（prediction / pre-presentation）\u003c/strong\u003e です。\u003c/p\u003e\n\u003cp\u003eただし、クライアントを信頼できない（チート対策）というFPSの前提は20年変わっていません。\u003cbr\u003e\nそのうえで「即時応答」と「サーバー権威」の両立を図るのがネットコード設計の核心になります。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"即時応答が必要な操作\"\u003e即時応答が必要な操作\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e移動\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eスキル\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e武器（発射や装填などのスキル付き武器）\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e当たり判定（Hit Registration）\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e共通原則はただ一つ：\u003cbr\u003e\n\u003cstrong\u003e「プレイヤーがボタンを押したら即、見た目の挙動が起きること」\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eどれほど ping が高くても、遅延を感じさせてはならない。\u003c/p\u003e\n\u003cp\u003eしかし、予測を行う以上、避けられない問題があります──\u003cbr\u003e\nそれが \u003cstrong\u003e予測ミス（misprediction）\u003c/strong\u003e です。\u003c/p\u003e\n\u003cp\u003e予測ミスは、\u003cbr\u003e\n\u003cstrong\u003e「クライアント側では成功したように見えた操作が、サーバー上では成立していない」\u003c/strong\u003e\u003cbr\u003e\n状態を指します。\u003c/p\u003e\n\u003cp\u003eOverwatch では、予測ミスが起きても「操作を遅延させる」のではなく、\u003cbr\u003e\n\u003cstrong\u003eできる限り予測ミスを減らす設計（＝確定性 Determinism）\u003c/strong\u003e が採用されています。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"予測ミスの例サーバーとの食い違い\"\u003e予測ミスの例：サーバーとの食い違い\u003c/h2\u003e\n\u003cp\u003eping 250ms。\u003cbr\u003e\nクライアントでは「ジャンプした」つもりでも、サーバー上では \u003cstrong\u003eMei の氷結を受けていた\u003c/strong\u003e 。\u003cbr\u003e\nその結果、クライアントは一度ジャンプの予測を行った後、サーバーからの正しい状態（氷結）で「強制的に巻き戻される」。\u003c/p\u003e\n\u003cp\u003eこのように、超高速応答を目指すほど、予測ミスが発生しやすくなります。\u003c/p\u003e\n\u003chr\u003e\n\u003ch1 id=\"-ここから低ミス予測を支える確定性determinism\"\u003e▼ ここから：低ミス予測を支える「確定性（Determinism）」\u003c/h1\u003e\n\u003cp\u003e以下では Overwatch が採用している \u003cstrong\u003eECS + Deterministic Simulation\u003c/strong\u003e の要点を整理します。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-時間の量子化command-frame\"\u003e1. 時間の量子化：Command Frame\u003c/h2\u003e\n\u003cp\u003e確定的なシミュレーションは、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e時間の同期\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e固定更新周期\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e値の量子化\u003c/p\u003e","title":"🦔 Overwatchにおけるネットコードと予測技術の解説"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\n〜意図（ビジネス要件）と低レイヤ知識の役割について考える〜 「最近のエンジニアは低レイヤを知らない」──そんな声を耳にすることが増えました。\nそれは本当に問題なのでしょうか？\n1. プログラミングの本質：意図をコンピュータに伝えること プログラミングとは、人間の意図をコードという形でコンピュータに伝える行為 です。\n言語やフレームワークはそのための「伝達チャンネル」にすぎません。\n用途や目的が異なるからこそ、多様な言語やフレームワークが存在しています。\n業務アプリ：可読性・保守性を重視\nゲームやリアルタイム処理：性能やレイテンシを重視\n研究・プロトタイピング：表現力・試行錯誤のしやすさを重視\n重要なのは「何を伝えたいのか（ビジネスの意図） 」であり、\n低レイヤ（ハードウェアや OS の仕組み）はそれを実現するための手段 にすぎません。\n2. 抽象化は「ノイズ除去」の進化である ソフトウェア開発の歴史を振り返ると、それは常に「ノイズを減らす」試みの連続でした。\nアセンブリ言語：レジスタやメモリ操作のノイズを扱う\n高級言語：ポインタ操作や手動メモリ管理のノイズを隠蔽\nモダンフレームワーク：接続プールやリトライなど運用上のノイズを隠蔽\nこの観点から見ると、抽象化は「情報理論的に正しい方向への進化」です。\n不要な実装詳細を隠すことで、開発者はより本質的な意図に集中できる ようになります。\n3. それでも低レイヤ知識は不要にならない 抽象化が進んだからといって、低レイヤの知識が不要になるわけではありません。\n実際には「抽象の漏れ（Leaky Abstraction）」や、フレームワークの誤用によって問題が生じることがあります。\nそうしたときに求められるのは、下のレイヤを理解し、意図が正しく実現されているかを検証する力 です。\nただし、低レイヤの学習そのものが目的化してしまうと、肝心の「ビジネス意図の理解」が置き去りになります。\n4. 最適化の本質は「情報の圧縮」である 最適化とは、突き詰めれば「情報の圧縮」です。\nプログラムはビジネスの意図を機械命令に符号化したものとも言えます。\n良い最適化とは、業務知識（確信）をもとに冗長性を取り除くこと によって成立します。\nたとえば：\nキャッシュ導入の根拠は「そのデータは一定期間変化しない」という業務知見\nインデックス設計の根拠は「特定フィールドの検索頻度が高い」という利用傾向\nつまり、最適化とは単なる技術的チューニングではなく、業務理解と技術の融合による意思決定 なのです。\n5. 戦術的勤勉 vs 戦略的怠惰 低レイヤを極めること自体は悪いことではありません。\nしかし、それが「業務理解を放棄する言い訳」になってしまっては本末転倒です。\n戦術的勤勉：低レイヤを丹念に学ぶ（手段）\n戦略的怠惰：業務要件の理解を怠る（目的の喪失）\nまずは正しい問題設定──すなわち「ビジネスの意図」を明確にすること。\nそのうえで、低レイヤの知識を活用して実装や最適化を行う。\nこの順序こそが、エンジニアリングにおける本質的なアプローチです。\n6. 結論：抽象化の流れは正しい。ただし、バランスが鍵 モダンな抽象化は「意図に近づく」ための進化であり、退化ではない。\n低レイヤ知識は「検証・トラブルシューティング・高性能化」のために必要だが、目的ではない。\n成功するエンジニアは、**業務理解（何を伝えるか）と 技術力（どう実現するか）**をバランスよく使い分ける。\n本質は「技術」ではなく「意図」。\n技術は、意図を実現するための道具にすぎない。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-27adec63ef2b94/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/27adec63ef2b94\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"意図ビジネス要件と低レイヤ知識の役割について考える\"\u003e〜意図（ビジネス要件）と低レイヤ知識の役割について考える〜\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e「最近のエンジニアは低レイヤを知らない」──そんな声を耳にすることが増えました。\u003cbr\u003e\nそれは本当に問題なのでしょうか？\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-プログラミングの本質意図をコンピュータに伝えること\"\u003e1. プログラミングの本質：意図をコンピュータに伝えること\u003c/h2\u003e\n\u003cp\u003eプログラミングとは、人間の意図を\u003cstrong\u003eコードという形でコンピュータに伝える行為\u003c/strong\u003e です。\u003cbr\u003e\n言語やフレームワークはそのための「伝達チャンネル」にすぎません。\u003cbr\u003e\n用途や目的が異なるからこそ、多様な言語やフレームワークが存在しています。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e業務アプリ：可読性・保守性を重視\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eゲームやリアルタイム処理：性能やレイテンシを重視\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e研究・プロトタイピング：表現力・試行錯誤のしやすさを重視\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e重要なのは「\u003cstrong\u003e何を伝えたいのか（ビジネスの意図）\u003c/strong\u003e 」であり、\u003cbr\u003e\n低レイヤ（ハードウェアや OS の仕組み）はそれを実現するための\u003cstrong\u003e手段\u003c/strong\u003e にすぎません。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"2-抽象化はノイズ除去の進化である\"\u003e2. 抽象化は「ノイズ除去」の進化である\u003c/h2\u003e\n\u003cp\u003eソフトウェア開発の歴史を振り返ると、それは常に「ノイズを減らす」試みの連続でした。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eアセンブリ言語：レジスタやメモリ操作のノイズを扱う\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e高級言語：ポインタ操作や手動メモリ管理のノイズを隠蔽\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eモダンフレームワーク：接続プールやリトライなど運用上のノイズを隠蔽\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eこの観点から見ると、抽象化は「情報理論的に正しい方向への進化」です。\u003cbr\u003e\n不要な実装詳細を隠すことで、\u003cstrong\u003e開発者はより本質的な意図に集中できる\u003c/strong\u003e ようになります。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"3-それでも低レイヤ知識は不要にならない\"\u003e3. それでも低レイヤ知識は不要にならない\u003c/h2\u003e\n\u003cp\u003e抽象化が進んだからといって、低レイヤの知識が不要になるわけではありません。\u003cbr\u003e\n実際には「抽象の漏れ（Leaky Abstraction）」や、フレームワークの誤用によって問題が生じることがあります。\u003c/p\u003e\n\u003cp\u003eそうしたときに求められるのは、\u003cstrong\u003e下のレイヤを理解し、意図が正しく実現されているかを検証する力\u003c/strong\u003e です。\u003cbr\u003e\nただし、低レイヤの学習そのものが目的化してしまうと、肝心の「ビジネス意図の理解」が置き去りになります。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"4-最適化の本質は情報の圧縮である\"\u003e4. 最適化の本質は「情報の圧縮」である\u003c/h2\u003e\n\u003cp\u003e最適化とは、突き詰めれば「情報の圧縮」です。\u003cbr\u003e\nプログラムはビジネスの意図を機械命令に符号化したものとも言えます。\u003cbr\u003e\n良い最適化とは、\u003cstrong\u003e業務知識（確信）をもとに冗長性を取り除くこと\u003c/strong\u003e によって成立します。\u003c/p\u003e\n\u003cp\u003eたとえば：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eキャッシュ導入の根拠は「そのデータは一定期間変化しない」という業務知見\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eインデックス設計の根拠は「特定フィールドの検索頻度が高い」という利用傾向\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eつまり、\u003cstrong\u003e最適化とは単なる技術的チューニングではなく、業務理解と技術の融合による意思決定\u003c/strong\u003e なのです。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"5-戦術的勤勉-vs-戦略的怠惰\"\u003e5. 戦術的勤勉 vs 戦略的怠惰\u003c/h2\u003e\n\u003cp\u003e低レイヤを極めること自体は悪いことではありません。\u003cbr\u003e\nしかし、それが「業務理解を放棄する言い訳」になってしまっては本末転倒です。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e戦術的勤勉：低レイヤを丹念に学ぶ（手段）\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e戦略的怠惰：業務要件の理解を怠る（目的の喪失）\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eまずは正しい問題設定──すなわち「ビジネスの意図」を明確にすること。\u003cbr\u003e\nそのうえで、低レイヤの知識を活用して実装や最適化を行う。\u003cbr\u003e\nこの順序こそが、エンジニアリングにおける本質的なアプローチです。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"6-結論抽象化の流れは正しいただしバランスが鍵\"\u003e6. 結論：抽象化の流れは正しい。ただし、バランスが鍵\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eモダンな抽象化は「意図に近づく」ための進化であり、退化ではない。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e低レイヤ知識は「検証・トラブルシューティング・高性能化」のために必要だが、目的ではない。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e成功するエンジニアは、**業務理解（何を伝えるか）\u003cstrong\u003eと\u003c/strong\u003e 技術力（どう実現するか）**をバランスよく使い分ける。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e本質は「技術」ではなく「意図」。\u003cbr\u003e\n技術は、意図を実現するための道具にすぎない。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"🧠 モダンなフレームワークは「低レイヤの理解」から遠ざけるか？"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに C++11 で「右辺値参照（rvalue reference）」が導入されたことで、C++ の値分類（value category）は大きく進化しました。\nC++98 では「左辺値（lvalue）」と「右辺値（rvalue）」の二分法しかありませんでしたが、C++11 ではさらに細かく分類されます：\nC++11で導入された新しい概念である\nxvalue（期限切れ右辺値） と prvalue（純粋右辺値） が、\nこの章の中核的な進化ポイントです。\nC++98 時代の「単純な世界」 C++98 の時代はとてもシンプルでした。\n左辺値 (lvalue) ：代入の左側に置けるもの。永続的なアドレスを持つ。\n例：a, *ptr, array[i]\n右辺値 (rvalue) ：一時的な値。すぐに破棄される。\n例：a + 5, 42, func()\nルールも直感的でした：\nint a = 3; \u0026amp;a; // OK 左辺値はアドレスが取れる \u0026amp;(a + 5); // エラー！右辺値はアドレスが取れない しかしこの単純さが、性能の壁 になっていました。\n問題：無駄なコピーが多すぎる std::vector\u0026lt;int\u0026gt; create_vector() { std::vector\u0026lt;int\u0026gt; temp; temp.push_back(1); temp.push_back(2); temp.push_back(3); return temp; // ここで何が起こる？ } std::vector\u0026lt;int\u0026gt; v = create_vector(); C++98 の仕様では return temp; で コピーが発生 します。\ntemp のヒープメモリをまるごと複製し、呼び出し側に返すのです。\nRVO（Return Value Optimization） により最適化できる場合もありますが、\nあくまでコンパイラ依存で、保証はありません。\n委員会の発想：「コピーせずに“盗む”ことはできないか？」 2000年代初期、C++標準化委員会が考えました。\n「どうせこの一時オブジェクトはすぐ消える。なら資源をコピーせず盗めないか？ 」\nつまりこうです：\nstd::vector\u0026lt;int\u0026gt; v = create_vector(); このとき、create_vector() の戻り値が「もうすぐ死ぬ」ことを言語レベルで認識できれば、\nヒープの所有権をそのまま v に移動 させることができます。\nこれが「ムーブセマンティクス（move semantics） 」の誕生です。\nでも、どのオブジェクトが「盗める」の？ 単純に「右辺値なら盗める」とすると、こんな問題が出ます。\nstd::vector\u0026lt;int\u0026gt; v1 = {1, 2, 3}; std::vector\u0026lt;int\u0026gt; v2 = v1; // v1 は右辺値？→違う！ v1 は 名前を持つ オブジェクト。つまり左辺値。\n「まだ使うかもしれない」ので、資源を盗んではいけません。\nではどうするか？\nstd::move の登場と「期限切れ右辺値 (xvalue)」 std::vector\u0026lt;int\u0026gt; v1 = {1, 2, 3}; std::vector\u0026lt;int\u0026gt; v2 = std::move(v1); std::move(v1) は、左辺値を「盗んでもいい状態」に変換 します。\n構文的には左辺値（名前を持つ）\nしかし意味的には「もう使わない」\nこの「見た目は左辺値、意味は右辺値 」な存在こそが、\nC++11 最大の革新―― xvalue（期限切れ右辺値） です。\n5分類の整理 種類 名称 例 特徴 lvalue 左辺値 a, *ptr, obj.member 名前・アドレスあり。盗めない prvalue 純粋右辺値 42, a + b, create_vector() 一時的。アドレスなし。盗める xvalue 期限切れ右辺値 std::move(v) アドレスあり。資源を移動できる glvalue 一般化左辺値 lvalue + xvalue の総称 アドレスを持つ式 rvalue 右辺値 prvalue + xvalue の総称 資源を移動できる式 つまり実体としては3つ：\nlvalue / prvalue / xvalue\n残り2つは分類上の集合にすぎません。\nなぜここまで複雑にしたのか？ 理由はひとつ。\n“移動可能かつ識別可能” なオブジェクトを正しく表現するため。\n例を見てみましょう：\nstd::vector\u0026lt;int\u0026gt; create_vector() { std::vector\u0026lt;int\u0026gt; temp = {1, 2, 3}; return temp; } auto\u0026amp;\u0026amp; ref = create_vector(); std::cout \u0026lt;\u0026lt; ref.size() \u0026lt;\u0026lt; std::endl; create_vector() の戻り値は一見右辺値（rvalue）ですが、\nref に束縛されて寿命が延長されています。\nこれは「アドレスがあり、かつ移動可能」。\nすなわち xvalue（期限切れ右辺値） です。\nこれにより可能になったこと 移動コンストラクタ / 移動代入 std::vector\u0026lt;int\u0026gt; v2 = std::move(v1); // move ctor v2 = std::move(v3); // move assignment 値の種類によって、自動的に「コピー」か「ムーブ」が選ばれます。\n完全転送（Perfect Forwarding） template\u0026lt;typename T\u0026gt; void wrapper(T\u0026amp;\u0026amp; arg) { real_function(std::forward\u0026lt;T\u0026gt;(arg)); } この T\u0026amp;\u0026amp; は「右辺値参照」ではなく「転送参照（forwarding reference）」。\n左辺値を渡す → T は int\u0026amp;、arg は左辺値\n右辺値を渡す → T は int、arg は xvalue\nstd::forward が、元の値カテゴリを保持して転送します。\nこれも xvalue が存在するおかげです。\n他言語との比較 言語 解決アプローチ 特徴 Rust 所有権システム 所有権が自動移動。std::move 不要 Java/C# 参照型＋GC コピーもムーブも不要。性能コストあり Python 全て参照渡し コピー概念なし。副作用リスクあり C++ 値と資源の制御を開発者に委ねる ゼロオーバーヘッドを追求 C++ は「全て自分で管理する」という哲学を貫いています。\nそれが難しさであり、強さでもあります。\nまとめ：覚えておきたい実戦ポイント 関数からローカルを返すときはコピーされない （C++11以降自動でムーブ）\n資源を移したいときはstd::move\nテンプレート引数の転送にはstd::forward\nlvalue = 名前を持つ\nprvalue = 一時的な値\nxvalue = “使い終わった” 左辺値\n結論 C++11 の値カテゴリ体系は一見複雑ですが、\nこれを理解すれば「C++の現代的な型システムの本質」に到達します。\n「C++ は、シンプルな問題を複雑にする最も上手い言語」\n——しかし、その複雑さの裏には“ハードウェアの限界を引き出す力”がある。\nこれは呪いであり、同時に誇りでもあります。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-bdadcd1b03c135/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/bdadcd1b03c135\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003eC++11 で「右辺値参照（rvalue reference）」が導入されたことで、C++ の値分類（value category）は大きく進化しました。\u003cbr\u003e\nC++98 では「左辺値（lvalue）」と「右辺値（rvalue）」の二分法しかありませんでしたが、C++11 ではさらに細かく分類されます：\u003c/p\u003e\n\u003cp\u003eC++11で導入された新しい概念である\u003cbr\u003e\n\u003cstrong\u003exvalue（期限切れ右辺値）\u003c/strong\u003e と \u003cstrong\u003eprvalue（純粋右辺値）\u003c/strong\u003e が、\u003cbr\u003e\nこの章の中核的な進化ポイントです。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"c98-時代の単純な世界\"\u003eC++98 時代の「単純な世界」\u003c/h2\u003e\n\u003cp\u003eC++98 の時代はとてもシンプルでした。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e左辺値 (lvalue)\u003c/strong\u003e ：代入の左側に置けるもの。永続的なアドレスを持つ。\u003cbr\u003e\n例：\u003ccode\u003ea\u003c/code\u003e, \u003ccode\u003e*ptr\u003c/code\u003e, \u003ccode\u003earray[i]\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e右辺値 (rvalue)\u003c/strong\u003e ：一時的な値。すぐに破棄される。\u003cbr\u003e\n例：\u003ccode\u003ea + 5\u003c/code\u003e, \u003ccode\u003e42\u003c/code\u003e, \u003ccode\u003efunc()\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eルールも直感的でした：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eint a = 3;\n\u0026amp;a;        // OK 左辺値はアドレスが取れる\n\u0026amp;(a + 5);  // エラー！右辺値はアドレスが取れない\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eしかしこの単純さが、\u003cstrong\u003e性能の壁\u003c/strong\u003e になっていました。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"問題無駄なコピーが多すぎる\"\u003e問題：無駄なコピーが多すぎる\u003c/h2\u003e\n\u003cpre\u003e\u003ccode\u003estd::vector\u0026lt;int\u0026gt; create_vector() {\n    std::vector\u0026lt;int\u0026gt; temp;\n    temp.push_back(1);\n    temp.push_back(2);\n    temp.push_back(3);\n    return temp;  // ここで何が起こる？\n}\n\nstd::vector\u0026lt;int\u0026gt; v = create_vector();\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eC++98 の仕様では \u003ccode\u003ereturn temp;\u003c/code\u003e で \u003cstrong\u003eコピーが発生\u003c/strong\u003e します。\u003cbr\u003e\n\u003ccode\u003etemp\u003c/code\u003e のヒープメモリをまるごと複製し、呼び出し側に返すのです。\u003c/p\u003e","title":"🌟 C++11 が左辺値と右辺値を5分類に拡張した理由"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに UGUIで部分的にハイライトを当てたい場面、特にチュートリアル中の誘導UI では「特定のボタンだけを明るく見せ、他の部分を半透明で覆う」演出がよく使われます。\n一般的には Mask や RectMask2D を利用しますが、これらにはいくつかの課題があります。\nMask ：Stencilバッファを使うため、描画コストが高い\nRectMask2D ：矩形領域しか扱えず、穴あき表現（逆マスク） はできない\nそこで今回は、MaskableGraphic を継承し、完全プログラム制御で「逆マスク（穴あきマスク）」を生成する方法 を紹介します。\n最終的には、ハイライト部分がクリック可能で、他の部分はブロックされる 、高性能な HollowOutMask コンポーネントを実装していきます。\n目標仕様 機能 説明 視覚効果 自身の RectTransform 領域内に半透明のマスクを描画し、指定の RectTransform 部分を「くり抜く」 射線処理 穴の部分はクリックが通過 し、マスク部分はクリックをブロック 動的追従 対象オブジェクトが動いたりスケールが変化しても、くり抜き領域が追従 これを、Mask 系コンポーネントを使わずに、頂点操作 だけで実現します。\n実装の基本構造 HollowOutMask は MaskableGraphic を継承し、OnPopulateMesh() をオーバーライドします。\n頂点生成の考え方 想像してみてください。\nマスク全体を表す「外側の矩形（Outer Rect）」と、くり抜く「内側の矩形（Inner Rect）」があるとします。\nこの２つの矩形を組み合わせ、外側を塗りつつ内側を抜くには、8つの頂点 と8枚の三角形 が必要です。\nコード例：OnPopulateMesh() protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); if (_isTargetNull) { base.OnPopulateMesh(vh); return; } // Outer Rect（自身のRectTransform基準） float outerLx = rectTransform.rect.xMin; float outerBy = rectTransform.rect.yMin; float outerRx = rectTransform.rect.xMax; float outerTy = rectTransform.rect.yMax; vh.AddVert(new Vector3(outerLx, outerTy), color, Vector2.zero); vh.AddVert(new Vector3(outerRx, outerTy), color, Vector2.zero); vh.AddVert(new Vector3(outerRx, outerBy), color, Vector2.zero); vh.AddVert(new Vector3(outerLx, outerBy), color, Vector2.zero); // Inner Rect（穴部分） float innerLx = _targetMin.x; float innerBy = _targetMin.y; float innerRx = _targetMax.x; float innerTy = _targetMax.y; vh.AddVert(new Vector3(innerLx, innerTy), color, Vector2.zero); vh.AddVert(new Vector3(innerRx, innerTy), color, Vector2.zero); vh.AddVert(new Vector3(innerRx, innerBy), color, Vector2.zero); vh.AddVert(new Vector3(innerLx, innerBy), color, Vector2.zero); // Triangles（外側と内側の間を埋める） vh.AddTriangle(0, 1, 5); vh.AddTriangle(5, 4, 0); vh.AddTriangle(1, 2, 6); vh.AddTriangle(6, 5, 1); vh.AddTriangle(2, 3, 7); vh.AddTriangle(7, 6, 2); vh.AddTriangle(3, 0, 4); vh.AddTriangle(4, 7, 3); } これで、中央が透明な「リング状ポリゴン を生成できます。\nポイントは：\nすべての頂点を 同一ローカル座標系 で扱うこと\n外側と内側の矩形を8つの三角形 に分割すること\nターゲット位置の動的追跡 HollowOutMask の肝は、ターゲットの位置やサイズが変わったときに、くり抜き部分を自動更新する点です。\nLateUpdateによる安定した追従 void LateUpdate() { bool selfRectChanged = (_lastSelfRect != rectTransform.rect); bool targetMatrixChanged = (_lastTargetMatrix != _target.localToWorldMatrix); if (selfRectChanged || targetMatrixChanged) { ForceRefresh(); } } なぜ LateUpdate？ UGUIのレイアウト更新（LayoutGroupなど）はUpdate中に行われるため、\nLateUpdateで追従することで 「1フレーム遅れ」や「不一致」 を防ぎます。\n高効率な変化検知 _target.localToWorldMatrix は、Transformのあらゆる変更（位置・回転・スケール）を反映する行列です。\nこれをキャッシュ比較することで、最小コストで変化を検知できます。\nForceRefresh：再計算と再描画 private void ForceRefresh() { _lastSelfRect = rectTransform.rect; if (_isTargetNull) return; _lastTargetMatrix = _target.localToWorldMatrix; _target.GetWorldCorners(_targetWorldCorners); Matrix4x4 selfWorldToLocal = rectTransform.worldToLocalMatrix; Vector3 vMin = new(float.MaxValue, float.MaxValue, float.MaxValue); Vector3 vMax = new(float.MinValue, float.MinValue, float.MinValue); for (int i = 0; i \u0026lt; 4; i++) { Vector3 localPoint = selfWorldToLocal.MultiplyPoint3x4(_targetWorldCorners[i]); vMin = Vector3.Min(vMin, localPoint); vMax = Vector3.Max(vMax, localPoint); } _targetMin = vMin; _targetMax = vMax; SetVerticesDirty(); } これにより、世界座標 → 自身ローカル座標 の変換を正確に行い、\nCanvasに「頂点データ更新が必要」と通知します。\n射線処理：ICanvasRaycastFilter の活用 最後に、穴の部分をクリックが通過 するように設定します。\npublic bool IsRaycastLocationValid(Vector2 screenPos, Camera eventCamera) { if (!isActiveAndEnabled) return true; if (_isTargetNull) return true; // 穴の部分は Raycast 無効（クリックを通過させる） return !RectTransformUtility.RectangleContainsScreenPoint(_target, screenPos, eventCamera); } EventSystem からの「この点は命中したか？」という問いに対して：\n穴の部分 → 「命中していない」(false) → 下のボタンまで届く\nマスク部分 → 「命中した」(true) → クリックをブロック\nという仕組みになります。\nまとめ 要素 解説 MaskやRectMask2Dを使わない パフォーマンス改善と柔軟性確保 頂点を自前で生成 任意形状のマスクを実現 LateUpdate＋Matrix比較 高速かつ正確な位置追従 ICanvasRaycastFilter 穴部分のクリック透過を簡潔に実装 この方法を使えば、\n「特定UIをハイライトしつつ、他を暗く覆う」 演出を、軽量かつ完全制御で実現できます。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-731d17a8464927/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/731d17a8464927\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003eUGUIで部分的にハイライトを当てたい場面、特に\u003cstrong\u003eチュートリアル中の誘導UI\u003c/strong\u003e では「特定のボタンだけを明るく見せ、他の部分を半透明で覆う」演出がよく使われます。\u003cbr\u003e\n一般的には \u003ccode\u003eMask\u003c/code\u003e や \u003ccode\u003eRectMask2D\u003c/code\u003e を利用しますが、これらにはいくつかの課題があります。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e\u003ccode\u003eMask\u003c/code\u003e\u003c/strong\u003e ：Stencilバッファを使うため、描画コストが高い\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e\u003ccode\u003eRectMask2D\u003c/code\u003e\u003c/strong\u003e ：矩形領域しか扱えず、\u003cstrong\u003e穴あき表現（逆マスク）\u003c/strong\u003e はできない\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eそこで今回は、\u003ccode\u003eMaskableGraphic\u003c/code\u003e を継承し、\u003cstrong\u003e完全プログラム制御で「逆マスク（穴あきマスク）」を生成する方法\u003c/strong\u003e を紹介します。\u003cbr\u003e\n最終的には、\u003cstrong\u003eハイライト部分がクリック可能で、他の部分はブロックされる\u003c/strong\u003e 、高性能な \u003ccode\u003eHollowOutMask\u003c/code\u003e コンポーネントを実装していきます。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"目標仕様\"\u003e目標仕様\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e機能\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e説明\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e視覚効果\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e自身の \u003ccode\u003eRectTransform\u003c/code\u003e 領域内に半透明のマスクを描画し、指定の \u003ccode\u003eRectTransform\u003c/code\u003e 部分を「くり抜く」\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e射線処理\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e穴の部分は\u003cstrong\u003eクリックが通過\u003c/strong\u003e し、マスク部分は\u003cstrong\u003eクリックをブロック\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e動的追従\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e対象オブジェクトが動いたりスケールが変化しても、くり抜き領域が追従\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eこれを、\u003ccode\u003eMask\u003c/code\u003e 系コンポーネントを使わずに、\u003cstrong\u003e頂点操作\u003c/strong\u003e だけで実現します。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"実装の基本構造\"\u003e実装の基本構造\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eHollowOutMask\u003c/code\u003e は \u003ccode\u003eMaskableGraphic\u003c/code\u003e を継承し、\u003ccode\u003eOnPopulateMesh()\u003c/code\u003e をオーバーライドします。\u003c/p\u003e\n\u003ch3 id=\"頂点生成の考え方\"\u003e頂点生成の考え方\u003c/h3\u003e\n\u003cp\u003e想像してみてください。\u003cbr\u003e\nマスク全体を表す「外側の矩形（Outer Rect）」と、くり抜く「内側の矩形（Inner Rect）」があるとします。\u003c/p\u003e\n\u003cp\u003eこの２つの矩形を組み合わせ、外側を塗りつつ内側を抜くには、\u003cstrong\u003e8つの頂点\u003c/strong\u003e と\u003cstrong\u003e8枚の三角形\u003c/strong\u003e が必要です。\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"コード例onpopulatemesh\"\u003eコード例：OnPopulateMesh()\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003eprotected override void OnPopulateMesh(VertexHelper vh)\n{\n    vh.Clear();\n\n    if (_isTargetNull)\n    {\n        base.OnPopulateMesh(vh);\n        return;\n    }\n\n    // Outer Rect（自身のRectTransform基準）\n    float outerLx = rectTransform.rect.xMin;\n    float outerBy = rectTransform.rect.yMin;\n    float outerRx = rectTransform.rect.xMax;\n    float outerTy = rectTransform.rect.yMax;\n\n    vh.AddVert(new Vector3(outerLx, outerTy), color, Vector2.zero);\n    vh.AddVert(new Vector3(outerRx, outerTy), color, Vector2.zero);\n    vh.AddVert(new Vector3(outerRx, outerBy), color, Vector2.zero);\n    vh.AddVert(new Vector3(outerLx, outerBy), color, Vector2.zero);\n\n    // Inner Rect（穴部分）\n    float innerLx = _targetMin.x;\n    float innerBy = _targetMin.y;\n    float innerRx = _targetMax.x;\n    float innerTy = _targetMax.y;\n\n    vh.AddVert(new Vector3(innerLx, innerTy), color, Vector2.zero);\n    vh.AddVert(new Vector3(innerRx, innerTy), color, Vector2.zero);\n    vh.AddVert(new Vector3(innerRx, innerBy), color, Vector2.zero);\n    vh.AddVert(new Vector3(innerLx, innerBy), color, Vector2.zero);\n\n    // Triangles（外側と内側の間を埋める）\n    vh.AddTriangle(0, 1, 5); vh.AddTriangle(5, 4, 0);\n    vh.AddTriangle(1, 2, 6); vh.AddTriangle(6, 5, 1);\n    vh.AddTriangle(2, 3, 7); vh.AddTriangle(7, 6, 2);\n    vh.AddTriangle(3, 0, 4); vh.AddTriangle(4, 7, 3);\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eこれで、\u003cstrong\u003e中央が透明な「リング状ポリゴン\u003c/strong\u003e を生成できます。\u003c/p\u003e","title":"💡 【Unity UGUI】Maskを使わずに実現する「穴あき」ハイライト演出"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに 多くの人は「UI開発＝手の速さ」だと思っています。\nでも実際のプロジェクトでは、進行を遅らせる原因はコードの速さではなく、データフローとUI構造設計の悪さ です。\nUnity（UGUI）で時間を食う典型的なポイントを挙げると、以下のようになります。\nデータ前処理がバラバラ ：ソートやフィルタが各UIに散らばっていると、UI層が処理待ちになる。\n階層構造が深すぎる ：1回の更新で大量のTransformを走査、処理も重くバグも出やすい。\nアニメーション・状態管理が未抽象化 ：新しい状態を追加するたびにコードを壊す。\n同じランキングUIでも、ある人は半日で完成、ある人は3日かかる。\nその差は、手の速さではなく設計の差 です。\nデータ層とUI層を明確に分離する ありがちなアンチパターンは、OnClick() の中で\nデータ取得 → オブジェクト生成 → UI更新を全部やるケース。\n短期的には動いても、UI改修のたびに全部作り直しになります。\nもっと良い設計はこうです👇\nDataService / Manager にデータロジックをまとめる\nUI は DTO（Data Transfer Object） を受け取って表示専念\nデータ前処理は Python や外部ツールで事前に済ませる\nこうすると要件変更が来ても、UI側はほとんど触らずに済みます。\nデータ層が変わってもUIは安定、これが真の開発効率化です。\nリスト描画は「オブジェクトプール」が必須 ランキング、インベントリ、スキルリストなど、\n毎回 Instantiate ＋ Destroy していると、\n処理が重くなるだけでなく、開発時間も無駄に増えます。\n対策： オブジェクトプールを構築する ：\n再利用可能なUI要素をキャッシュして、再描画時はデータだけ差し替え。\nシンプルな PoolManager でOK ：\n使い終わった要素は SetActive(false) して再利用。\nScrollRect の仮想化レンダリングと併用 ：\n数千件でも軽く動作。\nあるプロジェクトでは、インベントリUIの描画時間が\n500セルで200ms → プール導入後は30msに短縮。\n体感で「カクつき」が消えました。\nUIアニメーションはモジュール化せよ 多くのチームでは、UIごとにアニメーションをバラバラに実装しており、\n時間が経つと修正不能なスパゲッティになります。\nベストプラクティス： 共通の UIアニメーションコントローラ を定義\nShow() と Hide() を統一インターフェースとして提供 DOTween / Animator を内部で使い分け\nイベント連携は「コールバック」で行い、アニメーション内にロジックを書かない\nこうしておけば、デザイン変更が入ってもコントローラだけ差し替えで済みます。\nUIロジックは一切手を触れずに済むのです。\nツールと自動化こそが最大の効率化手段 「早く書く」ことより、「書かなくても済む」環境を作るほうが強い。\nおすすめのツール化： 自動バインド生成\nEditor拡張でPrefabに紐づくUI要素を自動コード生成。\n手動の Find() は完全廃止。\nデータ駆動UI\nレイアウトを設定ファイルで制御し、\nプランナーがExcelを編集するだけでUI更新可能。\n一括編集ツール\nフォントやアンカーを一括変更できるツールを用意。\n機械的な作業を全削減。\nチーム全体の標準化が本当の効率化 個人が速くても、チームがバラバラなら総合的には遅くなります。\n本当に効率が上がるのは「統一された開発文化」があるとき。\n成功チームに共通するポイント： UI規約ドキュメントの整備\nPrefab命名、コンポーネント構成、命名規則を統一。\n共通UIフレームワーク\n全UIが BaseUI を継承し、ライフサイクルが統一されている。\nホットリロード対応\nUIロジックをホット更新できるようにして、\nプランナーが自分で調整できる。\nこうした共通基盤が整うと、チーム全体のスピードが倍以上に跳ね上がります。\nまとめ UI開発の効率差は 10倍 ついても不思議ではありません。\nその分かれ目は、次の4点にあります。\nデータとUIを分離できているか\nリスト描画に再利用設計があるか\nアニメーションをモジュール化しているか\nツールとチーム標準が整っているか\n「全部自分で書くしかない」と思っているうちは、\nまだ本当の効率化に到達していません。\nUIは「見た目」ではなく、設計と工程の芸術 です。\n速く書けるのは職人。\n壊れずに書けるのはエンジニア。\n速くて壊れないのが、本当のプロ。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-143fe2d003d031/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/143fe2d003d031\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003e多くの人は「UI開発＝手の速さ」だと思っています。\u003cbr\u003e\nでも実際のプロジェクトでは、進行を遅らせる原因は\u003cstrong\u003eコードの速さではなく、データフローとUI構造設計の悪さ\u003c/strong\u003e です。\u003c/p\u003e\n\u003cp\u003eUnity（UGUI）で時間を食う典型的なポイントを挙げると、以下のようになります。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eデータ前処理がバラバラ\u003c/strong\u003e ：ソートやフィルタが各UIに散らばっていると、UI層が処理待ちになる。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e階層構造が深すぎる\u003c/strong\u003e ：1回の更新で大量のTransformを走査、処理も重くバグも出やすい。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eアニメーション・状態管理が未抽象化\u003c/strong\u003e ：新しい状態を追加するたびにコードを壊す。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e同じランキングUIでも、ある人は半日で完成、ある人は3日かかる。\u003cbr\u003e\nその差は、手の速さではなく\u003cstrong\u003e設計の差\u003c/strong\u003e です。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"データ層とui層を明確に分離する\"\u003eデータ層とUI層を明確に分離する\u003c/h2\u003e\n\u003cp\u003eありがちなアンチパターンは、\u003ccode\u003eOnClick()\u003c/code\u003e の中で\u003cbr\u003e\nデータ取得 → オブジェクト生成 → UI更新を全部やるケース。\u003c/p\u003e\n\u003cp\u003e短期的には動いても、UI改修のたびに全部作り直しになります。\u003c/p\u003e\n\u003cp\u003eもっと良い設計はこうです👇\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eDataService / Manager\u003c/strong\u003e にデータロジックをまとめる\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eUI は \u003cstrong\u003eDTO（Data Transfer Object）\u003c/strong\u003e を受け取って表示専念\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eデータ前処理は Python や外部ツールで事前に済ませる\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eこうすると要件変更が来ても、UI側はほとんど触らずに済みます。\u003cbr\u003e\nデータ層が変わってもUIは安定、これが真の開発効率化です。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"リスト描画はオブジェクトプールが必須\"\u003eリスト描画は「オブジェクトプール」が必須\u003c/h2\u003e\n\u003cp\u003eランキング、インベントリ、スキルリストなど、\u003cbr\u003e\n毎回 \u003ccode\u003eInstantiate\u003c/code\u003e ＋ \u003ccode\u003eDestroy\u003c/code\u003e していると、\u003cbr\u003e\n処理が重くなるだけでなく、開発時間も無駄に増えます。\u003c/p\u003e\n\u003ch3 id=\"対策\"\u003e対策：\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eオブジェクトプールを構築する\u003c/strong\u003e ：\u003cbr\u003e\n再利用可能なUI要素をキャッシュして、再描画時はデータだけ差し替え。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eシンプルな PoolManager でOK\u003c/strong\u003e ：\u003cbr\u003e\n使い終わった要素は \u003ccode\u003eSetActive(false)\u003c/code\u003e して再利用。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eScrollRect の仮想化レンダリングと併用\u003c/strong\u003e ：\u003cbr\u003e\n数千件でも軽く動作。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eあるプロジェクトでは、インベントリUIの描画時間が\u003cbr\u003e\n500セルで200ms → プール導入後は30msに短縮。\u003cbr\u003e\n体感で「カクつき」が消えました。\u003c/p\u003e","title":"📚 UI開発を10倍速くするための実戦ポイント"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに C++ のテンプレートには数多くの応用テクニックがありますが、その中でも「CRTP（Curiously Recurring Template Pattern：奇妙な再帰テンプレートパターン） 」は特にユニークで強力な手法です。\nBoost をはじめとする有名ライブラリでも多用されており、C++ の表現力を大きく広げてくれます。\nこの記事では、CRTP の基本から実用例、さらには応用パターンまでを整理して解説します。\nCRTP とは？ CRTP の基本形は以下のようになります。\ntemplate \u0026lt;typename T\u0026gt; class Base { public: T\u0026amp; getDerived() { return static_cast\u0026lt;T\u0026amp;\u0026gt;(*this); } }; class Derived : public Base\u0026lt;Derived\u0026gt; { // Derived 自身を Base のテンプレート引数として渡す }; 一見すると「派生クラス Derived が、まだ完成していない自分自身を基底クラス Base に渡している」ように見えます。この“自己参照”こそが CRTP の特徴です。\nコンパイラはこれを問題なく処理でき、基底クラスはコンパイル時に派生クラスの型情報を知ることができます。\n実用例：比較演算子の自動生成 C++ では == や \u0026lt; などの比較演算子を定義するのはよくある作業ですが、すべてを個別に書くと冗長になりがちです。\nCRTP を用いると、基底クラスに「共通の比較演算子」をまとめ、派生クラスは最小限の実装だけで済ませられます。\n基底クラス template \u0026lt;typename Derived\u0026gt; class Comparable { public: bool operator==(const Derived\u0026amp; other) const { return static_cast\u0026lt;const Derived*\u0026gt;(this)-\u0026gt;equalsImpl(other); } bool operator!=(const Derived\u0026amp; other) const { return !(*this == other); } bool operator\u0026lt;(const Derived\u0026amp; other) const { return static_cast\u0026lt;const Derived*\u0026gt;(this)-\u0026gt;lessImpl(other); } bool operator\u0026gt;(const Derived\u0026amp; other) const { return other \u0026lt; *this; } bool operator\u0026lt;=(const Derived\u0026amp; other) const { return !(*this \u0026gt; other); } bool operator\u0026gt;=(const Derived\u0026amp; other) const { return !(*this \u0026lt; other); } }; 派生クラス例 class Point : public Comparable\u0026lt;Point\u0026gt; { public: Point(int x_val, int y_val) : x(x_val), y(y_val) {} bool equalsImpl(const Point\u0026amp; other) const { return x == other.x \u0026amp;\u0026amp; y == other.y; } bool lessImpl(const Point\u0026amp; other) const { return (x != other.x) ? (x \u0026lt; other.x) : (y \u0026lt; other.y); } private: int x, y; }; これで ==, !=, \u0026lt;, \u0026gt;, \u0026lt;=, \u0026gt;= がすべて利用可能になります。\nCRTP の利点 静的ポリモーフィズム\n仮想関数を使わずに「派生クラスが特定のメソッドを持つ」ことをコンパイル時に保証できる。\nゼロコスト抽象化\n実行時の仮想関数呼び出し（vtable 参照）を避け、インライン展開が可能。\nコード再利用\n共通処理を基底クラスに集約し、派生クラスは本質的な処理のみを書く。\n応用パターン CRTP は比較演算子以外にもさまざまな用途があります。\nMixin の実装\nロギング機能を自動付与\nインスタンス数カウンタ\nシングルトン制約\nポリシーベースデザイン\nメモリ管理戦略\nスレッドセーフ戦略\nインターフェース強制\n派生クラスに特定のメソッド実装をコンパイル時に強制 イテレータ実装支援\nBoost の iterator_facade が代表例 まとめ CRTP は「基底クラスが派生クラスを知っている」という一見逆説的な構造を活かし、静的ポリモーフィズムやコード再利用を実現する強力なテクニックです。\nC++ を本格的に使いこなしたい人にとって必須の知識であり、Boost をはじめとする実用ライブラリでも広く応用されています。\n「奇妙」な見た目に反して、その応用範囲と恩恵は非常に大きい。これを理解し使いこなせば、あなたの C++ コードはさらに強力で表現力豊かになるでしょう。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-b13f0c8ee91a17/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/b13f0c8ee91a17\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003eC++ のテンプレートには数多くの応用テクニックがありますが、その中でも「\u003cstrong\u003eCRTP（Curiously Recurring Template Pattern：奇妙な再帰テンプレートパターン）\u003c/strong\u003e 」は特にユニークで強力な手法です。\u003cbr\u003e\nBoost をはじめとする有名ライブラリでも多用されており、C++ の表現力を大きく広げてくれます。\u003c/p\u003e\n\u003cp\u003eこの記事では、CRTP の基本から実用例、さらには応用パターンまでを整理して解説します。\u003c/p\u003e\n\u003ch2 id=\"crtp-とは\"\u003eCRTP とは？\u003c/h2\u003e\n\u003cp\u003eCRTP の基本形は以下のようになります。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003etemplate \u0026lt;typename T\u0026gt;\nclass Base {\npublic:\n    T\u0026amp; getDerived() { return static_cast\u0026lt;T\u0026amp;\u0026gt;(*this); }\n};\n\nclass Derived : public Base\u0026lt;Derived\u0026gt; {\n    // Derived 自身を Base のテンプレート引数として渡す\n};\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e一見すると「派生クラス \u003ccode\u003eDerived\u003c/code\u003e が、まだ完成していない自分自身を基底クラス \u003ccode\u003eBase\u003c/code\u003e に渡している」ように見えます。この“自己参照”こそが CRTP の特徴です。\u003cbr\u003e\nコンパイラはこれを問題なく処理でき、基底クラスはコンパイル時に派生クラスの型情報を知ることができます。\u003c/p\u003e\n\u003ch2 id=\"実用例比較演算子の自動生成\"\u003e実用例：比較演算子の自動生成\u003c/h2\u003e\n\u003cp\u003eC++ では \u003ccode\u003e==\u003c/code\u003e や \u003ccode\u003e\u0026lt;\u003c/code\u003e などの比較演算子を定義するのはよくある作業ですが、すべてを個別に書くと冗長になりがちです。\u003cbr\u003e\nCRTP を用いると、基底クラスに「共通の比較演算子」をまとめ、派生クラスは最小限の実装だけで済ませられます。\u003c/p\u003e\n\u003ch3 id=\"基底クラス\"\u003e基底クラス\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003etemplate \u0026lt;typename Derived\u0026gt;\nclass Comparable {\npublic:\n    bool operator==(const Derived\u0026amp; other) const {\n        return static_cast\u0026lt;const Derived*\u0026gt;(this)-\u0026gt;equalsImpl(other);\n    }\n    bool operator!=(const Derived\u0026amp; other) const {\n        return !(*this == other);\n    }\n\n    bool operator\u0026lt;(const Derived\u0026amp; other) const {\n        return static_cast\u0026lt;const Derived*\u0026gt;(this)-\u0026gt;lessImpl(other);\n    }\n    bool operator\u0026gt;(const Derived\u0026amp; other) const { return other \u0026lt; *this; }\n    bool operator\u0026lt;=(const Derived\u0026amp; other) const { return !(*this \u0026gt; other); }\n    bool operator\u0026gt;=(const Derived\u0026amp; other) const { return !(*this \u0026lt; other); }\n};\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"派生クラス例\"\u003e派生クラス例\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003eclass Point : public Comparable\u0026lt;Point\u0026gt; {\npublic:\n    Point(int x_val, int y_val) : x(x_val), y(y_val) {}\n    bool equalsImpl(const Point\u0026amp; other) const {\n        return x == other.x \u0026amp;\u0026amp; y == other.y;\n    }\n    bool lessImpl(const Point\u0026amp; other) const {\n        return (x != other.x) ? (x \u0026lt; other.x) : (y \u0026lt; other.y);\n    }\nprivate:\n    int x, y;\n};\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eこれで \u003ccode\u003e==\u003c/code\u003e, \u003ccode\u003e!=\u003c/code\u003e, \u003ccode\u003e\u0026lt;\u003c/code\u003e, \u003ccode\u003e\u0026gt;\u003c/code\u003e, \u003ccode\u003e\u0026lt;=\u003c/code\u003e, \u003ccode\u003e\u0026gt;=\u003c/code\u003e がすべて利用可能になります。\u003c/p\u003e","title":"💡 C++ の「奇妙な再帰テンプレートパターン（CRTP）」を理解する"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに 「どうすればプログラミングが上手くなるのか？」\nこれは、ゲーム業界を目指すエンジニアにとって、常につきまとうテーマです。\nUnity や Unreal Engine などの便利なエンジンやライブラリが整っている時代でも、「本当にゲームを作れる」プログラマーになるためには、言語やフレームワークの知識以上に重要な根本的スキル があります。\n本記事では、ゲームプログラマーとして実務を経験する中で特に大事だと感じる3つの力 を紹介します。\n① 抽象化の力 —— 巨大なコードを「分けて」考える ゲーム開発は、数千〜数百万行にもなるコードの集合です。\nその全てを頭に入れることは不可能なので、必要な部分だけをブラックボックスとして扱う 考え方が必須になります。\n例えば、「プレイヤーが持つ全ての武器を取得する」処理を作る場合を考えます。\n// Repositoryパターンによる例 class WeaponRepository { public List\u0026lt;Weapon\u0026gt; GetAllWeapons(PlayerId playerId); } この WeaponRepository クラスを作るときは、\nDBやセーブデータへのアクセス\nログやエラーハンドリング\nフォーマット変換\nなど、たくさんの下位レイヤのライブラリを呼び出しますが、上のレイヤからは「GetAllWeapons」だけ見えればよい のです。\nこれが抽象化であり、モジュールを積み重ねて大規模ゲームを成立させる基本 です。\n実戦ポイント オブジェクト指向（OOP）、関数型（FP）、手続き型といった 複数のパラダイムを状況に応じて使い分ける\nトップダウン（設計から実装）とボトムアップ（共通処理の抽出）を行き来しながら開発する\n抽象化しすぎると開発が重くなり、抽象化しなさすぎるとコピペ地獄になるため、バランスを取る\n② デバッグの力 —— ブラックボックスを「開ける」技術 抽象化したコードは便利ですが、バグが起きたときには中を開ける必要 があります。\nたとえば「インベントリ画面に武器一覧が表示されない」と報告されたとき、どこから原因を追うべきでしょうか？\nF12（DevTools）や curl でクライアント→サーバ間通信を確認\nサーバで WeaponRepository.GetAllWeapons() の戻り値をログ出力\nDBにクエリを直打ちして期待通りの結果か確認\nこうして層を一つずつ下って調査 するのがデバッグの基本です。\nこのスキルはバグ修正だけでなく、新しいプロジェクトやチームに参加したときにコードを理解する力 にもなります。\n実戦ポイント\nログやアサートを常に入れておく\nデバッガでステップ実行できるように設計する\nバグ報告を「再現可能な条件」に落とし込む\n③ 規律と習慣 —— 安定して動くコードのために 多くの人は天才ではありません。\n「バグを生まない」のではなく、「バグが出ても壊れない」ようにする のが現実的な戦い方です。\nそのためには、個人の感覚ではなく規律とプロセス が必要です。\n入力値を常に疑い、防御的にコーディングする\n単体テスト・統合テストを用意する\nログとエラーハンドリングを丁寧に書く\nコードレビューやペアプロを習慣化する\n実戦ポイント\n「人は必ずミスする」前提で設計する\npageSizeなど外部入力は必ずバリデーションする\n「動いたコードをそのまま放置」せずリファクタする\n技術はあくまで手段 最新のツールやフレームワーク、AI補完やIDEは開発効率を大幅に上げますが、\nそれらは本質ではなく、「抽象」「デバッグ」「規律」を支えるための手段 です。\nゲーム開発では、\n便利なエンジン機能（UnityやUE）を理解し使いこなす力\nその内部が壊れたときに調査・修正できる力\nチーム全体が安定して動けるようにする力\nが求められます。\nつまり「何を使うか」ではなく、「どう考えるか 」「どう学ぶか 」が最も重要です。\n何より、たくさん手を動かして失敗し、改善する こと。\nその経験こそが、最も確実にあなたを「強いゲームプログラマー」にしてくれます。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-f6d20f7d12e8b4/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/f6d20f7d12e8b4\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003e「どうすればプログラミングが上手くなるのか？」\u003cbr\u003e\nこれは、ゲーム業界を目指すエンジニアにとって、常につきまとうテーマです。\u003c/p\u003e\n\u003cp\u003eUnity や Unreal Engine などの便利なエンジンやライブラリが整っている時代でも、「本当にゲームを作れる」プログラマーになるためには、\u003cstrong\u003e言語やフレームワークの知識以上に重要な根本的スキル\u003c/strong\u003e があります。\u003c/p\u003e\n\u003cp\u003e本記事では、ゲームプログラマーとして実務を経験する中で特に大事だと感じる\u003cstrong\u003e3つの力\u003c/strong\u003e を紹介します。\u003c/p\u003e\n\u003ch2 id=\"-抽象化の力--巨大なコードを分けて考える\"\u003e① 抽象化の力 —— 巨大なコードを「分けて」考える\u003c/h2\u003e\n\u003cp\u003eゲーム開発は、数千〜数百万行にもなるコードの集合です。\u003cbr\u003e\nその全てを頭に入れることは不可能なので、\u003cstrong\u003e必要な部分だけをブラックボックスとして扱う\u003c/strong\u003e 考え方が必須になります。\u003c/p\u003e\n\u003cp\u003e例えば、「プレイヤーが持つ全ての武器を取得する」処理を作る場合を考えます。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e// Repositoryパターンによる例\nclass WeaponRepository {\n    public List\u0026lt;Weapon\u0026gt; GetAllWeapons(PlayerId playerId);\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eこの \u003ccode\u003eWeaponRepository\u003c/code\u003e クラスを作るときは、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eDBやセーブデータへのアクセス\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eログやエラーハンドリング\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eフォーマット変換\u003cbr\u003e\nなど、たくさんの下位レイヤのライブラリを呼び出しますが、\u003cstrong\u003e上のレイヤからは「GetAllWeapons」だけ見えればよい\u003c/strong\u003e のです。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eこれが抽象化であり、\u003cstrong\u003eモジュールを積み重ねて大規模ゲームを成立させる基本\u003c/strong\u003e です。\u003c/p\u003e\n\u003ch3 id=\"実戦ポイント\"\u003e実戦ポイント\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eオブジェクト指向（OOP）、関数型（FP）、手続き型といった \u003cstrong\u003e複数のパラダイムを状況に応じて使い分ける\u003c/strong\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eトップダウン（設計から実装）とボトムアップ（共通処理の抽出）を行き来しながら開発する\u003c/strong\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e抽象化しすぎると開発が重くなり、抽象化しなさすぎるとコピペ地獄になるため、バランスを取る\u003c/strong\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"-デバッグの力--ブラックボックスを開ける技術\"\u003e② デバッグの力 —— ブラックボックスを「開ける」技術\u003c/h2\u003e\n\u003cp\u003e抽象化したコードは便利ですが、\u003cstrong\u003eバグが起きたときには中を開ける必要\u003c/strong\u003e があります。\u003c/p\u003e\n\u003cp\u003eたとえば「インベントリ画面に武器一覧が表示されない」と報告されたとき、どこから原因を追うべきでしょうか？\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eF12（DevTools）や \u003ccode\u003ecurl\u003c/code\u003e でクライアント→サーバ間通信を確認\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eサーバで \u003ccode\u003eWeaponRepository.GetAllWeapons()\u003c/code\u003e の戻り値をログ出力\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eDBにクエリを直打ちして期待通りの結果か確認\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eこうして\u003cstrong\u003e層を一つずつ下って調査\u003c/strong\u003e するのがデバッグの基本です。\u003cbr\u003e\nこのスキルはバグ修正だけでなく、\u003cstrong\u003e新しいプロジェクトやチームに参加したときにコードを理解する力\u003c/strong\u003e にもなります。\u003c/p\u003e\n\u003cp\u003e実戦ポイント\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eログやアサートを常に入れておく\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eデバッガでステップ実行できるように設計する\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eバグ報告を「再現可能な条件」に落とし込む\u003c/p\u003e","title":"🔥 ゲームプログラマーとして成長するために大切な3つの力"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに 本記事では、私自身が経験したことや、信頼できる実践的な知見を元に、C++で高速な数値処理や行列演算を行う際に意識すべき最適化ポイント をわかりやすく整理しました。\nHPCやリアルタイム演算に関心のある方に参考になれば幸いです。\n1. メモリのアライメントとキャッシュ効率 アライメント（__attribute__((aligned(n))） CPUはキャッシュライン（通常64バイト）単位 でメモリを読み込む。\nメモリが正しく整列されていないと、キャッシュミスやfalse sharing が発生し、性能が大幅に低下。\nGCC/Clangなら __attribute__((aligned(32))) など、MSVCでは __declspec(align(32)) を使う。\n連続したメモリを使用する（std::vector\u0026lt;T\u0026gt;など） 行列は行優先(row-major)の順番で連続配置することで、CPUの事前読み込み(prefetch)が効きやすくなる。\nstd::vector\u0026lt;std::vector\u0026lt;float\u0026gt;\u0026gt; のような非連続構造は避ける。\n2. メモリ確保の最適化：高速メモリアロケータ 標準の malloc や new は汎用的で安全ですが、性能面ではボトルネックになることがあります。\nおすすめのアロケータ tcmalloc （Google製、スレッドセーフ）\njemalloc （Facebook製、ゲームやサーバーで人気）\nHoard （スケーラブルなスレッド対応）\nこれらを使うと、多スレッド環境でのアロケーション性能が大幅に向上 します。\n3. 行列演算の高速化手法 Strassenアルゴリズム（O(n^2.81)） 通常のO(n³)よりも理論上速い。\n再帰的に分割しながら計算することで、掛け算回数を削減。\nブロック分割（Block-based Multiplication） 大きな行列を小さなサブブロックに分けて計算 すると、キャッシュが有効活用できる。\n代表的なライブラリ（後述するBLASなど）ではこの最適化が組み込まれている。\n4. 並列計算（マルチスレッド化） C++11の標準スレッド std::thread, std::async を使って並列化可能。\nシンプルな制御だが、スレッド数の管理や同期は自前で対応が必要。\nOpenMP #pragma omp parallel for のように、簡潔にfor文を並列化。\nコンパイラ依存（GCC/Clang/MSVC全て対応）。\nIntel TBB（Thread Building Blocks） 動的なタスクスケジューリング・負荷分散・ワークスティーリングが可能。\n不規則な処理を高速並列化したいときに強力。\n5. SIMD命令の活用（SSE / AVX） SIMD（Single Instruction, Multiple Data） を使うと、1命令で複数のデータを同時処理できる。\n例：AVX2なら、32bit float を8個同時に加算や乗算 できる。\n#include \u0026lt;immintrin.h\u0026gt;\n__m256 a = _mm256_load_ps(\u0026hellip;); __m256 b = _mm256_load_ps(\u0026hellip;); __m256 c = _mm256_mul_ps(a, b);\n-mavx2 などのフラグが必要（GCC/Clang）。\nメモリアライメント必須。\n6. ループ内の不要な計算を減らす for (int i = 0; i \u0026lt; N; ++i) { double val = expensive_func(); // 毎回呼び出すのは無駄 data[i] += val * i; } ↓ 最適化後：\ndouble val = expensive_func(); // ループ外に出す for (int i = 0; i \u0026lt; N; ++i) { data[i] += val * i; } ループの中は徹底的に「軽く」する ことが原則です。\n7. コンパイラ最適化フラグの活用 -O3: 高レベル最適化（ループ展開、ベクトル化など）\n-Ofast: -O3 よりさらに積極的。-ffast-math を含み、浮動小数点の精度保証を緩和。\n-march=native: 現在のCPUアーキテクチャに最適化。\n注意 ：-Ofast はNaNの扱いや丸め誤差が変わる場合があるため、数値の正確性が重要な場合は注意。\n8. 高速な数値ライブラリの活用（BLAS / LAPACK） 自分で行列計算を書くのではなく、信頼できる数学ライブラリを使うのがベストです。\nBLAS（Basic Linear Algebra Subprograms） ：行列の基本操作\nLAPACK ：LU分解やSVDなどの高度なアルゴリズム\n実装例：\nIntel MKL（無料だがプロプライエタリ）\nOpenBLAS（オープンソース）\ncuBLAS（GPU向け）\n9. プロファイリングと性能解析 使用ツール ツール名 内容 gprof 関数単位の呼び出し・時間分析 Valgrind メモリリーク・キャッシュグラインド Intel VTune 本格的なCPUマイクロアーキ解析 perf (Linux) ハードウェアカウンタベースの分析 最適化の第一歩は「測定すること」 。予想ではなく、データでボトルネックを特定する のが重要です。\nおわりに C++による高性能計算は奥が深いですが、正しい知識と習慣を身につければ、数倍〜数十倍の性能差 を実現することも可能です。\n今回紹介した内容はほんの入り口にすぎませんが、実際の開発現場でも非常に役立つポイントです。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-d2508bad444e4d/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/d2508bad444e4d\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003e本記事では、私自身が経験したことや、信頼できる実践的な知見を元に、\u003cstrong\u003eC++で高速な数値処理や行列演算を行う際に意識すべき最適化ポイント\u003c/strong\u003e をわかりやすく整理しました。\u003cbr\u003e\nHPCやリアルタイム演算に関心のある方に参考になれば幸いです。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-メモリのアライメントとキャッシュ効率\"\u003e1. メモリのアライメントとキャッシュ効率\u003c/h2\u003e\n\u003ch3 id=\"アライメント__attribute__alignedn\"\u003eアライメント（\u003ccode\u003e__attribute__((aligned(n))\u003c/code\u003e）\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eCPUは\u003cstrong\u003eキャッシュライン（通常64バイト）単位\u003c/strong\u003e でメモリを読み込む。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eメモリが正しく整列されていないと、\u003cstrong\u003eキャッシュミスやfalse sharing\u003c/strong\u003e が発生し、性能が大幅に低下。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eGCC/Clangなら \u003ccode\u003e__attribute__((aligned(32)))\u003c/code\u003e など、MSVCでは \u003ccode\u003e__declspec(align(32))\u003c/code\u003e を使う。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"連続したメモリを使用するstdvectortなど\"\u003e連続したメモリを使用する（\u003ccode\u003estd::vector\u0026lt;T\u0026gt;\u003c/code\u003eなど）\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e行列は行優先(row-major)の順番で連続配置することで、CPUの事前読み込み(prefetch)が効きやすくなる。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003estd::vector\u0026lt;std::vector\u0026lt;float\u0026gt;\u0026gt;\u003c/code\u003e のような非連続構造は避ける。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"2-メモリ確保の最適化高速メモリアロケータ\"\u003e2. メモリ確保の最適化：高速メモリアロケータ\u003c/h2\u003e\n\u003cp\u003e標準の \u003ccode\u003emalloc\u003c/code\u003e や \u003ccode\u003enew\u003c/code\u003e は汎用的で安全ですが、性能面ではボトルネックになることがあります。\u003c/p\u003e\n\u003ch3 id=\"おすすめのアロケータ\"\u003eおすすめのアロケータ\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003etcmalloc\u003c/strong\u003e （Google製、スレッドセーフ）\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003ejemalloc\u003c/strong\u003e （Facebook製、ゲームやサーバーで人気）\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eHoard\u003c/strong\u003e （スケーラブルなスレッド対応）\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eこれらを使うと、\u003cstrong\u003e多スレッド環境でのアロケーション性能が大幅に向上\u003c/strong\u003e します。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"3-行列演算の高速化手法\"\u003e3. 行列演算の高速化手法\u003c/h2\u003e\n\u003ch3 id=\"strassenアルゴリズムon281\"\u003eStrassenアルゴリズム（O(n^2.81)）\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e通常のO(n³)よりも理論上速い。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e再帰的に分割しながら計算することで、掛け算回数を削減。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"ブロック分割block-based-multiplication\"\u003eブロック分割（Block-based Multiplication）\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e大きな行列を\u003cstrong\u003e小さなサブブロックに分けて計算\u003c/strong\u003e すると、キャッシュが有効活用できる。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e代表的なライブラリ（後述するBLASなど）ではこの最適化が組み込まれている。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"4-並列計算マルチスレッド化\"\u003e4. 並列計算（マルチスレッド化）\u003c/h2\u003e\n\u003ch3 id=\"c11の標準スレッド\"\u003eC++11の標準スレッド\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003estd::thread\u003c/code\u003e, \u003ccode\u003estd::async\u003c/code\u003e を使って並列化可能。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eシンプルな制御だが、スレッド数の管理や同期は自前で対応が必要。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"openmp\"\u003eOpenMP\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003e#pragma omp parallel for\u003c/code\u003e のように、簡潔にfor文を並列化。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eコンパイラ依存（GCC/Clang/MSVC全て対応）。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"intel-tbbthread-building-blocks\"\u003eIntel TBB（Thread Building Blocks）\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e動的なタスクスケジューリング・負荷分散・ワークスティーリングが可能。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e不規則な処理を高速並列化したいときに強力。\u003c/p\u003e","title":"😯 C++で高性能計算を書くときに意識すべき最適化ポイントまとめ"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに C++11 から導入された emplace_back は、コンテナに要素をその場で構築できる便利なメソッドです。\n一方、それ以前から使われてきた push_back とはどんな違いがあり、emplace_back は完全に push_back を代替できるのでしょうか？\n本記事では、\nemplace_back が本当に push_back より常に速いのか\n両者がどんなケースに使えるのか\n実装の違いと注意点\nについて、自分なりに整理した考えをまとめます。\npush_back と emplace_back の違い push_back // C++20以降 constexpr void push_back(const T\u0026amp; value); // コピー constexpr void push_back(T\u0026amp;\u0026amp; value); // ムーブ push_back は、すでに生成されたオブジェクトをコピーまたはムーブして格納する関数です。\nemplace_back // C++20以降 template\u0026lt;class... Args\u0026gt; constexpr reference emplace_back(Args\u0026amp;\u0026amp;... args); // その場で構築 emplace_back は、コンストラクタに渡す引数をそのまま受け取り、コンテナ内部で直接オブジェクトを構築します。そのため、中間オブジェクトなし に要素を追加できる点が特徴です。\nこれらの大きな違いは、push_backが既に生成されたオブジェクト（T型）をコピーまたはムーブする一方で、emplace_backは任意のコンストラクタ引数からその場でT型のインスタンスを構築できる点です。\nLLVMにおける内部実装 以下はLLVM(libc++)におけるpush_back内部の実装です。\nif (this-\u0026gt;__end_ != this-\u0026gt;__end_cap()) { __construct_one_at_end(__x); // 残り容量がある場合、__xをコピーまたはムーブ構築 } else { __push_back_slow_path(__x); // 足りない場合、新規領域を確保して再配置 } __construct_one_at_end の中では、allocatorが提供する construct() を使って、指定されたメモリにオブジェクトを構築しています。\nまた、allocatorが construct() を持たない場合には単純に in-place new を呼び出す仕組みです。\n__push_back_slow_path 容量が足りない場合は、__split_buffer を使って新規メモリを確保し、既存要素をムーブまたはコピー後、新しい要素を追加してから内部バッファをスワップします。\nこれにより、強い例外保証が確保され、例外が投げられても元のベクタが壊れないように作られているのがポイントです。\nemplace_back の内部 非特化版 emplace_back は以下のように、任意の引数から直接要素を構築します。\nif (this-\u0026gt;__end_ \u0026lt; this-\u0026gt;__end_cap()) { __construct_one_at_end(std::forward\u0026lt;Args\u0026gt;(args)...); } else { __emplace_back_slow_path(std::forward\u0026lt;Args\u0026gt;(args)...); } slow path も push_back とほぼ同じで、新しいメモリ領域を確保してからその場で構築を行っています。\n大きな違い は、__construct_one_at_end に渡す引数が「T型そのものではなく、Tのコンストラクタ引数」である点です。\n性能面で emplace_back は常に有利か？ 結論から言うと、必ずしも有利とは限りません 。\n有利なケース 要素が重たいコピーコンストラクタを持ち、かつムーブできない場合。\nコンテナ内部で直接構築できると、一時オブジェクトが不要なので効率的。\n反例：リテラルからの文字列構築 Arthur O\u0026rsquo;Dwyer の記事「Don\u0026rsquo;t blindly prefer emplace_back to push_back」では、以下のPythonコードでベンチマークが取られていました。\nimport sys print('#include \u0026lt;vector\u0026gt;') print('#include \u0026lt;string\u0026gt;') print('extern std::vector\u0026lt;std::string\u0026gt; v;') for i in range(1000): print(f'void test{i}() {{') print(f' v.{sys.argv[1]}_back(\u0026quot;{ \u0026quot;A\u0026quot; * i }\u0026quot;);') print('}') これにより、例えば次のような呼び出しが生成されます。\nv.push_back(\u0026quot;A\u0026quot;); v.push_back(\u0026quot;AA\u0026quot;); v.push_back(\u0026quot;AAA\u0026quot;); // ... 省略 ... v.emplace_back(\u0026quot;A\u0026quot;); v.emplace_back(\u0026quot;AA\u0026quot;); v.emplace_back(\u0026quot;AAA\u0026quot;); // ... 省略 ... 結果 push_backでは、各呼び出しで単にstring(const char*)が呼ばれます。\nemplace_backではリテラル長ごとに1000通りの異なるテンプレート展開 が行われ、コンパイル負荷と実行速度に影響が出ます。\nベンチマークではpush_back版が1.0s、emplace_back版が4.2sとなり、明らかにemplace_backが遅いケースがあることが示されました。\n結論：使い分けが重要 多くの場合、複雑なオブジェクトを直接構築できるemplace_back が有利。\nただし、単純に既存オブジェクトやリテラルから追加するだけの場合は push_back でも十分。\nテンプレート展開によるコンパイル影響が気になる場面では、push_back を選ぶべきです。\nまとめ emplace_backは中間オブジェクトなしに直接構築できるため、高コストなコピーが避けられる場面では有利です。\nただし、単純に既存オブジェクトやリテラルから追加するだけの場合、テンプレート展開やコード膨張が性能面でマイナスになる場合があります。\n結局のところ、状況に応じて使い分けることが大切です。 両者にはそれぞれ利点と適用場面があるからこそ、標準ライブラリには二通りが用意されているのです。\n※ 本記事は Qiita にも同時投稿しています。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-c73aa0d12885ff/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/c73aa0d12885ff\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003eC++11 から導入された \u003ccode\u003eemplace_back\u003c/code\u003e は、コンテナに要素をその場で構築できる便利なメソッドです。\u003cbr\u003e\n一方、それ以前から使われてきた \u003ccode\u003epush_back\u003c/code\u003e とはどんな違いがあり、\u003ccode\u003eemplace_back\u003c/code\u003e は完全に \u003ccode\u003epush_back\u003c/code\u003e を代替できるのでしょうか？\u003c/p\u003e\n\u003cp\u003e本記事では、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003eemplace_back\u003c/code\u003e が本当に \u003ccode\u003epush_back\u003c/code\u003e より常に速いのか\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e両者がどんなケースに使えるのか\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e実装の違いと注意点\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eについて、自分なりに整理した考えをまとめます。\u003c/p\u003e\n\u003ch2 id=\"push_back-と-emplace_back-の違い\"\u003e\u003ccode\u003epush_back\u003c/code\u003e と \u003ccode\u003eemplace_back\u003c/code\u003e の違い\u003c/h2\u003e\n\u003ch3 id=\"push_back\"\u003e\u003ccode\u003epush_back\u003c/code\u003e\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003e// C++20以降\nconstexpr void push_back(const T\u0026amp; value);   // コピー\nconstexpr void push_back(T\u0026amp;\u0026amp; value);       // ムーブ\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ccode\u003epush_back\u003c/code\u003e は、すでに生成されたオブジェクトをコピーまたはムーブして格納する関数です。\u003c/p\u003e\n\u003ch3 id=\"emplace_back\"\u003e\u003ccode\u003eemplace_back\u003c/code\u003e\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003e// C++20以降\ntemplate\u0026lt;class... Args\u0026gt;\nconstexpr reference emplace_back(Args\u0026amp;\u0026amp;... args);  // その場で構築\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ccode\u003eemplace_back\u003c/code\u003e は、コンストラクタに渡す引数をそのまま受け取り、コンテナ内部で直接オブジェクトを構築します。そのため、\u003cstrong\u003e中間オブジェクトなし\u003c/strong\u003e に要素を追加できる点が特徴です。\u003c/p\u003e\n\u003cp\u003eこれらの大きな違いは、\u003ccode\u003epush_back\u003c/code\u003eが\u003cstrong\u003e既に生成されたオブジェクト（T型）\u003cstrong\u003eをコピーまたはムーブする一方で、\u003ccode\u003eemplace_back\u003c/code\u003eは\u003c/strong\u003e任意のコンストラクタ引数からその場でT型のインスタンスを構築できる\u003c/strong\u003e点です。\u003c/p\u003e\n\u003ch2 id=\"llvmにおける内部実装\"\u003eLLVMにおける内部実装\u003c/h2\u003e\n\u003cp\u003e以下はLLVM(libc++)における\u003ccode\u003epush_back\u003c/code\u003e内部の実装です。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eif (this-\u0026gt;__end_ != this-\u0026gt;__end_cap()) {\n    __construct_one_at_end(__x); // 残り容量がある場合、__xをコピーまたはムーブ構築\n} else {\n    __push_back_slow_path(__x);   // 足りない場合、新規領域を確保して再配置\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ccode\u003e__construct_one_at_end\u003c/code\u003e の中では、allocatorが提供する \u003ccode\u003econstruct()\u003c/code\u003e を使って、指定されたメモリにオブジェクトを構築しています。\u003cbr\u003e\nまた、allocatorが \u003ccode\u003econstruct()\u003c/code\u003e を持たない場合には単純に in-place new を呼び出す仕組みです。\u003c/p\u003e","title":"💡 C++ の emplace_back は push_back を完全に置き換えられるか？"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに C++ の std::function は「小さいオブジェクトならヒープ確保しない（Small Object Optimization, SOO）」という知識は一般的です。しかし、以下のようなコードでは意外な挙動が観察されます：\nstruct A { int value; A(int v) : value(v) {} A(const A \u0026amp;other) : value(other.value) {} }; struct B { int value; B(int v) : value(v) {} }; A a{0}; B b{0}; std::function\u0026lt;void()\u0026gt; funcA = [a] {}; // ヒープ確保あり std::function\u0026lt;void()\u0026gt; funcB = [b] {}; // ヒープ確保なし このように、コピーコンストラクタの有無だけ で挙動が変わるのはなぜでしょうか？\nstd::function がローカル格納を行う条件（GCC/libstdc++ 編） libstdc++ の実装（GCC 標準ライブラリ）では、以下の条件を満たすとオブジェクトはヒープではなくローカルに保存されます：\nstatic const bool __stored_locally = (__is_location_invariant\u0026lt;_Functor\u0026gt;::value \u0026amp;\u0026amp; sizeof(_Functor) \u0026lt;= _M_max_size \u0026amp;\u0026amp; __alignof__(_Functor) \u0026lt;= _M_max_align \u0026amp;\u0026amp; (_M_max_align % __alignof__(_Functor) == 0)); この中で特に重要なのが：\ntemplate\u0026lt;typename _Tp\u0026gt; struct __is_location_invariant : is_trivially_copyable\u0026lt;_Tp\u0026gt;::type {}; つまり、トリビアルコピー可能（is_trivially_copyable）であることがローカル格納の前提条件です。\nA はユーザー定義のコピーコンストラクタを持っているため、is_trivially_copyable が false\nB はコピーコンストラクタが定義されておらず、コンパイラが暗黙定義 → トリビアルコピー可能\nそのため、[a]{} はヒープ確保、[b]{} はローカル格納となります。\nlibc++（Clang）や MSVC の挙動は？ Clang の libc++ や MSVC の std::function 実装では、GCC より条件が緩和されています。たとえば libc++ では以下の条件です：\nif (sizeof(_Fun) \u0026lt;= sizeof(__buf_) \u0026amp;\u0026amp; is_nothrow_copy_constructible\u0026lt;_Fp\u0026gt;::value \u0026amp;\u0026amp; is_nothrow_copy_constructible\u0026lt;_FunAlloc\u0026gt;::value) { // ローカル格納 } つまり、トリビアルである必要はなく、noexcept 付きのコピーコンストラクタであればローカル格納できます。\n試しに以下のように A に noexcept をつけてみてください：\nA(const A \u0026amp;other) noexcept : value(other.value) {} この変更だけで、std::function のヒープ確保がなくなります。\n✅ Godbolt 上で確認する\n各コンパイラでの std::function サイズとヒープ確保の違い 以下のコードで std::function 自体のサイズを比較してみます：\nstd::function\u0026lt;void()\u0026gt; func = [a]{}; printf(\u0026quot;%zu\\n\u0026quot;, sizeof(func)); 結果（x64 プラットフォーム）：\nコンパイラ ヒープ確保サイズ std::function のサイズ GCC 4 bytes 32 bytes Clang/libc++ 16 bytes 48 bytes MSVC 16 bytes 64 bytes GCC は最も省メモリな設計で、関数オブジェクトを memcpy できるように限定することで、コピーやムーブの方法を記録するコストも回避しています。 参考リンク Godbolt デモ（コピーに noexcept あり）\nGodbolt デモ（サイズ比較）\nまとめ libstdc++（GCC） : is_trivially_copyable がローカル格納の条件。コピー可能だがトリビアルでないとヒープ確保。\nlibc++（Clang）・MSVC : noexcept 付きコピーコンストラクタであればヒープ確保せずローカル格納。\nパフォーマンスと ABI のトレードオフ ：GCC は高速・軽量だが制約が厳しい。libc++ は柔軟だがメモリコスト増。\nこのような細かい挙動の違いは、高パフォーマンスなコードやバグ回避 において重要なポイントになります。ぜひ、自分の環境で noexcept の有無やサイズを試してみてください。\nソースコードを読むと、すべてが明らかになります😉\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-fdcfc181acf35d/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/fdcfc181acf35d\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003eC++ の \u003ccode\u003estd::function\u003c/code\u003e は「小さいオブジェクトならヒープ確保しない（Small Object Optimization, SOO）」という知識は一般的です。しかし、以下のようなコードでは意外な挙動が観察されます：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003estruct A {\n    int value;\n    A(int v) : value(v) {}\n    A(const A \u0026amp;other) : value(other.value) {}\n};\nstruct B {\n    int value;\n    B(int v) : value(v) {}\n};\n\nA a{0};\nB b{0};\n\nstd::function\u0026lt;void()\u0026gt; funcA = [a] {}; // ヒープ確保あり\nstd::function\u0026lt;void()\u0026gt; funcB = [b] {}; // ヒープ確保なし\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eこのように、\u003cstrong\u003eコピーコンストラクタの有無だけ\u003c/strong\u003e で挙動が変わるのはなぜでしょうか？\u003c/p\u003e\n\u003ch2 id=\"stdfunction-がローカル格納を行う条件gcclibstdc-編\"\u003e\u003ccode\u003estd::function\u003c/code\u003e がローカル格納を行う条件（GCC/libstdc++ 編）\u003c/h2\u003e\n\u003cp\u003elibstdc++ の実装（GCC 標準ライブラリ）では、以下の条件を満たすとオブジェクトはヒープではなくローカルに保存されます：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003estatic const bool __stored_locally =\n  (__is_location_invariant\u0026lt;_Functor\u0026gt;::value \u0026amp;\u0026amp;\n   sizeof(_Functor) \u0026lt;= _M_max_size \u0026amp;\u0026amp;\n   __alignof__(_Functor) \u0026lt;= _M_max_align \u0026amp;\u0026amp;\n   (_M_max_align % __alignof__(_Functor) == 0));\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eこの中で特に重要なのが：\u003c/p\u003e","title":"🎉 std::function がヒープメモリを確保する条件とは？— コピーキャプチャされた変数の型がカギ"},{"content":" 📌 この記事は Zenn に投稿した内容のアーカイブです。\nはじめに Photon Fusionは、高性能・高柔軟性なネットワーク同期システムを提供する次世代ネットワークライブラリです。\nその中でも特に重要な役割を担っているのが、[Networked] 属性です。\nこの属性を使うことで、プレイヤーの位置やアニメーション状態、ゲーム内オブジェクトの変化 などを自動的に同期でき、煩雑な手動のデータ送受信処理を省くことができます。\n本シリーズでは、Fusionの内部同期処理を段階的に掘り下げて解説していきますが、今回は、[Networked] 属性の裏側で何が起きているのか、どのようにして自動同期が実現されているのかを詳しく見ていきます。\nFusionにおける[Networked]属性の同期処理概要 [Networked] 属性は？ Fusionでは、[Networked] 属性が付与されたプロパティはネットワーク状態の同期対象 として扱われますが、ユーザーが手動で同期コードを書く必要はありません。\nこの仕組みは、Unityが提供する ILPostProcessor API を活用しており、C# で書かれたコードをビルド後にIL（中間言語）レベルで自動変換 しています。\nIL\nILは、.NET における中間言語です。\nUnity では、C# で書いたスクリプトは必ずこの IL を経由してからネイティブコードに変換されます。\n具体的には、以下のような処理が行われます：\nNetworkedWeaved という属性を追加して、該当プロパティがネットワーク状態バッファに割り当てられることを明示\ngetter/setter が NetworkBehaviour の内部バッファ（StateBuffer）へのメモリアクセスに差し替え られる\n実行時に Spawned() されていない場合のアクセス制限も挿入される\n変換前のC#コード： [Networked] private Vector2 _position { get; set; } IL変換後（ILSpyで逆コンパイルしたコード）： [Networked] [NetworkedWeaved(0, 2)] private unsafe Vector2 _position { get { if (base.Ptr == null) { throw new InvalidOperationException(\u0026quot;Error when accessing PhotonCapybara._position. Networked properties can only be accessed when Spawned() has been called.\u0026quot;); } return (Vector2)base.Ptr[0]; } set { if (base.Ptr == null) { throw new InvalidOperationException(\u0026quot;Error when accessing PhotonCapybara._position. Networked properties can only be accessed when Spawned() has been called.\u0026quot;); } Unsafe.Write(base.Ptr + 0, value); } } 補足解説： base.Ptr は NetworkBehaviour に割り当てられたネットワーク状態メモリの先頭ポインタ です。\nbase.Ptr[0] はオフセット0の位置からデータを読み書きします（ここでは _position のデータが格納されている）\nUnsafe.Write() を使うことで、型変換を伴わない高速なバイナリ書き込み が可能になります\nNetworkedWeaved(0, 2) の 0 はオフセットインデックス、2 は使用ワード数（32bit単位）を示します\nNetworked値はどのように同期される？ IL変換された [Networked] プロパティは、NetworkBehaviourのStateBufferに書き込まれるだけでは同期は完了しません 。\n実際には、以下のようなステップで「同期パケット」に含まれて送信されます：\nSimulation.Update() 内部で StateBuffer の変化を検出\nScanStructForChanges() 関数で、現在の StateBuffer と前回スナップショットを比較します。\n差分があれば、それが「更新対象」としてマークされます。\nPreparePacket() で同期データをバッファに詰める\n差分があるフィールドのみが、送信パケット用バッファに書き出されます。\nFusion はこのとき、可能な限り bit圧縮 を行って帯域を節約します。\n送信→受信→復元処理（Deserialize）\nリモート側では、受信したパケットを元に StateBuffer が上書きされ、プロパティの get 結果が最新値になります。 このように、[Networked] 属性は IL変換 → メモリ書き込み → 差分抽出 → パケット送信 → 同期反映 の一連の流れで、自動的に同期処理が行われているのです。\n見えない同期の仕組みを理解することの意味 [Networked] 属性は、一見ただのマークアップのように見えますが、実際にはILレベルで動作を拡張し、パフォーマンスと汎用性を両立した設計 になっていることがわかりました。\nFusionを「使う」だけでなく「理解して使う」ためには、こうした仕組みを知ることが非常に重要です。\n今後の記事では、さらに深く掘り下げていきます。\n本記事はQiitaで公開した内容を、Zenn向けに加筆したものです。\n","permalink":"https://wuyukwi.duckdns.org/posts/zenn-79685434adc2f5/","summary":"\u003cblockquote\u003e\n\u003cp\u003e📌 \u003cem\u003eこの記事は \u003ca href=\"https://zenn.dev/wuyukwi/articles/79685434adc2f5\"\u003eZenn\u003c/a\u003e に投稿した内容のアーカイブです。\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"はじめに\"\u003eはじめに\u003c/h2\u003e\n\u003cp\u003ePhoton Fusionは、高性能・高柔軟性なネットワーク同期システムを提供する次世代ネットワークライブラリです。\u003cbr\u003e\nその中でも特に重要な役割を担っているのが、\u003ccode\u003e[Networked]\u003c/code\u003e 属性です。\u003c/p\u003e\n\u003cp\u003eこの属性を使うことで、\u003cstrong\u003eプレイヤーの位置やアニメーション状態、ゲーム内オブジェクトの変化\u003c/strong\u003e などを自動的に同期でき、煩雑な手動のデータ送受信処理を省くことができます。\u003c/p\u003e\n\u003cp\u003e本シリーズでは、Fusionの内部同期処理を段階的に掘り下げて解説していきますが、今回は、\u003ccode\u003e[Networked]\u003c/code\u003e 属性の裏側で何が起きているのか、どのようにして自動同期が実現されているのかを詳しく見ていきます。\u003c/p\u003e\n\u003ch2 id=\"fusionにおけるnetworked属性の同期処理概要\"\u003eFusionにおける[Networked]属性の同期処理概要\u003c/h2\u003e\n\u003ch3 id=\"networked-属性は\"\u003e\u003ccode\u003e[Networked]\u003c/code\u003e 属性は？\u003c/h3\u003e\n\u003cp\u003eFusionでは、\u003ccode\u003e[Networked]\u003c/code\u003e 属性が付与されたプロパティは\u003cstrong\u003eネットワーク状態の同期対象\u003c/strong\u003e として扱われますが、ユーザーが手動で同期コードを書く必要はありません。\u003cbr\u003e\nこの仕組みは、Unityが提供する \u003cstrong\u003eILPostProcessor API\u003c/strong\u003e を活用しており、C# で書かれたコードをビルド後に\u003cstrong\u003eIL（中間言語）レベルで自動変換\u003c/strong\u003e しています。\u003c/p\u003e\n\u003cp\u003eIL\u003c/p\u003e\n\u003cp\u003eILは、.NET における中間言語です。\u003cbr\u003e\nUnity では、C# で書いたスクリプトは必ずこの IL を経由してからネイティブコードに変換されます。\u003c/p\u003e\n\u003cp\u003e具体的には、以下のような処理が行われます：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003eNetworkedWeaved\u003c/code\u003e という属性を追加して、該当プロパティがネットワーク状態バッファに割り当てられることを明示\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003egetter/setter が \u003ccode\u003eNetworkBehaviour\u003c/code\u003e の内部バッファ（\u003ccode\u003eStateBuffer\u003c/code\u003e）への\u003cstrong\u003eメモリアクセスに差し替え\u003c/strong\u003e られる\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e実行時に \u003ccode\u003eSpawned()\u003c/code\u003e されていない場合のアクセス制限も挿入される\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"変換前のcコード\"\u003e変換前のC#コード：\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\t[Networked] private Vector2 _position { get; set; }\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"il変換後ilspyで逆コンパイルしたコード\"\u003eIL変換後（ILSpyで逆コンパイルしたコード）：\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003e    [Networked]  \n    [NetworkedWeaved(0, 2)]  \n    private unsafe Vector2 _position  \n    {  \n        get  \n        {  \n            if (base.Ptr == null)  \n            {  \n                throw new InvalidOperationException(\u0026quot;Error when accessing PhotonCapybara._position. Networked properties can only be accessed when Spawned() has been called.\u0026quot;);  \n            }  \n            return (Vector2)base.Ptr[0];  \n        }  \n        set  \n        {  \n            if (base.Ptr == null)  \n            {  \n                throw new InvalidOperationException(\u0026quot;Error when accessing PhotonCapybara._position. Networked properties can only be accessed when Spawned() has been called.\u0026quot;);  \n            }  \n            Unsafe.Write(base.Ptr + 0, value);  \n        }  \n    }\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"補足解説\"\u003e補足解説：\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003ebase.Ptr\u003c/code\u003e は \u003ccode\u003eNetworkBehaviour\u003c/code\u003e に割り当てられた\u003cstrong\u003eネットワーク状態メモリの先頭ポインタ\u003c/strong\u003e です。\u003c/p\u003e","title":"💨 Fusionの[Networked]属性はどう動いている？IL変換と自動同期の仕組みを解剖する"}]