﻿/*
 * ZPR - zadanie dodatkowe
 * autor: Marcin Sadowski
 *
 * Kompilator MinGW 11.0
 *
 * Nowe udogodnienia dostępne w standardzie C++20 - Biblioteka <barrier>
 * C++20 wprowadził nową bibliotekę <barrier>, która ułatwia synhcoronizację wielu wątków pracujących nad wspólnym zadaniem.
 * Podobną fnkcjonalność ma std::condition_variable, w którym wątki muszą czekać na spełnienie jakiegoś warunku, 
 * natomiast std::barrier określa punkt, który musi osiągnąć zadana liczba punktów synchronizacji, zanim będą mogły one kontynuować działanie.
 * Poniżej znajduje się przykładowa aplikacja domonstrująca funkcje tej biblioteki.
 */

#include <barrier>
#include <condition_variable>
#include <chrono>
#include <iostream>
#include <mutex>
#include <string>
#include <syncstream>
#include <thread>
#include <vector>

using namespace std::chrono;


int main()
{
    const auto cats = { "Birman Cat", "Bombay Cat", "Bengal Cat", "Munchkin Cat", "Ragamuffin Cat" };

    // Funkcja wkonująca się po zniesieniu bariery
    auto on_completion = []() noexcept {
        std::osyncstream(std::cout) << "... all cats gathered for this phase!\n";
        };

    // W konstruktorze bariery podaje się liczbę wątków, na które będzie czekała bariera oraz dodatkowo można podać
    // funkcję, która wykona się po zniesieniu bariery

    // Bariera bez funkcji końcowej
    std::barrier sync_point1(std::ssize(cats));

    // Bariera z funkcją końcową
    std::barrier sync_point2(std::ssize(cats), on_completion);

    // Bariera z funkcją końcową dla arrive_and_drop
    std::barrier sync_point_drop(std::ssize(cats), on_completion);

    std::vector<std::jthread> threads;
    threads.reserve(std::size(cats));

    /*
    * Funkcja do wywołania, żeby porównać wynki bez bariery
    */
    auto play = [&](std::string name) {
        std::osyncstream(std::cout) << "  " << name << ": miau\n";

        std::osyncstream(std::cout) << "  " << name << " sleep\n";
        };

    /*
     * Funkcja arrive_and_wait:
     * - Wątek wywołujący tę funkcję zgłasza, że dotarł do bariery.
     * - Licznik w barierze (określający oczekiwaną liczbę uczestników) jest zmniejszany o 1.
     * - Wątek czeka, aż wszystkie pozostałe wątki dotrą do bariery, po czym bariera przechodzi do kolejnej fazy.
     * - Wszyscy uczestnicy mogą kontynuować swoje działanie po zakończeniu obecnej fazy.
     */
    auto play1 = [&](std::string name) {
        std::osyncstream(std::cout) << "  " << name << ": miau\n";
        sync_point1.arrive_and_wait();

        std::osyncstream(std::cout) << "  " << name << " sleep\n";
        sync_point1.arrive_and_wait();
        };

    /*
     * Funkcja arrive_and_wait (z funkcją końcową):
     * - Działa jak arrive_and_wait, ale po osiągnięciu bariery dodatkowo wywoływana jest
     *   funkcja końcowa (przekazana w konstruktorze bariery).
     * - Funkcja końcowa jest wykonywana dokładnie raz dla każdej fazy, po czym wątki są zwalniane.
     */
    auto play2 = [&](std::string name) {
        std::osyncstream(std::cout) << "  " << name << ": miau\n";
        sync_point2.arrive_and_wait();

        std::osyncstream(std::cout) << "  " << name << " sleep\n";
        sync_point2.arrive_and_wait();
        };

    /*
     * Funkcja arrive_and_drop:
     * - Wątek wywołujący tę funkcję zgłasza, że dotarł do bariery, ale NIE BIERZE udziału
     *   w kolejnych fazach synchronizacji.
     * - Liczba oczekiwanych uczestników w kolejnych fazach bariery jest zmniejszana o 1.
     * - Wątek jest natychmiast zwalniany po osiągnięciu bariery i nie oczekuje na pozostałe wątki.
     * - Funkcja ta jest przydatna, gdy niektóre wątki kończą swoje działanie wcześniej.
     */
    auto play_drop = [&](std::string name, bool drop) {
        std::osyncstream(std::cout) << "  " << name << ": miau\n";
        if (drop) {
            std::osyncstream(std::cout) << "  " << name << " leaves barrier\n";
            sync_point_drop.arrive_and_drop(); // Opuszcza barierę
        }
        else {
            sync_point_drop.arrive_and_wait(); // Czeka na pozostałych
        }

        if (!drop) {
            std::osyncstream(std::cout) << "  " << name << " sleep\n";
            sync_point_drop.arrive_and_wait();
        }
        };

    // Przykład bez bariery
    std::cout << "\nNormal threads.\n";
    for (auto const& cat : cats) {
        threads.emplace_back(play, cat);
    }
    for (auto& t : threads) t.join();
    threads.clear();

    // Testowanie arrive_and_wait
    std::cout << "\nWait without end function.\n";
    for (auto const& cat : cats) {
        threads.emplace_back(play1, cat);
    }
    for (auto& t : threads) t.join();
    threads.clear();

    // Testowanie arrive_and_wait z funkcją końcową
    std::cout << "\nWait with end function.\n";
    for (auto const& cat : cats) {
        threads.emplace_back(play2, cat);
    }
    for (auto& t : threads) t.join();
    threads.clear();

    // Testowanie arrive_and_drop
    std::cout << "\nDrop with end function.\n";
    int i = 0;
    for (auto const& cat : cats) {
        bool drop = (i++ % 2 == 0); // Co drugi wątek opuszcza barierę
        threads.emplace_back(play_drop, cat, drop);
    }
    for (auto& t : threads) t.join();


}


/*
Materiały:
- https://en.cppreference.com/w/cpp/thread/barrier
- https://www.geeksforgeeks.org/std-barrier-in-cpp-20/
*/