Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
table_hot_reload.cpp
Go to the documentation of this file.
1// table_hot_reload.cpp — 反応表のホットリロードを世界の刻みの境界へつなぐ(T-0139・ADR-0047)。
3
4#include <algorithm>
5#include <format>
6#include <utility>
7
8#include "core/aliases.h"
9#include "core/log.h"
10
11namespace bicameral::frame {
12
13 namespace {
14
15 // ファイルを見る間隔(フレーム)。見るたびにパッケージのファイルを全部読むので、毎フレームにはしない(60 fps で約 0.25 秒)
16 constexpr uint64_t POLL_INTERVAL_FRAMES = 15;
17
18 } // namespace
19
21 std::shared_ptr<const script::LoadedReactionTable> initial, bool watch)
22 : m_appliedVersion(initial->tableVersion) {
23 Remember(initial);
24 if (watch)
25 m_reload.emplace(std::move(source), std::move(initial));
26 }
27
28 void TableHotReload::Remember(const std::shared_ptr<const script::LoadedReactionTable>& table) {
29 m_known.emplace(table->tableVersion, table); // 同じ版は同じ法則なので、先に覚えた方のままでよい
30 }
31
32 void TableHotReload::Poll(uint64_t frameNumber) {
33 if (!m_reload || frameNumber % POLL_INTERVAL_FRAMES != 0)
34 return;
35
36 script::HotReloadResult result = m_reload->Poll();
39 m_lastFailed = true;
40 m_lastMessage = std::move(result.message);
41 Log(Channel::Sim, Level::Warning, "反応表を読み直せなかった(今の表のまま): {}", m_lastMessage);
42 return;
43 }
44
46 return;
47
48 Remember(result.table);
49 m_waiting = std::move(result.table);
50 m_lastFailed = false;
51 m_lastMessage = std::format("版 {:016x} を読んだ(物質 {}・規則 {})。次の刻みの境界で当てる{}{}",
52 m_waiting->tableVersion, m_waiting->table.species.size() - 1,
53 m_waiting->table.rules.size(), result.message.empty() ? "" : "。", result.message);
54 Log(Channel::Sim, Level::Info, "反応表を読み直した: {}", m_lastMessage);
55 }
56
57 std::expected<void, std::string> TableHotReload::ScheduleReplayed(std::span<const sim::ProbeCommand> commands) {
58 for (const sim::ProbeCommand& command : commands) {
59 if (command.type != sim::PROBE_COMMAND_TYPE_TABLE)
60 continue;
61
62 const uint64_t version = sim::TableCommandVersion(command);
63 const auto known = m_known.find(version);
64 if (known == m_known.end()) {
65 return std::unexpected(
66 std::format("再生ファイルの刻み {} で反応表が版 {:016x} "
67 "に変わるが、その表を持っていない(起動時の表は版 {:016x}。"
68 "表の中身を再生ファイルに残すのは T-0193)",
69 command.targetTick, version, m_appliedVersion));
70 }
71
72 m_scheduled.push_back({.tick = command.targetTick, .table = known->second});
73 }
74
75 return {};
76 }
77
78 std::expected<std::vector<sim::ProbeTableSwap>, std::string> TableHotReload::TakeSwaps(
79 uint64_t firstUnit, uint32_t unitCount, uint32_t unitsPerTick, bool replaying, bool canAddCommand,
80 uint32_t& nextSequence, std::vector<sim::ProbeCommand>& commands) {
81 m_inFlight.clear(); // 前のフレームの表は RecordFrame が写し終えた
82
83 // --- 新しい差し替え: 再生は印から、生の操作は待っている表をこのフレームの最初の適用の単位へ ---
84 const uint64_t applyTick = (firstUnit + unitsPerTick - 1) / unitsPerTick; // ProbeSim::NextApplyTick と同じ
85 if (replaying) {
86 if (auto scheduled = ScheduleReplayed(commands); !scheduled)
87 return std::unexpected(std::move(scheduled.error()));
88 } else if (m_waiting && canAddCommand && applyTick * unitsPerTick < firstUnit + unitCount) {
89 commands.push_back(sim::MakeTableCommand(applyTick, nextSequence++, m_waiting->tableVersion));
90 rng::sort(commands, sim::CommandPrecedes);
91 m_scheduled.push_back({.tick = applyTick, .table = std::move(m_waiting)});
92 m_waiting = nullptr;
93 }
94
95 // --- このフレームに適用の単位がある差し替えを返す ---
96 std::vector<sim::ProbeTableSwap> swaps;
97 for (auto it = m_scheduled.begin(); it != m_scheduled.end();) {
98 const uint64_t swapUnit = it->tick * unitsPerTick;
99 if (swapUnit < firstUnit)
100 return std::unexpected(std::format("反応表の差し替えの刻み {} を過ぎてしまった", it->tick));
101
102 if (swapUnit >= firstUnit + unitCount) {
103 ++it;
104 continue;
105 }
106
107 swaps.push_back({.tick = it->tick, .table = &it->table->table});
108 m_appliedVersion = it->table->tableVersion;
110 Log(Channel::Sim, Level::Info, "反応表を刻み {} の始めに版 {:016x} へ差し替える", it->tick,
112 m_inFlight.push_back(std::move(it->table));
113 it = m_scheduled.erase(it);
114 }
115
116 return swaps;
117 }
118
119} // namespace bicameral::frame
std::vector< Scheduled > m_scheduled
std::map< uint64_t, std::shared_ptr< const script::LoadedReactionTable > > m_known
void Poll(uint64_t frameNumber)
std::expected< std::vector< sim::ProbeTableSwap >, std::string > TakeSwaps(uint64_t firstUnit, uint32_t unitCount, uint32_t unitsPerTick, bool replaying, bool canAddCommand, uint32_t &nextSequence, std::vector< sim::ProbeCommand > &commands)
std::optional< script::ReactionTableHotReload > m_reload
void Remember(const std::shared_ptr< const script::LoadedReactionTable > &table)
TableHotReload(script::ReactionTableSource source, std::shared_ptr< const script::LoadedReactionTable > initial, bool watch)
std::shared_ptr< const script::LoadedReactionTable > m_waiting
std::expected< void, std::string > ScheduleReplayed(std::span< const sim::ProbeCommand > commands)
std::vector< std::shared_ptr< const script::LoadedReactionTable > > m_inFlight
constexpr bool CommandPrecedes(const Command &a, const Command &b)
Definition command.h:32
ProbeCommand MakeTableCommand(uint64_t targetTick, uint32_t sequence, uint64_t version)
uint64_t TableCommandVersion(const ProbeCommand &command)
Definition probe_sim.h:59
Command ProbeCommand
Definition probe_sim.h:53
void Log(Channel channel, Level level, FormatWithLocation< std::type_identity_t< Args >... > pattern, Args &&... args)
Definition log.h:140
std::shared_ptr< const LoadedReactionTable > table