134 lines
3.4 KiB
C++
134 lines
3.4 KiB
C++
#include <iostream>
|
|
#include <string>
|
|
#include <list>
|
|
#include <functional>
|
|
#include <tuple>
|
|
#include <variant>
|
|
#include <thread>
|
|
#include <sstream>
|
|
#include <optional>
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
template <typename Message> struct ConsoleIO {
|
|
static auto ReadlineS(std::istream& is, std::function<Message(std::optional<std::string>)> f) {
|
|
return [=,&is] {
|
|
if (std::string line; std::getline(is, line)) {
|
|
return f(std::make_optional(line));
|
|
} else {
|
|
return f(std::nullopt);
|
|
}
|
|
};
|
|
}
|
|
|
|
static std::function<Message()>
|
|
Readline(std::function<Message(std::optional<std::string>)> f) {
|
|
return ReadlineS (std::ref(std::cin), f);
|
|
}
|
|
|
|
static std::function<Message()>
|
|
Prompt(std::string prompt, std::function<Message(std::optional<std::string>)> f) {
|
|
return [=] {
|
|
std::cout << prompt << std::flush;
|
|
return Readline (f)();
|
|
};
|
|
}
|
|
};
|
|
|
|
template <typename ModelT, typename MessageT> struct AppT {
|
|
using Model = ModelT;
|
|
using Message = MessageT;
|
|
using Command = std::function<Message()>;
|
|
using InitResult = std::tuple<Model, std::list<Command>>;
|
|
using UpdateResult = InitResult;
|
|
using ViewResult = std::string;
|
|
using ReadLine = std::function<Message (std::string)>;
|
|
using Console = ConsoleIO<Message>;
|
|
|
|
using Init = std::function<InitResult ()>;
|
|
using Update = std::function<UpdateResult (Model, const Message&)>;
|
|
using View = std::function<ViewResult (const Model&)>;
|
|
using Done = std::function<bool (const Model&)>;
|
|
|
|
struct Config {
|
|
Init init;
|
|
Update update;
|
|
View view;
|
|
Done done;
|
|
};
|
|
|
|
int run (const Config & cfg) {
|
|
auto const& [init, update, view, done] = cfg;
|
|
auto [state, cmds] = init();
|
|
|
|
while (! done (state)) {
|
|
std::cout << view(state) << "\n";
|
|
|
|
if (!cmds.empty()) {
|
|
auto [newstate, newcmds] = update (state, cmds.front()());
|
|
cmds.erase (cmds.begin());
|
|
cmds.insert (cmds.end(), std::begin(newcmds), std::end (newcmds));
|
|
|
|
state = newstate;
|
|
}
|
|
std::this_thread::sleep_for (100ms);
|
|
}
|
|
|
|
std::cout << "app finished with " << cmds.size() << " pending commands and final state: \n" << view(state) << '\n';
|
|
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
// user defined
|
|
|
|
struct Model {
|
|
bool done{false};
|
|
int value{0};
|
|
};
|
|
|
|
struct SetValue {
|
|
int s;
|
|
};
|
|
|
|
struct Stop {
|
|
};
|
|
|
|
using Message = std::variant<SetValue, Stop>;
|
|
|
|
auto userInputToMsg(const std::optional<std::string>& s) -> Message {
|
|
|
|
if (s) {
|
|
std::stringstream sstr(*s);
|
|
int r;
|
|
sstr >> r;
|
|
return SetValue { r };
|
|
} else {
|
|
return Stop{};
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
using App = AppT<Model, Message>;
|
|
App app;
|
|
return app.run(App::Config{
|
|
[]() -> App::InitResult {
|
|
return {{false, 23}, {App::Console::Prompt("> ", userInputToMsg)}};
|
|
}, // init
|
|
[](App::Model state,
|
|
const App::Message &msg) -> App::UpdateResult {
|
|
if (auto stop = std::get_if<Stop>(&msg); stop) {
|
|
state.done = true;
|
|
return {state, {}};
|
|
} else {
|
|
state.value = std::get<SetValue>(msg).s;
|
|
return {state, {App::Console::Prompt("> ", userInputToMsg)}};
|
|
}
|
|
}, // update
|
|
[](const App::Model &state) -> App::ViewResult {
|
|
return "Model " + std::to_string(state.value);
|
|
}, // view
|
|
[](const App::Model &state) -> bool { return state.done; } // done
|
|
});
|
|
}
|