Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/file: VirtualFS's Open should always a new entry for "." #3081

Closed
11 tasks
kazzmir opened this issue Sep 5, 2024 · 12 comments
Closed
11 tasks

internal/file: VirtualFS's Open should always a new entry for "." #3081

kazzmir opened this issue Sep 5, 2024 · 12 comments
Labels
Milestone

Comments

@kazzmir
Copy link

kazzmir commented Sep 5, 2024

Operating System

  • Windows
  • macOS
  • Linux
  • FreeBSD
  • OpenBSD
  • Android
  • iOS
  • Nintendo Switch
  • PlayStation 5
  • Xbox
  • Web Browsers

What feature would you like to be added?

fs.ReadDir(ebiten.DroppedFiles) returns a slice of all the files that have been dropped so far, but calling fs.ReadDir() again (within the same update tick) returns an empty slice.

Why is this needed?

It is useful to be able to get back the list of files in the DroppedFiles filesystem without having to cache the results manually.

fs.ReadDir() passes -1 for the count to root.ReadDir(int). Both file_js.go and file_notjs.go keep track of the number of files that ReadDir() has returned in the offset field. If count < 0 then the offset could remain the same.

file_notjs.go

ents := make([]fs.DirEntry, n)
for i := range ents {
  ...
}
// propose to add this if condition here
if count > 0 {
  v.offset += n
}

And a similar change in file_js.go

@hajimehoshi
Copy link
Owner

Unfortunately I cannot change this for backward compatibility. I might revisit this for v3 later.

@hajimehoshi hajimehoshi added this to the v3.0.0 milestone Sep 5, 2024
@hajimehoshi
Copy link
Owner

within the same update tick

Hmm, on second thought, this sounds a bug. Which platform did you try this?

@hajimehoshi
Copy link
Owner

Apparently everyplatform has this issue, right? I'll take a look. Thanks,

@hajimehoshi hajimehoshi changed the title DroppedFiles.ReadDir() should be idempotent internal/file: virtualFSRoot and dir don't implement fs.ReadDirFile correctly Sep 5, 2024
@hajimehoshi
Copy link
Owner

hajimehoshi commented Sep 5, 2024

https://pkg.go.dev/io/fs#ReadDirFile

type ReadDirFile interface {
	File

	// ReadDir reads the contents of the directory and returns
	// a slice of up to n DirEntry values in directory order.
	// Subsequent calls on the same file will yield further DirEntry values.
	//
	// If n > 0, ReadDir returns at most n DirEntry structures.
	// In this case, if ReadDir returns an empty slice, it will return
	// a non-nil error explaining why.
	// At the end of a directory, the error is io.EOF.
	// (ReadDir must return io.EOF itself, not an error wrapping io.EOF.)
	//
	// If n <= 0, ReadDir returns all the DirEntry values from the directory
	// in a single slice. In this case, if ReadDir succeeds (reads all the way
	// to the end of the directory), it returns the slice and a nil error.
	// If it encounters an error before the end of the directory,
	// ReadDir returns the DirEntry list read until that point and a non-nil error.
	ReadDir(n int) ([]DirEntry, error)
}

So, if n <= 0, all the entries must be returned whatever the internal offset is.

@hajimehoshi hajimehoshi modified the milestones: v2.8.0, v2.7.9 Sep 5, 2024
@kazzmir
Copy link
Author

kazzmir commented Sep 5, 2024

I tested on linux and in the browser, so that covers the two possible implementations in ebiten. I agree with your interpretation of ReadDir when n <= 0. I'm not quite sure what the designers of fs intended if the user did this

// suppose there are 8 total entries
r.ReadDir(2)
r.ReadDir(3)
r.ReadDir(-1) // should return all 8?
r.ReadDir(4) // should return 3, since there are 3 left as a result of the previous read of 2 and 3?

@hajimehoshi
Copy link
Owner

https://go.dev/play/p/PqKzzuemepx

Interestingly, os.DirFS's entry doesn't follow fs.ReadDirFile...???

@hajimehoshi
Copy link
Owner

I reported this. golang/go#69301 IIUC, either the implementation or the documentation is wrong.

@hajimehoshi hajimehoshi removed this from the v2.7.9 milestone Sep 6, 2024
@hajimehoshi hajimehoshi added external and removed bug labels Sep 6, 2024
@kazzmir
Copy link
Author

kazzmir commented Sep 6, 2024

Also the golang File.readdir implementation specifically mentions this behavior, but that means some other code in their implementation fails to account for it.

https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/os/dir_unix.go;l=103

 // Change the meaning of n for the implementation below.
    //  
    // The n above was for the public interface of "if n <= 0,
    // Readdir returns all the FileInfo from the directory in a
    // single slice".
    //
    // But below, we use only negative to mean looping until the
    // end and positive to mean bounded, with positive
    // terminating at 0.
    if n == 0 {
        n = -1
    }

I interpret this to mean 'if the public interface ReadDir(n) is passed an n value <= 0 then the public method shall return all entries in a single slice, but the low level implementation of File.readdir is under no such constraint to treat n that way.'

@hajimehoshi
Copy link
Owner

all the FileInfo

So this can be interpreted as 1) all the entries from the first whatever the offset is, or 2) all the remaining entries after the offset. Thought the actual os.DirFS adopts 2), IMHO the natural interpretation is 1). I hope the documentation would be improved. Let's wait for the Go team's response anyway.

@hajimehoshi hajimehoshi added bug and removed external labels Sep 6, 2024
@hajimehoshi hajimehoshi added this to the v2.7.9 milestone Sep 6, 2024
@hajimehoshi
Copy link
Owner

hajimehoshi commented Sep 6, 2024

Back to the original issue, this should always returns the number of files. This is a bug in Ebitengine

diff --git a/examples/dropfile/main.go b/examples/dropfile/main.go
index f78c27dab..cce148047 100644
--- a/examples/dropfile/main.go
+++ b/examples/dropfile/main.go
@@ -79,6 +79,14 @@ func (g *Game) Update() error {
                                }
                                g.m.Unlock()
                        }
+
+                       for i := 0; i < 3; i++ {
+                               ents, err := fs.ReadDir(files, ".")
+                               if err != nil {
+                                       panic(err)
+                               }
+                               println(len(ents)) // This should always returns the number of dropped files, not 0
+                       }
                }()
        }
        return nil

@hajimehoshi hajimehoshi changed the title internal/file: virtualFSRoot and dir don't implement fs.ReadDirFile correctly internal/file: VirtualFS's Open should always a new entry for "." Sep 6, 2024
@hajimehoshi hajimehoshi changed the title internal/file: VirtualFS's Open should always a new entry for "." internal/file: VirtualFS's Open should always a new entry for "." Sep 6, 2024
@kazzmir
Copy link
Author

kazzmir commented Sep 6, 2024

Doesn't file_js.go need a similar fix?

@hajimehoshi
Copy link
Owner

Doesn't file_js.go need a similar fix?

No. This should already work. fs.ReadDir calls the file system's Open, and before the fix, the desktop version's Open didn't return a new entry. The browser version always returned a new entry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants