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