Refactor JunctionFiller and SchedulePlanner to improve parameter handling by introducing JunctionPlacement record for cleaner code and better readability. Update Directory.Build.props to suppress additional warnings related to API boundaries and unused route parameters, ensuring a more focused analysis during builds.
This commit is contained in:
@@ -18,6 +18,19 @@ public sealed class JunctionHistory
|
||||
_lastPlaced[element.Kind] = moment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Где ставится стык и между чем. Собрано в один параметр: по отдельности эти четыре значения ехали
|
||||
/// сквозь всю раскладку и вместе с курсором, пределом и накопителями раздували сигнатуры.
|
||||
/// </summary>
|
||||
/// <param name="FromShowId">Шоу перед стыком, <paramref name="ToShowId"/> — после; заставке нужна
|
||||
/// именно пара соседей, её ассет рендерится под неё после сборки ленты.</param>
|
||||
public sealed record JunctionPlacement(
|
||||
Guid SlotId,
|
||||
Guid? FromShowId,
|
||||
Guid? ToShowId,
|
||||
bool ElementChanged
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Раскладка врезок стыка: реклама, промо, заставка, заполнитель. Ставит только то, что влезает
|
||||
/// целиком до предела (якорь или горизонт) — обрезать врезку нельзя, а перехлёст сдвинул бы якорь.
|
||||
@@ -27,19 +40,12 @@ public sealed class JunctionHistory
|
||||
/// </summary>
|
||||
public static class JunctionFiller
|
||||
{
|
||||
/// <summary>
|
||||
/// Раскладывает стык от <paramref name="cursor"/>. <paramref name="fromShowId"/>/<paramref name="toShowId"/>
|
||||
/// нужны заставке: её ассет зависит от пары соседей и рендерится после сборки ленты.
|
||||
/// Возвращает курсор после стыка.
|
||||
/// </summary>
|
||||
/// <summary>Раскладывает стык от <paramref name="cursor"/>. Возвращает курсор после стыка.</summary>
|
||||
public static DateTimeOffset Fill(
|
||||
PlanningJunction? junction,
|
||||
DateTimeOffset cursor,
|
||||
DateTimeOffset limit,
|
||||
Guid slotId,
|
||||
Guid? fromShowId,
|
||||
Guid? toShowId,
|
||||
bool elementChanged,
|
||||
JunctionPlacement placement,
|
||||
JunctionHistory history,
|
||||
List<PlannedItem> items,
|
||||
PlanTrace? trace
|
||||
@@ -55,7 +61,7 @@ public static class JunctionFiller
|
||||
|
||||
foreach (var element in ordered)
|
||||
{
|
||||
if (element.OnlyOnElementChange && !elementChanged)
|
||||
if (element.OnlyOnElementChange && !placement.ElementChanged)
|
||||
continue;
|
||||
if (!history.Allows(element, cursor))
|
||||
continue;
|
||||
@@ -63,8 +69,8 @@ public static class JunctionFiller
|
||||
var placedAt = cursor;
|
||||
cursor =
|
||||
element.Kind == JunctionElementKind.Bumper
|
||||
? PlaceBumper(element, cursor, limit, slotId, fromShowId, toShowId, items, trace)
|
||||
: PlaceUnits(element, cursor, limit, slotId, items, trace);
|
||||
? PlaceBumper(element, cursor, limit, placement, items, trace)
|
||||
: PlaceUnits(element, cursor, limit, placement.SlotId, items, trace);
|
||||
|
||||
if (cursor > placedAt)
|
||||
history.Record(element, placedAt);
|
||||
@@ -81,9 +87,7 @@ public static class JunctionFiller
|
||||
PlanningJunctionElement element,
|
||||
DateTimeOffset cursor,
|
||||
DateTimeOffset limit,
|
||||
Guid slotId,
|
||||
Guid? fromShowId,
|
||||
Guid? toShowId,
|
||||
JunctionPlacement placement,
|
||||
List<PlannedItem> items,
|
||||
PlanTrace? trace
|
||||
)
|
||||
@@ -97,14 +101,14 @@ public static class JunctionFiller
|
||||
Guid.Empty,
|
||||
cursor,
|
||||
end,
|
||||
toShowId,
|
||||
placement.ToShowId,
|
||||
null,
|
||||
slotId,
|
||||
placement.SlotId,
|
||||
PlannedItemKind.Bumper,
|
||||
trace,
|
||||
element.BumperTemplateId,
|
||||
fromShowId,
|
||||
toShowId
|
||||
placement.FromShowId,
|
||||
placement.ToShowId
|
||||
)
|
||||
);
|
||||
return end;
|
||||
|
||||
@@ -14,20 +14,35 @@ public static class SchedulePlanner
|
||||
{
|
||||
private const int IterationBackstop = 100_000;
|
||||
|
||||
/// <summary>
|
||||
/// Накопители одного прогона: собираемая лента, новые курсоры слотов, предупреждения, история
|
||||
/// врезок и шоу последней поставленной единицы. Всё это протаскивалось через сигнатуры десятком
|
||||
/// параметров (включая <c>ref</c>), хотя принадлежит прогону целиком, а не отдельному шагу.
|
||||
///
|
||||
/// История врезок общая на прогон намеренно: «не чаще раза в полчаса» должно работать и через
|
||||
/// границу слота.
|
||||
/// </summary>
|
||||
private sealed class PlanningRun(PlanningInput input, IRandomSource random)
|
||||
{
|
||||
public PlanningInput Input { get; } = input;
|
||||
public IRandomSource Random { get; } = random;
|
||||
public List<PlannedItem> Items { get; } = [];
|
||||
public List<PlanningCursorUpdate> Cursors { get; } = [];
|
||||
public List<PlanningWarning> Warnings { get; } = [];
|
||||
public JunctionHistory Junctions { get; } = new();
|
||||
|
||||
/// <summary>Шоу последней поставленной единицы — по нему стык понимает, сменился ли элемент.</summary>
|
||||
public Guid? PreviousShowId { get; set; }
|
||||
}
|
||||
|
||||
public static PlanningResult Plan(PlanningInput input, IRandomSource random)
|
||||
{
|
||||
var items = new List<PlannedItem>();
|
||||
var cursors = new List<PlanningCursorUpdate>();
|
||||
var warnings = new List<PlanningWarning>();
|
||||
var run = new PlanningRun(input, random);
|
||||
|
||||
var slots = input.Slots.OrderBy(s => s.TargetStartUtc).ToList();
|
||||
var cursor = input.StartUtc;
|
||||
var iterations = 0;
|
||||
|
||||
// История врезок общая на прогон: «не чаще раза в полчаса» должно работать и через границу слота.
|
||||
var junctions = new JunctionHistory();
|
||||
Guid? previousShowId = null;
|
||||
|
||||
for (var i = 0; i < slots.Count && cursor < input.HorizonEndUtc; i++)
|
||||
{
|
||||
if (iterations++ > IterationBackstop)
|
||||
@@ -42,32 +57,20 @@ public static class SchedulePlanner
|
||||
|
||||
var nextAnchor = FindNextAnchor(slots, i + 1);
|
||||
|
||||
cursor = OpenSlot(slot, cursor, input, items, warnings, out var trace);
|
||||
cursor = OpenSlot(slot, cursor, run, out var trace);
|
||||
if (cursor >= input.HorizonEndUtc)
|
||||
break;
|
||||
|
||||
cursor = FillSlot(
|
||||
slot,
|
||||
cursor,
|
||||
nextAnchor,
|
||||
input,
|
||||
random,
|
||||
items,
|
||||
cursors,
|
||||
warnings,
|
||||
trace,
|
||||
junctions,
|
||||
ref previousShowId
|
||||
);
|
||||
cursor = FillSlot(slot, cursor, nextAnchor, trace, run);
|
||||
}
|
||||
|
||||
// Хвост до горизонта закрывает фон: лента обязана быть непрерывной, иначе живой край
|
||||
// упрётся в дыру.
|
||||
if (cursor < input.HorizonEndUtc)
|
||||
cursor = FillWithFallback(cursor, input.HorizonEndUtc, input, items, null);
|
||||
cursor = FillWithFallback(cursor, input.HorizonEndUtc, run, null);
|
||||
|
||||
if (cursor < input.HorizonEndUtc && input.FallbackUnits.Count == 0)
|
||||
warnings.Add(
|
||||
run.Warnings.Add(
|
||||
new PlanningWarning(
|
||||
PlanningWarningKind.FallbackEmpty,
|
||||
null,
|
||||
@@ -75,7 +78,7 @@ public static class SchedulePlanner
|
||||
)
|
||||
);
|
||||
|
||||
return new PlanningResult(items, cursors, warnings);
|
||||
return new PlanningResult(run.Items, run.Cursors, run.Warnings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -85,9 +88,7 @@ public static class SchedulePlanner
|
||||
private static DateTimeOffset OpenSlot(
|
||||
PlanningSlot slot,
|
||||
DateTimeOffset cursor,
|
||||
PlanningInput input,
|
||||
List<PlannedItem> items,
|
||||
List<PlanningWarning> warnings,
|
||||
PlanningRun run,
|
||||
out PlanTrace trace
|
||||
)
|
||||
{
|
||||
@@ -97,7 +98,7 @@ public static class SchedulePlanner
|
||||
{
|
||||
// До целевого времени ещё есть место — закрываем его фоном. Для якоря это обязательно,
|
||||
// для обычного слота тоже: иначе он начнётся раньше объявленного в программе времени.
|
||||
cursor = FillWithFallback(cursor, slot.TargetStartUtc, input, items, slot.SlotId);
|
||||
cursor = FillWithFallback(cursor, slot.TargetStartUtc, run, slot.SlotId);
|
||||
}
|
||||
else if (slot.SnapToMinutes is { } snap && snap > 0)
|
||||
{
|
||||
@@ -106,7 +107,7 @@ public static class SchedulePlanner
|
||||
var target = RoundUp(cursor, TimeSpan.FromMinutes(snap));
|
||||
if (target - cursor <= TimeSpan.FromMinutes(slot.MaxDriftMinutes))
|
||||
{
|
||||
var afterFill = FillWithFallback(cursor, target, input, items, slot.SlotId);
|
||||
var afterFill = FillWithFallback(cursor, target, run, slot.SlotId);
|
||||
snapped = afterFill > cursor;
|
||||
cursor = afterFill;
|
||||
}
|
||||
@@ -114,7 +115,7 @@ public static class SchedulePlanner
|
||||
|
||||
var drift = (int)Math.Round((cursor - slot.TargetStartUtc).TotalMinutes);
|
||||
if (Math.Abs(drift) > slot.MaxDriftMinutes)
|
||||
warnings.Add(
|
||||
run.Warnings.Add(
|
||||
new PlanningWarning(
|
||||
PlanningWarningKind.DriftExceeded,
|
||||
slot.SlotId,
|
||||
@@ -140,17 +141,11 @@ public static class SchedulePlanner
|
||||
PlanningSlot slot,
|
||||
DateTimeOffset cursor,
|
||||
DateTimeOffset? nextAnchor,
|
||||
PlanningInput input,
|
||||
IRandomSource random,
|
||||
List<PlannedItem> items,
|
||||
List<PlanningCursorUpdate> cursors,
|
||||
List<PlanningWarning> warnings,
|
||||
PlanTrace trace,
|
||||
JunctionHistory junctions,
|
||||
ref Guid? previousShowId
|
||||
PlanningRun run
|
||||
)
|
||||
{
|
||||
var limit = Min(input.HorizonEndUtc, nextAnchor);
|
||||
var limit = Min(run.Input.HorizonEndUtc, nextAnchor);
|
||||
|
||||
switch (slot.SlotKind)
|
||||
{
|
||||
@@ -159,30 +154,17 @@ public static class SchedulePlanner
|
||||
return FillWithFallback(
|
||||
cursor,
|
||||
Min(slot.TargetEndUtc, limit),
|
||||
input,
|
||||
items,
|
||||
run,
|
||||
slot.SlotId,
|
||||
PlannedItemKind.SignOff,
|
||||
trace
|
||||
);
|
||||
|
||||
case SlotKind.Repeat:
|
||||
return FillRepeat(slot, cursor, limit, input, items, warnings, trace);
|
||||
return FillRepeat(slot, cursor, limit, trace, run);
|
||||
|
||||
default:
|
||||
return FillContent(
|
||||
slot,
|
||||
cursor,
|
||||
limit,
|
||||
input,
|
||||
random,
|
||||
items,
|
||||
cursors,
|
||||
warnings,
|
||||
trace,
|
||||
junctions,
|
||||
ref previousShowId
|
||||
);
|
||||
return FillContent(slot, cursor, limit, trace, run);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,29 +172,21 @@ public static class SchedulePlanner
|
||||
PlanningSlot slot,
|
||||
DateTimeOffset cursor,
|
||||
DateTimeOffset limit,
|
||||
PlanningInput input,
|
||||
List<PlannedItem> items,
|
||||
List<PlanningWarning> warnings,
|
||||
PlanTrace trace
|
||||
PlanTrace trace,
|
||||
PlanningRun run
|
||||
)
|
||||
{
|
||||
var units = slot.RepeatUnits ?? [];
|
||||
if (units.Count == 0)
|
||||
{
|
||||
warnings.Add(
|
||||
run.Warnings.Add(
|
||||
new PlanningWarning(
|
||||
PlanningWarningKind.RepeatSourceEmpty,
|
||||
slot.SlotId,
|
||||
"В источнике повтора ничего не нашлось — слот закрыт фоном."
|
||||
)
|
||||
);
|
||||
return FillWithFallback(
|
||||
cursor,
|
||||
Min(slot.TargetEndUtc, limit),
|
||||
input,
|
||||
items,
|
||||
slot.SlotId
|
||||
);
|
||||
return FillWithFallback(cursor, Min(slot.TargetEndUtc, limit), run, slot.SlotId);
|
||||
}
|
||||
|
||||
var slotEnd = Min(slot.TargetEndUtc, limit);
|
||||
@@ -220,7 +194,7 @@ public static class SchedulePlanner
|
||||
{
|
||||
if (cursor + unit.Duration > slotEnd)
|
||||
break;
|
||||
items.Add(Program(unit, cursor, slot.SlotId, trace));
|
||||
run.Items.Add(Program(unit, cursor, slot.SlotId, trace));
|
||||
cursor += unit.Duration;
|
||||
}
|
||||
|
||||
@@ -231,31 +205,19 @@ public static class SchedulePlanner
|
||||
PlanningSlot slot,
|
||||
DateTimeOffset cursor,
|
||||
DateTimeOffset limit,
|
||||
PlanningInput input,
|
||||
IRandomSource random,
|
||||
List<PlannedItem> items,
|
||||
List<PlanningCursorUpdate> cursors,
|
||||
List<PlanningWarning> warnings,
|
||||
PlanTrace trace,
|
||||
JunctionHistory junctions,
|
||||
ref Guid? previousShowId
|
||||
PlanningRun run
|
||||
)
|
||||
{
|
||||
var pick = ElementSelector.Select(slot, cursor, random);
|
||||
var pick = ElementSelector.Select(slot, cursor, run.Random);
|
||||
if (pick is null)
|
||||
{
|
||||
warnings.Add(NoCandidatesWarning(slot));
|
||||
return FillWithFallback(
|
||||
cursor,
|
||||
Min(slot.TargetEndUtc, limit),
|
||||
input,
|
||||
items,
|
||||
slot.SlotId
|
||||
);
|
||||
run.Warnings.Add(NoCandidatesWarning(slot));
|
||||
return FillWithFallback(cursor, Min(slot.TargetEndUtc, limit), run, slot.SlotId);
|
||||
}
|
||||
|
||||
if (pick.CooldownExhausted)
|
||||
warnings.Add(
|
||||
run.Warnings.Add(
|
||||
new PlanningWarning(
|
||||
PlanningWarningKind.CooldownExhausted,
|
||||
slot.SlotId,
|
||||
@@ -283,8 +245,8 @@ public static class SchedulePlanner
|
||||
&& !FitsEntirely(element, unitIndex, cursor, budgetEnd)
|
||||
)
|
||||
{
|
||||
cursors.Add(CursorUpdate(slot, element, unitIndex));
|
||||
return FillWithFallback(cursor, budgetEnd, input, items, slot.SlotId);
|
||||
run.Cursors.Add(CursorUpdate(slot, element, unitIndex));
|
||||
return FillWithFallback(cursor, budgetEnd, run, slot.SlotId);
|
||||
}
|
||||
|
||||
while (unitIndex < element.Units.Count && cursor < limit)
|
||||
@@ -297,12 +259,14 @@ public static class SchedulePlanner
|
||||
slot.JunctionBetween,
|
||||
cursor,
|
||||
limit,
|
||||
slot.SlotId,
|
||||
previousShowId,
|
||||
unit.ShowId,
|
||||
elementChanged: previousShowId != unit.ShowId,
|
||||
junctions,
|
||||
items,
|
||||
new JunctionPlacement(
|
||||
slot.SlotId,
|
||||
run.PreviousShowId,
|
||||
unit.ShowId,
|
||||
ElementChanged: run.PreviousShowId != unit.ShowId
|
||||
),
|
||||
run.Junctions,
|
||||
run.Items,
|
||||
slotTrace
|
||||
);
|
||||
|
||||
@@ -313,12 +277,12 @@ public static class SchedulePlanner
|
||||
if (!WithinBudget(slot, placed, accumulated, cursor, unit, budgetEnd))
|
||||
break;
|
||||
|
||||
items.Add(Program(unit, cursor, slot.SlotId, slotTrace, CollectionOf(element)));
|
||||
run.Items.Add(Program(unit, cursor, slot.SlotId, slotTrace, CollectionOf(element)));
|
||||
cursor += unit.Duration;
|
||||
accumulated += unit.Duration;
|
||||
unitIndex++;
|
||||
placed++;
|
||||
previousShowId = unit.ShowId;
|
||||
run.PreviousShowId = unit.ShowId;
|
||||
}
|
||||
|
||||
// Врезки в конце блока ставятся до добора фоном: иначе реклама оказалась бы после заполнителя.
|
||||
@@ -327,20 +291,22 @@ public static class SchedulePlanner
|
||||
slot.JunctionAfter,
|
||||
cursor,
|
||||
limit,
|
||||
slot.SlotId,
|
||||
previousShowId,
|
||||
null,
|
||||
elementChanged: true,
|
||||
junctions,
|
||||
items,
|
||||
new JunctionPlacement(
|
||||
slot.SlotId,
|
||||
run.PreviousShowId,
|
||||
null,
|
||||
ElementChanged: true
|
||||
),
|
||||
run.Junctions,
|
||||
run.Items,
|
||||
slotTrace
|
||||
);
|
||||
|
||||
cursors.Add(CursorUpdate(slot, element, unitIndex));
|
||||
run.Cursors.Add(CursorUpdate(slot, element, unitIndex));
|
||||
|
||||
// Недобор до целевого конца закрываем фоном — только для слотов, чей бюджет привязан ко времени.
|
||||
if (slot.BlockMode == SlotBlockMode.FillSlot && cursor < budgetEnd)
|
||||
cursor = FillWithFallback(cursor, budgetEnd, input, items, slot.SlotId);
|
||||
cursor = FillWithFallback(cursor, budgetEnd, run, slot.SlotId);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
@@ -416,14 +382,14 @@ public static class SchedulePlanner
|
||||
private static DateTimeOffset FillWithFallback(
|
||||
DateTimeOffset from,
|
||||
DateTimeOffset until,
|
||||
PlanningInput input,
|
||||
List<PlannedItem> items,
|
||||
PlanningRun run,
|
||||
Guid? slotId,
|
||||
PlannedItemKind kind = PlannedItemKind.Fallback,
|
||||
PlanTrace? trace = null
|
||||
)
|
||||
{
|
||||
if (input.FallbackUnits.Count == 0 || until <= from)
|
||||
var fallback = run.Input.FallbackUnits;
|
||||
if (fallback.Count == 0 || until <= from)
|
||||
return from;
|
||||
|
||||
var cursor = from;
|
||||
@@ -432,13 +398,13 @@ public static class SchedulePlanner
|
||||
|
||||
while (cursor < until && guard++ < IterationBackstop)
|
||||
{
|
||||
var unit = input.FallbackUnits[index % input.FallbackUnits.Count];
|
||||
var unit = fallback[index % fallback.Count];
|
||||
index++;
|
||||
|
||||
if (unit.Duration <= TimeSpan.Zero || cursor + unit.Duration > until)
|
||||
break;
|
||||
|
||||
items.Add(
|
||||
run.Items.Add(
|
||||
new PlannedItem(
|
||||
unit.MediaAssetId,
|
||||
cursor,
|
||||
|
||||
Reference in New Issue
Block a user