1 | //===-- ProgressEvent.cpp ---------------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include <atomic> |
10 | #include <mutex> |
11 | #include <optional> |
12 | #include <queue> |
13 | #include <thread> |
14 | |
15 | #include "DAPForward.h" |
16 | |
17 | #include "llvm/Support/JSON.h" |
18 | |
19 | namespace lldb_dap { |
20 | |
21 | enum ProgressEventType { progressStart, progressUpdate, progressEnd }; |
22 | |
23 | class ProgressEvent; |
24 | using ProgressEventReportCallback = std::function<void(ProgressEvent &)>; |
25 | |
26 | class ProgressEvent { |
27 | public: |
28 | /// Actual constructor to use that returns an optional, as the event might be |
29 | /// not apt for the IDE, e.g. an unnamed start event, or a redundant one. |
30 | /// |
31 | /// \param[in] progress_id |
32 | /// ID for this event. |
33 | /// |
34 | /// \param[in] message |
35 | /// Message to display in the UI. Required for start events. |
36 | /// |
37 | /// \param[in] completed |
38 | /// Number of jobs completed. |
39 | /// |
40 | /// \param[in] total |
41 | /// Total number of jobs, or \b UINT64_MAX if not determined. |
42 | /// |
43 | /// \param[in] prev_event |
44 | /// Previous event if this one is an update. If \b nullptr, then a start |
45 | /// event will be created. |
46 | static std::optional<ProgressEvent> |
47 | Create(uint64_t progress_id, std::optional<llvm::StringRef> message, |
48 | uint64_t completed, uint64_t total, |
49 | const ProgressEvent *prev_event = nullptr); |
50 | |
51 | llvm::json::Value ToJSON() const; |
52 | |
53 | /// \return |
54 | /// \b true if two event messages would result in the same event for the |
55 | /// IDE, e.g. same rounded percentage. |
56 | bool EqualsForIDE(const ProgressEvent &other) const; |
57 | |
58 | llvm::StringRef GetEventName() const; |
59 | |
60 | ProgressEventType GetEventType() const; |
61 | |
62 | /// Report this progress event to the provided callback only if enough time |
63 | /// has passed since the creation of the event and since the previous reported |
64 | /// update. |
65 | bool Report(ProgressEventReportCallback callback); |
66 | |
67 | bool Reported() const; |
68 | |
69 | private: |
70 | ProgressEvent(uint64_t progress_id, std::optional<llvm::StringRef> message, |
71 | uint64_t completed, uint64_t total, |
72 | const ProgressEvent *prev_event); |
73 | |
74 | uint64_t m_progress_id; |
75 | std::string m_message; |
76 | ProgressEventType m_event_type; |
77 | std::optional<uint32_t> m_percentage; |
78 | std::chrono::duration<double> m_creation_time = |
79 | std::chrono::system_clock::now().time_since_epoch(); |
80 | std::chrono::duration<double> m_minimum_allowed_report_time; |
81 | bool m_reported = false; |
82 | }; |
83 | |
84 | /// Class that keeps the start event and its most recent update. |
85 | /// It controls when the event should start being reported to the IDE. |
86 | class ProgressEventManager { |
87 | public: |
88 | ProgressEventManager(const ProgressEvent &start_event, |
89 | ProgressEventReportCallback report_callback); |
90 | |
91 | /// Report the start event and the most recent update if the event has lasted |
92 | /// for long enough. |
93 | /// |
94 | /// \return |
95 | /// \b false if the event hasn't finished and hasn't reported anything |
96 | /// yet. |
97 | bool ReportIfNeeded(); |
98 | |
99 | /// Receive a new progress event for the start event and try to report it if |
100 | /// appropriate. |
101 | void Update(uint64_t progress_id, uint64_t completed, uint64_t total); |
102 | |
103 | /// \return |
104 | /// \b true if a \a progressEnd event has been notified. There's no |
105 | /// need to try to report manually an event that has finished. |
106 | bool Finished() const; |
107 | |
108 | const ProgressEvent &GetMostRecentEvent() const; |
109 | |
110 | private: |
111 | ProgressEvent m_start_event; |
112 | std::optional<ProgressEvent> m_last_update_event; |
113 | bool m_finished; |
114 | ProgressEventReportCallback m_report_callback; |
115 | }; |
116 | |
117 | using ProgressEventManagerSP = std::shared_ptr<ProgressEventManager>; |
118 | |
119 | /// Class that filters out progress event messages that shouldn't be reported |
120 | /// to the IDE, because they are invalid, they carry no new information, or they |
121 | /// don't last long enough. |
122 | /// |
123 | /// We need to limit the amount of events that are sent to the IDE, as they slow |
124 | /// the render thread of the UI user, and they end up spamming the DAP |
125 | /// connection, which also takes some processing time out of the IDE. |
126 | class ProgressEventReporter { |
127 | public: |
128 | /// \param[in] report_callback |
129 | /// Function to invoke to report the event to the IDE. |
130 | ProgressEventReporter(ProgressEventReportCallback report_callback); |
131 | |
132 | ~ProgressEventReporter(); |
133 | |
134 | /// Add a new event to the internal queue and report the event if |
135 | /// appropriate. |
136 | void Push(uint64_t progress_id, const char *message, uint64_t completed, |
137 | uint64_t total); |
138 | |
139 | private: |
140 | /// Report to the IDE events that haven't been reported to the IDE and have |
141 | /// lasted long enough. |
142 | void ReportStartEvents(); |
143 | |
144 | ProgressEventReportCallback m_report_callback; |
145 | std::map<uint64_t, ProgressEventManagerSP> m_event_managers; |
146 | /// Queue of start events in chronological order |
147 | std::queue<ProgressEventManagerSP> m_unreported_start_events; |
148 | /// Thread used to invoke \a ReportStartEvents periodically. |
149 | std::thread m_thread; |
150 | std::atomic<bool> m_thread_should_exit; |
151 | /// Mutex that prevents running \a Push and \a ReportStartEvents |
152 | /// simultaneously, as both read and modify the same underlying objects. |
153 | std::mutex m_mutex; |
154 | }; |
155 | |
156 | } // namespace lldb_dap |
157 | |