mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 05:40:49 -04:00
added support for stella.pro files
added properties package ROM select window display property information
This commit is contained in:
parent
58315e5182
commit
685bf7ccc7
|
@ -17,6 +17,7 @@ package cartridgeloader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -65,6 +66,9 @@ type Loader struct {
|
||||||
// string is empty then that check passes.
|
// string is empty then that check passes.
|
||||||
Hash string
|
Hash string
|
||||||
|
|
||||||
|
// HashMD5 is an alternative to hash for use with the properties package
|
||||||
|
HashMD5 string
|
||||||
|
|
||||||
// does the Data field consist of sound (PCM) data
|
// does the Data field consist of sound (PCM) data
|
||||||
IsSoundData bool
|
IsSoundData bool
|
||||||
|
|
||||||
|
@ -320,6 +324,7 @@ func NewLoaderFromEmbed(name string, data []byte, mapping string) (Loader, error
|
||||||
Data: &data,
|
Data: &data,
|
||||||
embedded: true,
|
embedded: true,
|
||||||
Hash: fmt.Sprintf("%x", sha1.Sum(data)),
|
Hash: fmt.Sprintf("%x", sha1.Sum(data)),
|
||||||
|
HashMD5: fmt.Sprintf("%x", md5.Sum(data)),
|
||||||
NotificationHook: func(cart mapper.CartMapper, notice notifications.Notify, args ...interface{}) error {
|
NotificationHook: func(cart mapper.CartMapper, notice notifications.Notify, args ...interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -450,5 +455,12 @@ func (cl *Loader) Load() error {
|
||||||
}
|
}
|
||||||
cl.Hash = hash
|
cl.Hash = hash
|
||||||
|
|
||||||
|
// generate md5 hash and check for consistency
|
||||||
|
hashmd5 := fmt.Sprintf("%x", md5.Sum(*cl.Data))
|
||||||
|
if cl.HashMD5 != "" && cl.HashMD5 != hashmd5 {
|
||||||
|
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
||||||
|
}
|
||||||
|
cl.HashMD5 = hashmd5
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/patch"
|
"github.com/jetsetilly/gopher2600/patch"
|
||||||
"github.com/jetsetilly/gopher2600/prefs"
|
"github.com/jetsetilly/gopher2600/prefs"
|
||||||
"github.com/jetsetilly/gopher2600/preview"
|
"github.com/jetsetilly/gopher2600/preview"
|
||||||
|
"github.com/jetsetilly/gopher2600/properties"
|
||||||
"github.com/jetsetilly/gopher2600/recorder"
|
"github.com/jetsetilly/gopher2600/recorder"
|
||||||
"github.com/jetsetilly/gopher2600/reflection"
|
"github.com/jetsetilly/gopher2600/reflection"
|
||||||
"github.com/jetsetilly/gopher2600/reflection/counter"
|
"github.com/jetsetilly/gopher2600/reflection/counter"
|
||||||
|
@ -120,6 +121,9 @@ type Debugger struct {
|
||||||
term terminal.Terminal
|
term terminal.Terminal
|
||||||
controllers *userinput.Controllers
|
controllers *userinput.Controllers
|
||||||
|
|
||||||
|
// stella.pro file support
|
||||||
|
pro properties.Properties
|
||||||
|
|
||||||
// bots coordinator
|
// bots coordinator
|
||||||
bots *wrangler.Bots
|
bots *wrangler.Bots
|
||||||
|
|
||||||
|
@ -364,6 +368,12 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
|
||||||
// create bot coordinator
|
// create bot coordinator
|
||||||
dbg.bots = wrangler.NewBots(dbg.vcs.Input, dbg.vcs.TV)
|
dbg.bots = wrangler.NewBots(dbg.vcs.Input, dbg.vcs.TV)
|
||||||
|
|
||||||
|
// stella.pro support
|
||||||
|
dbg.pro, err = properties.Load()
|
||||||
|
if err != nil {
|
||||||
|
logger.Logf("debugger", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// create preview emulation
|
// create preview emulation
|
||||||
dbg.preview, err = preview.NewEmulation(dbg.vcs.Env.Prefs)
|
dbg.preview, err = preview.NewEmulation(dbg.vcs.Env.Prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/debugger/govern"
|
"github.com/jetsetilly/gopher2600/debugger/govern"
|
||||||
"github.com/jetsetilly/gopher2600/disassembly"
|
"github.com/jetsetilly/gopher2600/disassembly"
|
||||||
"github.com/jetsetilly/gopher2600/logger"
|
"github.com/jetsetilly/gopher2600/logger"
|
||||||
|
"github.com/jetsetilly/gopher2600/properties"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PushFunction onto the event queue. Used to ensure that the events are
|
// PushFunction onto the event queue. Used to ensure that the events are
|
||||||
|
@ -72,3 +73,11 @@ func (dbg *Debugger) PushTogglePCBreak(e *disassembly.Entry) {
|
||||||
dbg.halting.breakpoints.togglePCBreak(f)
|
dbg.halting.breakpoints.togglePCBreak(f)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PushPropertyLookup looks up the supplied MD5 hash in the properties table
|
||||||
|
func (dbg *Debugger) PushPropertyLookup(hashMD5 string, result chan properties.Entry) {
|
||||||
|
dbg.PushFunctionImmediate(func() {
|
||||||
|
e, _ := dbg.pro.Lookup(hashMD5)
|
||||||
|
result <- e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||||
"github.com/jetsetilly/gopher2600/logger"
|
"github.com/jetsetilly/gopher2600/logger"
|
||||||
|
"github.com/jetsetilly/gopher2600/properties"
|
||||||
"github.com/jetsetilly/gopher2600/thumbnailer"
|
"github.com/jetsetilly/gopher2600/thumbnailer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,7 +44,12 @@ type winSelectROM struct {
|
||||||
entries []os.DirEntry
|
entries []os.DirEntry
|
||||||
err error
|
err error
|
||||||
|
|
||||||
selectedFile string
|
selectedFile string
|
||||||
|
selectedFileBase string
|
||||||
|
loader cartridgeloader.Loader
|
||||||
|
properties properties.Entry
|
||||||
|
propertiesOpen bool
|
||||||
|
|
||||||
showAllFiles bool
|
showAllFiles bool
|
||||||
showHidden bool
|
showHidden bool
|
||||||
|
|
||||||
|
@ -58,15 +64,20 @@ type winSelectROM struct {
|
||||||
|
|
||||||
thmbImage *image.RGBA
|
thmbImage *image.RGBA
|
||||||
thmbDimensions image.Point
|
thmbDimensions image.Point
|
||||||
|
|
||||||
|
// the return channel from the emulation goroutine for the property lookup
|
||||||
|
// for the selected cartridge
|
||||||
|
propertyResult chan properties.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSelectROM(img *SdlImgui) (window, error) {
|
func newSelectROM(img *SdlImgui) (window, error) {
|
||||||
win := &winSelectROM{
|
win := &winSelectROM{
|
||||||
img: img,
|
img: img,
|
||||||
showAllFiles: false,
|
showAllFiles: false,
|
||||||
showHidden: false,
|
showHidden: false,
|
||||||
scrollToTop: true,
|
scrollToTop: true,
|
||||||
centreOnFile: true,
|
centreOnFile: true,
|
||||||
|
propertyResult: make(chan properties.Entry, 1),
|
||||||
}
|
}
|
||||||
win.debuggerGeom.noFousTracking = true
|
win.debuggerGeom.noFousTracking = true
|
||||||
|
|
||||||
|
@ -219,6 +230,12 @@ func (win *winSelectROM) render() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (win *winSelectROM) draw() {
|
func (win *winSelectROM) draw() {
|
||||||
|
// check for new property information
|
||||||
|
select {
|
||||||
|
case win.properties = <-win.propertyResult:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
// reset centreOnFile at end of draw
|
// reset centreOnFile at end of draw
|
||||||
defer func() {
|
defer func() {
|
||||||
win.centreOnFile = false
|
win.centreOnFile = false
|
||||||
|
@ -310,7 +327,7 @@ func (win *winSelectROM) draw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.Mode().IsRegular() {
|
if fi.Mode().IsRegular() {
|
||||||
selected := f.Name() == filepath.Base(win.selectedFile)
|
selected := f.Name() == win.selectedFileBase
|
||||||
|
|
||||||
if selected && win.centreOnFile {
|
if selected && win.centreOnFile {
|
||||||
imgui.SetScrollHereY(0.0)
|
imgui.SetScrollHereY(0.0)
|
||||||
|
@ -337,19 +354,75 @@ func (win *winSelectROM) draw() {
|
||||||
|
|
||||||
// control buttons. start controlHeight measurement
|
// control buttons. start controlHeight measurement
|
||||||
win.controlHeight = imguiMeasureHeight(func() {
|
win.controlHeight = imguiMeasureHeight(func() {
|
||||||
if win.thmb.IsEmulating() {
|
func() {
|
||||||
imgui.Text(win.thmb.String())
|
if !win.thmb.IsEmulating() {
|
||||||
} else {
|
imgui.PushItemFlag(imgui.ItemFlagsDisabled, true)
|
||||||
imgui.Text("")
|
imgui.PushStyleVarFloat(imgui.StyleVarAlpha, disabledAlpha)
|
||||||
}
|
defer imgui.PopItemFlag()
|
||||||
|
defer imgui.PopStyleVar()
|
||||||
|
}
|
||||||
|
|
||||||
|
imgui.SetNextItemOpen(win.propertiesOpen, imgui.ConditionAlways)
|
||||||
|
if !imgui.CollapsingHeaderV(win.selectedFileBase, imgui.TreeNodeFlagsNone) {
|
||||||
|
win.propertiesOpen = false
|
||||||
|
} else {
|
||||||
|
win.propertiesOpen = true
|
||||||
|
if win.properties.IsValid() {
|
||||||
|
if imgui.BeginTable("#properties", 2) {
|
||||||
|
imgui.TableSetupColumnV("#category", imgui.TableColumnFlagsWidthFixed, -1, 0)
|
||||||
|
imgui.TableSetupColumnV("#detail", imgui.TableColumnFlagsWidthFixed, -1, 1)
|
||||||
|
|
||||||
|
imgui.TableNextRow()
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text("Name")
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text(win.properties.Name)
|
||||||
|
|
||||||
|
if win.properties.Manufacturer != "" {
|
||||||
|
imgui.TableNextRow()
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text("Manufacturer")
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text(win.properties.Manufacturer)
|
||||||
|
}
|
||||||
|
if win.properties.Rarity != "" {
|
||||||
|
imgui.TableNextRow()
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text("Rarity")
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text(win.properties.Rarity)
|
||||||
|
}
|
||||||
|
if win.properties.Model != "" {
|
||||||
|
imgui.TableNextRow()
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text("Model")
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text(win.properties.Model)
|
||||||
|
}
|
||||||
|
|
||||||
|
if win.properties.Note != "" {
|
||||||
|
imgui.TableNextRow()
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text("Note")
|
||||||
|
imgui.TableNextColumn()
|
||||||
|
imgui.Text(win.properties.Note)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgui.EndTable()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
imgui.Text("No information")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
imguiSeparator()
|
imguiSeparator()
|
||||||
|
|
||||||
imgui.Checkbox("Show all files", &win.showAllFiles)
|
// imgui.Checkbox("Show all files", &win.showAllFiles)
|
||||||
imgui.SameLine()
|
// imgui.SameLine()
|
||||||
imgui.Checkbox("Show hidden files", &win.showHidden)
|
// imgui.Checkbox("Show hidden files", &win.showHidden)
|
||||||
|
|
||||||
imgui.Spacing()
|
// imgui.Spacing()
|
||||||
|
|
||||||
if imgui.Button("Cancel") {
|
if imgui.Button("Cancel") {
|
||||||
// close rom selected in both the debugger and playmode
|
// close rom selected in both the debugger and playmode
|
||||||
|
@ -363,9 +436,9 @@ func (win *winSelectROM) draw() {
|
||||||
|
|
||||||
// load or reload button
|
// load or reload button
|
||||||
if win.selectedFile == win.img.cache.VCS.Mem.Cart.Filename {
|
if win.selectedFile == win.img.cache.VCS.Mem.Cart.Filename {
|
||||||
s = fmt.Sprintf("Reload %s", filepath.Base(win.selectedFile))
|
s = fmt.Sprintf("Reload %s", win.selectedFileBase)
|
||||||
} else {
|
} else {
|
||||||
s = fmt.Sprintf("Load %s", filepath.Base(win.selectedFile))
|
s = fmt.Sprintf("Load %s", win.selectedFileBase)
|
||||||
}
|
}
|
||||||
|
|
||||||
// only show load cartridge button if the file is being
|
// only show load cartridge button if the file is being
|
||||||
|
@ -419,15 +492,28 @@ func (win *winSelectROM) setSelectedFile(filename string) {
|
||||||
// update selected file. return immediately if the filename is empty
|
// update selected file. return immediately if the filename is empty
|
||||||
win.selectedFile = filename
|
win.selectedFile = filename
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
|
win.selectedFileBase = ""
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// base filename for presentation purposes
|
||||||
|
win.selectedFileBase = filepath.Base(filename)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
// create cartridge loader and start thumbnail emulation
|
// create cartridge loader and start thumbnail emulation
|
||||||
cartload, err := cartridgeloader.NewLoader(filename, "AUTO")
|
win.loader, err = cartridgeloader.NewLoader(filename, "AUTO")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("ROM Select", err.Error())
|
logger.Logf("ROM Select", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
win.thmb.Create(cartload, thumbnailer.UndefinedNumFrames)
|
// push function to emulation goroutine. result will be checked for in
|
||||||
|
// draw() function
|
||||||
|
if err := win.loader.Load(); err == nil {
|
||||||
|
win.img.dbg.PushPropertyLookup(win.loader.HashMD5, win.propertyResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create thumbnail animation
|
||||||
|
win.thmb.Create(win.loader, thumbnailer.UndefinedNumFrames)
|
||||||
}
|
}
|
||||||
|
|
18
properties/doc.go
Normal file
18
properties/doc.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// 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 properties adds support for the Stella properties file, named
|
||||||
|
// stella.pro. The file should be placed in the Gopher2600 configuration folder
|
||||||
|
package properties
|
132
properties/properties.go
Normal file
132
properties/properties.go
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
// 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 properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/logger"
|
||||||
|
"github.com/jetsetilly/gopher2600/resources"
|
||||||
|
)
|
||||||
|
|
||||||
|
const propertiesFile = "stella.pro"
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
Hash string
|
||||||
|
Name string
|
||||||
|
Manufacturer string
|
||||||
|
Note string
|
||||||
|
Rarity string
|
||||||
|
Model string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Entry) IsValid() bool {
|
||||||
|
return len(e.Hash) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Properties struct {
|
||||||
|
available bool
|
||||||
|
entries map[string]Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (Properties, error) {
|
||||||
|
pro := Properties{
|
||||||
|
entries: make(map[string]Entry),
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := resources.JoinPath(propertiesFile)
|
||||||
|
if err != nil {
|
||||||
|
return Properties{}, fmt.Errorf("properties: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return Properties{}, fmt.Errorf("properties: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
|
||||||
|
var entry Entry
|
||||||
|
var line int
|
||||||
|
var rejected int
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line++
|
||||||
|
|
||||||
|
flds := strings.SplitN(scanner.Text(), " ", 2)
|
||||||
|
if len(flds) < 2 {
|
||||||
|
continue // for loop
|
||||||
|
}
|
||||||
|
|
||||||
|
flds[0] = strings.Trim(flds[0], "\"")
|
||||||
|
flds[1] = strings.Trim(flds[1], "\"")
|
||||||
|
switch strings.ToUpper(flds[0]) {
|
||||||
|
case "CART.MD5":
|
||||||
|
// create new entry
|
||||||
|
if len(entry.Hash) == 32 {
|
||||||
|
pro.entries[entry.Hash] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare new entry if has is valid
|
||||||
|
if len(flds[1]) != 32 {
|
||||||
|
logger.Logf("properties", "invalid hash entry at line %d", line)
|
||||||
|
rejected++
|
||||||
|
} else {
|
||||||
|
entry = Entry{
|
||||||
|
Hash: flds[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "CART.NAME":
|
||||||
|
entry.Name = flds[1]
|
||||||
|
|
||||||
|
case "CART.MANUFACTURER":
|
||||||
|
entry.Manufacturer = flds[1]
|
||||||
|
|
||||||
|
case "CART.NOTE":
|
||||||
|
entry.Note = flds[1]
|
||||||
|
|
||||||
|
case "CART.RARITY":
|
||||||
|
entry.Rarity = flds[1]
|
||||||
|
|
||||||
|
case "CART.MODEL":
|
||||||
|
entry.Model = flds[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = scanner.Err(); err != nil {
|
||||||
|
return Properties{}, fmt.Errorf("pro: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Logf("properties", "%d entries loaded", len(pro.entries))
|
||||||
|
if rejected > 0 {
|
||||||
|
logger.Logf("properties", "%d entries rejected", rejected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pro, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the property entry for the ROM with the supplied md5 hash
|
||||||
|
func (pro Properties) Lookup(md5Hash string) (Entry, bool) {
|
||||||
|
if e, ok := pro.entries[md5Hash]; ok {
|
||||||
|
return e, true
|
||||||
|
}
|
||||||
|
return Entry{}, false
|
||||||
|
}
|
|
@ -55,6 +55,9 @@ type Anim struct {
|
||||||
emulationCompleted chan bool
|
emulationCompleted chan bool
|
||||||
|
|
||||||
Render chan *image.RGBA
|
Render chan *image.RGBA
|
||||||
|
|
||||||
|
snapshot chan bool
|
||||||
|
snapshotVCS chan *hardware.VCS
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAnim is the preferred method of initialisation for the Anim type
|
// NewAnim is the preferred method of initialisation for the Anim type
|
||||||
|
@ -63,6 +66,8 @@ func NewAnim(prefs *preferences.Preferences) (*Anim, error) {
|
||||||
emulationQuit: make(chan bool, 1),
|
emulationQuit: make(chan bool, 1),
|
||||||
emulationCompleted: make(chan bool, 1),
|
emulationCompleted: make(chan bool, 1),
|
||||||
Render: make(chan *image.RGBA, 60),
|
Render: make(chan *image.RGBA, 60),
|
||||||
|
snapshot: make(chan bool, 1),
|
||||||
|
snapshotVCS: make(chan *hardware.VCS, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
// emulation has completed, by definition, on startup
|
// emulation has completed, by definition, on startup
|
||||||
|
@ -211,6 +216,7 @@ func (thmb *Anim) Create(cartload cartridgeloader.Loader, numFrames int) {
|
||||||
select {
|
select {
|
||||||
case <-thmb.emulationQuit:
|
case <-thmb.emulationQuit:
|
||||||
return govern.Ending, nil
|
return govern.Ending, nil
|
||||||
|
case <-thmb.snapshot:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,3 +329,9 @@ func (thmb *Anim) Reset() {
|
||||||
func (thmb *Anim) EndRendering() error {
|
func (thmb *Anim) EndRendering() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Snapshot safely returns a copy of the emulation
|
||||||
|
func (thmb *Anim) Snapshot() *hardware.VCS {
|
||||||
|
thmb.snapshot <- true
|
||||||
|
return <-thmb.snapshotVCS
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue