diff --git a/builder.cpp b/builder.cpp index e59cbe2..89c85b4 100644 --- a/builder.cpp +++ b/builder.cpp @@ -1,12 +1,6 @@ #include "builder.hpp" #include "dbc.hpp" -#include "watcher.hpp" -#include "game_engine.hpp" #include // for milliseconds -#include -#include -#include -#include #include #include #include @@ -82,122 +76,116 @@ MatchResult Builder::parse_line(const string &line) { } } -enum BuildState { - WAITING, BUILDING, DONE, STARTING, READING -}; +void Builder::run() { + int rc = gui.main_loop(game, [&] { + event(GO); + return 0; + }); + if(rc != 0) println("ERROR IN GUI"); -void Builder::run() { - git_repository* repo = nullptr; - - try { - gui.output(format("Using build command: {}", build_cmd)); - efsw::FileWatcher* fileWatcher = new efsw::FileWatcher(); - dbc::check(fileWatcher != nullptr, "Failed to create filewatcher."); - - git_libgit2_init(); - - int err = git_repository_open(&repo, git_path.c_str()); - dbc::check(err == 0, git_error_last()->message); - - UpdateListener* listener = new UpdateListener(repo); - dbc::check(listener != nullptr, "Failed to create listener."); - - gui.output(format("Watching directory {} for changes...", git_path)); - efsw::WatchID wid = fileWatcher->addWatch(git_path, listener, true); - - FILE *build_out = NULL; - bool build_done = false; - string line = ""; - BuildState state = WAITING; - std::future build_fut; - - fileWatcher->watch(); - - int rc = gui.main_loop(game, [&] { - switch(state) { - case WAITING: - if(listener->changes) { - game.start_round(); - gui.building(); - gui.output(format("CHANGES! Running build {}", build_cmd)); - build_fut = std::async([&]() { - return start_command(build_cmd); - }); - state = STARTING; - } - break; - - case STARTING: { - println(">>> STARTING"); - std::future_status status = build_fut.wait_for(0ms); - - if(status == std::future_status::ready) { - build_out = build_fut.get(); - state = READING; - } else { - state = STARTING; - } - } - break; - - case READING: { - line = read_line(build_out, build_done); - state = BUILDING; - } - break; - case BUILDING: { - println(">>> BUILDING"); - // check if there's output - if(build_done) { - bool good = end_build(build_out, gui, build_cmd); - - if(good) { - gui.build_works(); - } else { - // BUG: make it not play two sounds - gui.build_failed(true, build_cmd); - } - - build_out = NULL; - state = DONE; - } else { - auto m = parse_line(line); - - if(m.match) { - gui.output(format("HIT WITH {} @ {}:{}:{} {}", m.type, m.file_name, m.lnumber, m.col, m.message)); - game.hit(m.type); - } - state = READING; - } - } - break; - - case DONE: { - game.end_round(); - - if(game.is_dead()) { - gui.you_died(); - game.reset(); - } - - listener->reset_state(); - gui.output("^^^^^^^^^^^ END ^^^^^^^^^^^"); - state = WAITING; - } - break; - } + event(QUIT); +} - return 0; +void Builder::building(BuildEvent ev) { + println(">>> BUILDING"); + // check if there's output + if(build_done) { + bool good = end_build(build_out, gui, build_cmd); + + if(good) { + gui.build_works(); + } else { + // BUG: make it not play two sounds + gui.build_failed(true, build_cmd); + } + + build_out = NULL; + state(DONE); + } else { + auto m = parse_line(line); + + if(m.match) { + gui.output(format("HIT WITH {} @ {}:{}:{} {}", m.type, m.file_name, m.lnumber, m.col, m.message)); + game.hit(m.type); + } + state(READING); + } +} + +void Builder::start(BuildEvent ev) { + gui.output(format("Using build command: {}", build_cmd)); + fileWatcher = new efsw::FileWatcher(); + dbc::check(fileWatcher != nullptr, "Failed to create filewatcher."); + + git_libgit2_init(); + + int err = git_repository_open(&repo, git_path.c_str()); + dbc::check(err == 0, git_error_last()->message); + + listener = new UpdateListener(repo); + dbc::check(listener != nullptr, "Failed to create listener."); + + gui.output(format("Watching directory {} for changes...", git_path)); + wid = fileWatcher->addWatch(git_path, listener, true); + fileWatcher->watch(); + + state(WAITING); +} + +void Builder::waiting(BuildEvent ev) { + if(listener->changes) { + game.start_round(); + gui.building(); + gui.output(format("CHANGES! Running build {}", build_cmd)); + build_fut = std::async([&]() { + return start_command(build_cmd); }); + state(STARTING); + } +} - dbc::check(rc == 0, "Invalid return from main_loop."); +void Builder::starting(BuildEvent ev) { + std::future_status status = build_fut.wait_for(0ms); + + if(status == std::future_status::ready) { + build_out = build_fut.get(); + state(READING); + } else { + state(STARTING); + } +} + +void Builder::reading(BuildEvent ev) { + line = read_line(build_out, build_done); + state(BUILDING); +} +void Builder::done(BuildEvent ev) { + game.end_round(); + + if(game.is_dead()) { + gui.you_died(); + game.reset(); + } + + listener->reset_state(); + gui.output("^^^^^^^^^^^ END ^^^^^^^^^^^"); + state(WAITING); +} + +void Builder::exit(BuildEvent ev) { + if(ev == QUIT) { fileWatcher->removeWatch(wid); git_libgit2_shutdown(); - } catch(dbc::Error &err) { + state(EXIT); + } +} + +void Builder::error(BuildEvent ev) { + // how to avoid doing this more than once? + if(ev == CRASH) { if(repo != nullptr) git_repository_free(repo); git_libgit2_shutdown(); - throw err; } } diff --git a/builder.hpp b/builder.hpp index 57a140c..16e370e 100644 --- a/builder.hpp +++ b/builder.hpp @@ -2,6 +2,11 @@ #include "gui.hpp" #include "game_engine.hpp" #include +#include "fsm.hpp" +#include +#include +#include +#include "watcher.hpp" using std::string; @@ -14,11 +19,28 @@ struct MatchResult { string message = ""; }; -class Builder { +enum BuildState { + START, WAITING, BUILDING, DONE, STARTING, READING, + EXIT, ERROR +}; + +enum BuildEvent { + GO, QUIT, CRASH +}; + +class Builder : DeadSimpleFSM { GUI gui; GameEngine game; string git_path = "NOT SET"; string build_cmd = "NOT SET"; + efsw::FileWatcher* fileWatcher = NULL; + UpdateListener* listener = NULL; + efsw::WatchID wid; + FILE *build_out = NULL; + bool build_done = false; + string line = ""; + std::future build_fut; + git_repository* repo = nullptr; public: @@ -27,4 +49,34 @@ class Builder { MatchResult parse_line(const string &line); void run(); + + void event(BuildEvent ev) override { + try { + if(ev == QUIT) { + exit(ev); + } + + switch(_state) { + FSM_STATE(BUILDING, building, ev); + FSM_STATE(START, start, ev); + FSM_STATE(WAITING, waiting, ev); + FSM_STATE(DONE, done, ev); + FSM_STATE(STARTING, starting, ev); + FSM_STATE(READING, reading, ev); + FSM_STATE(EXIT, exit, ev); + FSM_STATE(ERROR, exit, ev); + } + } catch(...) { + error(ev); + } + } + + void building(BuildEvent ev); + void start(BuildEvent ev); + void waiting(BuildEvent ev); + void done(BuildEvent ev); + void starting(BuildEvent ev); + void reading(BuildEvent ev); + void error(BuildEvent ev); + void exit(BuildEvent ev); }; diff --git a/fsm.hpp b/fsm.hpp index 79117f3..fc2b505 100644 --- a/fsm.hpp +++ b/fsm.hpp @@ -1,5 +1,10 @@ #pragma once +#include + +#define FSM_EV(S, F) case S: F(); break +#define FSM_STATE(S, F, E) case S: fmt::println(">>> " #S ":" #F ":{}", int(E)); F(E); break + template class DeadSimpleFSM { protected: @@ -11,6 +16,8 @@ public: void state(S next_state) { _state = next_state; } -}; -#define FSM_T(S, F) case S: F(); break + bool in_state(S state) { + return _state == state; + } +}; diff --git a/fsmtest.cpp b/fsmtest.cpp index c18c033..d1a67d0 100644 --- a/fsmtest.cpp +++ b/fsmtest.cpp @@ -14,24 +14,24 @@ enum MyEvent { class MyFSM : DeadSimpleFSM { public: void event(MyEvent ev) override { - switch(ev) { - FSM_T(STARTED, start); - FSM_T(PUSH, push); - FSM_T(QUIT, quit); + switch(_state) { + FSM_STATE(START, start, ev); + FSM_STATE(RUNNING, push, ev); + FSM_STATE(END, quit, ev); } } - void start() { + void start(MyEvent ev) { println("<<< START"); state(RUNNING); } - void push() { + void push(MyEvent ev) { println("<<< RUN"); state(RUNNING); } - void quit() { + void quit(MyEvent ev) { println("<<< STOP"); state(END); }