clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat: report crawl progress and mark RuneScape as pixel art

Dami committed d444238
A deep level is tens of thousands of tiles and runs for hours, but only
printed a line once it finished. Levels now report as they go, with an ETA
drawn from the rate so far. On a terminal the line redraws in place; piped
into a log it leaves ordinary lines behind.

Old School RuneScape is drawn on a pixel grid like the Pokemon maps, so it
folds down with nearest-neighbour sampling and renders without interpolation
rather than being blurred into its own grid.
commit
d44423871cdfff7b27cb3dfc7536fe608744e451
parent
c3f7b509e62ef5071e6b4f677872c5c68e0ba9b2
author
Dami <dami.etoile@gmail.com>
2 files changed, +53 -1
Mtools/crawl/main.go42++++++++++++++++++++++++++++++
Mtools/tiles/main.go12++++++++-
M tools/crawl/main.go +42 -0
@@ -38,6 +38,16 @@ const (
3838 wholeLevelBudget = 10243939 )4040 41+// lineEnd redraws the progress line in place on a terminal, and leaves ordinary42+// lines behind when the output is being piped into a log.43+var lineEnd = func() string {44+ info, err := os.Stdout.Stat()45+ if err == nil && info.Mode()&os.ModeCharDevice != 0 {46+ return "\r"47+ }48+ return "\n"49+}()50+4151 func main() {4252 options := parseOptions()4353 if err := run(options); err != nil {
@@ -260,6 +270,30 @@ func fetchLevel(
260270 var failure error261271 gate := make(chan struct{}, o.concurrency)262272 273+ // A deep level is tens of thousands of tiles and takes hours. Report as it274+ // goes, so the run can be watched rather than waited on. Held under mu with275+ // the counters it reads.276+ started := time.Now()277+ lastReport := started278+ report := func(done bool) {279+ if !done && (time.Since(lastReport) < 15*time.Second) {280+ return281+ }282+ lastReport = time.Now()283+ seen := result.fetched + result.skipped + result.absent + result.failed284+ if seen == 0 {285+ return286+ }287+ elapsed := time.Since(started)288+ remaining := time.Duration(0)289+ if seen < len(wanted) {290+ remaining = time.Duration(float64(elapsed) / float64(seen) * float64(len(wanted)-seen))291+ }292+ fmt.Printf(" z%-2d %6d/%d %5.1f MB %s elapsed %s left%s",293+ zoom, seen, len(wanted), float64(result.bytes)/1e6,294+ elapsed.Truncate(time.Second), remaining.Truncate(time.Second), lineEnd)295+ }296+263297 for _, coordinate := range wanted {264298 url := fmt.Sprintf("%s/games/%s/%d/%d/%d.%s",265299 strings.TrimSuffix(base, "/"), set.Path, zoom, coordinate[0], coordinate[1], extension)
@@ -312,6 +346,7 @@ func fetchLevel(
312346 failure = err313347 }314348 }349+ report(false)315350 index.put(tileRecord{316351 URL: url, Status: status, TileSetID: setID,317352 Zoom: zoom, X: coordinate[0], Y: coordinate[1],
@@ -337,9 +372,16 @@ func fetchLevel(
337372 result.fetched++338373 result.bytes += int64(len(body))339374 result.hashes[coordinate] = hash375+ report(false)340376 }(coordinate, url, path)341377 }342378 group.Wait()379+ mu.Lock()380+ if result.fetched > 0 && lineEnd == "\r" {381+ report(true)382+ fmt.Println()383+ }384+ mu.Unlock()343385 return result, failure344386 }345387
M tools/tiles/main.go +11 -1