TEC PFE replica #2: Datas

With our UI in place, we're now diving into the most exciting part - the logic. This phase might be less visually stimulating since it's more theoretical, but it's intriguing to see how the logic comes together.

Implementing the logic

Understanding how we'll manage our data is crucial, especially considering we have multiple threads. Passing data around without proper security isn't an option.

The Store

Our solution is a store - a struct that holds the data accessible from all threads. We'll use a mutex for safe access, ensuring only one thread can manipulate the data at any given time. Data returned will always be a copy, ensuring no unintended modifications or nullptr manipulation.

Here's a snippet:

class Store {
    std::shared_mutex mutex;
    std::vector<Bus> buses;

public:
    std::vector<Bus> get_buses() {
        std::shared_lock lock(mutex);
        return buses;
    }

    void set_buses(std::vector<Bus> buses) {
        std::unique_lock lock(mutex);
        this->buses = buses;
    }
}

This design as the advantage of being simple and easy to understand. The only downside is that we need to copy the data when we want to read it. But it's not a big deal as we don't have a lot of data and we access it at very low frequency.

The data

We're setting up a side server to write data to /tmp/tec-gps.socket. This employs UDS for communication between processes. The client side will read and parse this data.

Pseudo-code :

It's not rocket science and it's pretty straight foward.

Static Data

For static data like bus stops and lines, we'll use a SQLite database. While a remote centralized database is usually better, it's not necessary for this project.

Strugle

Due to some issues with my WSL, I had to compile SQLite3 directly into the project.

Setup :

The 'ORM'

We'll use a simple wrapper around SQLite3 for database access. This wrapper is thread-safe, thanks to a mutex.

Example code for the Database class:

class Database {
    sqlite3 *db;
    std::mutex mutex;

public:
    Database(std::string path) {
        sqlite3_open(path.c_str(), &db);
    }

    ~Database() {
        sqlite3_close(db);
    }

    std::vector<std::vector<std::string>> execute(std::string query) {
        std::lock_guard lock(mutex);
        sqlite3_stmt *stmt;
        sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, NULL);

        std::vector<std::vector<std::string>> result;

        int rc = sqlite3_step(stmt);
        while (rc != SQLITE_DONE) {
            if (rc == SQLITE_ROW) {
                std::vector<std::string> row;
                int col_count = sqlite3_column_count(stmt);
                for (int i = 0; i < col_count; i++) {
                    row.push_back((char *) sqlite3_column_text(stmt, i));
                }
                result.push_back(row);
            }
            rc = sqlite3_step(stmt);
        }

        sqlite3_finalize(stmt);

        return result;
    }
};

This is a very simple but it's just to show the idea. We could have used a library like sqlite_orm but it's not necessary for this project.

Bus Display

A separate thread will manage the display rotation, reading data from the store and updating the display.

Picking a trip

Selecting a trip will retrieve relevant data from the DB and store it in the store, also updating the display.

Where are we

Github repo

Take me to the 3rd part!

Back to the index