mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 13:48:02 -04:00
3aa5885ebe
curated package predated the standard errors package introduced in go1.13 the standard package does a better job of what curated attempted to do the change of package also gave me a opportunity to clean up the error messages a little bit
221 lines
6.8 KiB
Go
221 lines
6.8 KiB
Go
// This file is part of Gopher2600.
|
|
//
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Gopher2600 is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package rewind
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jetsetilly/gopher2600/debugger/govern"
|
|
"github.com/jetsetilly/gopher2600/hardware/television"
|
|
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
|
)
|
|
|
|
// TimelineCounts is returned by a TimelineCounter implementation. The value
|
|
// should be updated every *video* cycle. Users can divide by three to get the
|
|
// color-clock count.
|
|
type TimelineCounts struct {
|
|
WSYNC int
|
|
CoProc int
|
|
}
|
|
|
|
// TimelineCounter implementations provide system information for the
|
|
// timeline that would otherwise be awkward to collate.
|
|
//
|
|
// Implementations should reset counts in time for the next call to
|
|
// TimelineCounts().
|
|
type TimelineCounter interface {
|
|
TimelineCounts() TimelineCounts
|
|
}
|
|
|
|
type TimelineRatios struct {
|
|
WSYNC float32
|
|
CoProc float32
|
|
}
|
|
|
|
// Timeline provides a summary of the current state of the rewind system.
|
|
//
|
|
// Useful for GUIs for example, to present the range of frame numbers that are
|
|
// available in the rewind history.
|
|
type Timeline struct {
|
|
FrameNum []int
|
|
TotalScanlines []int
|
|
Counts []TimelineCounts
|
|
Ratios []TimelineRatios
|
|
|
|
// peripheral input (including the panel). entry is true if the peripheral
|
|
// was "active" at the end of a frame
|
|
LeftPlayerInput []bool
|
|
RightPlayerInput []bool
|
|
PanelInput []bool
|
|
|
|
// will be updated if reflection has been attached
|
|
|
|
// These two "available" fields state the earliest and latest frames that
|
|
// are available in the rewind history.
|
|
//
|
|
// The earliest information in the Timeline array fields may be different.
|
|
AvailableStart int
|
|
AvailableEnd int
|
|
}
|
|
|
|
const timelineLength = 1000
|
|
|
|
func newTimeline() Timeline {
|
|
return Timeline{
|
|
FrameNum: make([]int, 0),
|
|
TotalScanlines: make([]int, 0),
|
|
Counts: make([]TimelineCounts, 0),
|
|
Ratios: make([]TimelineRatios, 0),
|
|
LeftPlayerInput: make([]bool, 0),
|
|
RightPlayerInput: make([]bool, 0),
|
|
PanelInput: make([]bool, 0),
|
|
}
|
|
}
|
|
|
|
func (tl *Timeline) reset() {
|
|
tl.FrameNum = tl.FrameNum[:0]
|
|
tl.TotalScanlines = tl.TotalScanlines[:0]
|
|
tl.Counts = tl.Counts[:0]
|
|
tl.Ratios = tl.Ratios[:0]
|
|
tl.LeftPlayerInput = tl.LeftPlayerInput[:0]
|
|
tl.RightPlayerInput = tl.RightPlayerInput[:0]
|
|
tl.PanelInput = tl.PanelInput[:0]
|
|
}
|
|
|
|
func (tl *Timeline) checkIntegrity() error {
|
|
if len(tl.FrameNum) != len(tl.TotalScanlines) {
|
|
return fmt.Errorf("timeline arrays are different lengths")
|
|
}
|
|
|
|
if len(tl.FrameNum) != len(tl.Counts) {
|
|
return fmt.Errorf("timeline arrays are different lengths")
|
|
}
|
|
|
|
if len(tl.FrameNum) != len(tl.LeftPlayerInput) {
|
|
return fmt.Errorf("timeline arrays are different lengths")
|
|
}
|
|
|
|
if len(tl.FrameNum) != len(tl.RightPlayerInput) {
|
|
return fmt.Errorf("timeline arrays are different lengths")
|
|
}
|
|
|
|
if len(tl.FrameNum) != len(tl.PanelInput) {
|
|
return fmt.Errorf("timeline arrays are different lengths")
|
|
}
|
|
|
|
if len(tl.FrameNum) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(tl.FrameNum) > 1 {
|
|
prev := tl.FrameNum[0]
|
|
for _, fn := range tl.FrameNum[1:] {
|
|
if fn != prev+1 {
|
|
return fmt.Errorf("frame numbers in timeline are not consecutive (mising frame %d)", prev+1)
|
|
}
|
|
prev = fn
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetTimeline returns a copy of the current Timeline. Checks integrity of the
|
|
// timeline and will cause the program to panic if it is insane.
|
|
func (r *Rewind) GetTimeline() Timeline {
|
|
if err := r.timeline.checkIntegrity(); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
e := r.lastEntryIdx()
|
|
|
|
// because of how we generate visual state we cannot generate the image for
|
|
// the first frame in the history unless the first entry represents a
|
|
// machine reset
|
|
//
|
|
// this has a consequence when the first time the circular array wraps
|
|
// around for the first time (the number of available entries drops by one)
|
|
sf := r.entries[r.start].TV.GetCoords().Frame
|
|
if r.entries[r.start].level != levelReset {
|
|
sf++
|
|
}
|
|
|
|
r.timeline.AvailableStart = sf
|
|
r.timeline.AvailableEnd = r.entries[e].TV.GetCoords().Frame
|
|
|
|
return r.timeline
|
|
}
|
|
|
|
func (r *Rewind) addTimelineEntry(frameInfo television.FrameInfo) {
|
|
// do not alter the timeline information if we're in the rewinding state
|
|
if r.emulation.State() == govern.Rewinding {
|
|
return
|
|
}
|
|
|
|
// splice timeline if newframe is less than the current end framenum. this
|
|
// can happen when the machine has been rewound to an earlier state
|
|
if len(r.timeline.FrameNum) > 0 && frameInfo.FrameNum <= r.timeline.FrameNum[len(r.timeline.FrameNum)-1] {
|
|
for i := range r.timeline.FrameNum {
|
|
if frameInfo.FrameNum <= r.timeline.FrameNum[i] {
|
|
r.timeline.FrameNum = r.timeline.FrameNum[:i]
|
|
r.timeline.TotalScanlines = r.timeline.TotalScanlines[:i]
|
|
r.timeline.Counts = r.timeline.Counts[:i]
|
|
r.timeline.LeftPlayerInput = r.timeline.LeftPlayerInput[:i]
|
|
r.timeline.RightPlayerInput = r.timeline.RightPlayerInput[:i]
|
|
r.timeline.PanelInput = r.timeline.PanelInput[:i]
|
|
break // for loop
|
|
}
|
|
}
|
|
}
|
|
|
|
cts := TimelineCounts{}
|
|
if r.ctr != nil {
|
|
cts = r.ctr.TimelineCounts()
|
|
}
|
|
|
|
r.timeline.FrameNum = append(r.timeline.FrameNum, frameInfo.FrameNum)
|
|
r.timeline.TotalScanlines = append(r.timeline.TotalScanlines, frameInfo.TotalScanlines)
|
|
r.timeline.Counts = append(r.timeline.Counts, cts)
|
|
r.timeline.LeftPlayerInput = append(r.timeline.LeftPlayerInput, r.vcs.RIOT.Ports.LeftPlayer.IsActive())
|
|
r.timeline.RightPlayerInput = append(r.timeline.RightPlayerInput, r.vcs.RIOT.Ports.RightPlayer.IsActive())
|
|
r.timeline.PanelInput = append(r.timeline.PanelInput, r.vcs.RIOT.Ports.Panel.IsActive())
|
|
|
|
// ratios
|
|
b := float32(frameInfo.TotalScanlines * specification.ClksScanline)
|
|
if b == 0 {
|
|
r.timeline.Ratios = append(r.timeline.Ratios, TimelineRatios{
|
|
WSYNC: 0,
|
|
CoProc: 0,
|
|
})
|
|
} else {
|
|
r.timeline.Ratios = append(r.timeline.Ratios, TimelineRatios{
|
|
WSYNC: float32(cts.WSYNC) / b,
|
|
CoProc: float32(cts.CoProc) / b,
|
|
})
|
|
}
|
|
|
|
if len(r.timeline.FrameNum) > timelineLength {
|
|
r.timeline.FrameNum = r.timeline.FrameNum[1:]
|
|
r.timeline.TotalScanlines = r.timeline.TotalScanlines[1:]
|
|
r.timeline.Counts = r.timeline.Counts[1:]
|
|
r.timeline.Ratios = r.timeline.Ratios[1:]
|
|
r.timeline.LeftPlayerInput = r.timeline.LeftPlayerInput[1:]
|
|
r.timeline.RightPlayerInput = r.timeline.RightPlayerInput[1:]
|
|
r.timeline.PanelInput = r.timeline.PanelInput[1:]
|
|
}
|
|
|
|
}
|