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