summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/frame_driver.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'packages/kernel/src/frame_driver.hpp')
-rw-r--r--packages/kernel/src/frame_driver.hpp65
1 files changed, 65 insertions, 0 deletions
diff --git a/packages/kernel/src/frame_driver.hpp b/packages/kernel/src/frame_driver.hpp
new file mode 100644
index 0000000..fdf806a
--- /dev/null
+++ b/packages/kernel/src/frame_driver.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <unbox/kernel/frames.hpp>
+#include <unbox/kernel/hooks.hpp> // ExtensionId
+
+#include <functional>
+#include <unordered_map>
+#include <vector>
+
+// The kernel's per-frame animation driver. Holds the set of live per-frame
+// callbacks registered via Host::request_frames; the kernel's output frame
+// handler drains them (in order, reentrancy-safe) BEFORE the ui substrate ticks
+// and the scene commits, and — while the set is non-empty — keeps requesting
+// output frames so animations advance even when the scene is idle.
+//
+// Error-isolated: a throwing callback is caught at the boundary and the owning
+// extension is disabled via the injected sink (same contract as hooks / file
+// watches), never the session.
+//
+// Single wl_event_loop thread throughout; no internal locking.
+
+namespace unbox::kernel {
+
+class FrameDriver final : public detail::FrameRegistry {
+public:
+ using Token = detail::FrameRegistry::Token;
+
+ // `disable` disables the owning extension when its callback throws (injected
+ // by the kernel, same as the bus/substrate/file-watch isolation sink).
+ explicit FrameDriver(std::function<void(ExtensionId)> disable);
+ ~FrameDriver() override = default;
+ FrameDriver(const FrameDriver&) = delete;
+ auto operator=(const FrameDriver&) -> FrameDriver& = delete;
+
+ // Register `on_frame` (fired each frame with dt seconds, error-isolated to
+ // `who`). Returns a FrameRequest RAII handle; destroying it removes the
+ // callback. Always active (the driver itself has no loop dependency — the
+ // caller decides whether a handle is inert when there is no output).
+ [[nodiscard]] auto add(std::function<void(double)> on_frame, ExtensionId who) -> FrameRequest;
+
+ // True if at least one callback is live (the kernel keeps scheduling frames
+ // while this holds).
+ [[nodiscard]] auto has_requests() const noexcept -> bool { return !entries_.empty(); }
+
+ // Fire every live callback once with `dt_seconds`. Reentrancy-safe: a
+ // callback may add or remove requests (including its own) mid-drain — a
+ // request added during the drain first fires NEXT frame, one removed during
+ // the drain does not fire again this drain.
+ void drain(double dt_seconds);
+
+ // detail::FrameRegistry — stop the callback with this token (handle dtor).
+ void remove_frame_request(Token token) noexcept override;
+
+private:
+ struct Entry {
+ std::function<void(double)> on_frame;
+ ExtensionId who{};
+ };
+
+ std::function<void(ExtensionId)> disable_;
+ Token next_token_ = 0;
+ std::unordered_map<Token, Entry> entries_;
+};
+
+} // namespace unbox::kernel