Skip to content

Commit

Permalink
cleaned up argparse
Browse files Browse the repository at this point in the history
  • Loading branch information
Philemon Benner authored and Philemon Benner committed Dec 4, 2024
1 parent 1fb4ff9 commit 07ae29d
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 106 deletions.
Binary file modified bin/PathFinding.exe
Binary file not shown.
2 changes: 1 addition & 1 deletion bin/md5sum.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3c15806bfbd628fa20ac4a01ad021ee2
14f960f02031a37e658f32f87cd0840f
146 changes: 71 additions & 75 deletions src/argparse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@
namespace st::argparse {
namespace {

constexpr string_view kHelp = "--help";
constexpr string_view kWidth = "--width";
constexpr string_view kHeight = "--height";
constexpr string_view kThreads = "--threads";
constexpr string_view kEntities = "--entities";
constexpr string_view kObstacles = "--obstacles";
constexpr string_view kLoopTime = "--loopTime";
constexpr string_view kAlgorithm = "--algorithm";

constexpr int kDefaultEntities = 20;
constexpr PathAlgorithm kDefaultAlgo = PathAlgorithm::AStar;
constexpr std::chrono::milliseconds kDefaultLoopTime(20);

auto GetTerminalSize() -> Size {
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
Expand All @@ -20,94 +33,77 @@ auto GetTerminalSize() -> Size {
return Size{csbi.dwSize.X - 10, csbi.dwSize.Y - 10};
}

bool HasArgument(char **begin, char **end, string_view option) { return std::find(begin, end, option) != end; }
auto GetArgumentValue(char **begin, char **end, string_view option) -> std::string {
char **itr = std::find(begin, end, option);
if (itr != end && ++itr != end) {
return std::string(*itr);
}
return {};
}

int CalculateDefaultObstacles(const Size &monitor_size) { return monitor_size.width * monitor_size.height / 10; }

} // namespace

void PrintHelpMessage() {
const auto monitor_size = GetTerminalSize();
const auto thread_count = std::thread::hardware_concurrency();
std::stringstream ss;
ss << "Help \n";
ss << Args::khelp << " Print this help message\n";
ss << Args::kWidth << " Monitor width default: " << monitor_size.width << "\n";
ss << Args::kHeight << " Monitor height default: " << monitor_size.height << "\n";
ss << Args::kAlgorithm << " Algorithms available: \n";
ss << " " << std::to_underlying(PathAlgorithm::Greedy) << ": "
<< PathAlgorithmToString(PathAlgorithm::Greedy) << "\n";
ss << " " << std::to_underlying(PathAlgorithm::AStar) << ": "
<< PathAlgorithmToString(PathAlgorithm::AStar) << "\n";
ss << Args::kEntities << " Number of entities to spawn default: " << Args::kDefaultEntities << "\n";
ss << Args::kObstacles << " Number of obstacles to spawn default: " << CalculateDefaultObstacles(monitor_size)
<< "\n";
ss << Args::kThreads << " Thread count default: " << thread_count << "\n";
ss << Args::kLoopTime << " Loop time default: " << Args::kDefaultLoopTime << "\n";
std::println("{}", ss.str());
template <typename T>
void PrintlnOption(string_view option, string_view description, T _default) {
std::println("{:<15}{:<40}{}", option, description, _default);
}

auto ParseArguments(int argc, char *argv[]) -> unique_ptr<Args> {
auto args = std::make_unique<Args>();
args->monitor_size = GetTerminalSize();
args->thread_count = std::thread::hardware_concurrency();
void PrintlnOption(string_view option, string_view description) { std::println("{:<15}{}", option, description); }

if (HasArgument(argv, argv + argc, "--help")) {
PrintHelpMessage();
std::exit(0);
}

if (HasArgument(argv, argv + argc, Args::kWidth)) {
const auto width = GetArgumentValue(argv, argv + argc, Args::kHeight);
if (!width.empty()) args->monitor_size.height = std::stoi(width);
}

if (HasArgument(argv, argv + argc, Args::kHeight)) {
const auto height = GetArgumentValue(argv, argv + argc, Args::kHeight);
if (!height.empty()) args->monitor_size.height = std::stoi(height);
}

if (HasArgument(argv, argv + argc, Args::kEntities)) {
const auto entities = GetArgumentValue(argv, argv + argc, Args::kEntities);
if (!entities.empty()) args->num_entities = std::stoi(entities);
}

args->num_obstacles = CalculateDefaultObstacles(args->monitor_size);
if (HasArgument(argv, argv + argc, Args::kObstacles)) {
const auto obstacles = GetArgumentValue(argv, argv + argc, Args::kObstacles);
if (!obstacles.empty()) args->num_obstacles = std::stoi(obstacles);
template <typename T>
void PrintlnOption(string_view option, string_view description, std::vector<std::pair<string_view, T>> selection) {
std::println("{:<15}{:<40}", option, description);
for (auto &[desc, value] : selection) {
std::println("{:>15}{} = {}", "", desc, value);
}
std::println("");
}

if (HasArgument(argv, argv + argc, Args::kThreads)) {
const auto thread_count = GetArgumentValue(argv, argv + argc, Args::kThreads);
if (!thread_count.empty()) args->thread_count = std::stoi(thread_count);
}
void PrintHelpMessageAndExit() {
const auto monitor_size = GetTerminalSize();
std::println("Help");
std::println("");
std::println("{:<15}{:<40}{}", "Option", "Description", "Default");
std::println("");
PrintlnOption(kHelp, "Print this help message");
PrintlnOption(kWidth, "Monitor width", monitor_size.width);
PrintlnOption(kHeight, "Monitor height", monitor_size.height);
PrintlnOption(kEntities, "Number of entities to spawn", kDefaultEntities);
PrintlnOption(kObstacles, "Number of obstacles to spawn", CalculateDefaultObstacles(monitor_size));
PrintlnOption(kThreads, "Threads available for pool", std::thread::hardware_concurrency());
PrintlnOption(kLoopTime, "Main loop sleep time", kDefaultLoopTime);
PrintlnOption(
kAlgorithm, "Algorithm to use for path finding",
std::vector{
std::make_pair(PathAlgorithmToString(PathAlgorithm::Greedy), std::to_underlying(PathAlgorithm::Greedy)),
std::make_pair(PathAlgorithmToString(PathAlgorithm::AStar), std::to_underlying(PathAlgorithm::AStar))});
std::exit(0);
}

if (HasArgument(argv, argv + argc, Args::kLoopTime)) {
const auto loop_time = GetArgumentValue(argv, argv + argc, Args::kLoopTime);
if (!loop_time.empty()) args->loop_time = std::chrono::milliseconds(std::stoi(loop_time));
auto ParseArguments(int argc, char *argv[]) -> Arguments {
CLI cli(argc, argv);
Arguments args;
args.monitor_size = GetTerminalSize();
args.thread_count = std::thread::hardware_concurrency();
args.algorithm = kDefaultAlgo;
args.loop_time = kDefaultLoopTime;
args.num_entities = kDefaultEntities;

if (cli.Contains(kHelp)) {
PrintHelpMessageAndExit();
}

if (HasArgument(argv, argv + argc, Args::kAlgorithm)) {
const auto algorithm = GetArgumentValue(argv, argv + argc, Args::kAlgorithm);
if (!algorithm.empty()) {
int algo = std::stoi(algorithm);
// NOTE: For now best solution i could come up with if Algorithm order changes this will be UB
if (algo < std::to_underlying(PathAlgorithm::Greedy) || algo > std::to_underlying(PathAlgorithm::AStar)) {
std::println("[Argparse] Unknown algorithm: {} !!", algo);
return nullptr;
}
args->algorithm = static_cast<PathAlgorithm>(algo);
cli.VisitIfContains<int>(kWidth, [&args](int val) { args.monitor_size.width = val; });
cli.VisitIfContains<int>(kHeight, [&args](int val) { args.monitor_size.height = val; });
// Set the default here as we now have our monitor size set
args.num_obstacles = CalculateDefaultObstacles(args.monitor_size);

cli.VisitIfContains<int>(kEntities, [&args](int val) { args.num_entities = val; });
cli.VisitIfContains<int>(kObstacles, [&args](int val) { args.num_obstacles = val; });
cli.VisitIfContains<int>(kThreads, [&args](int val) { args.thread_count = val; });
cli.VisitIfContains<int>(kLoopTime, [&args](int val) { args.loop_time = std::chrono::milliseconds(val); });
cli.VisitIfContains<int>(kAlgorithm, [&args](int val) {
// NOTE: For now best solution i could come up with if Algorithm order changes this will be UB
if (val < std::to_underlying(PathAlgorithm::Greedy) || val > std::to_underlying(PathAlgorithm::AStar)) {
std::println("[Argparse] Unknown algorithm: {} !", val);
PrintHelpMessageAndExit();
}
}

args.algorithm = static_cast<PathAlgorithm>(val);
});
return args;
}

Expand Down
70 changes: 53 additions & 17 deletions src/argparse.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,72 @@
#include <string_view>
#include <chrono>
#include <memory>
#include <functional>

#include "types.h"
#include "from_chars.h"
#include "point.h"
#include "path_finding.h"

namespace st::argparse {

struct Args {
static constexpr string_view khelp = "--help";
static constexpr string_view kWidth = "--width";
static constexpr string_view kHeight = "--height";
static constexpr string_view kThreads = "--threads";
static constexpr string_view kEntities = "--entities";
static constexpr string_view kObstacles = "--obstacles";
static constexpr string_view kLoopTime = "--loopTime";
static constexpr string_view kAlgorithm = "--algorithm";
class CLI {
public:
CLI(int argc, const char* const* argv) : view_(argv, argc) {}

static constexpr int kDefaultEntities = 20;
static constexpr int kDefaultLoopTime = 20;
static constexpr PathAlgorithm kDefaultAlgo = PathAlgorithm::AStar;
auto Contains(std::string_view option) const { return std::ranges::find(view_, option) != view_.end(); }

template <typename T>
auto GetValue(std::string_view option) const -> std::optional<T> {
auto it = std::ranges::find(view_, option);
if (it == view_.end()) {
return std::nullopt;
}

auto vit = it + 1;
if (vit == view_.end()) {
return std::nullopt;
}

if constexpr (std::is_same<T, std::string>()) {
return std::string(*vit);
} else if constexpr (std::is_integral<T>()) {
return FromChars<T>(*vit);
} else if constexpr (std::is_floating_point<T>()) {
return FromChars<T>(*vit);
} else {
static_assert(false, "Get argument only supports std::string integral and floating point types");
}
}

template <typename T, typename F>
void VisitIfContains(std::string_view option, F visitor) {
if (!Contains(option)) {
return;
}

auto value = GetValue<T>(option);
if (value) {
std::invoke(visitor, std::forward<T>(*value));
}
}

auto Empty() const { return view_.empty(); }
auto Size() const { return view_.size(); }

private:
std::basic_string_view<const char*> view_;
};

struct Arguments {
Size monitor_size;
PathAlgorithm algorithm;
std::chrono::milliseconds loop_time;
int thread_count;
int num_obstacles;
PathAlgorithm algorithm{kDefaultAlgo};
int num_entities{kDefaultEntities};
std::chrono::milliseconds loop_time{kDefaultLoopTime};
int num_entities;
};

void PrintHelpMessage();
auto ParseArguments(int argc, char *argv[]) -> unique_ptr<Args>;
auto ParseArguments(int argc, char* argv[]) -> Arguments;

} // namespace st::argparse
34 changes: 34 additions & 0 deletions src/from_chars.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include <string_view>
#include <optional>
#include <cstdint>
#include <charconv>

// std::optional wrapper around the from chars interface

namespace st {

/**
* @brief std::from_chars wrapper, returning nullopt if from chars reports error
*
* @tparam T
* @param s
* @return std::optional<T>
*/
template <typename T>
constexpr auto FromChars(std::string_view s) -> std::optional<T> {
T val;
if (std::from_chars(s.data(), s.data() + s.size(), val).ec == std::errc{}) {
return val;
} else {
return std::nullopt;
}
}

template <>
constexpr auto FromChars<bool>(std::string_view s) -> std::optional<bool> {
return FromChars<uint8_t>(s).transform([](uint8_t val) { return static_cast<bool>(val); });
}

} // namespace st
20 changes: 7 additions & 13 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,16 @@ void DrawObstacles(Drawer *drawer, const std::vector<Point> &obstacles) {
}
}

void MainLoop(std::stop_token stoken, std::unique_ptr<argparse::Args> args) {
BS::thread_pool pool{static_cast<unsigned int>(args->thread_count)};
Monitor monitor{args->monitor_size};
void MainLoop(std::stop_token stoken, argparse::Arguments args) {
BS::thread_pool pool{static_cast<unsigned int>(args.thread_count)};
Monitor monitor{args.monitor_size};
std::vector<std::pair<Entity, std::future<PointVec>>> pending_missions;

auto system = CreateEntitySystem(monitor.size(), args->num_entities);
auto obstacles = CreateObstacles(monitor.size(), args->num_obstacles);
auto system = CreateEntitySystem(monitor.size(), args.num_entities);
auto obstacles = CreateObstacles(monitor.size(), args.num_obstacles);

const std::chrono::milliseconds loop_time{args->loop_time};
const PathAlgorithm algo{args->algorithm};
args.reset();
const std::chrono::milliseconds loop_time{args.loop_time};
const PathAlgorithm algo{args.algorithm};

monitor.SetTitle("Mission Path Finding Simulation 9000");
monitor.SetHeader(std::format("Config: Loop time: {}ms Thread Count: {} Obstacles: {} Algorithm: {}",
Expand Down Expand Up @@ -139,11 +138,6 @@ int main(int argc, char *argv[]) {
signal(SIGINT, &SignalHandler);

auto args = argparse::ParseArguments(argc, argv);
if (!args) {
argparse::PrintHelpMessage();
std::exit(1);
}

g_thread = std::jthread(MainLoop, std::move(args));
for (;;) {
std::this_thread::sleep_for(std::chrono::seconds(1));
Expand Down

0 comments on commit 07ae29d

Please sign in to comment.