diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 00000000..d1a2ad32 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,102 @@ +name: Build Check + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + windows-build: + runs-on: windows-latest + + strategy: + matrix: + build_type: [Debug, Release] + + env: + VCPKG_MAX_CONCURRENCY: 8 + VCPKG_ROOT: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\vcpkg" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Visual Studio Developer Command Prompt + uses: microsoft/setup-msbuild@v2 + with: + vs-version: '17.0' + msbuild-architecture: x64 + + - name: Cache vcpkg + uses: actions/cache@v4 + with: + path: | + ${{ github.workspace }}/build/vcpkg_installed + C:/Users/runneradmin/AppData/Local/vcpkg/archives + C:/Users/runneradmin/AppData/Local/vcpkg/registries + + key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}-${{ matrix.build_type }} + restore-keys: | + vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}- + vcpkg-${{ runner.os }}- + + - name: Configure CMake + run: | + cmake ` + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ` + -DVCPKG_TARGET_TRIPLET=x64-windows ` + -DVCPKG_USE_BINARY_PACKAGES=ON ` + -B ${{ github.workspace }}/build + + - name: Build Project + run: | + cmake --build ${{ github.workspace }}/build --config ${{ matrix.build_type }} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-build-${{ matrix.build_type }} + path: | + ${{ github.workspace }}/build/**/*.exe + if-no-files-found: error + + gcc-build: + runs-on: ubuntu-latest + container: ubuntu:jammy + strategy: + matrix: + build_type: [ Debug, Release ] + gcc_version: [ 13 ] + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + software-properties-common gpg-agent wget \ + build-essential cmake libwxgtk3.0-gtk3-dev libwxgtk3.0-gtk3-0v5 \ + libopencv-dev python3-opencv \ + pkg-config + add-apt-repository ppa:ubuntu-toolchain-r/test + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + g++-${{matrix.gcc_version}} gcc-${{matrix.gcc_version}} + + - name: Setup GCC + run: | + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{matrix.gcc_version}} 100 + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-${{matrix.gcc_version}} 100 + + - name: Build + run: | + cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} + cmake --build build --config ${{matrix.build_type}} + mkdir -p /github/workspace/artifacts + cp build/traffic_sim /github/workspace/artifacts/ + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: linux-gcc${{matrix.gcc_version}}-${{matrix.build_type}} + path: /github/workspace/artifacts/traffic_sim + if-no-files-found: error \ No newline at end of file diff --git a/.gitignore b/.gitignore index c129b08a..b1001a65 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -.github/** +.idea/** diff --git a/CMakeLists.txt b/CMakeLists.txt index 10271dbc..21eb049b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,36 @@ cmake_minimum_required(VERSION 3.11.3) +set(CMAKE_CXX_STANDARD 17) -# set(CMAKE_CXX_STANDARD 17) -project(traffic_simulation) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -pthread") +# Set output directories to the build +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) -find_package(OpenCV 4.1 REQUIRED) +if(WIN32) + set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -pthread") +endif() + +project(traffic_sim) + +if(DEFINED VCPKG_INSTALLED_DIR) + set(OpenCV_DIR "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share/opencv4") +else() + message(WARNING "VCPKG_INSTALLED_DIR not defined. OpenCV_DIR might need to be set manually.") +endif() +# Find opencv4 dependency +find_package(OpenCV 4.1 REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) link_directories(${OpenCV_LIBRARY_DIRS}) add_definitions(${OpenCV_DEFINITIONS}) -# Find all executables -file(GLOB project_SRCS src/*.cpp) #src/*.h +# Find sources +file(GLOB project_SRCS src/*.cpp) + +# Create an executable +add_executable(traffic_sim ${project_SRCS}) -# Add project executable -add_executable(traffic_simulation ${project_SRCS}) -target_link_libraries(traffic_simulation ${OpenCV_LIBRARIES}) +# Link libraries +target_link_libraries(traffic_sim ${OpenCV_LIBRARIES}) diff --git a/src/Intersection.cpp b/src/Intersection.cpp index b7cd6bc4..70951748 100644 --- a/src/Intersection.cpp +++ b/src/Intersection.cpp @@ -72,9 +72,9 @@ std::vector> Intersection::queryStreets(std::shared_ptr< // adds a new vehicle to the queue and returns once the vehicle is allowed to enter void Intersection::addVehicleToQueue(std::shared_ptr vehicle) { - std::unique_lock lck(_mtx); + std::unique_lock cout_lck(_cout_mtx); std::cout << "Intersection #" << _id << "::addVehicleToQueue: thread id = " << std::this_thread::get_id() << std::endl; - lck.unlock(); + cout_lck.unlock(); // add new vehicle to the end of the waiting line std::promise prmsVehicleAllowedToEnter; @@ -83,12 +83,14 @@ void Intersection::addVehicleToQueue(std::shared_ptr vehicle) // wait until the vehicle is allowed to enter ftrVehicleAllowedToEnter.wait(); - lck.lock(); + cout_lck.lock(); std::cout << "Intersection #" << _id << ": Vehicle #" << vehicle->getID() << " is granted entry." << std::endl; - - // FP.6b : use the methods TrafficLight::getCurrentPhase and TrafficLight::waitForGreen to block the execution until the traffic light turns green. + cout_lck.unlock(); - lck.unlock(); + // FP.6b : use the methods TrafficLight::getCurrentPhase and TrafficLight::waitForGreen to block the execution until the traffic light turns green. + _trafficLight.waitForGreen(); + cout_lck.lock(); + std::cout << "Intersection #" << _id << ": Vehicle #" << vehicle->getID() << " got a green light." << '\n'; } void Intersection::vehicleHasLeft(std::shared_ptr vehicle) @@ -109,9 +111,10 @@ void Intersection::setIsBlocked(bool isBlocked) void Intersection::simulate() // using threads + promises/futures + exceptions { // FP.6a : In Intersection.h, add a private member _trafficLight of type TrafficLight. At this position, start the simulation of _trafficLight. - + _trafficLight.simulate(); + // launch vehicle queue processing in a thread - threads.emplace_back(std::thread(&Intersection::processVehicleQueue, this)); + threads.emplace_back(&Intersection::processVehicleQueue, this); } void Intersection::processVehicleQueue() @@ -140,12 +143,5 @@ void Intersection::processVehicleQueue() bool Intersection::trafficLightIsGreen() { // please include this part once you have solved the final project tasks - /* - if (_trafficLight.getCurrentPhase() == TrafficLightPhase::green) - return true; - else - return false; - */ - - return true; // makes traffic light permanently green + return _trafficLight.getCurrentPhase() == TrafficLightPhase::green; } \ No newline at end of file diff --git a/src/Intersection.h b/src/Intersection.h index c3530bf4..68260599 100644 --- a/src/Intersection.h +++ b/src/Intersection.h @@ -1,11 +1,11 @@ #ifndef INTERSECTION_H #define INTERSECTION_H -#include +#include "TrafficLight.h" #include -#include #include -#include "TrafficObject.h" +#include +#include // forward declarations to avoid include cycle class Street; @@ -54,6 +54,8 @@ class Intersection : public TrafficObject std::vector> _streets; // list of all streets connected to this intersection WaitingVehicles _waitingVehicles; // list of all vehicles and their associated promises waiting to enter the intersection bool _isBlocked; // flag indicating wether the intersection is blocked by a vehicle + + TrafficLight _trafficLight; }; #endif diff --git a/src/Street.h b/src/Street.h index 55325320..0b96b87a 100644 --- a/src/Street.h +++ b/src/Street.h @@ -2,6 +2,7 @@ #define STREET_H #include "TrafficObject.h" +#include // forward declaration to avoid include cycle class Intersection; diff --git a/src/TrafficLight.cpp b/src/TrafficLight.cpp index 12dde562..51883b51 100644 --- a/src/TrafficLight.cpp +++ b/src/TrafficLight.cpp @@ -1,16 +1,22 @@ -#include -#include #include "TrafficLight.h" +using namespace std::chrono; +using namespace std::chrono_literals; + /* Implementation of class "MessageQueue" */ -/* template T MessageQueue::receive() { // FP.5a : The method receive should use std::unique_lock and _condition.wait() // to wait for and receive new messages and pull them from the queue using move semantics. - // The received object should then be returned by the receive function. + // The received object should then be returned by the receive function. + std::unique_lock queue_lck(_queue_mtx); + _queue_cv.wait(queue_lck, [this] { return !_queue.empty(); }); + T message = std::move(_queue.front()); + _queue.pop_front(); + + return message; } template @@ -18,41 +24,65 @@ void MessageQueue::send(T &&msg) { // FP.4a : The method send should use the mechanisms std::lock_guard // as well as _condition.notify_one() to add a new message to the queue and afterwards send a notification. + std::unique_lock queue_lck(_queue_mtx); + _queue.push_back(std::move(msg)); + _queue_cv.notify_one(); } -*/ /* Implementation of class "TrafficLight" */ -/* TrafficLight::TrafficLight() { + // the thread should not be running, no synchronization needed _currentPhase = TrafficLightPhase::red; } void TrafficLight::waitForGreen() { + // return if already green + if (getCurrentPhase() == TrafficLightPhase::green) { + return; + } + // FP.5b : add the implementation of the method waitForGreen, in which an infinite while-loop // runs and repeatedly calls the receive function on the message queue. // Once it receives TrafficLightPhase::green, the method returns. + while (_messages.receive() != TrafficLightPhase::green) { + std::this_thread::yield(); + } } TrafficLightPhase TrafficLight::getCurrentPhase() { + std::lock_guard lock(_currentPhase_mtx); // read is synchronized return _currentPhase; } void TrafficLight::simulate() { // FP.2b : Finally, the private method „cycleThroughPhases“ should be started in a thread when the public method „simulate“ is called. To do this, use the thread queue in the base class. + threads.emplace_back(&TrafficLight::cycleThroughPhases, this); +} + +void TrafficLight::toggleCurrentPhase() { + _currentPhase = _currentPhase == TrafficLightPhase::red ? TrafficLightPhase::green : TrafficLightPhase::red; + _messages.send(std::move(_currentPhase)); } // virtual function which is executed in a thread -void TrafficLight::cycleThroughPhases() -{ +void TrafficLight::cycleThroughPhases() { // FP.2a : Implement the function with an infinite loop that measures the time between two loop cycles // and toggles the current phase of the traffic light between red and green and sends an update method // to the message queue using move semantics. The cycle duration should be a random value between 4 and 6 seconds. - // Also, the while-loop should use std::this_thread::sleep_for to wait 1ms between two cycles. -} + // Also, the while-loop should use std::this_thread::sleep_for to wait 1ms between two cycles. + auto next(high_resolution_clock::now() + seconds(_dis(_gen))); + + for (time_point now = high_resolution_clock::now();;now = high_resolution_clock::now()) { + if (now > next) { + toggleCurrentPhase(); + next = now + seconds(_dis(_gen)); + } -*/ \ No newline at end of file + std::this_thread::sleep_for(milliseconds(1)); + } +} diff --git a/src/TrafficLight.h b/src/TrafficLight.h index 76c89dcc..914c8652 100644 --- a/src/TrafficLight.h +++ b/src/TrafficLight.h @@ -5,10 +5,15 @@ #include #include #include "TrafficObject.h" +#include "random" // forward declarations to avoid include cycle class Vehicle; +enum class TrafficLightPhase { + red, + green, +}; // FP.3 Define a class „MessageQueue“ which has the public methods send and receive. // Send should take an rvalue reference of type TrafficLightPhase whereas receive should return this type. @@ -19,9 +24,15 @@ template class MessageQueue { public: - -private: + void send(T&& pase); + T receive(); +private: + std::deque _queue; + + // _queue synchronisation members + std::condition_variable _queue_cv; + std::mutex _queue_mtx; }; // FP.1 : Define a class „TrafficLight“ which is a child class of TrafficObject. @@ -29,25 +40,38 @@ class MessageQueue // as well as „TrafficLightPhase getCurrentPhase()“, where TrafficLightPhase is an enum that // can be either „red“ or „green“. Also, add the private method „void cycleThroughPhases()“. // Furthermore, there shall be the private member _currentPhase which can take „red“ or „green“ as its value. - -class TrafficLight +class TrafficLight : TrafficObject { public: // constructor / desctructor - + TrafficLight(); + // getters / setters - + TrafficLightPhase getCurrentPhase(); + // typical behaviour methods - + void simulate() override; + void waitForGreen(); + private: // typical behaviour methods - + void cycleThroughPhases(); + void toggleCurrentPhase(); + + std::mt19937 _gen{std::random_device{}()}; + std::uniform_int_distribution _dis{4, 6}; + // FP.4b : create a private member of type MessageQueue for messages of type TrafficLightPhase // and use it within the infinite loop to push each new TrafficLightPhase into it by calling // send in conjunction with move semantics. + MessageQueue _messages; - std::condition_variable _condition; - std::mutex _mutex; + // _currentPhase synchronizatin members + std::condition_variable _currentPhase_cv; + std::mutex _currentPhase_mtx; + + // shared resource + TrafficLightPhase _currentPhase; }; #endif \ No newline at end of file diff --git a/src/TrafficObject.cpp b/src/TrafficObject.cpp index 600cf66a..ed3aa596 100644 --- a/src/TrafficObject.cpp +++ b/src/TrafficObject.cpp @@ -6,7 +6,7 @@ // init static variable int TrafficObject::_idCnt = 0; -std::mutex TrafficObject::_mtx; +std::mutex TrafficObject::_cout_mtx; void TrafficObject::setPosition(double x, double y) { diff --git a/src/TrafficObject.h b/src/TrafficObject.h index 5393b628..d067364f 100644 --- a/src/TrafficObject.h +++ b/src/TrafficObject.h @@ -34,7 +34,7 @@ class TrafficObject int _id; // every traffic object has its own unique id double _posX, _posY; // vehicle position in pixels std::vector threads; // holds all threads that have been launched within this object - static std::mutex _mtx; // mutex shared by all traffic objects for protecting cout + static std::mutex _cout_mtx; // mutex shared by all traffic objects for protecting cout private: static int _idCnt; // global variable for counting object ids diff --git a/src/Vehicle.cpp b/src/Vehicle.cpp index abe52d26..dd374c6a 100644 --- a/src/Vehicle.cpp +++ b/src/Vehicle.cpp @@ -32,9 +32,9 @@ void Vehicle::simulate() void Vehicle::drive() { // print id of the current thread - std::unique_lock lck(_mtx); + std::unique_lock _cout_lck(_cout_mtx); std::cout << "Vehicle #" << _id << "::drive: thread id = " << std::this_thread::get_id() << std::endl; - lck.unlock(); + _cout_lck.unlock(); // initalize variables bool hasEnteredIntersection = false; diff --git a/src/Vehicle.h b/src/Vehicle.h index f6624506..70e3fa2c 100644 --- a/src/Vehicle.h +++ b/src/Vehicle.h @@ -2,6 +2,7 @@ #define VEHICLE_H #include "TrafficObject.h" +#include // forward declarations to avoid include cycle class Street; diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..bf730587 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,13 @@ +{ + "name": "cpp-traffic-sim", + "version": "0.0.0", + "builtin-baseline": "2960d7d80e8d09c84ae8abf15c12196c2ca7d39a", + "dependencies": [ + { + "name": "opencv4", + "default-features": false, + "features": ["jpeg", "png"], + "version>=": "4.8.0#22" + } + ] +} \ No newline at end of file