diff --git a/src/CromulentBisgetti.ContainerPacking/Algorithms/EB_AFIT.cs b/src/CromulentBisgetti.ContainerPacking/Algorithms/EB_AFIT.cs
index c10292c..05ef057 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 cancellationToken)
{
- Initialize(container, items);
+ Initialize(container, items, cancellationToken);
ExecuteIterations(container);
Report(container);
@@ -41,8 +42,6 @@ public AlgorithmPackingResult Run(Container container, List
- items)
}
result.PackedItems = itemsPackedInOrder;
-
-
if (result.UnpackedItems.Count == 0)
{
@@ -52,13 +51,14 @@ public AlgorithmPackingResult Run(Container container, List
- items)
return result;
}
- #endregion Public Methods
+ #endregion Public Methods
- #region Private Variables
+ #region Private Variables
- private List
- itemsToPack;
+ private List
- itemsToPack;
private List
- itemsPackedInOrder;
private List layers;
+ private CancellationToken algorithmCancellationToken;
private ContainerPackingResult result;
private ScrapPad scrapfirst;
@@ -70,7 +70,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 +287,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) && !algorithmCancellationToken.IsCancellationRequested; containerOrientationVariant++)
{
switch (containerOrientationVariant)
{
@@ -321,7 +320,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) && !algorithmCancellationToken.IsCancellationRequested; layersIndex++)
{
packedVolume = 0.0M;
packedy = 0;
@@ -347,7 +346,7 @@ private void ExecuteIterations(Container container)
packedy = packedy + layerThickness;
remainpy = py - packedy;
- if (layerinlayer != 0 && !quit)
+ if (layerinlayer != 0 && !algorithmCancellationToken.IsCancellationRequested)
{
prepackedy = packedy;
preremainpy = remainpy;
@@ -365,9 +364,9 @@ private void ExecuteIterations(Container container)
}
FindLayer(remainpy);
- } while (packing && !quit);
+ } while (packing && !algorithmCancellationToken.IsCancellationRequested);
- if ((packedVolume > bestVolume) && !quit)
+ if ((packedVolume > bestVolume) && !algorithmCancellationToken.IsCancellationRequested)
{
bestVolume = packedVolume;
bestVariant = containerOrientationVariant;
@@ -525,10 +524,12 @@ private void FindSmallestZ()
///
/// Initializes everything.
///
- private void Initialize(Container container, List
- items)
+ private void Initialize(Container container, List
- items, CancellationToken cancellationToken)
{
itemsToPack = new List
- ();
itemsPackedInOrder = new List
- ();
+ algorithmCancellationToken = cancellationToken;
+
result = new ContainerPackingResult();
// The original code uses 1-based indexing everywhere. This fake entry is added to the beginning
@@ -565,13 +566,12 @@ private void Initialize(Container container, List
- items)
scrapfirst.Post = null;
packingBest = false;
hundredPercentPacked = false;
- quit = false;
}
- ///
- /// Lists all possible layer heights by giving a weight value to each of them.
- ///
- private void ListCanditLayers()
+ ///
+ /// Lists all possible layer heights by giving a weight value to each of them.
+ ///
+ private void ListCanditLayers()
{
bool same;
decimal exdim = 0;
@@ -752,7 +752,7 @@ private void PackLayer()
scrapfirst.CumX = px;
scrapfirst.CumZ = 0;
- for (; !quit;)
+ for (; !algorithmCancellationToken.IsCancellationRequested;)
{
FindSmallestZ();
@@ -1037,8 +1037,6 @@ private void PackLayer()
///
private void Report(Container container)
{
- quit = false;
-
switch (bestVariant)
{
case 1:
@@ -1112,11 +1110,11 @@ private void Report(Container container)
remainpz = pz;
}
- if (!quit)
+ if (!algorithmCancellationToken.IsCancellationRequested)
{
FindLayer(remainpy);
}
- } while (packing && !quit);
+ } while (packing && !algorithmCancellationToken.IsCancellationRequested);
}
///
diff --git a/src/CromulentBisgetti.ContainerPacking/Algorithms/IPackingAlgorithm.cs b/src/CromulentBisgetti.ContainerPacking/Algorithms/IPackingAlgorithm.cs
index 4213bee..eff5bbc 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.
+ /// Algorith will check this token to detect if the current packing attempt should be cancelled.
/// 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..1c6a73c 100644
--- a/src/CromulentBisgetti.ContainerPacking/PackingService.cs
+++ b/src/CromulentBisgetti.ContainerPacking/PackingService.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
namespace CromulentBisgetti.ContainerPacking
@@ -20,7 +21,14 @@ public static class PackingService
/// The items to pack.
/// 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)
+ 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,7 +53,7 @@ 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;
diff --git a/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingCancelTests.cs b/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingCancelTests.cs
new file mode 100644
index 0000000..1d3445b
--- /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");
+ }
+ }
+}
diff --git a/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingTests.cs b/src/CromulentBisgetti.ContainerPackingTests/ContainerPackingTests.cs
index 897479c..d9f6f10 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))
@@ -53,7 +57,9 @@ public void EB_AFIT_Passes_700_Standard_Reference_Tests()
containers.Add(new Container(0, Convert.ToDecimal(containerDims[0]), Convert.ToDecimal(containerDims[1]), Convert.ToDecimal(containerDims[2])));
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,13 +67,15 @@ 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.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]));
+ Assert.AreEqual(result[0].AlgorithmPackingResults[0].PercentItemVolumePacked, Convert.ToDecimal(testResults[4], decimalPointCulture));
counter++;
}