diff --git a/src/CromulentBisgetti.ContainerPacking/Algorithms/EB_AFIT.cs b/src/CromulentBisgetti.ContainerPacking/Algorithms/EB_AFIT.cs
index c10292c..97b4df3 100644
--- a/src/CromulentBisgetti.ContainerPacking/Algorithms/EB_AFIT.cs
+++ b/src/CromulentBisgetti.ContainerPacking/Algorithms/EB_AFIT.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
namespace CromulentBisgetti.ContainerPacking.Algorithms
{
@@ -20,9 +21,9 @@ public class EB_AFIT : IPackingAlgorithm
/// The container to pack items into.
/// The items to pack.
/// The bin packing result.
- public AlgorithmPackingResult Run(Container container, List- items)
+ public AlgorithmPackingResult Run(Container container, List
- items, CancellationToken ct)
{
- Initialize(container, items);
+ Initialize(container, items, ct);
ExecuteIterations(container);
Report(container);
@@ -59,6 +60,7 @@ public AlgorithmPackingResult Run(Container container, List
- items)
private List
- itemsToPack;
private List
- itemsPackedInOrder;
private List layers;
+ private CancellationToken _ct;
private ContainerPackingResult result;
private ScrapPad scrapfirst;
@@ -70,7 +72,6 @@ public AlgorithmPackingResult Run(Container container, List
- items)
private bool layerDone;
private bool packing;
private bool packingBest = false;
- private bool quit = false;
private int bboxi;
private int bestIteration;
@@ -288,7 +289,7 @@ private void ExecuteIterations(Container container)
int layersIndex;
decimal bestVolume = 0.0M;
- for (int containerOrientationVariant = 1; (containerOrientationVariant <= 6) && !quit; containerOrientationVariant++)
+ for (int containerOrientationVariant = 1; (containerOrientationVariant <= 6) && !_ct.IsCancellationRequested; containerOrientationVariant++)
{
switch (containerOrientationVariant)
{
@@ -321,7 +322,7 @@ private void ExecuteIterations(Container container)
ListCanditLayers();
layers = layers.OrderBy(l => l.LayerEval).ToList();
- for (layersIndex = 1; (layersIndex <= layerListLen) && !quit; layersIndex++)
+ for (layersIndex = 1; (layersIndex <= layerListLen) && !_ct.IsCancellationRequested; layersIndex++)
{
packedVolume = 0.0M;
packedy = 0;
@@ -347,7 +348,7 @@ private void ExecuteIterations(Container container)
packedy = packedy + layerThickness;
remainpy = py - packedy;
- if (layerinlayer != 0 && !quit)
+ if (layerinlayer != 0 && !_ct.IsCancellationRequested)
{
prepackedy = packedy;
preremainpy = remainpy;
@@ -365,9 +366,9 @@ private void ExecuteIterations(Container container)
}
FindLayer(remainpy);
- } while (packing && !quit);
+ } while (packing && !_ct.IsCancellationRequested);
- if ((packedVolume > bestVolume) && !quit)
+ if ((packedVolume > bestVolume) && !_ct.IsCancellationRequested)
{
bestVolume = packedVolume;
bestVariant = containerOrientationVariant;
@@ -525,10 +526,11 @@ private void FindSmallestZ()
///
/// Initializes everything.
///
- private void Initialize(Container container, List
- items)
+ private void Initialize(Container container, List
- items, CancellationToken ct)
{
itemsToPack = new List
- ();
itemsPackedInOrder = new List
- ();
+ _ct = ct;
result = new ContainerPackingResult();
// The original code uses 1-based indexing everywhere. This fake entry is added to the beginning
@@ -565,7 +567,6 @@ private void Initialize(Container container, List
- items)
scrapfirst.Post = null;
packingBest = false;
hundredPercentPacked = false;
- quit = false;
}
///
@@ -752,7 +753,7 @@ private void PackLayer()
scrapfirst.CumX = px;
scrapfirst.CumZ = 0;
- for (; !quit;)
+ for (; !_ct.IsCancellationRequested;)
{
FindSmallestZ();
@@ -1037,8 +1038,6 @@ private void PackLayer()
///
private void Report(Container container)
{
- quit = false;
-
switch (bestVariant)
{
case 1:
@@ -1112,11 +1111,11 @@ private void Report(Container container)
remainpz = pz;
}
- if (!quit)
+ if (!_ct.IsCancellationRequested)
{
FindLayer(remainpy);
}
- } while (packing && !quit);
+ } while (packing && !_ct.IsCancellationRequested);
}
///
diff --git a/src/CromulentBisgetti.ContainerPacking/Algorithms/IPackingAlgorithm.cs b/src/CromulentBisgetti.ContainerPacking/Algorithms/IPackingAlgorithm.cs
index 4213bee..a2bc388 100644
--- a/src/CromulentBisgetti.ContainerPacking/Algorithms/IPackingAlgorithm.cs
+++ b/src/CromulentBisgetti.ContainerPacking/Algorithms/IPackingAlgorithm.cs
@@ -1,5 +1,6 @@
using CromulentBisgetti.ContainerPacking.Entities;
using System.Collections.Generic;
+using System.Threading;
namespace CromulentBisgetti.ContainerPacking.Algorithms
{
@@ -13,7 +14,8 @@ public interface IPackingAlgorithm
///
/// The container.
/// The items to pack.
+ /// Sets the System.Threading.CancellationToken associated with this AlgorithmPackingResult instance.
/// The algorithm packing result.
- AlgorithmPackingResult Run(Container container, List
- items);
+ AlgorithmPackingResult Run(Container container, List
- items, CancellationToken cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/CromulentBisgetti.ContainerPacking/PackingService.cs b/src/CromulentBisgetti.ContainerPacking/PackingService.cs
index e7fae8b..65b61c4 100644
--- a/src/CromulentBisgetti.ContainerPacking/PackingService.cs
+++ b/src/CromulentBisgetti.ContainerPacking/PackingService.cs
@@ -4,10 +4,11 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
namespace CromulentBisgetti.ContainerPacking
-{
+{
///
/// The container packing service.
///
@@ -21,6 +22,13 @@ public static class PackingService
/// The list of algorithm type IDs to use for packing.
/// A container packing result with lists of the packed and unpacked items.
public static List Pack(List containers, List
- itemsToPack, List algorithmTypeIDs)
+ {
+ var source = new CancellationTokenSource();
+
+ return Pack(containers, itemsToPack, algorithmTypeIDs, source.Token);
+ }
+
+ public static List Pack(List containers, List
- itemsToPack, List algorithmTypeIDs, CancellationToken cancellationToken)
{
Object sync = new Object { };
List result = new List();
@@ -45,16 +53,16 @@ public static List Pack(List containers, List
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
- AlgorithmPackingResult algorithmResult = algorithm.Run(container, items);
+ AlgorithmPackingResult algorithmResult = algorithm.Run(container, items, cancellationToken);
stopwatch.Stop();
algorithmResult.PackTimeInMilliseconds = stopwatch.ElapsedMilliseconds;
- decimal containerVolume = container.Length * container.Width * container.Height;
- decimal itemVolumePacked = algorithmResult.PackedItems.Sum(i => i.Volume);
+ decimal containerVolume = container.Length * container.Width * container.Height;
+ decimal itemVolumePacked = algorithmResult.PackedItems.Sum(i => i.Volume);
decimal itemVolumeUnpacked = algorithmResult.UnpackedItems.Sum(i => i.Volume);
- algorithmResult.PercentContainerVolumePacked = Math.Round(itemVolumePacked / containerVolume * 100, 2);
+ algorithmResult.PercentContainerVolumePacked = Math.Round(itemVolumePacked / containerVolume * 100, 2);
algorithmResult.PercentItemVolumePacked = Math.Round(itemVolumePacked / (itemVolumePacked + itemVolumeUnpacked) * 100, 2);
lock (sync)
diff --git a/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingCancelTests.cs b/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingCancelTests.cs
new file mode 100644
index 0000000..651c46d
--- /dev/null
+++ b/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingCancelTests.cs
@@ -0,0 +1,54 @@
+using CromulentBisgetti.ContainerPacking;
+using CromulentBisgetti.ContainerPacking.Algorithms;
+using CromulentBisgetti.ContainerPacking.Entities;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace CromulentBisgetti.ContainerPackingTests
+{
+ [TestClass]
+ public class ContainerPackingCancelTests
+ {
+ [TestMethod]
+ public async Task LongRunningTest_CanBeCancelled()
+ {
+ // One of the longer-running 700 tests, #479
+ var itemsToPack = new List
-
+ {
+ new Item(1, 64, 48, 64, 14),
+ new Item(2, 79, 25, 79, 23),
+ new Item(3, 89, 85, 89, 19),
+ new Item(4, 79, 66, 79, 17),
+ new Item(5, 79, 54, 79, 16),
+ new Item(6, 115, 95, 115, 11),
+ new Item(7, 76, 54, 76, 20),
+ new Item(8, 80, 44, 80, 10),
+ new Item(9, 66, 33, 66, 15),
+ new Item(10, 50, 32, 50, 15),
+ new Item(11, 116, 93, 116, 19),
+ new Item(12, 113, 64, 113, 11),
+ };
+ var containers = new List
+ {
+ new Container(1, 587, 233, 220),
+ };
+
+ var source = new CancellationTokenSource();
+
+ // start the packing task on another thread and give it a bit of time to start
+ var packingTask = Task.Run(() =>
+ PackingService.Pack(containers, itemsToPack, new List { (int)AlgorithmType.EB_AFIT }, source.Token));
+ await Task.Delay(50);
+
+ // then cancel it. Packing should return quickly
+ source.Cancel();
+
+ var result = await packingTask;
+
+ var elapsedMilliSec = result[0].AlgorithmPackingResults[0].PackTimeInMilliseconds;
+ Assert.IsTrue(elapsedMilliSec < 100, $"Expected elapsed time to be less than 100 but found {elapsedMilliSec} msec");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingTests.cs b/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingTests.cs
index 897479c..3f52caf 100644
--- a/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingTests.cs
+++ b/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingTests.cs
@@ -6,6 +6,8 @@
using CromulentBisgetti.ContainerPacking;
using CromulentBisgetti.ContainerPacking.Entities;
using CromulentBisgetti.ContainerPacking.Algorithms;
+using System.Globalization;
+using System.Diagnostics;
namespace CromulentBisgetti.ContainerPackingTests
{
@@ -19,6 +21,8 @@ public void EB_AFIT_Passes_700_Standard_Reference_Tests()
string resourceName = "CromulentBisgetti.ContainerPackingTests.DataFiles.ORLibrary.txt";
Assembly assembly = Assembly.GetExecutingAssembly();
+ var decimalPointCulture = CultureInfo.GetCultureInfo("en-us");
+
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
using (StreamReader reader = new StreamReader(stream))
@@ -54,6 +58,8 @@ public void EB_AFIT_Passes_700_Standard_Reference_Tests()
List result = PackingService.Pack(containers, itemsToPack, new List { (int)AlgorithmType.EB_AFIT });
+ Debug.WriteLine($"Test #{counter} took {result[0].AlgorithmPackingResults[0].PackTimeInMilliseconds}msec");
+
// Assert that the number of items we tried to pack equals the number stated in the published reference.
Assert.AreEqual(result[0].AlgorithmPackingResults[0].PackedItems.Count + result[0].AlgorithmPackingResults[0].UnpackedItems.Count, Convert.ToDecimal(testResults[1]));
@@ -61,14 +67,16 @@ public void EB_AFIT_Passes_700_Standard_Reference_Tests()
Assert.AreEqual(result[0].AlgorithmPackingResults[0].PackedItems.Count, Convert.ToDecimal(testResults[2]));
// Assert that the packed container volume percentage is equal to the published reference result.
- // Make an exception for a couple of tests where this algorithm yields 87.20% and the published result
- // was 87.21% (acceptable rounding error).
- Assert.IsTrue(result[0].AlgorithmPackingResults[0].PercentContainerVolumePacked == Convert.ToDecimal(testResults[3]) ||
- (result[0].AlgorithmPackingResults[0].PercentContainerVolumePacked == 87.20M && Convert.ToDecimal(testResults[3]) == 87.21M));
+ var actualPercentage = result[0].AlgorithmPackingResults[0].PercentContainerVolumePacked;
+ var expectedPrecentage = Convert.ToDecimal(testResults[3], decimalPointCulture);
- // Assert that the packed item volume percentage is equal to the published reference result.
- Assert.AreEqual(result[0].AlgorithmPackingResults[0].PercentItemVolumePacked, Convert.ToDecimal(testResults[4]));
+ Assert.IsTrue(
+ Math.Abs(actualPercentage - expectedPrecentage) < 0.02M,
+ $"Test #{counter} failed: expected%={expectedPrecentage}; actual%={actualPercentage};");
+ // Assert that the packed item volume percentage is equal to the published reference result.
+ Assert.AreEqual(result[0].AlgorithmPackingResults[0].PercentItemVolumePacked, Convert.ToDecimal(testResults[4], decimalPointCulture));
+
counter++;
}
}