using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening; namespace ZB.MOM.WW.ScadaBridge.DeploymentManager; /// /// Memoises the repository reads that performs, /// for the lifetime of ONE flatten/validate session. /// /// /// Without a session every instance flatten re-walks its whole template /// inheritance chain (one query per link), re-loads the compositions of every /// template in that chain and of every composed chain it reaches, and re-issues /// the three session-global queries — shared scripts, the shared-schema library, /// and the target site's data connections. For a bulk deploy of N instances off /// the same template that is N× the same work; even a single instance re-loads a /// composed chain once per composing template. /// /// /// /// Cache key. Templates are keyed on (id, ITemplateGraphWatermark /// version) and chain MEMBERSHIP additionally on the watermark's /// , so a template edited /// mid-session is re-read rather than served from the memo. The session is also /// short-lived by construction — it is created per deploy/validate operation and /// discarded with it — so a cached graph can never outlive the operation that /// captured it. That is the design's "template state is captured at the time of /// flatten" guarantee, unchanged: the session narrows the capture window, it does /// not widen it. /// /// /// /// Threading. A session is NOT thread-safe and must not be shared across /// threads. It caches entities materialised by one scoped DbContext, which /// is itself single-threaded, and it hands out tracked entities that the /// in-flight bundle importer relies on observing. DeploySiteAsync honours /// this by running the whole prepare (flatten) phase serially on one scope and /// parallelising only the site round-trips, which touch no repository at all. /// /// public sealed class FlattenSession { private readonly ITemplateGraphWatermark _watermark; private readonly Dictionary _templates = new(); private readonly Dictionary _chains = new(); private readonly Dictionary Compositions)> _compositions = new(); private readonly Dictionary> _dataConnections = new(); private IReadOnlyList? _sharedScripts; private IReadOnlyDictionary? _schemaLibrary; /// Initializes a session bound to the process-wide graph watermark. /// Watermark supplying the per-template versions used as cache keys. public FlattenSession(ITemplateGraphWatermark watermark) { _watermark = watermark ?? throw new ArgumentNullException(nameof(watermark)); } /// /// Number of template-chain walks that actually hit the repository. Reset /// never; used by tests to assert that N instances sharing a template load /// that chain exactly once. /// public int ChainLoads { get; private set; } /// Number of single-template repository reads that missed the memo. public int TemplateLoads { get; private set; } /// Number of per-template composition queries that missed the memo. public int CompositionLoads { get; private set; } /// Number of times the session-global queries (shared scripts, schema library) ran. public int GlobalLoads { get; private set; } /// Number of per-site data-connection queries that missed the memo. public int DataConnectionLoads { get; private set; } /// /// Every template id this session resolved — the union of all inheritance and /// composed chains it walked. The staleness fast path records the watermark /// version of exactly this set, so a change to ANY template that fed a /// flatten invalidates the memoised revision hash. /// public IReadOnlyCollection VisitedTemplateIds => _templates.Keys; /// /// Every visited template id paired with the watermark version this session /// READ it at — the pre-flatten watermark the staleness fast path must memoise /// against. /// /// /// Re-reading the versions after the flatten instead is a correctness bug, not /// an optimisation detail: an edit that lands mid-flatten pairs the PRE-edit /// content (and therefore the pre-edit hash) with the POST-edit version, and /// the memo then answers "not stale" until something else moves the watermark. /// Recording the version each template was read at can only ever cause an extra /// flatten, never a stale answer. /// /// public IReadOnlyDictionary VisitedTemplateVersions => _templates.ToDictionary(entry => entry.Key, entry => entry.Value.Version); /// /// Returns the inheritance chain for (the /// template itself, then each ancestor), loading it through /// on a miss. /// /// Root of the chain to build. /// Repository read for one template with its children. /// The chain, ordered leaf-first. public async Task> GetChainAsync( int templateId, Func> loadTemplate) { var structure = _watermark.StructureVersion; if (_chains.TryGetValue(templateId, out var cached) && IsChainCurrent(cached, structure)) return cached.Chain; var chain = new List