99
1010#include < string>
1111#include " Common/Compiler.h"
12+ #include " Common/Cpp/AbstractLogger.h"
1213#include " Common/Cpp/Time.h"
14+ #include " Common/Cpp/CancellableScope.h"
1315
1416class QKeyEvent ;
1517
1618namespace PokemonAutomation {
1719
18-
1920enum class ControllerType ;
2021
2122
23+
24+ inline Milliseconds round_up_to_ticksize (Milliseconds ticksize, Milliseconds duration){
25+ if (ticksize == Milliseconds::zero ()){
26+ return duration;
27+ }
28+ return (duration + ticksize - Milliseconds (1 )) / ticksize * ticksize;
29+ }
30+
31+
32+
33+
2234class AbstractController {
2335public:
2436 virtual ~AbstractController () = default ;
2537
38+ virtual Logger& logger () = 0;
39+
2640
2741public:
2842 // Static Information
@@ -45,6 +59,89 @@ class AbstractController{
4559 virtual std::string error_string () const = 0;
4660
4761
62+ public:
63+ //
64+ // Cancellation
65+ //
66+ // These functions will return immediately and are thread-safe with
67+ // everything. The intended use-case is for an inference thread to cancel
68+ // a running sequence of commands on a program thread.
69+ //
70+
71+ // Cancel all commands. This returns the controller to the neutral button
72+ // state and clears the command queue.
73+ // This does not wait for the commands to finish cancelling. This is an
74+ // asynchronous function that merely initiates the cancellation process.
75+ virtual void cancel_all_commands () = 0;
76+
77+ // Same as "cancel_all_commands()", but instead of cancelling the stream,
78+ // it lets it keep running. Then on the next command issued after this
79+ // cancel, it will atomically replace the stream without gapping.
80+ // This lets you do stuff like suddenly change joystick movement in
81+ // response to inference while simultaneously holding a button without
82+ // ever releasing it during the transition.
83+ virtual void replace_on_next_command () = 0;
84+
85+
86+ public:
87+ //
88+ // Superscalar Commands (the "ssf" framework)
89+ //
90+
91+ //
92+ // delay Time to wait before moving onto the next command.
93+ // hold Time to hold the button/stick down for.
94+ // cooldown After the button has been released, prevent it from being
95+ // used again for this much time.
96+ //
97+ // For "normal" use, you should always set (delay == hold + cooldown).
98+ // This is the easiest case to understand and is what the "pbf" interface
99+ // exposes.
100+ //
101+ // If the button is busy (due to still being held down or is waiting out
102+ // the cooldown), the command will wait until the button is ready.
103+ //
104+ // By setting (delay < hold), the command will "return" early and move onto
105+ // the next command while the button is still being held down.
106+ //
107+ // If a command returns before it is finished (delay < hold + cooldown),
108+ // it is considered a "hanging" command.
109+ //
110+ // This allows you to overlap buttons. But is confusing to use because it
111+ // implies a non-trivial controller state with pending button presses that
112+ // are scheduled to be released at a later time.
113+ //
114+ // Each button/stick has its own independent timeline/schedule.
115+ // Users are responsible for understanding the controller state and
116+ // managing the timeline/scheduling.
117+ //
118+ // It is important to remember that the "timeline" here is the timeline
119+ // being fed to the Switch. Thus the timing parameters written in the C++
120+ // code here is what you will get on the console (or as close as possible).
121+ //
122+ // The actual calls to the methods in the class will return or block in an
123+ // unspecified manner as they merely enqueue into a FIFO which is then
124+ // "replayed" to the Switch in an implementation-dependent manner.
125+ //
126+
127+ // Wait for all unfinished commands to finish. This will also wait out
128+ // hanging commands including their cooldown periods.
129+ // This is not a true command function as it waits for the entire queue to
130+ // empty out rather than entering itself into the queue.
131+ // If a cancellation happens inside this function, it will immediately
132+ // throw an OperationCancelledException.
133+ virtual void wait_for_all (const Cancellable* cancellable) = 0;
134+
135+ // Tell the scheduler to wait for all pending commands to finish
136+ // (including cooldowns) before executing further instructions.
137+ // This is used to prevent hanging commands from overlapping with new
138+ // commands issued after this barrier.
139+ virtual void issue_barrier (const Cancellable* cancellable) = 0;
140+
141+ // Do nothing for this much time.
142+ virtual void issue_nop (const Cancellable* cancellable, Milliseconds duration) = 0;
143+
144+
48145public:
49146 // Keyboard Controls
50147
@@ -56,12 +153,79 @@ class AbstractController{
56153
57154
58155
59- inline Milliseconds round_up_to_ticksize (Milliseconds ticksize, Milliseconds duration){
60- if (ticksize == Milliseconds::zero ()){
61- return duration;
156+ //
157+ // The context wrapper to support asynchronous cancel.
158+ //
159+ template <typename Type>
160+ class ControllerContext final : public CancellableScope{
161+ public:
162+ using ControllerType = Type;
163+
164+ public:
165+ ControllerContext (ControllerType& botbase)
166+ : m_controller(botbase)
167+ {}
168+ ControllerContext (CancellableScope& parent, ControllerType& botbase)
169+ : m_controller(botbase)
170+ {
171+ attach (parent);
62172 }
63- return (duration + ticksize - Milliseconds (1 )) / ticksize * ticksize;
64- }
173+ virtual ~ControllerContext (){
174+ detach ();
175+ }
176+
177+ ControllerType* operator ->(){
178+ m_lifetime_sanitizer.check_usage ();
179+ return &m_controller;
180+ }
181+
182+ operator ControllerType&() const { return m_controller; }
183+ ControllerType& controller () const { return m_controller; }
184+
185+ void wait_for_all_requests () const {
186+ auto scope = m_lifetime_sanitizer.check_scope ();
187+ m_controller.wait_for_all (this );
188+ }
189+
190+ // Stop all commands in this context now.
191+ void cancel_now (){
192+ auto scope = m_lifetime_sanitizer.check_scope ();
193+ CancellableScope::cancel (nullptr );
194+ m_controller.cancel_all_commands ();
195+ }
196+
197+ // Stop the commands in this context, but do it lazily.
198+ //
199+ // 1. Stop new commands from being issued to the device from this context.
200+ // 2. Tell the device that the next command that is issued should replace
201+ // the command queue.
202+ //
203+ // This cancel is used when you need continuity from an ongoing
204+ // sequence.
205+ void cancel_lazy (){
206+ auto scope = m_lifetime_sanitizer.check_scope ();
207+ CancellableScope::cancel (nullptr );
208+ m_controller.replace_on_next_command ();
209+ }
210+
211+ virtual bool cancel (std::exception_ptr exception) noexcept override {
212+ auto scope = m_lifetime_sanitizer.check_scope ();
213+ if (CancellableScope::cancel (std::move (exception))){
214+ return true ;
215+ }
216+ try {
217+ m_controller.cancel_all_commands ();
218+ }catch (...){}
219+ return false ;
220+ }
221+
222+
223+ private:
224+ ControllerType& m_controller;
225+ LifetimeSanitizer m_lifetime_sanitizer;
226+ };
227+
228+
65229
66230
67231
0 commit comments