using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest.Diagnostics;
///
/// Reuses each clean drawing program's converted entities and prepared (validated, chorded,
/// triangulation-checked) material across overlap requests. Preparation dominates the cost of
/// drawings with many holes, and it depends only on the drawing, not on where parts sit, so a
/// recheck after moving parts only repeats the cheap pose transforms and pair clipping.
/// Entries are keyed by reference and released with it. A changed code
/// count or program rotation is detected, but that is not a geometry hash: call
/// before any in-place edit of a clean program or its hole subprograms.
/// Capture on one thread at a time; prepared entries may be shared by concurrent analyses.
///
public sealed class OverlapMaterialCache
{
private readonly ConditionalWeakTable sources = new();
public void Clear() => sources.Clear();
internal OverlapSource Get(Program program, Func create)
{
if (sources.TryGetValue(program, out var source) && source.Matches(program))
return source;
source = create(program);
sources.AddOrUpdate(program, source);
return source;
}
}
/// Owned converted entities for one clean program, plus its lazily prepared material.
internal sealed class OverlapSource
{
private readonly object gate = new();
private readonly int codeCount;
private readonly long rotation;
private PreparedMaterial prepared;
internal OverlapSource(Program program, List entities, string error)
{
codeCount = program.Codes.Count;
rotation = BitConverter.DoubleToInt64Bits(program.Rotation);
Entities = entities;
Error = error;
}
/// Never mutated; preparation clones before chaining.
internal List Entities { get; }
internal string Error { get; }
internal bool Matches(Program program) =>
program.Codes.Count == codeCount && BitConverter.DoubleToInt64Bits(program.Rotation) == rotation;
///
/// Prepares once and shares the result. A geometry failure is cached like a success;
/// cancellation is not, so a superseded request cannot poison the next one.
///
internal PreparedMaterial Prepare(CancellationToken cancellationToken)
{
var current = Volatile.Read(ref prepared);
if (current != null)
return current;
lock (gate)
{
if (prepared != null)
return prepared;
PreparedMaterial result;
try
{
result = new PreparedMaterial(OverlapMaterial.Read(Entities, cancellationToken), null);
}
catch (Exception exception) when (PlateOverlapAnalyzer.IsGeometryFailure(exception))
{
result = new PreparedMaterial(null, exception.Message);
}
Volatile.Write(ref prepared, result);
return result;
}
}
}
/// Validated local-frame material, only ever read (transformed into new polygons).
internal sealed record PreparedMaterial(OverlapMaterial Material, string Error);