diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5cca158..ac4327d 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:25.10 LABEL maintainer="Felix Renzikowski " @@ -16,7 +16,10 @@ RUN apt-get update && apt-get install -y \ qtbase5-dev \ qt5-qmake \ libgtest-dev \ - libopencv-dev + libopencv-dev \ + gprbuild \ + ninja-build \ + gnat RUN mkdir /usr/src/gtest/build RUN cd /usr/src/gtest/build && cmake .. && make && cd lib && cp *.a /usr/lib diff --git a/.github/workflows/build_test_only.yml b/.github/workflows/build_test_only.yml index 625233a..fdcedff 100644 --- a/.github/workflows/build_test_only.yml +++ b/.github/workflows/build_test_only.yml @@ -14,7 +14,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y qtcreator qtbase5-dev qt5-qmake cmake ninja-build make libgtest-dev libopencv-dev + sudo apt-get install -y qtcreator qtbase5-dev qt5-qmake cmake ninja-build make libgtest-dev libopencv-dev gprbuild gnat sudo mkdir /usr/src/gtest/build sudo cd /usr/src/gtest/build && sudo cmake .. && sudo make && sudo cd lib && sudo cp *.a /usr/lib sudo rm -r /usr/src/gtest/build diff --git a/.github/workflows/full.yml b/.github/workflows/full.yml index 6715e8a..98ba86e 100644 --- a/.github/workflows/full.yml +++ b/.github/workflows/full.yml @@ -15,7 +15,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y qtcreator qtbase5-dev texlive-full qt5-qmake doxygen build-essential - sudo apt-get install -y cmake graphviz ninja-build make libgtest-dev libopencv-dev + sudo apt-get install -y cmake graphviz ninja-build make libgtest-dev libopencv-dev gprbuild gnat sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build && sudo cmake .. && sudo make && cd lib && sudo cp *.a /usr/lib sudo rm -r /usr/src/gtest/build @@ -23,8 +23,8 @@ jobs: - name: Configure and Build CMake run: | - cmake -GNinja -S ${{github.workspace}} -B ${{github.workspace}}/build - cmake --build ${{github.workspace}}/build + cmake -GNinja -S ${{github.workspace}} -B ${{github.workspace}}/build -DWITH_DOCS=ON -DWITH_PAPER=ON + cmake --build ${{github.workspace}}/build - name: Test working-directory: ${{github.workspace}}/build diff --git a/.gitignore b/.gitignore index b88509b..db14d7e 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,9 @@ # Custom cmake-build-debug/ +build/ +Core/Pathfinder/ada_backend/lib +Core/Pathfinder/ada_backend/obj .idea/ Paper/out *.log diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c0cfb78 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Pathfinding (GUI)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/Pathfinding_Bench_Program", + "args": ["--gui"], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb", + "setupCommands": [ + { "description": "Enable pretty-printing", "text": "-enable-pretty-printing", "ignoreFailures": true } + ], + "preLaunchTask": "CMake: build" + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 93b344f..cf9840f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,9 @@ project( LANGUAGES C CXX ASM ) +option(WITH_DOCS "Build documentation" OFF) +option(WITH_PAPER "Build paper output" OFF) + enable_testing() set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -35,33 +38,37 @@ add_subdirectory(Core/GUI) add_executable(Pathfinding_Bench_Program Core/main.cpp) target_link_libraries(Pathfinding_Bench_Program Application GridGenerator Pathfinder GUI RunInterface GUI_widgets) -# doxygen documentation -find_package(Doxygen) - -if (DOXYGEN_FOUND) - set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) - set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - - configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) - add_custom_command(TARGET Pathfinding_Bench_Program POST_BUILD - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} > /dev/null 2>&1 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen" - ) - - find_program(MAKE_EXECUTABLE make) - if (MAKE_EXECUTABLE) - add_custom_command(TARGET Pathfinding_Bench_Program POST_BUILD - COMMAND ${MAKE_EXECUTABLE} > /dev/null 2>&1 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs/latex - COMMENT "Generating PDF documentation" - ) - else (MAKE_EXECUTABLE) - message("Could not find 'make' command for generating PDF documentation.") - endif () -else (DOXYGEN_FOUND) - message("Doxygen needs to be installed to generate the doxygen documentation") -endif (DOXYGEN_FOUND) +if(WITH_DOCS) + # doxygen documentation + find_package(Doxygen) + + if (DOXYGEN_FOUND) + set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) + set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) + add_custom_command(TARGET Pathfinding_Bench_Program POST_BUILD + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} > /dev/null 2>&1 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + ) + else (DOXYGEN_FOUND) + message("Doxygen needs to be installed to generate the doxygen documentation") + endif (DOXYGEN_FOUND) +endif(WITH_DOCS) + +if(WITH_PAPER) + find_program(MAKE_EXECUTABLE make) + if (MAKE_EXECUTABLE) + add_custom_command(TARGET Pathfinding_Bench_Program POST_BUILD + COMMAND ${MAKE_EXECUTABLE} > /dev/null 2>&1 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs/latex + COMMENT "Generating PDF documentation" + ) + else (MAKE_EXECUTABLE) + message("Could not find 'make' command for generating PDF documentation.") + endif () +endif(WITH_PAPER) # set up testing find_package(GTest) diff --git a/Core/Pathfinder/CMakeLists.txt b/Core/Pathfinder/CMakeLists.txt index af72fc3..96bfc07 100644 --- a/Core/Pathfinder/CMakeLists.txt +++ b/Core/Pathfinder/CMakeLists.txt @@ -1,7 +1,41 @@ -# Glob for the inc and src directories in each module -file(GLOB SOURCE_FILES "src/*.cpp") +find_program(GPRBUILD_EXECUTABLE NAMES gprbuild gnatmake) +if(NOT GPRBUILD_EXECUTABLE) + message(FATAL_ERROR "gprbuild not found; please install GNAT or ensure gprbuild is on PATH") +endif() + +include(ExternalProject) + +set(ADA_OUTPUT_LIB "${CMAKE_CURRENT_SOURCE_DIR}/ada_backend/lib/libpathfinding_ada.so") +set(ADA_PROJECT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/ada_backend/libpathfinding_ada.gpr") + +add_custom_command( + OUTPUT "${ADA_OUTPUT_LIB}" + COMMAND gprbuild -p -P "${ADA_PROJECT_FILE}" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + COMMENT "Building Ada library via gprbuild" + VERBATIM +) + +add_custom_target(Pathfinder_Ada_Proj ALL + DEPENDS "${ADA_OUTPUT_LIB}" +) + +add_library(Pathfinder_C_ABI STATIC ${CMAKE_CURRENT_SOURCE_DIR}/c_abi/src/pathfinding_c_inf.cpp) +target_include_directories(Pathfinder_C_ABI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/c_abi/inc) -# Add each module as a library and set its dependencies +add_library(Pathfinder_Ada SHARED IMPORTED GLOBAL) +set_target_properties(Pathfinder_Ada PROPERTIES + IMPORTED_LOCATION "${ADA_OUTPUT_LIB}" +) + +add_dependencies(Pathfinder_Ada Pathfinder_Ada_Proj Pathfinder_C_ABI) + +target_link_libraries(Pathfinder_Ada INTERFACE Pathfinder_C_ABI) +add_dependencies(Pathfinder_Ada Pathfinder_Ada_Proj Pathfinder_C_ABI) +target_link_libraries(Pathfinder_C_ABI PRIVATE GridGenerator) + +file(GLOB SOURCE_FILES "src/*.cpp") add_library(Pathfinder ${SOURCE_FILES}) target_include_directories(Pathfinder PUBLIC inc) -target_link_libraries(Pathfinder GridGenerator) +target_link_libraries(Pathfinder GridGenerator Pathfinder_Ada Pathfinder_C_ABI) + diff --git a/Core/Pathfinder/ada_backend/libpathfinding_ada.gpr b/Core/Pathfinder/ada_backend/libpathfinding_ada.gpr new file mode 100644 index 0000000..a3df94f --- /dev/null +++ b/Core/Pathfinder/ada_backend/libpathfinding_ada.gpr @@ -0,0 +1,10 @@ +library project libpathfinding_ada is + for Source_Dirs use ("src"); + for Library_Dir use "lib"; + for Object_Dir use "obj"; + for Library_Name use "pathfinding_ada"; + for Library_Kind use "dynamic"; + package Compiler is + for Default_Switches ("Ada") use ("-g", "-O0"); + end Compiler; +end libpathfinding_ada; \ No newline at end of file diff --git a/Core/Pathfinder/ada_backend/src/a_star.adb b/Core/Pathfinder/ada_backend/src/a_star.adb new file mode 100644 index 0000000..cb64015 --- /dev/null +++ b/Core/Pathfinder/ada_backend/src/a_star.adb @@ -0,0 +1,83 @@ +with Pathfinder_C_Inf; use Pathfinder_C_Inf; +with Pathfinder_Common; use Pathfinder_Common; +with Interfaces.C; use Interfaces.C; +with Interfaces; use Interfaces; +with Ada.Text_IO; use Ada.Text_IO; + +package body a_star is + procedure nextStep + (grid : Grid_Handle; + current_coord_x : Pathfinder_C_Inf.Grid_Idx; + current_coord_y : Pathfinder_C_Inf.Grid_Idx; + end_coord_x : Pathfinder_C_Inf.Grid_Idx; + end_coord_y : Pathfinder_C_Inf.Grid_Idx; + queue : Cell_Queue_Handle) + with SPARK_Mode => On + is + current_coord : Pathfinder_C_Inf.Grid_Coord := + Pathfinder_C_Inf.Grid_Coord'(current_coord_x, current_coord_y); + end_coord : Pathfinder_C_Inf.Grid_Coord := + Pathfinder_C_Inf.Grid_Coord'(end_coord_x, end_coord_y); + neighbors : Neighbors_Array := + get_neighbors_from_coord (grid, current_coord.x, current_coord.y); + current_costs : Cell_Cost := + get_cell_cost (grid, current_coord.x, current_coord.y); + current_g : Safe_Cost := current_costs.gCost; + neighbor_coord : Grid_Coord; + neighbor_costs : Cell_Cost; + neighbor_state : Cell_State; + neighbor_g_from_current : C_Double; + begin + for I in GridCoordArray'Range loop + neighbor_coord := neighbors.coords (I); + + -- skip empty sentinel entries (wrapper pads remaining slots with U32_MAX) + exit when neighbor_coord.x = -1 and neighbor_coord.y = -1; + neighbor_g_from_current := + current_g + Octile_Distance (neighbor_coord, current_coord); + + neighbor_state := + get_cell_state (grid, neighbor_coord.x, neighbor_coord.y); + + if neighbor_state = CELL_OPEN then + set_cell_costs + (grid, + neighbor_coord.x, + neighbor_coord.y, + neighbor_g_from_current, + Absolute_Distance (neighbor_coord, end_coord)); + set_parent_cell_pointer + (grid, + neighbor_coord.x, + neighbor_coord.y, + current_coord.x, + current_coord.y); + mark_cell_visited (grid, neighbor_coord.x, neighbor_coord.y); + push_to_cell_queue (queue, neighbor_coord.x, neighbor_coord.y); + increment_visited_cell_count (grid); + + elsif neighbor_state = CELL_VISITED then + neighbor_costs := + get_cell_cost (grid, neighbor_coord.x, neighbor_coord.y); + if neighbor_costs.gCost > neighbor_g_from_current then + -- preserve previous h-cost + set_cell_costs + (grid, + neighbor_coord.x, + neighbor_coord.y, + neighbor_g_from_current, + neighbor_costs.hCost); + set_parent_cell_pointer + (grid, + neighbor_coord.x, + neighbor_coord.y, + current_coord.x, + current_coord.y); + push_to_cell_queue (queue, neighbor_coord.x, neighbor_coord.y); + increment_visited_cell_count (grid); + end if; + end if; + end loop; + end nextStep; + +end a_star; diff --git a/Core/Pathfinder/ada_backend/src/a_star.ads b/Core/Pathfinder/ada_backend/src/a_star.ads new file mode 100644 index 0000000..5d63b19 --- /dev/null +++ b/Core/Pathfinder/ada_backend/src/a_star.ads @@ -0,0 +1,18 @@ +with Pathfinder_C_Inf; use Pathfinder_C_Inf; + +package a_star is + procedure nextStep + (grid : Grid_Handle; + current_coord_x : Pathfinder_C_Inf.Grid_Idx; + current_coord_y : Pathfinder_C_Inf.Grid_Idx; + end_coord_x : Pathfinder_C_Inf.Grid_Idx; + end_coord_y : Pathfinder_C_Inf.Grid_Idx; + queue : Cell_Queue_Handle) + with + Convention => C, + SPARK_Mode => On, + Export => True, + External_Name => "ada_astar_next_step", + Global => null; + +end a_star; diff --git a/Core/Pathfinder/ada_backend/src/dijkstra.adb b/Core/Pathfinder/ada_backend/src/dijkstra.adb new file mode 100644 index 0000000..4951666 --- /dev/null +++ b/Core/Pathfinder/ada_backend/src/dijkstra.adb @@ -0,0 +1,79 @@ +with Pathfinder_C_Inf; use Pathfinder_C_Inf; +with Pathfinder_Common; use Pathfinder_Common; +with Interfaces.C; use Interfaces.C; +with Interfaces; use Interfaces; +with Ada.Text_IO; use Ada.Text_IO; + +package body dijkstra is + procedure nextStep + (grid : Grid_Handle; + current_coord_x : Pathfinder_C_Inf.Grid_Idx; + current_coord_y : Pathfinder_C_Inf.Grid_Idx; + queue : Cell_Queue_Handle) + with SPARK_Mode => On + is + current_coord : Pathfinder_C_Inf.Grid_Coord := + (x => current_coord_x, y => current_coord_y); + neighbors : constant Neighbors_Array := + get_neighbors_from_coord (grid, current_coord.x, current_coord.y); + current_costs : constant Cell_Cost := + get_cell_cost (grid, current_coord.x, current_coord.y); + current_g : constant Pathfinder_C_Inf.Safe_Cost := + current_costs.gCost; + neighbor_coord : Grid_Coord; + neighbor_costs : Cell_Cost; + neighbor_state : Cell_State; + neighbor_g_from_current : Pathfinder_C_Inf.C_Double; + begin + for I in GridCoordArray'Range loop + neighbor_coord := neighbors.coords (I); + + -- skip empty sentinel entries (wrapper pads remaining slots with U32_MAX) + exit when neighbor_coord.x = -1 and neighbor_coord.y = -1; + neighbor_g_from_current := + current_g + Octile_Distance (neighbor_coord, current_coord); + + neighbor_state := + get_cell_state (grid, neighbor_coord.x, neighbor_coord.y); + + if neighbor_state = CELL_OPEN then + set_cell_costs + (grid, + neighbor_coord.x, + neighbor_coord.y, + neighbor_g_from_current, + 0.0); + set_parent_cell_pointer + (grid, + neighbor_coord.x, + neighbor_coord.y, + current_coord.x, + current_coord.y); + mark_cell_visited (grid, neighbor_coord.x, neighbor_coord.y); + push_to_cell_queue (queue, neighbor_coord.x, neighbor_coord.y); + increment_visited_cell_count (grid); + + elsif neighbor_state = CELL_VISITED then + neighbor_costs := + get_cell_cost (grid, neighbor_coord.x, neighbor_coord.y); + if neighbor_costs.gCost > neighbor_g_from_current then + set_cell_costs + (grid, + neighbor_coord.x, + neighbor_coord.y, + neighbor_g_from_current, + 0.0); + set_parent_cell_pointer + (grid, + neighbor_coord.x, + neighbor_coord.y, + current_coord.x, + current_coord.y); + push_to_cell_queue (queue, neighbor_coord.x, neighbor_coord.y); + increment_visited_cell_count (grid); + end if; + end if; + end loop; + end nextStep; + +end dijkstra; diff --git a/Core/Pathfinder/ada_backend/src/dijkstra.ads b/Core/Pathfinder/ada_backend/src/dijkstra.ads new file mode 100644 index 0000000..68ad950 --- /dev/null +++ b/Core/Pathfinder/ada_backend/src/dijkstra.ads @@ -0,0 +1,17 @@ +with Pathfinder_C_Inf; use Pathfinder_C_Inf; +with Interfaces.C; use type Interfaces.C.double; + +package dijkstra is + procedure nextStep + (grid : Grid_Handle; + current_coord_x : Pathfinder_C_Inf.Grid_Idx; + current_coord_y : Pathfinder_C_Inf.Grid_Idx; + queue : Cell_Queue_Handle) + with + Convention => C, + SPARK_Mode => On, + Export => True, + External_Name => "ada_dijkstra_next_step", + Global => null; + +end dijkstra; diff --git a/Core/Pathfinder/ada_backend/src/pathfinder_c_inf.ads b/Core/Pathfinder/ada_backend/src/pathfinder_c_inf.ads new file mode 100644 index 0000000..bb755a3 --- /dev/null +++ b/Core/Pathfinder/ada_backend/src/pathfinder_c_inf.ads @@ -0,0 +1,86 @@ +with Interfaces.C; use type Interfaces.C.double; +with Interfaces.C; use type Interfaces.C.long; +with System; + +package Pathfinder_C_Inf is + subtype Grid_Handle is System.Address; + subtype Cell_Queue_Handle is System.Address; + + -- Basic integer/byte aliases + subtype C_Long is Interfaces.C.long; + subtype C_Double is Interfaces.C.double; + -- Grid indices are implemented in the backend as uint32_t and not long + subtype Grid_Idx is C_Long range 0 .. 2**32-1; + subtype Grid_Idx_Dev is C_Long range -2**32-1 .. 2**32-1; + subtype Safe_Cost is C_Double range 0.0 .. 6.1E9; + + -- C-types + type Grid_Coord is record + x : Grid_Idx; + y : Grid_Idx; + end record + with Convention => C; + + type GridCoordArray is array (0 .. 7) of Grid_Coord with Convention => C; + + type Neighbors_Array is record + coords : GridCoordArray; + end record + with Convention => C; + + type Cell_Cost is record + gCost : Safe_Cost; + hCost : Safe_Cost; + end record + with Convention => C; + + -- C-style state Enum + type Cell_State is + (CELL_OBSTACLE, CELL_OPEN, CELL_VISITED, CELL_CLOSED, CELL_PATH) + with Convention => C; + + -- Imported functions + function get_neighbors_from_coord + (grid : Grid_Handle; coordX : Grid_Idx; coordY : Grid_Idx) + return Neighbors_Array + with Import => True, Convention => C, Global => null; + + function get_cell_cost + (grid : Grid_Handle; coordX : Grid_Idx; coordY : Grid_Idx) return Cell_Cost + with + Import => True, + Convention => C, + Global => null; + + function get_cell_state + (grid : Grid_Handle; coordX : Grid_Idx; coordY : Grid_Idx) return Cell_State + with Import => True, Convention => C, Global => null; + + procedure increment_visited_cell_count (grid : Grid_Handle) + with Import => True, Convention => C, Global => null, Always_Terminates; + + procedure mark_cell_visited + (grid : Grid_Handle; coordX : Grid_Idx; coordY : Grid_Idx) + with Import => True, Convention => C, Global => null, Always_Terminates; + + procedure set_cell_costs + (grid : Grid_Handle; + coordX : Grid_Idx; + coordY : Grid_Idx; + gCost : C_Double; + hCost : C_Double) + with Import => True, Convention => C, Global => null, Always_Terminates; + + procedure set_parent_cell_pointer + (grid : Grid_Handle; + coordX : Grid_Idx; + coordY : Grid_Idx; + parentCoordX : Grid_Idx; + parentCoordY : Grid_Idx) + with Import => True, Convention => C, Global => null, Always_Terminates; + + procedure push_to_cell_queue + (queue : Cell_Queue_Handle; coordX : Grid_Idx; coordY : Grid_Idx) + with Import => True, Convention => C, Global => null, Always_Terminates; + +end Pathfinder_C_Inf; diff --git a/Core/Pathfinder/ada_backend/src/pathfinder_common.adb b/Core/Pathfinder/ada_backend/src/pathfinder_common.adb new file mode 100644 index 0000000..a497cdd --- /dev/null +++ b/Core/Pathfinder/ada_backend/src/pathfinder_common.adb @@ -0,0 +1,55 @@ +with Ada.Numerics.Long_Elementary_Functions; +use Ada.Numerics.Long_Elementary_Functions; +with Interfaces.C; use Interfaces.C; + +package body Pathfinder_Common is + + function Octile_Distance (A, B : Grid_Coord) return Safe_Cost + with SPARK_Mode => On + is + DX, DY : Grid_Idx_Dev; + Val : Long_Float; + begin + DX := Grid_Idx_Dev(A.x - B.x); + DY := Grid_Idx_Dev(A.y - B.y); + if DX < 0 then + DX := -DX; + end if; + if DY < 0 then + DY := -DY; + end if; + + -- octile: min*sqrt(2) + abs(dx-dy) + if DX > DY then + Val := Long_Float (DY) * Sqrt2 + Long_Float (DX - DY); + else + Val := Long_Float (DX) * Sqrt2 + Long_Float (DY - DX); + end if; + + -- clamp to Safe_Cost'Last + if Val > Long_Float (Safe_Cost'Last) then + Val := Long_Float (Safe_Cost'Last); + end if; + return Safe_Cost (Val); + end Octile_Distance; + + function Absolute_Distance (A, B : Grid_Coord) return Safe_Cost + with SPARK_Mode => On + is + DX, DY : Long_Float; + Val : Long_Float; + begin + DX := Long_Float (A.x) - Long_Float (B.x); + DY := Long_Float (A.y) - Long_Float (B.y); + + Val := Sqrt (Long_Float (DX * DX + DY * DY)); + + -- clamp to Safe_Cost'Last + if Val > Long_Float (Safe_Cost'Last) then + Val := Long_Float (Safe_Cost'Last); + end if; + + return Safe_Cost (Val); + end Absolute_Distance; + +end Pathfinder_Common; diff --git a/Core/Pathfinder/ada_backend/src/pathfinder_common.ads b/Core/Pathfinder/ada_backend/src/pathfinder_common.ads new file mode 100644 index 0000000..8f40c42 --- /dev/null +++ b/Core/Pathfinder/ada_backend/src/pathfinder_common.ads @@ -0,0 +1,15 @@ +with Pathfinder_C_Inf; use Pathfinder_C_Inf; +with Interfaces.C; +use type Interfaces.C.double; + +package Pathfinder_Common is + Sqrt2 : constant Long_Float := 1.4142135623730951; + + function Octile_Distance (A, B : Grid_Coord) return Safe_Cost + with + SPARK_Mode => On; + + function Absolute_Distance (A, B : Grid_Coord) return Safe_Cost + with + SPARK_Mode => On; +end Pathfinder_Common; diff --git a/Core/Pathfinder/c_abi/inc/pathfinding_c_inf.h b/Core/Pathfinder/c_abi/inc/pathfinding_c_inf.h new file mode 100644 index 0000000..e67feb8 --- /dev/null +++ b/Core/Pathfinder/c_abi/inc/pathfinding_c_inf.h @@ -0,0 +1,52 @@ +#pragma once + +#include "grid.h" +#include + +extern "C" { + using grid_handle_t = void*; + using cell_queue_handle_t = void*; + + typedef struct { + long x; + long y; + } grid_coordinate_struct_t; + + typedef struct { + grid_coordinate_struct_t coords[8]; + } neigbors_array_t; + + typedef struct { + double gCost; + double hCost; + } cost_struct_t; + + enum Cell_State { + CELL_OBSTACLE = 0, /**< Represents cells that are obstacles. */ + CELL_OPEN = 1, /**< Represents open cells. */ + CELL_VISITED = 2, /**< Represents cells that have been visited. */ + CELL_CLOSED = 3, /**< Represents cells that have been closed. */ + CELL_PATH = 4 /**< Represents cells that are part of the path. */ + }; + + typedef void (*push_fn_t)(void *queue_handle, long x, long y); + + void register_queue_push(push_fn_t f); + + void push_to_cell_queue(void *queue_handle, long x, long y); + + neigbors_array_t get_neighbors_from_coord(grid_handle_t grid_handle, long x, long y); + + cost_struct_t get_cell_cost(grid_handle_t grid_handle, long x, long y); + + Cell_State get_cell_state(grid_handle_t grid_handle, long x, long y); + + void increment_visited_cell_count(grid_handle_t grid_handle); + + void mark_cell_visited(grid_handle_t grid_handle, long x, long y); + + void set_cell_costs(grid_handle_t grid_handle, long x, long y, double gCost, double hCost); + + void set_parent_cell_pointer(grid_handle_t grid_handle, long x, long y, + long parentX, long parentY); +} \ No newline at end of file diff --git a/Core/Pathfinder/c_abi/src/pathfinding_c_inf.cpp b/Core/Pathfinder/c_abi/src/pathfinding_c_inf.cpp new file mode 100644 index 0000000..71a1565 --- /dev/null +++ b/Core/Pathfinder/c_abi/src/pathfinding_c_inf.cpp @@ -0,0 +1,92 @@ +#include "pathfinding_c_inf.h" + +#include + +static std::atomic registered_push{nullptr}; + +extern "C" void register_queue_push(push_fn_t f) +{ + registered_push.store(f, std::memory_order_release); +} + +extern "C" void push_to_cell_queue(void *queue_handle, long x, long y) +{ + auto f = registered_push.load(std::memory_order_acquire); + if (f) + { + f(queue_handle, x, y); + } +} + +neigbors_array_t get_neighbors_from_coord(grid_handle_t grid_handle, long x, long y) +{ + auto &grid = *reinterpret_cast(grid_handle); + GridGenerator::GridCoordinate gc{x, y}; + auto neighbors = grid.getNeighborsCoordinates(gc); + neigbors_array_t result; + size_t i = 0; + for (const auto &neighbor : neighbors) + { + result.coords[i].x = neighbor.x; + result.coords[i].y = neighbor.y; + i++; + } + for (; i < 8; i++) + { + result.coords[i].x = -1; + result.coords[i].y = -1; + } + return result; +} + +cost_struct_t get_cell_cost(grid_handle_t grid_handle, long x, long y) +{ + auto &grid = *reinterpret_cast(grid_handle); + GridGenerator::GridCoordinate gc{x, y}; + auto &cell = grid(gc); + cost_struct_t result; + result.gCost = cell.getCost().gCost; + result.hCost = cell.getCost().hCost; + return result; +} + +Cell_State get_cell_state(grid_handle_t grid_handle, long x, long y) +{ + auto &grid = *reinterpret_cast(grid_handle); + GridGenerator::GridCoordinate gc{x, y}; + auto &cell = grid(gc); + return static_cast(cell.getState()); +} + +void increment_visited_cell_count(grid_handle_t grid_handle) +{ + auto &grid = *reinterpret_cast(grid_handle); + grid.incrementVisitedCellCount(); +} + +void mark_cell_visited(grid_handle_t grid_handle, long x, long y) +{ + auto &grid = *reinterpret_cast(grid_handle); + GridGenerator::GridCoordinate gc{x, y}; + auto &cell = grid(gc); + cell.markVisited(); +} + +void set_cell_costs(grid_handle_t grid_handle, long x, long y, double gCost, double hCost) +{ + auto &grid = *reinterpret_cast(grid_handle); + GridGenerator::GridCoordinate gc{x, y}; + auto &cell = grid(gc); + cell.setGCost(gCost); + cell.setHCost(hCost); +} + +void set_parent_cell_pointer(grid_handle_t grid_handle, long x, long y, + long parentX, long parentY) +{ + auto &grid = *reinterpret_cast(grid_handle); + GridGenerator::GridCoordinate gc{x, y}; + GridGenerator::GridCoordinate parent_gc{parentX, parentY}; + auto &cell = grid(gc); + cell.setParentCellPointer(&grid(parent_gc)); +} \ No newline at end of file diff --git a/Core/Pathfinder/inc/pathfinding.h b/Core/Pathfinder/inc/pathfinding.h index b026b82..edd3a68 100644 --- a/Core/Pathfinder/inc/pathfinding.h +++ b/Core/Pathfinder/inc/pathfinding.h @@ -28,7 +28,9 @@ namespace Pathfinder { enum class PathfinderStrategy { PATHFINDER_DIJKSTRA = 1, /**< Dijkstra's algorithm pathfinding. */ PATHFINDER_A_STAR = 2, /**< A* algorithm pathfinding. */ - PATHFINDER_JUMP_POINT_SEARCH = 3 /**< Jump-Point-Search algorithm pathfinding. */ + PATHFINDER_JUMP_POINT_SEARCH = 3, /**< Jump Point Search pathfinding. */ + PATHFINDER_DIJKSTRA_ADA = 4, /**< Dijkstra's algorithm pathfinding using Ada backend. */ + PATHFINDER_A_STAR_ADA = 5 /**< A* algorithm pathfinding using the Ada backend. */ }; /** @@ -74,6 +76,9 @@ namespace Pathfinder { */ class PathfindingParent { public: + using compareCellsFunction = std::function; + /** * @brief Constructor for the PathfindingParent class. * @param grid Reference to the Grid object used in the pathfinding. @@ -130,8 +135,6 @@ namespace Pathfinder { void reInit(); protected: - using compareCellsFunction = std::function; /** * @fn virtual getStrat @@ -258,4 +261,60 @@ namespace Pathfinder { std::pair direction); }; + /** + * @class DijkstraSolveAda + * @brief Extension of the PathfindingParent class. Defines an object which applies the Dijkstra algorithm for pathfinding using the Ada backend. + */ + class DijkstraSolveAda : public PathfindingParent { + public: + /** + * @brief Constructor for the JumpPointSolve class. Uses the parent Class constructor. + * @param grid Reference to the Grid object where the Jump-Point-Search algorithm will be applied. + */ + explicit DijkstraSolveAda(GridGenerator::Grid &grid); + + /** + * @fn void nextStep + * @brief Overrides the nextStep() function to implement the Dijkstra algorithm's next step. + */ + void nextStep() override; + + private: + /** + * @fn getStrat + * @brief Overrides the getStrat() function to get the strategy (Dijkstra). + */ + [[nodiscard]] inline PathfinderStrategy getStrat() const override { + return PathfinderStrategy::PATHFINDER_DIJKSTRA_ADA; + } + }; + + /** + * @class AStarSolveAda + * @brief Extension of the PathfindingParent class. Defines an object which applies the A* algorithm for pathfinding using the Ada backend. + */ + class AStarSolveAda : public PathfindingParent { + public: + /** + * @brief Constructor for the AStarSolve class. Uses the parent Class constructor. + * @param grid Reference to the Grid object where the A* algorithm will be applied. + */ + explicit AStarSolveAda(GridGenerator::Grid &grid); + + /** + * @fn void nextStep + * @brief Overrides the nextStep() function to implement the A* algorithm's next step. + */ + void nextStep() override; + + private: + /** + * @fn getStrat + * @brief Overrides the getStrat() function to get the strategy (A*). + */ + [[nodiscard]] inline PathfinderStrategy getStrat() const override { + return PathfinderStrategy::PATHFINDER_A_STAR_ADA; + } + }; + } \ No newline at end of file diff --git a/Core/Pathfinder/src/pathfinding.cpp b/Core/Pathfinder/src/pathfinding.cpp index 88f26c5..0b2e5d2 100644 --- a/Core/Pathfinder/src/pathfinding.cpp +++ b/Core/Pathfinder/src/pathfinding.cpp @@ -4,7 +4,9 @@ const std::map Pathfinder::PathfinderStrategyParser::pathfindingStrategyToDisplayableText = {{PathfinderStrategy::PATHFINDER_DIJKSTRA, "Dijkstra's Algorithm"}, {PathfinderStrategy::PATHFINDER_A_STAR, "A* Algorithm"}, - {PathfinderStrategy::PATHFINDER_JUMP_POINT_SEARCH, "Jump Point Search"}}; + {PathfinderStrategy::PATHFINDER_JUMP_POINT_SEARCH, "Jump Point Search"}, + {PathfinderStrategy::PATHFINDER_DIJKSTRA_ADA, "Dijkstra's Algorithm (Ada Backend)"}, + {PathfinderStrategy::PATHFINDER_A_STAR_ADA, "A* Algorithm (Ada Backend)"}}; std::unique_ptr Pathfinder::PathfinderStrategyParser::parsePathfinderStrategy( PathfinderStrategy strat, @@ -16,6 +18,10 @@ std::unique_ptr Pathfinder::PathfinderStrategyPar return std::make_unique(grid); case PathfinderStrategy::PATHFINDER_JUMP_POINT_SEARCH: return std::make_unique(grid); + case PathfinderStrategy::PATHFINDER_A_STAR_ADA: + return std::make_unique(grid); + case PathfinderStrategy::PATHFINDER_DIJKSTRA_ADA: + return std::make_unique(grid); } return nullptr; } @@ -60,3 +66,12 @@ void Pathfinder::PathfindingParent::reInit() { grid.resetGrid(); initGenericSolver(); } + +extern "C" void pathfinder_push_impl(void *queue_handle, uint32_t x, uint32_t y) { + GridGenerator::GridCoordinate gc{x, y}; + auto& queue = *reinterpret_cast, + Pathfinder::PathfindingParent::compareCellsFunction>*>(queue_handle); + queue.push(gc); +} \ No newline at end of file diff --git a/Core/Pathfinder/src/pathfinding_a_star_ada.cpp b/Core/Pathfinder/src/pathfinding_a_star_ada.cpp new file mode 100644 index 0000000..8b9466a --- /dev/null +++ b/Core/Pathfinder/src/pathfinding_a_star_ada.cpp @@ -0,0 +1,40 @@ +#include "pathfinding.h" +#include "pathfinding_c_inf.h" + +extern "C" void ada_astar_next_step(void *grid_handle, long currentX, long currentY, long endX, long endY, void *queue_handle); +extern "C" void pathfinder_push_impl(void *queue_handle, long x, long y); + +Pathfinder::AStarSolveAda::AStarSolveAda(GridGenerator::Grid &grid) : PathfindingParent(grid) +{ + register_queue_push(pathfinder_push_impl); +} + +void Pathfinder::AStarSolveAda::nextStep() +{ + timer.stepBegin(); + GridGenerator::GridCoordinate endCoordinates = grid.getEndCoordinates(); + + GridGenerator::GridCoordinate currentCoordinates = nextCellQueue.top(); + nextCellQueue.pop(); + + GridGenerator::Cell ¤tCell = grid(currentCoordinates); + if (currentCell.getState() == GridGenerator::CellState::CELL_CLOSED) + return; + if (currentCoordinates == endCoordinates) + { + timer.stepEnd(); + grid.markPathByParentCells(true); + return; + } + + ada_astar_next_step(reinterpret_cast(&grid), + static_cast(currentCoordinates.x), + static_cast(currentCoordinates.y), + static_cast(endCoordinates.x), + static_cast(endCoordinates.y), + reinterpret_cast(&nextCellQueue)); + + currentCell.markClosed(); + grid.incrementClosedCellCount(); + timer.stepEnd(); +} diff --git a/Core/Pathfinder/src/pathfinding_dijkstra_ada.cpp b/Core/Pathfinder/src/pathfinding_dijkstra_ada.cpp new file mode 100644 index 0000000..a120a43 --- /dev/null +++ b/Core/Pathfinder/src/pathfinding_dijkstra_ada.cpp @@ -0,0 +1,35 @@ +#include "pathfinding.h" +#include "pathfinding_c_inf.h" + +extern "C" void ada_dijkstra_next_step(void *grid_handle, long currentX, long currentY, void *queue_handle); +extern "C" void pathfinder_push_impl(void *queue_handle, long x, long y); + +Pathfinder::DijkstraSolveAda::DijkstraSolveAda(GridGenerator::Grid &grid) : PathfindingParent(grid) +{ + register_queue_push(pathfinder_push_impl); +} + +void Pathfinder::DijkstraSolveAda::nextStep() +{ + timer.stepBegin(); + GridGenerator::GridCoordinate endCoordinates = grid.getEndCoordinates(); + + GridGenerator::GridCoordinate currentCoordinates = nextCellQueue.top(); + nextCellQueue.pop(); + GridGenerator::Cell ¤tCell = grid(currentCoordinates); + if (currentCoordinates == endCoordinates) + { + timer.stepEnd(); + grid.markPathByParentCells(true); + return; + } + + ada_dijkstra_next_step(reinterpret_cast(&grid), + static_cast(currentCoordinates.x), + static_cast(currentCoordinates.y), + reinterpret_cast(&nextCellQueue)); + + currentCell.markClosed(); + grid.incrementClosedCellCount(); + timer.stepEnd(); +} diff --git a/README.md b/README.md index e84fe30..84395ab 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,19 @@ There are three key pathfinding algorithms that are currently implemented in thi - **Dijkstra**: This is a renowned pathfinding algorithm that operates by exploring the neighbouring nodes of the start point. It then traverses towards the goal by frequently jumping to the node with the lowest cost. The Dijkstra - pathfinding algorithm guarantees the shortest path. + pathfinding algorithm guarantees the shortest path. This algorithm is implemented in C++ and in Ada. - **A\* Algorithm**: This pathfinding algorithm employs a heuristic to guide its way towards the goal. The key distinguishing factor between the A\* and Dijkstra's algorithm is that, while Dijkstra's algorithm seeks the shortest path to all possible destinations, the A\* algorithm uses a heuristic to estimate the cost to the goal, which allows it to focus on more promising paths and reach the goal more efficiently. In other words, A\* is more strategic in terms of selecting the next node for expansion, quickly finding an optimal path in large graphs compared to Dijkstra's - algorithm. + algorithm. This algorithm is implemented in C++ and in Ada. - **Jump Point Search**: This search algorithm is essentially an optimization of the A\* algorithm for uniform-cost grid maps, and this is achieved without affecting optimality. Here, instead of examining each individual cell, the - algorithm has the liberty to "jump" in between points in the direction of the goal, bypassing any irrelevant nodes. + algorithm has the liberty to "jump" in between points in the direction of the goal, bypassing any irrelevant nodes. + This algorithm is only implemented in C++. ## Obstacle Generation @@ -55,6 +56,7 @@ cmake qtcreator qtbase5-dev qt5-qmake +gprbuild ``` For the Doxygen documentation generation, you will need to have the following dependencies: @@ -75,7 +77,19 @@ fontenc In order to simplify the setup and usage process, a Devcontainer has been provided. This essentially allows you to use the project inside a Docker environment. This isolates your development environment from your local machine. To get -started all you need to do is clone the Git repository and then use the Devcontainer. +started all you need to do is clone the Git repository and then use the Devcontainer. Building the Container may take some +time as a texlive-full installation is a part of the container to be able to build the documentation and PDF-Paper. +To build the project using the Devcontainer, set it up and then run the following commands in the devcontainer shell: +```console +cmake -GNinja -S . -B ./build -DWITH_DOCS=ON -DWITH_PAPER=ON +cmake --build ./build +``` +To only build the executable and not the documentation and the PDF-Paper run the following commands instead: +```console +cmake -GNinja -S . -B ./build +cmake --build ./build +``` +When operating on Windows, WSL can be used to execute the compiled binary afterwards. ## Documentation