@chestnutlabs/toolpath-core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/metadata.d.ts +37 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/source-map.d.ts +31 -0
- package/dist/source-map.d.ts.map +1 -0
- package/dist/source-map.js +74 -0
- package/dist/time.d.ts +33 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +41 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Neutral **`ToolpathIR`** and **capability model** for the Chestnut Labs G-code Preview toolpath stack.
|
|
4
4
|
This is the replacement seam for the whole stack: the parser **writes** it, renderers and analyzers **read**
|
|
5
|
-
it. See [`docs/design/DD-001`](
|
|
5
|
+
it. See [`docs/design/DD-001`](https://github.com/ChestnutLabs/gcode-preview/blob/dev/docs/design/DD-001-toolpath-ir-and-capability-model.md).
|
|
6
6
|
|
|
7
7
|
**Design rules**
|
|
8
8
|
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/metadata.d.ts
CHANGED
|
@@ -64,6 +64,39 @@ export interface FilamentInfo {
|
|
|
64
64
|
color?: string;
|
|
65
65
|
name?: string;
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Slicer-reported filament consumption (#183) — the "info panel" totals a slicer emits as G-code
|
|
69
|
+
* comments. Absent when the slicer doesn't report it (capability-honest). Aggregate across extruders;
|
|
70
|
+
* fields are independently optional (a slicer may report length but not weight).
|
|
71
|
+
*/
|
|
72
|
+
export interface FilamentUsage {
|
|
73
|
+
/** Total filament length in millimetres. */
|
|
74
|
+
lengthMm?: number;
|
|
75
|
+
/** Total extruded volume in cm³. */
|
|
76
|
+
volumeCm3?: number;
|
|
77
|
+
/** Total filament weight in grams. */
|
|
78
|
+
weightG?: number;
|
|
79
|
+
source: {
|
|
80
|
+
adapterId: string;
|
|
81
|
+
evidence: string;
|
|
82
|
+
srcByte?: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The slicer's own print-time estimate (#183) — the trustworthy figure for a time readout / time
|
|
87
|
+
* scrub, versus a kinematic estimate. Absent when the slicer doesn't emit it.
|
|
88
|
+
*/
|
|
89
|
+
export interface PrintEstimate {
|
|
90
|
+
/** Estimated print duration in seconds. */
|
|
91
|
+
seconds: number;
|
|
92
|
+
/** Slicer mode label when given (e.g. PrusaSlicer 'normal' / 'silent'). */
|
|
93
|
+
mode?: string;
|
|
94
|
+
source: {
|
|
95
|
+
adapterId: string;
|
|
96
|
+
evidence: string;
|
|
97
|
+
srcByte?: number;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
67
100
|
export interface ThumbnailData {
|
|
68
101
|
width: number;
|
|
69
102
|
height: number;
|
|
@@ -80,6 +113,10 @@ export interface DialectMetadata {
|
|
|
80
113
|
dialects?: DialectDetection[];
|
|
81
114
|
machine?: MachineGeometry;
|
|
82
115
|
filaments?: FilamentInfo[];
|
|
116
|
+
/** Slicer-reported total filament consumption (#183); absent when not emitted. */
|
|
117
|
+
filamentUsage?: FilamentUsage;
|
|
118
|
+
/** The slicer's own print-time estimate (#183); absent when not emitted. */
|
|
119
|
+
printEstimate?: PrintEstimate;
|
|
83
120
|
thumbnails?: ThumbnailData[];
|
|
84
121
|
/** Whitelisted key/value settings only — bounded, never local paths. */
|
|
85
122
|
raw?: Record<string, string>;
|
package/dist/metadata.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,WAAW,MAAM;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,gEAAgE;AAChE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EACC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAC1C;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACtD;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC1C,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,UAAU,EAAE,UAAU,CAAC;IACvB,oEAAoE;IACpE,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACnE;AAED,oEAAoE;AACpE,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IACxC,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;IAC7B,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B"}
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source-line ↔ segment mapping (#184) — the "G-code debugger" surface: resolve a rendered segment
|
|
3
|
+
* to the G-code line that produced it, and a source line back to its segment. Framework-free and
|
|
4
|
+
* built on data the IR already carries (`segments.srcByte`, the byte→segment `sourceIndex`); the only
|
|
5
|
+
* missing piece is byte↔line, which needs the source text (the IR stores byte offsets, not lines).
|
|
6
|
+
*
|
|
7
|
+
* Build a {@link SourceLineIndex} once from the source, then all four lookups are O(log n).
|
|
8
|
+
*/
|
|
9
|
+
import { type SourceIndex } from './ir.js';
|
|
10
|
+
/** Byte offsets of each line start (index 0 = line 1 at byte 0), plus the total length. */
|
|
11
|
+
export interface SourceLineIndex {
|
|
12
|
+
/** `lineStarts[k]` is the byte offset where line `k + 1` begins. */
|
|
13
|
+
lineStarts: Uint32Array;
|
|
14
|
+
/** Total source length in bytes. */
|
|
15
|
+
byteLength: number;
|
|
16
|
+
}
|
|
17
|
+
/** Build the line index from the raw source (string or the parsed bytes). O(bytes), one pass. */
|
|
18
|
+
export declare function buildSourceLineIndex(source: string | Uint8Array): SourceLineIndex;
|
|
19
|
+
/** 1-based line number containing `byteOffset` (binary search). Clamped to [1, lineCount]. */
|
|
20
|
+
export declare function lineAtByte(index: SourceLineIndex, byteOffset: number): number;
|
|
21
|
+
/** Byte range `[start, end)` of a 1-based `line`; null when out of range. `end` excludes the newline-led next line. */
|
|
22
|
+
export declare function byteRangeOfLine(index: SourceLineIndex, line: number): [number, number] | null;
|
|
23
|
+
/** 1-based source line that produced segment `segIndex`; null when the index is out of range. */
|
|
24
|
+
export declare function sourceLineOfSegment(srcByte: Uint32Array, lineIndex: SourceLineIndex, segIndex: number): number | null;
|
|
25
|
+
/**
|
|
26
|
+
* The segment produced by a 1-based source `line`, or -1 when the line produced no segment (a comment,
|
|
27
|
+
* a blank line, or a non-motion command). Resolves to the first segment whose source byte falls within
|
|
28
|
+
* the line's byte range.
|
|
29
|
+
*/
|
|
30
|
+
export declare function segmentAtSourceLine(sourceIndex: SourceIndex, lineIndex: SourceLineIndex, line: number): number;
|
|
31
|
+
//# sourceMappingURL=source-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-map.d.ts","sourceRoot":"","sources":["../src/source-map.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,UAAU,EAAE,WAAW,CAAC;IACxB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iGAAiG;AACjG,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,eAAe,CAYjF;AAED,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAe7E;AAED,uHAAuH;AACvH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAM7F;AAED,iGAAiG;AACjG,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGrH;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAiB9G"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Build the line index from the raw source (string or the parsed bytes). O(bytes), one pass. */
|
|
2
|
+
export function buildSourceLineIndex(source) {
|
|
3
|
+
// TextEncoder is a runtime global (browser + Node); reach it via globalThis so this DOM-free
|
|
4
|
+
// package (lib: ES2022, types: []) typechecks without pulling the DOM lib.
|
|
5
|
+
const Enc = globalThis.TextEncoder;
|
|
6
|
+
const bytes = typeof source === 'string' ? new Enc().encode(source) : source;
|
|
7
|
+
const starts = [0];
|
|
8
|
+
// A trailing '\n' ends the last line rather than starting an empty new one (editor convention),
|
|
9
|
+
// so only record a start when bytes follow the newline.
|
|
10
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
11
|
+
if (bytes[i] === 0x0a /* \n */ && i + 1 < bytes.length)
|
|
12
|
+
starts.push(i + 1);
|
|
13
|
+
}
|
|
14
|
+
return { lineStarts: Uint32Array.from(starts), byteLength: bytes.length };
|
|
15
|
+
}
|
|
16
|
+
/** 1-based line number containing `byteOffset` (binary search). Clamped to [1, lineCount]. */
|
|
17
|
+
export function lineAtByte(index, byteOffset) {
|
|
18
|
+
const starts = index.lineStarts;
|
|
19
|
+
let lo = 0;
|
|
20
|
+
let hi = starts.length - 1;
|
|
21
|
+
let ans = 0;
|
|
22
|
+
while (lo <= hi) {
|
|
23
|
+
const mid = (lo + hi) >> 1;
|
|
24
|
+
if (starts[mid] <= byteOffset) {
|
|
25
|
+
ans = mid;
|
|
26
|
+
lo = mid + 1;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
hi = mid - 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return ans + 1; // 1-based
|
|
33
|
+
}
|
|
34
|
+
/** Byte range `[start, end)` of a 1-based `line`; null when out of range. `end` excludes the newline-led next line. */
|
|
35
|
+
export function byteRangeOfLine(index, line) {
|
|
36
|
+
const starts = index.lineStarts;
|
|
37
|
+
if (line < 1 || line > starts.length)
|
|
38
|
+
return null;
|
|
39
|
+
const start = starts[line - 1];
|
|
40
|
+
const end = line < starts.length ? starts[line] : index.byteLength;
|
|
41
|
+
return [start, end];
|
|
42
|
+
}
|
|
43
|
+
/** 1-based source line that produced segment `segIndex`; null when the index is out of range. */
|
|
44
|
+
export function sourceLineOfSegment(srcByte, lineIndex, segIndex) {
|
|
45
|
+
if (segIndex < 0 || segIndex >= srcByte.length)
|
|
46
|
+
return null;
|
|
47
|
+
return lineAtByte(lineIndex, srcByte[segIndex]);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The segment produced by a 1-based source `line`, or -1 when the line produced no segment (a comment,
|
|
51
|
+
* a blank line, or a non-motion command). Resolves to the first segment whose source byte falls within
|
|
52
|
+
* the line's byte range.
|
|
53
|
+
*/
|
|
54
|
+
export function segmentAtSourceLine(sourceIndex, lineIndex, line) {
|
|
55
|
+
const range = byteRangeOfLine(lineIndex, line);
|
|
56
|
+
if (range === null)
|
|
57
|
+
return -1;
|
|
58
|
+
// Smallest indexed byte >= range start whose byte is < range end.
|
|
59
|
+
const offsets = sourceIndex.byteOffsets;
|
|
60
|
+
let lo = 0;
|
|
61
|
+
let hi = offsets.length;
|
|
62
|
+
while (lo < hi) {
|
|
63
|
+
const mid = (lo + hi) >>> 1;
|
|
64
|
+
if (offsets[mid] < range[0])
|
|
65
|
+
lo = mid + 1;
|
|
66
|
+
else
|
|
67
|
+
hi = mid;
|
|
68
|
+
}
|
|
69
|
+
if (lo < offsets.length && offsets[lo] >= range[0] && offsets[lo] < range[1]) {
|
|
70
|
+
return sourceIndex.segmentIndices[lo];
|
|
71
|
+
}
|
|
72
|
+
// The line produced no segment (comment, blank, or a non-motion command) — honestly -1.
|
|
73
|
+
return -1;
|
|
74
|
+
}
|
package/dist/time.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinematic time axis over the IR (#181) — a per-segment duration model for a time readout and
|
|
3
|
+
* time-based scrubbing, derived from geometry the IR already carries.
|
|
4
|
+
*
|
|
5
|
+
* Each segment's duration is its 3D length divided by its feedrate (`feedrate` is mm/min). This is a
|
|
6
|
+
* **kinematic estimate**: constant-velocity per segment, NOT acceleration/jerk-aware, so it slightly
|
|
7
|
+
* *under*estimates real print time. A segment with an unknown feedrate (NaN, before the first `F`)
|
|
8
|
+
* contributes zero and flags the estimate as approximate — never a fabricated duration. When the
|
|
9
|
+
* slicer's own estimate is available (`DialectMetadata.printEstimate`, #183), prefer it; this is the
|
|
10
|
+
* honest fallback and the axis time-scrub always needs.
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolpathIR } from './ir.js';
|
|
13
|
+
export interface ToolpathTime {
|
|
14
|
+
/** Cumulative END time (ms) of each segment; length = `segments.count`, monotonic non-decreasing. */
|
|
15
|
+
cumulativeMs: Float64Array;
|
|
16
|
+
/** Total kinematic duration (ms). */
|
|
17
|
+
totalMs: number;
|
|
18
|
+
/**
|
|
19
|
+
* True if any moving segment had an unknown feedrate (contributed 0). The estimate is then a lower
|
|
20
|
+
* bound / approximate — a consumer should disclose it (and prefer the slicer estimate when present).
|
|
21
|
+
*/
|
|
22
|
+
hasUnknownFeedrate: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Build the cumulative kinematic time axis for `ir`. O(segments), no allocation beyond the axis. */
|
|
25
|
+
export declare function computeToolpathTime(ir: ToolpathIR): ToolpathTime;
|
|
26
|
+
/**
|
|
27
|
+
* The number of segments completed at time `ms` — i.e. the count of segments whose cumulative END
|
|
28
|
+
* time is ≤ `ms` (binary search). 0 before the first segment finishes; `count` at/after the end. This
|
|
29
|
+
* is exactly a scrub cut position (segments `[0, result)` are done), so it maps straight to a
|
|
30
|
+
* segment-index scrub.
|
|
31
|
+
*/
|
|
32
|
+
export declare function segmentsCompletedAtTime(cumulativeMs: Float64Array, ms: number): number;
|
|
33
|
+
//# sourceMappingURL=time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,WAAW,YAAY;IAC3B,qGAAqG;IACrG,YAAY,EAAE,YAAY,CAAC;IAC3B,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,qGAAqG;AACrG,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,UAAU,GAAG,YAAY,CAoBhE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAStF"}
|
package/dist/time.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Build the cumulative kinematic time axis for `ir`. O(segments), no allocation beyond the axis. */
|
|
2
|
+
export function computeToolpathTime(ir) {
|
|
3
|
+
const seg = ir.segments;
|
|
4
|
+
const n = seg.count;
|
|
5
|
+
const cumulativeMs = new Float64Array(n);
|
|
6
|
+
let t = 0;
|
|
7
|
+
let hasUnknownFeedrate = false;
|
|
8
|
+
for (let i = 0; i < n; i++) {
|
|
9
|
+
const dx = seg.x1[i] - seg.x0[i];
|
|
10
|
+
const dy = seg.y1[i] - seg.y0[i];
|
|
11
|
+
const dz = seg.z1[i] - seg.z0[i];
|
|
12
|
+
const dist = Math.hypot(dx, dy, dz);
|
|
13
|
+
const f = seg.feedrate[i]; // mm/min
|
|
14
|
+
if (Number.isFinite(f) && f > 0) {
|
|
15
|
+
t += dist / (f / 60000); // mm ÷ (mm/ms) = ms
|
|
16
|
+
}
|
|
17
|
+
else if (dist > 0) {
|
|
18
|
+
hasUnknownFeedrate = true;
|
|
19
|
+
}
|
|
20
|
+
cumulativeMs[i] = t;
|
|
21
|
+
}
|
|
22
|
+
return { cumulativeMs, totalMs: t, hasUnknownFeedrate };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The number of segments completed at time `ms` — i.e. the count of segments whose cumulative END
|
|
26
|
+
* time is ≤ `ms` (binary search). 0 before the first segment finishes; `count` at/after the end. This
|
|
27
|
+
* is exactly a scrub cut position (segments `[0, result)` are done), so it maps straight to a
|
|
28
|
+
* segment-index scrub.
|
|
29
|
+
*/
|
|
30
|
+
export function segmentsCompletedAtTime(cumulativeMs, ms) {
|
|
31
|
+
let lo = 0;
|
|
32
|
+
let hi = cumulativeMs.length;
|
|
33
|
+
while (lo < hi) {
|
|
34
|
+
const mid = (lo + hi) >>> 1;
|
|
35
|
+
if (cumulativeMs[mid] <= ms)
|
|
36
|
+
lo = mid + 1;
|
|
37
|
+
else
|
|
38
|
+
hi = mid;
|
|
39
|
+
}
|
|
40
|
+
return lo;
|
|
41
|
+
}
|