aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/codes/Aztec.mc21
-rw-r--r--source/codes/Codes.mc72
-rw-r--r--source/codes/DataMatrix.mc24
-rw-r--r--source/codes/Pdf417.mc21
-rw-r--r--source/codes/Qr.mc30
-rw-r--r--source/codes/ReedSolomon.mc5
-rw-r--r--source/ui/CodeView.mc3
-rw-r--r--test/codes/CodeCacheTest.mc46
8 files changed, 214 insertions, 8 deletions
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<Number> {
+ 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<Number> = [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<Number> {
+ 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<Number>, 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<Number> {
+ 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<Number> {
+ 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<Number> {
+ 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;
diff --git a/source/ui/CodeView.mc b/source/ui/CodeView.mc
index 20b9bf2..efa23d2 100644
--- a/source/ui/CodeView.mc
+++ b/source/ui/CodeView.mc
@@ -179,6 +179,9 @@ class CodeView extends WatchUi.View {
_code = new Codes.CodeError(err != null ? err : "could not encode");
} else {
var cache = job.cache;
+ var ms = job.stageMs;
+ Diag.data("matrix_stages", "type=" + header.type + " cached=" + (cache == null) + " encode_ms=" + ms[0]
+ + " pack_ms=" + ms[1] + " runs_ms=" + ms[2]);
if (cache != null) {
Diag.data("matrix_encoded", "type=" + header.type + " size=" + runs.width + "x" + runs.height
+ " ms=" + (System.getTimer() - _startedAt));
diff --git a/test/codes/CodeCacheTest.mc b/test/codes/CodeCacheTest.mc
index 2ad7f05..3b2a02e 100644
--- a/test/codes/CodeCacheTest.mc
+++ b/test/codes/CodeCacheTest.mc
@@ -110,3 +110,49 @@ function codeRunsCoverExactlyTheDarkModules(logger as Test.Logger) as Boolean {
}
return true;
}
+
+// Steps a display job to the end and checks its percentage follows the
+// work: never down, never above 99, no big jumps, and a real spread (the
+// "Encoding" screen once showed 90 % at the first frame and sat there).
+function progressIsSteady(job as Codes.DisplayJob) as Boolean {
+ var last = job.percent();
+ var biggest = last;
+ var steps = 0;
+ var steady = last < 35;
+ while (!job.step(1)) {
+ var now = job.percent();
+ steady = steady && now >= last && now <= 99;
+ biggest = now - last > biggest ? now - last : biggest;
+ last = now;
+ steps++;
+ }
+ return steady && biggest <= 35 && steps >= 3 && last >= 60 && job.error == null;
+}
+
+(:test)
+function displayProgressFollowsTheWork(logger as Test.Logger) as Boolean {
+ var member = "https://example.com/member/fake-0042?name=Alice%20Example&card=0000111122223333&valid=2027-12-31&level=gold";
+ var long = "";
+ for (var i = 0; i < 40; i++) {
+ long += "abcdefghij";
+ }
+ var qr = progressIsSteady(new Codes.DisplayJob(new Qr.QrJob(member, 1, 0, -1), null));
+ var qrBig = progressIsSteady(new Codes.DisplayJob(new Qr.QrJob(long, 0, 0, -1), null));
+ var qrForced = progressIsSteady(new Codes.DisplayJob(new Qr.QrJob(member, 1, 0, 3), null));
+ var dm = progressIsSteady(new Codes.DisplayJob(new DataMatrix.DataMatrixJob(long.substring(0, 200) as String), null));
+ var az = progressIsSteady(new Codes.DisplayJob(new Aztec.AztecJob(long.substring(0, 200) as String, 1), null));
+ var pdf = progressIsSteady(new Codes.DisplayJob(new Pdf417.Pdf417Job(long.substring(0, 200) as String, 1, 260, 260, true),
+ null));
+ // A cached code only runs the finder: 0 at the start, then up to 99.
+ var bits = new [60 * 60]b;
+ for (var i = 0; i < bits.size(); i++) {
+ bits[i] = (i * 7 / 3) % 2;
+ }
+ var packed = Codes.Packed.parse(new Codes.Matrix(60, 60, 4, bits).toBytes()) as Codes.Packed;
+ var cached = new Codes.DisplayJob(null, packed);
+ var start = cached.percent();
+ var cachedSteady = progressIsSteady(cached);
+ logger.debug("progress qr=" + qr + " big=" + qrBig + " forced=" + qrForced + " dm=" + dm + " az=" + az + " pdf=" + pdf
+ + " cached=" + cachedSteady);
+ return qr && qrBig && qrForced && dm && az && pdf && start == 0 && cachedSteady;
+}