mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 13:48:02 -04:00
153 lines
4.8 KiB
Go
153 lines
4.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 reflection
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jetsetilly/gopher2600/debugger/govern"
|
|
"github.com/jetsetilly/gopher2600/hardware"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
|
"github.com/jetsetilly/gopher2600/hardware/television"
|
|
"github.com/jetsetilly/gopher2600/hardware/television/signal"
|
|
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
|
"github.com/jetsetilly/gopher2600/logger"
|
|
)
|
|
|
|
// Reflector should be run (with the Check() function) every video cycle.
|
|
type Reflector struct {
|
|
vcs *hardware.VCS
|
|
renderer Renderer
|
|
emulationState govern.State
|
|
|
|
history []ReflectedVideoStep
|
|
|
|
// the index of the most recent signal returned by television.GetLastSignal()
|
|
lastIdx int
|
|
}
|
|
|
|
// NewReflector is the preferred method of initialisation for the Monitor type.
|
|
func NewReflector(vcs *hardware.VCS) *Reflector {
|
|
ref := &Reflector{vcs: vcs}
|
|
ref.Clear()
|
|
return ref
|
|
}
|
|
|
|
// AddRenderer adds an implementation of the Renderer interface to the Reflector.
|
|
func (ref *Reflector) AddRenderer(renderer Renderer) {
|
|
ref.renderer = renderer
|
|
}
|
|
|
|
// Clear existing reflected information. This doesn't need to be called that
|
|
// often and only when a new cartridge is inserted to make sure there is no
|
|
// stale data hanging around
|
|
func (ref *Reflector) Clear() {
|
|
ref.history = make([]ReflectedVideoStep, specification.AbsoluteMaxClks)
|
|
}
|
|
|
|
// SetEmulationState is called by the emulation whenever state changes. How we
|
|
// handle reflections depends on the current state.
|
|
func (ref *Reflector) SetEmulationState(state govern.State) {
|
|
prev := ref.emulationState
|
|
ref.emulationState = state
|
|
|
|
switch prev {
|
|
case govern.Rewinding:
|
|
ref.render()
|
|
}
|
|
|
|
switch state {
|
|
case govern.Paused:
|
|
err := ref.render()
|
|
if err != nil {
|
|
logger.Logf(logger.Allow, "reflection", "%v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step should be called every video cycle to record a complete
|
|
// reflection of the system.
|
|
func (ref *Reflector) Step(bank mapper.BankInfo) error {
|
|
sig := ref.vcs.TV.GetLastSignal()
|
|
|
|
// check that signal is not the NoSignal signal
|
|
//
|
|
// at the time of writng, this can sometimes happen if the VCS has been
|
|
// reset but the emulator loop has not been unwound. The newly reset TV
|
|
// will return an invalid signal leading to an index that is too large
|
|
if sig == signal.NoSignal {
|
|
return nil
|
|
}
|
|
|
|
idx := int((sig & signal.Index) >> signal.IndexShift)
|
|
h := ref.history[idx : idx+1]
|
|
|
|
h[0].CPU = ref.vcs.CPU.LastResult
|
|
h[0].WSYNC = !ref.vcs.CPU.RdyFlg
|
|
h[0].Bank = bank
|
|
h[0].VideoElement = ref.vcs.TIA.Video.LastElement
|
|
h[0].Signal = sig
|
|
h[0].Collision = *ref.vcs.TIA.Video.Collisions
|
|
h[0].IsHblank = ref.vcs.TIA.Hblank
|
|
h[0].CoProcSync = ref.vcs.Mem.Cart.CoProcExecutionState().Sync
|
|
|
|
h[0].Hmove.Delay = ref.vcs.TIA.Hmove.Future.IsActive()
|
|
h[0].Hmove.DelayCt = ref.vcs.TIA.Hmove.Future.Remaining()
|
|
h[0].Hmove.Latch = ref.vcs.TIA.Hmove.Latch
|
|
h[0].Hmove.RippleCt = ref.vcs.TIA.Hmove.Ripple
|
|
|
|
h[0].RSYNCalign, h[0].RSYNCreset = ref.vcs.TIA.RSYNCstate()
|
|
h[0].AudioPhase0, h[0].AudioPhase1, h[0].AudioChanged = ref.vcs.TIA.Audio.HasTicked()
|
|
|
|
// nullify entries at the head of the array that do not have a
|
|
// corresponding signal. we do this because the first index of a signal
|
|
// after a NewFrame might be different that the previous frame
|
|
for i := ref.lastIdx; i < idx-1; i++ {
|
|
ref.history[i] = ReflectedVideoStep{}
|
|
}
|
|
ref.lastIdx = idx
|
|
|
|
return nil
|
|
}
|
|
|
|
// push history to reflection renderer
|
|
func (ref *Reflector) render() error {
|
|
if ref.emulationState != govern.Rewinding {
|
|
if ref.renderer != nil {
|
|
if err := ref.renderer.Reflect(ref.history); err != nil {
|
|
return fmt.Errorf("reflection: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewFrame implements the television.FrameTrigger interface.
|
|
func (ref *Reflector) NewFrame(_ television.FrameInfo) error {
|
|
// nullify unused entries at end of frame
|
|
//
|
|
// note that this echoes a similar construct in the television.NewFrame()
|
|
// function. it's important that this happens here or the results in the
|
|
// Renderer will not be satisfactory.
|
|
for i := ref.lastIdx; i < len(ref.history); i++ {
|
|
ref.history[i] = ReflectedVideoStep{}
|
|
}
|
|
ref.lastIdx = 0
|
|
|
|
return ref.render()
|
|
}
|