diff --git a/cmd/qrunproxy/show.go b/cmd/qrunproxy/show.go index 6811750..16ebf36 100644 --- a/cmd/qrunproxy/show.go +++ b/cmd/qrunproxy/show.go @@ -19,6 +19,8 @@ type Block struct { Track string `json:"track,omitempty"` Name string `json:"name"` Loop bool `json:"loop,omitempty"` + + weight uint64 } type Trigger struct { diff --git a/cmd/qrunproxy/timeline.go b/cmd/qrunproxy/timeline.go index a3e406c..f6b66f2 100644 --- a/cmd/qrunproxy/timeline.go +++ b/cmd/qrunproxy/timeline.go @@ -1,6 +1,7 @@ package main import ( + "cmp" "fmt" "slices" ) @@ -35,11 +36,11 @@ const ( ) type TimelineCell struct { - Type CellType `json:"type"` - BlockID string `json:"block_id,omitempty"` - Event string `json:"event,omitempty"` - row int `json:"-"` - track *TimelineTrack `json:"-"` + Type CellType `json:"type"` + BlockID string `json:"block_id,omitempty"` + Event string `json:"event,omitempty"` + row int `json:"-"` + track *TimelineTrack `json:"-"` } func (t *TimelineTrack) cellTypeAt(index int, types ...CellType) bool { @@ -150,6 +151,8 @@ func BuildTimeline(show *Show) (Timeline, error) { tl.Blocks[block.ID] = block } + sortedBlocks := tl.sortBlocks() + endChains := map[string]bool{} for _, trigger := range show.Triggers { if trigger.Source.Signal != "END" { @@ -163,7 +166,7 @@ func BuildTimeline(show *Show) (Timeline, error) { } } - tl.buildCells(endChains) + tl.buildCells(endChains, sortedBlocks) tl.buildConstraints() if err := tl.assignRows(); err != nil { return Timeline{}, err @@ -172,6 +175,33 @@ func BuildTimeline(show *Show) (Timeline, error) { return tl, nil } +func (tl *Timeline) sortBlocks() []*Block { + for i, b := range tl.show.Blocks { + b.weight = uint64(i) << 32 + } + + changed := true + for changed { + changed = false + for _, t := range tl.show.Triggers { + src := tl.Blocks[t.Source.Block] + for _, target := range t.Targets { + dst := tl.Blocks[target.Block] + if dst.weight <= src.weight { + dst.weight = src.weight + 1 + changed = true + } + } + } + } + + sorted := slices.Clone(tl.show.Blocks) + slices.SortFunc(sorted, func(a, b *Block) int { + return cmp.Compare(a.weight, b.weight) + }) + return sorted +} + func (tl *Timeline) addConstraint(kind constraintKind, a, b *TimelineCell) { tl.constraints = append(tl.constraints, constraint{kind: kind, a: a, b: b}) } @@ -208,13 +238,13 @@ func (tl *Timeline) findCell(blockID, event string) *TimelineCell { panic("cell not found: " + blockID + " " + event) } -func (tl *Timeline) buildCells(endChains map[string]bool) { +func (tl *Timeline) buildCells(endChains map[string]bool, sortedBlocks []*Block) { lastOnTrack := map[string]*Block{} - for _, block := range tl.show.Blocks { + for _, block := range sortedBlocks { lastOnTrack[block.Track] = block } - for _, block := range tl.show.Blocks { + for _, block := range sortedBlocks { track := tl.trackIdx[block.Track] var cells []*TimelineCell switch block.Type { diff --git a/cmd/qrunproxy/timeline_test.go b/cmd/qrunproxy/timeline_test.go index 11ce22f..9b5de6f 100644 --- a/cmd/qrunproxy/timeline_test.go +++ b/cmd/qrunproxy/timeline_test.go @@ -35,3 +35,41 @@ func BenchmarkBuildTimeline(b *testing.B) { BuildTimeline(show) } } + +func TestTimelineOrderDependency(t *testing.T) { + show := &Show{ + Tracks: []*Track{ + {ID: "T1", Name: "Track 1"}, + {ID: "T2", Name: "Track 2"}, + }, + Blocks: []*Block{ + {ID: "B", Type: "media", Track: "T1", Name: "Block B"}, + {ID: "A", Type: "media", Track: "T1", Name: "Block A"}, + {ID: "C", Type: "media", Track: "T2", Name: "Block C"}, + {ID: "C1", Type: "cue", Name: "Cue 1"}, + }, + Triggers: []*Trigger{ + { + Source: TriggerSource{Block: "C1", Signal: "GO"}, + Targets: []TriggerTarget{{Block: "A", Hook: "START"}}, + }, + { + Source: TriggerSource{Block: "A", Signal: "END"}, + Targets: []TriggerTarget{{Block: "C", Hook: "START"}}, + }, + { + Source: TriggerSource{Block: "C", Signal: "END"}, + Targets: []TriggerTarget{{Block: "B", Hook: "START"}}, + }, + }, + } + + if err := show.Validate(); err != nil { + t.Fatalf("Validate failed: %v", err) + } + + _, err := BuildTimeline(show) + if err != nil { + t.Fatalf("BuildTimeline failed: %v", err) + } +}