summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/frame_driver.hpp
blob: fdf806ac2a49ef457fdf73e7ad17cf93085ea0f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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