Skip to content

Commit f123de2

Browse files
committed
feat(DataArray): add preserveTypedArrays option to getState()
getState({ preserveTypedArrays: true }) preserves TypedArray values without converting and copying to plain Arrays. This avoids out-of-memory crashes for large data arrays (e.g. 268M-element labelmaps where Array.from() would allocate ~1.6GB of boxed Numbers). The default behavior is unchanged — getState() without options still returns JSON-safe plain Arrays.
1 parent f60fdaa commit f123de2

6 files changed

Lines changed: 72 additions & 16 deletions

File tree

Sources/Common/Core/DataArray/index.d.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { vtkObject, vtkRange } from '../../../interfaces';
1+
import { vtkObject, vtkRange, GetStateOptions } from '../../../interfaces';
22
import { float, int, Nullable, Range, TypedArray } from '../../../types';
33

44
/**
@@ -274,9 +274,12 @@ export interface vtkDataArray extends vtkObject {
274274

275275
/**
276276
* Get the state of this array.
277+
*
278+
* Pass `{ preserveTypedArrays: true }` to keep TypedArray values
279+
* without converting and copying to a plain Array.
277280
* @returns {object}
278281
*/
279-
getState(): object;
282+
getState(options?: GetStateOptions): object;
280283

281284
/**
282285
* Deep copy of another vtkDataArray into this one.

Sources/Common/Core/DataArray/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,14 +450,15 @@ function vtkDataArray(publicAPI, model) {
450450
};
451451

452452
// Override serialization support
453-
publicAPI.getState = () => {
453+
publicAPI.getState = ({ preserveTypedArrays = false } = {}) => {
454454
if (model.deleted) {
455455
return null;
456456
}
457457
const jsonArchive = { ...model, vtkClass: publicAPI.getClassName() };
458458

459-
// Convert typed array to regular array
460-
jsonArchive.values = Array.from(jsonArchive.values);
459+
if (!preserveTypedArrays) {
460+
jsonArchive.values = Array.from(jsonArchive.values);
461+
}
461462
delete jsonArchive.buffer;
462463

463464
// Clean any empty data

Sources/Common/Core/DataArray/test/testDataArray.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import test from 'tape';
2+
import vtk from 'vtk.js/Sources/vtk';
23
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
34
import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants';
45
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
@@ -508,3 +509,31 @@ test('Test vtkDataArray resize function', (t) => {
508509

509510
t.end();
510511
});
512+
513+
test('Test vtkDataArray getState preserveTypedArrays option', (t) => {
514+
const values = new Uint8Array([1, 2, 3, 4, 5]);
515+
const da = vtkDataArray.newInstance({ values });
516+
517+
// Default: values converted to plain Array
518+
const state = da.getState();
519+
t.ok(Array.isArray(state.values), 'default getState returns plain Array');
520+
521+
// With option: values preserved as TypedArray
522+
const transferable = da.getState({ preserveTypedArrays: true });
523+
t.ok(
524+
transferable.values instanceof Uint8Array,
525+
'TypedArray type is preserved'
526+
);
527+
528+
// Round-trip via vtk() works with TypedArray values
529+
const da2 = vtk(transferable);
530+
t.ok(da2, 'Can reconstruct from state with TypedArray values');
531+
t.deepEqual(
532+
Array.from(da2.getData()),
533+
Array.from(values),
534+
'Values preserved after round-trip'
535+
);
536+
t.equal(da2.getDataType(), 'Uint8Array', 'Data type preserved');
537+
538+
t.end();
539+
});

Sources/Common/DataModel/DataSetAttributes/FieldData.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,11 @@ function vtkFieldData(publicAPI, model) {
252252
publicAPI.getNumberOfTuples = () =>
253253
model.arrays.length > 0 ? model.arrays[0].getNumberOfTuples() : 0;
254254

255-
publicAPI.getState = () => {
256-
const result = superGetState();
255+
publicAPI.getState = (options) => {
256+
const result = superGetState(options);
257257
if (result) {
258258
result.arrays = model.arrays.map((item) => ({
259-
data: item.data.getState(),
259+
data: item.data.getState(options),
260260
}));
261261
}
262262
return result;

Sources/interfaces.d.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@ import vtkDataArray from './Common/Core/DataArray';
22
import { vtkPipelineConnection } from './types';
33
import { EVENT_ABORT, VOID } from './macros';
44

5+
export interface GetStateOptions {
6+
/**
7+
* When true, TypedArrays are preserved without converting and
8+
* copying to plain Arrays. Use for structured clone / postMessage.
9+
*
10+
* Note: the resulting state is not JSON-safe. TypedArrays serialize
11+
* as `{"0":v,"1":v,...}` via `JSON.stringify`, not as arrays.
12+
* @default false
13+
*/
14+
preserveTypedArrays?: boolean;
15+
}
16+
517
/**
618
* Object returned on any subscription call
719
*/
@@ -241,15 +253,20 @@ export interface vtkObject {
241253
* Such state can then be reused to clone or rebuild a full
242254
* vtkObject tree using the root vtk() function.
243255
*
244-
* The following example will grab mapper and dataset that are
245-
* beneath the vtkActor instance as well.
246-
*
247256
* ```
248257
* const actorStr = JSON.stringify(actor.getState());
249258
* const newActor = vtk(JSON.parse(actorStr));
250259
* ```
260+
*
261+
* Pass `{ preserveTypedArrays: true }` to keep TypedArrays
262+
* without converting and copying to plain Arrays. Useful for
263+
* structured clone / postMessage transfers.
264+
*
265+
* ```
266+
* worker.postMessage(dataset.getState({ preserveTypedArrays: true }));
267+
* ```
251268
*/
252-
getState(): object;
269+
getState(options?: GetStateOptions): object;
253270

254271
/**
255272
* Used internally by JSON.stringify to get the content to serialize.

Sources/macros.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,11 @@ export function obj(publicAPI = {}, model = {}) {
368368
};
369369

370370
// Add serialization support
371-
publicAPI.getState = () => {
371+
publicAPI.getState = ({ preserveTypedArrays = false } = {}) => {
372372
if (model.deleted) {
373373
return null;
374374
}
375+
const options = { preserveTypedArrays };
375376
const jsonArchive = { ...model, vtkClass: publicAPI.getClassName() };
376377

377378
// Convert every vtkObject to its serializable form
@@ -383,11 +384,16 @@ export function obj(publicAPI = {}, model = {}) {
383384
) {
384385
delete jsonArchive[keyName];
385386
} else if (jsonArchive[keyName].isA) {
386-
jsonArchive[keyName] = jsonArchive[keyName].getState();
387+
jsonArchive[keyName] = jsonArchive[keyName].getState(options);
387388
} else if (Array.isArray(jsonArchive[keyName])) {
388-
jsonArchive[keyName] = jsonArchive[keyName].map(getStateArrayMapFunc);
389+
jsonArchive[keyName] = jsonArchive[keyName].map((item) =>
390+
item && item.isA ? item.getState(options) : item
391+
);
389392
} else if (isTypedArray(jsonArchive[keyName])) {
390-
jsonArchive[keyName] = Array.from(jsonArchive[keyName]);
393+
if (!preserveTypedArrays) {
394+
jsonArchive[keyName] = Array.from(jsonArchive[keyName]);
395+
}
396+
// else: keep TypedArray as-is for structured clone / postMessage
391397
}
392398
});
393399

0 commit comments

Comments
 (0)