From 8b21eff5d0bf4eaf561e73f518acc10ac33bed12 Mon Sep 17 00:00:00 2001 From: uGuardian Date: Thu, 7 Jul 2022 09:23:52 -0600 Subject: [PATCH 1/4] Added support for quick hotkeys --- Opus/App.config | 1 + Opus/UI/Rendering/InstructionRenderer.cs | 28 ++++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Opus/App.config b/Opus/App.config index ce22e30..f616e78 100644 --- a/Opus/App.config +++ b/Opus/App.config @@ -9,6 +9,7 @@ + diff --git a/Opus/UI/Rendering/InstructionRenderer.cs b/Opus/UI/Rendering/InstructionRenderer.cs index 3b3cbf4..7daf84c 100644 --- a/Opus/UI/Rendering/InstructionRenderer.cs +++ b/Opus/UI/Rendering/InstructionRenderer.cs @@ -25,6 +25,8 @@ public class InstructionRenderer { Instruction.Repeat, Keys.V } }; + public static bool QuickHotkeysEnabled { get; set; } = false; + public InstructionRenderer(ProgramGrid grid) { m_grid = grid; @@ -42,15 +44,27 @@ public void Render(Vector2 position, Instruction instruction, int delay = 0) var gridLocation = m_grid.GetCellLocation(position); int keyTime = delay; - int clickTime = delay + 50; + int clickTime = delay + 50; - KeyDown(key); - MouseUtils.SetCursorPosition(gridLocation); - ThreadUtils.SleepOrAbort(keyTime); - MouseUtils.LeftClick(clickTime); + if (!QuickHotkeysEnabled) + { + KeyDown(key); + MouseUtils.SetCursorPosition(gridLocation); + ThreadUtils.SleepOrAbort(keyTime); + MouseUtils.LeftClick(clickTime); - KeyUp(key); - ThreadUtils.SleepOrAbort(keyTime); + KeyUp(key); + ThreadUtils.SleepOrAbort(keyTime); + } + else + { + MouseUtils.SetCursorPosition(gridLocation); + ThreadUtils.SleepOrAbort(keyTime); + KeyDown(key); + ThreadUtils.SleepOrAbort(keyTime); + KeyUp(key); + ThreadUtils.SleepOrAbort(keyTime); + } } } } From dd9158f23d0eee6cee30509d35d4f2f57fddebed Mon Sep 17 00:00:00 2001 From: uGuardian Date: Thu, 7 Jul 2022 09:40:47 -0600 Subject: [PATCH 2/4] Added track laying optimization --- Opus/UI/HexGrid.cs | 44 +++++++++++++++++++++ Opus/UI/Rendering/SolutionRenderer.cs | 46 +++++++++++++++++----- Opus/UI/ScrollableArea.cs | 56 +++++++++++++++++++++++++++ Opus/Utils/MouseUtils.cs | 31 +++++++++++++-- 4 files changed, 164 insertions(+), 13 deletions(-) diff --git a/Opus/UI/HexGrid.cs b/Opus/UI/HexGrid.cs index f20ba08..678d26e 100644 --- a/Opus/UI/HexGrid.cs +++ b/Opus/UI/HexGrid.cs @@ -136,6 +136,50 @@ public void EnsureCellsVisible(Bounds bounds, bool scrollToCenter = false) } } + /// + /// Checks if scrolling the grid if necessary so that the cells in the coordinates given by bounds + /// are completely visible. + /// + public bool CheckCellsVisible(Vector2 position, out Rectangle scrollTargetRect, bool scrollToCenter = false) + { + return CheckCellsVisible(new Bounds(position, position), out scrollTargetRect, scrollToCenter); + } + + /// + /// Checks if scrolling the grid if necessary so that the cells in the coordinates given by bounds + /// are completely visible. + /// + public bool CheckCellsVisible(Bounds bounds, out Rectangle scrollTargetRect, bool scrollToCenter = false) + { + var bottomLeft = GetCellLocationLocal(bounds.Min); + var topRight = GetCellLocationLocal(bounds.Max.Add(new Vector2(0, 1))); // Add (0, 1) so we avoid the exit button in the top-right corner of the screen + scrollTargetRect = new Rectangle(bottomLeft.X - HexWidth / 2, topRight.Y - HexHeight / 2, + topRight.X - bottomLeft.X + HexWidth, bottomLeft.Y - topRight.Y + HexHeight); + if (scrollToCenter) + { + return m_area.CheckScrollToCenterIfNecessary(scrollTargetRect); + } + else + { + return m_area.CheckScrollMinimalIfNecessary(scrollTargetRect); + } + } + + /// + /// Applies scrolling to a specific rect. + /// + public void ApplyCellsVisible(Rectangle rect, bool scrollToCenter = false) + { + if (scrollToCenter) + { + m_area.ScrollToCenterIfNecessary(rect); + } + else + { + m_area.ScrollMinimalIfNecessary(rect); + } + } + /// /// Scrolls the grid so that the center of the cell at the specified coordinates is in the middle of the grid. /// diff --git a/Opus/UI/Rendering/SolutionRenderer.cs b/Opus/UI/Rendering/SolutionRenderer.cs index 77aae4d..df0c797 100644 --- a/Opus/UI/Rendering/SolutionRenderer.cs +++ b/Opus/UI/Rendering/SolutionRenderer.cs @@ -2,6 +2,7 @@ using System.Windows.Forms; using Opus.Solution; using static System.FormattableString; +using System.Drawing; namespace Opus.UI.Rendering { @@ -68,11 +69,15 @@ private void RenderTracks() var objects = Solution.GetObjects(); foreach (var obj in objects) { + // To minimize scrollling, first try to get as much as possible of the path visible + Screen.Grid.EnsureCellsVisible(obj.GetBounds()); + var pos = obj.GetWorldPosition(); Screen.Grid.EnsureCellVisible(pos); var toolLocation = Sidebar.ScrollTo(Sidebar.Mechanisms, Sidebar.Mechanisms[obj.Type]); var gridLocation = Screen.Grid.GetScreenLocationForCell(pos); + MouseUtils.LeftDrag(toolLocation, gridLocation); RenderTrackPath(obj); @@ -84,22 +89,43 @@ private void RenderTrackPath(Track track) var trackPos = track.GetWorldPosition(); sm_log.Info(Invariant($"Rendering track at {trackPos}")); - // To minimize scrollling, first try to get as much as possible of the path visible - Screen.Grid.EnsureCellsVisible(track.GetBounds()); - + var position = trackPos.Add(track.Path.Skip(1).First()); var prevPosition = trackPos; - foreach (var offset in track.Path.Skip(1)) + Screen.Grid.EnsureCellVisible(position, scrollToCenter: true); // scroll to center to minimise number of scrolls if it's a long track + + Point location = Screen.Grid.GetScreenLocationForCell(trackPos.Add(track.Path.Skip(1).First())); + Point prevLocation = Screen.Grid.GetScreenLocationForCell(prevPosition); + Rectangle checkRect; + + MouseUtils.LeftDrag(prevLocation, location, keepMouseDown: true); + + prevPosition = position; + + foreach (var offset in track.Path.Skip(2)) { - var position = trackPos.Add(offset); - Screen.Grid.EnsureCellVisible(position, scrollToCenter: true); // scroll to center to minimise number of scrolls if it's a long track + position = trackPos.Add(offset); + var scrollCheck = Screen.Grid.CheckCellsVisible(position, out checkRect, scrollToCenter: true); // scroll to center to minimise number of scrolls if it's a long track - // We need to recalculate both locations because the grid may have scrolled - var location = Screen.Grid.GetScreenLocationForCell(position); - var prevLocation = Screen.Grid.GetScreenLocationForCell(prevPosition); - MouseUtils.LeftDrag(prevLocation, location); + if (scrollCheck) + { + // Release mouse and then scroll; + MouseUtils.LeftUp(); + Screen.Grid.ApplyCellsVisible(checkRect, scrollToCenter: true); + // We need to recalculate both locations because the grid has scrolled + prevLocation = Screen.Grid.GetScreenLocationForCell(prevPosition); + } + else + { + prevLocation = location; + } + location = Screen.Grid.GetScreenLocationForCell(position); + MouseUtils.LeftDrag(prevLocation, location, keepMouseDown: true); prevPosition = position; } + + // Release the mouse when done. + MouseUtils.LeftUp(); } private void RenderArms() diff --git a/Opus/UI/ScrollableArea.cs b/Opus/UI/ScrollableArea.cs index 3e6c485..707d78d 100644 --- a/Opus/UI/ScrollableArea.cs +++ b/Opus/UI/ScrollableArea.cs @@ -156,6 +156,52 @@ public void ScrollMinimalIfNecessary(Rectangle targetRect) ScrollTo(targetLocation); } + /// + /// Checks if scrolling is necessary so that the target rectangle is completely visible on the screen. + /// Doesn't scroll unnecessarily in one direction if it's not required. + /// + public bool CheckScrollToCenterIfNecessary(Rectangle targetRect) + { + if (targetRect.Left < m_scrollPosition.X || targetRect.Right > m_scrollPosition.X + Rect.Width) + { + return true; + } + + if (targetRect.Top < m_scrollPosition.Y || targetRect.Bottom > m_scrollPosition.Y + Rect.Height) + { + return true; + } + + return false; + } + + /// + /// Checks if scrolling is necessary so that the target rectangle is completely visible on the screen. + /// Doesn't scroll unnecessarily in one direction if it's not required. + /// + public bool CheckScrollMinimalIfNecessary(Rectangle targetRect) + { + if (targetRect.Left < m_scrollPosition.X) + { + return true; + } + else if (targetRect.Right > m_scrollPosition.X + Rect.Width) + { + return true; + } + + if (targetRect.Top < m_scrollPosition.Y) + { + return true; + } + else if (targetRect.Bottom > m_scrollPosition.Y + Rect.Height) + { + return true; + } + + return false; + } + /// /// Scrolls the area by the specified amount (relative to the current scroll position). /// @@ -178,6 +224,11 @@ public void ScrollTo(Point targetLocation) } var delta = targetLocation.Subtract(m_scrollPosition); + if (delta == new Point(0, 0)) + { + return; + } + if (VerticalCheckRect.HasValue) { // Scroll X and Y separately to reduce chances of errors @@ -190,6 +241,11 @@ public void ScrollTo(Point targetLocation) } } + /// + /// Checks if scrolling is neccecary. + /// + public bool ScrollCheck(Point targetLocation) => targetLocation.Subtract(m_scrollPosition) == new Point(0, 0); + /// /// Scrolls the area by the specified amount (relative to the current scroll position). /// diff --git a/Opus/Utils/MouseUtils.cs b/Opus/Utils/MouseUtils.cs index 5f838cd..ea15e3c 100644 --- a/Opus/Utils/MouseUtils.cs +++ b/Opus/Utils/MouseUtils.cs @@ -24,6 +24,8 @@ public static class MouseUtils { private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(MouseUtils)); + static MouseEvent currentEvent; + public static int GlobalDragDelay { get; set; } = 0; static MouseUtils() @@ -53,6 +55,7 @@ public static Point GetCursorPosition() public static void SendMouseEvent(MouseEvent flags) { + currentEvent = flags; var point = GetCursorPosition(); NativeMethods.mouse_event((int)flags, point.X, point.Y, 0, IntPtr.Zero); } @@ -74,6 +77,25 @@ public static void RightDrag(Point start, Point end, int delay = 0, bool keepMou Drag(MouseEvent.RightDown, MouseEvent.RightUp, start, end, delay, keepMouseDown); } + public static void LeftUp(int delay = 0) + { + if (currentEvent != MouseEvent.LeftUp) + { + int delayAfterMouse = GlobalDragDelay + delay + 50; + ThreadUtils.SleepOrAbort(delayAfterMouse); + SendMouseEvent(MouseEvent.LeftUp); + } + } + public static void LeftDown(int delay = 0) + { + if (currentEvent != MouseEvent.LeftDown) + { + int delayAfterMouse = GlobalDragDelay + delay + 50; + ThreadUtils.SleepOrAbort(delayAfterMouse); + SendMouseEvent(MouseEvent.LeftDown); + } + } + public static void Drag(MouseEvent startFlags, MouseEvent endFlags, Point start, Point end, int delay = 0, bool keepMouseDown = false) { sm_log.Info(Invariant($"Dragging from {start} to {end}; startFlags: {startFlags}; endFlags: {endFlags}")); @@ -84,13 +106,16 @@ public static void Drag(MouseEvent startFlags, MouseEvent endFlags, Point start, SetCursorPosition(start); ThreadUtils.SleepOrAbort(delayAfterMove); - SendMouseEvent(startFlags); - ThreadUtils.SleepOrAbort(delayAfterMouse); + if (startFlags != currentEvent) + { + SendMouseEvent(startFlags); + ThreadUtils.SleepOrAbort(delayAfterMouse); + } SetCursorPosition(end); ThreadUtils.SleepOrAbort(delayAfterMove); - if (!keepMouseDown) + if (!keepMouseDown && endFlags != currentEvent) { SendMouseEvent(endFlags); ThreadUtils.SleepOrAbort(delayAfterMouse); From 3a1a8e50f66da0b34ae37adb4ff97ee8db5896cc Mon Sep 17 00:00:00 2001 From: uGuardian Date: Thu, 7 Jul 2022 09:41:38 -0600 Subject: [PATCH 3/4] No longer increases delay on first retry to account for hiccups --- Opus/UI/Rendering/ProgramRenderer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Opus/UI/Rendering/ProgramRenderer.cs b/Opus/UI/Rendering/ProgramRenderer.cs index 4cd54bd..5a6889d 100644 --- a/Opus/UI/Rendering/ProgramRenderer.cs +++ b/Opus/UI/Rendering/ProgramRenderer.cs @@ -172,8 +172,12 @@ private void EnsureRowCorrect(int startTime, int endTime, int armIndex, List 1) + { + // First retry did not work, increase render delay + m_renderDelay += 10; + sm_log.Info("Increasing render delay to " + m_renderDelay); + } foreach (int timeIndex in errors) { From 582bd0440b4cce80a8c527b632a7bffcc8351d9a Mon Sep 17 00:00:00 2001 From: uGuardian Date: Thu, 7 Jul 2022 12:08:50 -0600 Subject: [PATCH 4/4] Optimized multiple back to back copies and tweaked error delay increases --- Opus/UI/Rendering/ProgramRenderer.cs | 74 ++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/Opus/UI/Rendering/ProgramRenderer.cs b/Opus/UI/Rendering/ProgramRenderer.cs index 5a6889d..6e6121e 100644 --- a/Opus/UI/Rendering/ProgramRenderer.cs +++ b/Opus/UI/Rendering/ProgramRenderer.cs @@ -20,6 +20,9 @@ public class ProgramRenderer private InstructionRenderer m_instructionRenderer; private int m_renderDelay = 10; + private List lastCopy; + private bool lastRowErrored = false; + public ProgramRenderer(ProgramGrid grid, Program program, IEnumerable arms) { m_grid = grid; @@ -62,6 +65,8 @@ private void RenderRow(int startTime, int endTime, int armIndex, List 1) { m_grid.EnsureCellsVisible(new Vector2(startTime, armIndex - 1), new Vector2(numToCopy, 2)); - CopyInstructionsFromPrevious(timeIndex, numToCopy, armIndex); + CopyInstructionsFromPrevious(timeIndex, numToCopy, armIndex, out chainCopy, copyList); + + if (chainCopy > 0) + { + chainCopy = numToCopy; + } timeIndex += numToCopy - 1; } else { + lastCopy = null; m_instructionRenderer.Render(new Vector2(timeIndex, armIndex), instructions[timeIndex], m_renderDelay); } } - EnsureRowCorrect(startTime, endTime, armIndex, instructions); + EnsureRowCorrect(startTime, endTime, armIndex, instructions, chainCopy); } /// /// Finds the number of instructions that are the same between the specified arm and the /// previous arm, starting at startTime. /// - private int FindCopyableInstructions(int startTime, int endTime, int armIndex) + private int FindCopyableInstructions(int startTime, int endTime, int armIndex, out List copyList) { + copyList = null; + int result = 0; if (armIndex == 0) { - return 0; + return result; } var instructions = m_program.GetArmInstructions(m_arms[armIndex]); @@ -119,24 +132,39 @@ private int FindCopyableInstructions(int startTime, int endTime, int armIndex) int lastRenderable = instructions.FindLastIndex(lastSame, lastSame - startTime + 1, i => i.IsRenderable()); if (lastRenderable < 0) { - return 0; + return result; } else { - return lastRenderable - startTime + 1; + result = lastRenderable - startTime + 1; + copyList = instructions.GetRange(startTime, result); + return result; } } - return lastSame - startTime + 1; + result = lastSame - startTime + 1; + copyList = instructions.GetRange(startTime, result); + return result; } - private void CopyInstructionsFromPrevious(int timeIndex, int width, int armIndex) + private void CopyInstructionsFromPrevious(int timeIndex, int width, int armIndex, out int chainCopy, List copyList = null) { + chainCopy = 0; + // Select the instructions to copy var sourcePos = new Vector2(timeIndex, armIndex - 1); - var dragStart = m_grid.GetCellLocation(new Vector2(timeIndex + width - 1, armIndex)); var dragEnd = m_grid.GetCellLocation(sourcePos); - MouseUtils.LeftDrag(dragStart, dragEnd); + + if (lastCopy == null || copyList == null || !copyList.SequenceEqual(lastCopy)) + { + var dragStart = m_grid.GetCellLocation(new Vector2(timeIndex + width - 1, armIndex)); + MouseUtils.LeftDrag(dragStart, dragEnd); + lastCopy = copyList; + } + else + { + chainCopy = 1; + } // Copy them KeyDown(Keys.ControlKey); @@ -148,7 +176,7 @@ private void CopyInstructionsFromPrevious(int timeIndex, int width, int armIndex ThreadUtils.SleepOrAbort(m_renderDelay); } - private void EnsureRowCorrect(int startTime, int endTime, int armIndex, List instructions) + private void EnsureRowCorrect(int startTime, int endTime, int armIndex, List instructions, int chainCopy = 0) { const int maxRetries = 5; int retryCount = 0; @@ -157,10 +185,16 @@ private void EnsureRowCorrect(int startTime, int endTime, int armIndex, List 0) + { + m_grid.EnsureCellsVisible(new Vector2(startTime, armIndex - 1), new Vector2(chainCopy, 2)); + CopyInstructionsFromPrevious(timeIndex, chainCopy, armIndex, out chainCopy); + } + else + { + m_instructionRenderer.Render(new Vector2(timeIndex, armIndex), instructions[timeIndex], m_renderDelay); + } } errors = FindErrors(startTime, endTime, armIndex, instructions); } + if (retryCount == 0) + { + lastRowErrored = false; + } } private IEnumerable FindErrors(int startTime, int endTime, int armIndex, List instructions)