// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The session: the app's end of the debug connection. // // ThreadPool::Start(); // Debug::Session debug({ .name = "MyApp" }); // EventListener pump(&window.onBeforeUpdate, [] { Debug::Pump(); }); // // Requests arrive on transport threads and are never answered by blocking // one: what needs only the session's own state (status, the command list, // the log, pause, resume) is answered there and then; what touches the app // (calls, watches, traces, screenshots) waits in a queue for the next Pump, // which runs it on the app's own thread between frames; waits and steps are // answered by the Pump that sees them satisfied. A request that cannot run // before its timeout is answered Timeout and is guaranteed not to have run. // // Pause and step only stop what the app gates on them: // // while (acc >= dt && Debug::Advance()) { sim.Tick(); acc -= dt; } // frame loops // if (Debug::Paused()) { for (auto n = Debug::TakeSteps(); n > 0; --n) sim.Tick(); acc = 0; } // // fixed-step loops // // Rendering and Pump keep running while paused, so the agent can still read // watches, take screenshots and step. export module Crafter.Debug:Session; import std; import Crafter.Debug.Core; export namespace Crafter::Debug::Detail { // One request's way back to its client. A transport implements it; the // session calls it from transport threads, the Pump thread and whatever // thread logs, so it must be safe from any thread and must not block. class Exchange { public: virtual ~Exchange() = default; // The final reply, ending the exchange. virtual void Reply(const Value& reply) = 0; // One more object on a streaming exchange (`log --follow`). False // when the client is gone or too far behind; the session then drops it. virtual bool Stream(const Value& line) = 0; // Reply, then call `afterSent` once the reply has left: what // `quit --force` exits from. virtual void ReplyThen(const Value& reply, std::function afterSent) { Reply(reply); afterSent(); } }; } export namespace Crafter::Debug { struct SessionOptions { // What `crafter-debug ls` calls this instance; the executable's // file name when empty. std::string name; // 0 for any free port; the discovery file says which. std::uint16_t port = 0; // Also record what the app writes to stdout and stderr on the timeline. bool captureStdio = false; // Called on the Pump thread when an agent asks the app to quit, // after QuitRequested() turned true. Don't call std::exit from it: // close the window or end the loop, so the Session is destroyed // before main returns. std::function onQuit; // False: no transport and no discovery file; requests come only // through Handle. For tests. bool listen = true; }; // One per process; a second throws. Destroy it before // ThreadPool::Stop() and before main returns: it closes its connections, // and msquic waits for open ones at exit. class Session { public: explicit Session(SessionOptions options = {}); ~Session(); Session(const Session&) = delete; Session& operator=(const Session&) = delete; // One request, from a transport or a test. Never blocks; the reply // goes through `exchange`. void Handle(const Value& request, std::shared_ptr exchange); // What every request must carry. const std::string& Token() const; struct State; private: std::shared_ptr state; }; // Once per frame, on the thread that owns the app's state: runs queued // requests, polls Async results, evaluates waits and conditions, records // traces, and counts the frame. Does nothing without a Session. void Pump(); // For frame loops: false while paused, true once per step granted. bool Advance(); // For fixed-step loops: whether the session is paused; see TakeSteps. bool Paused(); // The steps granted since the last call, all at once. std::uint32_t TakeSteps(); // Whether an agent asked the app to quit. bool QuitRequested(); // Where `crafter-debug screenshot` gets a PNG: called on the Pump // thread, returning the bytes when they are ready (a frame or two later // for a GPU readback). See the README for the Crafter.Graphics version. void SetCapture(std::function>()> capture); }