Skip to content

Commit

Permalink
Rewrote search engine
Browse files Browse the repository at this point in the history
  • Loading branch information
hgv79116 committed Jun 26, 2024
1 parent b436923 commit 280e796
Show file tree
Hide file tree
Showing 13 changed files with 533 additions and 3 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ FetchContent_MakeAvailable(ftxui)

# Note: globbing sources is considered bad practice as CMake's generators may not detect new files
# automatically. Keep that in mind when changing files, or explicitly mention them here.
file(GLOB_RECURSE headers CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
file(GLOB_RECURSE headers CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*")
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*")

# ---- Create library ----

Expand Down
1 change: 1 addition & 0 deletions include/background_task/background_task.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
enum class BackgroundTaskStatus { NotStarted = 0, Running = 1, Finished = 2, Cancelled = 3 };
44 changes: 44 additions & 0 deletions include/background_task/task_logger.hpp
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;
};
34 changes: 34 additions & 0 deletions include/dispatchers/reverse_dispatch.cpp
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;
};
28 changes: 28 additions & 0 deletions include/dispatchers/search_dispatch.cpp
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();
}
}
20 changes: 20 additions & 0 deletions include/dispatchers/search_dispatch.hpp
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;
};
173 changes: 173 additions & 0 deletions include/search_engine/search.cpp
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);
}
}
Loading

0 comments on commit 280e796

Please sign in to comment.