diff --git a/cmd/qrunproxy/main.go b/cmd/qrunproxy/main.go index 20369ae..751d839 100644 --- a/cmd/qrunproxy/main.go +++ b/cmd/qrunproxy/main.go @@ -34,6 +34,11 @@ func main() { os.Exit(1) } + if err := show.Validate(); err != nil { + fmt.Fprintf(os.Stderr, "Error validating show: %v\n", err) + os.Exit(1) + } + timeline, err := BuildTimeline(show) if err != nil { fmt.Fprintf(os.Stderr, "Error building timeline: %v\n", err) diff --git a/cmd/qrunproxy/mockshow.go b/cmd/qrunproxy/mockshow.go new file mode 100644 index 0000000..0fc371f --- /dev/null +++ b/cmd/qrunproxy/mockshow.go @@ -0,0 +1,70 @@ +package main + +import "fmt" + +func GenerateMockShow(numTracks, numCues, numBlocks int) *Show { + show := &Show{} + + for i := range numTracks { + show.Tracks = append(show.Tracks, &Track{ + ID: fmt.Sprintf("track_%d", i), + Name: fmt.Sprintf("Track %d", i), + }) + } + + for i := range numCues { + show.Blocks = append(show.Blocks, &Block{ + ID: fmt.Sprintf("cue_%d", i), + Type: "cue", + Name: fmt.Sprintf("Cue %d", i), + }) + } + + blocksByTrack := make([][]*Block, numTracks) + for i := range numBlocks { + trackIdx := i % numTracks + trackID := fmt.Sprintf("track_%d", trackIdx) + block := &Block{ + ID: fmt.Sprintf("block_%d_%d", trackIdx, len(blocksByTrack[trackIdx])), + Type: "media", + Track: trackID, + Name: fmt.Sprintf("Block %d-%d", trackIdx, len(blocksByTrack[trackIdx])), + } + show.Blocks = append(show.Blocks, block) + blocksByTrack[trackIdx] = append(blocksByTrack[trackIdx], block) + } + + for trackIdx := range numTracks { + blocks := blocksByTrack[trackIdx] + for i := 1; i < len(blocks); i++ { + show.Triggers = append(show.Triggers, &Trigger{ + Source: TriggerSource{Block: blocks[i-1].ID, Signal: "END"}, + Targets: []TriggerTarget{{Block: blocks[i].ID, Hook: "START"}}, + }) + } + } + + headPerTrack := make([]int, numTracks) + for i := range numCues { + cue := show.Blocks[i] + targets := []TriggerTarget{} + for trackIdx := range numTracks { + if headPerTrack[trackIdx] >= len(blocksByTrack[trackIdx]) { + continue + } + block := blocksByTrack[trackIdx][headPerTrack[trackIdx]] + targets = append(targets, TriggerTarget{Block: block.ID, Hook: "START"}) + depth := len(blocksByTrack[trackIdx]) - headPerTrack[trackIdx] + advance := max(depth/(numCues-i), 1) + headPerTrack[trackIdx] += advance + } + if len(targets) > 0 { + show.Triggers = append(show.Triggers, &Trigger{ + Source: TriggerSource{Block: cue.ID, Signal: "GO"}, + Targets: targets, + }) + } + } + + return show +} diff --git a/cmd/qrunproxy/show.go b/cmd/qrunproxy/show.go index a5c364b..ef53f68 100644 --- a/cmd/qrunproxy/show.go +++ b/cmd/qrunproxy/show.go @@ -63,7 +63,7 @@ func isValidEventForBlock(block *Block, event string) bool { } } -func (show *Show) validate() error { +func (show *Show) Validate() error { if show == nil { return fmt.Errorf("show is nil") } diff --git a/cmd/qrunproxy/timeline.go b/cmd/qrunproxy/timeline.go index 9c2d258..a193d81 100644 --- a/cmd/qrunproxy/timeline.go +++ b/cmd/qrunproxy/timeline.go @@ -47,10 +47,6 @@ type cellKey struct { } func BuildTimeline(show *Show) (Timeline, error) { - if err := show.validate(); err != nil { - return Timeline{}, err - } - tl := Timeline{ show: show, Blocks: map[string]*Block{}, diff --git a/cmd/qrunproxy/timeline_test.go b/cmd/qrunproxy/timeline_test.go new file mode 100644 index 0000000..b97e5ec --- /dev/null +++ b/cmd/qrunproxy/timeline_test.go @@ -0,0 +1,52 @@ +package main + +import ( + "encoding/json" + "os" + "testing" +) + +func TestBuildTimelineFromShowJSON(t *testing.T) { + buf, err := os.ReadFile("static/show.json") + if err != nil { + t.Fatal(err) + } + var show Show + if err := json.Unmarshal(buf, &show); err != nil { + t.Fatal(err) + } + if err := show.Validate(); err != nil { + t.Fatal(err) + } + if _, err := BuildTimeline(&show); err != nil { + t.Fatal(err) + } +} + +func TestBuildTimelineFromMockShow(t *testing.T) { + show := GenerateMockShow(7, 100, 1000) + if err := show.Validate(); err != nil { + t.Fatalf("generated show failed validation: %v", err) + } + tl, err := BuildTimeline(show) + if err != nil { + t.Fatalf("BuildTimeline failed: %v", err) + } + if len(tl.Tracks) != 8 { + t.Errorf("expected 8 tracks (7 + cue), got %d", len(tl.Tracks)) + } + if len(tl.Blocks) != 1100 { + t.Errorf("expected 1100 blocks (100 cues + 1000), got %d", len(tl.Blocks)) + } +} + +func BenchmarkBuildTimeline(b *testing.B) { + show := GenerateMockShow(7, 100, 1000) + if err := show.Validate(); err != nil { + b.Fatal(err) + } + b.ResetTimer() + for range b.N { + BuildTimeline(show) + } +}