From 4c872a01f284cbf181f52c84e2ba6b4618e62556 Mon Sep 17 00:00:00 2001
From: Scott Lahteine <thinkyhead@users.noreply.github.com>
Date: Wed, 19 Jun 2019 00:00:19 -0500
Subject: [PATCH] G-code queue singleton, front injection (#14236)

---
 Marlin/src/Marlin.cpp                        |  18 +-
 Marlin/src/core/macros.h                     |   4 +-
 Marlin/src/feature/host_actions.cpp          |   2 +-
 Marlin/src/feature/power_loss_recovery.cpp   |  18 +-
 Marlin/src/feature/power_loss_recovery.h     |   4 +-
 Marlin/src/feature/prusa_MMU2/mmu2.cpp       |   2 +-
 Marlin/src/gcode/bedlevel/abl/G29.cpp        |   2 +-
 Marlin/src/gcode/bedlevel/mbl/G29.cpp        |   2 +-
 Marlin/src/gcode/calibrate/M100.cpp          |   2 +-
 Marlin/src/gcode/control/M999.cpp            |   3 +-
 Marlin/src/gcode/gcode.cpp                   |  12 +-
 Marlin/src/gcode/gcode.h                     |   2 +-
 Marlin/src/gcode/host/M110.cpp               |   4 +-
 Marlin/src/gcode/queue.cpp                   | 270 ++++++++-----------
 Marlin/src/gcode/queue.h                     | 204 +++++++-------
 Marlin/src/gcode/sdcard/M28_M29.cpp          |   2 +-
 Marlin/src/lcd/extensible_ui/ui_api.cpp      |   4 +-
 Marlin/src/lcd/malyanlcd.cpp                 |  12 +-
 Marlin/src/lcd/menu/menu.cpp                 |  10 +-
 Marlin/src/lcd/menu/menu.h                   |   4 +-
 Marlin/src/lcd/menu/menu_advanced.cpp        |   4 +-
 Marlin/src/lcd/menu/menu_bed_corners.cpp     |   2 +-
 Marlin/src/lcd/menu/menu_bed_leveling.cpp    |  10 +-
 Marlin/src/lcd/menu/menu_configuration.cpp   |   2 +-
 Marlin/src/lcd/menu/menu_custom.cpp          |   2 +-
 Marlin/src/lcd/menu/menu_delta_calibrate.cpp |   2 +-
 Marlin/src/lcd/menu/menu_filament.cpp        |   2 +-
 Marlin/src/lcd/menu/menu_job_recovery.cpp    |   2 +-
 Marlin/src/lcd/menu/menu_ubl.cpp             |  70 ++---
 Marlin/src/lcd/ultralcd.cpp                  |   8 +-
 Marlin/src/module/planner.h                  |   2 +-
 Marlin/src/sd/cardreader.cpp                 |   6 +-
 32 files changed, 337 insertions(+), 356 deletions(-)

diff --git a/Marlin/src/Marlin.cpp b/Marlin/src/Marlin.cpp
index b21d5593fd..ef07116368 100644
--- a/Marlin/src/Marlin.cpp
+++ b/Marlin/src/Marlin.cpp
@@ -369,7 +369,7 @@ void disable_all_steppers() {
     #endif // HOST_ACTION_COMMANDS
 
     if (run_runout_script)
-      enqueue_and_echo_commands_front_P(PSTR(FILAMENT_RUNOUT_SCRIPT));
+      queue.inject_P(PSTR(FILAMENT_RUNOUT_SCRIPT));
   }
 
 #endif // HAS_FILAMENT_SENSOR
@@ -425,7 +425,7 @@ void manage_inactivity(const bool ignore_stepper_queue/*=false*/) {
     runout.run();
   #endif
 
-  if (commands_in_queue < BUFSIZE) get_available_commands();
+  if (queue.length < BUFSIZE) queue.get_available_commands();
 
   const millis_t ms = millis();
 
@@ -508,7 +508,7 @@ void manage_inactivity(const bool ignore_stepper_queue/*=false*/) {
     const int HOME_DEBOUNCE_DELAY = 2500;
     if (!IS_SD_PRINTING() && !READ(HOME_PIN)) {
       if (!homeDebounceCount) {
-        enqueue_and_echo_commands_P(PSTR("G28"));
+        queue.inject_P(PSTR("G28"));
         LCD_MESSAGEPGM(MSG_AUTO_HOME);
       }
       if (homeDebounceCount < HOME_DEBOUNCE_DELAY)
@@ -797,7 +797,7 @@ void stop() {
   #endif
 
   if (IsRunning()) {
-    Stopped_gcode_LastN = gcode_LastN; // Save last g_code for restart
+    queue.stop();
     SERIAL_ERROR_MSG(MSG_ERR_STOPPED);
     LCD_MESSAGEPGM(MSG_STOPPED);
     safe_delay(350);       // allow enough time for messages to get out before stopping
@@ -926,8 +926,6 @@ void setup() {
   SERIAL_ECHO_START();
   SERIAL_ECHOLNPAIR(MSG_FREE_MEMORY, freeMemory(), MSG_PLANNER_BUFFER_BYTES, (int)sizeof(block_t) * (BLOCK_BUFFER_SIZE));
 
-  queue_setup();
-
   // UI must be initialized before EEPROM
   // (because EEPROM code calls the UI).
   ui.init();
@@ -1135,7 +1133,7 @@ void loop() {
             true
           #endif
         );
-        clear_command_queue();
+        queue.clear();
         quickstop_stepper();
         print_job_timer.stop();
         #if DISABLED(SD_ABORT_NO_COOLDOWN)
@@ -1147,14 +1145,14 @@ void loop() {
           card.removeJobRecoveryFile();
         #endif
         #ifdef EVENT_GCODE_SD_STOP
-          enqueue_and_echo_commands_P(PSTR(EVENT_GCODE_SD_STOP));
+          queue.inject_P(PSTR(EVENT_GCODE_SD_STOP));
         #endif
       }
 
     #endif // SDSUPPORT
 
-    if (commands_in_queue < BUFSIZE) get_available_commands();
-    advance_command_queue();
+    if (queue.length < BUFSIZE) queue.get_available_commands();
+    queue.advance();
     endstops.event_handler();
     idle();
   }
diff --git a/Marlin/src/core/macros.h b/Marlin/src/core/macros.h
index 3805c216fe..2faf9900d1 100644
--- a/Marlin/src/core/macros.h
+++ b/Marlin/src/core/macros.h
@@ -56,7 +56,9 @@
 #define NANOSECONDS_PER_CYCLE (1000000000.0 / F_CPU)
 
 // Remove compiler warning on an unused variable
-#define UNUSED(X) (void)X
+#ifndef UNUSED
+  #define UNUSED(x) ((void)(x))
+#endif
 
 // Macros to make a string from a macro
 #define STRINGIFY_(M) #M
diff --git a/Marlin/src/feature/host_actions.cpp b/Marlin/src/feature/host_actions.cpp
index 1154553e27..6893f2c593 100644
--- a/Marlin/src/feature/host_actions.cpp
+++ b/Marlin/src/feature/host_actions.cpp
@@ -142,7 +142,7 @@ void host_action(const char * const pstr, const bool eol) {
         break;
       case PROMPT_PAUSE_RESUME:
         msg = PSTR("LCD_PAUSE_RESUME");
-        enqueue_and_echo_commands_P(PSTR("M24"));
+        queue.inject_P(PSTR("M24"));
         break;
       case PROMPT_INFO:
         msg = PSTR("GCODE_INFO");
diff --git a/Marlin/src/feature/power_loss_recovery.cpp b/Marlin/src/feature/power_loss_recovery.cpp
index 3605d0e486..6394a611c0 100644
--- a/Marlin/src/feature/power_loss_recovery.cpp
+++ b/Marlin/src/feature/power_loss_recovery.cpp
@@ -98,7 +98,7 @@ void PrintJobRecovery::check() {
     if (card.isDetected()) {
       load();
       if (!valid()) return purge();
-      enqueue_and_echo_commands_P(PSTR("M1000 S"));
+      queue.inject_P(PSTR("M1000 S"));
     }
   }
 }
@@ -209,9 +209,9 @@ void PrintJobRecovery::save(const bool force/*=false*/, const bool save_queue/*=
     info.relative_modes_e = gcode.axis_relative_modes[E_AXIS];
 
     // Commands in the queue
-    info.commands_in_queue = save_queue ? commands_in_queue : 0;
-    info.cmd_queue_index_r = cmd_queue_index_r;
-    COPY(info.command_queue, command_queue);
+    info.queue_length = save_queue ? queue.length : 0;
+    info.queue_index_r = queue.index_r;
+    COPY(info.queue_buffer, queue.buffer);
 
     // Elapsed print job time
     info.print_job_elapsed = print_job_timer.duration();
@@ -402,9 +402,9 @@ void PrintJobRecovery::resume() {
   #endif
 
   // Process commands from the old pending queue
-  uint8_t c = info.commands_in_queue, r = info.cmd_queue_index_r;
+  uint8_t c = info.queue_length, r = info.queue_index_r;
   for (; c--; r = (r + 1) % BUFSIZE)
-    gcode.process_subcommands_now(info.command_queue[r]);
+    gcode.process_subcommands_now(info.queue_buffer[r]);
 
   // Resume the SD file from the last position
   char *fn = info.sd_filename;
@@ -484,9 +484,9 @@ void PrintJobRecovery::resume() {
           DEBUG_EOL();
           DEBUG_ECHOLNPAIR("retract_hop: ", info.retract_hop);
         #endif
-        DEBUG_ECHOLNPAIR("cmd_queue_index_r: ", int(info.cmd_queue_index_r));
-        DEBUG_ECHOLNPAIR("commands_in_queue: ", int(info.commands_in_queue));
-        for (uint8_t i = 0; i < info.commands_in_queue; i++) DEBUG_ECHOLNPAIR("> ", info.command_queue[i]);
+        DEBUG_ECHOLNPAIR("queue_index_r: ", int(info.queue_index_r));
+        DEBUG_ECHOLNPAIR("queue_length: ", int(info.queue_length));
+        for (uint8_t i = 0; i < info.queue_length; i++) DEBUG_ECHOLNPAIR("> ", info.queue_buffer[i]);
         DEBUG_ECHOLNPAIR("sd_filename: ", info.sd_filename);
         DEBUG_ECHOLNPAIR("sdpos: ", info.sdpos);
         DEBUG_ECHOLNPAIR("print_job_elapsed: ", info.print_job_elapsed);
diff --git a/Marlin/src/feature/power_loss_recovery.h b/Marlin/src/feature/power_loss_recovery.h
index e7f8c09124..b7684fccb6 100644
--- a/Marlin/src/feature/power_loss_recovery.h
+++ b/Marlin/src/feature/power_loss_recovery.h
@@ -87,8 +87,8 @@ typedef struct {
   bool relative_mode, relative_modes_e;
 
   // Command queue
-  uint8_t commands_in_queue, cmd_queue_index_r;
-  char command_queue[BUFSIZE][MAX_CMD_SIZE];
+  uint8_t queue_length, queue_index_r;
+  char queue_buffer[BUFSIZE][MAX_CMD_SIZE];
 
   // SD Filename and position
   char sd_filename[MAXPATHNAMELENGTH];
diff --git a/Marlin/src/feature/prusa_MMU2/mmu2.cpp b/Marlin/src/feature/prusa_MMU2/mmu2.cpp
index b3eb60bf5e..0870b168ba 100644
--- a/Marlin/src/feature/prusa_MMU2/mmu2.cpp
+++ b/Marlin/src/feature/prusa_MMU2/mmu2.cpp
@@ -650,7 +650,7 @@ void MMU2::set_filament_type(uint8_t index, uint8_t filamentType) {
 }
 
 void MMU2::filament_runout() {
-  enqueue_and_echo_commands_P(PSTR(MMU2_FILAMENT_RUNOUT_SCRIPT));
+  queue.inject_P(PSTR(MMU2_FILAMENT_RUNOUT_SCRIPT));
   planner.synchronize();
 }
 
diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp
index f43cfa6a35..bcef33ec4a 100644
--- a/Marlin/src/gcode/bedlevel/abl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp
@@ -966,7 +966,7 @@ G29_TYPE GcodeSuite::G29() {
     #ifdef Z_PROBE_END_SCRIPT
       if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Z Probe End Script: ", Z_PROBE_END_SCRIPT);
       planner.synchronize();
-      enqueue_and_echo_commands_P(PSTR(Z_PROBE_END_SCRIPT));
+      queue.inject_P(PSTR(Z_PROBE_END_SCRIPT));
     #endif
 
     // Auto Bed Leveling is complete! Enable if possible.
diff --git a/Marlin/src/gcode/bedlevel/mbl/G29.cpp b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
index dd6faea955..6a073f577e 100644
--- a/Marlin/src/gcode/bedlevel/mbl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
@@ -85,7 +85,7 @@ void GcodeSuite::G29() {
       mbl.reset();
       mbl_probe_index = 0;
       if (!ui.wait_for_bl_move) {
-        enqueue_and_echo_commands_P(PSTR("G28\nG29 S2"));
+        queue.inject_P(PSTR("G28\nG29 S2"));
         return;
       }
       state = MeshNext;
diff --git a/Marlin/src/gcode/calibrate/M100.cpp b/Marlin/src/gcode/calibrate/M100.cpp
index bc3c0fc59b..53eb4c0ace 100644
--- a/Marlin/src/gcode/calibrate/M100.cpp
+++ b/Marlin/src/gcode/calibrate/M100.cpp
@@ -153,7 +153,7 @@ inline int32_t count_test_bytes(const char * const start_free_memory) {
       SERIAL_CHAR('|');                   // Point out non test bytes
       for (uint8_t i = 0; i < 16; i++) {
         char ccc = (char)start_free_memory[i]; // cast to char before automatically casting to char on assignment, in case the compiler is broken
-        if (&start_free_memory[i] >= (char*)command_queue && &start_free_memory[i] < (char*)command_queue + sizeof(command_queue)) { // Print out ASCII in the command buffer area
+        if (&start_free_memory[i] >= (char*)queue.buffer && &start_free_memory[i] < (char*)queue.buffer + sizeof(queue.buffer)) { // Print out ASCII in the command buffer area
           if (!WITHIN(ccc, ' ', 0x7E)) ccc = ' ';
         }
         else { // If not in the command buffer area, flag bytes that don't match the test byte
diff --git a/Marlin/src/gcode/control/M999.cpp b/Marlin/src/gcode/control/M999.cpp
index 1d2dfae733..6f8fe06f0f 100644
--- a/Marlin/src/gcode/control/M999.cpp
+++ b/Marlin/src/gcode/control/M999.cpp
@@ -42,6 +42,5 @@ void GcodeSuite::M999() {
 
   if (parser.boolval('S')) return;
 
-  // gcode_LastN = Stopped_gcode_LastN;
-  flush_and_request_resend();
+  queue.flush_and_request_resend();
 }
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index 8607ae2eb8..98091f78fd 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -187,7 +187,7 @@ void GcodeSuite::dwell(millis_t time) {
 /**
  * Process the parsed command and dispatch it to its handler
  */
-void GcodeSuite::process_parsed_command(const bool no_ok) {
+void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
   KEEPALIVE_STATE(IN_HANDLER);
 
   // Handle a known G, M, or T
@@ -798,7 +798,7 @@ void GcodeSuite::process_parsed_command(const bool no_ok) {
 
   KEEPALIVE_STATE(NOT_BUSY);
 
-  if (!no_ok) ok_to_send();
+  if (!no_ok) queue.ok_to_send();
 }
 
 /**
@@ -806,16 +806,16 @@ void GcodeSuite::process_parsed_command(const bool no_ok) {
  * This is called from the main loop()
  */
 void GcodeSuite::process_next_command() {
-  char * const current_command = command_queue[cmd_queue_index_r];
+  char * const current_command = queue.buffer[queue.index_r];
 
-  PORT_REDIRECT(command_queue_port[cmd_queue_index_r]);
+  PORT_REDIRECT(queue.port[queue.index_r]);
 
   if (DEBUGGING(ECHO)) {
     SERIAL_ECHO_START();
     SERIAL_ECHOLN(current_command);
     #if ENABLED(M100_FREE_MEMORY_DUMPER)
-      SERIAL_ECHOPAIR("slot:", cmd_queue_index_r);
-      M100_dump_routine(PSTR("   Command Queue:"), (const char*)command_queue, (const char*)(command_queue) + sizeof(command_queue));
+      SERIAL_ECHOPAIR("slot:", queue.index_r);
+      M100_dump_routine(PSTR("   Command Queue:"), queue.buffer, queue.buffer + sizeof(queue.buffer));
     #endif
   }
 
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
index 21f4a1cb80..0ec531e550 100644
--- a/Marlin/src/gcode/gcode.h
+++ b/Marlin/src/gcode/gcode.h
@@ -315,7 +315,7 @@ public:
   static void process_parsed_command(const bool no_ok=false);
   static void process_next_command();
 
-  // Execute G-code as a macro, preserving parser state
+  // Execute G-code in-place, preserving current G-code parameters
   static void process_subcommands_now_P(PGM_P pgcode);
   static void process_subcommands_now(char * gcode);
 
diff --git a/Marlin/src/gcode/host/M110.cpp b/Marlin/src/gcode/host/M110.cpp
index 108f414d2b..33e6e67900 100644
--- a/Marlin/src/gcode/host/M110.cpp
+++ b/Marlin/src/gcode/host/M110.cpp
@@ -21,11 +21,11 @@
  */
 
 #include "../gcode.h"
-#include "../queue.h" // for gcode_LastN
+#include "../queue.h" // for last_N
 
 /**
  * M110: Set Current Line Number
  */
 void GcodeSuite::M110() {
-  if (parser.seenval('N')) gcode_LastN = parser.value_long();
+  if (parser.seenval('N')) queue.last_N = parser.value_long();
 }
diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp
index afc76bbe1f..d09f678c3c 100644
--- a/Marlin/src/gcode/queue.cpp
+++ b/Marlin/src/gcode/queue.cpp
@@ -25,6 +25,8 @@
  */
 
 #include "queue.h"
+GCodeQueue queue;
+
 #include "gcode.h"
 
 #include "../lcd/ultralcd.h"
@@ -42,7 +44,7 @@
  * sending commands to Marlin, and lines will be checked for sequentiality.
  * M110 N<int> sets the current line number.
  */
-long gcode_N, gcode_LastN, Stopped_gcode_LastN = 0;
+long gcode_N, GCodeQueue::last_N, GCodeQueue::stopped_N = 0;
 
 /**
  * GCode Command Queue
@@ -53,17 +55,17 @@ long gcode_N, gcode_LastN, Stopped_gcode_LastN = 0;
  * the main loop. The gcode.process_next_command method parses the next
  * command and hands off execution to individual handler functions.
  */
-uint8_t commands_in_queue = 0, // Count of commands in the queue
-        cmd_queue_index_r = 0, // Ring buffer read position
-        cmd_queue_index_w = 0; // Ring buffer write position
+uint8_t GCodeQueue::length = 0,  // Count of commands in the queue
+        GCodeQueue::index_r = 0, // Ring buffer read position
+        GCodeQueue::index_w = 0; // Ring buffer write position
 
-char command_queue[BUFSIZE][MAX_CMD_SIZE];
+char GCodeQueue::buffer[BUFSIZE][MAX_CMD_SIZE];
 
 /*
  * The port that the command was received on
  */
 #if NUM_SERIAL > 1
-  int16_t command_queue_port[BUFSIZE];
+  int16_t GCodeQueue::port[BUFSIZE];
 #endif
 
 /**
@@ -82,7 +84,7 @@ bool send_ok[BUFSIZE];
  */
 static PGM_P injected_commands_P = nullptr;
 
-void queue_setup() {
+GCodeQueue::GCodeQueue() {
   // Send "ok" after commands by default
   for (uint8_t i = 0; i < COUNT(send_ok); i++) send_ok[i] = true;
 }
@@ -90,24 +92,24 @@ void queue_setup() {
 /**
  * Clear the Marlin command queue
  */
-void clear_command_queue() {
-  cmd_queue_index_r = cmd_queue_index_w = commands_in_queue = 0;
+void GCodeQueue::clear() {
+  index_r = index_w = length = 0;
 }
 
 /**
  * Once a new command is in the ring buffer, call this to commit it
  */
-inline void _commit_command(bool say_ok
+void GCodeQueue::_commit_command(bool say_ok
   #if NUM_SERIAL > 1
-    , int16_t port = -1
+    , int16_t p/*=-1*/
   #endif
 ) {
-  send_ok[cmd_queue_index_w] = say_ok;
+  send_ok[index_w] = say_ok;
   #if NUM_SERIAL > 1
-    command_queue_port[cmd_queue_index_w] = port;
+    port[index_w] = p;
   #endif
-  if (++cmd_queue_index_w >= BUFSIZE) cmd_queue_index_w = 0;
-  commands_in_queue++;
+  if (++index_w >= BUFSIZE) index_w = 0;
+  length++;
 }
 
 /**
@@ -115,16 +117,16 @@ inline void _commit_command(bool say_ok
  * Return true if the command was successfully added.
  * Return false for a full buffer, or if the 'command' is a comment.
  */
-inline bool _enqueuecommand(const char* cmd, bool say_ok=false
+bool GCodeQueue::_enqueue(const char* cmd, bool say_ok/*=false*/
   #if NUM_SERIAL > 1
-    , int16_t port = -1
+    , int16_t pn/*=-1*/
   #endif
 ) {
-  if (*cmd == ';' || commands_in_queue >= BUFSIZE) return false;
-  strcpy(command_queue[cmd_queue_index_w], cmd);
+  if (*cmd == ';' || length >= BUFSIZE) return false;
+  strcpy(buffer[index_w], cmd);
   _commit_command(say_ok
     #if NUM_SERIAL > 1
-      , port
+      , pn
     #endif
   );
   return true;
@@ -134,15 +136,15 @@ inline bool _enqueuecommand(const char* cmd, bool say_ok=false
  * Enqueue with Serial Echo
  * Return true if the command was consumed
  */
-bool enqueue_and_echo_command(const char* cmd) {
+bool GCodeQueue::enqueue_one(const char* cmd) {
 
-  //SERIAL_ECHOPGM("enqueue_and_echo_command(\"");
+  //SERIAL_ECHOPGM("enqueue_one(\"");
   //SERIAL_ECHO(cmd);
   //SERIAL_ECHOPGM("\") \n");
 
   if (*cmd == 0 || *cmd == '\n' || *cmd == '\r') return true;
 
-  if (_enqueuecommand(cmd)) {
+  if (_enqueue(cmd)) {
     SERIAL_ECHO_START();
     SERIAL_ECHOLNPAIR(MSG_ENQUEUEING, cmd, "\"");
     return true;
@@ -150,104 +152,67 @@ bool enqueue_and_echo_command(const char* cmd) {
   return false;
 }
 
-#if HAS_QUEUE_FRONT
-
-  bool early_cmd; // = false
-
-  /**
-   * Insert a high Priority command from RAM into the main command buffer.
-   * Return true if the command was consumed
-   * Return false for a full buffer, or if the 'command' is a comment.
-   */
-  inline bool _enqueuecommand_front(const char* cmd) {
-    if (*cmd == 0 || *cmd == '\n' || *cmd == '\r') return true;
-    if (*cmd == ';' || commands_in_queue >= BUFSIZE) return false;
-    if (cmd_queue_index_r == 0) cmd_queue_index_r = BUFSIZE;
-    --cmd_queue_index_r;
-    strcpy(command_queue[cmd_queue_index_r], cmd);
-    send_ok[cmd_queue_index_r] = false;
-    #if NUM_SERIAL > 1
-      command_queue_port[cmd_queue_index_r] = -1;
-    #endif
-    commands_in_queue++;
-    return true;
-  }
-
-  /**
-   * Insert in the front of queue, one or many commands to run from program memory.
-   * Aborts the current queue, if any.
-   * Note: drain_injected_commands_P() must be called repeatedly to drain the commands afterwards
-   */
-  void enqueue_and_echo_commands_front_P(PGM_P const pgcode) {
-    early_cmd = true;
-    enqueue_and_echo_commands_P(pgcode);
-  }
-
-#endif
-
 /**
- * Inject the next "immediate" command, when possible, onto the front of the queue.
- * Return true if any immediate commands remain to inject.
- * Do not inject a comment or use leading space!.
+ * Process the next "immediate" command.
  */
-static bool drain_injected_commands_P() {
-  while (injected_commands_P != nullptr) {
-    size_t i = 0;
-    char c, cmd[60];
-    strncpy_P(cmd, injected_commands_P, sizeof(cmd) - 1);
-    cmd[sizeof(cmd) - 1] = '\0';
-    while ((c = cmd[i]) && c != '\n') i++; // find the end of this gcode command
-    cmd[i] = '\0';
+bool GCodeQueue::process_injected_command() {
+  if (injected_commands_P == nullptr) return false;
 
-    if (
-      #if HAS_QUEUE_FRONT
-        early_cmd ? _enqueuecommand_front(cmd) :
-      #endif
-      enqueue_and_echo_command(cmd)
-    ) {
-      injected_commands_P = c ? injected_commands_P + i + 1 : nullptr; // next command or done
-      #if HAS_QUEUE_FRONT
-        if (!c) early_cmd = false;
-      #endif
-    }
-    else
-      return true; // buffer is full (or command is comment);
-  }
-  return false;   // return whether any more remain
+  char c;
+  size_t i = 0;
+  while ((c = pgm_read_byte(&injected_commands_P[i])) && c != '\n') i++;
+  if (!i) return false;
+
+  char cmd[i + 1];
+  memcpy_P(cmd, injected_commands_P, i);
+  cmd[i] = '\0';
+
+  injected_commands_P = c ? injected_commands_P + i + 1 : nullptr;
+
+  parser.parse(cmd);
+  PORT_REDIRECT(SERIAL_PORT);
+  gcode.process_parsed_command();
+  PORT_RESTORE();
+
+  return true;
 }
 
 /**
  * Enqueue one or many commands to run from program memory.
+ * Do not inject a comment or use leading spaces!
  * Aborts the current queue, if any.
- * Note: drain_injected_commands_P() must be called repeatedly to drain the commands afterwards
+ * Note: process_injected_command() will be called to drain any commands afterwards
  */
-void enqueue_and_echo_commands_P(PGM_P const pgcode) {
-  #if HAS_QUEUE_FRONT
-    early_cmd = false;
-  #endif
+void GCodeQueue::inject_P(PGM_P const pgcode) {
   injected_commands_P = pgcode;
-  (void)drain_injected_commands_P(); // first command executed asap (when possible)
 }
 
-#if HAS_QUEUE_NOW
-  /**
-   * Enqueue and return only when commands are actually enqueued.
-   * Never call this from a G-code handler!
-   */
-  void enqueue_and_echo_command_now(const char* cmd) {
-    while (!enqueue_and_echo_command(cmd)) idle();
+/**
+ * Enqueue and return only when commands are actually enqueued.
+ * Never call this from a G-code handler!
+ */
+void GCodeQueue::enqueue_one_now(const char* cmd) {
+  while (!enqueue_one(cmd)) idle();
+}
+
+/**
+ * Enqueue from program memory and return only when commands are actually enqueued
+ * Never call this from a G-code handler!
+ */
+void GCodeQueue::enqueue_now_P(PGM_P const pgcode) {
+  size_t i = 0;
+  PGM_P p = pgcode;
+  for (;;) {
+    char c;
+    while ((c = p[i]) && c != '\n') i++;
+    char cmd[i + 1];
+    memcpy_P(cmd, p, i);
+    cmd[i] = '\0';
+    enqueue_one_now(cmd);
+    if (!c) break;
+    p += i + 1;
   }
-  #if HAS_LCD_QUEUE_NOW
-    /**
-     * Enqueue from program memory and return only when commands are actually enqueued
-     * Never call this from a G-code handler!
-     */
-    void enqueue_and_echo_commands_now_P(PGM_P const pgcode) {
-      enqueue_and_echo_commands_P(pgcode);
-      while (drain_injected_commands_P()) idle();
-    }
-  #endif
-#endif
+}
 
 /**
  * Send an "ok" message to the host, indicating
@@ -258,16 +223,16 @@ void enqueue_and_echo_commands_P(PGM_P const pgcode) {
  *   P<int>  Planner space remaining
  *   B<int>  Block queue space remaining
  */
-void ok_to_send() {
+void GCodeQueue::ok_to_send() {
   #if NUM_SERIAL > 1
-    const int16_t port = command_queue_port[cmd_queue_index_r];
-    if (port < 0) return;
-    PORT_REDIRECT(port);
+    const int16_t p = port[index_r];
+    if (p < 0) return;
+    PORT_REDIRECT(p);
   #endif
-  if (!send_ok[cmd_queue_index_r]) return;
+  if (!send_ok[index_r]) return;
   SERIAL_ECHOPGM(MSG_OK);
   #if ENABLED(ADVANCED_OK)
-    char* p = command_queue[cmd_queue_index_r];
+    char* p = buffer[index_r];
     if (*p == 'N') {
       SERIAL_ECHO(' ');
       SERIAL_ECHO(*p++);
@@ -275,7 +240,7 @@ void ok_to_send() {
         SERIAL_ECHO(*p++);
     }
     SERIAL_ECHOPGM(" P"); SERIAL_ECHO(int(BLOCK_BUFFER_SIZE - planner.movesplanned() - 1));
-    SERIAL_ECHOPGM(" B"); SERIAL_ECHO(BUFSIZE - commands_in_queue);
+    SERIAL_ECHOPGM(" B"); SERIAL_ECHO(BUFSIZE - length);
   #endif
   SERIAL_EOL();
 }
@@ -284,15 +249,15 @@ void ok_to_send() {
  * Send a "Resend: nnn" message to the host to
  * indicate that a command needs to be re-sent.
  */
-void flush_and_request_resend() {
+void GCodeQueue::flush_and_request_resend() {
   #if NUM_SERIAL > 1
-    const int16_t port = command_queue_port[cmd_queue_index_r];
-    if (port < 0) return;
-    PORT_REDIRECT(port);
+    const int16_t p = port[index_r];
+    if (p < 0) return;
+    PORT_REDIRECT(p);
   #endif
   SERIAL_FLUSH();
   SERIAL_ECHOPGM(MSG_RESEND);
-  SERIAL_ECHOLN(gcode_LastN + 1);
+  SERIAL_ECHOLN(last_N + 1);
   ok_to_send();
 }
 
@@ -315,16 +280,6 @@ inline int read_serial(const uint8_t index) {
   }
 }
 
-void gcode_line_error(PGM_P const err, const int8_t port) {
-  PORT_REDIRECT(port);
-  SERIAL_ERROR_START();
-  serialprintPGM(err);
-  SERIAL_ECHOLN(gcode_LastN);
-  while (read_serial(port) != -1);           // clear out the RX buffer
-  flush_and_request_resend();
-  serial_count[port] = 0;
-}
-
 #if ENABLED(BINARY_FILE_TRANSFER)
 
   inline bool serial_data_available(const uint8_t index) {
@@ -575,6 +530,16 @@ void gcode_line_error(PGM_P const err, const int8_t port) {
 
 #endif // BINARY_FILE_TRANSFER
 
+void GCodeQueue::gcode_line_error(PGM_P const err, const int8_t port) {
+  PORT_REDIRECT(port);
+  SERIAL_ERROR_START();
+  serialprintPGM(err);
+  SERIAL_ECHOLN(last_N);
+  while (read_serial(port) != -1);           // clear out the RX buffer
+  flush_and_request_resend();
+  serial_count[port] = 0;
+}
+
 FORCE_INLINE bool is_M29(const char * const cmd) {  // matches "M29" & "M29 ", but not "M290", etc
   const char * const m29 = strstr_P(cmd, PSTR("M29"));
   return m29 && !NUMERIC(m29[3]);
@@ -585,7 +550,7 @@ FORCE_INLINE bool is_M29(const char * const cmd) {  // matches "M29" & "M29 ", b
  * Exit when the buffer is full or when no more characters are
  * left on the serial port.
  */
-inline void get_serial_commands() {
+void GCodeQueue::get_serial_commands() {
   static char serial_line_buffer[NUM_SERIAL][MAX_CMD_SIZE];
   static bool serial_comment_mode[NUM_SERIAL] = { false }
               #if ENABLED(PAREN_COMMENTS)
@@ -610,7 +575,7 @@ inline void get_serial_commands() {
   #if NO_TIMEOUTS > 0
     static millis_t last_command_time = 0;
     const millis_t ms = millis();
-    if (commands_in_queue == 0 && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
+    if (length == 0 && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
       SERIAL_ECHOLNPGM(MSG_WAIT);
       last_command_time = ms;
     }
@@ -619,7 +584,7 @@ inline void get_serial_commands() {
   /**
    * Loop while serial characters are incoming and the queue is not full
    */
-  while (commands_in_queue < BUFSIZE && serial_data_available()) {
+  while (length < BUFSIZE && serial_data_available()) {
     for (uint8_t i = 0; i < NUM_SERIAL; ++i) {
       int c;
       if ((c = read_serial(i)) < 0) continue;
@@ -659,7 +624,7 @@ inline void get_serial_commands() {
 
           gcode_N = strtol(npos + 1, nullptr, 10);
 
-          if (gcode_N != gcode_LastN + 1 && !M110)
+          if (gcode_N != last_N + 1 && !M110)
             return gcode_line_error(PSTR(MSG_ERR_LINE_NO), i);
 
           char *apos = strrchr(command, '*');
@@ -672,7 +637,7 @@ inline void get_serial_commands() {
           else
             return gcode_line_error(PSTR(MSG_ERR_NO_CHECKSUM), i);
 
-          gcode_LastN = gcode_N;
+          last_N = gcode_N;
         }
         #if ENABLED(SDSUPPORT)
           // Pronterface "M29" and "M29 " has no line number
@@ -718,7 +683,7 @@ inline void get_serial_commands() {
         #endif
 
         // Add the command to the queue
-        _enqueuecommand(serial_line_buffer[i], true
+        _enqueue(serial_line_buffer[i], true
           #if NUM_SERIAL > 1
             , i
           #endif
@@ -760,7 +725,7 @@ inline void get_serial_commands() {
    * or until the end of the file is reached. The special character '#'
    * can also interrupt buffering.
    */
-  inline void get_sdcard_commands() {
+  inline void GCodeQueue::get_sdcard_commands() {
     static bool stop_buffering = false,
                 sd_comment_mode = false
                 #if ENABLED(PAREN_COMMENTS)
@@ -777,11 +742,11 @@ inline void get_serial_commands() {
      * due to checksums, however, no checksums are used in SD printing.
      */
 
-    if (commands_in_queue == 0) stop_buffering = false;
+    if (length == 0) stop_buffering = false;
 
     uint16_t sd_count = 0;
     bool card_eof = card.eof();
-    while (commands_in_queue < BUFSIZE && !card_eof && !stop_buffering) {
+    while (length < BUFSIZE && !card_eof && !stop_buffering) {
       const int16_t n = card.get();
       char sd_char = (char)n;
       card_eof = card.eof();
@@ -804,7 +769,7 @@ inline void get_serial_commands() {
             #if ENABLED(PRINTER_EVENT_LEDS)
               printerEventLEDs.onPrintCompleted();
               #if HAS_RESUME_CONTINUE
-                enqueue_and_echo_commands_P(PSTR("M0 S"
+                inject_P(PSTR("M0 S"
                   #if HAS_LCD_MENU
                     "1800"
                   #else
@@ -828,7 +793,7 @@ inline void get_serial_commands() {
         // Skip empty lines and comments
         if (!sd_count) { thermalManager.manage_heater(); continue; }
 
-        command_queue[cmd_queue_index_w][sd_count] = '\0'; // terminate string
+        buffer[index_w][sd_count] = '\0'; // terminate string
         sd_count = 0; // clear sd line buffer
 
         _commit_command(false);
@@ -849,7 +814,7 @@ inline void get_serial_commands() {
           #if ENABLED(PAREN_COMMENTS)
             && ! sd_comment_paren_mode
           #endif
-        ) command_queue[cmd_queue_index_w][sd_count++] = sd_char;
+        ) buffer[index_w][sd_count++] = sd_char;
       }
     }
   }
@@ -862,10 +827,7 @@ inline void get_serial_commands() {
  *  - The active serial input (usually USB)
  *  - The SD card file being actively printed
  */
-void get_available_commands() {
-
-  // if any immediate commands remain, don't get other commands yet
-  if (drain_injected_commands_P()) return;
+void GCodeQueue::get_available_commands() {
 
   get_serial_commands();
 
@@ -877,14 +839,18 @@ void get_available_commands() {
 /**
  * Get the next command in the queue, optionally log it to SD, then dispatch it
  */
-void advance_command_queue() {
+void GCodeQueue::advance() {
 
-  if (!commands_in_queue) return;
+  // Process immediate commands
+  if (process_injected_command()) return;
+
+  // Return if the G-code buffer is empty
+  if (!length) return;
 
   #if ENABLED(SDSUPPORT)
 
     if (card.flag.saving) {
-      char* command = command_queue[cmd_queue_index_r];
+      char* command = buffer[index_r];
       if (is_M29(command)) {
         // M29 closes the file
         card.closefile();
@@ -921,9 +887,9 @@ void advance_command_queue() {
   #endif // SDSUPPORT
 
   // The queue may be reset by a command handler or by code invoked by idle() within a handler
-  if (commands_in_queue) {
-    --commands_in_queue;
-    if (++cmd_queue_index_r >= BUFSIZE) cmd_queue_index_r = 0;
+  if (length) {
+    --length;
+    if (++index_r >= BUFSIZE) index_r = 0;
   }
 
 }
diff --git a/Marlin/src/gcode/queue.h b/Marlin/src/gcode/queue.h
index 07e164c5f5..49c01f0431 100644
--- a/Marlin/src/gcode/queue.h
+++ b/Marlin/src/gcode/queue.h
@@ -28,109 +28,125 @@
 
 #include "../inc/MarlinConfig.h"
 
-/**
- * GCode line number handling. Hosts may include line numbers when sending
- * commands to Marlin, and lines will be checked for sequentiality.
- * M110 N<int> sets the current line number.
- */
-extern long gcode_LastN, Stopped_gcode_LastN;
-
-/**
- * GCode Command Queue
- * A simple ring buffer of BUFSIZE command strings.
- *
- * Commands are copied into this buffer by the command injectors
- * (immediate, serial, sd card) and they are processed sequentially by
- * the main loop. The gcode.process_next_command method parses the next
- * command and hands off execution to individual handler functions.
- */
-extern uint8_t commands_in_queue, // Count of commands in the queue
-               cmd_queue_index_r; // Ring buffer read position
-
-extern char command_queue[BUFSIZE][MAX_CMD_SIZE];
-
-/*
- * The port that the command was received on
- */
-#if NUM_SERIAL > 1
-  extern int16_t command_queue_port[BUFSIZE];
-#endif
-
-/**
- * Initialization of queue for setup()
- */
-void queue_setup();
-
-/**
- * Clear the Marlin command queue
- */
-void clear_command_queue();
-
-/**
- * Clear the serial line and request a resend of
- * the next expected line number.
- */
-void flush_and_request_resend();
-
-/**
- * Send an "ok" message to the host, indicating
- * that a command was successfully processed.
- *
- * If ADVANCED_OK is enabled also include:
- *   N<int>  Line number of the command, if any
- *   P<int>  Planner space remaining
- *   B<int>  Block queue space remaining
- */
-void ok_to_send();
-
-#if ENABLED(ADVANCED_PAUSE_FEATURE)
+class GCodeQueue {
+public:
   /**
-   * Insert in the front of queue, one or many commands to run from program memory.
-   * Aborts the current queue, if any.
-   * Note: drain_injected_commands_P() must be called repeatedly to drain the commands afterwards
+   * GCode line number handling. Hosts may include line numbers when sending
+   * commands to Marlin, and lines will be checked for sequentiality.
+   * M110 N<int> sets the current line number.
    */
-  void enqueue_and_echo_commands_front_P(PGM_P const pgcode);
-#endif
+  static long last_N, stopped_N;
 
-/**
- * Enqueue one or many commands to run from program memory.
- * Aborts the current queue, if any.
- * Note: drain_injected_commands_P() must be called repeatedly to drain the commands afterwards
- */
-void enqueue_and_echo_commands_P(PGM_P const pgcode);
+  static inline void stop() { stopped_N = last_N; }
 
-/**
- * Enqueue with Serial Echo
- * Return true on success
- */
-bool enqueue_and_echo_command(const char* cmd);
+  /**
+   * GCode Command Queue
+   * A simple ring buffer of BUFSIZE command strings.
+   *
+   * Commands are copied into this buffer by the command injectors
+   * (immediate, serial, sd card) and they are processed sequentially by
+   * the main loop. The gcode.process_next_command method parses the next
+   * command and hands off execution to individual handler functions.
+   */
+  static uint8_t length,  // Count of commands in the queue
+                 index_r; // Ring buffer read position
 
-#define HAS_LCD_QUEUE_NOW (ENABLED(MALYAN_LCD) || (HAS_LCD_MENU && ANY(AUTO_BED_LEVELING_UBL, PID_AUTOTUNE_MENU, ADVANCED_PAUSE_FEATURE)))
-#define HAS_QUEUE_NOW (ENABLED(SDSUPPORT) || HAS_LCD_QUEUE_NOW)
-#define HAS_QUEUE_FRONT ENABLED(ADVANCED_PAUSE_FEATURE)
+  static char buffer[BUFSIZE][MAX_CMD_SIZE];
+
+  /*
+   * The port that the command was received on
+   */
+  #if NUM_SERIAL > 1
+    static int16_t port[BUFSIZE];
+  #endif
+
+  GCodeQueue();
+
+  /**
+   * Clear the Marlin command queue
+   */
+  static void clear();
+
+  /**
+   * Enqueue one or many commands to run from program memory.
+   * Aborts the current queue, if any.
+   * Note: process_injected_command() will process them.
+   */
+  static void inject_P(PGM_P const pgcode);
 
-#if HAS_QUEUE_NOW
   /**
    * Enqueue and return only when commands are actually enqueued
    */
-  void enqueue_and_echo_command_now(const char* cmd);
-  #if HAS_LCD_QUEUE_NOW
-    /**
-     * Enqueue from program memory and return only when commands are actually enqueued
-     */
-    void enqueue_and_echo_commands_now_P(PGM_P const cmd);
+  static void enqueue_one_now(const char* cmd);
+
+  /**
+   * Enqueue from program memory and return only when commands are actually enqueued
+   */
+  static void enqueue_now_P(PGM_P const cmd);
+
+  /**
+   * Get the next command in the queue, optionally log it to SD, then dispatch it
+   */
+  static void advance();
+
+  /**
+   * Add to the circular command queue the next command from:
+   *  - The command-injection queue (injected_commands_P)
+   *  - The active serial input (usually USB)
+   *  - The SD card file being actively printed
+   */
+  static void get_available_commands();
+
+  /**
+   * Send an "ok" message to the host, indicating
+   * that a command was successfully processed.
+   *
+   * If ADVANCED_OK is enabled also include:
+   *   N<int>  Line number of the command, if any
+   *   P<int>  Planner space remaining
+   *   B<int>  Block queue space remaining
+   */
+  static void ok_to_send();
+
+  /**
+   * Clear the serial line and request a resend of
+   * the next expected line number.
+   */
+  static void flush_and_request_resend();
+
+private:
+
+  static uint8_t index_w;  // Ring buffer write position
+
+  static void get_serial_commands();
+
+  #if ENABLED(SDSUPPORT)
+    static void get_sdcard_commands();
   #endif
-#endif
 
-/**
- * Add to the circular command queue the next command from:
- *  - The command-injection queue (injected_commands_P)
- *  - The active serial input (usually USB)
- *  - The SD card file being actively printed
- */
-void get_available_commands();
+  static void _commit_command(bool say_ok
+    #if NUM_SERIAL > 1
+      , int16_t p=-1
+    #endif
+  );
 
-/**
- * Get the next command in the queue, optionally log it to SD, then dispatch it
- */
-void advance_command_queue();
+  static bool _enqueue(const char* cmd, bool say_ok=false
+    #if NUM_SERIAL > 1
+      , int16_t p=-1
+    #endif
+  );
+
+  // Process the next "immediate" command
+  static bool process_injected_command();
+
+  /**
+   * Enqueue with Serial Echo
+   * Return true on success
+   */
+  static bool enqueue_one(const char* cmd);
+
+  static void gcode_line_error(PGM_P const err, const int8_t port);
+
+};
+
+extern GCodeQueue queue;
diff --git a/Marlin/src/gcode/sdcard/M28_M29.cpp b/Marlin/src/gcode/sdcard/M28_M29.cpp
index 815a43ae0c..ae31eec165 100644
--- a/Marlin/src/gcode/sdcard/M28_M29.cpp
+++ b/Marlin/src/gcode/sdcard/M28_M29.cpp
@@ -53,7 +53,7 @@ void GcodeSuite::M28() {
       SERIAL_ECHOLN(p);
       card.openFile(p, false);
       #if NUM_SERIAL > 1
-        card.transfer_port_index = command_queue_port[cmd_queue_index_r];
+        card.transfer_port_index = queue.port[queue.index_r];
       #endif
     }
     else
diff --git a/Marlin/src/lcd/extensible_ui/ui_api.cpp b/Marlin/src/lcd/extensible_ui/ui_api.cpp
index cabed6f322..e89ed2ec64 100644
--- a/Marlin/src/lcd/extensible_ui/ui_api.cpp
+++ b/Marlin/src/lcd/extensible_ui/ui_api.cpp
@@ -777,10 +777,10 @@ namespace ExtUI {
   float getFeedrate_percent() { return feedrate_percentage; }
 
   void enqueueCommands_P(PGM_P const gcode) {
-    enqueue_and_echo_commands_P(gcode);
+    queue.inject_P(gcode);
   }
 
-  bool commandsInQueue() { return (planner.movesplanned() || commands_in_queue); }
+  bool commandsInQueue() { return (planner.movesplanned() || queue.length); }
 
   bool isAxisPositionKnown(const axis_t axis) {
     return TEST(axis_known_position, axis);
diff --git a/Marlin/src/lcd/malyanlcd.cpp b/Marlin/src/lcd/malyanlcd.cpp
index a3462d295d..ea474d0b93 100644
--- a/Marlin/src/lcd/malyanlcd.cpp
+++ b/Marlin/src/lcd/malyanlcd.cpp
@@ -190,8 +190,8 @@ void process_lcd_j_command(const char* command) {
     case 'E':
       // enable or disable steppers
       // switch to relative
-      enqueue_and_echo_commands_now_P(PSTR("G91"));
-      enqueue_and_echo_commands_now_P(steppers_enabled ? PSTR("M18") : PSTR("M17"));
+      queue.enqueue_now_P(PSTR("G91"));
+      queue.enqueue_now_P(steppers_enabled ? PSTR("M18") : PSTR("M17"));
       steppers_enabled = !steppers_enabled;
       break;
     case 'A':
@@ -204,7 +204,7 @@ void process_lcd_j_command(const char* command) {
       // The M200 class UI seems to send movement in .1mm values.
       char cmd[20];
       sprintf_P(cmd, PSTR("G1 %c%03.1f"), axis, atof(command + 1) / 10.0);
-      enqueue_and_echo_command_now(cmd);
+      queue.enqueue_one_now(cmd);
     } break;
     default:
       SERIAL_ECHOLNPAIR("UNKNOWN J COMMAND", command);
@@ -247,7 +247,7 @@ void process_lcd_p_command(const char* command) {
             true
           #endif
         );
-        clear_command_queue();
+        queue.clear();
         quickstop_stepper();
         print_job_timer.stop();
         thermalManager.disable_all_heaters();
@@ -258,7 +258,7 @@ void process_lcd_p_command(const char* command) {
       break;
     case 'H':
       // Home all axis
-      enqueue_and_echo_commands_now_P(PSTR("G28"));
+      queue.enqueue_now_P(PSTR("G28"));
       break;
     default: {
       #if ENABLED(SDSUPPORT)
@@ -321,7 +321,7 @@ void process_lcd_s_command(const char* command) {
 
     case 'H':
       // Home all axis
-      enqueue_and_echo_command("G28");
+      queue.inject_P(PSTR("G28"));
       break;
 
     case 'L': {
diff --git a/Marlin/src/lcd/menu/menu.cpp b/Marlin/src/lcd/menu/menu.cpp
index ff2b5fd6e9..85f876d68e 100644
--- a/Marlin/src/lcd/menu/menu.cpp
+++ b/Marlin/src/lcd/menu/menu.cpp
@@ -99,7 +99,7 @@ void MarlinUI::goto_previous_screen() {
 /////////// Common Menu Actions ////////////
 ////////////////////////////////////////////
 
-void MenuItem_gcode::action(PGM_P pgcode) { enqueue_and_echo_commands_P(pgcode); }
+void MenuItem_gcode::action(PGM_P pgcode) { queue.inject_P(pgcode); }
 
 ////////////////////////////////////////////
 /////////// Menu Editing Actions ///////////
@@ -410,15 +410,15 @@ void MarlinUI::completion_feedback(const bool good/*=true*/) {
 
 #if ANY(AUTO_BED_LEVELING_UBL, PID_AUTOTUNE_MENU, ADVANCED_PAUSE_FEATURE)
 
-  void lcd_enqueue_command(const char * const cmd) {
+  void lcd_enqueue_one_now(const char * const cmd) {
     no_reentry = true;
-    enqueue_and_echo_command_now(cmd);
+    queue.enqueue_one_now(cmd);
     no_reentry = false;
   }
 
-  void lcd_enqueue_commands_P(PGM_P const cmd) {
+  void lcd_enqueue_one_now_P(PGM_P const cmd) {
     no_reentry = true;
-    enqueue_and_echo_commands_now_P(cmd);
+    queue.enqueue_now_P(cmd);
     no_reentry = false;
   }
 
diff --git a/Marlin/src/lcd/menu/menu.h b/Marlin/src/lcd/menu/menu.h
index b70f2e3e3a..8135d2a973 100644
--- a/Marlin/src/lcd/menu/menu.h
+++ b/Marlin/src/lcd/menu/menu.h
@@ -375,8 +375,8 @@ void _lcd_draw_homing();
 #endif
 
 #if ANY(AUTO_BED_LEVELING_UBL, PID_AUTOTUNE_MENU, ADVANCED_PAUSE_FEATURE)
-  void lcd_enqueue_command(const char * const cmd);
-  void lcd_enqueue_commands_P(PGM_P const cmd);
+  void lcd_enqueue_one_now(const char * const cmd);
+  void lcd_enqueue_one_now_P(PGM_P const cmd);
 #endif
 
 #if ENABLED(LEVEL_BED_CORNERS)
diff --git a/Marlin/src/lcd/menu/menu_advanced.cpp b/Marlin/src/lcd/menu/menu_advanced.cpp
index e709eb339a..537fb769c8 100644
--- a/Marlin/src/lcd/menu/menu_advanced.cpp
+++ b/Marlin/src/lcd/menu/menu_advanced.cpp
@@ -101,7 +101,7 @@ void menu_backlash();
   // Set the home offset based on the current_position
   //
   void _lcd_set_home_offsets() {
-    enqueue_and_echo_commands_P(PSTR("M428"));
+    queue.inject_P(PSTR("M428"));
     ui.return_to_status();
   }
 #endif
@@ -255,7 +255,7 @@ void menu_backlash();
         autotune_temp[e]
       #endif
     );
-    lcd_enqueue_command(cmd);
+    lcd_enqueue_one_now(cmd);
   }
 
 #endif // PID_AUTOTUNE_MENU
diff --git a/Marlin/src/lcd/menu/menu_bed_corners.cpp b/Marlin/src/lcd/menu/menu_bed_corners.cpp
index fd90043d0f..43841dd4dc 100644
--- a/Marlin/src/lcd/menu/menu_bed_corners.cpp
+++ b/Marlin/src/lcd/menu/menu_bed_corners.cpp
@@ -120,7 +120,7 @@ void _lcd_level_bed_corners() {
   ui.defer_status_screen();
   if (!all_axes_known()) {
     set_all_unhomed();
-    enqueue_and_echo_commands_P(PSTR("G28"));
+    queue.inject_P(PSTR("G28"));
   }
 
   // Disable leveling so the planner won't mess with us
diff --git a/Marlin/src/lcd/menu/menu_bed_leveling.cpp b/Marlin/src/lcd/menu/menu_bed_leveling.cpp
index e8107aa58a..0d534d86ee 100644
--- a/Marlin/src/lcd/menu/menu_bed_leveling.cpp
+++ b/Marlin/src/lcd/menu/menu_bed_leveling.cpp
@@ -105,9 +105,9 @@
         ui.wait_for_bl_move = true;
         ui.goto_screen(_lcd_level_bed_done);
         #if ENABLED(MESH_BED_LEVELING)
-          enqueue_and_echo_commands_P(PSTR("G29 S2"));
+          queue.inject_P(PSTR("G29 S2"));
         #elif ENABLED(PROBE_MANUALLY)
-          enqueue_and_echo_commands_P(PSTR("G29 V1"));
+          queue.inject_P(PSTR("G29 V1"));
         #endif
       }
       else
@@ -157,9 +157,9 @@
     // G29 Records Z, moves, and signals when it pauses
     ui.wait_for_bl_move = true;
     #if ENABLED(MESH_BED_LEVELING)
-      enqueue_and_echo_commands_P(manual_probe_index ? PSTR("G29 S2") : PSTR("G29 S1"));
+      queue.inject_P(manual_probe_index ? PSTR("G29 S2") : PSTR("G29 S1"));
     #elif ENABLED(PROBE_MANUALLY)
-      enqueue_and_echo_commands_P(PSTR("G29 V1"));
+      queue.inject_P(PSTR("G29 V1"));
     #endif
   }
 
@@ -194,7 +194,7 @@
     ui.defer_status_screen();
     set_all_unhomed();
     ui.goto_screen(_lcd_level_bed_homing);
-    enqueue_and_echo_commands_P(PSTR("G28"));
+    queue.inject_P(PSTR("G28"));
   }
 
 #endif // PROBE_MANUALLY || MESH_BED_LEVELING
diff --git a/Marlin/src/lcd/menu/menu_configuration.cpp b/Marlin/src/lcd/menu/menu_configuration.cpp
index 3e666d9f14..0f17a3ca44 100644
--- a/Marlin/src/lcd/menu/menu_configuration.cpp
+++ b/Marlin/src/lcd/menu/menu_configuration.cpp
@@ -127,7 +127,7 @@ static void lcd_factory_settings() {
 
     auto _recalc_offsets = []{
       if (active_extruder && all_axes_known()) {  // For the 2nd extruder re-home so the next tool-change gets the new offsets.
-        enqueue_and_echo_commands_P(PSTR("G28")); // In future, we can babystep the 2nd extruder (if active), making homing unnecessary.
+        queue.inject_P(PSTR("G28")); // In future, we can babystep the 2nd extruder (if active), making homing unnecessary.
         active_extruder = 0;
       }
     };
diff --git a/Marlin/src/lcd/menu/menu_custom.cpp b/Marlin/src/lcd/menu/menu_custom.cpp
index 9d6cd9e8a1..bc9a23adfb 100644
--- a/Marlin/src/lcd/menu/menu_custom.cpp
+++ b/Marlin/src/lcd/menu/menu_custom.cpp
@@ -38,7 +38,7 @@
 #endif
 
 void _lcd_user_gcode(PGM_P const cmd) {
-  enqueue_and_echo_commands_P(cmd);
+  queue.inject_P(cmd);
   #if ENABLED(USER_SCRIPT_AUDIBLE_FEEDBACK)
     ui.completion_feedback();
   #endif
diff --git a/Marlin/src/lcd/menu/menu_delta_calibrate.cpp b/Marlin/src/lcd/menu/menu_delta_calibrate.cpp
index 6931f32726..54915e12f9 100644
--- a/Marlin/src/lcd/menu/menu_delta_calibrate.cpp
+++ b/Marlin/src/lcd/menu/menu_delta_calibrate.cpp
@@ -73,7 +73,7 @@ void _man_probe_pt(const float &rx, const float &ry) {
   }
 
   void _lcd_delta_calibrate_home() {
-    enqueue_and_echo_commands_P(PSTR("G28"));
+    queue.inject_P(PSTR("G28"));
     ui.goto_screen(_lcd_calibrate_homing);
   }
 
diff --git a/Marlin/src/lcd/menu/menu_filament.cpp b/Marlin/src/lcd/menu/menu_filament.cpp
index 2902d12a00..4d4beb9e1c 100644
--- a/Marlin/src/lcd/menu/menu_filament.cpp
+++ b/Marlin/src/lcd/menu/menu_filament.cpp
@@ -58,7 +58,7 @@ static void _change_filament_temp(const uint16_t temperature) {
   char cmd[11];
   sprintf_P(cmd, _change_filament_temp_command(), _change_filament_temp_extruder);
   thermalManager.setTargetHotend(temperature, _change_filament_temp_extruder);
-  lcd_enqueue_command(cmd);
+  lcd_enqueue_one_now(cmd);
 }
 inline void _lcd_change_filament_temp_1_func()    { _change_filament_temp(ui.preheat_hotend_temp[0]); }
 inline void _lcd_change_filament_temp_2_func()    { _change_filament_temp(ui.preheat_hotend_temp[1]); }
diff --git a/Marlin/src/lcd/menu/menu_job_recovery.cpp b/Marlin/src/lcd/menu/menu_job_recovery.cpp
index f819052977..72fe4cd992 100644
--- a/Marlin/src/lcd/menu/menu_job_recovery.cpp
+++ b/Marlin/src/lcd/menu/menu_job_recovery.cpp
@@ -35,7 +35,7 @@
 
 static void lcd_power_loss_recovery_resume() {
   ui.return_to_status();
-  enqueue_and_echo_commands_P(PSTR("M1000"));
+  queue.inject_P(PSTR("M1000"));
 }
 
 static void lcd_power_loss_recovery_cancel() {
diff --git a/Marlin/src/lcd/menu/menu_ubl.cpp b/Marlin/src/lcd/menu/menu_ubl.cpp
index 2bb9409794..cd8262b611 100644
--- a/Marlin/src/lcd/menu/menu_ubl.cpp
+++ b/Marlin/src/lcd/menu/menu_ubl.cpp
@@ -107,15 +107,15 @@ void lcd_z_offset_edit_setup(const float &initial) {
  * UBL Build Custom Mesh Command
  */
 void _lcd_ubl_build_custom_mesh() {
-  char UBL_LCD_GCODE[20];
-  enqueue_and_echo_commands_P(PSTR("G28"));
+  char ubl_lcd_gcode[20];
+  queue.inject_P(PSTR("G28"));
   #if HAS_HEATED_BED
-    sprintf_P(UBL_LCD_GCODE, PSTR("M190 S%i"), custom_bed_temp);
-    lcd_enqueue_command(UBL_LCD_GCODE);
+    sprintf_P(ubl_lcd_gcode, PSTR("M190 S%i"), custom_bed_temp);
+    lcd_enqueue_one_now(ubl_lcd_gcode);
   #endif
-  sprintf_P(UBL_LCD_GCODE, PSTR("M109 S%i"), custom_hotend_temp);
-  lcd_enqueue_command(UBL_LCD_GCODE);
-  enqueue_and_echo_commands_P(PSTR("G29 P1"));
+  sprintf_P(ubl_lcd_gcode, PSTR("M109 S%i"), custom_hotend_temp);
+  lcd_enqueue_one_now(ubl_lcd_gcode);
+  queue.inject_P(PSTR("G29 P1"));
 }
 
 /**
@@ -141,11 +141,11 @@ void _lcd_ubl_custom_mesh() {
  * UBL Adjust Mesh Height Command
  */
 void _lcd_ubl_adjust_height_cmd() {
-  char UBL_LCD_GCODE[16];
+  char ubl_lcd_gcode[16];
   const int ind = ubl_height_amount > 0 ? 9 : 10;
-  strcpy_P(UBL_LCD_GCODE, PSTR("G29 P6 C -"));
-  sprintf_P(&UBL_LCD_GCODE[ind], PSTR(".%i"), ABS(ubl_height_amount));
-  lcd_enqueue_command(UBL_LCD_GCODE);
+  strcpy_P(ubl_lcd_gcode, PSTR("G29 P6 C -"));
+  sprintf_P(&ubl_lcd_gcode[ind], PSTR(".%i"), ABS(ubl_height_amount));
+  lcd_enqueue_one_now(ubl_lcd_gcode);
 }
 
 /**
@@ -187,7 +187,7 @@ void _lcd_ubl_edit_mesh() {
  * UBL Validate Custom Mesh Command
  */
 void _lcd_ubl_validate_custom_mesh() {
-  char UBL_LCD_GCODE[24];
+  char ubl_lcd_gcode[24];
   const int temp =
     #if HAS_HEATED_BED
       custom_bed_temp
@@ -195,9 +195,9 @@ void _lcd_ubl_validate_custom_mesh() {
       0
     #endif
   ;
-  sprintf_P(UBL_LCD_GCODE, PSTR("G26 C B%i H%i P"), temp, custom_hotend_temp);
-  lcd_enqueue_commands_P(PSTR("G28"));
-  lcd_enqueue_command(UBL_LCD_GCODE);
+  sprintf_P(ubl_lcd_gcode, PSTR("G26 C B%i H%i P"), temp, custom_hotend_temp);
+  lcd_enqueue_one_now_P(PSTR("G28"));
+  lcd_enqueue_one_now(ubl_lcd_gcode);
 }
 
 /**
@@ -228,9 +228,9 @@ void _lcd_ubl_validate_mesh() {
  * UBL Grid Leveling Command
  */
 void _lcd_ubl_grid_level_cmd() {
-  char UBL_LCD_GCODE[12];
-  sprintf_P(UBL_LCD_GCODE, PSTR("G29 J%i"), side_points);
-  lcd_enqueue_command(UBL_LCD_GCODE);
+  char ubl_lcd_gcode[12];
+  sprintf_P(ubl_lcd_gcode, PSTR("G29 J%i"), side_points);
+  lcd_enqueue_one_now(ubl_lcd_gcode);
 }
 
 /**
@@ -269,9 +269,9 @@ void _lcd_ubl_mesh_leveling() {
  * UBL Fill-in Amount Mesh Command
  */
 void _lcd_ubl_fillin_amount_cmd() {
-  char UBL_LCD_GCODE[18];
-  sprintf_P(UBL_LCD_GCODE, PSTR("G29 P3 R C.%i"), ubl_fillin_amount);
-  lcd_enqueue_command(UBL_LCD_GCODE);
+  char ubl_lcd_gcode[18];
+  sprintf_P(ubl_lcd_gcode, PSTR("G29 P3 R C.%i"), ubl_fillin_amount);
+  lcd_enqueue_one_now(ubl_lcd_gcode);
 }
 
 /**
@@ -361,22 +361,22 @@ void _lcd_ubl_build_mesh() {
  * UBL Load Mesh Command
  */
 void _lcd_ubl_load_mesh_cmd() {
-  char UBL_LCD_GCODE[25];
-  sprintf_P(UBL_LCD_GCODE, PSTR("G29 L%i"), ubl_storage_slot);
-  lcd_enqueue_command(UBL_LCD_GCODE);
-  sprintf_P(UBL_LCD_GCODE, PSTR("M117 " MSG_MESH_LOADED), ubl_storage_slot);
-  lcd_enqueue_command(UBL_LCD_GCODE);
+  char ubl_lcd_gcode[25];
+  sprintf_P(ubl_lcd_gcode, PSTR("G29 L%i"), ubl_storage_slot);
+  lcd_enqueue_one_now(ubl_lcd_gcode);
+  sprintf_P(ubl_lcd_gcode, PSTR("M117 " MSG_MESH_LOADED), ubl_storage_slot);
+  lcd_enqueue_one_now(ubl_lcd_gcode);
 }
 
 /**
  * UBL Save Mesh Command
  */
 void _lcd_ubl_save_mesh_cmd() {
-  char UBL_LCD_GCODE[25];
-  sprintf_P(UBL_LCD_GCODE, PSTR("G29 S%i"), ubl_storage_slot);
-  lcd_enqueue_command(UBL_LCD_GCODE);
-  sprintf_P(UBL_LCD_GCODE, PSTR("M117 " MSG_MESH_SAVED), ubl_storage_slot);
-  lcd_enqueue_command(UBL_LCD_GCODE);
+  char ubl_lcd_gcode[25];
+  sprintf_P(ubl_lcd_gcode, PSTR("G29 S%i"), ubl_storage_slot);
+  lcd_enqueue_one_now(ubl_lcd_gcode);
+  sprintf_P(ubl_lcd_gcode, PSTR("M117 " MSG_MESH_SAVED), ubl_storage_slot);
+  lcd_enqueue_one_now(ubl_lcd_gcode);
 }
 
 /**
@@ -420,11 +420,11 @@ void _lcd_ubl_map_homing() {
  * UBL LCD "radar" map point editing
  */
 void _lcd_ubl_map_lcd_edit_cmd() {
-  char UBL_LCD_GCODE[50], str[10], str2[10];
+  char ubl_lcd_gcode[50], str[10], str2[10];
   dtostrf(pgm_read_float(&ubl._mesh_index_to_xpos[x_plot]), 0, 2, str);
   dtostrf(pgm_read_float(&ubl._mesh_index_to_ypos[y_plot]), 0, 2, str2);
-  snprintf_P(UBL_LCD_GCODE, sizeof(UBL_LCD_GCODE), PSTR("G29 P4 X%s Y%s R%i"), str, str2, n_edit_pts);
-  lcd_enqueue_command(UBL_LCD_GCODE);
+  snprintf_P(ubl_lcd_gcode, sizeof(ubl_lcd_gcode), PSTR("G29 P4 X%s Y%s R%i"), str, str2, n_edit_pts);
+  lcd_enqueue_one_now(ubl_lcd_gcode);
 }
 
 /**
@@ -529,7 +529,7 @@ void _lcd_ubl_output_map_lcd() {
 void _lcd_ubl_output_map_lcd_cmd() {
   if (!all_axes_known()) {
     set_all_unhomed();
-    enqueue_and_echo_commands_P(PSTR("G28"));
+    queue.inject_P(PSTR("G28"));
   }
   ui.goto_screen(_lcd_ubl_map_homing);
 }
diff --git a/Marlin/src/lcd/ultralcd.cpp b/Marlin/src/lcd/ultralcd.cpp
index 7a3ea4538f..6dba900cb7 100644
--- a/Marlin/src/lcd/ultralcd.cpp
+++ b/Marlin/src/lcd/ultralcd.cpp
@@ -441,7 +441,7 @@ bool MarlinUI::get_blink() {
 
         #endif // HAS_LCD_MENU
 
-        if (!homed && RRK(EN_KEYPAD_F1)) enqueue_and_echo_commands_P(PSTR("G28"));
+        if (!homed && RRK(EN_KEYPAD_F1)) queue.inject_P(PSTR("G28"));
         return true;
       }
 
@@ -1461,9 +1461,9 @@ void MarlinUI::update() {
       #if HAS_SPI_LCD
         lcd_pause_show_message(PAUSE_MESSAGE_PAUSING, PAUSE_MODE_PAUSE_PRINT);  // Show message immediately to let user know about pause in progress
       #endif
-      enqueue_and_echo_commands_front_P(PSTR("M25 P\nM24"));
+      queue.inject_P(PSTR("M25 P\nM24"));
     #elif ENABLED(SDSUPPORT)
-      enqueue_and_echo_commands_P(PSTR("M25"));
+      queue.inject_P(PSTR("M25"));
     #elif defined(ACTION_ON_PAUSE)
       host_action_pause();
     #endif
@@ -1475,7 +1475,7 @@ void MarlinUI::update() {
       wait_for_heatup = wait_for_user = false;
     #endif
     #if ENABLED(SDSUPPORT)
-      if (card.isPaused()) enqueue_and_echo_commands_P(PSTR("M24"));
+      if (card.isPaused()) queue.inject_P(PSTR("M24"));
     #endif
     #ifdef ACTION_ON_RESUME
       host_action_resume();
diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h
index bebcf113a3..5bcb37f39a 100644
--- a/Marlin/src/module/planner.h
+++ b/Marlin/src/module/planner.h
@@ -735,7 +735,7 @@ class Planner {
       if (cleaning_buffer_counter) {
         --cleaning_buffer_counter;
         #if ENABLED(SD_FINISHED_STEPPERRELEASE) && defined(SD_FINISHED_RELEASECOMMAND)
-          if (!cleaning_buffer_counter) enqueue_and_echo_commands_P(PSTR(SD_FINISHED_RELEASECOMMAND));
+          if (!cleaning_buffer_counter) queue.inject_P(PSTR(SD_FINISHED_RELEASECOMMAND));
         #endif
       }
     }
diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp
index 5a17c0633a..c7a97a2705 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -367,8 +367,8 @@ void CardReader::openAndPrintFile(const char *name) {
   char cmd[4 + strlen(name) + 1]; // Room for "M23 ", filename, and null
   sprintf_P(cmd, PSTR("M23 %s"), name);
   for (char *c = &cmd[4]; *c; c++) *c = tolower(*c);
-  enqueue_and_echo_command_now(cmd);
-  enqueue_and_echo_commands_P(PSTR("M24"));
+  queue.enqueue_one_now(cmd);
+  queue.inject_P(PSTR("M24"));
 }
 
 void CardReader::startFileprint() {
@@ -1001,7 +1001,7 @@ void CardReader::printingHasFinished() {
     #endif
 
     print_job_timer.stop();
-    if (print_job_timer.duration() > 60) enqueue_and_echo_commands_P(PSTR("M31"));
+    if (print_job_timer.duration() > 60) queue.inject_P(PSTR("M31"));
 
     #if ENABLED(SDCARD_SORT_ALPHA)
       presort();