#include "icsneo/api/eventmanager.h" #include #include #include #include using namespace icsneo; EventManager& EventManager::GetInstance() { static EventManager inst; return inst; } void EventManager::downgradeErrorsOnCurrentThread() { if(destructing) return; std::lock_guard lk(downgradedThreadsMutex); auto i = downgradedThreads.find(std::this_thread::get_id()); if(i != downgradedThreads.end()) { i->second = true; } else { downgradedThreads.insert({std::this_thread::get_id(), true}); } } void EventManager::cancelErrorDowngradingOnCurrentThread() { if(destructing) return; std::lock_guard lk(downgradedThreadsMutex); auto i = downgradedThreads.find(std::this_thread::get_id()); if(i != downgradedThreads.end()) { i->second = false; } } void EventManager::add(APIEvent event) { if(destructing) return; static const auto printLevel = []() -> std::optional { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4996) #endif const auto level = std::getenv("LIBICSNEO_PRINT_EVENTS"); #ifdef _MSC_VER #pragma warning(pop) #endif if(!level) return std::nullopt; try { return (uint8_t)std::stoi(level); } catch (std::invalid_argument const&) { return std::nullopt; } }(); if(printLevel && (uint8_t)event.getSeverity() >= *printLevel) std::cerr << event.describe() << std::endl; if(event.getSeverity() == APIEvent::Severity::Error) { // if the error was added on a thread that downgrades errors (non-user thread) std::lock_guard lk(downgradedThreadsMutex); auto i = downgradedThreads.find(std::this_thread::get_id()); if(i != downgradedThreads.end() && i->second) { event.downgradeFromError(); { std::lock_guard eventsLock(eventsMutex); addEventInternal(event); } // free the lock so that callbacks may modify events runCallbacks(event); } else { std::lock_guard errorsLock(errorsMutex); lastUserErrors[std::this_thread::get_id()] = event; } } else { { std::lock_guard eventsLock(eventsMutex); addEventInternal(event); } // free the lock so that callbacks may modify events runCallbacks(event); } } void EventManager::addEventInternal(APIEvent event) { // Ensure the event list is at most exactly full (size of eventLimit - 1, leaving room for a potential APIEvent::TooManyEvents) // Removes any events of type TooManyEvents from the end before checking to avoid duplicates. enforceLimit(); // We are exactly full, either because the list was truncated or because we were simply full before if(events.size() == eventLimit - 1) { // If the event is worth adding if(event.getType() != APIEvent::Type::TooManyEvents) { discardOldest(1); events.push_back(event); } events.push_back(APIEvent(APIEvent::Type::TooManyEvents, APIEvent::Severity::EventWarning)); } else { if (event.getType() != APIEvent::Type::TooManyEvents) events.push_back(event); } } void EventManager::runCallbacks(APIEvent event) { std::lock_guard lk(callbacksMutex); for(auto& i : callbacks) i.second.callIfMatch(std::make_shared(event)); } void EventManager::setEventLimit(size_t newLimit) { std::lock_guard eventLimitLock(eventLimitMutex); if(newLimit == eventLimit) return; if(newLimit < 10) { add(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error); return; } eventLimit = newLimit; std::lock_guard eventsLock(eventsMutex); if(enforceLimit()) addEventInternal(APIEvent(APIEvent::Type::TooManyEvents, APIEvent::Severity::EventWarning)); } void EventManager::ResetInstance() { std::lock_guard eventsLock(eventsMutex); std::lock_guard errorsLock(errorsMutex); std::lock_guard downgradedThreadsLock(downgradedThreadsMutex); std::lock_guard callbacksLock(callbacksMutex); std::lock_guard callbackIDLock(callbackIDMutex); std::lock_guard eventLimitLock(eventLimitMutex); events.clear(); lastUserErrors.clear(); downgradedThreads.clear(); callbacks.clear(); callbackID = 0; eventLimit = 10000; } int EventManager::addEventCallback(const EventCallback &cb) { std::lock_guard callbacksLock(callbacksMutex); std::lock_guard callbackIDLock(callbackIDMutex); callbacks.insert({callbackID, cb}); return callbackID++; } bool EventManager::removeEventCallback(int id) { std::lock_guard lk(callbacksMutex); auto iter = callbacks.find(id); if(iter != callbacks.end()) { callbacks.erase(iter); return true; } else return false; } bool EventManager::isDowngradingErrorsOnCurrentThread() const { auto i = downgradedThreads.find(std::this_thread::get_id()); if(i != downgradedThreads.end()) { return i->second; } return false; } void EventManager::get(std::vector& eventOutput, size_t max, EventFilter filter) { std::lock_guard lk(eventsMutex); if(max == 0) // A limit of 0 indicates no limit max = (size_t)-1; size_t count = 0; eventOutput.clear(); auto it = events.begin(); while(it != events.end()) { if(filter.match(*it)) { eventOutput.push_back(*it); it = events.erase(it); if(++count >= max) break; // We now have as many written to output as we can } else { it++; } } } /** * Removes the returned error from the map * If no error was found, return a NoErrorFound Info event */ APIEvent EventManager::getLastError() { std::lock_guard lk(errorsMutex); auto it = lastUserErrors.find(std::this_thread::get_id()); if(it == lastUserErrors.end()) { return APIEvent(APIEvent::Type::NoErrorFound, APIEvent::Severity::EventInfo); } else { APIEvent ret = it->second; it = lastUserErrors.erase(it); return ret; } } void EventManager::discard(EventFilter filter) { std::lock_guard lk(eventsMutex); events.remove_if([&filter](const APIEvent& event) { return filter.match(event); }); } size_t EventManager::countInternal(EventFilter filter) const { size_t ret = 0; for(auto& event : events) if(filter.match(event)) ret++; return ret; } /** * Ensures events is always at most eventLimit - 1 in size. * Returns true if any events were removed in the process of doing so. */ bool EventManager::enforceLimit() { // Remove all TooManyEvents from the end before checking auto filter = EventFilter(APIEvent::Type::TooManyEvents); auto it = events.rbegin(); while(it != events.rend() && filter.match(*it)) { it = decltype(it){events.erase( std::next(it).base() )}; } // We are not overflowing if(events.size() < eventLimit) return false; size_t amountToRemove = events.size() + 1 - eventLimit; discardOldest(amountToRemove); return true; } void EventManager::discardOldest(size_t count) { if(count == 0) return; auto it = events.begin(); while(it != events.end()) { it = events.erase(it); if(--count == 0) break; } }