#pragma once #include "runtime/Gallery_Runtime.hpp" #include #include #include #include #include namespace aethera::mcp { struct Control_Service::Private { struct Entry; struct Entry_Retirement; struct Entry_Stop_Lifetime; struct Policy_Scene : Def { struct Private; }; struct Headless_Sink : Def { struct Private; }; struct Entry { Entry(std::string id, web::Gallery_Build build, proxy sink, std::uint32_t width, std::uint32_t height); ~Entry(); Entry(const Entry&) = delete; Entry& operator=(const Entry&) = delete; void start(); void stop(std::shared_ptr retirement) noexcept; void request_frame(web::Gallery_Frame_Request request); proxy create_frame(); scene::Render_Result render(proxy& frame, scene::Render_Completion completion); [[nodiscard]] nlohmann::json schema() const; [[nodiscard]] nlohmann::json diagnostics() const; void reset_diagnostics() noexcept; void request_trace(std::size_t frame_count); [[nodiscard]] nlohmann::json trace() const; [[nodiscard]] std::shared_ptr current_policy() const noexcept; [[nodiscard]] bool reserve_trace() noexcept; void restore_trace() noexcept; void store_trace(const Taskflow_Execution_Trace& trace, const web::Gallery_Frame_Request& request, const nlohmann::json& backend = {}); std::unique_ptr components; /* Sole owner of plot models, camera and axes; declared before Scene so it outlives every Scene borrow. */ web::Gallery_Scene scene; /* Sole owner of the dimension-specific Scene. */ proxy sink; /* Sole owner until start transfers the concrete media Sink into Frame Policy. */ std::atomic> policy{}; /* Shared only across asynchronous stop retirement. */ std::string id; /* Stable catalog identifier. */ std::uint32_t output_width{}; /* Default width for timer-driven requests. */ std::uint32_t output_height{}; /* Default height for timer-driven requests. */ std::chrono::steady_clock::time_point started_at{std::chrono::steady_clock::now()}; /* Runtime animation clock origin. */ std::atomic_uint64_t next_sequence{}; /* Authoritative next rendered-frame sequence source. */ std::atomic> requested_frame{}; /* Latest explicit request consumed by one subsequent policy tick. */ std::atomic> datoviz_observation{}; /* Latest immutable 3D backend diagnostic publication. */ static constexpr std::size_t maximum_taskflow_trace_frames{120}; std::atomic_size_t taskflow_trace_remaining{}; /* Number of requested successful Scene executions not yet captured. */ std::atomic_uint64_t taskflow_trace_control{}; /* High 32 bits requested, low 32 bits captured. */ std::array>, maximum_taskflow_trace_frames> taskflow_trace_slots{}; /* Immutable captured frame traces. */ }; struct Entry_Retirement { std::shared_ptr entry; /* Sole retirement owner after the registry has detached the Entry. */ std::function completion; ~Entry_Retirement() noexcept; }; struct Entry_Stop_Lifetime { std::shared_ptr retirement; /* Declared before policy so Entry retirement is released after the stopped policy. */ std::shared_ptr policy; }; std::unordered_map> plots; /* Stable plot registry; entries retire themselves asynchronously. */ bool stop_started{}; /* Sole Control_Service lifecycle authority; public operations are unavailable after the one shutdown begins. */ }; struct Control_Service::Private::Policy_Scene::Private : Prev_Private { explicit Private(Entry* entry) : entry(entry) {} proxy create_frame(); scene::Render_Result render(proxy& frame, scene::Render_Completion completion); Entry* entry; /* Required non-owning borrow; Entry owns the policy and outlives asynchronous stop completion. */ }; struct Control_Service::Private::Headless_Sink::Private : Prev_Private { void send(proxy& frame, frame_policy::Frame_Completion completion) { completion(frame); } }; } // namespace aethera::mcp