@fusion.dev/helix-graph 0.187.0 → 0.189.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/isometric/geometry.d.ts +9 -87
- package/dist/isometric/geometry.js +2 -143
- package/dist/three/three-driver.js +5 -6
- package/package.json +4 -4
- package/dist/three/budget.d.ts +0 -49
- package/dist/three/budget.js +0 -43
|
@@ -1,97 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where an isometric diagram puts each note, link, and group, as SVG path
|
|
3
|
-
* data, seen from a camera that can turn and tilt.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* data, seen from a camera that can turn and tilt.
|
|
4
|
+
*
|
|
5
|
+
* **The projection and the solids are `helix-core`'s**, which every spatial
|
|
6
|
+
* drawing shares, and are exported again here for the driver. What is here
|
|
7
|
+
* is the graph's own: which solid a shape is, its links, its groups' plates,
|
|
8
|
+
* and its layouts. Pure, so a spec holds the arithmetic a picture hides.
|
|
7
9
|
*
|
|
8
10
|
* **The floor is measured in cells.** A note at `x: 2, y: 1` stands two cells
|
|
9
|
-
* across and one back, and a solid's height is in the same unit.
|
|
10
|
-
* isometric camera, across runs down to the right on the page and back runs
|
|
11
|
-
* up to the right, as a plan is seen from its front left corner.
|
|
12
|
-
*/
|
|
13
|
-
/** A point on the page, in cells. */
|
|
14
|
-
export interface Point {
|
|
15
|
-
x: number;
|
|
16
|
-
y: number;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Where the diagram is seen from, in degrees: `turn`, the compass bearing
|
|
20
|
-
* the camera looks from, and `tilt`, its height above the floor, from a low
|
|
21
|
-
* angle to 90, a plan seen straight down.
|
|
22
|
-
*/
|
|
23
|
-
export interface Camera {
|
|
24
|
-
tilt: number;
|
|
25
|
-
turn: number;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* The isometric camera: from the front left corner, at the height where the
|
|
29
|
-
* floor's two axes and the vertical are drawn the same length, 35.26 degrees.
|
|
30
|
-
*/
|
|
31
|
-
export declare const ISOMETRIC: Camera;
|
|
32
|
-
/** The lowest a camera may tilt, below which the floor folds to a line. */
|
|
33
|
-
export declare const LOWEST_TILT = 15;
|
|
34
|
-
/** A camera's view of the floor. */
|
|
35
|
-
export interface Projection {
|
|
36
|
-
/** A place on the floor and a height, on the page. */
|
|
37
|
-
at(x: number, y: number, z?: number): Point;
|
|
38
|
-
/** How near the camera a place on the floor stands: the larger, the nearer. */
|
|
39
|
-
depth(x: number, y: number): number;
|
|
40
|
-
/** The place on the floor drawn at a point on the page. */
|
|
41
|
-
floorAt(point: Point): Point;
|
|
42
|
-
/** Toward the camera from the floor, a unit length: across, back, and up. */
|
|
43
|
-
toward: readonly [number, number, number];
|
|
44
|
-
/** Across the page, in floor directions: how far a step across or back goes to the right. */
|
|
45
|
-
across: readonly [number, number];
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* The view from `camera`, an orthographic one: the floor turned by `turn`
|
|
49
|
-
* and seen from `tilt` above it, with no perspective, so a cell is the same
|
|
50
|
-
* size wherever it stands.
|
|
11
|
+
* across and one back, and a solid's height is in the same unit.
|
|
51
12
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
export type Solid = 'block' | 'cylinder' | 'pyramid';
|
|
13
|
+
import { type Camera, type Drawn, depthOrder, type Face, ISOMETRIC, LOWEST_TILT, type PagePoint as Point, type Projection, projectionOf, type Solid, solid } from '@fusion.dev/helix-core';
|
|
14
|
+
export { type Camera, type Drawn, depthOrder, type Face, ISOMETRIC, LOWEST_TILT, type Point, type Projection, projectionOf, type Solid, solid, };
|
|
55
15
|
/** A note's shape, as the frame names it, and the solid that stands for it. */
|
|
56
16
|
export declare const solidOf: (shape: string | undefined) => Solid;
|
|
57
|
-
/** One face of a solid, as path data, and how far it is darkened toward black. */
|
|
58
|
-
export interface Face {
|
|
59
|
-
d: string;
|
|
60
|
-
/** From 0, the note's own color, as a top facing the light takes it. */
|
|
61
|
-
darken: number;
|
|
62
|
-
}
|
|
63
|
-
/** One note drawn as a solid: the faces the camera sees, in the order to paint them. */
|
|
64
|
-
export interface Drawn {
|
|
65
|
-
/** How high the solid reaches on the page: the top of its outline, which its name is drawn above. */
|
|
66
|
-
crown: number;
|
|
67
|
-
/** The faces the camera sees, the sides first and the top last. */
|
|
68
|
-
faces: Face[];
|
|
69
|
-
/** The outline of the whole solid, which a ring is drawn along. */
|
|
70
|
-
outline: string;
|
|
71
|
-
/** The middle of the solid's top, which its name is centered over. */
|
|
72
|
-
peak: Point;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* A note as a solid, standing on the floor at `x` and `y`, `width` cells
|
|
76
|
-
* across its footprint and `height` cells tall, as the camera sees it.
|
|
77
|
-
*
|
|
78
|
-
* **Each face is drawn only where it turns toward the camera**, so a block
|
|
79
|
-
* shows its top and the two sides that face the viewer from any turn, and a
|
|
80
|
-
* plan seen from straight above shows tops alone.
|
|
81
|
-
*/
|
|
82
|
-
export declare const solid: (projection: Projection, kind: Solid, x: number, y: number, width: number, height: number) => Drawn;
|
|
83
|
-
/**
|
|
84
|
-
* The order to paint notes in, back to front: by how near the camera each
|
|
85
|
-
* stands on the floor, and the taller first where two stand level.
|
|
86
|
-
*
|
|
87
|
-
* Exact for notes on whole cells, which do not overlap on the floor; two
|
|
88
|
-
* notes placed inside one cell can still cross.
|
|
89
|
-
*/
|
|
90
|
-
export declare const depthOrder: <T extends {
|
|
91
|
-
height: number;
|
|
92
|
-
x: number;
|
|
93
|
-
y: number;
|
|
94
|
-
}>(projection: Projection, notes: readonly T[]) => T[];
|
|
95
17
|
/** A link drawn on the floor from the middle of one note's footprint to the other's. */
|
|
96
18
|
export declare const floorLine: (projection: Projection, from: Point, to: Point) => string;
|
|
97
19
|
/**
|
|
@@ -1,147 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
const SCALE = Math.sqrt(1.5);
|
|
4
|
-
export const projectionOf = (camera) => {
|
|
5
|
-
const turn = (camera.turn * Math.PI) / 180;
|
|
6
|
-
const tilt = (Math.min(Math.max(camera.tilt, LOWEST_TILT), 90) * Math.PI) / 180;
|
|
7
|
-
const [cos, sin] = [Math.cos(turn), Math.sin(turn)];
|
|
8
|
-
const [rise, fall] = [Math.sin(tilt), Math.cos(tilt)];
|
|
9
|
-
return {
|
|
10
|
-
across: [cos, sin],
|
|
11
|
-
at: (x, y, z = 0) => ({
|
|
12
|
-
x: (x * cos + y * sin) * SCALE,
|
|
13
|
-
y: ((x * sin - y * cos) * rise - z * fall) * SCALE,
|
|
14
|
-
}),
|
|
15
|
-
depth: (x, y) => x * sin - y * cos,
|
|
16
|
-
floorAt: (point) => {
|
|
17
|
-
const u = point.x / SCALE;
|
|
18
|
-
const v = point.y / SCALE / rise;
|
|
19
|
-
return { x: u * cos + v * sin, y: u * sin - v * cos };
|
|
20
|
-
},
|
|
21
|
-
toward: [sin * fall, -cos * fall, rise],
|
|
22
|
-
};
|
|
23
|
-
};
|
|
1
|
+
import { depthOrder, ISOMETRIC, LOWEST_TILT, pathData as path, projectionOf, shortNumber as short, solid, } from '@fusion.dev/helix-core';
|
|
2
|
+
export { depthOrder, ISOMETRIC, LOWEST_TILT, projectionOf, solid, };
|
|
24
3
|
export const solidOf = (shape) => shape === 'circle' || shape === 'ring' ? 'cylinder' : shape === 'diamond' ? 'pyramid' : 'block';
|
|
25
|
-
const short = (value) => String(Math.round(value * 1000) / 1000 || 0);
|
|
26
|
-
const path = (points) => `M${points.map((point) => `${short(point.x)} ${short(point.y)}`).join('L')}Z`;
|
|
27
|
-
const hullOf = (points) => {
|
|
28
|
-
const sorted = [...points].sort((one, other) => one.x - other.x || one.y - other.y);
|
|
29
|
-
if (sorted.length < 3) {
|
|
30
|
-
return sorted;
|
|
31
|
-
}
|
|
32
|
-
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
33
|
-
const half = (list) => {
|
|
34
|
-
const kept = [];
|
|
35
|
-
for (const point of list) {
|
|
36
|
-
while (kept.length >= 2 && cross(kept.at(-2), kept.at(-1), point) <= 0) {
|
|
37
|
-
kept.pop();
|
|
38
|
-
}
|
|
39
|
-
kept.push(point);
|
|
40
|
-
}
|
|
41
|
-
kept.pop();
|
|
42
|
-
return kept;
|
|
43
|
-
};
|
|
44
|
-
return [...half(sorted), ...half([...sorted].reverse())];
|
|
45
|
-
};
|
|
46
|
-
const darkenOf = (normal, projection) => {
|
|
47
|
-
const [x, y, z] = normal;
|
|
48
|
-
const length = Math.hypot(x, y, z) || 1;
|
|
49
|
-
const level = Math.hypot(x, y) || 1;
|
|
50
|
-
const right = (x * projection.across[0] + y * projection.across[1]) / level;
|
|
51
|
-
return Math.max(0, 0.28 + 0.113 * right) * (1 - z / length);
|
|
52
|
-
};
|
|
53
|
-
const facing = (normal, projection) => normal[0] * projection.toward[0] +
|
|
54
|
-
normal[1] * projection.toward[1] +
|
|
55
|
-
normal[2] * projection.toward[2] >
|
|
56
|
-
1e-6;
|
|
57
|
-
const ARC = 12;
|
|
58
|
-
export const solid = (projection, kind, x, y, width, height) => {
|
|
59
|
-
const half = width / 2;
|
|
60
|
-
const tall = Math.max(0, height);
|
|
61
|
-
const on = (corner) => projection.at(corner[0], corner[1], corner[2]);
|
|
62
|
-
if (kind === 'cylinder') {
|
|
63
|
-
const turn = Math.atan2(projection.across[1], projection.across[0]);
|
|
64
|
-
const rim = (z, from, to) => Array.from({ length: ARC + 1 }, (_, step) => {
|
|
65
|
-
const angle = from + ((to - from) * step) / ARC;
|
|
66
|
-
return projection.at(x + Math.cos(angle) * half, y + Math.sin(angle) * half, z);
|
|
67
|
-
});
|
|
68
|
-
const near = rim(0, turn + Math.PI, turn + 2 * Math.PI);
|
|
69
|
-
const nearTop = rim(tall, turn + Math.PI, turn + 2 * Math.PI);
|
|
70
|
-
const farTop = rim(tall, turn, turn + Math.PI);
|
|
71
|
-
const top = rim(tall, 0, 2 * Math.PI).slice(0, -1);
|
|
72
|
-
const side = projection.toward[2] < 0.9999 && tall > 0;
|
|
73
|
-
const outline = hullOf([...farTop, ...nearTop, ...near]);
|
|
74
|
-
return {
|
|
75
|
-
crown: Math.min(...outline.map((point) => point.y)),
|
|
76
|
-
faces: [
|
|
77
|
-
...(side ? [{ d: path([...nearTop, ...[...near].reverse()]), darken: 0.28 }] : []),
|
|
78
|
-
{ d: path(top), darken: 0 },
|
|
79
|
-
],
|
|
80
|
-
outline: path(outline),
|
|
81
|
-
peak: projection.at(x, y, tall),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
const floor = [
|
|
85
|
-
[x - half, y - half, 0],
|
|
86
|
-
[x + half, y - half, 0],
|
|
87
|
-
[x + half, y + half, 0],
|
|
88
|
-
[x - half, y + half, 0],
|
|
89
|
-
];
|
|
90
|
-
const outward = [
|
|
91
|
-
[0, -1, 0],
|
|
92
|
-
[1, 0, 0],
|
|
93
|
-
[0, 1, 0],
|
|
94
|
-
[-1, 0, 0],
|
|
95
|
-
];
|
|
96
|
-
const faces = [];
|
|
97
|
-
let corners;
|
|
98
|
-
let peak;
|
|
99
|
-
if (kind === 'pyramid') {
|
|
100
|
-
const apex = [x, y, Math.max(tall, width)];
|
|
101
|
-
corners = [...floor, apex];
|
|
102
|
-
peak = on(apex);
|
|
103
|
-
floor.forEach((corner, at) => {
|
|
104
|
-
const next = floor[(at + 1) % 4];
|
|
105
|
-
const [ox, oy] = outward[at];
|
|
106
|
-
const normal = [ox * apex[2], oy * apex[2], half];
|
|
107
|
-
if (facing(normal, projection)) {
|
|
108
|
-
faces.push({
|
|
109
|
-
d: path([on(corner), on(next), on(apex)]),
|
|
110
|
-
darken: darkenOf(normal, projection),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
const up = (corner) => [corner[0], corner[1], tall];
|
|
117
|
-
corners = [...floor, ...floor.map(up)];
|
|
118
|
-
peak = projection.at(x, y, tall);
|
|
119
|
-
if (tall > 0) {
|
|
120
|
-
floor.forEach((corner, at) => {
|
|
121
|
-
const next = floor[(at + 1) % 4];
|
|
122
|
-
const normal = outward[at];
|
|
123
|
-
if (facing(normal, projection)) {
|
|
124
|
-
faces.push({
|
|
125
|
-
d: path([on(up(corner)), on(up(next)), on(next), on(corner)]),
|
|
126
|
-
darken: darkenOf(normal, projection),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
faces.push({ d: path(floor.map(up).map(on)), darken: 0 });
|
|
132
|
-
}
|
|
133
|
-
const outline = hullOf(corners.map(on));
|
|
134
|
-
return {
|
|
135
|
-
crown: Math.min(...outline.map((point) => point.y)),
|
|
136
|
-
faces,
|
|
137
|
-
outline: path(outline),
|
|
138
|
-
peak,
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
export const depthOrder = (projection, notes) => [...notes].sort((one, other) => {
|
|
142
|
-
const nearer = projection.depth(one.x, one.y) - projection.depth(other.x, other.y);
|
|
143
|
-
return Math.abs(nearer) > 1e-9 ? nearer : other.height - one.height;
|
|
144
|
-
});
|
|
145
4
|
export const floorLine = (projection, from, to) => {
|
|
146
5
|
const start = projection.at(from.x, from.y);
|
|
147
6
|
const end = projection.at(to.x, to.y);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { reportRuntime } from '@fusion.dev/helix-core';
|
|
1
|
+
import { pageBudget, reportRuntime } from '@fusion.dev/helix-core';
|
|
2
2
|
import { AdditiveBlending, BoxGeometry, BufferAttribute, BufferGeometry, DoubleSide, Euler, InstancedBufferAttribute, InstancedMesh, Matrix4, Mesh, NormalBlending, OctahedronGeometry, PerspectiveCamera, Points, Quaternion, Raycaster, Scene, ShaderMaterial, TOUCH, Vector2, Vector3, WebGLRenderer, } from 'three';
|
|
3
3
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
4
4
|
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
|
@@ -9,7 +9,6 @@ import { linkKey } from '../connect-graph/diff.js';
|
|
|
9
9
|
import { rgbOf } from '../connect-graph/looks.js';
|
|
10
10
|
import { ease, gesturesHeld, groupsOver, isLight, namesOver, signalsOver, } from '../overlay/overlay.js';
|
|
11
11
|
import { surfaceOf } from '../shapes/surface.js';
|
|
12
|
-
import { pageBudget } from './budget.js';
|
|
13
12
|
const SHAPES = { circle: 0, diamond: 2, ring: 0, square: 1 };
|
|
14
13
|
const REACH = 100;
|
|
15
14
|
const STEP = 250;
|
|
@@ -1099,14 +1098,14 @@ export const threeDriver = (container, options = {}) => {
|
|
|
1099
1098
|
release: () => renderer.forceContextLoss(),
|
|
1100
1099
|
restore: () => renderer.forceContextRestore(),
|
|
1101
1100
|
};
|
|
1102
|
-
pageBudget.join(holder);
|
|
1101
|
+
pageBudget().join(holder);
|
|
1103
1102
|
const nearby = typeof IntersectionObserver === 'function'
|
|
1104
1103
|
? new IntersectionObserver((entries) => {
|
|
1105
1104
|
if (entries.at(-1)?.isIntersecting ?? true) {
|
|
1106
|
-
pageBudget.near(holder);
|
|
1105
|
+
pageBudget().near(holder);
|
|
1107
1106
|
}
|
|
1108
1107
|
else {
|
|
1109
|
-
pageBudget.far(holder, performance.now());
|
|
1108
|
+
pageBudget().far(holder, performance.now());
|
|
1110
1109
|
}
|
|
1111
1110
|
}, { rootMargin: '50%' })
|
|
1112
1111
|
: undefined;
|
|
@@ -1381,7 +1380,7 @@ export const threeDriver = (container, options = {}) => {
|
|
|
1381
1380
|
stopped = true;
|
|
1382
1381
|
canvas.removeEventListener('webglcontextrestored', restored);
|
|
1383
1382
|
nearby?.disconnect();
|
|
1384
|
-
pageBudget.leave(holder);
|
|
1383
|
+
pageBudget().leave(holder);
|
|
1385
1384
|
cancelAnimationFrame(pending);
|
|
1386
1385
|
cancelAnimationFrame(hovering);
|
|
1387
1386
|
cancelAnimationFrame(resizing);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-graph",
|
|
3
3
|
"description": "Connects the Helix Graph frame to a graph renderer: Sigma.js, cosmos.gl, three.js, or a driver of your own.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.189.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
},
|
|
60
60
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@fusion.dev/helix-core": "0.
|
|
62
|
+
"@fusion.dev/helix-core": "0.189.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@cosmos.gl/graph": "^3.4.2",
|
|
66
|
-
"@fusion.dev/helix-headless": "0.
|
|
67
|
-
"@fusion.dev/helix-testing": "0.
|
|
66
|
+
"@fusion.dev/helix-headless": "0.189.0",
|
|
67
|
+
"@fusion.dev/helix-testing": "0.189.0",
|
|
68
68
|
"@types/bun": "^1.4.2",
|
|
69
69
|
"@types/three": "^0.186.0",
|
|
70
70
|
"d3-force-3d": "^3.0.6",
|
package/dist/three/budget.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How many WebGL contexts the three.js driver keeps live on one page.
|
|
3
|
-
*
|
|
4
|
-
* **A browser holds only so many, and drops the oldest without asking**:
|
|
5
|
-
* Chrome about sixteen on a desktop and fewer on a phone. A page of 3D panels
|
|
6
|
-
* past that lost a graph it could not get back, and the maps, Aura, and
|
|
7
|
-
* Strands on the same page count against the same limit. Six leaves them
|
|
8
|
-
* room.
|
|
9
|
-
*/
|
|
10
|
-
export declare const LIVE = 6;
|
|
11
|
-
/** One drawing that holds a context: it gives the context back, and takes it again. */
|
|
12
|
-
export interface Holder {
|
|
13
|
-
release(): void;
|
|
14
|
-
restore(): void;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Keeps the contexts a page's drawings hold under `cap`.
|
|
18
|
-
*
|
|
19
|
-
* **A drawing near the screen always keeps its context.** Past the cap, the
|
|
20
|
-
* drawings off screen give theirs back, the one away longest first, and each
|
|
21
|
-
* takes its context again as it comes near. So a page of any number of
|
|
22
|
-
* graphs holds as many contexts as it shows at once, and never more than
|
|
23
|
-
* `cap` beyond that.
|
|
24
|
-
*/
|
|
25
|
-
export declare const contextBudget: (cap?: number) => {
|
|
26
|
-
/** A drawing far from the screen, from `now`. */
|
|
27
|
-
far(holder: Holder, now: number): void;
|
|
28
|
-
/** How many drawings hold a context now. */
|
|
29
|
-
readonly holding: number;
|
|
30
|
-
/** A new drawing, which made a context to draw with. */
|
|
31
|
-
join(holder: Holder): void;
|
|
32
|
-
/** A drawing that ended and freed its context itself. */
|
|
33
|
-
leave(holder: Holder): void;
|
|
34
|
-
/** A drawing near the screen, which takes its context again where it gave it back. */
|
|
35
|
-
near(holder: Holder): void;
|
|
36
|
-
};
|
|
37
|
-
/** The budget every three.js driver on the page shares. */
|
|
38
|
-
export declare const pageBudget: {
|
|
39
|
-
/** A drawing far from the screen, from `now`. */
|
|
40
|
-
far(holder: Holder, now: number): void;
|
|
41
|
-
/** How many drawings hold a context now. */
|
|
42
|
-
readonly holding: number;
|
|
43
|
-
/** A new drawing, which made a context to draw with. */
|
|
44
|
-
join(holder: Holder): void;
|
|
45
|
-
/** A drawing that ended and freed its context itself. */
|
|
46
|
-
leave(holder: Holder): void;
|
|
47
|
-
/** A drawing near the screen, which takes its context again where it gave it back. */
|
|
48
|
-
near(holder: Holder): void;
|
|
49
|
-
};
|
package/dist/three/budget.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export const LIVE = 6;
|
|
2
|
-
export const contextBudget = (cap = LIVE) => {
|
|
3
|
-
const holding = new Set();
|
|
4
|
-
const away = new Map();
|
|
5
|
-
const balance = () => {
|
|
6
|
-
const longest = [...away]
|
|
7
|
-
.filter(([holder]) => holding.has(holder))
|
|
8
|
-
.sort((one, other) => one[1] - other[1]);
|
|
9
|
-
for (const [holder] of longest) {
|
|
10
|
-
if (holding.size <= cap) {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
holding.delete(holder);
|
|
14
|
-
holder.release();
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
return {
|
|
18
|
-
far(holder, now) {
|
|
19
|
-
away.set(holder, now);
|
|
20
|
-
balance();
|
|
21
|
-
},
|
|
22
|
-
get holding() {
|
|
23
|
-
return holding.size;
|
|
24
|
-
},
|
|
25
|
-
join(holder) {
|
|
26
|
-
holding.add(holder);
|
|
27
|
-
balance();
|
|
28
|
-
},
|
|
29
|
-
leave(holder) {
|
|
30
|
-
holding.delete(holder);
|
|
31
|
-
away.delete(holder);
|
|
32
|
-
},
|
|
33
|
-
near(holder) {
|
|
34
|
-
away.delete(holder);
|
|
35
|
-
if (!holding.has(holder)) {
|
|
36
|
-
holding.add(holder);
|
|
37
|
-
holder.restore();
|
|
38
|
-
}
|
|
39
|
-
balance();
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
export const pageBudget = contextBudget();
|