#region ================== Copyright (c) 2022 Boris Iwanski /* * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the * * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program.If not, see. */ #endregion #region ================== Namespaces using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using CodeImp.DoomBuilder.Windows; #endregion namespace CodeImp.DoomBuilder.UDBScript { public partial class ScriptRunnerForm : DelayedForm { #region ================== Constants /// /// How long a script is allowed to run until the form is made visible. /// const int RUNTIME_THRESHOLD = 1000; #endregion #region ================== Variables /// /// Cancellation token for stopping the script. /// CancellationTokenSource cancellationtokensource; /// /// If script is currently executed or not. /// bool running; /// /// How many milliseconds the script has been running /// double runningseconds; /// /// Determines if the form should be automatically closed when the script is finished. /// bool autoclose; /// /// Stopwatch used to determine how long the script is already running. /// Stopwatch stopwatch; /// /// Timer for making the form visible when the script is running for too long. /// System.Windows.Forms.Timer timer; #endregion #region ================== Methods public ScriptRunnerForm() { InitializeComponent(); } /// /// Invokes a method, but stops the timer before running the code, and starting the timer after the code ran. /// /// Method to invoke /// Return value of the method public object InvokePaused(Delegate method) { if (InvokeRequired) { return Invoke(new Func(() => InvokePaused(method))); } else { stopwatch.Stop(); object result = Invoke(method); stopwatch.Start(); return result; } } /// /// Invokes a method. /// /// Method to invoke public void RunAction(Action action) { if (InvokeRequired) Invoke(action); else action(); } /// /// Sets the value of the progress bar (in the range from 0 to 100). /// /// Value of the progress bar (in the range from 0 to 100) private void SetProgress(int value) { if (progressbar.Style != ProgressBarStyle.Continuous) progressbar.Style = ProgressBarStyle.Continuous; // Do some trickery to remove the movement of the progress bar, since it can // otherwise screw with how much the progress bar is filled if (progressbar.Value != value) { if (value > progressbar.Maximum) value = progressbar.Maximum; else if (value < progressbar.Minimum) value = progressbar.Minimum; if (progressbar.Maximum == value) { progressbar.Value = value; progressbar.Value = value - 1; } else { progressbar.Value = value + 1; } progressbar.Value = value; } // Make the form visible so that the user can actually see the progress bar MakeVisible(); } private void SetProgressStatus(string status) { lbStatus.Text = status; } private void Log(string text) { // If there's something in the log we don't want to automatically // close the form, otherwise the user could not read the contents autoclose = false; // Since we don't want to have a useless line at the end of the textbox // we add a new line before adding the new text (unless there's no text // at all yet, then we don't add a new line if (!string.IsNullOrEmpty(tbLog.Text)) tbLog.AppendText(Environment.NewLine); // Add the new text tbLog.AppendText(text); // Make the form visible so that the user can actually see the status bar MakeVisible(); } private async void RunScript(CancellationToken cancellationtoken) { // Callbacks for setting the progress bar, status text, and adding log lines from the script Progress progress = new Progress(SetProgress); Progress status = new Progress(SetProgressStatus); Progress log = new Progress(Log); running = true; // Prepare running the script BuilderPlug.Me.ScriptRunner.PreRun(cancellationtoken, progress, status, log); try { await Task.Run(() => BuilderPlug.Me.ScriptRunner.Run()); stopwatch.Stop(); } catch (Exception ex) { stopwatch.Stop(); BuilderPlug.Me.ScriptRunner.HandleExceptions(ex); } // Clean up and update after running the script BuilderPlug.Me.ScriptRunner.PostRun(); running = false; Text = "Script finished"; lbStatus.Text = "Script finished. Runtime: " + BuilderPlug.Me.ScriptRunner.GetRuntimeString(); btnAction.Text = "Close"; btnAction.Enabled = true; SetProgress(0); // Stop the progress bar from animating when the script finished if (progressbar.Style == ProgressBarStyle.Marquee) progressbar.Style = ProgressBarStyle.Continuous; if (autoclose) { MakeInvisible(); //Hide(); Close(); } } /// /// Makes the form visible. /// private void MakeVisible() { Opacity = 1.0; btnAction.Enabled = true; } /// /// Makes the form invisible. /// private void MakeInvisible() { Opacity = 0.0; } #endregion #region ================== Events /// /// Cancels the currently running script, or closes the form if no script is running. /// /// The sender /// Event arguments private void btnAction_Click(object sender, EventArgs e) { if (running) { btnAction.Enabled = false; cancellationtokensource.Cancel(); } else { MakeInvisible(); //Hide(); Close(); } } /// /// Sets everything up for running the script, and then immediately runs the script. /// /// The sender /// Event arguments private void ScriptRunnerForm_Shown(object sender, EventArgs e) { cancellationtokensource = new CancellationTokenSource(); autoclose = true; runningseconds = 0; progressbar.Value = 0; progressbar.Style = ProgressBarStyle.Marquee; Text = "Running script"; lbStatus.Text = "Running script..."; btnAction.Text = "Cancel"; // Disable the button because it could otherwise be pressed while the form is invisible. // It'll be enabled as soon as the form is made visible btnAction.Enabled = false; tbLog.Clear(); // The timer ticks ever 100ms. The method it runs checks how long the script is running // and makes the form visible if the runtime threshold has been reached timer = new System.Windows.Forms.Timer(); timer.Interval = 100; timer.Tick += timerShow_Tick; timer.Start(); // This stopwatch is used to measure how long the script has been running stopwatch = new Stopwatch(); stopwatch.Start(); // Start running the script RunScript(cancellationtokensource.Token); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); MakeInvisible(); } /// /// Makes the form visible if the runtime threshold has been reached. Shows the elapsed time the script is running. /// /// The sender /// Event arguments private void timerShow_Tick(object sender, EventArgs e) { if (Opacity == 0.0 && stopwatch.ElapsedMilliseconds > 1000) { MakeVisible(); } double newrunningsecods = Math.Floor(stopwatch.Elapsed.TotalSeconds); if(newrunningsecods > runningseconds) { runningseconds = newrunningsecods; Text = "Running script (" + string.Format("{0:D2}:{1:D2}:{2:D2}", stopwatch.Elapsed.Hours, stopwatch.Elapsed.Minutes, stopwatch.Elapsed.Seconds) + ")"; } } private void ScriptRunnerForm_FormClosed(object sender, FormClosedEventArgs e) { timer.Stop(); } #endregion } }