From 541534fc931092a03f27f84a570d102c0f047807 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 5 Oct 2023 13:52:57 +0200 Subject: [PATCH 1/3] clean up the C++ reflex game example --- examples/Cpp/ReflexGame/src/ReflexGame.cmake | 2 - examples/Cpp/ReflexGame/src/ReflexGame.lf | 158 +++++++++---------- 2 files changed, 79 insertions(+), 81 deletions(-) delete mode 100644 examples/Cpp/ReflexGame/src/ReflexGame.cmake diff --git a/examples/Cpp/ReflexGame/src/ReflexGame.cmake b/examples/Cpp/ReflexGame/src/ReflexGame.cmake deleted file mode 100644 index dec27269..00000000 --- a/examples/Cpp/ReflexGame/src/ReflexGame.cmake +++ /dev/null @@ -1,2 +0,0 @@ -find_package (Threads) -target_link_libraries(${LF_MAIN_TARGET} ${CMAKE_THREAD_LIBS_INIT}) diff --git a/examples/Cpp/ReflexGame/src/ReflexGame.lf b/examples/Cpp/ReflexGame/src/ReflexGame.lf index a68f94cd..8d16bb90 100644 --- a/examples/Cpp/ReflexGame/src/ReflexGame.lf +++ b/examples/Cpp/ReflexGame/src/ReflexGame.lf @@ -2,104 +2,97 @@ * This example illustrates the use of logical and physical actions, asynchronous external inputs, * the use of startup and shutdown reactions, and the use of actions with values. * + * @author Christian Menard * @author Felix Wittwer * @author Edward A. Lee * @author Marten Lohstroh */ -target Cpp { - keepalive: true, - cmake-include: "ReflexGame.cmake" -} +target Cpp -/** - * Produce a counting sequence at random times with a minimum and maximum time between outputs - * specified as parameters. - * - * @param min_time The minimum time between outputs. - * @param max_time The maximum time between outputs. - */ -reactor RandomSource(min_time: time(2 sec), max_time: time(8 sec)) { - private preamble {= - // Generate a random additional delay over the minimum. - // Assume millisecond precision is enough. - reactor::Duration additional_time(reactor::Duration min_time, reactor::Duration max_time) { - int interval_in_msec = (max_time - min_time) / std::chrono::milliseconds(1); - return (std::rand() % interval_in_msec) * std::chrono::milliseconds(1); - } +reactor RandomDelay(min_delay: time(2 sec), max_delay: time(8 sec)) { + public preamble {= + #include =} - input another: void + + input in: void output out: void - logical action prompt(min_time) - state count: int(0) + logical action delay(min_delay): void - reaction(startup) -> prompt {= - std::cout << "***********************************************" << std::endl; - std::cout << "Watch for the prompt, then hit Return or Enter." << std::endl; - std::cout << "Type Control-D (EOF) to quit." << std::endl << std::endl; + // a random number generator seeded with the current physiceal time + state rand: std::mt19937({= get_physical_time().time_since_epoch().count() =}) + // a uniform random distribution with a range from 1 to (max_delay - min_delay) milliseconds + state dist: std::uniform_int_distribution( + 1, + {= (max_delay - min_delay) / std::chrono::milliseconds(1) =}) - // TODO: Manual inclusion of header necessary? - // Set a seed for random number generation based on the current time. - std::srand(std::time(nullptr)); + reaction(delay) -> out {= + out.set(); + =} - // Schedule the first event. - prompt.schedule(additional_time(0ms, max_time - min_time)); + reaction(in) -> delay {= + delay.schedule(dist(rand) * std::chrono::milliseconds(1)); =} +} - reaction(prompt) -> out {= - count++; - std::cout << count << ". Hit Return or Enter!" << std::endl << std::flush; - out.set(); +reactor KeyboardInput { + state thread: std::thread + state terminate: std::atomic = false + physical action keyboard_input: char + + output enter: void + output quit: void + + reaction(startup) -> keyboard_input {= + // Start the thread that listens for keyboard input. + thread = std::thread([&] () { + int c{0}; + while(!terminate.load()) { + c = getchar(); + keyboard_input.schedule(c); + } + }); =} - reaction(another) -> prompt {= - // Schedule the next event. - prompt.schedule(additional_time(0ms, max_time - min_time)); + reaction(keyboard_input) -> enter, quit {= + char key = *keyboard_input.get(); + if(key == '\n') { + enter.set(); + } else if (key == EOF) { + quit.set(); + } =} -} -/** - * Upon receiving a prompt, record the time of the prompt, then listen for user input. When the user - * hits return, then schedule a physical action that records the time of this event and then report - * the response time. - */ -reactor GetUserInput { - public preamble {= - #include + reaction(shutdown) {= + terminate.store(true); + thread.join(); =} +} - physical action user_response: char - state prompt_time: {= reactor::TimePoint =}({= reactor::TimePoint::min() =}) +reactor GameLogic { + output request_prompt: void + input prompt: void + input enter: void + input quit: void + + state prompt_time: {= reactor::TimePoint =} = {= reactor::TimePoint::min() =} + state count: unsigned = 0 state total_time: time(0) - state count: int(0) - state thread: {= std::thread =} - input prompt: void - output another: void + reaction(startup) -> request_prompt {= + std::cout << "***********************************************" << std::endl; + std::cout << "Watch for the prompt, then hit Return or Enter." << std::endl; + std::cout << "Type Control-D (EOF) to quit." << std::endl << std::endl; - reaction(startup) -> user_response {= - // Start the thread that listens for Enter or Return. - thread = std::thread([&] () { - int c; - while(1) { - while((c = getchar()) != '\n') { - if (c == EOF) break; - } - user_response.schedule(c, 0ms); - if (c == EOF) break; - } - }); + // request the first prompt + request_prompt.set(); =} reaction(prompt) {= prompt_time = get_physical_time(); + std::cout << std::endl << "Hit Return or Enter!" << std::endl; =} - reaction(user_response) -> another {= - auto c = user_response.get(); - if (*c == EOF) { - environment()->sync_shutdown(); - return; - } + reaction(enter) -> request_prompt {= // If the prompt_time is 0, then the user is cheating and // hitting return before being prompted. if (prompt_time == reactor::TimePoint::min()) { @@ -114,13 +107,16 @@ reactor GetUserInput { total_time += time_in_ms; // Reset the prompt_time to indicate that there is no new prompt. prompt_time = reactor::TimePoint::min(); - // Trigger another prompt. - another.set(); + // Request another prompt. + request_prompt.set(); } =} + reaction(quit) {= + environment()->sync_shutdown(); + =} + reaction(shutdown) {= - thread.join(); if (count > 0) { std::cout << std::endl << "**** Average response time: " << std::chrono::duration_cast(total_time/count) << std::endl; } else { @@ -129,9 +125,13 @@ reactor GetUserInput { =} } -main reactor ReflexGame { - p = new RandomSource() - g = new GetUserInput() - p.out -> g.prompt - g.another -> p.another +main reactor { + delay = new RandomDelay() + keyboard = new KeyboardInput() + logic = new GameLogic() + + logic.request_prompt -> delay.in + delay.out -> logic.prompt + keyboard.enter -> logic.enter + keyboard.quit -> logic.quit } From 2de9eabeec04003a88856dcf24f2619819524eda Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Thu, 5 Oct 2023 13:53:28 +0200 Subject: [PATCH 2/3] Add an enclaved based implementation of the keyboard input --- .../Cpp/ReflexGame/src/ReflexGameEnclave.lf | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 examples/Cpp/ReflexGame/src/ReflexGameEnclave.lf diff --git a/examples/Cpp/ReflexGame/src/ReflexGameEnclave.lf b/examples/Cpp/ReflexGame/src/ReflexGameEnclave.lf new file mode 100644 index 00000000..8a78a336 --- /dev/null +++ b/examples/Cpp/ReflexGame/src/ReflexGameEnclave.lf @@ -0,0 +1,52 @@ +/** + * This example illustrates the use of enclaves to avoid spawning an extra thread for handling user + * input. + * + * The thread that is created in the startup reaction of KeyboardInput in eflexgame.lf uses a while + * loop. This loop is replaced by a enclave with a reaction that schedules using a physical action. + * A physical action was chosen here, because it alligns the progress of logical time in the enclave + * with the progress of physical time. Also it avoids the need for specifying or calculating a + * delay. + * + * The outputs that the EnclaveKeybardInput reactor produce are forwarded to the other reactors via + * physical actions. This fully decouples the input from the rest of the system, and time stamps of + * keyboard events are assigned based on the current physical time. + * + * @author Christian Menard + */ +target Cpp + +import GameLogic from "ReflexGame.lf" +import RandomDelay from "ReflexGame.lf" + +reactor EnclaveKeyboardInput { + physical action get_next + + output enter: void + output quit: void + + reaction(startup, get_next) -> get_next, enter, quit {= + int key = getchar(); + if(key == '\n') { + enter.set(); + get_next.schedule(); + } else if (key == EOF) { + quit.set(); + environment()->sync_shutdown(); + } else { + get_next.schedule(); + } + =} +} + +main reactor { + @enclave + keyboard = new EnclaveKeyboardInput() + delay = new RandomDelay() + logic = new GameLogic() + + logic.request_prompt -> delay.in + delay.out -> logic.prompt + keyboard.enter ~> logic.enter + keyboard.quit ~> logic.quit +} From f09009c7906c91cc0ce3e7ceeda18ed21b1b3389 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 31 Oct 2023 17:04:27 -0700 Subject: [PATCH 3/3] Update examples/Cpp/ReflexGame/src/ReflexGame.lf Co-authored-by: Edward A. Lee --- examples/Cpp/ReflexGame/src/ReflexGame.lf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Cpp/ReflexGame/src/ReflexGame.lf b/examples/Cpp/ReflexGame/src/ReflexGame.lf index 8d16bb90..88ca6af0 100644 --- a/examples/Cpp/ReflexGame/src/ReflexGame.lf +++ b/examples/Cpp/ReflexGame/src/ReflexGame.lf @@ -18,7 +18,7 @@ reactor RandomDelay(min_delay: time(2 sec), max_delay: time(8 sec)) { output out: void logical action delay(min_delay): void - // a random number generator seeded with the current physiceal time + // a random number generator seeded with the current physical time state rand: std::mt19937({= get_physical_time().time_since_epoch().count() =}) // a uniform random distribution with a range from 1 to (max_delay - min_delay) milliseconds state dist: std::uniform_int_distribution(