@flighthq/textshaper 0.3.0-next.906.07cea63 → 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/dist/contract.d.ts +1 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +1 -0
- package/dist/contract.js.map +1 -1
- package/dist/enableTextShaperGuards.d.ts +13 -0
- package/dist/enableTextShaperGuards.d.ts.map +1 -0
- package/dist/enableTextShaperGuards.js +25 -0
- package/dist/enableTextShaperGuards.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/textShaperPool.d.ts +4 -0
- package/dist/textShaperPool.d.ts.map +1 -1
- package/dist/textShaperPool.js +25 -3
- package/dist/textShaperPool.js.map +1 -1
- package/package.json +6 -3
- package/src/enableTextShaperGuards.test.ts +62 -0
- package/src/textShaperPool.test.ts +69 -1
package/dist/contract.d.ts
CHANGED
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
package/dist/contract.js
CHANGED
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Uninstalls the guard installed by `enableTextShaperGuards`. */
|
|
2
|
+
export declare function disableTextShaperGuards(): void;
|
|
3
|
+
/**
|
|
4
|
+
* Installs the caller-facing shaped-run pool guard (opt-in, dev-only). `acquireShapedRun` and
|
|
5
|
+
* `releaseShapedRun` are paired brackets: each acquired run must be released exactly once and must not
|
|
6
|
+
* be used afterwards. Core already ignores a repeated release to preserve the pool invariant, but that
|
|
7
|
+
* silent recovery otherwise hides the caller bug that unbalanced the bracket.
|
|
8
|
+
*
|
|
9
|
+
* The guard warns once through `@flighthq/log`. Not importing this module costs production nothing: the
|
|
10
|
+
* message and logging dependency live only here, while the pool keeps a null callback by default.
|
|
11
|
+
*/
|
|
12
|
+
export declare function enableTextShaperGuards(): void;
|
|
13
|
+
//# sourceMappingURL=enableTextShaperGuards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enableTextShaperGuards.d.ts","sourceRoot":"","sources":["../src/enableTextShaperGuards.ts"],"names":[],"mappings":"AAMA,kEAAkE;AAClE,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { logOnce } from '@flighthq/log/contract';
|
|
2
|
+
import { LogLevel } from '@flighthq/types/contract';
|
|
3
|
+
import { setShapedRunReleaseGuard } from './textShaperPool';
|
|
4
|
+
/** Uninstalls the guard installed by `enableTextShaperGuards`. */
|
|
5
|
+
export function disableTextShaperGuards() {
|
|
6
|
+
setShapedRunReleaseGuard(null);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Installs the caller-facing shaped-run pool guard (opt-in, dev-only). `acquireShapedRun` and
|
|
10
|
+
* `releaseShapedRun` are paired brackets: each acquired run must be released exactly once and must not
|
|
11
|
+
* be used afterwards. Core already ignores a repeated release to preserve the pool invariant, but that
|
|
12
|
+
* silent recovery otherwise hides the caller bug that unbalanced the bracket.
|
|
13
|
+
*
|
|
14
|
+
* The guard warns once through `@flighthq/log`. Not importing this module costs production nothing: the
|
|
15
|
+
* message and logging dependency live only here, while the pool keeps a null callback by default.
|
|
16
|
+
*/
|
|
17
|
+
export function enableTextShaperGuards() {
|
|
18
|
+
setShapedRunReleaseGuard(warnOnDoubleRelease);
|
|
19
|
+
}
|
|
20
|
+
function warnOnDoubleRelease(_run) {
|
|
21
|
+
logOnce('textshaper:double-release', LogLevel.Warn, {
|
|
22
|
+
message: 'releaseShapedRun: this run is already in the pool, so it is being released twice. The repeated release was ignored to preserve the pool, but it indicates an unbalanced bracket. Every acquireShapedRun call must pair with exactly one releaseShapedRun call, and the run must not be used after release.',
|
|
23
|
+
}, 'textshaper');
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=enableTextShaperGuards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enableTextShaperGuards.js","sourceRoot":"","sources":["../src/enableTextShaperGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,kEAAkE;AAClE,MAAM,UAAU,uBAAuB;IACrC,wBAAwB,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB;IACpC,wBAAwB,CAAC,mBAAmB,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAyB;IACpD,OAAO,CACL,2BAA2B,EAC3B,QAAQ,CAAC,IAAI,EACb;QACE,OAAO,EACL,4SAA4S;KAC/S,EACD,YAAY,CACb,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { acquireShapedRun, clearShapedRun, clearTextShaperCache, createShapedRun, createTextShaperCache, disposeTextShaperCache, disposeTextShaperSignals, enableTextShaperSignals, getCaretPositionsForRun, getClusterForIndex, getCodePointForGlyph, getFontMetrics, getFontMetricsInto, getFontUnitScale, getGlyphExtents, getGlyphExtentsBatch, getGlyphExtentsInto, getGlyphIndexForCodePoint, getGlyphName, getIndexRangeForCluster, getTextShaperSignals, itemizeText, measureText, releaseShapedRun, shapeTextRun, shapeTextRunCached, shapeTextRunInto, shapeTextRuns, } from './contract';
|
|
1
|
+
export { acquireShapedRun, clearShapedRun, clearTextShaperCache, createShapedRun, createTextShaperCache, disposeTextShaperCache, disposeTextShaperSignals, disableTextShaperGuards, enableTextShaperGuards, enableTextShaperSignals, getCaretPositionsForRun, getClusterForIndex, getCodePointForGlyph, getFontMetrics, getFontMetricsInto, getFontUnitScale, getGlyphExtents, getGlyphExtentsBatch, getGlyphExtentsInto, getGlyphIndexForCodePoint, getGlyphName, getIndexRangeForCluster, getTextShaperSignals, itemizeText, measureText, releaseShapedRun, shapeTextRun, shapeTextRunCached, shapeTextRunInto, shapeTextRuns, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,EACzB,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,EACzB,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { acquireShapedRun, clearShapedRun, clearTextShaperCache, createShapedRun, createTextShaperCache, disposeTextShaperCache, disposeTextShaperSignals, enableTextShaperSignals, getCaretPositionsForRun, getClusterForIndex, getCodePointForGlyph, getFontMetrics, getFontMetricsInto, getFontUnitScale, getGlyphExtents, getGlyphExtentsBatch, getGlyphExtentsInto, getGlyphIndexForCodePoint, getGlyphName, getIndexRangeForCluster, getTextShaperSignals, itemizeText, measureText, releaseShapedRun, shapeTextRun, shapeTextRunCached, shapeTextRunInto, shapeTextRuns, } from './contract';
|
|
1
|
+
export { acquireShapedRun, clearShapedRun, clearTextShaperCache, createShapedRun, createTextShaperCache, disposeTextShaperCache, disposeTextShaperSignals, disableTextShaperGuards, enableTextShaperGuards, enableTextShaperSignals, getCaretPositionsForRun, getClusterForIndex, getCodePointForGlyph, getFontMetrics, getFontMetricsInto, getFontUnitScale, getGlyphExtents, getGlyphExtentsBatch, getGlyphExtentsInto, getGlyphIndexForCodePoint, getGlyphName, getIndexRangeForCluster, getTextShaperSignals, itemizeText, measureText, releaseShapedRun, shapeTextRun, shapeTextRunCached, shapeTextRunInto, shapeTextRuns, } from './contract';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,EACzB,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,EACzB,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
package/dist/textShaperPool.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ShapedRun } from '@flighthq/types/contract';
|
|
2
2
|
export declare function acquireShapedRun(): ShapedRun;
|
|
3
3
|
export declare function releaseShapedRun(run: ShapedRun): void;
|
|
4
|
+
/** Installs the shaped-run release guard, or clears it with `null`. The seam keeps warning text and the
|
|
5
|
+
* `@flighthq/log` dependency in the separately importable guard module. Use `enableTextShaperGuards`
|
|
6
|
+
* rather than installing a callback directly in application code. */
|
|
7
|
+
export declare function setShapedRunReleaseGuard(guard: ((run: Readonly<ShapedRun>) => void) | null): void;
|
|
4
8
|
//# sourceMappingURL=textShaperPool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textShaperPool.d.ts","sourceRoot":"","sources":["../src/textShaperPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAU1D,wBAAgB,gBAAgB,IAAI,SAAS,
|
|
1
|
+
{"version":3,"file":"textShaperPool.d.ts","sourceRoot":"","sources":["../src/textShaperPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAU1D,wBAAgB,gBAAgB,IAAI,SAAS,CAK5C;AASD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CASrD;AAED;;sEAEsE;AACtE,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAEjG"}
|
package/dist/textShaperPool.js
CHANGED
|
@@ -6,19 +6,41 @@ import { createShapedRun } from './textShaperRun';
|
|
|
6
6
|
// The returned run is in an unspecified state — always populate it before use (e.g. via
|
|
7
7
|
// `shapeTextRunInto`).
|
|
8
8
|
export function acquireShapedRun() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const pooled = _pool.pop();
|
|
10
|
+
if (pooled === undefined)
|
|
11
|
+
return createShapedRun();
|
|
12
|
+
_pooled.delete(pooled);
|
|
13
|
+
return pooled;
|
|
12
14
|
}
|
|
13
15
|
// Returns a ShapedRun to the pool. The run must not be used after release. Pairs with
|
|
14
16
|
// `acquireShapedRun`. Runs released beyond the pool capacity are silently discarded (GC-collected).
|
|
17
|
+
//
|
|
18
|
+
// Releasing a run that is already pooled is ignored rather than honoured. Without that check the run
|
|
19
|
+
// sits in the pool twice, and the next two acquires hand the same object to two callers who then shape
|
|
20
|
+
// into one buffer -- corruption that surfaces as wrong glyphs far from its cause. Ignoring keeps the
|
|
21
|
+
// pool invariant (an entry appears at most once) intact whatever the caller does.
|
|
15
22
|
export function releaseShapedRun(run) {
|
|
23
|
+
if (_pooled.has(run)) {
|
|
24
|
+
_releaseGuard?.(run);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
16
27
|
if (_pool.length < _POOL_MAX_SIZE) {
|
|
17
28
|
_pool.push(run);
|
|
29
|
+
_pooled.add(run);
|
|
18
30
|
}
|
|
19
31
|
}
|
|
32
|
+
/** Installs the shaped-run release guard, or clears it with `null`. The seam keeps warning text and the
|
|
33
|
+
* `@flighthq/log` dependency in the separately importable guard module. Use `enableTextShaperGuards`
|
|
34
|
+
* rather than installing a callback directly in application code. */
|
|
35
|
+
export function setShapedRunReleaseGuard(guard) {
|
|
36
|
+
_releaseGuard = guard;
|
|
37
|
+
}
|
|
20
38
|
// Maximum number of ShapedRuns to retain in the pool before discarding on release. Keeps
|
|
21
39
|
// memory bounded in cases where burst shaping produces many runs.
|
|
22
40
|
const _POOL_MAX_SIZE = 64;
|
|
23
41
|
const _pool = [];
|
|
42
|
+
// Membership mirror for `_pool`, so the double-release check is O(1) rather than scanning the pool on
|
|
43
|
+
// every release. Weak so a run dropped past capacity stays collectable.
|
|
44
|
+
const _pooled = new WeakSet();
|
|
45
|
+
let _releaseGuard = null;
|
|
24
46
|
//# sourceMappingURL=textShaperPool.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textShaperPool.js","sourceRoot":"","sources":["../src/textShaperPool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,mFAAmF;AACnF,oFAAoF;AACpF,0FAA0F;AAC1F,EAAE;AACF,wFAAwF;AACxF,uBAAuB;AACvB,MAAM,UAAU,gBAAgB;IAC9B,
|
|
1
|
+
{"version":3,"file":"textShaperPool.js","sourceRoot":"","sources":["../src/textShaperPool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,mFAAmF;AACnF,oFAAoF;AACpF,0FAA0F;AAC1F,EAAE;AACF,wFAAwF;AACxF,uBAAuB;AACvB,MAAM,UAAU,gBAAgB;IAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,eAAe,EAAE,CAAC;IACnD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,sFAAsF;AACtF,oGAAoG;AACpG,EAAE;AACF,qGAAqG;AACrG,uGAAuG;AACvG,qGAAqG;AACrG,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,GAAc;IAC7C,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;sEAEsE;AACtE,MAAM,UAAU,wBAAwB,CAAC,KAAkD;IACzF,aAAa,GAAG,KAAK,CAAC;AACxB,CAAC;AAED,yFAAyF;AACzF,kEAAkE;AAClE,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,KAAK,GAAgB,EAAE,CAAC;AAC9B,sGAAsG;AACtG,wEAAwE;AACxE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAa,CAAC;AACzC,IAAI,aAAa,GAAgD,IAAI,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/textshaper",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"author": "Joshua Granick and other contributors",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"repository": {
|
|
5
7
|
"type": "git",
|
|
6
8
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,8 +38,9 @@
|
|
|
36
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@flighthq/
|
|
40
|
-
"@flighthq/
|
|
41
|
+
"@flighthq/log": "0.3.0",
|
|
42
|
+
"@flighthq/signals": "0.3.0",
|
|
43
|
+
"@flighthq/types": "0.3.0"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"typescript": "^5.3.0"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
|
|
2
|
+
import type { LogEntry } from '@flighthq/types/contract';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { disableTextShaperGuards, enableTextShaperGuards } from './enableTextShaperGuards';
|
|
6
|
+
import { acquireShapedRun, releaseShapedRun } from './textShaperPool';
|
|
7
|
+
|
|
8
|
+
function captureLog(run: () => void): readonly LogEntry[] {
|
|
9
|
+
const sink = createMemoryLogSink(8);
|
|
10
|
+
addLogSink(sink.sink);
|
|
11
|
+
try {
|
|
12
|
+
run();
|
|
13
|
+
return getMemoryLogSinkEntries(sink);
|
|
14
|
+
} finally {
|
|
15
|
+
removeLogSink(sink.sink);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('disableTextShaperGuards', () => {
|
|
20
|
+
it('stops repeated releases from reporting', () => {
|
|
21
|
+
const entries = captureLog(() => {
|
|
22
|
+
enableTextShaperGuards();
|
|
23
|
+
disableTextShaperGuards();
|
|
24
|
+
const run = acquireShapedRun();
|
|
25
|
+
releaseShapedRun(run);
|
|
26
|
+
releaseShapedRun(run);
|
|
27
|
+
});
|
|
28
|
+
expect(entries).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('enableTextShaperGuards', () => {
|
|
33
|
+
it('warns when one acquired run is released twice', () => {
|
|
34
|
+
const entries = captureLog(() => {
|
|
35
|
+
enableTextShaperGuards();
|
|
36
|
+
try {
|
|
37
|
+
const run = acquireShapedRun();
|
|
38
|
+
releaseShapedRun(run);
|
|
39
|
+
releaseShapedRun(run);
|
|
40
|
+
} finally {
|
|
41
|
+
disableTextShaperGuards();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
expect(entries).toHaveLength(1);
|
|
45
|
+
const data = entries[0].data;
|
|
46
|
+
expect(typeof data === 'string' ? data : String(data.message)).toContain('released twice');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('stays silent for correctly paired acquire and release calls', () => {
|
|
50
|
+
const entries = captureLog(() => {
|
|
51
|
+
enableTextShaperGuards();
|
|
52
|
+
try {
|
|
53
|
+
const run = acquireShapedRun();
|
|
54
|
+
releaseShapedRun(run);
|
|
55
|
+
releaseShapedRun(acquireShapedRun());
|
|
56
|
+
} finally {
|
|
57
|
+
disableTextShaperGuards();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
expect(entries).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { acquireShapedRun, releaseShapedRun } from './textShaperPool';
|
|
1
|
+
import { acquireShapedRun, releaseShapedRun, setShapedRunReleaseGuard } from './textShaperPool';
|
|
2
2
|
|
|
3
3
|
describe('acquireShapedRun', () => {
|
|
4
4
|
it('returns a ShapedRun with the expected shape', () => {
|
|
@@ -33,4 +33,72 @@ describe('releaseShapedRun', () => {
|
|
|
33
33
|
const run = acquireShapedRun();
|
|
34
34
|
expect(() => releaseShapedRun(run)).not.toThrow();
|
|
35
35
|
});
|
|
36
|
+
|
|
37
|
+
// A pool entry must appear at most once. Honouring a second release put the run in twice, so the
|
|
38
|
+
// next two acquires handed one object to two callers who then shaped into the same buffer -- the
|
|
39
|
+
// failure shows up as wrong glyphs somewhere else entirely.
|
|
40
|
+
it('ignores a second release of the same run, so two acquires stay distinct', () => {
|
|
41
|
+
const run = acquireShapedRun();
|
|
42
|
+
releaseShapedRun(run);
|
|
43
|
+
releaseShapedRun(run);
|
|
44
|
+
|
|
45
|
+
const first = acquireShapedRun();
|
|
46
|
+
const second = acquireShapedRun();
|
|
47
|
+
|
|
48
|
+
expect(first).not.toBe(second);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// The membership record has to be cleared on the way out, or a run legitimately re-released after a
|
|
52
|
+
// later acquire is mistaken for a double release and silently dropped from the pool.
|
|
53
|
+
it('accepts a release again after the run has been re-acquired', () => {
|
|
54
|
+
const run = acquireShapedRun();
|
|
55
|
+
releaseShapedRun(run);
|
|
56
|
+
const reacquired = acquireShapedRun();
|
|
57
|
+
expect(reacquired).toBe(run);
|
|
58
|
+
|
|
59
|
+
releaseShapedRun(reacquired);
|
|
60
|
+
expect(acquireShapedRun()).toBe(run);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('keeps distinct runs distinct through an acquire-release cycle', () => {
|
|
64
|
+
const first = acquireShapedRun();
|
|
65
|
+
const second = acquireShapedRun();
|
|
66
|
+
releaseShapedRun(first);
|
|
67
|
+
releaseShapedRun(second);
|
|
68
|
+
|
|
69
|
+
const a = acquireShapedRun();
|
|
70
|
+
const b = acquireShapedRun();
|
|
71
|
+
|
|
72
|
+
expect(a).not.toBe(b);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('setShapedRunReleaseGuard', () => {
|
|
77
|
+
it('reports a repeated release after core detects it', () => {
|
|
78
|
+
const seen: unknown[] = [];
|
|
79
|
+
const run = acquireShapedRun();
|
|
80
|
+
setShapedRunReleaseGuard((released) => seen.push(released));
|
|
81
|
+
try {
|
|
82
|
+
releaseShapedRun(run);
|
|
83
|
+
releaseShapedRun(run);
|
|
84
|
+
expect(seen).toEqual([run]);
|
|
85
|
+
} finally {
|
|
86
|
+
setShapedRunReleaseGuard(null);
|
|
87
|
+
releaseShapedRun(acquireShapedRun());
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('stays silent for paired reuse and after being cleared', () => {
|
|
92
|
+
const seen: unknown[] = [];
|
|
93
|
+
const run = acquireShapedRun();
|
|
94
|
+
setShapedRunReleaseGuard((released) => seen.push(released));
|
|
95
|
+
releaseShapedRun(run);
|
|
96
|
+
const reacquired = acquireShapedRun();
|
|
97
|
+
releaseShapedRun(reacquired);
|
|
98
|
+
expect(seen).toEqual([]);
|
|
99
|
+
|
|
100
|
+
setShapedRunReleaseGuard(null);
|
|
101
|
+
releaseShapedRun(reacquired);
|
|
102
|
+
expect(seen).toEqual([]);
|
|
103
|
+
});
|
|
36
104
|
});
|