using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.OpcUaServer; /// One walked-back reverse events page (). /// At most the requested count, newest first by (EventTimeUtc, EventId). /// What the next page of the same read resumes with. internal readonly record struct ReverseEventsPage(List Events, ReverseWalkHint Hint); /// /// Gitea #691.4: serves a Part 11 reverse events read — (lo, hi] newest-first — on a backend that reads events /// forward only ( = false: the Gateway) with the /// . A segment read is cut short when the source flags truncation (its continuation point); ties /// are ordered by because the gateway orders by event time only. /// internal static class ReverseEventsReader { /// Reads (lo, hi] newest-first from forward event reads. /// A backend that reads events forward. /// Event-source filter (null = all). /// The later (inclusive) bound. /// The earlier (exclusive) bound. /// How many events; 0 = all in the window (one forward read). /// The tie budget (MaxTieClusterOverfetch) — a ceiling only. /// Now, for the look-ahead clamp. /// What the previous page carried (default = none). /// Cancellation. /// The events, newest first, and the hint for the next page. /// One timestamp holds more than the tie budget. public static async Task ReadAsync( IHistorianDataSource source, string? sourceName, DateTime hi, DateTime lo, uint want, int maxTieCluster, DateTime nowUtc, ReverseWalkHint hint, CancellationToken ct) { var budget = Math.Max(1, maxTieCluster); var (rows, next) = await ReverseWalk.WalkAsync( async (start, end, cap, token) => { // cap 0 = the whole window: int.MaxValue is the events contract's "no cap" (a non-positive value would // mean the backend's DEFAULT cap). var r = await source.ReadEventsAsync(sourceName, start, end, cap == 0 ? int.MaxValue : cap, reverse: false, token) .ConfigureAwait(false); return new ForwardSegmentRead(r.Events, r.ContinuationPoint is not null); }, ev => ev.EventTimeUtc.Ticks, EventIdTieOrder.Instance, hi, lo, want, budget, source.TimestampResolutionTicks, nowUtc, hint, at => new HistoryReadLimitExceededException(budget, $"A reverse events read of '{sourceName}' at {at:O} found more than {budget} events within one millisecond; the " + "backend reads forward only, so the latest of them cannot be selected. Increase " + "ServerHistorian:MaxTieClusterOverfetch."), ct) .ConfigureAwait(false); return new ReverseEventsPage(rows, next); } }