From 140a6660f63f88d05ba9aae0bf1c4b401bb6574d Mon Sep 17 00:00:00 2001 From: cirakg Date: Sun, 25 Oct 2026 14:17:45 +0100 Subject: fix(codes): encoding progress follows the work The QR percentage counted phases and the display froze at 90 % once encoding ended, so the first opening sat at 90 % while the slow stages ran. Each 2D symbology now weights its phases by measured cost, interpolates inside the long ones, and the display splits encode, pack and runs; a cached code counts its runs from 0. Co-Authored-By: Claude Opus 5.5 --- source/codes/Aztec.mc | 21 ++++++++++++- source/codes/Codes.mc | 72 +++++++++++++++++++++++++++++++++++++++++++-- source/codes/DataMatrix.mc | 24 ++++++++++++++- source/codes/Pdf417.mc | 21 ++++++++++++- source/codes/Qr.mc | 30 +++++++++++++++++-- source/codes/ReedSolomon.mc | 5 ++++ 6 files changed, 165 insertions(+), 8 deletions(-) (limited to 'source/codes') diff --git a/source/codes/Aztec.mc b/source/codes/Aztec.mc index 495abbf..0d4a856 100644 --- a/source/codes/Aztec.mc +++ b/source/codes/Aztec.mc @@ -44,6 +44,8 @@ module Aztec { const FIELD_ENTRIES_PER_STEP = 500; // GF(1024) / GF(4096) tables const GENERATOR_WORK_PER_STEP = 500; // generator coefficient updates const ECC_WORK_PER_STEP = 400; // data symbols x EC symbols + // Cumulative percent at the end of each phase 0-5 (see AztecJob.percent). + const PHASE_ENDS = [8, 14, 20, 64, 72, 100]; const GRID_LINES_PER_STEP = 4; // reference grid const WORDS_PER_STEP = 50; // codewords into bits const PAIRS_PER_STEP = 250; // spiral bit pairs @@ -314,8 +316,25 @@ module Aztec { _ec = EC_PERCENT[option] as Number; } + // Progress 0-100: the share of each phase (simulator timings; the + // Reed-Solomon codewords and the spiral take the most). function percent() as Number { - return _phase * 100 / 6; + var done = 0; + var total = 0; + var e = _encoder; + if (_phase == 3 && e != null) { // data symbols done + done = e.percent(); + total = 100; + } else if (_phase == 5) { // bits placed + done = _at; + total = _bits.size(); + } + return Codes.phased(PHASE_ENDS, _phase, done, total); + } + + // Packing and runs: about 86 / 3 / 11 % of a whole display job. + function stageEnds() as Array { + return [86, 89]; } function matrix() as Codes.Matrix? { diff --git a/source/codes/Codes.mc b/source/codes/Codes.mc index 247acfa..9527a75 100644 --- a/source/codes/Codes.mc +++ b/source/codes/Codes.mc @@ -7,6 +7,7 @@ // come back as a CodeError with a message for the user, never an exception. import Toybox.Lang; +import Toybox.System; module Codes { @@ -174,6 +175,12 @@ module Codes { result[6] = matrix.rowHeight; } + // Progress 0-99 through the bytes. + function percent() as Number { + var full = _matrix.modules.size() / 8; + return full > 0 ? 99 * _i / full : 0; + } + function step(budget as Number) as Boolean { var m = _matrix.modules; var n = m.size(); @@ -235,6 +242,11 @@ module Codes { result = new Runs(packed); } + // Progress 0-99 through the rows. + function percent() as Number { + return 99 * _row / _packed.height; + } + function step(budget as Number) as Boolean { var p = _packed; var w = p.width; @@ -276,31 +288,63 @@ module Codes { // A 2D code from the cache or a fresh encoding to Runs: encode (unless // cached), pack (`cache` then holds the bytes to store), find the runs. + // percent() follows the work: the encoder's share of 0-100 comes from + // its stageEnds(), then the packer's, then the run finder's; a cached + // code only runs the finder, over the whole range. class DisplayJob extends Jobs.Job { var error as String? = null; var cache as ByteArray? = null; var runs as Runs? = null; + // Milliseconds spent in each stage's steps (encode, pack, runs), for + // the debug log. + var stageMs as Array = [0, 0, 0]; private var _encoder as MatrixJob?; private var _packer as Packer? = null; private var _finder as RunFinder? = null; + private var _encodeEnd as Number = 0; + private var _packEnd as Number = 0; // Either an encoder (fresh) or the cached bytes. function initialize(encoder as MatrixJob?, packed as Packed?) { Job.initialize(); _encoder = encoder; + if (encoder != null) { + var ends = encoder.stageEnds(); + _encodeEnd = ends[0]; + _packEnd = ends[1]; + } if (packed != null) { _finder = new RunFinder(packed); } } - // Progress 0-100: encoding is most of it. + // Progress 0-99 (100 is never shown: the code replaces this screen), + // never going back: each stage starts where the one before ended. function percent() as Number { var e = _encoder; - return e != null ? e.percent() * 9 / 10 : 90; + var p = _packer; + var f = _finder; + var done = 0; + if (e != null) { + done = e.percent() * _encodeEnd / 100; + } else if (p != null) { + done = _encodeEnd + (_packEnd - _encodeEnd) * p.percent() / 100; + } else if (f != null) { + done = _packEnd + (100 - _packEnd) * f.percent() / 100; + } + return done < 99 ? done : 99; } function step(budget as Number) as Boolean { + var started = System.getTimer(); + var stage = _finder != null ? 2 : (_packer != null ? 1 : 0); + var done = advance(budget); + stageMs[stage] += System.getTimer() - started; + return done; + } + + private function advance(budget as Number) as Boolean { var finder = _finder; if (finder != null) { if (!finder.step(budget)) { @@ -344,11 +388,20 @@ module Codes { Job.initialize(); } - // Progress 0-100 for the UI. + // Progress 0-100 for the UI, following the work done so far. function percent() as Number { return 0; } + // Where the pack and the run stages begin in a display job's 0-100 + // (the encoding is [0, ends[0]], packing [ends[0], ends[1]], the + // runs the rest), weighted by their time in the simulator's + // benchmark: encoding takes by far the most, except for PDF417, + // whose wide matrices make the runs a third. + function stageEnds() as Array { + return [90, 92]; + } + function matrix() as Matrix? { return null; } @@ -379,6 +432,19 @@ module Codes { return isLinear(symbology) || symbology == PDF417 ? 1 : 2; } + // Progress 0-100 through phases: ends[i] is the percent at the end of + // phase i; `done` of `total` is how far the current phase has got (it + // may be a flat 0 for a phase that reports no progress). A finished job + // (phase past the last) is 100. Never goes back while `done` doesn't. + function phased(ends as Array, phase as Number, done as Number, total as Number) as Number { + if (phase >= ends.size()) { + return 100; + } + var from = phase > 0 ? ends[phase - 1] : 0; + var inside = total > 0 && done > 0 ? (done < total ? done : total) : 0; + return from + (ends[phase] - from) * inside / (total > 0 ? total : 1); + } + // True when a code of w x h modules (quiet zone included) at px pixels // per module fits the screen: inside the circle (less a 4 px margin) on // round screens; elsewhere the full width and the height less room for diff --git a/source/codes/DataMatrix.mc b/source/codes/DataMatrix.mc index 76a29e8..4fcc179 100644 --- a/source/codes/DataMatrix.mc +++ b/source/codes/DataMatrix.mc @@ -39,6 +39,8 @@ module DataMatrix { // with a 1,500-byte payload (144 x 144 modules). const BYTES_PER_STEP = 250; // codeword encodation const ECC_WORK_PER_STEP = 400; // data symbols x EC symbols + // Cumulative percent at the end of each phase 0-4 (see DataMatrixJob.percent). + const PHASE_ENDS = [2, 44, 45, 69, 100]; const BUILD_MODULES_PER_STEP = 300; // symbol modules var field as ReedSolomon.Field? = null; @@ -184,8 +186,28 @@ module DataMatrix { _data = Bytes.fromUtf8(text); } + // Progress 0-100: the share of each phase (simulator timings; the + // Reed-Solomon blocks, the placement and the symbol take the most). function percent() as Number { - return _phase * 20; + var done = 0; + var total = 0; + if (_phase == 1 && _size.size() > 0) { // blocks done, and the current one + var e = _encoder; + done = _block * 100 + (e != null ? e.percent() : 0); + total = _size[5] * 100; + } else if (_phase == 3 && _size.size() > 0) { // codewords placed + done = _chr - 1; + total = _size[3] + _size[4]; + } else if (_phase == 4 && _size.size() > 0) { // rows built + done = _buildRow; + total = _size[0]; + } + return Codes.phased(PHASE_ENDS, _phase, done, total); + } + + // Packing and runs: about 84 / 3 / 13 % of a whole display job. + function stageEnds() as Array { + return [84, 87]; } function matrix() as Codes.Matrix? { diff --git a/source/codes/Pdf417.mc b/source/codes/Pdf417.mc index a294aa7..c905a2d 100644 --- a/source/codes/Pdf417.mc +++ b/source/codes/Pdf417.mc @@ -35,6 +35,8 @@ module Pdf417 { const NUMERIC_LATCH = 902; const ROW_HEIGHT = 3; const QUIET = 2; + // Cumulative percent at the end of each phase 0-2 (see Pdf417Job.percent). + const PHASE_ENDS = [40, 70, 100]; const MAX_CODEWORDS = 928; const KIND_TEXT = 0; @@ -383,8 +385,25 @@ module Pdf417 { level = ecLevel; } + // Progress 0-100: the share of each phase (simulator timings), and + // inside the Reed-Solomon and row phases how far they have got. function percent() as Number { - return _phase == 1 && _words.size() > 0 ? 20 + 60 * _at / _words.size() : _phase * 30; + var done = 0; + var total = 0; + if (_phase == 1) { // data codewords done + done = _at; + total = _words.size(); + } else if (_phase == 2) { // rows built + done = _at; + total = rows; + } + return Codes.phased(PHASE_ENDS, _phase, done, total); + } + + // Packing and runs: about 62 / 7 / 31 % of a whole display job (the + // wide matrices make the runs a third). + function stageEnds() as Array { + return [62, 69]; } function matrix() as Codes.Matrix? { diff --git a/source/codes/Qr.mc b/source/codes/Qr.mc index 1fac051..5ced89e 100644 --- a/source/codes/Qr.mc +++ b/source/codes/Qr.mc @@ -49,6 +49,8 @@ module Qr { const WRITE_CHARS_PER_STEP = 300; // Phase 1: data symbols times EC symbols per step (about 35 bytecodes each). const ECC_WORK_PER_STEP = 400; + // Cumulative percent at the end of each phase 0-6 (see QrJob.percent). + const PHASE_ENDS = [8, 16, 18, 27, 39, 93, 100]; // Phase 2: codewords interleaved per step. const INTERLEAVE_PER_STEP = 600; const PREP_SEGMENT = 0; @@ -366,9 +368,33 @@ module Qr { _version = forceVersion; } - // Progress 0-100 for the UI. + // Progress 0-100 for the UI: the share of each phase of the work + // (simulator timings of a version 6, a version 40 and a level H + // code; mask scoring is about half of it), and inside the long ones + // how far they have got. function percent() as Number { - return _phase * 100 / 7; + var done = 0; + var total = 0; + if (_phase == 1 && _blocks.size() > 0) { // blocks done, and the current one + var e = _encoder; + done = _cursor * 100 + (e != null ? e.percent() : 0); + total = _blocks.size() * 100; + } else if (_phase == 3) { // column pairs, right to left + done = _size - 1 - _cursor; + total = _size - 1; + } else if (_phase == 5) { // lines of the masks scored so far + done = (_maskIndex * 2 + _maskPass) * _size + _cursor; + total = 16 * _size; + } else if (_phase == 6) { // rows masked + done = _cursor; + total = _size; + } + return Codes.phased(PHASE_ENDS, _phase, done, total); + } + + // Packing and runs: about 92 / 2 / 6 % of a whole display job. + function stageEnds() as Array { + return [92, 94]; } // The code as a generic matrix with QR's 4-module quiet zone. diff --git a/source/codes/ReedSolomon.mc b/source/codes/ReedSolomon.mc index 58339ae..5fc75aa 100644 --- a/source/codes/ReedSolomon.mc +++ b/source/codes/ReedSolomon.mc @@ -171,6 +171,11 @@ module ReedSolomon { } } + // Progress 0-100 through the data symbols. + function percent() as Number { + return _data.size() > 0 ? 100 * _i / _data.size() : 100; + } + // Up to `symbols` more data symbols; true when all are in. function step(symbols as Number) as Boolean { var rem = remainder; -- cgit v1.2.3