@graphty/graph-format 0.0.0 → 0.2.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/LICENSE +21 -0
- package/README.md +216 -43
- package/dist/graph-format.d.ts +1 -0
- package/dist/graph-format.d.ts.map +1 -0
- package/dist/graph-format.js +15798 -0
- package/dist/graph-format.js.map +1 -0
- package/dist/src/builder/arena.d.ts +85 -0
- package/dist/src/builder/arena.d.ts.map +1 -0
- package/dist/src/builder/arena.js +154 -0
- package/dist/src/builder/arena.js.map +1 -0
- package/dist/src/builder/compact.d.ts +515 -0
- package/dist/src/builder/compact.d.ts.map +1 -0
- package/dist/src/builder/compact.js +1553 -0
- package/dist/src/builder/compact.js.map +1 -0
- package/dist/src/builder/counting-sort.d.ts +96 -0
- package/dist/src/builder/counting-sort.d.ts.map +1 -0
- package/dist/src/builder/counting-sort.js +430 -0
- package/dist/src/builder/counting-sort.js.map +1 -0
- package/dist/src/builder/freeze.d.ts +72 -0
- package/dist/src/builder/freeze.d.ts.map +1 -0
- package/dist/src/builder/freeze.js +548 -0
- package/dist/src/builder/freeze.js.map +1 -0
- package/dist/src/builder/graph-builder.d.ts +602 -0
- package/dist/src/builder/graph-builder.d.ts.map +1 -0
- package/dist/src/builder/graph-builder.js +1940 -0
- package/dist/src/builder/graph-builder.js.map +1 -0
- package/dist/src/builder/options.d.ts +16 -0
- package/dist/src/builder/options.d.ts.map +1 -0
- package/dist/src/builder/options.js +21 -0
- package/dist/src/builder/options.js.map +1 -0
- package/dist/src/columns/bitmap.d.ts +96 -0
- package/dist/src/columns/bitmap.d.ts.map +1 -0
- package/dist/src/columns/bitmap.js +184 -0
- package/dist/src/columns/bitmap.js.map +1 -0
- package/dist/src/columns/column.d.ts +220 -0
- package/dist/src/columns/column.d.ts.map +1 -0
- package/dist/src/columns/column.js +2109 -0
- package/dist/src/columns/column.js.map +1 -0
- package/dist/src/columns/dictionary.d.ts +60 -0
- package/dist/src/columns/dictionary.d.ts.map +1 -0
- package/dist/src/columns/dictionary.js +102 -0
- package/dist/src/columns/dictionary.js.map +1 -0
- package/dist/src/columns/growable.d.ts +198 -0
- package/dist/src/columns/growable.d.ts.map +1 -0
- package/dist/src/columns/growable.js +296 -0
- package/dist/src/columns/growable.js.map +1 -0
- package/dist/src/columns/infer.d.ts +121 -0
- package/dist/src/columns/infer.d.ts.map +1 -0
- package/dist/src/columns/infer.js +268 -0
- package/dist/src/columns/infer.js.map +1 -0
- package/dist/src/columns/remap.d.ts +143 -0
- package/dist/src/columns/remap.d.ts.map +1 -0
- package/dist/src/columns/remap.js +588 -0
- package/dist/src/columns/remap.js.map +1 -0
- package/dist/src/columns/table.d.ts +184 -0
- package/dist/src/columns/table.d.ts.map +1 -0
- package/dist/src/columns/table.js +485 -0
- package/dist/src/columns/table.js.map +1 -0
- package/dist/src/constants.d.ts +34 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +79 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/errors.d.ts +76 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +32 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/ids/edge-id-index.d.ts +77 -0
- package/dist/src/ids/edge-id-index.d.ts.map +1 -0
- package/dist/src/ids/edge-id-index.js +155 -0
- package/dist/src/ids/edge-id-index.js.map +1 -0
- package/dist/src/ids/node-id-map.d.ts +280 -0
- package/dist/src/ids/node-id-map.d.ts.map +1 -0
- package/dist/src/ids/node-id-map.js +1139 -0
- package/dist/src/ids/node-id-map.js.map +1 -0
- package/dist/src/ids/string-store.d.ts +219 -0
- package/dist/src/ids/string-store.d.ts.map +1 -0
- package/dist/src/ids/string-store.js +526 -0
- package/dist/src/ids/string-store.js.map +1 -0
- package/dist/src/index.d.ts +30 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +29 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/populate/from-csr.d.ts +61 -0
- package/dist/src/populate/from-csr.d.ts.map +1 -0
- package/dist/src/populate/from-csr.js +706 -0
- package/dist/src/populate/from-csr.js.map +1 -0
- package/dist/src/populate/from-edge-arrays.d.ts +51 -0
- package/dist/src/populate/from-edge-arrays.d.ts.map +1 -0
- package/dist/src/populate/from-edge-arrays.js +217 -0
- package/dist/src/populate/from-edge-arrays.js.map +1 -0
- package/dist/src/populate/from-records.d.ts +51 -0
- package/dist/src/populate/from-records.d.ts.map +1 -0
- package/dist/src/populate/from-records.js +447 -0
- package/dist/src/populate/from-records.js.map +1 -0
- package/dist/src/snapshot/derived.d.ts +211 -0
- package/dist/src/snapshot/derived.d.ts.map +1 -0
- package/dist/src/snapshot/derived.js +1254 -0
- package/dist/src/snapshot/derived.js.map +1 -0
- package/dist/src/snapshot/graph-meta.d.ts +23 -0
- package/dist/src/snapshot/graph-meta.d.ts.map +1 -0
- package/dist/src/snapshot/graph-meta.js +159 -0
- package/dist/src/snapshot/graph-meta.js.map +1 -0
- package/dist/src/snapshot/graph-snapshot.d.ts +509 -0
- package/dist/src/snapshot/graph-snapshot.d.ts.map +1 -0
- package/dist/src/snapshot/graph-snapshot.js +1247 -0
- package/dist/src/snapshot/graph-snapshot.js.map +1 -0
- package/dist/src/snapshot/hash.d.ts +90 -0
- package/dist/src/snapshot/hash.d.ts.map +1 -0
- package/dist/src/snapshot/hash.js +228 -0
- package/dist/src/snapshot/hash.js.map +1 -0
- package/dist/src/snapshot/queries.d.ts +74 -0
- package/dist/src/snapshot/queries.d.ts.map +1 -0
- package/dist/src/snapshot/queries.js +131 -0
- package/dist/src/snapshot/queries.js.map +1 -0
- package/dist/src/snapshot/validate.d.ts +192 -0
- package/dist/src/snapshot/validate.d.ts.map +1 -0
- package/dist/src/snapshot/validate.js +990 -0
- package/dist/src/snapshot/validate.js.map +1 -0
- package/dist/src/snapshot/views.d.ts +274 -0
- package/dist/src/snapshot/views.d.ts.map +1 -0
- package/dist/src/snapshot/views.js +856 -0
- package/dist/src/snapshot/views.js.map +1 -0
- package/dist/src/types/builder.d.ts +456 -0
- package/dist/src/types/builder.d.ts.map +1 -0
- package/dist/src/types/builder.js +11 -0
- package/dist/src/types/builder.js.map +1 -0
- package/dist/src/types/columns.d.ts +615 -0
- package/dist/src/types/columns.d.ts.map +1 -0
- package/dist/src/types/columns.js +14 -0
- package/dist/src/types/columns.js.map +1 -0
- package/dist/src/types/index.d.ts +14 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +11 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/internal.d.ts +172 -0
- package/dist/src/types/internal.d.ts.map +1 -0
- package/dist/src/types/internal.js +13 -0
- package/dist/src/types/internal.js.map +1 -0
- package/dist/src/types/snapshot.d.ts +801 -0
- package/dist/src/types/snapshot.d.ts.map +1 -0
- package/dist/src/types/snapshot.js +14 -0
- package/dist/src/types/snapshot.js.map +1 -0
- package/dist/src/types/wire.d.ts +196 -0
- package/dist/src/types/wire.d.ts.map +1 -0
- package/dist/src/types/wire.js +8 -0
- package/dist/src/types/wire.js.map +1 -0
- package/dist/src/util/mask.d.ts +55 -0
- package/dist/src/util/mask.d.ts.map +1 -0
- package/dist/src/util/mask.js +71 -0
- package/dist/src/util/mask.js.map +1 -0
- package/dist/src/util/options.d.ts +22 -0
- package/dist/src/util/options.d.ts.map +1 -0
- package/dist/src/util/options.js +49 -0
- package/dist/src/util/options.js.map +1 -0
- package/dist/src/util/shared-buffers.d.ts +42 -0
- package/dist/src/util/shared-buffers.d.ts.map +1 -0
- package/dist/src/util/shared-buffers.js +52 -0
- package/dist/src/util/shared-buffers.js.map +1 -0
- package/dist/src/util/typed-array.d.ts +205 -0
- package/dist/src/util/typed-array.d.ts.map +1 -0
- package/dist/src/util/typed-array.js +289 -0
- package/dist/src/util/typed-array.js.map +1 -0
- package/dist/src/wire/bytes.d.ts +69 -0
- package/dist/src/wire/bytes.d.ts.map +1 -0
- package/dist/src/wire/bytes.js +361 -0
- package/dist/src/wire/bytes.js.map +1 -0
- package/dist/src/wire/carried-views.d.ts +111 -0
- package/dist/src/wire/carried-views.d.ts.map +1 -0
- package/dist/src/wire/carried-views.js +286 -0
- package/dist/src/wire/carried-views.js.map +1 -0
- package/dist/src/wire/from-wire.d.ts +144 -0
- package/dist/src/wire/from-wire.d.ts.map +1 -0
- package/dist/src/wire/from-wire.js +1425 -0
- package/dist/src/wire/from-wire.js.map +1 -0
- package/dist/src/wire/to-wire.d.ts +171 -0
- package/dist/src/wire/to-wire.d.ts.map +1 -0
- package/dist/src/wire/to-wire.js +652 -0
- package/dist/src/wire/to-wire.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +72 -7
- package/src/builder/arena.ts +192 -0
- package/src/builder/compact.ts +1811 -0
- package/src/builder/counting-sort.ts +506 -0
- package/src/builder/freeze.ts +689 -0
- package/src/builder/graph-builder.ts +2264 -0
- package/src/builder/options.ts +29 -0
- package/src/columns/bitmap.ts +196 -0
- package/src/columns/column.ts +2574 -0
- package/src/columns/dictionary.ts +114 -0
- package/src/columns/growable.ts +350 -0
- package/src/columns/infer.ts +285 -0
- package/src/columns/remap.ts +663 -0
- package/src/columns/table.ts +568 -0
- package/src/constants.ts +89 -0
- package/src/errors.ts +115 -0
- package/src/ids/edge-id-index.ts +179 -0
- package/src/ids/node-id-map.ts +1340 -0
- package/src/ids/string-store.ts +589 -0
- package/src/index.ts +139 -0
- package/src/lib-resizable-array-buffer.d.ts +34 -0
- package/src/populate/from-csr.ts +802 -0
- package/src/populate/from-edge-arrays.ts +257 -0
- package/src/populate/from-records.ts +536 -0
- package/src/snapshot/derived.ts +1486 -0
- package/src/snapshot/graph-meta.ts +167 -0
- package/src/snapshot/graph-snapshot.ts +1503 -0
- package/src/snapshot/hash.ts +241 -0
- package/src/snapshot/queries.ts +136 -0
- package/src/snapshot/validate.ts +1108 -0
- package/src/snapshot/views.ts +943 -0
- package/src/types/builder.ts +480 -0
- package/src/types/columns.ts +727 -0
- package/src/types/index.ts +14 -0
- package/src/types/internal.ts +195 -0
- package/src/types/snapshot.ts +886 -0
- package/src/types/wire.ts +207 -0
- package/src/util/mask.ts +89 -0
- package/src/util/options.ts +57 -0
- package/src/util/shared-buffers.ts +56 -0
- package/src/util/typed-array.ts +371 -0
- package/src/wire/bytes.ts +440 -0
- package/src/wire/carried-views.ts +327 -0
- package/src/wire/from-wire.ts +1690 -0
- package/src/wire/to-wire.ts +755 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire encoder (design sections 4.5, 5.9, 9.1 and 12.2): `toWire()` turns a snapshot into a
|
|
3
|
+
* JSON-serialisable manifest plus a list of ArrayBuffers, `transferables()` lists the exclusively
|
|
4
|
+
* owned buffers a worker hand-off may transfer, and the segment collector shared with the GSNP
|
|
5
|
+
* container writer of bytes.ts lays every typed array out as a WireBufferRef.
|
|
6
|
+
*
|
|
7
|
+
* Two layouts exist. In REFERENCE mode (toWire) every distinct backing ArrayBuffer becomes one entry
|
|
8
|
+
* of `buffers` and a reference carries the array's own byteOffset, so the core stays views over the
|
|
9
|
+
* arena (one buffer) and nothing is copied unless `transfer: true` meets a shared buffer. In
|
|
10
|
+
* CONTAINER mode (toBytes / toByteChunks) every array is placed at a 256-byte-aligned offset of one
|
|
11
|
+
* buffer region in manifest order, core arrays first so the region's prefix is the arena.
|
|
12
|
+
*
|
|
13
|
+
* toWire allocates nothing for typed data except the first materialisation of a lazily kept
|
|
14
|
+
* representation: the Utf8 store of string ids and string columns and the f64 / tag arrays of a
|
|
15
|
+
* numeric or mixed id map are cached by their owners, the encoded dictionaries and the JSON text of
|
|
16
|
+
* json columns are cached here. Non-finite numbers and -0 inside metadata are carried as the tagged
|
|
17
|
+
* strings of design section 5.9 so the manifest survives JSON.stringify unchanged.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { CORE_ORDER } from "../builder/arena.js";
|
|
21
|
+
import { isPlainObject } from "../columns/column.js";
|
|
22
|
+
import { ALIGNMENT, FORMAT_VERSION, IS_LITTLE_ENDIAN, WIRE_FORMAT, WIRE_MAJOR, WIRE_MINOR } from "../constants.js";
|
|
23
|
+
import { GraphFormatError } from "../errors.js";
|
|
24
|
+
import { NodeIdMap, nodeIdMapToTyped } from "../ids/node-id-map.js";
|
|
25
|
+
import { type EncodedUtf8, encodeUtf8Rows } from "../ids/string-store.js";
|
|
26
|
+
import { type GraphSnapshot, peekView } from "../snapshot/graph-snapshot.js";
|
|
27
|
+
import { viewArrays } from "../snapshot/views.js";
|
|
28
|
+
import {
|
|
29
|
+
type AttributeTable,
|
|
30
|
+
type Column,
|
|
31
|
+
type ColumnMeta,
|
|
32
|
+
type CoreArrayName,
|
|
33
|
+
type GraphMeta,
|
|
34
|
+
type JsonColumn,
|
|
35
|
+
type ToWireOptions,
|
|
36
|
+
type TypedArrayData,
|
|
37
|
+
type ViewName,
|
|
38
|
+
type WireArena,
|
|
39
|
+
type WireBufferRef,
|
|
40
|
+
type WireColumn,
|
|
41
|
+
type WireDtype,
|
|
42
|
+
type WireIdMap,
|
|
43
|
+
type WireManifest,
|
|
44
|
+
type WireSnapshot,
|
|
45
|
+
type WireUtf8,
|
|
46
|
+
} from "../types/index.js";
|
|
47
|
+
import { isShared, noteShared } from "../util/shared-buffers.js";
|
|
48
|
+
import { alignUp } from "../util/typed-array.js";
|
|
49
|
+
|
|
50
|
+
// ============================================================ constants
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The `producer` string written into every manifest (design section 9.1): the package name and its
|
|
54
|
+
* npm version. Golden fixtures mask it when compared. Bumped together with package.json.
|
|
55
|
+
*/
|
|
56
|
+
export const WIRE_PRODUCER = "@graphty/graph-format@0.1.0";
|
|
57
|
+
|
|
58
|
+
/** The views the wire carries when named by includeViews; the scalar views are never carried. */
|
|
59
|
+
const SCALAR_VIEWS: ReadonlySet<ViewName> = new Set<ViewName>(["totalWeight", "symmetric"]);
|
|
60
|
+
|
|
61
|
+
/** The core arrays in arena order (design section 10.3). */
|
|
62
|
+
|
|
63
|
+
const encoder = new TextEncoder();
|
|
64
|
+
|
|
65
|
+
// ============================================================ host endianness
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Refuse to emit wire data on a big-endian host (design section 9.2): every supported platform is
|
|
69
|
+
* little-endian and there is no byte-swapping reader, so a big-endian host must never produce a file
|
|
70
|
+
* other hosts reject. The parameter exists so the refusal path is testable on a little-endian host.
|
|
71
|
+
* @param littleEndian - whether the host is little-endian; defaults to the detected value
|
|
72
|
+
*/
|
|
73
|
+
export function assertLittleEndianHost(littleEndian: boolean = IS_LITTLE_ENDIAN): void {
|
|
74
|
+
if (!littleEndian) {
|
|
75
|
+
throw new GraphFormatError("E_UNSUPPORTED", "the wire format cannot be written on a big-endian host", {
|
|
76
|
+
reason: "big-endian host",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============================================================ shared-buffer bookkeeping (9.1)
|
|
82
|
+
|
|
83
|
+
// ============================================================ JSON tagging (5.9)
|
|
84
|
+
|
|
85
|
+
/** The tag key of a non-finite or negative-zero number on the wire. */
|
|
86
|
+
const NUM_TAG = "$num";
|
|
87
|
+
const ESC_TAG = "$esc";
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Encode a JSON value for the manifest (design section 5.9): `Infinity`, `-Infinity`, `NaN` and `-0`
|
|
91
|
+
* become `{ "$num": "..." }`, arrays and plain objects are copied recursively, everything else is
|
|
92
|
+
* returned as is. A user object whose ONLY key is `$num` or `$esc` would collide with the tags, so
|
|
93
|
+
* it is wrapped as `{ "$esc": { ...the object... } }` and unwrapped by the reader.
|
|
94
|
+
* The input has already passed assertJsonValue (design section 5.9), so no other shape can occur.
|
|
95
|
+
* @param value - the JSON value
|
|
96
|
+
* @returns the tagged copy
|
|
97
|
+
*/
|
|
98
|
+
export function encodeJsonValue(value: unknown): unknown {
|
|
99
|
+
if (typeof value === "number") {
|
|
100
|
+
if (Number.isNaN(value)) {
|
|
101
|
+
return { [NUM_TAG]: "NaN" };
|
|
102
|
+
}
|
|
103
|
+
if (value === Infinity) {
|
|
104
|
+
return { [NUM_TAG]: "Infinity" };
|
|
105
|
+
}
|
|
106
|
+
if (value === -Infinity) {
|
|
107
|
+
return { [NUM_TAG]: "-Infinity" };
|
|
108
|
+
}
|
|
109
|
+
if (Object.is(value, -0)) {
|
|
110
|
+
return { [NUM_TAG]: "-0" };
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
return value.map((item: unknown) => encodeJsonValue(item));
|
|
116
|
+
}
|
|
117
|
+
if (isPlainObject(value)) {
|
|
118
|
+
const keys = Object.keys(value);
|
|
119
|
+
const out: Record<string, unknown> = {};
|
|
120
|
+
for (const key of keys) {
|
|
121
|
+
defineJsonKey(out, key, encodeJsonValue(value[key]));
|
|
122
|
+
}
|
|
123
|
+
if (keys.length === 1 && (keys[0] === NUM_TAG || keys[0] === ESC_TAG)) {
|
|
124
|
+
return { [ESC_TAG]: out };
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Add an own enumerable property to a plain object without going through a setter, so a key such as
|
|
133
|
+
* "__proto__" stays a data property (the manifest reader rejects it anyway).
|
|
134
|
+
* @param target - the object
|
|
135
|
+
* @param key - the property name
|
|
136
|
+
* @param value - the value
|
|
137
|
+
*/
|
|
138
|
+
export function defineJsonKey(target: Record<string, unknown>, key: string, value: unknown): void {
|
|
139
|
+
Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ============================================================ the segment collector
|
|
143
|
+
|
|
144
|
+
/** How references are laid out. */
|
|
145
|
+
type WireMode = "reference" | "container";
|
|
146
|
+
|
|
147
|
+
/** One typed array placed in the container region: the source view and where it goes. */
|
|
148
|
+
export interface ContainerSegment {
|
|
149
|
+
/** The source array. */
|
|
150
|
+
readonly view: TypedArrayData;
|
|
151
|
+
/** Its reference (buffer 0, region-relative byteOffset). */
|
|
152
|
+
readonly ref: WireBufferRef;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Collects typed arrays into WireBufferRefs (design sections 9.1 and 9.2). In reference mode
|
|
157
|
+
* `buffers` is the list of distinct backing buffers (a shared buffer copied under `transfer`, its
|
|
158
|
+
* index recorded in `copied`); in container mode `segments` is the placement of every distinct array
|
|
159
|
+
* at a 256-aligned offset and `regionLength` the padded length of the region.
|
|
160
|
+
*/
|
|
161
|
+
export class WireLayout {
|
|
162
|
+
/** The layout mode. */
|
|
163
|
+
readonly mode: WireMode;
|
|
164
|
+
/** Reference mode: whether shared buffers are copied so the rest can be transferred. */
|
|
165
|
+
readonly transfer: boolean;
|
|
166
|
+
/** Reference mode: the distinct buffers, or their copies. */
|
|
167
|
+
readonly buffers: ArrayBuffer[] = [];
|
|
168
|
+
/** Reference mode: the indices of `buffers` holding copies of shared buffers. */
|
|
169
|
+
readonly copied: number[] = [];
|
|
170
|
+
/** Container mode: every placed array in region order. */
|
|
171
|
+
readonly segments: ContainerSegment[] = [];
|
|
172
|
+
|
|
173
|
+
private readonly bufferIndex = new Map<ArrayBuffer, number>();
|
|
174
|
+
private readonly placed = new Map<string, WireBufferRef>();
|
|
175
|
+
private cursor = 0;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Create a collector.
|
|
179
|
+
* @param mode - reference (toWire) or container (toBytes)
|
|
180
|
+
* @param transfer - reference mode only: copy shared buffers so the others can be transferred
|
|
181
|
+
*/
|
|
182
|
+
constructor(mode: WireMode, transfer = false) {
|
|
183
|
+
this.mode = mode;
|
|
184
|
+
this.transfer = transfer;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Container mode: the padded byte length of the region (every segment padded to 256 bytes).
|
|
189
|
+
* @returns the region length
|
|
190
|
+
*/
|
|
191
|
+
get regionLength(): number {
|
|
192
|
+
return alignUp(this.cursor, ALIGNMENT);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Reference mode: the index a buffer was assigned, or null when no array over it was placed.
|
|
197
|
+
* @param buffer - the buffer
|
|
198
|
+
* @returns the index into `buffers`, or null
|
|
199
|
+
*/
|
|
200
|
+
indexOf(buffer: ArrayBuffer): number | null {
|
|
201
|
+
return this.bufferIndex.get(buffer) ?? null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Place a typed array and return its reference. The same byte range is placed once. When the
|
|
206
|
+
* holder of the array (its column, table or id map) is shared, the buffer is recorded as shared
|
|
207
|
+
* so `finalise()` copies it under `transfer` (design section 9.1).
|
|
208
|
+
* @param view - the array
|
|
209
|
+
* @param dtype - its wire dtype ("utf8" for byte stores, "u8" for u8 columns and tags)
|
|
210
|
+
* @param sharedHolder - whether the object holding the array is held by more than one snapshot or table
|
|
211
|
+
* @returns the reference
|
|
212
|
+
*/
|
|
213
|
+
ref(view: TypedArrayData, dtype: WireDtype, sharedHolder = false): WireBufferRef {
|
|
214
|
+
if (sharedHolder) {
|
|
215
|
+
noteShared(view.buffer);
|
|
216
|
+
}
|
|
217
|
+
if (this.mode === "reference") {
|
|
218
|
+
return {
|
|
219
|
+
buffer: this.bufferIndexOf(view.buffer),
|
|
220
|
+
byteOffset: view.byteOffset,
|
|
221
|
+
byteLength: view.byteLength,
|
|
222
|
+
dtype,
|
|
223
|
+
length: view.length,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const key = `${this.bufferIndexOf(view.buffer)}:${view.byteOffset}:${view.byteLength}:${dtype}`;
|
|
227
|
+
const existing = this.placed.get(key);
|
|
228
|
+
if (existing !== undefined) {
|
|
229
|
+
return existing;
|
|
230
|
+
}
|
|
231
|
+
const byteOffset = alignUp(this.cursor, ALIGNMENT);
|
|
232
|
+
this.cursor = byteOffset + view.byteLength;
|
|
233
|
+
const ref: WireBufferRef = { buffer: 0, byteOffset, byteLength: view.byteLength, dtype, length: view.length };
|
|
234
|
+
this.placed.set(key, ref);
|
|
235
|
+
this.segments.push({ view, ref });
|
|
236
|
+
return ref;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Reference mode with `transfer`: replace every buffer recorded as shared by a copy and list its
|
|
241
|
+
* index in `copied` (design section 9.1). Called once after every array was placed, so the
|
|
242
|
+
* decision does not depend on the order in which shared holders were met.
|
|
243
|
+
*/
|
|
244
|
+
finalise(): void {
|
|
245
|
+
if (this.mode !== "reference" || !this.transfer) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
for (let i = 0; i < this.buffers.length; i++) {
|
|
249
|
+
const buffer = this.buffers[i];
|
|
250
|
+
if (isShared(buffer)) {
|
|
251
|
+
this.buffers[i] = buffer.slice(0);
|
|
252
|
+
this.copied.push(i);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The index of a backing buffer, assigning the next one on first sight.
|
|
259
|
+
* @param buffer - the buffer
|
|
260
|
+
* @returns the index
|
|
261
|
+
*/
|
|
262
|
+
private bufferIndexOf(buffer: ArrayBuffer): number {
|
|
263
|
+
const known = this.bufferIndex.get(buffer);
|
|
264
|
+
if (known !== undefined) {
|
|
265
|
+
return known;
|
|
266
|
+
}
|
|
267
|
+
const index = this.buffers.length;
|
|
268
|
+
this.bufferIndex.set(buffer, index);
|
|
269
|
+
this.buffers.push(buffer);
|
|
270
|
+
return index;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ============================================================ dtype helpers
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The wire dtype of a typed array by its class.
|
|
278
|
+
* @param view - the array
|
|
279
|
+
* @returns the dtype ("u8" for a Uint8Array; byte stores pass "utf8" explicitly)
|
|
280
|
+
*/
|
|
281
|
+
function wireDtypeOf(view: TypedArrayData): WireDtype {
|
|
282
|
+
if (view instanceof Uint32Array) {
|
|
283
|
+
return "u32";
|
|
284
|
+
}
|
|
285
|
+
if (view instanceof Int32Array) {
|
|
286
|
+
return "i32";
|
|
287
|
+
}
|
|
288
|
+
if (view instanceof Float32Array) {
|
|
289
|
+
return "f32";
|
|
290
|
+
}
|
|
291
|
+
if (view instanceof Float64Array) {
|
|
292
|
+
return "f64";
|
|
293
|
+
}
|
|
294
|
+
return "u8";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ============================================================ caches of lazily kept representations
|
|
298
|
+
|
|
299
|
+
/** Encoded dictionaries, keyed by the dictionary array (shared between a static column and its temporal table). */
|
|
300
|
+
const DICTIONARY_CACHE = new WeakMap<readonly string[], EncodedUtf8>();
|
|
301
|
+
|
|
302
|
+
/** JSON text of json columns, keyed by the column, with the column version it was built from. */
|
|
303
|
+
const JSON_TEXT_CACHE = new WeakMap<Column, { readonly version: number; readonly encoded: EncodedUtf8 }>();
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Whether a cached Utf8 store is still usable: the same row count and not detached by a transfer.
|
|
307
|
+
* @param encoded - the cached store
|
|
308
|
+
* @param rows - the expected row count
|
|
309
|
+
* @returns true when the cache can be reused
|
|
310
|
+
*/
|
|
311
|
+
function cacheUsable(encoded: EncodedUtf8, rows: number): boolean {
|
|
312
|
+
return encoded.offsets.length === rows + 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The encoded form of a dictionary, built once per dictionary array.
|
|
317
|
+
* @param dictionary - the strings in code order
|
|
318
|
+
* @returns the offsets and bytes
|
|
319
|
+
*/
|
|
320
|
+
function encodedDictionary(dictionary: readonly string[]): EncodedUtf8 {
|
|
321
|
+
const cached = DICTIONARY_CACHE.get(dictionary);
|
|
322
|
+
if (cached !== undefined && cacheUsable(cached, dictionary.length)) {
|
|
323
|
+
return cached;
|
|
324
|
+
}
|
|
325
|
+
const encoded = encodeUtf8Rows(dictionary, dictionary.length);
|
|
326
|
+
DICTIONARY_CACHE.set(dictionary, encoded);
|
|
327
|
+
return encoded;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* The JSON text of every row of a json column as a Utf8 store (an unset row is empty text), built
|
|
332
|
+
* once per column version.
|
|
333
|
+
* @param column - the json column
|
|
334
|
+
* @returns the offsets and bytes
|
|
335
|
+
*/
|
|
336
|
+
function encodedJsonText(column: JsonColumn): EncodedUtf8 {
|
|
337
|
+
const cached = JSON_TEXT_CACHE.get(column);
|
|
338
|
+
if (cached !== undefined && cached.version === column.version && cacheUsable(cached.encoded, column.length)) {
|
|
339
|
+
return cached.encoded;
|
|
340
|
+
}
|
|
341
|
+
const texts = new Array<string>(column.length);
|
|
342
|
+
for (let row = 0; row < column.length; row++) {
|
|
343
|
+
const value = column.values[row];
|
|
344
|
+
texts[row] = value === undefined || !column.isSet(row) ? "" : JSON.stringify(encodeJsonValue(value));
|
|
345
|
+
}
|
|
346
|
+
const encoded = encodeUtf8Rows(texts, texts.length);
|
|
347
|
+
JSON_TEXT_CACHE.set(column, { version: column.version, encoded });
|
|
348
|
+
return encoded;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ============================================================ column encoding
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* A Utf8 store as two references.
|
|
355
|
+
* @param layout - the collector
|
|
356
|
+
* @param encoded - the offsets and bytes
|
|
357
|
+
* @param shared - whether the holder of the store is shared
|
|
358
|
+
* @returns the wire store
|
|
359
|
+
*/
|
|
360
|
+
function utf8Ref(layout: WireLayout, encoded: EncodedUtf8, shared: boolean): WireUtf8 {
|
|
361
|
+
return { offsets: layout.ref(encoded.offsets, "u32", shared), utf8: layout.ref(encoded.utf8, "utf8", shared) };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The manifest copy of a column's metadata: the JSON fields tagged per design section 5.9, the rest
|
|
366
|
+
* copied as is. The tagged fields no longer match ColumnMeta's static types when a value was
|
|
367
|
+
* non-finite; the reader decodes them before resolving the metadata again.
|
|
368
|
+
* @param meta - the resolved metadata
|
|
369
|
+
* @returns the manifest form
|
|
370
|
+
*/
|
|
371
|
+
function encodeColumnMeta(meta: ColumnMeta): ColumnMeta {
|
|
372
|
+
return {
|
|
373
|
+
name: meta.name,
|
|
374
|
+
domain: meta.domain,
|
|
375
|
+
dtype: meta.dtype,
|
|
376
|
+
components: meta.components,
|
|
377
|
+
itemDtype: meta.itemDtype,
|
|
378
|
+
itemComponents: meta.itemComponents,
|
|
379
|
+
nullable: meta.nullable,
|
|
380
|
+
mutable: meta.mutable,
|
|
381
|
+
role: meta.role,
|
|
382
|
+
refersTo: meta.refersTo,
|
|
383
|
+
unique: meta.unique,
|
|
384
|
+
default: encodeJsonValue(meta.default),
|
|
385
|
+
fill: encodeJsonValue(meta.fill) as number | string | boolean,
|
|
386
|
+
options: meta.options === null ? null : (encodeJsonValue(meta.options) as readonly unknown[]),
|
|
387
|
+
origin: meta.origin === null ? null : { ...meta.origin },
|
|
388
|
+
dynamic: meta.dynamic,
|
|
389
|
+
extra: encodeJsonValue(meta.extra) as Readonly<Record<string, unknown>>,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Encode one column (design section 9.1): the buffers its dtype uses, null for the rest. A column
|
|
395
|
+
* held by more than one table (or belonging to a table held by more than one snapshot) has every
|
|
396
|
+
* buffer recorded as shared, including the ones it materialises lazily while being encoded.
|
|
397
|
+
* @param layout - the collector
|
|
398
|
+
* @param column - the column
|
|
399
|
+
* @param tableShared - whether the table holding the column is shared
|
|
400
|
+
* @returns the wire column
|
|
401
|
+
*/
|
|
402
|
+
function encodeColumn(layout: WireLayout, column: Column, tableShared: boolean): WireColumn {
|
|
403
|
+
const shared = tableShared || isShared(column);
|
|
404
|
+
const base = {
|
|
405
|
+
meta: encodeColumnMeta(column.meta),
|
|
406
|
+
data: null as WireBufferRef | null,
|
|
407
|
+
validity: column.validity === null ? null : layout.ref(column.validity, "u32", shared),
|
|
408
|
+
nullCount: column.nullCount,
|
|
409
|
+
dictionary: null as WireUtf8 | null,
|
|
410
|
+
strings: null as WireUtf8 | null,
|
|
411
|
+
offsets: null as WireBufferRef | null,
|
|
412
|
+
child: null as WireColumn | null,
|
|
413
|
+
jsonText: null as WireUtf8 | null,
|
|
414
|
+
};
|
|
415
|
+
const { dtype } = column;
|
|
416
|
+
switch (dtype) {
|
|
417
|
+
case "f32":
|
|
418
|
+
case "f64":
|
|
419
|
+
case "i32":
|
|
420
|
+
case "u32":
|
|
421
|
+
case "u8":
|
|
422
|
+
base.data = layout.ref(column.data, dtype, shared);
|
|
423
|
+
break;
|
|
424
|
+
case "bool":
|
|
425
|
+
base.data = layout.ref(column.data, "u32", shared);
|
|
426
|
+
break;
|
|
427
|
+
case "dict":
|
|
428
|
+
base.data = layout.ref(column.codes, "u32", shared);
|
|
429
|
+
base.dictionary = utf8Ref(layout, encodedDictionary(column.dictionary), shared);
|
|
430
|
+
break;
|
|
431
|
+
case "string":
|
|
432
|
+
base.strings = {
|
|
433
|
+
offsets: layout.ref(column.offsets, "u32", shared),
|
|
434
|
+
utf8: layout.ref(column.utf8, "utf8", shared),
|
|
435
|
+
};
|
|
436
|
+
break;
|
|
437
|
+
case "list":
|
|
438
|
+
base.offsets = layout.ref(column.offsets, "u32", shared);
|
|
439
|
+
base.child = encodeColumn(layout, column.child, shared);
|
|
440
|
+
break;
|
|
441
|
+
case "json":
|
|
442
|
+
base.jsonText = utf8Ref(layout, encodedJsonText(column), shared);
|
|
443
|
+
break;
|
|
444
|
+
default: {
|
|
445
|
+
const name: string = dtype;
|
|
446
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown dtype ${name}`, { dtype: name });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return base;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Encode every column of a table in declaration order.
|
|
454
|
+
* @param layout - the collector
|
|
455
|
+
* @param table - the table
|
|
456
|
+
* @returns the wire columns
|
|
457
|
+
*/
|
|
458
|
+
function encodeTable(layout: WireLayout, table: AttributeTable): WireColumn[] {
|
|
459
|
+
const out: WireColumn[] = [];
|
|
460
|
+
const tableShared = isShared(table);
|
|
461
|
+
for (const column of table) {
|
|
462
|
+
out.push(encodeColumn(layout, column, tableShared));
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ============================================================ id map, meta, views
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Encode the id map (design section 4.5), materialising its typed form on first use.
|
|
471
|
+
* @param layout - the collector
|
|
472
|
+
* @param snapshot - the snapshot
|
|
473
|
+
* @returns the wire id map
|
|
474
|
+
*/
|
|
475
|
+
function encodeIds(layout: WireLayout, snapshot: GraphSnapshot): WireIdMap {
|
|
476
|
+
const { ids } = snapshot;
|
|
477
|
+
if (!(ids instanceof NodeIdMap)) {
|
|
478
|
+
throw new GraphFormatError("E_UNSUPPORTED", "the snapshot's id map is not a NodeIdMap of this package", {
|
|
479
|
+
reason: "foreign id map",
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
const typed = nodeIdMapToTyped(ids);
|
|
483
|
+
const shared = isShared(ids);
|
|
484
|
+
return {
|
|
485
|
+
kind: typed.kind,
|
|
486
|
+
size: typed.size,
|
|
487
|
+
offset: typed.offset,
|
|
488
|
+
values: typed.values === null ? null : layout.ref(typed.values, wireDtypeOf(typed.values), shared),
|
|
489
|
+
tags: typed.tags === null ? null : layout.ref(typed.tags, "u8", shared),
|
|
490
|
+
numbers: typed.numbers === null ? null : layout.ref(typed.numbers, "f64", shared),
|
|
491
|
+
offsets: typed.offsets === null ? null : layout.ref(typed.offsets, "u32", shared),
|
|
492
|
+
utf8: typed.utf8 === null ? null : layout.ref(typed.utf8, "utf8", shared),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* The manifest copy of the graph metadata with `extra` tagged per design section 5.9.
|
|
498
|
+
* @param meta - the metadata
|
|
499
|
+
* @returns the manifest form
|
|
500
|
+
*/
|
|
501
|
+
function encodeGraphMeta(meta: GraphMeta): GraphMeta {
|
|
502
|
+
return {
|
|
503
|
+
name: meta.name,
|
|
504
|
+
description: meta.description,
|
|
505
|
+
creator: meta.creator,
|
|
506
|
+
created: meta.created,
|
|
507
|
+
modified: meta.modified,
|
|
508
|
+
keywords: [...meta.keywords],
|
|
509
|
+
sourceFormat: meta.sourceFormat,
|
|
510
|
+
sourceVersion: meta.sourceVersion,
|
|
511
|
+
idType: meta.idType,
|
|
512
|
+
timeFormat: meta.timeFormat,
|
|
513
|
+
timeRepresentation: meta.timeRepresentation,
|
|
514
|
+
mode: meta.mode,
|
|
515
|
+
declaredMultigraph: meta.declaredMultigraph,
|
|
516
|
+
weightOrigin: meta.weightOrigin === null ? null : { ...meta.weightOrigin },
|
|
517
|
+
extra: encodeJsonValue(meta.extra) as Readonly<Record<string, unknown>>,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* The cached, non-scalar views named by includeViews, each as its materialised member arrays
|
|
523
|
+
* (design section 9.1). A view that is not resident is skipped: the receiver recomputes it.
|
|
524
|
+
* @param layout - the collector
|
|
525
|
+
* @param snapshot - the snapshot
|
|
526
|
+
* @param names - the requested views
|
|
527
|
+
* @returns the wire views, or null when none was carried
|
|
528
|
+
*/
|
|
529
|
+
function encodeViews(
|
|
530
|
+
layout: WireLayout,
|
|
531
|
+
snapshot: GraphSnapshot,
|
|
532
|
+
names: readonly ViewName[] | undefined,
|
|
533
|
+
): Record<string, Record<string, WireBufferRef>> | null {
|
|
534
|
+
if (names === undefined || names.length === 0) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
const out: Record<string, Record<string, WireBufferRef>> = {};
|
|
538
|
+
let count = 0;
|
|
539
|
+
for (const name of new Set(names)) {
|
|
540
|
+
if (SCALAR_VIEWS.has(name)) {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const value: unknown = peekView(snapshot, name);
|
|
544
|
+
if (value === null) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const members: Record<string, WireBufferRef> = {};
|
|
548
|
+
let any = false;
|
|
549
|
+
for (const [member, array] of Object.entries(viewArrays(snapshot, name, value))) {
|
|
550
|
+
const view = array as TypedArrayData;
|
|
551
|
+
members[member] = layout.ref(view, wireDtypeOf(view));
|
|
552
|
+
any = true;
|
|
553
|
+
}
|
|
554
|
+
if (any) {
|
|
555
|
+
out[name] = members;
|
|
556
|
+
count++;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return count === 0 ? null : out;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// ============================================================ the manifest
|
|
563
|
+
|
|
564
|
+
/** The result of collecting a snapshot: the manifest and the layout that placed its arrays. */
|
|
565
|
+
interface CollectedWire {
|
|
566
|
+
/** The manifest. */
|
|
567
|
+
readonly manifest: WireManifest;
|
|
568
|
+
/** The collector holding the buffers (reference mode) or the segments (container mode). */
|
|
569
|
+
readonly layout: WireLayout;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Build the manifest of a snapshot over a collector (design section 9.1): core arrays first (an
|
|
574
|
+
* identity permutation, an absent weights array and a zero-length array are null), then the id map,
|
|
575
|
+
* the tables, the extension tables, the metadata and the requested views.
|
|
576
|
+
* @param snapshot - the snapshot
|
|
577
|
+
* @param layout - the collector
|
|
578
|
+
* @param includeViews - the views to carry
|
|
579
|
+
* @param includeColumns - whether attribute and extension tables are carried (default true)
|
|
580
|
+
* @returns the manifest and the collector
|
|
581
|
+
*/
|
|
582
|
+
export function collectWire(
|
|
583
|
+
snapshot: GraphSnapshot,
|
|
584
|
+
layout: WireLayout,
|
|
585
|
+
includeViews: readonly ViewName[] | undefined,
|
|
586
|
+
includeColumns = true,
|
|
587
|
+
): CollectedWire {
|
|
588
|
+
assertLittleEndianHost();
|
|
589
|
+
const identity = snapshot.flags.arcToEdgeIsIdentity;
|
|
590
|
+
const coreArrays: Record<CoreArrayName, TypedArrayData | null> = {
|
|
591
|
+
rowPtr: snapshot.rowPtr,
|
|
592
|
+
colIdx: snapshot.colIdx,
|
|
593
|
+
weights: snapshot.weights,
|
|
594
|
+
arcToEdge: identity ? null : snapshot.arcToEdge,
|
|
595
|
+
edgeToArc: identity ? null : snapshot.edgeToArc,
|
|
596
|
+
};
|
|
597
|
+
const coreRefs: Record<CoreArrayName, WireBufferRef | null> = {
|
|
598
|
+
rowPtr: null,
|
|
599
|
+
colIdx: null,
|
|
600
|
+
weights: null,
|
|
601
|
+
arcToEdge: null,
|
|
602
|
+
edgeToArc: null,
|
|
603
|
+
};
|
|
604
|
+
const rowPtrRef = layout.ref(snapshot.rowPtr, "u32");
|
|
605
|
+
coreRefs.rowPtr = rowPtrRef;
|
|
606
|
+
for (const name of CORE_ORDER) {
|
|
607
|
+
const array = coreArrays[name];
|
|
608
|
+
if (name !== "rowPtr" && array !== null && array.length > 0) {
|
|
609
|
+
coreRefs[name] = layout.ref(array, wireDtypeOf(array));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const arena = encodeArena(snapshot, layout, coreRefs);
|
|
613
|
+
const ids = encodeIds(layout, snapshot);
|
|
614
|
+
const nodeColumns = includeColumns ? encodeTable(layout, snapshot.nodes) : [];
|
|
615
|
+
const edgeColumns = includeColumns ? encodeTable(layout, snapshot.edges) : [];
|
|
616
|
+
const graphColumns = includeColumns ? encodeTable(layout, snapshot.graph) : [];
|
|
617
|
+
const extensions: { name: string; rowCount: number; columns: WireColumn[] }[] = [];
|
|
618
|
+
if (includeColumns) {
|
|
619
|
+
for (const [name, table] of snapshot.extensions) {
|
|
620
|
+
extensions.push({ name, rowCount: table.rowCount, columns: encodeTable(layout, table) });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const views = encodeViews(layout, snapshot, includeViews);
|
|
624
|
+
layout.finalise();
|
|
625
|
+
const manifest: WireManifest = {
|
|
626
|
+
format: WIRE_FORMAT,
|
|
627
|
+
wire: [WIRE_MAJOR, WIRE_MINOR],
|
|
628
|
+
producer: WIRE_PRODUCER,
|
|
629
|
+
formatVersion: FORMAT_VERSION,
|
|
630
|
+
directed: snapshot.directed,
|
|
631
|
+
counts: {
|
|
632
|
+
nodes: snapshot.nodeCount,
|
|
633
|
+
edges: snapshot.edgeCount,
|
|
634
|
+
arcs: snapshot.arcCount,
|
|
635
|
+
selfLoops: snapshot.selfLoopCount,
|
|
636
|
+
},
|
|
637
|
+
flags: { ...snapshot.flags },
|
|
638
|
+
core: {
|
|
639
|
+
rowPtr: rowPtrRef,
|
|
640
|
+
colIdx: coreRefs.colIdx,
|
|
641
|
+
weights: coreRefs.weights,
|
|
642
|
+
arcToEdge: coreRefs.arcToEdge,
|
|
643
|
+
edgeToArc: coreRefs.edgeToArc,
|
|
644
|
+
},
|
|
645
|
+
arena,
|
|
646
|
+
ids,
|
|
647
|
+
nodeColumns,
|
|
648
|
+
edgeColumns,
|
|
649
|
+
graphColumns,
|
|
650
|
+
extensions,
|
|
651
|
+
meta: encodeGraphMeta(snapshot.meta),
|
|
652
|
+
views,
|
|
653
|
+
copied: layout.copied,
|
|
654
|
+
label: snapshot.label,
|
|
655
|
+
};
|
|
656
|
+
return { manifest, layout };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* The arena descriptor: in container mode the prefix of the region holding the core arrays (always
|
|
661
|
+
* present); in reference mode the snapshot's own arena when it has one.
|
|
662
|
+
* @param snapshot - the snapshot
|
|
663
|
+
* @param layout - the collector, after the core arrays were placed
|
|
664
|
+
* @param coreRefs - the core references
|
|
665
|
+
* @returns the descriptor, or null
|
|
666
|
+
*/
|
|
667
|
+
function encodeArena(
|
|
668
|
+
snapshot: GraphSnapshot,
|
|
669
|
+
layout: WireLayout,
|
|
670
|
+
coreRefs: Readonly<Record<CoreArrayName, WireBufferRef | null>>,
|
|
671
|
+
): WireArena | null {
|
|
672
|
+
if (layout.mode === "container") {
|
|
673
|
+
let end = 0;
|
|
674
|
+
let hotByteLength = 0;
|
|
675
|
+
for (const name of CORE_ORDER) {
|
|
676
|
+
const ref = coreRefs[name];
|
|
677
|
+
if (ref === null) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
end = ref.byteOffset + ref.byteLength;
|
|
681
|
+
if (name === "rowPtr" || name === "colIdx" || name === "weights") {
|
|
682
|
+
hotByteLength = end;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return { buffer: 0, byteOffset: 0, byteLength: end, hotByteLength };
|
|
686
|
+
}
|
|
687
|
+
const { arena } = snapshot;
|
|
688
|
+
if (arena === null) {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
const buffer = layout.indexOf(arena.buffer);
|
|
692
|
+
if (buffer === null) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
return { buffer, byteOffset: arena.byteOffset, byteLength: arena.byteLength, hotByteLength: arena.hotByteLength };
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// ============================================================ public entry points
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* The plain-object wire form of a snapshot (design section 9.1): a JSON-serialisable manifest plus
|
|
702
|
+
* the distinct backing ArrayBuffers of the core, the id map, the columns and the extension tables.
|
|
703
|
+
* Without `transfer` every buffer is a plain reference and postMessage clones; with `transfer: true`
|
|
704
|
+
* every buffer held by more than one snapshot, table or column (design section 9.1, tracked by
|
|
705
|
+
* src/util/shared-buffers.ts) is copied (its index listed in `manifest.copied`) so the remaining
|
|
706
|
+
* buffers can be transferred with `transferables()`.
|
|
707
|
+
* @param snapshot - the snapshot
|
|
708
|
+
* @param options - transfer mode, the cached views to carry and whether columns are carried
|
|
709
|
+
* @returns the wire snapshot; E_UNSUPPORTED on a big-endian host
|
|
710
|
+
*/
|
|
711
|
+
export function toWire(snapshot: GraphSnapshot, options: ToWireOptions = {}): WireSnapshot {
|
|
712
|
+
const transfer = options.transfer === true;
|
|
713
|
+
const layout = new WireLayout("reference", transfer);
|
|
714
|
+
const { manifest } = collectWire(snapshot, layout, options.includeViews, options.includeColumns !== false);
|
|
715
|
+
if (transfer) {
|
|
716
|
+
// the transfer list of THIS wire: exclusive originals plus the fresh copies of shared buffers
|
|
717
|
+
TRANSFER_LISTS.set(snapshot, [...layout.buffers]);
|
|
718
|
+
}
|
|
719
|
+
return { manifest, buffers: layout.buffers };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** Per snapshot, the buffers its most recent `toWire({ transfer: true })` put into `wire.buffers`. */
|
|
723
|
+
const TRANSFER_LISTS = new WeakMap<GraphSnapshot, ArrayBuffer[]>();
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* The postMessage transfer list of design section 9.1: the same set `toWire({ transfer: true })`
|
|
727
|
+
* put into `wire.buffers` -- exclusively owned originals plus the fresh copies made for shared
|
|
728
|
+
* buffers -- so `postMessage(wire, snapshot.transferables())` transfers exactly the buffers the
|
|
729
|
+
* wire carries, whatever `includeColumns` / `includeViews` selected (a buffer the wire does not
|
|
730
|
+
* reference is never listed, so no column is lost on either side). Before any transfer-mode toWire()
|
|
731
|
+
* call the list is computed for the default wire shape: the distinct exclusively owned buffers of
|
|
732
|
+
* the core, the id map, the typed columns, the string stores and the extension tables; a buffer
|
|
733
|
+
* held by more than one snapshot, table or column is excluded and an identity permutation is never
|
|
734
|
+
* included.
|
|
735
|
+
* @param snapshot - the snapshot
|
|
736
|
+
* @returns the buffers, in manifest order
|
|
737
|
+
*/
|
|
738
|
+
export function transferables(snapshot: GraphSnapshot): ArrayBuffer[] {
|
|
739
|
+
const recorded = TRANSFER_LISTS.get(snapshot);
|
|
740
|
+
if (recorded !== undefined) {
|
|
741
|
+
return [...recorded];
|
|
742
|
+
}
|
|
743
|
+
const layout = new WireLayout("reference", false);
|
|
744
|
+
collectWire(snapshot, layout, undefined, true);
|
|
745
|
+
return layout.buffers.filter((buffer) => !isShared(buffer));
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* The UTF-8 bytes of a manifest as the container stores them (design section 9.2).
|
|
750
|
+
* @param manifest - the manifest
|
|
751
|
+
* @returns the encoded JSON text
|
|
752
|
+
*/
|
|
753
|
+
export function encodeManifest(manifest: WireManifest): Uint8Array {
|
|
754
|
+
return encoder.encode(JSON.stringify(manifest));
|
|
755
|
+
}
|