mirror of
https://github.com/fadden/6502bench.git
synced 2024-12-11 13:50:13 +00:00
1da98d8628
Generation of HTML is extremely fast, but compressing thousands of frames for wireframe animated GIFs can take a little while. Sharing bitmaps between threads required two changes: (1) bitmaps need to be "frozen" after being drawn; (2) you can't use Path because BackgroundWorker isn't a STAThread. You can, however, use a DrawingVisual / DrawingContext to do the rendering. Which is really what I should have been doing all along; I just didn't know the approach existed until I was forced to go looking for it. Also, we now do a "run finalizers" call before generating an animated GIF. Without it things explode after more than 10K GDI objects have been allocated.
410 lines
16 KiB
C#
410 lines
16 KiB
C#
/*
|
|
* Copyright 2019 faddenSoft
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
|
|
using CommonUtil;
|
|
using CommonWPF;
|
|
using SourceGen.WpfGui;
|
|
|
|
namespace SourceGen.AsmGen.WpfGui {
|
|
/// <summary>
|
|
/// Code generation and assembler execution dialog.
|
|
/// </summary>
|
|
public partial class GenAndAsm : Window {
|
|
private const int PREVIEW_BUF_SIZE = 64 * 1024; // 64KB should be enough for preview
|
|
private static string NO_PREVIEW_FILES = "<" + Res.Strings.NO_FILES_AVAILABLE + ">";
|
|
|
|
/// <summary>
|
|
/// Holds data for the preview combo box.
|
|
/// </summary>
|
|
private class ComboPath {
|
|
public string FileName { get; private set; }
|
|
public string PathName { get; private set; }
|
|
public ComboPath(string pathName) {
|
|
PathName = pathName;
|
|
if (string.IsNullOrEmpty(pathName)) {
|
|
FileName = NO_PREVIEW_FILES;
|
|
} else {
|
|
FileName = Path.GetFileName(pathName);
|
|
}
|
|
}
|
|
public override string ToString() {
|
|
return FileName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main controller object, used to open app settings dialog.
|
|
/// </summary>
|
|
private MainController mMainCtrl;
|
|
|
|
/// <summary>
|
|
/// Project with data.
|
|
/// </summary>
|
|
private DisasmProject mProject;
|
|
|
|
/// <summary>
|
|
/// Directory where generated files and assembler output will go.
|
|
/// </summary>
|
|
private string mWorkDirectory;
|
|
|
|
/// <summary>
|
|
/// Base file name. For example, if this is "GenFile", we might generate
|
|
/// "GenFile_Cc65.S".
|
|
/// </summary>
|
|
private string mBaseFileName;
|
|
|
|
/// <summary>
|
|
/// Currently-selected assembler ID.
|
|
/// </summary>
|
|
private AssemblerInfo.Id mSelectedAssemblerId;
|
|
|
|
/// <summary>
|
|
/// Results from last source generation.
|
|
/// </summary>
|
|
private List<string> mGenerationResults;
|
|
|
|
/// <summary>
|
|
/// Holds an item for the pick-your-assembler combox box.
|
|
/// </summary>
|
|
private class AsmComboItem {
|
|
public AssemblerInfo.Id AssemblerId { get; private set; }
|
|
public string Name { get; private set; }
|
|
public AssemblerVersion AsmVersion { get; private set; }
|
|
|
|
public AsmComboItem(AssemblerInfo info, AssemblerVersion version) {
|
|
AssemblerId = info.AssemblerId;
|
|
Name = info.Name;
|
|
AsmVersion = version;
|
|
}
|
|
// This determines what the combo box shows.
|
|
public override string ToString() {
|
|
if (AsmVersion == null) {
|
|
return Name + " " + Res.Strings.ASM_LATEST_VERSION;
|
|
} else {
|
|
return Name + " v" + AsmVersion.VersionStr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="project">Project reference.</param>
|
|
/// <param name="projectPathName">Full path to the project file.</param>
|
|
public GenAndAsm(Window owner, MainController mainCtrl, DisasmProject project,
|
|
string projectPathName) {
|
|
InitializeComponent();
|
|
Owner = owner;
|
|
|
|
mMainCtrl = mainCtrl;
|
|
mProject = project;
|
|
mWorkDirectory = Path.GetDirectoryName(projectPathName);
|
|
mBaseFileName = Path.GetFileNameWithoutExtension(projectPathName);
|
|
|
|
workDirectoryTextBox.Text = mWorkDirectory;
|
|
}
|
|
|
|
private void Window_Loaded(object sender, RoutedEventArgs e) {
|
|
// Try to select the previously-used asm format.
|
|
string defaultAsm =
|
|
AppSettings.Global.GetString(AppSettings.SRCGEN_DEFAULT_ASM, string.Empty);
|
|
PopulateAssemblerComboBox(defaultAsm);
|
|
|
|
ResetElements();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates the assembler combo box. Attempts to match the defaultAsm arg with
|
|
/// the entries to configure the initial value.
|
|
/// </summary>
|
|
private void PopulateAssemblerComboBox(string defaultAsm) {
|
|
//assemblerComboBox.DisplayMember = "Name"; // show this property
|
|
|
|
assemblerComboBox.Items.Clear();
|
|
IEnumerator<AssemblerInfo> iter = AssemblerInfo.GetInfoEnumerator();
|
|
bool foundMatch = false;
|
|
while (iter.MoveNext()) {
|
|
AssemblerInfo info = iter.Current;
|
|
AssemblerVersion version = AssemblerVersionCache.GetVersion(info.AssemblerId);
|
|
AsmComboItem item = new AsmComboItem(info, version);
|
|
assemblerComboBox.Items.Add(item);
|
|
if (item.AssemblerId.ToString() == defaultAsm) {
|
|
Debug.WriteLine("matched current " + defaultAsm);
|
|
assemblerComboBox.SelectedItem = item;
|
|
foundMatch = true;
|
|
}
|
|
}
|
|
if (!foundMatch) {
|
|
// Need to do this or box will show empty.
|
|
assemblerComboBox.SelectedIndex = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the selected assembler as the combo box selection changes. This is
|
|
/// expected to be called during the window load event, to initialize the field.
|
|
/// </summary>
|
|
private void AssemblerComboBox_SelectionChanged(object sender,
|
|
SelectionChangedEventArgs e) {
|
|
AsmComboItem sel = (AsmComboItem)assemblerComboBox.SelectedItem;
|
|
if (sel == null) {
|
|
// this happens on Items.Clear()
|
|
return;
|
|
}
|
|
if (mSelectedAssemblerId != sel.AssemblerId) {
|
|
// Selection changed, discard window contents.
|
|
mSelectedAssemblerId = sel.AssemblerId;
|
|
AppSettings.Global.SetString(AppSettings.SRCGEN_DEFAULT_ASM,
|
|
mSelectedAssemblerId.ToString());
|
|
ResetElements();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the appropriate preview file when the combo box selection changes.
|
|
/// </summary>
|
|
private void PreviewFileComboBox_SelectionChanged(object sender,
|
|
SelectionChangedEventArgs e) {
|
|
ComboPath cpath = (ComboPath)previewFileComboBox.SelectedItem;
|
|
if (cpath == null || string.IsNullOrEmpty(cpath.PathName)) {
|
|
// nothing to do
|
|
return;
|
|
}
|
|
|
|
LoadPreviewFile(cpath.PathName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets all of the active elements to the initial state, before any source code
|
|
/// was generated.
|
|
/// </summary>
|
|
private void ResetElements() {
|
|
mGenerationResults = null;
|
|
previewFileComboBox.Items.Clear();
|
|
previewFileComboBox.Items.Add(new ComboPath(null));
|
|
previewFileComboBox.SelectedIndex = 0;
|
|
|
|
previewTextBox.Text = string.Empty;
|
|
|
|
cmdOutputTextBox.Text = string.Empty;
|
|
|
|
UpdateAssemblerControls();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the controls in the lower (assembler) half of the dialog.
|
|
/// </summary>
|
|
private void UpdateAssemblerControls() {
|
|
bool asmConf = IsAssemblerConfigured();
|
|
//Debug.WriteLine("ID=" + mSelectedAssemblerId + " asmConf=" + asmConf);
|
|
asmNotConfiguredText.Visibility = asmConf ? Visibility.Hidden : Visibility.Visible;
|
|
if (mGenerationResults == null || !asmConf) {
|
|
runAssemblerButton.IsEnabled = false;
|
|
} else {
|
|
runAssemblerButton.IsEnabled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the selected cross-assembler executable has been configured.
|
|
/// </summary>
|
|
private bool IsAssemblerConfigured() {
|
|
AssemblerConfig config =
|
|
AssemblerConfig.GetConfig(AppSettings.Global, mSelectedAssemblerId);
|
|
return config != null && !string.IsNullOrEmpty(config.ExecutablePath);
|
|
}
|
|
|
|
private void AssemblerSettingsButton_Click(object sender, RoutedEventArgs e) {
|
|
// Pop open the app settings dialog, with the appropriate tab selected.
|
|
mMainCtrl.ShowAppSettings(this, EditAppSettings.Tab.AsmConfig,
|
|
mSelectedAssemblerId);
|
|
|
|
// Update the controls based on whether or not the assembler is now available.
|
|
UpdateAssemblerControls();
|
|
AsmComboItem item = (AsmComboItem)assemblerComboBox.SelectedItem;
|
|
Debug.Assert(item != null);
|
|
PopulateAssemblerComboBox(item.AssemblerId.ToString());
|
|
}
|
|
|
|
private class GenWorker : WorkProgress.IWorker {
|
|
IGenerator mGenerator;
|
|
public List<string> Results { get; private set; }
|
|
|
|
public GenWorker(IGenerator gen) {
|
|
mGenerator = gen;
|
|
}
|
|
public object DoWork(BackgroundWorker worker) {
|
|
//worker.ReportProgress(50, "Halfway there!");
|
|
//System.Threading.Thread.Sleep(5000);
|
|
return mGenerator.GenerateSource(worker);
|
|
}
|
|
public void RunWorkerCompleted(object results) {
|
|
Results = (List<string>)results;
|
|
}
|
|
}
|
|
|
|
private void GenerateButton_Click(object sender, RoutedEventArgs e) {
|
|
IGenerator gen = AssemblerInfo.GetGenerator(mSelectedAssemblerId);
|
|
if (gen == null) {
|
|
Debug.WriteLine("Unable to get generator for " + mSelectedAssemblerId);
|
|
return;
|
|
}
|
|
gen.Configure(mProject, mWorkDirectory, mBaseFileName,
|
|
AssemblerVersionCache.GetVersion(mSelectedAssemblerId), AppSettings.Global);
|
|
|
|
GenWorker gw = new GenWorker(gen);
|
|
WorkProgress dlg = new WorkProgress(this, gw, false);
|
|
dlg.ShowDialog();
|
|
//Debug.WriteLine("Dialog returned: " + dlg.DialogResult);
|
|
|
|
List<string> pathNames = gw.Results;
|
|
|
|
if (pathNames == null) {
|
|
// error or cancelation; errors already reported
|
|
return;
|
|
}
|
|
|
|
ResetElements();
|
|
mGenerationResults = pathNames;
|
|
previewFileComboBox.Items.Clear();
|
|
foreach (string str in pathNames) {
|
|
previewFileComboBox.Items.Add(new ComboPath(str));
|
|
}
|
|
previewFileComboBox.SelectedIndex = 0; // should trigger update
|
|
|
|
UpdateAssemblerControls();
|
|
}
|
|
|
|
private void LoadPreviewFile(string pathName) {
|
|
Debug.WriteLine("LOAD " + pathName);
|
|
|
|
try {
|
|
using (StreamReader sr = new StreamReader(pathName, Encoding.UTF8)) {
|
|
char[] bigbuf = new char[PREVIEW_BUF_SIZE];
|
|
int actual = sr.Read(bigbuf, 0, bigbuf.Length);
|
|
string str = TextUtil.CharArrayToLineNumberedString(bigbuf);
|
|
if (actual < PREVIEW_BUF_SIZE) {
|
|
previewTextBox.Text = str;
|
|
} else {
|
|
previewTextBox.Text = str + "\r\n" +
|
|
Res.Strings.ERR_TOO_LARGE_FOR_PREVIEW;
|
|
}
|
|
}
|
|
} catch (Exception ex) {
|
|
previewTextBox.Text = ex.ToString();
|
|
}
|
|
}
|
|
|
|
private class AsmWorker : WorkProgress.IWorker {
|
|
private IAssembler mAssembler;
|
|
public AssemblerResults Results { get; private set; }
|
|
|
|
public AsmWorker(IAssembler asm) {
|
|
mAssembler = asm;
|
|
}
|
|
public object DoWork(BackgroundWorker worker) {
|
|
return mAssembler.RunAssembler(worker);
|
|
}
|
|
public void RunWorkerCompleted(object results) {
|
|
Results = (AssemblerResults)results;
|
|
}
|
|
}
|
|
|
|
private void RunAssemblerButton_Click(object sender, RoutedEventArgs e) {
|
|
IAssembler asm = AssemblerInfo.GetAssembler(mSelectedAssemblerId);
|
|
if (asm == null) {
|
|
Debug.WriteLine("Unable to get assembler for " + mSelectedAssemblerId);
|
|
return;
|
|
}
|
|
|
|
asm.Configure(mGenerationResults, mWorkDirectory);
|
|
|
|
AsmWorker aw = new AsmWorker(asm);
|
|
WorkProgress dlg = new WorkProgress(this, aw, true);
|
|
dlg.ShowDialog();
|
|
//Debug.WriteLine("Dialog returned: " + dlg.DialogResult);
|
|
if (dlg.DialogResult != true) {
|
|
// Canceled, or failed to even run the assembler.
|
|
return;
|
|
}
|
|
|
|
AssemblerResults results = aw.Results;
|
|
if (results == null) {
|
|
Debug.WriteLine("Dialog returned OK, but no assembler results found");
|
|
Debug.Assert(false);
|
|
return;
|
|
}
|
|
|
|
StringBuilder sb =
|
|
new StringBuilder(results.Stdout.Length + results.Stderr.Length + 200);
|
|
sb.Append(results.CommandLine);
|
|
sb.Append("\r\n");
|
|
sb.AppendFormat("ExitCode={0} - ", results.ExitCode);
|
|
if (results.ExitCode == 0) {
|
|
FileInfo fi = new FileInfo(results.OutputPathName);
|
|
if (!fi.Exists) {
|
|
MessageBox.Show(this, Res.Strings.ASM_OUTPUT_NOT_FOUND,
|
|
Res.Strings.ASM_MISMATCH_CAPTION,
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
|
sb.Append(Res.Strings.ASM_MATCH_FAILURE);
|
|
} else if (!CommonUtil.FileUtil.CompareBinaryFile(mProject.FileData,
|
|
results.OutputPathName, out int offset, out byte fileVal)) {
|
|
if (fi.Length != mProject.FileData.Length &&
|
|
offset == fi.Length || offset == mProject.FileData.Length) {
|
|
// The files matched up to the point where one ended.
|
|
string msg = string.Format(Res.Strings.ASM_MISMATCH_LENGTH_FMT,
|
|
fi.Length, mProject.FileData.Length);
|
|
MessageBox.Show(msg, Res.Strings.ASM_MISMATCH_CAPTION,
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
|
sb.Append(msg);
|
|
} else {
|
|
string msg = string.Format(Res.Strings.ASM_MISMATCH_DATA_FMT,
|
|
offset, fileVal, mProject.FileData[offset]);
|
|
MessageBox.Show(msg, Res.Strings.ASM_MISMATCH_CAPTION,
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
|
sb.Append(msg);
|
|
}
|
|
} else {
|
|
sb.Append(Res.Strings.ASM_MATCH_SUCCESS);
|
|
}
|
|
}
|
|
sb.Append("\r\n\r\n");
|
|
|
|
if (results.Stdout != null && results.Stdout.Length > 2) {
|
|
sb.Append("----- stdout -----\r\n");
|
|
sb.Append(results.Stdout);
|
|
sb.Append("\r\n");
|
|
}
|
|
if (results.Stderr != null && results.Stderr.Length > 2) {
|
|
sb.Append("----- stderr -----\r\n");
|
|
sb.Append(results.Stderr);
|
|
sb.Append("\r\n");
|
|
}
|
|
|
|
cmdOutputTextBox.Text = sb.ToString();
|
|
}
|
|
}
|
|
}
|