diff --git a/CommonWPF/WorkProgress.xaml.cs b/CommonWPF/WorkProgress.xaml.cs
index 5dd4fbd..8f0a274 100644
--- a/CommonWPF/WorkProgress.xaml.cs
+++ b/CommonWPF/WorkProgress.xaml.cs
@@ -107,7 +107,10 @@ namespace CommonWPF {
int percent = e.ProgressPercentage;
string msg = e.UserState as string;
- Debug.Assert(percent >= 0 && percent <= 100);
+ if (percent < 0 || percent > 100) {
+ Debug.WriteLine("WorkProgress: bad percent " + percent);
+ percent = 0;
+ }
if (!string.IsNullOrEmpty(msg)) {
messageText.Text = msg;
diff --git a/SourceGen/AsmGen/WpfGui/GenAndAsm.xaml.cs b/SourceGen/AsmGen/WpfGui/GenAndAsm.xaml.cs
index 814be39..7cfbd5d 100644
--- a/SourceGen/AsmGen/WpfGui/GenAndAsm.xaml.cs
+++ b/SourceGen/AsmGen/WpfGui/GenAndAsm.xaml.cs
@@ -318,7 +318,7 @@ namespace SourceGen.AsmGen.WpfGui {
}
private class AsmWorker : WorkProgress.IWorker {
- IAssembler mAssembler;
+ private IAssembler mAssembler;
public AssemblerResults Results { get; private set; }
public AsmWorker(IAssembler asm) {
diff --git a/SourceGen/Exporter.cs b/SourceGen/Exporter.cs
index a3eeaf3..d8ce1ce 100644
--- a/SourceGen/Exporter.cs
+++ b/SourceGen/Exporter.cs
@@ -15,6 +15,7 @@
*/
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
@@ -446,13 +447,45 @@ namespace SourceGen {
private const string HTML_EXPORT_CSS_FILE = "SGStyle.css";
private const string LABEL_LINK_PREFIX = "Sym";
+ private class ExportWorker : WorkProgress.IWorker {
+ private Exporter mExporter;
+ private string mPathName;
+ private bool mOverwriteCss;
+
+ public bool Success { get; private set; }
+
+ public ExportWorker(Exporter exp, string pathName, bool overwriteCss) {
+ mExporter = exp;
+ mPathName = pathName;
+ mOverwriteCss = overwriteCss;
+ }
+ public object DoWork(BackgroundWorker worker) {
+ return mExporter.OutputToHtml(worker, mPathName, mOverwriteCss);
+ }
+ public void RunWorkerCompleted(object results) {
+ if (results != null) {
+ Success = (bool)results;
+ }
+ }
+ }
+
///
/// Generates HTML output to the specified path.
///
/// Full pathname of output file (including ".html"). This
/// defines the root directory if there are additional files.
/// If set, existing CSS file will be replaced.
- public void OutputToHtml(string pathName, bool overwriteCss) {
+ public void OutputToHtml(Window parent, string pathName, bool overwriteCss) {
+ ExportWorker ew = new ExportWorker(this, pathName, overwriteCss);
+ WorkProgress dlg = new WorkProgress(parent, ew, false);
+ if (dlg.ShowDialog() != true) {
+ Debug.WriteLine("Export unsuccessful");
+ } else {
+ Debug.WriteLine("Export complete");
+ }
+ }
+
+ private bool OutputToHtml(BackgroundWorker worker, string pathName, bool overwriteCss) {
string exportTemplate = RuntimeDataAccess.GetPathName(HTML_EXPORT_TEMPLATE);
string tmplStr;
try {
@@ -463,7 +496,7 @@ namespace SourceGen {
pathName, ex.Message);
MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
MessageBoxButton.OK, MessageBoxImage.Error);
- return;
+ return false;
}
// We should only need the _IMG directory if there are visualizations.
@@ -478,7 +511,7 @@ namespace SourceGen {
imageDirPath);
MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
MessageBoxButton.OK, MessageBoxImage.Error);
- return;
+ return false;
}
exists = true;
} catch (FileNotFoundException) {
@@ -493,7 +526,7 @@ namespace SourceGen {
imageDirPath, ex.Message);
MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
MessageBoxButton.OK, MessageBoxImage.Error);
- return;
+ return false;
}
}
@@ -501,6 +534,13 @@ namespace SourceGen {
mImageDirPath = imageDirPath;
}
+ if (mImageDirPath == null) {
+ worker.ReportProgress(0, Res.Strings.EXPORTING_HTML);
+ } else {
+ worker.ReportProgress(0, Res.Strings.EXPORTING_HTML_AND_IMAGES);
+ }
+
+
// Perform some quick substitutions. This could be done more efficiently,
// but we're only doing this on the template file, which should be small.
tmplStr = tmplStr.Replace("$ProjectName$", mProject.DataFileName);
@@ -527,11 +567,13 @@ namespace SourceGen {
int splitPoint = tmplStr.IndexOf(CodeLinesStr);
if (splitPoint < 0) {
Debug.WriteLine("No place to put code");
- return;
+ return false;
}
string template1 = tmplStr.Substring(0, splitPoint);
string template2 = tmplStr.Substring(splitPoint + CodeLinesStr.Length);
+ int lastProgressPerc = 0;
+
// Generate UTF-8 text, without a byte-order mark.
using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) {
sw.Write(template1);
@@ -545,12 +587,27 @@ namespace SourceGen {
}
GenerateHtmlLine(lineIndex, sw, sb);
+
+ if (worker.CancellationPending) {
+ break;
+ }
+ int perc = (lineIndex * 100) / mCodeLineList.Count;
+ if (perc != lastProgressPerc) {
+ lastProgressPerc = perc;
+ worker.ReportProgress(perc);
+ }
}
sw.WriteLine("\r\n");
sw.Write(template2);
}
+ if (worker.CancellationPending) {
+ Debug.WriteLine("Cancel requested, deleting " + pathName);
+ File.Delete(pathName);
+ return false;
+ }
+
string cssFile = RuntimeDataAccess.GetPathName(HTML_EXPORT_CSS_FILE);
string outputDir = Path.GetDirectoryName(pathName);
string outputPath = Path.Combine(outputDir, HTML_EXPORT_CSS_FILE);
@@ -563,9 +620,11 @@ namespace SourceGen {
cssFile, outputPath, ex.Message);
MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
MessageBoxButton.OK, MessageBoxImage.Error);
- return;
+ return false;
}
}
+
+ return true;
}
///
diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs
index 3897a76..cff8c0a 100644
--- a/SourceGen/MainController.cs
+++ b/SourceGen/MainController.cs
@@ -2345,18 +2345,18 @@ namespace SourceGen {
eport.Selection = selection;
}
- // This is generally fast enough that I don't feel the need to create a
- // progress window.
- try {
- Mouse.OverrideCursor = Cursors.Wait;
-
- if (dlg.GenType == WpfGui.Export.GenerateFileType.Html) {
- eport.OutputToHtml(dlg.PathName, dlg.OverwriteCss);
- } else {
+ if (dlg.GenType == WpfGui.Export.GenerateFileType.Html) {
+ // Generating wireframe animations can be slow, so we need to use a
+ // progress dialog.
+ eport.OutputToHtml(mMainWin, dlg.PathName, dlg.OverwriteCss);
+ } else {
+ // Text output is generally very fast. Put up a wait cursor just in case.
+ try {
+ Mouse.OverrideCursor = Cursors.Wait;
eport.OutputToText(dlg.PathName, dlg.TextModeCsv);
+ } finally {
+ Mouse.OverrideCursor = null;
}
- } finally {
- Mouse.OverrideCursor = null;
}
}
diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml
index c0d3718..28b4498 100644
--- a/SourceGen/Res/Strings.xaml
+++ b/SourceGen/Res/Strings.xaml
@@ -81,6 +81,8 @@ limitations under the License.
Unable to save project file
[File was too large for preview window]
Symbol value is incompatible with current multi-mask
+ Exporting HTML...
+ Exporting HTML and images...
Symbol files and extension scripts must live in the application runtime directory ({0}) or project directory ({1}).
File {2} lives elsewhere.
File Not In Runtime Directory
All files (*.*)|*.*
diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs
index aa71c32..7ad3343 100644
--- a/SourceGen/Res/Strings.xaml.cs
+++ b/SourceGen/Res/Strings.xaml.cs
@@ -143,6 +143,10 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ErrTooLargeForPreview");
public static string ERR_VALUE_INCOMPATIBLE_WITH_MASK =
(string)Application.Current.FindResource("str_ErrValueIncompatibleWithMask");
+ public static string EXPORTING_HTML =
+ (string)Application.Current.FindResource("str_ExportingHtml");
+ public static string EXPORTING_HTML_AND_IMAGES =
+ (string)Application.Current.FindResource("str_ExportingHtmlAndImages");
public static string EXTERNAL_FILE_BAD_DIR_FMT =
(string)Application.Current.FindResource("str_ExternalFileBadDirFmt");
public static string EXTERNAL_FILE_BAD_DIR_CAPTION =
diff --git a/SourceGen/VisWireframeAnimation.cs b/SourceGen/VisWireframeAnimation.cs
index b6d0a8b..8b58bdf 100644
--- a/SourceGen/VisWireframeAnimation.cs
+++ b/SourceGen/VisWireframeAnimation.cs
@@ -79,7 +79,12 @@ namespace SourceGen {
public override void SetThumbnail(IVisualizationWireframe visWire,
ReadOnlyDictionary parms) {
base.SetThumbnail(visWire, parms);
- mWireObj = WireframeObject.Create(visWire);
+ if (visWire == null) {
+ // Thumbnail cache is being cleared. Throw out the wireframe object too.
+ mWireObj = null;
+ } else {
+ mWireObj = WireframeObject.Create(visWire);
+ }
}
///
@@ -99,6 +104,20 @@ namespace SourceGen {
bool doPersp = Util.GetFromObjDict(VisGenParams, VisWireframe.P_IS_PERSPECTIVE, true);
bool doBfc = Util.GetFromObjDict(VisGenParams, VisWireframe.P_IS_BFC_ENABLED, false);
+ // Try to avoid System.Runtime.InteropServices.COMException (0x88980003):
+ // MILERR_WIN32ERROR (Exception from HRESULT: 0x88980003)
+ // The problem seems to be that the bitmaps are GDI handles, there's a hard limit of
+ // 10,000, and they don't get released until finalizers run. If we wait for
+ // pending finalizers the pool stays at a manageable level. If we poke the GC
+ // every time the memory graph looks better but I suspect there's a performance hit.
+ //GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ if (mWireObj == null) {
+ Debug.WriteLine("EncodeGif: wire obj is null");
+ frameCount = 1;
+ }
+
for (int frame = 0; frame < frameCount; frame++) {
BitmapSource bs = GenerateWireframeImage(mWireObj, dim,
curX, curY, curZ, doPersp, doBfc);
diff --git a/SourceGen/Visualization.cs b/SourceGen/Visualization.cs
index 55c636f..7fa6a82 100644
--- a/SourceGen/Visualization.cs
+++ b/SourceGen/Visualization.cs
@@ -106,8 +106,11 @@ namespace SourceGen {
///
/// Image to show when things are broken.
///
- public static readonly BitmapImage BROKEN_IMAGE =
- new BitmapImage(new Uri("pack://application:,,,/Res/RedX.png"));
+ public static readonly BitmapImage BROKEN_IMAGE;
+ static Visualization() {
+ BROKEN_IMAGE = new BitmapImage(new Uri("pack://application:,,,/Res/RedX.png"));
+ BROKEN_IMAGE.Freeze();
+ }
///
/// Image to overlay on animation visualizations.
@@ -185,19 +188,24 @@ namespace SourceGen {
} else {
CachedImage = ConvertToBitmapSource(vis2d);
}
+ Debug.Assert(CachedImage.IsFrozen);
}
///
/// Updates the cached thumbnail image.
///
- /// Visualization object.
+ /// Visualization object, or null to clear the thumbnail.
/// Visualization parameters.
public virtual void SetThumbnail(IVisualizationWireframe visWire,
ReadOnlyDictionary parms) {
- Debug.Assert(visWire != null);
- Debug.Assert(parms != null);
- WireframeObject wireObj = WireframeObject.Create(visWire);
- CachedImage = GenerateWireframeImage(wireObj, THUMBNAIL_DIM, parms);
+ if (visWire == null) {
+ CachedImage = BROKEN_IMAGE;
+ } else {
+ Debug.Assert(parms != null);
+ WireframeObject wireObj = WireframeObject.Create(visWire);
+ CachedImage = GenerateWireframeImage(wireObj, THUMBNAIL_DIM, parms);
+ }
+ Debug.Assert(CachedImage.IsFrozen);
}
///
@@ -246,6 +254,7 @@ namespace SourceGen {
palette,
vis2d.GetPixels(),
vis2d.Width);
+ image.Freeze();
return image;
}
@@ -274,11 +283,17 @@ namespace SourceGen {
///
public static BitmapSource GenerateWireframeImage(WireframeObject wireObj,
double dim, int eulerX, int eulerY, int eulerZ, bool doPersp, bool doBfc) {
+ if (wireObj == null) {
+ // Can happen if the visualization generator is failing on stuff loaded from
+ // the project file.
+ return BROKEN_IMAGE;
+ }
+
// Generate the path geometry.
GeometryGroup geo = GenerateWireframePath(wireObj, dim, eulerX, eulerY, eulerZ,
doPersp, doBfc);
- // Render Path to bitmap -- https://stackoverflow.com/a/23582564/294248
+ // Render geometry to bitmap -- https://stackoverflow.com/a/869767/294248
Rect bounds = geo.GetRenderBounds(null);
//Debug.WriteLine("RenderWF dim=" + dim + " bounds=" + bounds + ": " + wireObj);
@@ -290,7 +305,18 @@ namespace SourceGen {
96,
96,
PixelFormats.Pbgra32);
- //RenderOptions.SetEdgeMode(bitmap, EdgeMode.Aliased); <-- doesn't work?
+ //RenderOptions.SetEdgeMode(bitmap, EdgeMode.Aliased); <-- no apparent effect
+
+ DrawingVisual dv = new DrawingVisual();
+ using (DrawingContext dc = dv.RenderOpen()) {
+ dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, bounds.Width, bounds.Height));
+ Pen pen = new Pen(Brushes.White, 1.0);
+ dc.DrawGeometry(Brushes.White, pen, geo);
+ }
+ bitmap.Render(dv);
+
+#if false
+ // Old way: render Path to bitmap -- https://stackoverflow.com/a/23582564/294248
// Clear the bitmap to black. (Is there an easier way?)
GeometryGroup bkgnd = new GeometryGroup();
@@ -308,7 +334,9 @@ namespace SourceGen {
path.Measure(bounds.Size);
path.Arrange(bounds);
bitmap.Render(path);
+#endif
+ bitmap.Freeze();
return bitmap;
}
@@ -405,6 +433,7 @@ namespace SourceGen {
private static BitmapSource GenerateBlankImage() {
RenderTargetBitmap bmp = new RenderTargetBitmap(1, 1, 96.0, 96.0,
PixelFormats.Pbgra32);
+ bmp.Freeze();
return bmp;
}
@@ -438,6 +467,7 @@ namespace SourceGen {
RenderTargetBitmap bmp = new RenderTargetBitmap(IMAGE_SIZE, IMAGE_SIZE, 96.0, 96.0,
PixelFormats.Pbgra32);
bmp.Render(visual);
+ bmp.Freeze();
return bmp;
}
diff --git a/SourceGen/VisualizationSet.cs b/SourceGen/VisualizationSet.cs
index d7ad6fe..0a62f8e 100644
--- a/SourceGen/VisualizationSet.cs
+++ b/SourceGen/VisualizationSet.cs
@@ -167,6 +167,7 @@ namespace SourceGen {
public void RefreshNeeded() {
foreach (Visualization vis in mList) {
vis.SetThumbnail(null);
+ vis.SetThumbnail(null, null);
}
}