mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 05:40:49 -04:00
24f3f32342
notifications interface instance moved to environment from cartridgeloader. the cartridgeloader package predates the environment package and had started to be used inappropriately simplified how notifications.Notify() is called. in particular the supercharger fastload starter no longer bundles a function hook. nor is the cartridge instance sent with the notification
150 lines
4.4 KiB
Go
150 lines
4.4 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 performance
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
|
"github.com/jetsetilly/gopher2600/debugger/govern"
|
|
"github.com/jetsetilly/gopher2600/hardware"
|
|
"github.com/jetsetilly/gopher2600/hardware/television"
|
|
"github.com/jetsetilly/gopher2600/setup"
|
|
)
|
|
|
|
// sentinal error returned by Run() loop.
|
|
var timedOut = errors.New("performance timed out")
|
|
|
|
// Check the performance of the emulator using the supplied cartridge.
|
|
//
|
|
// Emulation will run of specificed duration and will create a cpu, memory
|
|
// profile, a trace (or a combination of those) as defined by the Profile
|
|
// argument.
|
|
func Check(output io.Writer, profile Profile, cartload cartridgeloader.Loader, spec string, uncapped bool, duration string) error {
|
|
var err error
|
|
|
|
tv, err := television.NewTelevision(spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tv.End()
|
|
|
|
// set fps cap on television
|
|
tv.SetFPSCap(!uncapped)
|
|
|
|
// create vcs
|
|
vcs, err := hardware.NewVCS(tv, nil, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("performance: %w", err)
|
|
}
|
|
|
|
// attach cartridge to the vcs
|
|
err = setup.AttachCartridge(vcs, cartload, true)
|
|
if err != nil {
|
|
return fmt.Errorf("performance: %w", err)
|
|
}
|
|
|
|
// parse supplied duration
|
|
dur, err := time.ParseDuration(duration)
|
|
if err != nil {
|
|
return fmt.Errorf("performance: %w", err)
|
|
}
|
|
|
|
// get starting frame number (should be 0)
|
|
startFrame := tv.GetCoords().Frame
|
|
|
|
// run for specified period of time
|
|
runner := func() error {
|
|
// setup trigger that expires when duration has elapsed. signals true
|
|
// when duration has expired. signals false to indicate that
|
|
// performance measurement should start
|
|
timerChan := make(chan bool)
|
|
|
|
// force a two second leadtime to allow framerate to settle down and
|
|
// then restart timer for the specified duration
|
|
//
|
|
// the two second leadtime will put false on the timerChan. the
|
|
// conclusion of the reset of the time will put true on the timerChan.
|
|
go func() {
|
|
time.AfterFunc(2*time.Second, func() {
|
|
// signal parent function that 2 second leadtime has elapsed
|
|
timerChan <- false
|
|
|
|
// race condition when GetCoords() is called
|
|
time.AfterFunc(dur, func() {
|
|
timerChan <- true
|
|
})
|
|
})
|
|
}()
|
|
|
|
// only check for end of measurement period every PerformanceBrake CPU
|
|
// instructions. checking the timerChan is relatively expensive (worth
|
|
// a frame a two every 5 seconds)
|
|
performanceBrake := 0
|
|
|
|
// run until specified time elapses
|
|
err = vcs.Run(func() (govern.State, error) {
|
|
for {
|
|
performanceBrake++
|
|
if performanceBrake >= hardware.PerformanceBrake {
|
|
performanceBrake = 0
|
|
|
|
select {
|
|
case v := <-timerChan:
|
|
// timerChan has returned true, which means measurement
|
|
// period has finished, return false to cause vcs.Run() to
|
|
// return
|
|
if v {
|
|
return govern.Ending, timedOut
|
|
}
|
|
|
|
// timerChan has returned false which indicates that the
|
|
// leadtime has concluded. this means the performance
|
|
// measurement has begun and we should record the start
|
|
// frame.
|
|
startFrame = tv.GetCoords().Frame
|
|
default:
|
|
return govern.Running, nil
|
|
}
|
|
}
|
|
|
|
return govern.Running, nil
|
|
}
|
|
})
|
|
return err
|
|
}
|
|
|
|
// launch runner directly or through the CPU profiler, depending on
|
|
// supplied arguments
|
|
err = RunProfiler(profile, "performance", runner)
|
|
if err != nil && !errors.Is(err, timedOut) {
|
|
return fmt.Errorf("performance: %w", err)
|
|
}
|
|
|
|
// get ending frame number
|
|
endFrame := vcs.TV.GetCoords().Frame
|
|
|
|
// calculate performance
|
|
numFrames := endFrame - startFrame
|
|
fps, accuracy := CalcFPS(tv, numFrames, dur.Seconds())
|
|
output.Write([]byte(fmt.Sprintf("%.2f fps (%d frames in %.2f seconds) %.1f%%\n", fps, numFrames, dur.Seconds(), accuracy)))
|
|
|
|
return nil
|
|
}
|