-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
533 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
enum class BackgroundTaskStatus { NotStarted = 0, Running = 1, Finished = 2, Cancelled = 3 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#include <sstream> | ||
#include <queue> | ||
|
||
/* Common logging class for all background tasks */ | ||
class TaskLogger { | ||
public: | ||
TaskLogger() = default; | ||
|
||
TaskLogger(const TaskLogger&) = delete; | ||
TaskLogger(TaskLogger&&) = delete; | ||
TaskLogger& operator=(const TaskLogger&) = delete; | ||
TaskLogger& operator=(TaskLogger&&) = delete; | ||
|
||
~TaskLogger() = default; | ||
|
||
template <typename T> TaskLogger& operator<<(T value) { | ||
std::stringstream sstream{}; | ||
sstream << value; | ||
char chr{}; | ||
std::string cur_message; | ||
while (sstream >> chr) { | ||
if (chr == '\n') { | ||
_message_queue.push(cur_message); | ||
cur_message.clear(); | ||
} else { | ||
cur_message.push_back(chr); | ||
} | ||
} | ||
|
||
return *this; | ||
} | ||
|
||
bool hasQueuedMessage() { return !_message_queue.empty(); } | ||
|
||
// Pop the oldest message from the queue and return it | ||
std::string popMessage() { | ||
auto oldest = _message_queue.front(); | ||
_message_queue.pop(); | ||
return oldest; | ||
} | ||
|
||
private: | ||
std::queue<std::string> _message_queue; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#include "search_engine/search.hpp" | ||
|
||
#include <mutex> | ||
#include <ftxui/component/event.hpp> | ||
#include <ftxui/component/screen_interactive.hpp> | ||
|
||
// This class acts a simple PC-queue between the search engine and the UI | ||
class ReverseDispatcher: public SearchListener { | ||
public: | ||
void onEvent(SearchEvent event) override { | ||
_event_queue.emplace_back(event); | ||
|
||
_screen_ptr->PostEvent(ftxui::Event::Custom); | ||
} | ||
|
||
bool hasQueuedEvents() { | ||
std::unique_lock lock{_q_mutex}; | ||
return !_event_queue.empty(); | ||
} | ||
|
||
std::vector<SearchEvent> getQueuedEvents() { | ||
std::unique_lock lock{_q_mutex}; | ||
|
||
auto ret = _event_queue; | ||
_event_queue.clear(); | ||
|
||
return ret; | ||
} | ||
|
||
private: | ||
std::vector<SearchEvent> _event_queue; | ||
std::mutex _q_mutex; | ||
ftxui::ScreenInteractive* _screen_ptr; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
#include "search_dispatch.hpp" | ||
|
||
bool SearchDispatcher::tryDispatch(std::unique_ptr<std::istream> stream_ptr, SearchConfig config) { | ||
// The check if a search can be dispatched and the dispatch needs to be tied (atomically) | ||
// together, therefore we put them together into a method. | ||
// Might change to std::expected later for more meaningful error messages | ||
std::unique_lock lock{_mutex}; | ||
|
||
// Remove the current search if ended | ||
if (_search_ptr && _search_ptr->ended()) { | ||
_search_ptr.reset(); | ||
} | ||
|
||
if (_search_ptr) { | ||
return false; | ||
} | ||
|
||
_search_ptr = std::make_unique<Search>(std::move(stream_ptr), config, std::make_unique<TaskLogger>()); | ||
return true; | ||
} | ||
|
||
void SearchDispatcher::requestCancelCurrentSearch() { | ||
std::unique_lock lock{_mutex}; | ||
|
||
if (_search_ptr) { | ||
_search_ptr->requestCancel(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#pragma once | ||
|
||
#include "search_engine/search.hpp" | ||
|
||
#include <memory> | ||
#include <mutex> | ||
|
||
// This class is used to manage and dispatch searches. | ||
// Currently the only policy is to allow only one search at a time. | ||
class SearchDispatcher { | ||
public: | ||
// Atomically dispatches the search. | ||
// Returns true if the search has been dispatched, false otherwise. | ||
bool tryDispatch(std::unique_ptr<std::istream> file_stream, SearchConfig config); | ||
|
||
void requestCancelCurrentSearch(); | ||
private: | ||
std::mutex _mutex; | ||
std::unique_ptr<Search> _search_ptr; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
#include "search.hpp" | ||
#include "LFV/lfv_exception.hpp" | ||
#include <array> | ||
#include <iostream> | ||
#include <string> | ||
|
||
// value must be in range [0, mod) | ||
template<typename V, typename M> | ||
inline void modularIncrement(V& value, M mod) { | ||
if (value == mod - 1) { | ||
value = 0; | ||
} | ||
else { | ||
value++; | ||
} | ||
} | ||
|
||
// value must be in range [0, mod) | ||
template<typename V, typename M> | ||
inline void modularDecrement(V& value, M mod) { | ||
if (value == 0) { | ||
value = mod - 1; | ||
} | ||
else { | ||
value--; | ||
} | ||
} | ||
|
||
class CircularBuffer { | ||
public: | ||
CircularBuffer(size_t len) : _buffer(len), _len{len} {}; | ||
|
||
int& at(size_t index) { | ||
assert(index < _len && index >= 0); // TO BE REMOVED | ||
return (*this)[index]; | ||
} | ||
|
||
int& operator[] (size_t index) { | ||
size_t pos = _start + index; | ||
if (pos >= _len) pos -= _len; | ||
return _buffer[pos]; | ||
} | ||
|
||
// Pops the first value and adds new_val to the end | ||
// Returns the old front value | ||
int shift(int new_val) { | ||
int old_val = _buffer[_start]; | ||
_buffer[_start] = new_val; | ||
modularIncrement(_start, _len); | ||
return old_val; | ||
} | ||
|
||
bool operator==(std::string_view view) { | ||
if (_len != view.size()) return false; | ||
for (size_t index = 0; index < _len; index++) { | ||
if ((*this)[index] != view[index]) return false; | ||
} | ||
return true; | ||
} | ||
|
||
friend std::ostream& operator <<(std::ostream& out, CircularBuffer& buf) { | ||
out << "Buffer{"; | ||
for (size_t index = 0; index < buf._len; index++) { | ||
out << buf[index]; | ||
if (index + 1 < buf._len) out << ','; | ||
} | ||
out << '}'; | ||
return out; | ||
} | ||
|
||
private: | ||
std::vector<int> _buffer; | ||
size_t _len; | ||
size_t _start = 0; | ||
}; | ||
|
||
void Search::start() { | ||
constexpr int kMaxAlphabet = 1 << 8; | ||
constexpr int kMaxPatLen = 1 << 8; | ||
constexpr int kHeavyCycle = 1000; | ||
|
||
const std::string& pattern = _config.pattern; | ||
const int pat_len = static_cast<int>(pattern.size()); | ||
|
||
if (pat_len > kMaxPatLen) { | ||
throw LFVException{"Pattern length exceeded the limit of " + std::to_string(kMaxPatLen)}; | ||
} | ||
|
||
// Initialise BMH jump_table | ||
std::vector<int> jump_table (kMaxAlphabet, pat_len); | ||
for (int index = 0; index + 2 <= pat_len; index++) { | ||
int ascii = pattern[index]; | ||
jump_table[ascii] = pat_len - 1 - index; | ||
} | ||
|
||
CircularBuffer buffer(pat_len); | ||
|
||
// Start search | ||
// Clear fail bits to avoid weird stream behaviours | ||
_stream_ptr->clear(); | ||
_stream_ptr->seekg(_config.start); | ||
_status = BackgroundTaskStatus::Running; | ||
|
||
// Read the first pat_len - 1 characters | ||
for (int index = 0; | ||
index < pat_len - 1 && _stream_ptr->tellg() != _config.end && _stream_ptr->peek() != EOF; | ||
index++) { | ||
buffer[index] = _stream_ptr->get(); | ||
} | ||
|
||
int update_countdown = kHeavyCycle; | ||
int forward_steps = 1; | ||
// Cannot use stream::eof() as it only return false when we try to read past the end | ||
// E.g it eof bit is only set when we already consumed EOF | ||
while (_stream_ptr->tellg() != _config.end && _stream_ptr->peek() != EOF && _matches.size() < _config.limit) { | ||
while (forward_steps > 0 && _stream_ptr->tellg() != _config.end && _stream_ptr->peek() != EOF) { | ||
buffer.shift(_stream_ptr->get()); | ||
forward_steps--; | ||
} | ||
|
||
bool match = buffer == pattern; | ||
if (match) { | ||
addMatch(_stream_ptr->tellg() - (std::streampos)pat_len); | ||
forward_steps = 1; | ||
} else { | ||
int last_char = buffer[pat_len - 1]; | ||
forward_steps = jump_table[last_char]; | ||
} | ||
|
||
// Update progress and check exit condition | ||
// every heavy cycle/whenever a match is found to avoid overhead. | ||
update_countdown--; | ||
if (update_countdown == 0 || match) { | ||
if (_cancel_requested) { | ||
cancel(); | ||
return; | ||
} | ||
|
||
setSearchProgress(_stream_ptr->tellg(), false); | ||
update_countdown = kHeavyCycle; | ||
} | ||
} | ||
|
||
_status = BackgroundTaskStatus::Finished; | ||
|
||
announce({SearchEventType::Finished, clock::now()}); | ||
} | ||
|
||
void Search::addMatch(std::streampos pos) { | ||
_matches.push_back(pos); | ||
|
||
announce({SearchEventType::FoundNew, clock::now()}); | ||
} | ||
|
||
void Search::setSearchProgress(std::streampos cur_pos, bool should_announce) { | ||
_search_progress = cur_pos; | ||
|
||
if (should_announce) { | ||
announce({SearchEventType::ProgressUpdate, clock::now()}); | ||
} | ||
} | ||
|
||
void Search::cancel() { | ||
_status = BackgroundTaskStatus::Cancelled; | ||
|
||
announce({SearchEventType::Cancelled, clock::now()}); | ||
} | ||
|
||
void Search::announce(SearchEvent event) { | ||
for (auto listener_ptr: _listener_ptrs) { | ||
listener_ptr->onEvent(event); | ||
} | ||
} |
Oops, something went wrong.