Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
replay_session.cpp
Go to the documentation of this file.
1// replay_session.cpp — 記録と再生の帳簿。使い方は replay_session.h。
3
4#include <algorithm>
5#include <format>
6#include <iterator>
7#include <utility>
8
9#include "core/aliases.h"
10#include "core/log.h"
11
12namespace bicameral::save {
13 namespace {
14
15 constexpr uint64_t MAX_LOGGED_MISMATCHES = 8;
16
17 } // namespace
18
19 // --- 記録 ---
20
21 void ReplayRecorder::AddCommands(std::span<const sim::Command> commands) {
22 m_commands.insert(m_commands.end(), commands.begin(), commands.end());
23 }
24
25 void ReplayRecorder::AddHash(uint64_t tick, uint64_t hash) {
26 m_hashes.push_back({.tick = tick, .hash = hash});
27 }
28
29 // S(lastTick) まで確かめられるので、その状態に入るコマンド(targetTick < lastTick)だけを残す
31 ReplayFile replay{.world = ReplayWorld::Probe, .startTick = 0, .tickHashes = m_hashes};
32 const uint64_t lastTick = m_hashes.empty() ? 0 : m_hashes.back().tick;
33 rng::copy_if(m_commands, std::back_inserter(replay.commands),
34 [&](const sim::Command& command) { return command.targetTick < lastTick; });
35
36 return replay;
37 }
38
39 std::expected<void, std::string> ReplayRecorder::Write(const fs::path& path) const {
40 return WriteReplayFile(path, Build());
41 }
42
43 // --- 再生 ---
44
45 ReplayPlayer::ReplayPlayer(ReplayFile replay) : m_replay(std::move(replay)) {}
46
47 std::expected<ReplayPlayer, std::string> ReplayPlayer::Load(const fs::path& path) {
48 auto replay = ReadReplayFile(path);
49 if (!replay)
50 return std::unexpected(replay.error());
51
52 if (replay->world != ReplayWorld::Probe || replay->startTick != 0 || !replay->initialDelta.empty())
53 return std::unexpected("仮の世界は刻み 0 の全部 0 の状態からしか再生できない");
54
55 return ReplayPlayer(std::move(*replay));
56 }
57
58 std::vector<sim::Command> ReplayPlayer::TakeCommands(uint64_t applyTick, uint32_t limit) {
59 std::vector<sim::Command> commands;
60 while (m_nextCommand < m_replay.commands.size() && commands.size() < limit) {
61 const sim::Command& command = m_replay.commands[m_nextCommand];
62 if (command.targetTick >= applyTick + LOOKAHEAD_TICKS)
63 break;
64
66 if (command.targetTick < applyTick) {
68
70 "再生: 刻み {} のコマンド(番号 {})が適用に間に合わなかった(次の適用は刻み {})", command.targetTick,
71 command.sequence, applyTick);
72
73 continue;
74 }
75
76 commands.push_back(command);
77 }
78
79 return commands;
80 }
81
82 void ReplayPlayer::Rewind(uint64_t tick) {
83 const auto& commands = m_replay.commands;
84 const auto& hashes = m_replay.tickHashes;
85 m_nextCommand = static_cast<size_t>(
86 rng::find_if(commands, [&](const sim::Command& command) { return command.targetTick >= tick; }) -
87 commands.begin());
88 m_nextHash = static_cast<size_t>(
89 rng::find_if(hashes, [&](const ReplayTickHash& entry) { return entry.tick > tick; }) - hashes.begin());
90 }
91
92 void ReplayPlayer::CheckHash(uint64_t tick, uint64_t hash) {
93 const auto& expected = m_replay.tickHashes;
94 while (m_nextHash < expected.size() && expected[m_nextHash].tick < tick) {
95 // 読み戻しに無かった刻み(あり得ないはず。ハッシュは全部の刻みで戻る)
96 Log(Channel::Sim, Level::Error, "再生: S({}) のハッシュが戻ってこなかった", expected[m_nextHash].tick);
98 ++m_nextHash;
99 }
100
101 // ファイルが間引いた刻み
102 if (m_nextHash == expected.size() || expected[m_nextHash].tick != tick)
103 return;
104
105 if (expected[m_nextHash].hash == hash)
106 ++m_matches;
107 else {
108 if (m_mismatches < MAX_LOGGED_MISMATCHES) {
109 Log(Channel::Sim, Level::Error, "再生: S({}) = {:016x}(記録は {:016x})", tick, hash,
110 expected[m_nextHash].hash);
111 }
112
113 ++m_mismatches;
114 }
115
116 ++m_nextHash;
117 }
118
119} // namespace bicameral::save
std::vector< sim::Command > TakeCommands(uint64_t applyTick, uint32_t limit)
static constexpr uint64_t LOOKAHEAD_TICKS
void CheckHash(uint64_t tick, uint64_t hash)
static std::expected< ReplayPlayer, std::string > Load(const fs::path &path)
void AddCommands(std::span< const sim::Command > commands)
std::vector< sim::Command > m_commands
std::vector< ReplayTickHash > m_hashes
std::expected< void, std::string > Write(const fs::path &path) const
void AddHash(uint64_t tick, uint64_t hash)
std::expected< ReplayFile, std::string > ReadReplayFile(const fs::path &path)
std::expected< void, std::string > WriteReplayFile(const fs::path &path, const ReplayFile &replay)
void Log(Channel channel, Level level, FormatWithLocation< std::type_identity_t< Args >... > pattern, Args &&... args)
Definition log.h:140
std::vector< sim::Command > commands
Definition replay_file.h:53