84#ifndef CA_CHECKPOINT_H
85#define CA_CHECKPOINT_H
92#include <condition_variable>
106#include <string_view>
107#include <system_error>
109#include <type_traits>
119# include <sys/stat.h>
120# include <sys/types.h>
134namespace ca_checkpoint_detail {
140 = {{
'A',
'L',
'E',
'P',
'H',
'C',
'A',
'1'}};
153 = 8 + 4 + 8 + 4 + 4 + 4 * 8 + 8 + 1 + 7 + 8 + 8;
165 std::uint64_t
h = 0xcbf29ce484222325ull;
166 for (
const char c : s)
168 h ^=
static_cast<std::uint64_t
>(
static_cast<unsigned char>(c));
169 h *= 0x100000001b3ull;
190 static_assert(std::is_trivially_copyable_v<T>,
191 "write_le requires a trivially copyable type");
195 std::array<
char,
sizeof(
T)> bytes{};
196 std::memcpy(bytes.data(), &
value,
sizeof(
T));
197 out.write(bytes.data(),
sizeof(
T));
203 static_assert(std::is_trivially_copyable_v<T>,
204 "read_le requires a trivially copyable type");
205 std::array<
char,
sizeof(
T)> bytes{};
206 in.read(bytes.data(),
sizeof(
T));
208 std::memcpy(&
value, bytes.data(),
sizeof(
T));
222[[
nodiscard]]
inline std::filesystem::path
225 std::mt19937_64
rng(
static_cast<std::uint64_t
>(
227 ^
static_cast<std::uint64_t
>(
228 reinterpret_cast<std::uintptr_t
>(&target)));
229 const std::uint64_t
r =
rng();
233 const long pid =
static_cast<long>(
::getpid());
235 std::string
suffix =
".tmp." + std::to_string(pid) +
"." + std::to_string(
r);
236 std::filesystem::path
tmp = target;
262 <<
"sync_file: CreateFileW failed for '" << path.string() <<
"'";
267 <<
"sync_file: FlushFileBuffers failed for '" << path.string()
268 <<
"' (error " <<
err <<
")";
270 const int fd = ::open(path.c_str(),
O_WRONLY);
272 <<
"sync_file: open() failed for '" << path.string()
273 <<
"' (errno=" <<
errno <<
")";
278 <<
"sync_file: fsync() failed for '" << path.string()
279 <<
"' (errno=" <<
saved <<
")";
316 :
path_(std::move(p))
327 std::filesystem::remove(
path_,
ec);
348namespace ca_checkpoint_flags {
355inline constexpr std::uint32_t
delta = 0x2;
434 {
r.master_seed() } -> std::convertible_to<std::uint64_t>;
435 r.set_master_seed(s);
449 {
l.extents() } -> std::convertible_to<typename L::extents_type>;
474 and requires {
typename E::rule_type; }
475 and requires(E &e, std::size_t s) {
476 { e.frame() } -> std::convertible_to<const typename E::lattice_type &>;
477 { e.steps_run() } -> std::convertible_to<std::size_t>;
478 { e.rule() } -> std::convertible_to<typename E::rule_type &>;
479 { e.write_lattice() } -> std::convertible_to<typename E::lattice_type &>;
517 using namespace ca_checkpoint_detail;
518 std::ifstream
in(path, std::ios::binary);
520 <<
"inspect_checkpoint: cannot open '" << path.string() <<
"'";
523 in.read(
h.magic.data(),
h.magic.size());
525 <<
"inspect_checkpoint: bad magic in '" << path.string() <<
"'";
529 or h.format_version > format_version)
530 <<
"inspect_checkpoint: unsupported format version " <<
h.format_version
531 <<
" (supported [" << format_version_min_read <<
", " << format_version
532 <<
"]) in '" << path.string() <<
"'";
542 <<
"inspect_checkpoint: rank " <<
h.rank <<
" out of range in '"
543 << path.string() <<
"'";
544 for (std::size_t d = 0; d <
h.extents.size(); ++d)
549 for (
int i = 0; i < 7; ++i)
554 if (
h.format_version >= 2)
564 h.compression_level = 0;
565 h.payload_size =
h.cell_count *
h.state_type_size;
566 h.delta_base_step = 0;
570 <<
"inspect_checkpoint: short read while parsing header of '"
571 << path.string() <<
"'";
583 for (std::uint32_t d = 0; d <
h.rank; ++d)
585 const std::uint64_t e =
h.extents[d];
587 <<
"inspect_checkpoint: extent product overflows in '"
588 << path.string() <<
"'";
592 <<
"inspect_checkpoint: cell_count (" <<
h.cell_count
593 <<
") inconsistent with the product of extents (" <<
expected_cells
594 <<
") in '" << path.string() <<
"'";
597 <<
"inspect_checkpoint: cell_count * state_type_size overflows in '"
598 << path.string() <<
"'";
607namespace ca_checkpoint_detail {
620template <
typename Engine>
624 using Lattice =
typename Engine::lattice_type;
627 static_assert(std::is_trivially_copyable_v<state_t>,
628 "snapshot_frame_bytes requires a trivially copyable state_type");
630 "snapshot_frame_bytes currently supports rank <= 3");
633 std::uint64_t cell_count = 1;
635 cell_count *=
static_cast<std::uint64_t
>(frame.
size(d));
637 std::vector<std::uint8_t> bytes(
static_cast<std::size_t
>(cell_count) *
sizeof(
state_t));
639 auto append = [&](
const state_t &v)
641 std::memcpy(bytes.data() +
off, &v,
sizeof(
state_t));
652 for (
ca_size_t i = 0; i < frame.size(0); ++i)
653 for (
ca_size_t j = 0; j < frame.size(1); ++j)
659 for (
ca_size_t i = 0; i < frame.size(0); ++i)
660 for (
ca_size_t j = 0; j < frame.size(1); ++j)
671template <
typename Engine>
675 using Lattice =
typename Engine::lattice_type;
678 static_assert(std::is_trivially_copyable_v<state_t>,
679 "restore_frame_from_bytes requires a trivially copyable state_type");
680 static_assert(Lattice::rank <= 3,
681 "restore_frame_from_bytes currently supports rank <= 3");
684 std::uint64_t cell_count = 1;
685 for (std::size_t d = 0; d < Lattice::rank; ++d)
686 cell_count *=
static_cast<std::uint64_t
>(frame.
size(d));
687 ah_runtime_error_if(bytes.size() !=
static_cast<std::size_t
>(cell_count) *
sizeof(state_t))
688 <<
"restore_frame_from_bytes: size mismatch (expected "
689 << (cell_count *
sizeof(state_t)) <<
", got " << bytes.size() <<
")";
692 auto next = [&]() -> state_t
695 std::memcpy(&v, bytes.data() + off,
sizeof(state_t));
696 off +=
sizeof(state_t);
700 if constexpr (Lattice::rank == 1)
705 else if constexpr (Lattice::rank == 2)
709 engine.write_lattice().set(
713 else if constexpr (Lattice::rank == 3)
734 std::uint64_t engine_type_hash = 0;
735 std::uint32_t state_type_size = 0;
736 std::uint32_t rank = 0;
738 std::uint64_t step_count = 0;
739 std::uint8_t has_rng = 0;
740 std::uint64_t master_seed = 0;
741 std::uint64_t cell_count = 0;
754template <
typename Engine>
758 using Lattice =
typename Engine::lattice_type;
763 hs.
rank =
static_cast<std::uint32_t
>(Lattice::rank);
764 const auto &ext =
engine.frame().extents();
765 for (std::size_t d = 0; d < Lattice::rank; ++d)
766 hs.
extents[d] =
static_cast<std::uint64_t
>(ext[d]);
769 for (std::size_t d = 0; d < Lattice::rank; ++d)
783 const std::uint32_t flags,
784 const std::uint32_t compression_level,
785 const std::uint64_t payload_size,
786 const std::uint64_t delta_base_step)
792 write_le<std::uint32_t>(
out, hs.
rank);
793 for (std::size_t d = 0; d < hs.
extents.size(); ++d)
797 for (
int i = 0; i < 7; ++i)
798 write_le<std::uint8_t>(
out, std::uint8_t{0});
801 write_le<std::uint32_t>(
out, flags);
802 write_le<std::uint32_t>(
out, compression_level);
803 write_le<std::uint64_t>(
out, payload_size);
804 write_le<std::uint64_t>(
out, delta_base_step);
816 const std::uint32_t flags,
817 const std::uint32_t compression_level,
818 const std::vector<std::uint8_t> &payload,
819 const std::uint64_t delta_base_step,
825 std::ofstream
out(tmp, std::ios::binary | std::ios::trunc);
827 <<
"atomic_write_file: cannot open '" << tmp.string() <<
"' for write";
832 static_cast<std::uint64_t
>(payload.size()),
834 if (not payload.empty())
835 out.write(
reinterpret_cast<const char *
>(payload.data()),
836 static_cast<std::streamsize
>(payload.size()));
839 <<
"atomic_write_file: write failed for '" << tmp.string() <<
"'";
844 std::filesystem::rename(tmp, path, ec);
846 <<
"atomic_write_file: rename '" << tmp.string() <<
"' -> '"
847 << path.string() <<
"' failed: " << ec.message();
852 std::filesystem::path parent = path.parent_path();
854 parent = std::filesystem::path(
".");
861inline std::vector<std::uint8_t>
864 std::uint32_t &flags_out,
865 std::uint32_t &level_out)
867 if (not
options.compress or raw.empty())
874 std::vector<std::uint8_t> compressed =
deflate_bytes(raw.data(), raw.size(), level);
875 flags_out = ca_checkpoint_flags::compressed;
876 level_out =
static_cast<std::uint32_t
>(level);
882inline std::vector<std::uint8_t>
885 std::ifstream in(path, std::ios::binary);
887 <<
"read_raw_payload: cannot reopen '" << path.string() <<
"'";
888 const std::streamoff header_bytes
894 const std::uint64_t file_size
895 =
static_cast<std::uint64_t
>(std::filesystem::file_size(path));
896 const std::uint64_t hdr =
static_cast<std::uint64_t
>(header_bytes);
898 <<
"read_raw_payload: declared payload_size " <<
h.payload_size
899 <<
" exceeds the file body ("
900 << (file_size >= hdr ? file_size - hdr : 0)
901 <<
" bytes) in '" << path.string() <<
"'";
903 in.seekg(header_bytes, std::ios::beg);
904 std::vector<std::uint8_t> payload(
static_cast<std::size_t
>(
h.payload_size));
905 if (not payload.empty())
907 in.read(
reinterpret_cast<char *
>(payload.data()),
908 static_cast<std::streamsize
>(payload.size()));
910 <<
"read_raw_payload: short read of payload in '" << path.string() <<
"'";
912 if (
h.flags & ca_checkpoint_flags::compressed)
915 =
static_cast<std::size_t
>(
h.cell_count) *
h.state_type_size;
954template <
typename Engine>
956 const std::filesystem::path &path,
958 requires Checkpointable_Engine<Engine>
960 using namespace ca_checkpoint_detail;
961 const Header_Snapshot hs = capture_header(
engine);
962 std::vector<std::uint8_t> raw = snapshot_frame_bytes(
engine);
963 std::uint32_t flags = 0;
964 std::uint32_t level = 0;
965 std::vector<std::uint8_t> payload = build_payload(raw,
options, flags, level);
966 atomic_write_file(path, hs, flags, level, payload, 0,
options);
997template <
typename Engine>
999 const std::filesystem::path &path)
1002 using namespace ca_checkpoint_detail;
1003 using Lattice =
typename Engine::lattice_type;
1005 static_assert(std::is_trivially_copyable_v<state_t>,
1006 "load_checkpoint_into requires a trivially copyable state_type");
1013 <<
"load_checkpoint_into: type hash mismatch in '" << path.string()
1015 <<
" engine=" << type_hash<Engine>() <<
")";
1017 <<
"load_checkpoint_into: state size mismatch in '" << path.string()
1020 <<
"load_checkpoint_into: rank mismatch in '" << path.string()
1021 <<
"' (file=" << token.
header.
rank <<
" engine=" << Lattice::rank <<
")";
1023 <<
"load_checkpoint_into: '" << path.string() <<
"' is a delta snapshot; "
1024 <<
"use apply_delta_checkpoint() after loading the baseline";
1026 const auto &ext =
engine.frame().extents();
1027 for (std::size_t d = 0; d < Lattice::rank; ++d)
1029 <<
"load_checkpoint_into: extent[" << d <<
"] mismatch in '" << path.string()
1030 <<
"' (file=" << token.
header.
extents[d] <<
" engine=" << ext[d] <<
")";
1032 std::vector<std::uint8_t> raw = read_raw_payload(path, token.
header);
1033 restore_frame_from_bytes(
engine, raw);
1081template <
typename Engine>
1083 const std::vector<std::uint8_t> &baseline,
1084 const std::uint64_t base_step,
1085 const std::filesystem::path &path,
1087 requires Checkpointable_Engine<Engine>
1089 using namespace ca_checkpoint_detail;
1090 using Lattice =
typename Engine::lattice_type;
1091 using state_t =
typename Lattice::state_type;
1092 static_assert(std::is_trivially_copyable_v<state_t>,
1093 "save_delta_checkpoint requires a trivially copyable state_type");
1095 const Header_Snapshot hs = capture_header(
engine);
1096 const std::size_t cell_bytes =
sizeof(state_t);
1097 const std::size_t total_cells =
static_cast<std::size_t
>(hs.cell_count);
1099 <<
"save_delta_checkpoint: baseline size mismatch (expected "
1100 << (total_cells * cell_bytes) <<
", got " << baseline.size() <<
")";
1102 std::vector<std::uint8_t> current = snapshot_frame_bytes(
engine);
1103 std::vector<std::uint8_t>
diff;
1105 for (std::size_t i = 0; i < total_cells; ++i)
1107 const std::size_t off = i * cell_bytes;
1108 if (std::memcmp(current.data() + off,
1109 baseline.data() + off,
1113 const std::uint64_t idx =
static_cast<std::uint64_t
>(i);
1114 const std::size_t head =
diff.size();
1115 diff.resize(head +
sizeof(std::uint64_t) + cell_bytes);
1116 std::memcpy(
diff.data() + head, &idx,
sizeof(std::uint64_t));
1117 std::memcpy(
diff.data() + head +
sizeof(std::uint64_t),
1118 current.data() + off,
1123 std::uint32_t flags = ca_checkpoint_flags::delta;
1124 std::uint32_t level = 0;
1125 std::vector<std::uint8_t> payload;
1130 flags |= ca_checkpoint_flags::compressed;
1131 level =
static_cast<std::uint32_t
>(lvl);
1134 payload = std::move(diff);
1154template <
typename Engine>
1155[[nodiscard]] Resume_Token
1159 using namespace ca_checkpoint_detail;
1160 using Lattice =
typename Engine::lattice_type;
1163 static_assert(std::is_trivially_copyable_v<state_t>,
1164 "apply_delta_checkpoint requires a trivially copyable state_type");
1165 static_assert(Lattice::rank <= 3,
1166 "apply_delta_checkpoint currently supports rank <= 3");
1173 <<
"apply_delta_checkpoint: '" << path.string() <<
"' is not a delta snapshot";
1175 <<
"apply_delta_checkpoint: type hash mismatch in '" << path.string() <<
"'";
1177 <<
"apply_delta_checkpoint: state size mismatch in '" << path.string() <<
"'";
1179 <<
"apply_delta_checkpoint: rank mismatch in '" << path.string() <<
"'";
1181 std::ifstream in(path, std::ios::binary);
1183 <<
"apply_delta_checkpoint: cannot reopen '" << path.string() <<
"'";
1184 in.seekg(header_bytes_v2, std::ios::beg);
1185 std::vector<std::uint8_t> payload(
static_cast<std::size_t
>(token.
header.
payload_size));
1186 if (not payload.empty())
1188 in.read(
reinterpret_cast<char *
>(payload.data()),
1189 static_cast<std::streamsize
>(payload.size()));
1191 <<
"apply_delta_checkpoint: short read in '" << path.string() <<
"'";
1194 std::vector<std::uint8_t>
diff;
1195 if (token.
header.
flags & ca_checkpoint_flags::compressed)
1199 const std::size_t bound
1201 * (
sizeof(std::uint64_t) +
sizeof(state_t));
1202 diff = inflate_bytes_up_to(payload.data(), payload.size(), bound);
1205 diff = std::move(payload);
1207 const std::size_t cell_bytes =
sizeof(state_t);
1208 const std::size_t entry_bytes =
sizeof(std::uint64_t) + cell_bytes;
1210 <<
"apply_delta_checkpoint: delta payload size " <<
diff.size()
1211 <<
" is not a multiple of (" << entry_bytes <<
")";
1213 auto extents_from_header = [&](std::size_t d) ->
ca_size_t
1216 const std::size_t entries =
diff.size() / entry_bytes;
1217 for (std::size_t e = 0; e < entries; ++e)
1219 std::uint64_t idx = 0;
1221 std::memcpy(&idx,
diff.data() + e * entry_bytes,
sizeof(std::uint64_t));
1223 diff.data() + e * entry_bytes +
sizeof(std::uint64_t),
1226 <<
"apply_delta_checkpoint: linear index " << idx
1229 if constexpr (Lattice::rank == 1)
1231 engine.write_lattice().set(
1235 else if constexpr (Lattice::rank == 2)
1237 const auto j =
static_cast<ca_index_t>(idx % extents_from_header(1));
1238 const auto i =
static_cast<ca_index_t>(idx / extents_from_header(1));
1241 else if constexpr (Lattice::rank == 3)
1243 const auto e2 = extents_from_header(2);
1244 const auto e1 = extents_from_header(1);
1246 const auto j =
static_cast<ca_index_t>((idx / e2) % e1);
1247 const auto i =
static_cast<ca_index_t>(idx / (e1 * e2));
1288template <
typename Engine>
1307 : capacity_(capacity)
1311 <<
"Async_Checkpoint_Writer: capacity must be >= 1";
1312 worker_ = std::thread([
this] { this->worker_loop(); });
1331 std::unique_lock<std::mutex> lk(mu_);
1333 not_empty_.notify_all();
1335 if (worker_.joinable())
1346 std::filesystem::path path,
1350 task.header = ca_checkpoint_detail::capture_header(
engine);
1351 task.raw = ca_checkpoint_detail::snapshot_frame_bytes(
engine);
1352 task.path = std::move(path);
1355 std::unique_lock<std::mutex> lk(mu_);
1356 if (policy_ == Queue_Policy::Drop_Oldest)
1358 while (queue_.size() >= capacity_)
1361 total_dropped_.fetch_add(1, std::memory_order_relaxed);
1366 not_full_.wait(lk, [
this] {
return queue_.size() < capacity_ or stop_; });
1368 queue_.push_back(std::move(task));
1369 not_empty_.notify_one();
1382 std::unique_lock<std::mutex> lk(mu_);
1383 drained_.wait(lk, [
this] {
return queue_.empty() and not in_flight_; });
1386 const std::string msg = std::move(*worker_error_);
1387 worker_error_.reset();
1395 std::lock_guard<std::mutex> lk(mu_);
1396 return queue_.size();
1411 before_write_hook_ = std::move(hook);
1417 return total_written_.load(std::memory_order_relaxed);
1423 return total_dropped_.load(std::memory_order_relaxed);
1427 [[nodiscard]] std::size_t
capacity() const noexcept {
return capacity_; }
1436 std::vector<std::uint8_t>
raw;
1447 std::unique_lock<std::mutex> lk(mu_);
1448 not_empty_.wait(lk, [
this] {
return not queue_.empty() or stop_; });
1449 if (queue_.empty() and stop_)
1451 task = std::move(queue_.front());
1454 not_full_.notify_one();
1457 if (before_write_hook_)
1458 before_write_hook_();
1464 std::uint32_t flags = 0;
1465 std::uint32_t level = 0;
1466 std::vector<std::uint8_t> payload
1467 = ca_checkpoint_detail::build_payload(task.
raw, task.
options, flags, level);
1468 ca_checkpoint_detail::atomic_write_file(task.
path,
1476 catch (
const std::exception &e)
1484 err =
"Async_Checkpoint_Writer: unknown exception";
1488 std::unique_lock<std::mutex> lk(mu_);
1491 total_written_.fetch_add(1, std::memory_order_relaxed);
1492 else if (not worker_error_)
1493 worker_error_ = std::move(err);
1495 drained_.notify_all();
1508 bool in_flight_ =
false;
1510 std::atomic<std::size_t> total_written_{0};
1511 std::atomic<std::size_t> total_dropped_{0};
1562template <
typename Engine>
1574 [[nodiscard]] std::filesystem::path
format_path(
const std::size_t step)
const
1576 constexpr std::string_view placeholder =
"{step}";
1577 const std::size_t pos = pattern_.find(placeholder);
1578 std::string padded = std::to_string(step);
1579 if (padded.size() < zero_pad_)
1580 padded.insert(0, zero_pad_ - padded.size(),
'0');
1581 if (pos == std::string::npos)
1582 return std::filesystem::path(pattern_ +
"." + padded);
1583 return std::filesystem::path(pattern_.substr(0, pos) + padded
1584 + pattern_.substr(pos + placeholder.size()));
1598 const std::size_t every,
1599 std::string path_pattern,
1600 const std::size_t zero_pad = 6)
1601 : engine_(&
engine), every_(every), pattern_(
std::move(path_pattern)), zero_pad_(zero_pad)
1604 <<
"Periodic_Checkpoint_Observer: every must be >= 1";
1620 const std::size_t every,
1621 std::string path_pattern,
1624 const std::size_t zero_pad = 6)
1627 , pattern_(
std::move(path_pattern))
1628 , zero_pad_(zero_pad)
1629 , async_writer_(&writer)
1633 <<
"Periodic_Checkpoint_Observer: every must be >= 1";
1647 template <
typename Lattice>
1663 template <
typename Lattice>
1666 if (step == 0 or (step % every_) != 0)
1668 last_path_ = format_path(step);
1669 if (async_writer_ !=
nullptr)
1670 async_writer_->
submit(*engine_, last_path_, options_);
1688 [[nodiscard]]
const std::filesystem::path &
last_path() const noexcept
Exception handling system with formatted messages for Aleph-w.
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
size_t size_t int32_t value
size_t size_t int32_t * out
Internal compress / decompress helpers used by ca-checkpoint.H (Phase 17).
Reproducible random-number support for stochastic CA rules (Phase 8).
Common typedefs and tag types for the Cellular Automata module.
Background writer pumping snapshots onto a dedicated thread.
std::optional< std::string > worker_error_
std::deque< Task > queue_
std::size_t pending() const
Snapshot of pending queue depth (informational).
Async_Checkpoint_Writer(const std::size_t capacity=8, const Queue_Policy policy=Queue_Policy::Block)
Construct the writer and spawn its worker thread.
~Async_Checkpoint_Writer() noexcept
Destructor: drains the queue and joins the worker.
std::condition_variable not_empty_
std::size_t capacity() const noexcept
Configured queue capacity.
std::condition_variable drained_
std::size_t total_dropped() const noexcept
Total number of pending tasks discarded due to Drop_Oldest.
Async_Checkpoint_Writer(const Async_Checkpoint_Writer &)=delete
Async_Checkpoint_Writer & operator=(const Async_Checkpoint_Writer &)=delete
std::size_t total_written() const noexcept
Total number of files successfully written by the worker.
void set_before_write_hook(std::function< void()> hook)
Install a hook called by the worker just before each write.
void submit(Engine &engine, std::filesystem::path path, Checkpoint_Options options={})
Synchronously capture the engine state and enqueue a write.
std::function< void()> before_write_hook_
Queue_Policy policy() const noexcept
Configured back-pressure policy.
void worker_loop() noexcept
Queue_Policy
Behaviour when the internal queue is full.
std::condition_variable not_full_
void flush()
Block until the queue is drained.
Lattice that adds boundary-aware access on top of a storage.
typename Storage::state_type state_type
typename Storage::coord_type coord_type
static constexpr std::size_t rank
ca_size_t size() const noexcept
state_type at(const coord_type &c) const
Strict access: throws if c is out of range.
Observer that auto-saves the engine state every every steps to a templated path.
const std::filesystem::path & last_path() const noexcept
Path of the most recent checkpoint scheduled by this observer.
std::filesystem::path last_path_
std::filesystem::path format_path(const std::size_t step) const
Replace the first {step} placeholder with step.
void on_step_end(const std::size_t step, const Lattice &)
Snapshot when step % every == 0.
Periodic_Checkpoint_Observer(Engine &engine, const std::size_t every, std::string path_pattern, Async_Checkpoint_Writer< Engine > &writer, Checkpoint_Options options={}, const std::size_t zero_pad=6)
Build a periodic observer routing writes through an Async_Checkpoint_Writer.
void on_step_begin(const std::size_t step, const Lattice &frame) noexcept
Pre-step hook.
Periodic_Checkpoint_Observer(Engine &engine, const std::size_t every, std::string path_pattern, const std::size_t zero_pad=6)
Build a synchronous periodic checkpoint observer.
RAII guard that removes a temporary file unless committed.
Tmp_File_Guard(std::filesystem::path p) noexcept
std::filesystem::path path_
Tmp_File_Guard & operator=(const Tmp_File_Guard &)=delete
const std::filesystem::path & path() const noexcept
Access the underlying path.
Tmp_File_Guard(const Tmp_File_Guard &)=delete
~Tmp_File_Guard() noexcept
void commit() noexcept
Mark the temporary file as successfully renamed; suppress cleanup.
Minimal std::expected-style result type for C++20.
Shape (per-axis sizes) of an mdspan, mixing compile-time and run-time extents.
Concept: engine that exposes the minimum surface needed by checkpoint save/load.
Concept: lattice exposing the standard CA layout interface.
Concept: rule type that exposes a master RNG seed.
Storage + topology that carries the cell values.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
std::uint64_t type_hash() noexcept
Stable type hash of T.
std::vector< std::uint8_t > deflate_bytes(const std::uint8_t *src, const std::size_t len, const int level)
Compress a raw byte buffer with DEFLATE (miniz backend).
constexpr std::array< char, 8 > magic_bytes
Magic prefix identifying an Aleph CA checkpoint stream.
Header_Snapshot capture_header(const Engine &engine)
Capture all header fields a checkpoint file needs.
std::vector< std::uint8_t > build_payload(const std::vector< std::uint8_t > &raw, const Checkpoint_Options &options, std::uint32_t &flags_out, std::uint32_t &level_out)
Build the (possibly compressed) frame payload from a flat byte buffer, honouring options....
constexpr std::streamoff header_bytes_v1
Size in bytes of the v1 header (Phase 15 layout).
constexpr std::uint64_t fnv1a_64(std::string_view s) noexcept
FNV-1a 64-bit hash.
constexpr std::streamoff header_bytes_v2
Size in bytes of the v2 header (Phase 17 layout: v1 + 4 trailer fields of 4 + 4 + 8 + 8 = 24 bytes).
void sync_file(const std::filesystem::path &path)
Force a file's contents to be durable on the underlying storage device.
constexpr std::uint32_t format_version
Current format version.
std::vector< std::uint8_t > inflate_bytes(const std::uint8_t *src, const std::size_t len, const std::size_t expected_uncompressed)
Decompress a DEFLATE byte buffer back to its raw form.
void restore_frame_from_bytes(Engine &engine, const std::vector< std::uint8_t > &bytes)
Inverse of snapshot_frame_bytes: spray the buffer back into the engine's write lattice in row-major o...
void atomic_write_file(const std::filesystem::path &path, const Header_Snapshot &hs, const std::uint32_t flags, const std::uint32_t compression_level, const std::vector< std::uint8_t > &payload, const std::uint64_t delta_base_step, const Checkpoint_Options &options)
Write header + payload atomically to path.
std::filesystem::path make_tmp_path(const std::filesystem::path &target)
Generate a temporary sibling path for atomic writes.
T read_le(std::istream &in)
void write_header_bytes(std::ostream &out, const Header_Snapshot &hs, const std::uint32_t flags, const std::uint32_t compression_level, const std::uint64_t payload_size, const std::uint64_t delta_base_step)
Serialise the v2 header bytes for a checkpoint file.
constexpr std::uint32_t format_version_min_read
Oldest format version this file knows how to read.
void write_le(std::ostream &out, const T &value)
std::vector< std::uint8_t > read_raw_payload(const std::filesystem::path &path, const Checkpoint_Header &h)
Read the raw, uncompressed payload from a checkpoint file given a validated header.
void sync_directory(const std::filesystem::path &dir) noexcept
Force a directory's metadata to be durable.
std::vector< std::uint8_t > snapshot_frame_bytes(const Engine &engine)
Flatten an engine frame into a row-major byte buffer.
constexpr std::uint32_t compressed
Frame payload was written through DEFLATE (miniz).
constexpr std::uint32_t delta
File contains only the cells that changed relative to a baseline.
@ R
Recovered (and immune).
Checkpoint_Header inspect_checkpoint(const std::filesystem::path &path)
Read just the header from a checkpoint file.
std::ptrdiff_t ca_index_t
Signed coordinate component used by lattices and neighborhoods.
Resume_Token apply_delta_checkpoint(Engine &engine, const std::filesystem::path &path)
Apply a delta checkpoint to the engine's current state.
constexpr std::uint64_t splitmix64(std::uint64_t x) noexcept
64-bit SplitMix hash.
void save_delta_checkpoint(Engine &engine, const std::vector< std::uint8_t > &baseline, const std::uint64_t base_step, const std::filesystem::path &path, const Checkpoint_Options &options={})
Write a delta checkpoint relative to a previous payload.
std::size_t ca_size_t
Unsigned size component used for extents and counts.
Resume_Token load_checkpoint_into(Engine &engine, const std::filesystem::path &path)
Restore an engine's state in-place from a checkpoint file.
void save_checkpoint(Engine &engine, const std::filesystem::path &path, const Checkpoint_Options &options={})
Write a complete engine snapshot to disk atomically.
Main namespace for Aleph-w library functions.
static void suffix(Node *root, DynList< Node * > &acc)
@ Block
Scoped sequence of statements.
and
Check uniqueness with explicit hash + equality functors.
std::decay_t< typename HeadC::Item_Type > T
bool diff(const C1 &c1, const C2 &c2, Eq e=Eq())
Check if two containers differ.
void next()
Advance all underlying iterators (bounds-checked).
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
static struct argp_option options[]
Checkpoint_Options options
std::vector< std::uint8_t > raw
std::filesystem::path path
ca_checkpoint_detail::Header_Snapshot header
User-tunable knobs for save_checkpoint.
bool compress
Enable DEFLATE compression of the frame payload.
bool sync_dir
Call fsync on the parent directory after the atomic rename.
int level
DEFLATE level in [1, 9]. 0 falls back to 6.
Resume handle returned by load_checkpoint_into.
std::filesystem::path source_path
Path of the checkpoint file consumed by load.
Checkpoint_Header header
Validated header read from source_path.
Task with priority for job scheduling.
C++20 concepts for the Cellular Automata module.