added AsyncPath to archivefs. allows asynchronous use of archivefs

ROM select could noticeable stall the GUI if there were many files in a
directory
This commit is contained in:
JetSetIlly 2024-04-28 21:26:29 +01:00
parent 344b92e8f3
commit 3a5f7c79da
3 changed files with 129 additions and 35 deletions

102
archivefs/async.go Normal file
View file

@ -0,0 +1,102 @@
// 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 archivefs
// SetSelectedFilename is called after a successful Set()
type FilenameSetter interface {
SetSelectedFilename(string)
}
// AsyncResults are copies of archivefs path information that are safe to access asynchronously
type AsyncResults struct {
Entries []Node
Selected string
Dir string
Base string
}
// AsyncPath provides asynchronous access to an archivefs
type AsyncPath struct {
setter FilenameSetter
afs Path
results chan AsyncResults
err chan error
// Results of most recent change of path settings
Results AsyncResults
}
// NewAsyncPath is the preferred method of initialisation for the AsyncPath type
func NewAsyncPath(setter FilenameSetter) AsyncPath {
return AsyncPath{
setter: setter,
results: make(chan AsyncResults, 1),
err: make(chan error, 1),
}
}
// Close any open zip files and reset path
func (pth *AsyncPath) Close() {
// not sure how safe this is if it is called when the Set() goroutine is running
pth.afs.Close()
}
// Set archivefs path. Process() must be called in order to retreive the results
// of the Set()
func (pth *AsyncPath) Set(path string) error {
go func() {
pth.afs.Set(path)
entries, err := pth.afs.List()
if err != nil {
pth.err <- err
return
}
pth.results <- AsyncResults{
Entries: entries,
Selected: pth.afs.String(),
Dir: pth.afs.Dir(),
Base: pth.afs.Base(),
}
}()
return nil
}
// Process asynchronous requests. Must be called in order to receive the results
// of a Set(). Suitable to be called as part of a render loop
func (pth *AsyncPath) Process() error {
select {
case err := <-pth.err:
return err
case results := <-pth.results:
pth.Results = results
if pth.setter != nil {
if pth.afs.IsDir() {
pth.setter.SetSelectedFilename("")
} else {
pth.setter.SetSelectedFilename(pth.Results.Selected)
}
}
default:
}
return nil
}

View file

@ -219,6 +219,7 @@ func (afs *Path) List() ([]Node, error) {
return ent, nil
}
// Set archivefs path
func (afs *Path) Set(path string) error {
afs.Close()

View file

@ -45,10 +45,8 @@ type winSelectROM struct {
playmodeWin
debuggerWin
img *SdlImgui
path archivefs.Path
pathEntries []archivefs.Node
img *SdlImgui
path archivefs.AsyncPath
// selectedName is the name of the ROM in a normalised form
selectedName string
@ -98,6 +96,7 @@ func newSelectROM(img *SdlImgui) (window, error) {
propertyResult: make(chan properties.Entry, 1),
}
win.debuggerGeom.noFocusTracking = true
win.path = archivefs.NewAsyncPath(win)
var err error
@ -150,7 +149,7 @@ func (win *winSelectROM) setOpen(open bool) {
// open at the most recently selected ROM
recent := win.img.dbg.Prefs.RecentROM.String()
err := win.setPath(recent)
err := win.path.Set(recent)
if err != nil {
logger.Logf("sdlimgui", err.Error())
}
@ -252,6 +251,11 @@ func (win *winSelectROM) render() {
}
func (win *winSelectROM) draw() {
err := win.path.Process()
if err != nil {
logger.Logf("sdlimgui", err.Error())
}
imgui.BeginGroup()
// check for new property information
@ -259,7 +263,7 @@ func (win *winSelectROM) draw() {
case win.selectedProperties = <-win.propertyResult:
win.selectedName = win.selectedProperties.Name
if win.selectedName == "" {
win.selectedName = win.path.Base()
win.selectedName = win.path.Results.Base
win.selectedName = cartridgeloader.NameFromFilename(win.selectedName)
}
@ -281,16 +285,15 @@ func (win *winSelectROM) draw() {
}()
if imgui.Button("Parent") {
d := filepath.Dir(win.path.Dir())
err := win.setPath(d)
err := win.path.Set(filepath.Dir(win.path.Results.Dir))
if err != nil {
logger.Logf("sdlimgui", "error setting path (%s)", d)
logger.Logf("sdlimgui", err.Error())
}
win.scrollToTop = true
}
imgui.SameLine()
imgui.Text(archivefs.RemoveArchiveExt(win.path.Dir()))
imgui.Text(archivefs.RemoveArchiveExt(win.path.Results.Dir))
if imgui.BeginTable("romSelector", 2) {
imgui.TableSetupColumnV("filelist", imgui.TableColumnFlagsWidthStretch, -1, 0)
@ -309,7 +312,7 @@ func (win *winSelectROM) draw() {
// list directories
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.ROMSelectDir)
for _, e := range win.pathEntries {
for _, e := range win.path.Results.Entries {
// ignore dot files
if !win.showHidden && e.Name[0] == '.' {
continue
@ -328,10 +331,9 @@ func (win *winSelectROM) draw() {
}
if imgui.Selectable(s.String()) {
d := filepath.Join(win.path.Dir(), e.Name)
err := win.setPath(d)
err := win.path.Set(filepath.Join(win.path.Results.Dir, e.Name))
if err != nil {
logger.Logf("sdlimgui", "error setting path (%s)", d)
logger.Logf("sdlimgui", err.Error())
}
win.scrollToTop = true
}
@ -341,7 +343,7 @@ func (win *winSelectROM) draw() {
// list files
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.ROMSelectFile)
for _, e := range win.pathEntries {
for _, e := range win.path.Results.Entries {
// ignore dot files
if !win.showHidden && e.Name[0] == '.' {
continue
@ -371,14 +373,17 @@ func (win *winSelectROM) draw() {
}
if !e.IsDir {
selected := e.Name == win.path.Base()
selected := e.Name == win.path.Results.Base
if selected && win.centreOnFile {
imgui.SetScrollHereY(0.0)
}
if imgui.SelectableV(e.Name, selected, 0, imgui.Vec2{0, 0}) {
win.setPath(filepath.Join(win.path.Dir(), e.Name))
err := win.path.Set(filepath.Join(win.path.Results.Dir, e.Name))
if err != nil {
logger.Logf("sdlimgui", err.Error())
}
}
if imgui.IsItemHovered() && imgui.IsMouseDoubleClicked(0) {
win.insertCartridge()
@ -580,7 +585,7 @@ func (win *winSelectROM) draw() {
var s string
// load or reload button
if win.path.String() == win.img.cache.VCS.Mem.Cart.Filename {
if win.path.Results.Selected == win.img.cache.VCS.Mem.Cart.Filename {
s = fmt.Sprintf("Reload %s", win.selectedName)
} else {
s = fmt.Sprintf("Load %s", win.selectedName)
@ -622,29 +627,15 @@ func (win *winSelectROM) insertCartridge() {
return
}
win.img.dbg.InsertCartridge(win.path.String())
win.img.dbg.InsertCartridge(win.path.Results.Selected)
// close rom selected in both the debugger and playmode
win.debuggerSetOpen(false)
win.playmodeSetOpen(false)
}
func (win *winSelectROM) setPath(path string) error {
var err error
win.path.Set(path)
win.pathEntries, err = win.path.List()
if err != nil {
return err
}
if win.path.IsDir() {
win.setSelectedFile("")
} else {
win.setSelectedFile(win.path.String())
}
return nil
}
func (win *winSelectROM) setSelectedFile(filename string) {
// imnplements the archivefs.FilenameSetter interface
func (win *winSelectROM) SetSelectedFilename(filename string) {
// return immediately if the filename is empty
if filename == "" {
return