From 155025a1a0db1f3b52dd8600f01cd8426d8e3ad1 Mon Sep 17 00:00:00 2001 From: Jerry Belich Date: Wed, 9 Sep 2026 22:47:08 -0700 Subject: [PATCH] compiler: borrow deferred containers instead of copying them json_compiler stored each deferred named child by value, so a container nested d levels deep was copied d times over: compile_container() recursed into the copy and deferred its children the same way. On a memory-constrained target that dominated the cost of a compile - it was most of why one needed roughly 16x the source JSON in heap. The source document outlives the whole compile. It is the caller's json, reached by reference through compile() -> compile_container() -> handle_container_metadata(), so a pointer is safe and the copy was never serving a purpose. Measured on an ESP32-S3 across a range of story sizes, peak heap fell from 16.4x the source size to 12.05x. Co-Authored-By: Claude Opus 5 --- inkcpp_compiler/json_compiler.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/inkcpp_compiler/json_compiler.cpp b/inkcpp_compiler/json_compiler.cpp index e6550f0e..bd3a5842 100644 --- a/inkcpp_compiler/json_compiler.cpp +++ b/inkcpp_compiler/json_compiler.cpp @@ -18,7 +18,14 @@ namespace ink::compiler::internal using nlohmann::json; using std::vector; -typedef std::tuple defer_entry; +/* Holds a pointer, not the container itself. Storing the json by value here + * deep-copied every named child's whole subtree, and because compile_container + * then recursed into that copy and deferred *its* children the same way, a + * container nested d levels deep was copied d times over. The source document + * outlives the entire compile (it is the caller's json, reached by reference + * through compile() -> compile_container() -> handle_container_metadata()), so + * borrowing is safe. */ +typedef std::tuple defer_entry; json_compiler::json_compiler() : _emitter(nullptr) @@ -117,7 +124,7 @@ void json_compiler::handle_container_metadata(const json& meta, container_meta& // Child container else { // Add to deferred compilation list - data.deferred.push_back(std::make_tuple(meta_iter.value(), meta_iter.key())); + data.deferred.push_back(std::make_tuple(&meta_iter.value(), meta_iter.key())); } } } else if (is_knot) { @@ -230,7 +237,7 @@ void json_compiler::compile_container( using std::get; // Add to named child list - compile_container(get<0>(t), -1, depth + 1, get<1>(t)); + compile_container(*get<0>(t), -1, depth + 1, get<1>(t)); // Need a divert here uint32_t pos = _emitter->fallthrough_divert();