Switch to []*TimelineCell with embedded row/track, eliminating cellID and index maintenance

This commit is contained in:
Ian Gulliver
2026-02-20 16:46:37 -07:00
parent 6ff8675348
commit f12ef296aa

View File

@@ -42,7 +42,7 @@ type TriggerTarget struct {
type TimelineTrack struct { type TimelineTrack struct {
Track Track
Cells []TimelineCell `json:"cells"` Cells []*TimelineCell `json:"cells"`
} }
type Timeline struct { type Timeline struct {
@@ -64,21 +64,18 @@ type TimelineCell struct {
IsSignal bool `json:"is_signal,omitempty"` IsSignal bool `json:"is_signal,omitempty"`
IsGap bool `json:"-"` IsGap bool `json:"-"`
IsBreak bool `json:"-"` IsBreak bool `json:"-"`
} row int `json:"-"`
track *TimelineTrack `json:"-"`
type cellID struct {
track *TimelineTrack
index int
} }
type constraint struct { type constraint struct {
kind string kind string
a cellID a *TimelineCell
b cellID b *TimelineCell
} }
type exclusiveGroup struct { type exclusiveGroup struct {
members []cellID members []*TimelineCell
} }
func validateShow(show Show) error { func validateShow(show Show) error {
@@ -136,12 +133,20 @@ func BuildTimeline(show Show) (Timeline, error) {
return tl, nil return tl, nil
} }
func (tl *Timeline) addConstraint(kind string, a, b cellID) { func (tl *Timeline) addConstraint(kind string, a, b *TimelineCell) {
tl.constraints = append(tl.constraints, constraint{kind: kind, a: a, b: b}) tl.constraints = append(tl.constraints, constraint{kind: kind, a: a, b: b})
} }
func getCueCells(block Block) []TimelineCell { func (track *TimelineTrack) appendCells(cells ...*TimelineCell) {
return []TimelineCell{{ for _, c := range cells {
c.row = len(track.Cells)
c.track = track
track.Cells = append(track.Cells, c)
}
}
func getCueCells(block Block) []*TimelineCell {
return []*TimelineCell{{
BlockID: block.ID, BlockID: block.ID,
IsStart: true, IsStart: true,
IsEnd: true, IsEnd: true,
@@ -149,8 +154,8 @@ func getCueCells(block Block) []TimelineCell {
}} }}
} }
func getBlockCells(block Block) []TimelineCell { func getBlockCells(block Block) []*TimelineCell {
return []TimelineCell{ return []*TimelineCell{
{BlockID: block.ID, IsStart: true, Event: "START"}, {BlockID: block.ID, IsStart: true, Event: "START"},
{BlockID: block.ID, IsTitle: true}, {BlockID: block.ID, IsTitle: true},
{BlockID: block.ID, Event: "FADE_OUT"}, {BlockID: block.ID, Event: "FADE_OUT"},
@@ -158,11 +163,11 @@ func getBlockCells(block Block) []TimelineCell {
} }
} }
func (tl *Timeline) findCell(blockID, event string) cellID { func (tl *Timeline) findCell(blockID, event string) *TimelineCell {
track := tl.trackIdx[tl.Blocks[blockID].Track] track := tl.trackIdx[tl.Blocks[blockID].Track]
for i, c := range track.Cells { for _, c := range track.Cells {
if !c.IsGap && c.BlockID == blockID && c.Event == event { if !c.IsGap && c.BlockID == blockID && c.Event == event {
return cellID{track: track, index: i} return c
} }
} }
panic("cell not found: " + blockID + " " + event) panic("cell not found: " + blockID + " " + event)
@@ -187,40 +192,40 @@ func (tl *Timeline) buildCells() {
for _, sb := range tl.show.Blocks { for _, sb := range tl.show.Blocks {
block := tl.Blocks[sb.ID] block := tl.Blocks[sb.ID]
track := tl.trackIdx[block.Track] track := tl.trackIdx[block.Track]
var cells []TimelineCell var cells []*TimelineCell
switch block.Type { switch block.Type {
case "cue": case "cue":
cells = getCueCells(block) cells = getCueCells(block)
default: default:
cells = getBlockCells(block) cells = getBlockCells(block)
} }
track.Cells = append(track.Cells, cells...) track.appendCells(cells...)
if block.Type != "cue" && !tl.endChainsSameTrack(block.ID) { if block.Type != "cue" && !tl.endChainsSameTrack(block.ID) {
track.Cells = append(track.Cells, TimelineCell{IsGap: true, IsBreak: true}) track.appendCells(&TimelineCell{IsGap: true, IsBreak: true})
} }
} }
} }
func (tl *Timeline) buildConstraints() { func (tl *Timeline) buildConstraints() {
for _, trigger := range tl.show.Triggers { for _, trigger := range tl.show.Triggers {
sourceID := tl.findCell(trigger.Source.Block, trigger.Source.Signal) source := tl.findCell(trigger.Source.Block, trigger.Source.Signal)
group := exclusiveGroup{members: []cellID{sourceID}} group := exclusiveGroup{members: []*TimelineCell{source}}
hasCrossTrack := false hasCrossTrack := false
for _, target := range trigger.Targets { for _, target := range trigger.Targets {
targetID := tl.findCell(target.Block, target.Hook) t := tl.findCell(target.Block, target.Hook)
if sourceID.track == targetID.track { if source.track == t.track {
tl.addConstraint("next_row", sourceID, targetID) tl.addConstraint("next_row", source, t)
} else { } else {
tl.addConstraint("same_row", sourceID, targetID) tl.addConstraint("same_row", source, t)
hasCrossTrack = true hasCrossTrack = true
} }
group.members = append(group.members, targetID) group.members = append(group.members, t)
} }
if hasCrossTrack { if hasCrossTrack {
sourceID.track.Cells[sourceID.index].IsSignal = true source.IsSignal = true
} }
tl.exclusives = append(tl.exclusives, group) tl.exclusives = append(tl.exclusives, group)
} }
@@ -230,21 +235,18 @@ func (tl *Timeline) assignRows() {
for { for {
found := false found := false
for _, c := range tl.constraints { for _, c := range tl.constraints {
aRow := tl.rowOf(c.a)
bRow := tl.rowOf(c.b)
switch c.kind { switch c.kind {
case "same_row": case "same_row":
if aRow < bRow { if c.a.row < c.b.row {
tl.insertGap(c.a.track, c.a.index) tl.insertGap(c.a.track, c.a.row)
found = true found = true
} else if bRow < aRow { } else if c.b.row < c.a.row {
tl.insertGap(c.b.track, c.b.index) tl.insertGap(c.b.track, c.b.row)
found = true found = true
} }
case "next_row": case "next_row":
if bRow <= aRow { if c.b.row <= c.a.row {
tl.insertGap(c.b.track, c.b.index) tl.insertGap(c.b.track, c.b.row)
found = true found = true
} }
} }
@@ -266,12 +268,12 @@ func (tl *Timeline) enforceExclusives() bool {
if len(g.members) == 0 { if len(g.members) == 0 {
continue continue
} }
row := tl.rowOf(g.members[0]) row := g.members[0].row
allAligned := true allAligned := true
memberTracks := map[*TimelineTrack]bool{} memberTracks := map[*TimelineTrack]bool{}
for _, m := range g.members { for _, m := range g.members {
memberTracks[m.track] = true memberTracks[m.track] = true
if tl.rowOf(m) != row { if m.row != row {
allAligned = false allAligned = false
} }
} }
@@ -296,10 +298,6 @@ func (tl *Timeline) enforceExclusives() bool {
return false return false
} }
func (tl *Timeline) rowOf(id cellID) int {
return id.index
}
func (tl *Timeline) isAllGapRow(row int, except *TimelineTrack) bool { func (tl *Timeline) isAllGapRow(row int, except *TimelineTrack) bool {
for _, t := range tl.Tracks { for _, t := range tl.Tracks {
if t == except { if t == except {
@@ -317,23 +315,8 @@ func (tl *Timeline) isAllGapRow(row int, except *TimelineTrack) bool {
func (tl *Timeline) removeGapAt(track *TimelineTrack, index int) { func (tl *Timeline) removeGapAt(track *TimelineTrack, index int) {
track.Cells = append(track.Cells[:index], track.Cells[index+1:]...) track.Cells = append(track.Cells[:index], track.Cells[index+1:]...)
for i := index; i < len(track.Cells); i++ {
for i := range tl.constraints { track.Cells[i].row = i
c := &tl.constraints[i]
if c.a.track == track && c.a.index > index {
c.a.index--
}
if c.b.track == track && c.b.index > index {
c.b.index--
}
}
for i := range tl.exclusives {
for j := range tl.exclusives[i].members {
m := &tl.exclusives[i].members[j]
if m.track == track && m.index > index {
m.index--
}
}
} }
} }
@@ -341,8 +324,8 @@ func (tl *Timeline) insertGap(track *TimelineTrack, beforeIndex int) {
for { for {
blocked := false blocked := false
for _, c := range tl.constraints { for _, c := range tl.constraints {
if c.kind == "next_row" && c.a.track == track && c.b.track == track && c.a.index == beforeIndex-1 && c.b.index == beforeIndex { if c.kind == "next_row" && c.a.track == track && c.b.track == track && c.a.row == beforeIndex-1 && c.b.row == beforeIndex {
beforeIndex = c.a.index beforeIndex = c.a.row
blocked = true blocked = true
break break
} }
@@ -367,7 +350,7 @@ func (tl *Timeline) insertGap(track *TimelineTrack, beforeIndex int) {
return return
} }
gap := TimelineCell{IsGap: true} gap := &TimelineCell{IsGap: true, row: beforeIndex, track: track}
for i := beforeIndex - 1; i >= 0; i-- { for i := beforeIndex - 1; i >= 0; i-- {
c := track.Cells[i] c := track.Cells[i]
if c.IsGap { if c.IsGap {
@@ -379,28 +362,12 @@ func (tl *Timeline) insertGap(track *TimelineTrack, beforeIndex int) {
break break
} }
cells := track.Cells newCells := make([]*TimelineCell, 0, len(track.Cells)+1)
newCells := make([]TimelineCell, 0, len(cells)+1) newCells = append(newCells, track.Cells[:beforeIndex]...)
newCells = append(newCells, cells[:beforeIndex]...)
newCells = append(newCells, gap) newCells = append(newCells, gap)
newCells = append(newCells, cells[beforeIndex:]...) newCells = append(newCells, track.Cells[beforeIndex:]...)
track.Cells = newCells track.Cells = newCells
for i := beforeIndex + 1; i < len(track.Cells); i++ {
for i := range tl.constraints { track.Cells[i].row = i
c := &tl.constraints[i]
if c.a.track == track && c.a.index >= beforeIndex {
c.a.index++
}
if c.b.track == track && c.b.index >= beforeIndex {
c.b.index++
}
}
for i := range tl.exclusives {
for j := range tl.exclusives[i].members {
m := &tl.exclusives[i].members[j]
if m.track == track && m.index >= beforeIndex {
m.index++
}
}
} }
} }