diff --git a/cmd/qrunproxy/mockshow.go b/cmd/qrunproxy/mockshow.go index 81ff2b7..d96d200 100644 --- a/cmd/qrunproxy/mockshow.go +++ b/cmd/qrunproxy/mockshow.go @@ -80,11 +80,22 @@ func GenerateMockShow(numTracks, numScenes, avgCuesPerScene, avgBlocksPerCue int chainFromByTrack := make(map[int]*Block) needsEnd := make(map[string]*Block) + triggerIdx := make(map[TriggerSource]*Trigger) allowedTracks := make(map[int]bool, numTracks) for i := range numTracks { allowedTracks[i] = true } + addTrigger := func(source TriggerSource, target TriggerTarget) { + if t := triggerIdx[source]; t != nil { + t.Targets = append(t.Targets, target) + return + } + t := &Trigger{Source: source, Targets: []TriggerTarget{target}} + show.Triggers = append(show.Triggers, t) + triggerIdx[source] = t + } + for scene := 1; scene <= numScenes; scene++ { cuesInScene := 1 + rng.IntN(avgCuesPerScene*2) @@ -126,11 +137,27 @@ func GenerateMockShow(numTracks, numScenes, avgCuesPerScene, avgBlocksPerCue int block := randBlock(trackIdx) show.Blocks = append(show.Blocks, block) if prev := chainFromByTrack[trackIdx]; prev != nil { - show.Triggers = append(show.Triggers, &Trigger{ - Source: TriggerSource{Block: prev.ID, Signal: "END"}, - Targets: []TriggerTarget{{Block: block.ID, Hook: "START"}}, - }) + addTrigger( + TriggerSource{Block: prev.ID, Signal: "END"}, + TriggerTarget{Block: block.ID, Hook: "START"}, + ) delete(needsEnd, prev.ID) + } else if rng.Float64() < 0.3 { + var crossSrc *Block + for ti, blk := range chainFromByTrack { + if ti != trackIdx && blk != nil && needsEnd[blk.ID] == nil { + crossSrc = blk + break + } + } + if crossSrc != nil { + addTrigger( + TriggerSource{Block: crossSrc.ID, Signal: "END"}, + TriggerTarget{Block: block.ID, Hook: "START"}, + ) + } else { + cueTargets = append(cueTargets, TriggerTarget{Block: block.ID, Hook: "START"}) + } } else { cueTargets = append(cueTargets, TriggerTarget{Block: block.ID, Hook: "START"}) } diff --git a/cmd/qrunproxy/show.go b/cmd/qrunproxy/show.go index ccce6ee..29924e9 100644 --- a/cmd/qrunproxy/show.go +++ b/cmd/qrunproxy/show.go @@ -26,6 +26,14 @@ type Trigger struct { Targets []TriggerTarget `json:"targets"` } +func (t *Trigger) String() string { + s := fmt.Sprintf("%s/%s ->", t.Source.Block, t.Source.Signal) + for _, target := range t.Targets { + s += fmt.Sprintf(" %s/%s", target.Block, target.Hook) + } + return s +} + type TriggerSource struct { Block string `json:"block"` Signal string `json:"signal"` @@ -92,11 +100,33 @@ func (show *Show) Validate() error { hookTargeted := map[blockEvent]bool{} startTargeted := map[string]bool{} sourceUsed := map[blockEvent]bool{} + signalTargetedBy := map[blockEvent]*Trigger{} + + for _, trigger := range show.Triggers { + for _, target := range trigger.Targets { + signalTargetedBy[blockEvent{target.Block, target.Hook}] = trigger + } + } + for _, trigger := range show.Triggers { sourceBlock := blocksByID[trigger.Source.Block] if sourceBlock == nil { return fmt.Errorf("trigger source block %q not found", trigger.Source.Block) } + + targetedTracks := map[string]string{} + for _, target := range trigger.Targets { + targetBlock := blocksByID[target.Block] + if prev, ok := targetedTracks[targetBlock.Track]; ok { + return fmt.Errorf("trigger conflict: %s targets multiple blocks on track %q (%q and %q)", + trigger, targetBlock.Track, prev, target.Block) + } + targetedTracks[targetBlock.Track] = target.Block + } + + if t, ok := signalTargetedBy[blockEvent{trigger.Source.Block, trigger.Source.Signal}]; ok { + return fmt.Errorf("trigger conflict: %s vs %s", t, trigger) + } if !isValidEventForBlock(sourceBlock, trigger.Source.Signal) { return fmt.Errorf("trigger source signal %q is invalid for block %q", trigger.Source.Signal, trigger.Source.Block) } diff --git a/cmd/qrunproxy/timeline.go b/cmd/qrunproxy/timeline.go index 2d706aa..6a58291 100644 --- a/cmd/qrunproxy/timeline.go +++ b/cmd/qrunproxy/timeline.go @@ -257,13 +257,16 @@ func (tl *Timeline) buildConstraints() { } func (tl *Timeline) assignRows() error { - for range 1000000 { + for i := range 1000000 { if tl.enforceConstraints() { continue } if tl.enforceExclusives() { continue } + if i > 0 { + fmt.Printf("assignRows: converged in %d iterations\n", i) + } return nil } for _, c := range tl.constraints { @@ -284,6 +287,7 @@ func (tl *Timeline) enforceConstraints() bool { if c.satisfied() { continue } + fmt.Printf("enforceConstraints: unsatisfied %s\n", c) switch c.kind { case constraintSameRow: if c.a.row < c.b.row { @@ -304,6 +308,7 @@ func (tl *Timeline) enforceExclusives() bool { if g.satisfied(tl.Tracks) { continue } + fmt.Printf("enforceExclusives: unsatisfied %s\n", g) row := g.members[0].row memberTracks := map[*TimelineTrack]bool{} for _, m := range g.members { @@ -316,6 +321,7 @@ func (tl *Timeline) enforceExclusives() bool { if !t.cellTypeAt(row, CellEvent, CellTitle, CellSignal) { continue } + fmt.Printf("enforceExclusives: inserting gap in track %s at row %d\n", t.ID, row) tl.insertGap(t, row) return true } @@ -344,6 +350,7 @@ func (tl *Timeline) isAllRemovableGapRow(row int, except *TimelineTrack) bool { } func (tl *Timeline) removeGapAt(track *TimelineTrack, index int) { + fmt.Printf("removeGapAt: track %s row %d\n", track.ID, index) track.Cells = append(track.Cells[:index], track.Cells[index+1:]...) tl.reindexRowsFrom(track, index) } @@ -355,8 +362,9 @@ func (tl *Timeline) reindexRowsFrom(track *TimelineTrack, start int) { } func (tl *Timeline) insertGap(track *TimelineTrack, beforeIndex int) { - + fmt.Printf("insertGap: track %s before %d\n", track.ID, beforeIndex) if tl.isAllRemovableGapRow(beforeIndex, track) { + fmt.Printf("insertGap: found removable gap row at %d\n", beforeIndex) for _, t := range tl.Tracks { if t == track { continue