Files
wehub-resource-sync c4536f7e05
CI / test (push) Failing after 1s
CI / macOS amd64 (push) Waiting to run
CI / macOS arm64 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:30:31 +08:00

137 lines
2.8 KiB
Go

package main
import (
"os"
"strings"
"time"
"loov.dev/lensm/internal/disasm"
"loov.dev/lensm/internal/goobj"
"loov.dev/lensm/internal/wasmobj"
)
type fileLoadRequest struct {
generation uint64
path string
}
type fileLoadResult struct {
generation uint64
file disasm.File
err error
}
func loadDisasmFile(path string) (disasm.File, error) {
if workInProgressWASM {
return wasmobj.Load(path)
}
return goobj.Load(path)
}
// loader loads disassembly files on its own goroutine and, when watch
// is set, polls the current path for modifications with a debounce so
// a binary is not reloaded mid-write.
type loader struct {
loadFile func(string) (disasm.File, error)
watch bool
requests chan fileLoadRequest
results chan fileLoadResult
stop chan struct{}
}
func newLoader(loadFile func(string) (disasm.File, error), watch bool) *loader {
l := &loader{
loadFile: loadFile,
watch: watch,
requests: make(chan fileLoadRequest, 1),
results: make(chan fileLoadResult, 1),
stop: make(chan struct{}),
}
go l.run()
return l
}
func (l *loader) Results() <-chan fileLoadResult { return l.results }
func (l *loader) Close() { close(l.stop) }
// Request replaces any queued request; only the latest path matters.
// Main event loop only.
func (l *loader) Request(generation uint64, path string) {
select {
case <-l.requests:
default:
}
l.requests <- fileLoadRequest{generation: generation, path: path}
}
// finish publishes a result, replacing an unconsumed previous one and
// closing its file.
func (l *loader) finish(result fileLoadResult) {
select {
case old := <-l.results:
if old.file != nil {
_ = old.file.Close()
}
default:
}
l.results <- result
}
func (l *loader) run() {
var lastModTime time.Time
var pendingModTime time.Time
var pendingSince time.Time
var path string
var generation uint64
tick := time.NewTicker(150 * time.Millisecond)
defer tick.Stop()
load := func(force bool, now time.Time) {
if path == "" {
return
}
stat, err := os.Stat(path)
if err != nil {
l.finish(fileLoadResult{generation: generation, err: err})
return
}
if !force && stat.ModTime().Equal(lastModTime) {
return
}
if !force {
if !stat.ModTime().Equal(pendingModTime) {
pendingModTime = stat.ModTime()
pendingSince = now
return
}
if now.Sub(pendingSince) < 300*time.Millisecond {
return
}
}
lastModTime = stat.ModTime()
pendingModTime = time.Time{}
file, err := l.loadFile(path)
l.finish(fileLoadResult{generation: generation, file: file, err: err})
}
for {
select {
case req := <-l.requests:
path = strings.TrimSpace(req.path)
generation = req.generation
lastModTime = time.Time{}
pendingModTime = time.Time{}
load(true, time.Now())
case now := <-tick.C:
if l.watch {
load(false, now)
}
case <-l.stop:
return
}
}
}