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>
| M | tools/ | 42 | ++++++++++++++++++++++++++++++ |
| M | tools/ | 12 | ++++++++- |
@@ -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 @@ -936,8 +936,18 @@ func normalizeFormat(value string) string {
936936 return value937937 }938938 939+// isPixelArt marks maps drawn on a pixel grid. They are folded down with940+// nearest-neighbour sampling and rendered without interpolation, so the grid941+// survives instead of being blurred into it.939942 func isPixelArt(gameSlug string) bool {940- return strings.HasPrefix(gameSlug, "pokemon-")943+ if strings.HasPrefix(gameSlug, "pokemon-") {944+ return true945+ }946+ switch gameSlug {947+ case "old-school-runescape":948+ return true949+ }950+ return false941951 }942952 943953 func slugify(value string) string {