Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid state sharing #18

Merged
merged 2 commits into from
Dec 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ This library is also used in production by [KnowledgePicker](https://knowledgepi

As mentioned [above](#how-to-use), only subset of functionality is implemented now, but all contributions are welcome. Feel free to open [issues](https://github.com/knowledgepicker/word-cloud/issues) and [pull requests](https://github.com/knowledgepicker/word-cloud/pulls).

### Testing

Tests are currently only supported on Linux, because they are snapshot tests (generating a word cloud image and comparing it byte-by-byte with a snapshot) and more work is needed to ensure this is cross-platform (e.g., use exactly the same font). On Windows, tests can be run in WSL (Visual Studio supports this directly). Tests are also automatically run in GitHub Actions.

### Release process

After pushing a tag, GitHub workflow `release.yml` is triggered which builds and publishes the NuGet package.
2 changes: 2 additions & 0 deletions src/KnowledgePicker.WordCloud/Drawing/IGraphicEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,7 @@ public interface IGraphicEngine : IDisposable
public interface IGraphicEngine<TBitmap> : IGraphicEngine
{
TBitmap Bitmap { get; }

IGraphicEngine<TBitmap> Clone();
}
}
15 changes: 15 additions & 0 deletions src/KnowledgePicker.WordCloud/Drawing/SkGraphicEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ public sealed class SkGraphicEngine : IGraphicEngine<SKBitmap>
private readonly SKPaint textPaint;
private readonly WordCloudInput wordCloud;

private SkGraphicEngine(ISizer sizer, WordCloudInput wordCloud,
SKPaint textPaint)
{
Sizer = sizer;
this.wordCloud = wordCloud;
this.textPaint = textPaint;
Bitmap = new SKBitmap(wordCloud.Width, wordCloud.Height);
canvas = new SKCanvas(Bitmap);
}

public SkGraphicEngine(ISizer sizer, WordCloudInput wordCloud,
SKTypeface? font = null, bool antialias = true)
{
Expand Down Expand Up @@ -52,6 +62,11 @@ public void Draw(PointD location, RectangleD measured, string text, int count, s
(float)(location.Y - measured.Top), textPaint);
}

public IGraphicEngine<SKBitmap> Clone()
{
return new SkGraphicEngine(Sizer, wordCloud, textPaint);
}

public void Dispose()
{
textPaint.Dispose();
Expand Down
2 changes: 2 additions & 0 deletions src/KnowledgePicker.WordCloud/Layouts/BaseLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public IEnumerable<LayoutItem> GetWordsInArea(RectangleD area)
return QuadTree.Query(area);
}

public abstract ILayout Clone();

protected bool IsInsideSurface(RectangleD targetRectangle)
{
return IsInside(Surface, targetRectangle);
Expand Down
1 change: 1 addition & 0 deletions src/KnowledgePicker.WordCloud/Layouts/ILayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public interface ILayout
{
int Arrange(IEnumerable<WordCloudEntry> entries, IGraphicEngine engine);
IEnumerable<LayoutItem> GetWordsInArea(RectangleD area);
ILayout Clone();
}
}
5 changes: 5 additions & 0 deletions src/KnowledgePicker.WordCloud/Layouts/SpiralLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public override bool TryFindFreeRectangle(SizeD size, out RectangleD foundRectan
return false;
}

public override ILayout Clone()
{
return new SpiralLayout(WordCloud);
}

private static double GetPseudoRandomStartAngle(SizeD size)
{
return size.Height * size.Width;
Expand Down
2 changes: 1 addition & 1 deletion src/KnowledgePicker.WordCloud/Primitives/LayoutItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace KnowledgePicker.WordCloud.Primitives
/// <summary>
/// Word arranged somewhere in word cloud.
/// </summary>
public class LayoutItem
public record LayoutItem
{
public LayoutItem(WordCloudEntry entry, PointD location, RectangleD measured)
{
Expand Down
10 changes: 8 additions & 2 deletions src/KnowledgePicker.WordCloud/WordCloudGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,19 @@ public WordCloudGenerator(WordCloudInput wordCloud,
private T Process<T>(
Func<IGraphicEngine<TBitmap>, IEnumerable<LayoutItem>, T> handler)
{
// Ensure state is not shared.
// TODO: We should instead use factory pattern.
// But that would be a big change in usage of this class.
var localEngine = engine.Clone();
var localLayout = layout.Clone();

// Arrange word cloud.
var size = new SizeD(wordCloud.Width, wordCloud.Height);
layout.Arrange(wordCloud.Entries, engine);
localLayout.Arrange(wordCloud.Entries, localEngine);

// Process results.
var area = new RectangleD(new PointD(0, 0), size);
return handler(engine, layout.GetWordsInArea(area));
return handler(localEngine, localLayout.GetWordsInArea(area));
}

public IEnumerable<(LayoutItem Item, double FontSize)> Arrange()
Expand Down
39 changes: 39 additions & 0 deletions test/KnowledgePicker.WordCloud.Tests/WordCloudGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using KnowledgePicker.WordCloud.Drawing;
using KnowledgePicker.WordCloud.Layouts;
using KnowledgePicker.WordCloud.Primitives;
using KnowledgePicker.WordCloud.Sizers;
using SkiaSharp;

namespace KnowledgePicker.WordCloud.Tests;

public class WordCloudGeneratorTests
{
[Fact] // https://github.com/knowledgepicker/word-cloud/issues/17
public void DoesNotShareState()
{
// Arrange.
var wordCloud = new WordCloudInput(new[]
{
new WordCloudEntry("a", 1),
new WordCloudEntry("b", 1),
})
{
Width = 1024,
Height = 256,
MinFontSize = 8,
MaxFontSize = 32
};
var sizer = new LogSizer(wordCloud);
using var engine = new SkGraphicEngine(sizer, wordCloud);
var layout = new SpiralLayout(wordCloud);
var wcg = new WordCloudGenerator<SKBitmap>(wordCloud, engine, layout);

// Act.
var result1 = wcg.Arrange().ToArray();
var result2 = wcg.Arrange().ToArray();

// Assert.
Assert.Equal(result1.AsEnumerable(), result2);
Assert.Equal(2, result2.Length);
}
}