Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
multires_activity.cpp
Go to the documentation of this file.
1// multires_activity.cpp — 多重解像度の木の上の活性の CPU リファレンス(multires_nest.h の StepActive。17 §5「活性」。T-0100)。
2// 種(前の刻みに進める反応の規則があった・木の変更でつつかれた)とその面の隣(multires_activity.hlsli)に印を付け、印のあるブロックだけ刻む。
3// GPU(shaders/sim/multires_graph.hlsl の ActivitySeedNode → WakeFaceNode → ActivityStepNode)は同じことを並列に行う。
4// 印は「この刻みの印と交換して、前と違えば初めて」なので、どの順に起こしても刻むブロックの集合・数える欄・印は同じになる。
5// 面をたどる再帰も、たどる道(どの種のどの面からか)は順に依存しないので、上限で止まった数も同じになる。
6// 刻んでセルが変わったブロックと、木の変更でつつかれたブロックには忙しさの印(busyTick)を書き、印が古い本物の葉を
7// 粗くする要求を作る(SubmitQuietCoarsenRequests。GPU は multires_tree.hlsl の TreeQuiet。T-0101)。粗くするのは子のセル 2×2×2 の組ごとの差が
8// 許容差の中の葉だけ(静かになって初めての時に 1 回調べる。CheckQuietLeaves。GPU は TreeQuietCheck。D-430・T-0113)。
9// 一様なブロック(T-0102)は値 1 つで反応が進むかを調べ、進むなら刻んだ後に枠の順で頁に広げて刻む(GPU は TreeExpand → ExpandStepNode)。
10// 頁を持つブロックがちょうど静かになった刻みに一様なら、値 1 つに戻して枠の順に頁を返す(FoldQuietPages。GPU は TreeFoldCheck → TreeFold。T-0103)。
11// 許容差を渡すと、ほぼ同じ頁も平均の値で畳み、切り捨ての余りを帳簿へ移す(T-0104。GPU も同じ段で。T-0112)。
12// 反応の丸めは既定で待ちの丸め(ADR-0018。T-0115): 種 = 見出しを全部なめて起こす刻み(wakeTick)が来たブロック + つつかれたブロック。
13// 刻んだ後に変わったブロックは忙しさの印(= tc)を書いて次の刻みに起こし、評価して変わらなければ次に評価の要る刻みを書く(RecordWaitResults)。
14#include <algorithm>
15#include <optional>
16
17#include "common/multires_activity.hlsli"
18#include "sim/multires_nest.h"
20
21using namespace bicameral::multires;
22using namespace bicameral::reaction;
23
24namespace bicameral::sim {
25
26 namespace {
27
32
33 // --- 頁を畳む(T-0103・T-0104)---
34
35 // ビット単位で一様なら、その値(覆われていないセルが全部同じ・覆われたセルは空)
36 std::optional<RxCell> ExactFoldValue(MultiresNest& nest, uint32_t slot) {
37 const MrBlock& block = nest.blocks[slot];
38 const uint32_t valueCell = MrFoldValueCell(block);
39 const RxCell value = valueCell < MR_BLOCK_CELLS ? CellAt(nest, slot, valueCell) : RxMakeEmptyCell(0);
40 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
41 if (!MrFoldsCell(block, value, index, CellAt(nest, slot, index)))
42 return std::nullopt;
43 }
44
45 return value;
46 }
47
48 // 値 1 つに戻して、頁を空きのスタックに積む(枠の順)
49 void FoldPage(MultiresNest& nest, uint32_t slot, const RxCell& value) {
50 MrBlock& block = nest.blocks[slot];
51 FreePageAt(nest, nest.counters[MR_COUNTER_FREE_PAGES]++) = block.page;
52 UniformAt(nest, slot) = value;
53 block.page = MR_NO_PAGE;
54 nest.counters[MR_COUNTER_FOLDED] += 1;
55 }
56
57 // 帳簿の段 ledgerLevel に bits(その段の単位 × 2^-64)を足す。桁あふれ(= その段の 1 単位 = 1 段粗い段の 2^61)は粗い段へ繰り上げる
58 void AddLedgerBits(MultiresNest& nest, int32_t ledgerLevel, uint32_t column, uint64_t bits) {
59 for (; bits != 0; --ledgerLevel) {
60 const uint32_t address = MrLedgerAddress(ledgerLevel, column, nest.capacity.ledgerColumns);
61 if (address == MR_NO_BLOCK) {
62 nest.counters[MR_COUNTER_LEDGER_OUTSIDE] += 1;
63 return;
64 }
65
66 uint64_t& value = nest.ledger[address];
67 value += bits;
68 bits = value < bits ? uint64_t{1} << 61u : 0;
69 }
70 }
71
72 // レベル level の units 単位(units < 512。畳んだ平均の切り捨ての余り)を帳簿へ: 3 段粗い段の units × 2^55
73 void AddFoldRemainder(MultiresNest& nest, int32_t level, uint32_t column, uint64_t units) {
74 FX_ASSERT(units < MR_BLOCK_CELLS);
75 AddLedgerBits(nest, level - 3, column, units << 55u);
76 }
77
78 // 端数の枠のセルの端数を全部帳簿へ移し(端数はそのレベルの単位 × 2^-64 = 帳簿の同じ段の値)、枠を空きのスタックへ返す(T-0104)
79 void ReturnFractionsToLedger(MultiresNest& nest, uint32_t slot) {
80 MrBlock& block = nest.blocks[slot];
81 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
82 const MrFraction fraction = nest_detail::FractionAt(nest, block.fraction, index);
83 AddLedgerBits(nest, block.level, 0, fraction.energy);
84 for (uint32_t i = 0; i < fraction.speciesCount; ++i)
85 AddLedgerBits(nest, block.level, 1 + fraction.species[i], fraction.amounts[i]);
86 }
87
88 nest.freeFractions[nest.counters[MR_COUNTER_FREE_FRACTIONS]++] = block.fraction;
89 block.fraction = MR_NO_FRACTION;
90 }
91
92 // 刻む印を付ける。初めてなら数える
93 void Schedule(MultiresNest& nest, uint32_t slot, uint32_t mark) {
94 MrBlock& block = nest.blocks[slot];
95 if (block.activeTick == mark)
96 return;
97
98 block.activeTick = mark;
99 nest.counters[MR_COUNTER_SCHEDULED] += 1;
100 }
101
102 // 面の向き face に進んで入ったブロック(WakeFaceNode と同じ。remaining は残りの再帰の数)
103 void WakeFace(MultiresNest& nest, uint32_t slot, uint32_t face, uint32_t remaining, uint32_t mark) {
104 const MrBlock block = nest.blocks[slot];
105 bool wakeSelf = false;
106 for (uint32_t i = 0; i < MR_FACE_OCTANTS; ++i) {
107 const uint32_t child = block.children[MrNearOctant(face, i)];
108 if (child == MR_NO_BLOCK)
109 wakeSelf = true;
110 else if (remaining == 0)
111 nest.counters[MR_COUNTER_WAKE_TOO_DEEP] += 1;
112 else
113 WakeFace(nest, child, face, remaining - 1, mark);
114 }
115
116 if (wakeSelf)
117 Schedule(nest, slot, mark);
118 }
119
120 // --- 静かな葉を粗くできるか(T-0113。D-430)---
121
122 // MrCoarsenGroupWithin の Cells の約束(頁か一様の値のセル・温度)。完全に同じの時は温度を求めないので table は無くてよい
124 const MultiresNest* nest = nullptr;
125 const ReactionTableView* table = nullptr;
126 uint32_t slot = 0;
127
128 [[nodiscard]] RxCell Cell(uint32_t index) const { return LoadNestCell(*nest, slot, index); }
129
130 [[nodiscard]] int32_t Temperature(const RxCell& cell) const {
131 return RxComputeThermal(*table, cell).temperature;
132 }
133 };
134
135 // 葉を粗くしても、子のセル 2×2×2 の組のどれも許容差の中か(一様ならいつも)
136 bool LeafCoarsenable(const MultiresNest& nest, const ReactionTableView* table, uint32_t slot,
137 const MrFoldTolerance& tolerance) {
138 if (MrIsUniform(nest.blocks[slot]))
139 return true;
140
141 const QuietLeafCells cells{.nest = &nest, .table = table, .slot = slot};
142 for (uint32_t group = 0; group < MR_OCTANT_CELLS; ++group) {
143 if (!MrCoarsenGroupWithin(cells, group, tolerance))
144 return false;
145 }
146
147 return true;
148 }
149
150 // 今の忙しさの印でまだ調べていない静かな葉を調べ、結果を見出しの quietCheck に置く(GPU の TreeQuietCheck。要求を作る前に全部)
151 void CheckQuietLeaves(MultiresNest& nest, const ReactionTableView* table, uint64_t tick,
152 const MrFoldTolerance& tolerance) {
153 const uint64_t mark = MrChangeMark(tick);
154 for (uint32_t slot = 0; slot < nest.capacity.worldBlocks; ++slot) {
155 MrBlock& block = nest.blocks[slot];
156 if (MrNeedsQuietCheck(block, mark))
157 block.quietCheck = MrQuietCheckStamp(block.busyTick, LeafCoarsenable(nest, table, slot, tolerance));
158 }
159 }
160
161 // 静かな葉を調べてから、粗くできる葉の要求を枠の順に足す(table は許容差つきの時だけ要る)
162 void SubmitQuietRequests(MultiresNest& nest, const ReactionTableView* table, uint64_t tick,
163 const MrFoldTolerance& tolerance) {
164 CheckQuietLeaves(nest, table, tick, tolerance);
165
166 const CpuTree tree{.nest = &nest};
167 const uint64_t mark = MrChangeMark(tick);
168 uint32_t& count = nest.counters[MR_COUNTER_REQUESTS];
169 for (uint32_t slot = 0; slot < nest.capacity.worldBlocks; ++slot) {
170 if (!MrWantsQuietCoarsen(tree, slot, mark))
171 continue;
172
173 if (count == MR_MAX_REQUESTS) {
174 nest.counters[MR_COUNTER_QUIET_DEFERRED] += 1;
175 continue;
176 }
177
178 nest.requests[count++] = MrMakeQuietCoarsenRequest(nest.blocks[slot]);
179 nest.counters[MR_COUNTER_QUIET_REQUESTS] += 1;
180 }
181 }
182 } // namespace
183
184 void nest_detail::WakeAround(MultiresNest& nest, uint32_t slot, uint32_t mark) {
185 const CpuTree tree{.nest = &nest};
186 const MrBlock block = nest.blocks[slot];
187 Schedule(nest, slot, mark);
188 for (uint32_t check = 0; check < MR_WAKE_CHECKS; ++check) {
189 const uint32_t face = check % MR_FACES;
190 const MrWake wake = MrWakeAcross(tree, block, check / MR_FACES, face, nest.capacity.rootLevel);
191 if (wake.schedule != MR_NO_BLOCK)
192 Schedule(nest, wake.schedule, mark);
193
194 if (wake.descend != MR_NO_BLOCK)
195 WakeFace(nest, wake.descend, face, MR_MAX_WAKE_DEPTH, mark);
196 }
197 }
198
199 void StepActive(MultiresNest& nest, const BakedReactionTable& table, uint64_t worldSeed, uint64_t tick,
200 const MultiresStepOptions& options) {
201 const uint32_t worldBlocks = nest.capacity.worldBlocks;
202 const uint32_t mark = MrActivityMark(tick);
203
204 // --- 種とその面の隣に印を付ける(種は使い切る)。見出しを全部なめ、起こす刻みが来たブロックも種に(待ちの丸め。T-0115)。
205 // つつかれた印は刻む前に ResolvePokes が「この刻みの直前」の印にする ---
206 const uint64_t changeMark = MrChangeMark(tick);
207 for (uint32_t slot = 0; slot < worldBlocks; ++slot) {
208 const MrBlock& block = nest.blocks[slot];
209 const bool due = block.wakeTick <= changeMark;
210 if ((nest.seeds[slot] != 0 || due) && block.kind == MR_BLOCK_REAL)
211 nest_detail::WakeAround(nest, slot, mark);
212 }
213
214 std::ranges::fill(nest.seeds, uint8_t{0});
215
216 // --- 印のある世界のブロックと、観察の枠の全部(活性に入れない。D-403)を刻む ---
217 std::vector<uint8_t> stepped(nest.blocks.size(), 0);
218 for (uint32_t slot = 0; slot < nest.blocks.size(); ++slot)
219 stepped[slot] = slot >= worldBlocks || nest.blocks[slot].activeTick == mark ? 1 : 0;
220
221 const std::vector<nest_detail::BlockStepResult> results = nest_detail::StepBlocks(
222 nest, table.View(), stepped, worldSeed, tick, options, mark);
223
224 // --- 待ちの丸め: 変わったブロックは忙しさの印(= tc)を書いて次の刻みに起こし、評価したブロックは起こす刻みを書く ---
225 nest_detail::RecordWaitResults(nest, results, tick);
226 }
227
228 void FoldQuietPages(MultiresNest& nest, uint64_t tick) {
229 const uint64_t mark = MrChangeMark(tick);
230 for (uint32_t slot = 0; slot < nest.capacity.worldBlocks; ++slot) {
231 if (!MrWantsFoldCheck(nest.blocks[slot], mark))
232 continue;
233
234 const std::optional<RxCell> value = ExactFoldValue(nest, slot);
235 if (value)
236 FoldPage(nest, slot, *value);
237 }
238 }
239
240 void FoldQuietPages(MultiresNest& nest, const BakedReactionTable& table, uint64_t tick,
241 const MrFoldTolerance& tolerance) {
242 if (MrIsExactFold(tolerance)) {
243 FoldQuietPages(nest, tick);
244 return;
245 }
246
247 const ReactionTableView view = table.View();
248 const uint64_t mark = MrChangeMark(tick);
249 for (uint32_t slot = 0; slot < nest.capacity.worldBlocks; ++slot) {
250 // --- ちょうど静かになったら、端数の枠を帳簿へ移して返す(畳めるかはその後で調べる)---
251 if (MrWantsFractionReturn(nest.blocks[slot], mark))
252 ReturnFractionsToLedger(nest, slot);
253
254 const MrBlock& block = nest.blocks[slot];
255 if (!MrWantsFoldCheck(block, mark))
256 continue;
257
258 // --- ビット単位で一様ならそのまま畳む(値が変わらないのでつつかない。T-0125)。全部覆われていれば(覆われていないセルが無い)
259 // ビット単位の判定だけ ---
260 const std::optional<RxCell> exact = ExactFoldValue(nest, slot);
261 if (exact) {
262 FoldPage(nest, slot, *exact);
263 continue;
264 }
265
266 if (MrFoldValueCell(block) == MR_BLOCK_CELLS)
267 continue;
268
269 const MrFoldStats stats = CollectFoldStats(nest, view, slot);
270 if (!MrFoldStatsWithin(stats, tolerance))
271 continue;
272
273 // --- 平均の値で畳み、切り捨ての余りを帳簿へ(合計はビット一致)---
274 const MrFoldValue value = MrFoldStatsValue(stats);
275 AddFoldRemainder(nest, block.level, 0, value.energyRemainder);
276 for (uint32_t i = 0; i < stats.speciesCount; ++i)
277 AddFoldRemainder(nest, block.level, 1 + stats.species[i], value.amountRemainders[i]);
278
279 FoldPage(nest, slot, value.cell);
280
281 // --- セルの値が変わった(反応の速さ f も変わる)ので、つついて tc を書き直す(古い乱数・新しい f にしない。ADR-0018 追記 T-0125)---
282 nest_detail::PokeBlock(nest, slot);
283 }
284 }
285
286 MrFoldStats CollectFoldStats(const MultiresNest& nest, const ReactionTableView& table, uint32_t slot) {
287 const MrBlock& block = nest.blocks[slot];
288 MrFoldStats stats = MrMakeFoldStats();
289 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
290 if (MrIsCoveredCell(block, index))
291 continue;
292
293 const RxCell cell = LoadNestCell(nest, slot, index);
294 stats = MrAddFoldCell(stats, cell, RxComputeThermal(table, cell).temperature);
295 }
296
297 return stats;
298 }
299
300 void SubmitQuietCoarsenRequests(MultiresNest& nest, uint64_t tick) {
301 SubmitQuietRequests(nest, nullptr, tick, MrExactFoldTolerance());
302 }
303
304 void SubmitQuietCoarsenRequests(MultiresNest& nest, const BakedReactionTable& table, uint64_t tick,
305 const MrFoldTolerance& tolerance) {
306 const ReactionTableView view = table.View();
307 SubmitQuietRequests(nest, MrIsExactFold(tolerance) ? nullptr : &view, tick, tolerance);
308 }
309
310 std::vector<uint32_t> WaitSeedSlots(const MultiresNest& nest, uint64_t tick) {
311 std::vector<uint32_t> slots;
312 for (uint32_t slot = 0; slot < nest.capacity.worldBlocks; ++slot) {
313 const MrBlock& block = nest.blocks[slot];
314 if (block.kind == MR_BLOCK_REAL && (nest.seeds[slot] != 0 || block.wakeTick <= MrChangeMark(tick)))
315 slots.push_back(slot);
316 }
317
318 return slots;
319 }
320
321 std::vector<uint32_t> SeedSlots(const MultiresNest& nest) {
322 std::vector<uint32_t> slots;
323 for (uint32_t slot = 0; slot < nest.seeds.size(); ++slot) {
324 if (nest.seeds[slot] != 0)
325 slots.push_back(slot);
326 }
327
328 return slots;
329 }
330
331} // namespace bicameral::sim
uint32_t & FreePageAt(MultiresNest &nest, uint32_t position)
void ReturnFractionsToLedger(MultiresNest &nest, uint32_t slot)
void SubmitQuietRequests(MultiresNest &nest, const ReactionTableView *table, uint64_t tick, const MrFoldTolerance &tolerance)
std::optional< RxCell > ExactFoldValue(MultiresNest &nest, uint32_t slot)
void AddFoldRemainder(MultiresNest &nest, int32_t level, uint32_t column, uint64_t units)
void FoldPage(MultiresNest &nest, uint32_t slot, const RxCell &value)
void WakeFace(MultiresNest &nest, uint32_t slot, uint32_t face, uint32_t remaining, uint32_t mark)
reaction::RxCell & UniformAt(MultiresNest &nest, uint32_t slot)
void AddLedgerBits(MultiresNest &nest, int32_t ledgerLevel, uint32_t column, uint64_t bits)
reaction::RxCell & CellAt(MultiresNest &nest, uint32_t slot, uint32_t index)
bool LeafCoarsenable(const MultiresNest &nest, const ReactionTableView *table, uint32_t slot, const MrFoldTolerance &tolerance)
void Schedule(MultiresNest &nest, uint32_t slot, uint32_t mark)
void CheckQuietLeaves(MultiresNest &nest, const ReactionTableView *table, uint64_t tick, const MrFoldTolerance &tolerance)
void WakeAround(MultiresNest &nest, uint32_t slot, uint32_t mark)
uint32_t & FreePageAt(MultiresNest &nest, uint32_t position)
std::vector< BlockStepResult > StepBlocks(MultiresNest &nest, const ReactionTableView &view, std::span< uint8_t > stepped, uint64_t worldSeed, uint64_t tick, const MultiresStepOptions &options, uint32_t wakeMark)
multires::MrFraction FractionAt(const MultiresNest &nest, uint32_t fractionSlot, uint32_t index)
void PokeBlock(MultiresNest &nest, uint32_t slot)
void RecordWaitResults(MultiresNest &nest, std::span< const BlockStepResult > results, uint64_t tick)
reaction::RxCell & UniformAt(MultiresNest &nest, uint32_t slot)
reaction::RxCell & CellAt(MultiresNest &nest, uint32_t slot, uint32_t index)
void FoldQuietPages(MultiresNest &nest, uint64_t tick)
std::vector< uint32_t > SeedSlots(const MultiresNest &nest)
MrFoldStats CollectFoldStats(const MultiresNest &nest, const ReactionTableView &table, uint32_t slot)
void SubmitQuietCoarsenRequests(MultiresNest &nest, uint64_t tick)
RxCell LoadNestCell(const MultiresNest &nest, uint32_t slot, uint32_t index)
std::vector< uint32_t > WaitSeedSlots(const MultiresNest &nest, uint64_t tick)
void StepActive(MultiresNest &nest, const BakedReactionTable &table, uint64_t worldSeed, uint64_t tick, const MultiresStepOptions &options)
ReactionTableView View() const
std::vector< uint8_t > seeds
std::vector< multires::MrBlock > blocks
std::vector< uint32_t > freeFractions
std::vector< uint64_t > ledger
std::vector< multires::MrRequest > requests
std::array< uint32_t, multires::MR_COUNTER_COUNT > counters