diff --git a/src/annotation/backend.ts b/src/annotation/backend.ts index 1c8444e9a1..078d8f5ce3 100644 --- a/src/annotation/backend.ts +++ b/src/annotation/backend.ts @@ -33,7 +33,6 @@ import type { AnnotationId, SerializedAnnotations, } from "#src/annotation/index.js"; -import { fixAnnotationAfterStructuredCloning } from "#src/annotation/index.js"; import type { ChunkManager } from "#src/chunk_manager/backend.js"; import { Chunk, @@ -67,7 +66,6 @@ import { import type { TransformedSource } from "#src/sliceview/base.js"; import { registerNested, WatchableValue } from "#src/trackable_value.js"; import type { Borrowed } from "#src/util/disposable.js"; -import type { Uint64 } from "#src/util/uint64.js"; import { getBasePriority, getPriorityTier, @@ -153,7 +151,7 @@ export class AnnotationGeometryChunk extends GeometryChunkMixin( export class AnnotationSubsetGeometryChunk extends GeometryChunkMixin(Chunk) { declare source: AnnotationSubsetGeometryChunkSource; - objectId: Uint64; + objectId: bigint; } @registerSharedObject(ANNOTATION_METADATA_CHUNK_SOURCE_RPC_ID) @@ -193,14 +191,14 @@ class AnnotationSubsetGeometryChunkSource extends ChunkSource { parent: Borrowed | undefined = undefined; declare chunks: Map; relationshipIndex: number; - getChunk(objectId: Uint64) { + getChunk(objectId: bigint) { const key = getObjectKey(objectId); const { chunks } = this; let chunk = chunks.get(key); if (chunk === undefined) { chunk = this.getNewChunk_(AnnotationSubsetGeometryChunk); chunk.initialize(key); - chunk.objectId = objectId.clone(); + chunk.objectId = objectId; this.addChunk(chunk); } return chunk; @@ -303,9 +301,7 @@ registerRPC(ANNOTATION_REFERENCE_DELETE_RPC_ID, function (x: any) { registerRPC(ANNOTATION_COMMIT_UPDATE_RPC_ID, function (x: any) { const obj = this.get(x.id); const annotationId: AnnotationId | undefined = x.annotationId; - const newAnnotation: Annotation | null = fixAnnotationAfterStructuredCloning( - x.newAnnotation, - ); + const newAnnotation: Annotation | null = x.newAnnotation; let promise: Promise; if (annotationId === undefined) { diff --git a/src/annotation/frontend_source.ts b/src/annotation/frontend_source.ts index c33e684172..4bde7959b8 100644 --- a/src/annotation/frontend_source.ts +++ b/src/annotation/frontend_source.ts @@ -37,7 +37,6 @@ import { AnnotationType, annotationTypeHandlers, annotationTypes, - fixAnnotationAfterStructuredCloning, makeAnnotationId, makeAnnotationPropertySerializers, } from "#src/annotation/index.js"; @@ -250,7 +249,7 @@ export class AnnotationMetadataChunk extends Chunk { annotation: Annotation | null; constructor(source: Borrowed, x: any) { super(source); - this.annotation = fixAnnotationAfterStructuredCloning(x.annotation); + this.annotation = x.annotation; } } @@ -1008,8 +1007,7 @@ registerRPC(ANNOTATION_COMMIT_UPDATE_RESULT_RPC_ID, function (x) { if (error !== undefined) { source.handleFailedUpdate(annotationId, error); } else { - const newAnnotation: Annotation | null = - fixAnnotationAfterStructuredCloning(x.newAnnotation); + const newAnnotation: Annotation | null = x.newAnnotation; source.handleSuccessfulUpdate(annotationId, newAnnotation); } }); diff --git a/src/annotation/index.ts b/src/annotation/index.ts index db2dded24e..b50797935f 100644 --- a/src/annotation/index.ts +++ b/src/annotation/index.ts @@ -40,6 +40,7 @@ import { expectArray, parseArray, parseFixedLengthArray, + parseUint64, verifyEnumString, verifyFiniteFloat, verifyFiniteNonNegativeFloat, @@ -54,7 +55,6 @@ import { import { parseDataTypeValue } from "#src/util/lerp.js"; import { getRandomHexString } from "#src/util/random.js"; import { NullarySignal, Signal } from "#src/util/signal.js"; -import { Uint64 } from "#src/util/uint64.js"; export type AnnotationId = string; @@ -642,7 +642,7 @@ export interface AnnotationBase { id: AnnotationId; type: AnnotationType; - relatedSegments?: Uint64[][]; + relatedSegments?: BigUint64Array[]; properties: any[]; } @@ -1017,7 +1017,7 @@ export function annotationToJson( const { relatedSegments } = annotation; if (relatedSegments?.some((x) => x.length !== 0)) { result.segments = relatedSegments.map((segments) => - segments.map((x) => x.toString()), + Array.from(segments, (x) => x.toString()), ); } if (schema.properties.length !== 0) { @@ -1053,11 +1053,20 @@ function restoreAnnotation( return schema.relationships.map(() => []); } if (schema.relationships.length === 1 && !Array.isArray(a[0])) { - return [parseArray(a, (x) => Uint64.parseString(x))]; + return [ + parseFixedLengthArray(new BigUint64Array(a.length), a, parseUint64), + ]; } return parseArray( expectArray(relObj, schema.relationships.length), - (segments) => parseArray(segments, (y) => Uint64.parseString(y)), + (segments) => { + segments = expectArray(segments); + return parseFixedLengthArray( + new BigUint64Array(segments.length), + segments, + parseUint64, + ); + }, ); }); const properties = verifyObjectProperty(obj, "props", (propsObj) => { @@ -1438,24 +1447,3 @@ export class AnnotationSerializer { return serializeAnnotations(this.annotations, this.propertySerializers); } } - -export function fixAnnotationAfterStructuredCloning(obj: Annotation | null) { - if (obj == null) { - return obj; - } - const { relatedSegments } = obj; - if (relatedSegments !== undefined) { - for ( - let i = 0, numRelationships = relatedSegments.length; - i < numRelationships; - ++i - ) { - const segments = relatedSegments[i]; - if (segments === undefined) continue; - relatedSegments[i] = segments.map( - (x: { low: number; high: number }) => new Uint64(x.low, x.high), - ); - } - } - return obj; -} diff --git a/src/annotation/renderlayer.ts b/src/annotation/renderlayer.ts index b0ab74f9e4..dc5d46f082 100644 --- a/src/annotation/renderlayer.ts +++ b/src/annotation/renderlayer.ts @@ -126,7 +126,6 @@ import type { MessageList } from "#src/util/message_list.js"; import { MessageSeverity } from "#src/util/message_list.js"; import type { AnyConstructor, MixinConstructor } from "#src/util/mixin.js"; import { NullarySignal } from "#src/util/signal.js"; -import type { Uint64 } from "#src/util/uint64.js"; import { withSharedVisibility } from "#src/visibility_priority/frontend.js"; import { GLBuffer } from "#src/webgl/buffer.js"; import type { ParameterizedContextDependentShaderGetter } from "#src/webgl/dynamic_shader.js"; @@ -639,8 +638,7 @@ function AnnotationRenderLayer< pickId = renderContext.pickIDs.register( this, chunk.numPickIds, - 0, - 0, + 0n, chunk, ); } @@ -701,7 +699,7 @@ function AnnotationRenderLayer< updateMouseState( mouseState: MouseSelectionState, - _pickedValue: Uint64, + _pickedValue: bigint, pickedOffset: number, data: any, ) { diff --git a/src/async_computation/encode_compressed_segmentation.ts b/src/async_computation/encode_compressed_segmentation.ts index 13969cccfe..3d1ee67c83 100644 --- a/src/async_computation/encode_compressed_segmentation.ts +++ b/src/async_computation/encode_compressed_segmentation.ts @@ -38,16 +38,7 @@ registerAsyncComputation( encodeCompressedSegmentationUint64, async (rawData, shape, blockSize) => { tempBuffer.clear(); - encodeChannelsUint64( - tempBuffer, - blockSize, - new BigUint64Array( - rawData.buffer, - rawData.byteOffset, - rawData.length / 2, - ), - shape, - ); + encodeChannelsUint64(tempBuffer, blockSize, rawData, shape); return { value: tempBuffer.view }; }, ); diff --git a/src/datasource/brainmaps/backend.ts b/src/datasource/brainmaps/backend.ts index 51e2de5061..1846c76eab 100644 --- a/src/datasource/brainmaps/backend.ts +++ b/src/datasource/brainmaps/backend.ts @@ -84,6 +84,7 @@ import { kInfinityVec, kZeroVec, vec3, vec3Key } from "#src/util/geom.js"; import { parseArray, parseFixedLengthArray, + parseUint64, verifyObject, verifyObjectProperty, verifyOptionalString, @@ -91,7 +92,6 @@ import { verifyStringArray, } from "#src/util/json.js"; import { defaultStringCompare } from "#src/util/string.js"; -import { Uint64 } from "#src/util/uint64.js"; import * as vector from "#src/util/vector.js"; import { decodeZIndexCompressed, @@ -215,13 +215,8 @@ function getFragmentCorner( yBits: number, zBits: number, ): Uint32Array { - const id = new Uint64(); - if (!id.tryParseString(fragmentId, 16)) { - throw new Error( - `Couldn't parse fragmentId ${fragmentId} as hex-encoded Uint64`, - ); - } - return decodeZIndexCompressed(id.toBigInt(), xBits, yBits, zBits); + const value = parseUint64(BigInt("0x" + fragmentId)); + return decodeZIndexCompressed(value, xBits, yBits, zBits); } interface BrainmapsMultiscaleManifestChunk extends MultiscaleManifestChunk { @@ -383,9 +378,8 @@ function decodeBatchMeshResponse( if (index + headerSize > length) { throw new Error("Invalid batch mesh fragment response."); } - const objectIdLow = dataView.getUint32(index, /*littleEndian=*/ true); - const objectIdHigh = dataView.getUint32(index + 4, /*littleEndian=*/ true); - const objectIdString = new Uint64(objectIdLow, objectIdHigh).toString(); + const objectId = dataView.getBigUint64(index, /*littleEndian=*/ true); + const objectIdString = objectId.toString(); const prefix = objectIdString + "\0"; index += 8; const fragmentKeyLength = dataView.getUint32(index, /*littleEndian=*/ true); @@ -892,11 +886,11 @@ function parseBrainmapsAnnotationId(idPrefix: string, fullId: string) { return id; } -function parseObjectLabels(obj: any): Uint64[][] | undefined { +function parseObjectLabels(obj: any): BigUint64Array[] | undefined { if (obj == null) { return undefined; } - return [parseArray(obj, (x) => Uint64.parseString("" + x, 10))]; + return [BigUint64Array.from(parseArray(obj, parseUint64))]; } function parseAnnotation( @@ -1064,7 +1058,7 @@ function annotationToBrainmaps(annotation: Annotation): any { const objectLabels = annotation.relatedSegments === undefined ? undefined - : annotation.relatedSegments[0].map((x) => x.toString()); + : Array.from(annotation.relatedSegments[0], (x) => x.toString()); switch (annotation.type) { case AnnotationType.LINE: { const { pointA, pointB } = annotation; diff --git a/src/datasource/graphene/backend.ts b/src/datasource/graphene/backend.ts index 886a022e0d..b8dec5afb5 100644 --- a/src/datasource/graphene/backend.ts +++ b/src/datasource/graphene/backend.ts @@ -65,7 +65,7 @@ import { computeChunkBounds } from "#src/sliceview/volume/backend.js"; import { Uint64Set } from "#src/uint64_set.js"; import { vec3, vec3Key } from "#src/util/geom.js"; import { HttpError } from "#src/util/http_request.js"; -import { Uint64 } from "#src/util/uint64.js"; +import { parseUint64 } from "#src/util/json.js"; import { getBasePriority, getPriorityTier, @@ -136,7 +136,7 @@ export class GrapheneMeshSource extends WithParameters( this.parameters.fragmentUrl, ); - addNewSegment(segment: Uint64) { + addNewSegment(segment: bigint) { const { newSegments } = this; newSegments.add(segment); const TEN_MINUTES = 1000 * 60 * 10; @@ -196,8 +196,8 @@ export class GrapheneMeshSource extends WithParameters( export class ChunkedGraphChunk extends Chunk { chunkGridPosition: Float32Array; source: GrapheneChunkedGraphChunkSource | null = null; - segment: Uint64; - leaves: Uint64[] = []; + segment: bigint; + leaves: BigUint64Array = new BigUint64Array(0); chunkDataSize: Uint32Array | null; initializeVolumeChunk(key: string, chunkGridPosition: Float32Array) { @@ -208,7 +208,7 @@ export class ChunkedGraphChunk extends Chunk { initializeChunkedGraphChunk( key: string, chunkGridPosition: Float32Array, - segment: Uint64, + segment: bigint, ) { this.initializeVolumeChunk(key, chunkGridPosition); this.chunkDataSize = null; @@ -219,7 +219,7 @@ export class ChunkedGraphChunk extends Chunk { downloadSucceeded() { this.systemMemoryBytes = 16; // this.segment - this.systemMemoryBytes += 16 * this.leaves.length; + this.systemMemoryBytes += this.leaves.byteLength; this.queueManager.updateChunkState(this, ChunkState.SYSTEM_MEMORY_WORKER); if (this.priorityTier < ChunkPriorityTier.RECENT) { this.source!.chunkManager.scheduleUpdateChunkPriorities(); @@ -228,16 +228,12 @@ export class ChunkedGraphChunk extends Chunk { } freeSystemMemory() { - this.leaves = []; + this.leaves = new BigUint64Array(0); } } function decodeChunkedGraphChunk(leaves: string[]) { - const final: Uint64[] = new Array(leaves.length); - for (let i = 0; i < final.length; ++i) { - final[i] = Uint64.parseString(leaves[i]); - } - return final; + return BigUint64Array.from(leaves, parseUint64); } @registerSharedObject() @@ -290,7 +286,7 @@ export class GrapheneChunkedGraphChunkSource extends WithParameters( }); } - getChunk(chunkGridPosition: Float32Array, segment: Uint64) { + getChunk(chunkGridPosition: Float32Array, segment: bigint) { const key = `${vec3Key(chunkGridPosition)}-${segment}`; let chunk = this.chunks.get(key); @@ -442,7 +438,7 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState( forEachVisibleSegment(this, (segment, _) => { if (isBaseSegmentId(segment, this.nBitsForLayerId.value)) return; // TODO maybe support highBitRepresentation? - const chunk = source.getChunk(curPositionInChunks, segment.clone()); + const chunk = source.getChunk(curPositionInChunks, segment); chunkManager.requestChunk( chunk, priorityTier, @@ -460,7 +456,7 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState( } private forEachSelectedRootWithLeaves( - callback: (rootObjectKey: string, leaves: Uint64[]) => void, + callback: (rootObject: bigint, leaves: BigUint64Array) => void, ) { const { source } = this; @@ -470,7 +466,7 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState( chunk.priorityTier < ChunkPriorityTier.RECENT ) { if (this.visibleSegments.has(chunk.segment) && chunk.leaves.length) { - callback(chunk.segment.toString(), chunk.leaves); + callback(chunk.segment, chunk.leaves); } } } @@ -481,51 +477,41 @@ export class ChunkedGraphLayer extends withSegmentationLayerBackendState( }, 100); private updateDisplayState() { - const visibleLeaves = new Map(); - const capacities = new Map(); + const visibleLeaves = new Map(); + const capacities = new Map(); // Reserve - this.forEachSelectedRootWithLeaves((rootObjectKey, leaves) => { - if (!capacities.has(rootObjectKey)) { - capacities.set(rootObjectKey, leaves.length); - } else { - capacities.set( - rootObjectKey, - capacities.get(rootObjectKey)! + leaves.length, - ); - } + this.forEachSelectedRootWithLeaves((rootObject, leaves) => { + capacities.set( + rootObject, + (capacities.get(rootObject) ?? 0) + leaves.length, + ); }); // Collect unique leaves - this.forEachSelectedRootWithLeaves((rootObjectKey, leaves) => { - if (!visibleLeaves.has(rootObjectKey)) { - visibleLeaves.set(rootObjectKey, new Uint64Set()); - visibleLeaves - .get(rootObjectKey)! - .reserve(capacities.get(rootObjectKey)!); - visibleLeaves - .get(rootObjectKey)! - .add(Uint64.parseString(rootObjectKey)); + this.forEachSelectedRootWithLeaves((rootObject, leaves) => { + if (!visibleLeaves.has(rootObject)) { + visibleLeaves.set(rootObject, new Uint64Set()); + visibleLeaves.get(rootObject)!.reserve(capacities.get(rootObject)!); + visibleLeaves.get(rootObject)!.add(rootObject); } - visibleLeaves.get(rootObjectKey)!.add(leaves); + visibleLeaves.get(rootObject)!.add(leaves); }); for (const [root, leaves] of visibleLeaves) { // TODO: Delete segments not visible anymore from segmentEquivalences - requires a faster data // structure, though. - /*if (this.segmentEquivalences.has(Uint64.parseString(root))) { - this.segmentEquivalences.delete([...this.segmentEquivalences.setElements(Uint64.parseString(root))].filter(x + /*if (this.segmentEquivalences.has(root)) { + this.segmentEquivalences.delete([...this.segmentEquivalences.setElements(root)].filter(x => !leaves.has(x) && !this.visibleSegments.has(x))); }*/ const filteredLeaves = [...leaves].filter( (x) => !this.segmentEquivalences.has(x), ); - const rootInt = Uint64.parseString(root); - for (const leaf of filteredLeaves) { - this.segmentEquivalences.link(rootInt, leaf); + this.segmentEquivalences.link(root, leaf); } } } @@ -553,5 +539,5 @@ registerRPC(CHUNKED_GRAPH_RENDER_LAYER_UPDATE_SOURCES_RPC_ID, function (x) { registerRPC(GRAPHENE_MESH_NEW_SEGMENT_RPC_ID, function (x) { const obj = this.get(x.rpcId); - obj.addNewSegment(Uint64.parseString(x.segment)); + obj.addNewSegment(x.segment); }); diff --git a/src/datasource/graphene/base.ts b/src/datasource/graphene/base.ts index e8ec485731..844cddf352 100644 --- a/src/datasource/graphene/base.ts +++ b/src/datasource/graphene/base.ts @@ -29,7 +29,6 @@ import type { import { makeSliceViewChunkSpecification } from "#src/sliceview/base.js"; import type { mat4 } from "#src/util/geom.js"; import type { FetchOk, HttpError } from "#src/util/http_request.js"; -import { Uint64 } from "#src/util/uint64.js"; export const PYCG_APP_VERSION = 1; export const GRAPHENE_MESH_NEW_SEGMENT_RPC_ID = "GrapheneMeshSource:NewSegment"; @@ -71,9 +70,9 @@ export class MultiscaleMeshMetadata { sharding: Array | undefined; } -export function isBaseSegmentId(segmentId: Uint64, nBitsForLayerId: number) { - const layerId = Uint64.rshift(new Uint64(), segmentId, 64 - nBitsForLayerId); - return Uint64.equal(layerId, Uint64.ONE); +export function isBaseSegmentId(segmentId: bigint, nBitsForLayerId: number) { + const layerId = segmentId >> BigInt(64 - nBitsForLayerId); + return layerId == 1n; } export function getGrapheneFragmentKey(fragmentId: string) { diff --git a/src/datasource/graphene/frontend.ts b/src/datasource/graphene/frontend.ts index a0c1669020..d3fc71fa18 100644 --- a/src/datasource/graphene/frontend.ts +++ b/src/datasource/graphene/frontend.ts @@ -170,6 +170,7 @@ import { HttpError, isNotFoundError } from "#src/util/http_request.js"; import { parseArray, parseFixedLengthArray, + parseUint64, verify3dVec, verifyBoolean, verifyEnumString, @@ -188,7 +189,6 @@ import type { ProgressOptions } from "#src/util/progress_listener.js"; import { ProgressSpan } from "#src/util/progress_listener.js"; import { NullarySignal } from "#src/util/signal.js"; import type { Trackable } from "#src/util/trackable.js"; -import { Uint64 } from "#src/util/uint64.js"; import { makeDeleteButton } from "#src/widget/delete_button.js"; import type { DependentViewContext } from "#src/widget/dependent_view_widget.js"; import { makeIcon } from "#src/widget/icon.js"; @@ -206,9 +206,9 @@ const BLUE_COLOR_SEGMENT = vec4FromVec3(BLUE_COLOR, 0.5); const RED_COLOR_HIGHLIGHT = vec4FromVec3(RED_COLOR, 0.25); const BLUE_COLOR_HIGHTLIGHT = vec4FromVec3(BLUE_COLOR, 0.25); const TRANSPARENT_COLOR = vec4.fromValues(0.5, 0.5, 0.5, 0.01); -const RED_COLOR_SEGMENT_PACKED = new Uint64(packColor(RED_COLOR_SEGMENT)); -const BLUE_COLOR_SEGMENT_PACKED = new Uint64(packColor(BLUE_COLOR_SEGMENT)); -const TRANSPARENT_COLOR_PACKED = new Uint64(packColor(TRANSPARENT_COLOR)); +const RED_COLOR_SEGMENT_PACKED = BigInt(packColor(RED_COLOR_SEGMENT)); +const BLUE_COLOR_SEGMENT_PACKED = BigInt(packColor(BLUE_COLOR_SEGMENT)); +const TRANSPARENT_COLOR_PACKED = BigInt(packColor(TRANSPARENT_COLOR)); const MULTICUT_OFF_COLOR = vec4.fromValues(0, 0, 0, 0.5); class GrapheneMeshSource extends WithParameters( @@ -787,15 +787,11 @@ function makeColoredAnnotationState( } function getOptionalUint64(obj: any, key: string) { - return verifyOptionalObjectProperty(obj, key, (value) => - Uint64.parseString(String(value)), - ); + return verifyOptionalObjectProperty(obj, key, parseUint64); } function getUint64(obj: any, key: string) { - return verifyObjectProperty(obj, key, (value) => - Uint64.parseString(String(value)), - ); + return verifyObjectProperty(obj, key, parseUint64); } function restoreSegmentSelection(obj: any): SegmentSelection { @@ -866,8 +862,8 @@ class GrapheneState implements Trackable { } export interface SegmentSelection { - segmentId: Uint64; - rootId: Uint64; + segmentId: bigint; + rootId: bigint; position: Float32Array; annotationReference?: AnnotationReference; } @@ -893,8 +889,8 @@ class MergeState extends RefCounted implements Trackable { const segmentSelectionToJSON = (x: SegmentSelection) => { return { - [SEGMENT_ID_JSON_KEY]: x.segmentId.toJSON(), - [ROOT_ID_JSON_KEY]: x.rootId.toJSON(), + [SEGMENT_ID_JSON_KEY]: x.segmentId.toString(), + [ROOT_ID_JSON_KEY]: x.rootId.toString(), [POSITION_JSON_KEY]: [...x.position], }; }; @@ -908,7 +904,7 @@ class MergeState extends RefCounted implements Trackable { }; if (x.mergedRoot) { - res[MERGED_ROOT_JSON_KEY] = x.mergedRoot.toJSON(); + res[MERGED_ROOT_JSON_KEY] = x.mergedRoot.toString(); } if (x.error) { res[ERROR_JSON_KEY] = x.error; @@ -969,7 +965,7 @@ class MulticutState extends RefCounted implements Trackable { sources = new WatchableSet(); constructor( - public focusSegment = new TrackableValue( + public focusSegment = new TrackableValue( undefined, (x) => x, ), @@ -1004,14 +1000,14 @@ class MulticutState extends RefCounted implements Trackable { const segmentSelectionToJSON = (x: SegmentSelection) => { return { - [SEGMENT_ID_JSON_KEY]: x.segmentId.toJSON(), - [ROOT_ID_JSON_KEY]: x.rootId.toJSON(), + [SEGMENT_ID_JSON_KEY]: x.segmentId.toString(), + [ROOT_ID_JSON_KEY]: x.rootId.toString(), [POSITION_JSON_KEY]: [...x.position], }; }; return { - [FOCUS_SEGMENT_JSON_KEY]: focusSegment.toJSON(), + [FOCUS_SEGMENT_JSON_KEY]: focusSegment.toJSON()?.toString(), [SINKS_JSON_KEY]: [...sinks].map(segmentSelectionToJSON), [SOURCES_JSON_KEY]: [...sources].map(segmentSelectionToJSON), }; @@ -1025,7 +1021,7 @@ class MulticutState extends RefCounted implements Trackable { }; verifyOptionalObjectProperty(x, FOCUS_SEGMENT_JSON_KEY, (value) => { - this.focusSegment.restoreState(Uint64.parseString(String(value))); + this.focusSegment.restoreState(parseUint64(value)); }); const sinks = verifyObjectProperty( x, @@ -1062,13 +1058,13 @@ class MulticutState extends RefCounted implements Trackable { get redSegments() { return [...this.sinks] - .filter((x) => !Uint64.equal(x.segmentId, x.rootId)) + .filter((x) => x.segmentId !== x.rootId) .map((x) => x.segmentId); } get blueSegments() { return [...this.sources] - .filter((x) => !Uint64.equal(x.segmentId, x.rootId)) + .filter((x) => x.segmentId !== x.rootId) .map((x) => x.segmentId); } } @@ -1086,18 +1082,20 @@ class GraphConnection extends SegmentationGraphSourceConnection { super(graph, layer.displayState.segmentationGroupState.value); const segmentsState = layer.displayState.segmentationGroupState.value; segmentsState.selectedSegments.changed.add( - (segmentIds: Uint64[] | Uint64 | null, add: boolean) => { + (segmentIds: bigint[] | bigint | null, add: boolean) => { if (segmentIds !== null) { - segmentIds = Array().concat(segmentIds); + segmentIds = + typeof segmentIds === "bigint" ? [segmentIds] : segmentIds; } this.selectedSegmentsChanged(segmentIds, add); }, ); segmentsState.visibleSegments.changed.add( - (segmentIds: Uint64[] | Uint64 | null, add: boolean) => { + (segmentIds: bigint[] | bigint | null, add: boolean) => { if (segmentIds !== null) { - segmentIds = Array().concat(segmentIds); + segmentIds = + typeof segmentIds === "bigint" ? [segmentIds] : segmentIds; } this.visibleSegmentsChanged(segmentIds, add); }, @@ -1150,7 +1148,9 @@ class GraphConnection extends SegmentationGraphSourceConnection { mergeAnnotationState.source.childAdded.add((x) => { const annotation = x as Line; const relatedSegments = annotation.relatedSegments![0]; - const visibles = relatedSegments.map((x) => visibleSegments.has(x)); + const visibles = Array.from(relatedSegments, (x) => + visibleSegments.has(x), + ); if (visibles[0] === false) { setTimeout(() => { const { tool } = layer; @@ -1179,7 +1179,9 @@ class GraphConnection extends SegmentationGraphSourceConnection { const annotation = ref.value as Line | undefined; if (annotation) { const relatedSegments = annotation.relatedSegments![0]; - const visibles = relatedSegments.map((x) => visibleSegments.has(x)); + const visibles = Array.from(relatedSegments, (x) => + visibleSegments.has(x), + ); if (relatedSegments.length < 4) { mergeAnnotationState.source.delete(ref); StatusMessage.showTemporaryMessage( @@ -1242,7 +1244,7 @@ class GraphConnection extends SegmentationGraphSourceConnection { private lastDeselectionMessage: StatusMessage | undefined; private lastDeselectionMessageExists = false; - private visibleSegmentsChanged(segments: Uint64[] | null, added: boolean) { + private visibleSegmentsChanged(segments: bigint[] | null, added: boolean) { const { segmentsState } = this; const { focusSegment: { value: focusSegment }, @@ -1262,9 +1264,7 @@ class GraphConnection extends SegmentationGraphSourceConnection { segmentsState.selectedSegments.add(focusSegment); segmentsState.visibleSegments.add(focusSegment); if (segments) { - segments = segments.filter( - (segment) => !Uint64.equal(segment, focusSegment), - ); + segments = segments.filter((segment) => segment !== focusSegment); } } if (segments === null) { @@ -1300,7 +1300,7 @@ class GraphConnection extends SegmentationGraphSourceConnection { } } - private selectedSegmentsChanged(segments: Uint64[] | null, added: boolean) { + private selectedSegmentsChanged(segments: bigint[] | null, added: boolean) { const { segmentsState } = this; if (segments === null) { const leafSegmentCount = this.segmentsState.selectedSegments.size; @@ -1315,20 +1315,19 @@ class GraphConnection extends SegmentationGraphSourceConnection { segmentId, this.graph.info.graph.nBitsForLayerId, ); - const segmentConst = segmentId.clone(); if (added && isBaseSegment) { - this.graph.getRoot(segmentConst).then((rootId) => { - if (segmentsState.visibleSegments.has(segmentConst)) { + this.graph.getRoot(segmentId).then((rootId) => { + if (segmentsState.visibleSegments.has(segmentId)) { segmentsState.visibleSegments.add(rootId); } - segmentsState.selectedSegments.delete(segmentConst); + segmentsState.selectedSegments.delete(segmentId); segmentsState.selectedSegments.add(rootId); }); } } } - computeSplit(include: Uint64, exclude: Uint64): ComputedSplit | undefined { + computeSplit(include: bigint, exclude: bigint): ComputedSplit | undefined { include; exclude; return undefined; @@ -1359,13 +1358,13 @@ class GraphConnection extends SegmentationGraphSourceConnection { return undefined; } - meshAddNewSegments(segments: Uint64[]) { + meshAddNewSegments(segments: bigint[]) { const meshSource = this.getMeshSource(); if (meshSource) { for (const segment of segments) { meshSource.rpc!.invoke(GRAPHENE_MESH_NEW_SEGMENT_RPC_ID, { rpcId: meshSource.rpcId!, - segment: segment.toString(), + segment, }); } } @@ -1416,7 +1415,7 @@ class GraphConnection extends SegmentationGraphSourceConnection { private submitMerge = async ( submission: MergeSubmission, attempts = 1, - ): Promise => { + ): Promise => { this.graph; const loadedSubsource = getGraphLoadedSubsource(this.layer)!; const annotationToNanometers = @@ -1439,30 +1438,27 @@ class GraphConnection extends SegmentationGraphSourceConnection { } } - return Uint64.ZERO; // appease typescript + return 0n; // appease typescript }; async bulkMerge(submissions: MergeSubmission[]) { const { merges } = this.state.mergeState; const bulkMergeHelper = ( submissions: MergeSubmission[], - ): Promise => { + ): Promise => { return new Promise((f) => { if (submissions.length === 0) { f([]); return; } - const segmentsToRemove: Uint64[] = []; - const replaceSegment = (a: Uint64, b: Uint64) => { + const segmentsToRemove: bigint[] = []; + const replaceSegment = (a: bigint, b: bigint) => { segmentsToRemove.push(a); for (const submission of submissions) { - if ( - submission.source && - Uint64.equal(submission.source.rootId, a) - ) { + if (submission.source && submission.source.rootId === a) { submission.source.rootId = b; } - if (Uint64.equal(submission.sink.rootId, a)) { + if (submission.sink.rootId === a) { submission.sink.rootId = b; } } @@ -1519,7 +1515,7 @@ class GraphConnection extends SegmentationGraphSourceConnection { submissions = submissions.filter((x) => !x.locked && x.source); const segmentsToRemove = await bulkMergeHelper(submissions); - const segmentsToAdd: Uint64[] = []; + const segmentsToAdd: bigint[] = []; for (const submission of submissions) { if (submission.error) { submission.locked = false; @@ -1583,7 +1579,7 @@ class GrapheneGraphServerInterface { this.httpSource = getHttpSource(sharedKvStoreContext.kvStoreContext, url); } - async getRoot(segment: Uint64, timestamp = "") { + async getRoot(segment: bigint, timestamp = "") { const timestampEpoch = new Date(timestamp).valueOf() / 1000; const { fetchOkImpl, baseUrl } = this.httpSource; @@ -1600,14 +1596,14 @@ class GrapheneGraphServerInterface { errorPrefix: "Could not fetch root: ", }, ); - return Uint64.parseString(jsonResp.root_id); + return parseUint64(jsonResp.root_id); } async mergeSegments( first: SegmentSelection, second: SegmentSelection, annotationToNanometers: Float64Array, - ): Promise { + ): Promise { const { fetchOkImpl, baseUrl } = this.httpSource; const promise = fetchOkImpl(`${baseUrl}/merge?int64_as_str=1`, { method: "POST", @@ -1626,7 +1622,7 @@ class GrapheneGraphServerInterface { try { const response = await promise; const jsonResp = await response.json(); - return Uint64.parseString(jsonResp.new_root_ids[0]); + return parseUint64(jsonResp.new_root_ids[0]); } catch (e) { if (e instanceof HttpError) { const msg = await parseGrapheneError(e); @@ -1640,7 +1636,7 @@ class GrapheneGraphServerInterface { first: SegmentSelection[], second: SegmentSelection[], annotationToNanometers: Float64Array, - ): Promise { + ): Promise { const { fetchOkImpl, baseUrl } = this.httpSource; const promise = fetchOkImpl(`${baseUrl}/split?int64_as_str=1`, { method: "POST", @@ -1661,21 +1657,21 @@ class GrapheneGraphServerInterface { errorPrefix: "Split failed: ", }); const jsonResp = await response.json(); - const final: Uint64[] = new Array(jsonResp.new_root_ids.length); + const final: bigint[] = new Array(jsonResp.new_root_ids.length); for (let i = 0; i < final.length; ++i) { - final[i] = Uint64.parseString(jsonResp.new_root_ids[i]); + final[i] = parseUint64(jsonResp.new_root_ids[i]); } return final; } - async filterLatestRoots(segments: Uint64[]): Promise { + async filterLatestRoots(segments: bigint[]): Promise { const { fetchOkImpl, baseUrl } = this.httpSource; const url = `${baseUrl}/is_latest_roots`; const promise = fetchOkImpl(url, { method: "POST", body: JSON.stringify({ - node_ids: segments.map((x) => x.toJSON()), + node_ids: segments.map((x) => x.toString()), }), }); @@ -1686,7 +1682,7 @@ class GrapheneGraphServerInterface { }, ); - const res: Uint64[] = []; + const res: bigint[] = []; for (const [i, isLatest] of jsonResp.is_latest.entries()) { if (isLatest) { res.push(segments[i]); @@ -1737,7 +1733,7 @@ class GrapheneGraphSource extends SegmentationGraphSource { ); } - getRoot(segment: Uint64) { + getRoot(segment: bigint) { return this.graphServer.getRoot(segment); } @@ -1778,20 +1774,20 @@ class GrapheneGraphSource extends SegmentationGraphSource { // following not used - async merge(a: Uint64, b: Uint64): Promise { + async merge(a: bigint, b: bigint): Promise { a; b; - return new Uint64(); + return 0n; } async split( - include: Uint64, - exclude: Uint64, - ): Promise<{ include: Uint64; exclude: Uint64 }> { + include: bigint, + exclude: bigint, + ): Promise<{ include: bigint; exclude: bigint }> { return { include, exclude }; } - trackSegment(id: Uint64, callback: (id: Uint64 | null) => void): () => void { + trackSegment(id: bigint, callback: (id: bigint | null) => void): () => void { return () => { console.log("trackSegment... do nothing", id, callback); }; @@ -1998,7 +1994,9 @@ const synchronizeAnnotationSource = ( point: selection.position, type: AnnotationType.POINT, properties: [], - relatedSegments: [[selection.segmentId, selection.rootId]], + relatedSegments: [ + BigUint64Array.of(selection.segmentId, selection.rootId), + ], }; const ref = annotationSource.add(annotation); selection.annotationReference = ref; @@ -2234,21 +2232,21 @@ class MulticutSegmentsTool extends LayerTool { const { rootId, segmentId } = currentSegmentSelection; const { focusSegment, segments } = multicutState; if (focusSegment.value === undefined) { - focusSegment.value = rootId.clone(); + focusSegment.value = rootId; } - if (!Uint64.equal(focusSegment.value, rootId)) { + if (focusSegment.value !== rootId) { StatusMessage.showTemporaryMessage( - `The selected supervoxel has root segment ${rootId.toString()}, but the supervoxels already selected have root ${focusSegment.value.toString()}`, + `The selected supervoxel has root segment ${rootId}, but the supervoxels already selected have root ${focusSegment.value}`, 12000, ); return; } - const isRoot = Uint64.equal(rootId, segmentId); + const isRoot = rootId === segmentId; if (!isRoot) { for (const segment of segments) { - if (Uint64.equal(segment, segmentId)) { + if (segment === segmentId) { StatusMessage.showTemporaryMessage( - `Supervoxel ${segmentId.toString()} has already been selected`, + `Supervoxel ${segmentId} has already been selected`, 7000, ); return; @@ -2287,8 +2285,8 @@ const maybeGetSelection = ( const point = getPoint(layer, mouseState); if (point === undefined) return; return { - rootId: value.clone(), - segmentId: baseValue.clone(), + rootId: value, + segmentId: baseValue, position: point, }; }; @@ -2306,7 +2304,7 @@ interface MergeSubmission { status?: string; sink: SegmentSelection; source?: SegmentSelection; - mergedRoot?: Uint64; + mergedRoot?: bigint; } export class MergeSegmentsPlaceLineTool extends PlaceLineTool { @@ -2344,15 +2342,15 @@ function lineToSubmission(line: Line, pending: boolean): MergeSubmission { locked: false, sink: { position: line.pointA.slice(), - rootId: relatedSegments[0].clone(), - segmentId: relatedSegments[1].clone(), + rootId: relatedSegments[0], + segmentId: relatedSegments[1], }, }; if (!pending) { res.source = { position: line.pointB.slice(), - rootId: relatedSegments[2].clone(), - segmentId: relatedSegments[3].clone(), + rootId: relatedSegments[2], + segmentId: relatedSegments[3], }; } return res; @@ -2366,12 +2364,12 @@ function mergeToLine(submission: MergeSubmission): Line { pointA: sink.position.slice(), pointB: source!.position.slice(), relatedSegments: [ - [ - sink.rootId.clone(), - sink.segmentId.clone(), - source!.rootId.clone(), - source!.segmentId.clone(), - ], + BigUint64Array.of( + sink.rootId, + sink.segmentId, + source!.rootId, + source!.segmentId, + ), ], properties: [], }; @@ -2467,7 +2465,7 @@ class MergeSegmentsTool extends LayerTool { return row; }; - const createPointElement = (id: Uint64) => { + const createPointElement = (id: bigint) => { const containerEl = document.createElement("div"); containerEl.classList.add("graphene-merge-segments-point"); const widget = makeWidget(augmentSegmentId(this.layer.displayState, id)); diff --git a/src/datasource/nggraph/frontend.ts b/src/datasource/nggraph/frontend.ts index 8578c9cf6d..76128651a2 100644 --- a/src/datasource/nggraph/frontend.ts +++ b/src/datasource/nggraph/frontend.ts @@ -50,6 +50,7 @@ import { DisjointUint64Sets } from "#src/util/disjoint_sets.js"; import type { RequestInitWithProgress } from "#src/util/http_request.js"; import { parseArray, + parseUint64, verifyFiniteFloat, verifyInt, verifyObject, @@ -57,34 +58,31 @@ import { verifyString, verifyStringArray, } from "#src/util/json.js"; -import { Uint64 } from "#src/util/uint64.js"; const urlPattern = "^(https?://[^/]+)/(.*)$"; interface GraphSegmentInfo { - id: Uint64; - baseSegments: Uint64[]; - baseSegmentParents: Uint64[]; + id: bigint; + baseSegments: bigint[]; + baseSegmentParents: bigint[]; name: string; tags: string[]; numVoxels: number; bounds: number[]; - lastLogId: Uint64 | null; + lastLogId: bigint | null; } function parseGraphSegmentInfo(obj: any): GraphSegmentInfo { verifyObject(obj); return { - id: verifyObjectProperty(obj, "id", (x) => - Uint64.parseString(verifyString(x)), - ), + id: verifyObjectProperty(obj, "id", parseUint64), baseSegments: verifyObjectProperty(obj, "base_segment_ids", (x) => - parseArray(x, (y) => Uint64.parseString(verifyString(y))), + parseArray(x, parseUint64), ), baseSegmentParents: verifyObjectProperty( obj, "base_segment_parent_ids", - (x) => parseArray(x, (y) => Uint64.parseString(verifyString(y))), + (x) => parseArray(x, parseUint64), ), name: verifyObjectProperty(obj, "name", verifyString), tags: verifyObjectProperty(obj, "tags", verifyStringArray), @@ -93,16 +91,13 @@ function parseGraphSegmentInfo(obj: any): GraphSegmentInfo { parseArray(x, (y) => verifyFiniteFloat(y)), ), lastLogId: verifyObjectProperty(obj, "last_log_id", (x) => - x == null ? null : Uint64.parseString(verifyString(x)), + x == null ? null : parseUint64(x), ), }; } -/// Base-10 string representation of a segment id, used as map key. -type SegmentIdString = string; - interface ActiveSegmentQuery { - id: Uint64; + id: bigint; current: GraphSegmentInfo | undefined; addedEquivalences: boolean; seenGeneration: number; @@ -137,13 +132,12 @@ class GraphConnection extends SegmentationGraphSourceConnection { this.visibleSegmentsChanged(); } - computeSplit(include: Uint64, exclude: Uint64): ComputedSplit | undefined { + computeSplit(include: bigint, exclude: bigint): ComputedSplit | undefined { const { segmentEquivalences } = this.segmentsState; const graphSegment = segmentEquivalences.get(include); if (isBaseSegmentId(graphSegment)) return undefined; - if (!Uint64.equal(segmentEquivalences.get(exclude), graphSegment)) - return undefined; - const query = this.segmentQueries.get(graphSegment.toString()); + if (segmentEquivalences.get(exclude) !== graphSegment) return undefined; + const query = this.segmentQueries.get(graphSegment); if (query === undefined) return undefined; const { current } = query; if (current === undefined) return undefined; @@ -153,27 +147,21 @@ class GraphConnection extends SegmentationGraphSourceConnection { for (let i = 0; i < length; ++i) { const baseSegment = baseSegments[i]; const parent = baseSegmentParents[i]; - if (Uint64.equal(baseSegment, exclude) || Uint64.equal(parent, exclude)) - continue; + if (baseSegment === exclude || parent === exclude) continue; ds.link(baseSegment, parent); console.log( - `Linking ${baseSegment} - ${parent} == ${include}? ${Uint64.equal( - include, - baseSegment, - )} ${Uint64.equal( - include, - parent, - )} :: unioned with include = ${Uint64.equal( - include, - ds.get(baseSegment), - )}, with exclude = ${Uint64.equal(exclude, ds.get(baseSegment))}`, + `Linking ${baseSegment} - ${parent} == ${include}? ${ + include === baseSegment + } ${include === parent} :: unioned with include = ${ + include === ds.get(baseSegment) + }, with exclude = ${exclude === ds.get(baseSegment)}`, ); } - const includeSegments: Uint64[] = []; - const excludeSegments: Uint64[] = []; + const includeSegments: bigint[] = []; + const excludeSegments: bigint[] = []; const includeRep = ds.get(include); for (const segment of baseSegments) { - if (Uint64.equal(ds.get(segment), includeRep)) { + if (ds.get(segment) === includeRep) { includeSegments.push(segment); } else { excludeSegments.push(segment); @@ -197,7 +185,7 @@ class GraphConnection extends SegmentationGraphSourceConnection { debounce(() => this.visibleSegmentsChanged(), 0), ); - private segmentQueries = new Map(); + private segmentQueries = new Map(); private ignoreVisibleSegmentsChanged = false; @@ -208,8 +196,7 @@ class GraphConnection extends SegmentationGraphSourceConnection { const { segmentQueries } = this; const { segmentEquivalences } = this.segmentsState; segmentEquivalences.clear(); - for (const [segmentIdString, query] of segmentQueries) { - segmentIdString; + for (const [_segmentId, query] of segmentQueries) { if (query.current === undefined || isBaseSegmentId(query.id)) continue; const { id, baseSegments } = query.current; if (baseSegments.length > 0) { @@ -224,33 +211,27 @@ class GraphConnection extends SegmentationGraphSourceConnection { }, 0), ); - private registerVisibleSegment(segmentId: Uint64) { + private registerVisibleSegment(segmentId: bigint) { const query: ActiveSegmentQuery = { id: segmentId, current: undefined, addedEquivalences: false, seenGeneration: updateGeneration, disposer: this.graph.watchSegment(segmentId, (info) => - this.handleSegmentUpdate(query.id.toString(), info), + this.handleSegmentUpdate(query.id, info), ), }; - const segmentIdString = segmentId.toString(); - this.segmentQueries.set(segmentIdString, query); - console.log(`adding to segmentQueries: ${segmentIdString}`); + this.segmentQueries.set(segmentId, query); + console.log(`adding to segmentQueries: ${segmentId}`); } - private handleSegmentUpdate( - segmentIdString: SegmentIdString, - update: GraphSegmentUpdate, - ) { - console.log(`handleSegmentUpdate: ${segmentIdString}`); - const query = this.segmentQueries.get(segmentIdString)!; + private handleSegmentUpdate(segmentId: bigint, update: GraphSegmentUpdate) { + console.log(`handleSegmentUpdate: ${segmentId}`); + const query = this.segmentQueries.get(segmentId)!; if (update === "invalid") { query.disposer(); - console.log( - `removing from segmentQueries: ${segmentIdString} due to invalid`, - ); - this.segmentQueries.delete(segmentIdString); + console.log(`removing from segmentQueries: ${segmentId} due to invalid`); + this.segmentQueries.delete(segmentId); try { this.ignoreVisibleSegmentsChanged = true; this.segmentsState.visibleSegments.delete(query.id); @@ -270,21 +251,20 @@ class GraphConnection extends SegmentationGraphSourceConnection { } console.log( `Error from ${this.graph.serverUrl}/${this.graph.entityName}` + - ` watching segment ${segmentIdString}`, + ` watching segment ${segmentId}`, ); return; } query.current = update; const oldId = query.id; const newId = update.id; - if (!Uint64.equal(newId, oldId)) { + if (newId !== oldId) { query.id = newId; - const newSegmentIdString = newId.toString(); - const newQuery = this.segmentQueries.get(newSegmentIdString); + const newQuery = this.segmentQueries.get(newId); console.log( - `removing from segmentQueries: ${segmentIdString} due to rename -> ${newId}`, + `removing from segmentQueries: ${segmentId} due to rename -> ${newId}`, ); - this.segmentQueries.delete(segmentIdString); + this.segmentQueries.delete(segmentId); try { this.ignoreVisibleSegmentsChanged = true; if (this.segmentsState.visibleSegments.has(oldId)) { @@ -303,14 +283,14 @@ class GraphConnection extends SegmentationGraphSourceConnection { } if (newQuery === undefined) { console.log(`adding to segmentQueries due to rename -> ${newId}`); - this.segmentQueries.set(newSegmentIdString, query); + this.segmentQueries.set(newId, query); this.segmentEquivalencesChanged(); } else { if ( update.lastLogId !== null && (typeof newQuery.current !== "object" || newQuery.current.lastLogId === null || - Uint64.less(newQuery.current.lastLogId, update.lastLogId)) + newQuery.current.lastLogId < update.lastLogId) ) { newQuery.current = update; this.segmentEquivalencesChanged(); @@ -330,25 +310,24 @@ class GraphConnection extends SegmentationGraphSourceConnection { const { segmentQueries } = this; const generation = ++updateGeneration; const processVisibleSegments = (visibleSegments: Uint64Set) => { - for (const segmentId of visibleSegments.unsafeKeys()) { - if (Uint64.equal(segmentId, UNKNOWN_NEW_SEGMENT_ID)) continue; - const segmentIdString = segmentId.toString(); - const existingQuery = segmentQueries.get(segmentIdString); + for (const segmentId of visibleSegments.keys()) { + if (segmentId === UNKNOWN_NEW_SEGMENT_ID) continue; + const existingQuery = segmentQueries.get(segmentId); if (existingQuery !== undefined) { existingQuery.seenGeneration = generation; continue; } - this.registerVisibleSegment(segmentId.clone()); + this.registerVisibleSegment(segmentId); } }; processVisibleSegments(segmentsState.visibleSegments); processVisibleSegments(segmentsState.temporaryVisibleSegments); - for (const [segmentIdString, query] of segmentQueries) { + for (const [segmentId, query] of segmentQueries) { if (query.seenGeneration !== generation) { console.log( - `removing from segmentQueries due to seenGeneration: ${segmentIdString}`, + `removing from segmentQueries due to seenGeneration: ${segmentId}`, ); - segmentQueries.delete(segmentIdString); + segmentQueries.delete(segmentId); query.disposer(); if (query.addedEquivalences) { this.segmentEquivalencesChanged(); @@ -362,7 +341,7 @@ type GraphSegmentUpdateCallback = (info: GraphSegmentUpdate) => void; interface WatchInfo { callback: GraphSegmentUpdateCallback; - segment: Uint64; + segment: bigint; watchId: number; } @@ -491,7 +470,7 @@ export class NggraphSegmentationGraphSource extends SegmentationGraphSource { return new GraphConnection(this, segmentsState); } - trackSegment(id: Uint64, callback: (id: Uint64 | null) => void): () => void { + trackSegment(id: bigint, callback: (id: bigint | null) => void): () => void { return this.watchSegment(id, (info: GraphSegmentUpdate) => { if (info === "invalid") { callback(null); @@ -505,7 +484,7 @@ export class NggraphSegmentationGraphSource extends SegmentationGraphSource { } watchSegment( - segment: Uint64, + segment: bigint, callback: GraphSegmentUpdateCallback, ): () => void { const watchInfo = { @@ -547,7 +526,7 @@ export class NggraphSegmentationGraphSource extends SegmentationGraphSource { return disposer; } - async merge(a: Uint64, b: Uint64): Promise { + async merge(a: bigint, b: bigint): Promise { const response = await nggraphGraphFetch( this.chunkManager, this.serverUrl, @@ -562,15 +541,13 @@ export class NggraphSegmentationGraphSource extends SegmentationGraphSource { }, ); verifyObject(response); - return verifyObjectProperty(response, "merged", (x) => - Uint64.parseString(x), - ); + return verifyObjectProperty(response, "merged", parseUint64); } async split( - include: Uint64, - exclude: Uint64, - ): Promise<{ include: Uint64; exclude: Uint64 }> { + include: bigint, + exclude: bigint, + ): Promise<{ include: bigint; exclude: bigint }> { const response = await nggraphGraphFetch( this.chunkManager, this.serverUrl, @@ -586,12 +563,8 @@ export class NggraphSegmentationGraphSource extends SegmentationGraphSource { ); verifyObject(response); return { - include: verifyObjectProperty(response, "include", (x) => - Uint64.parseString(x), - ), - exclude: verifyObjectProperty(response, "exclude", (x) => - Uint64.parseString(x), - ), + include: verifyObjectProperty(response, "include", parseUint64), + exclude: verifyObjectProperty(response, "exclude", parseUint64), }; } } diff --git a/src/datasource/precomputed/backend.ts b/src/datasource/precomputed/backend.ts index 4bf3912530..a67a996a8a 100644 --- a/src/datasource/precomputed/backend.ts +++ b/src/datasource/precomputed/backend.ts @@ -79,7 +79,6 @@ import type { VolumeChunk } from "#src/sliceview/volume/backend.js"; import { VolumeChunkSource } from "#src/sliceview/volume/backend.js"; import { convertEndian32, Endianness } from "#src/util/endian.js"; import { vec3 } from "#src/util/geom.js"; -import { Uint64 } from "#src/util/uint64.js"; import { encodeZIndexCompressed, encodeZIndexCompressed3d, @@ -497,7 +496,7 @@ export class PrecomputedMultiscaleMeshSource extends WithParameters( } else { ({ response: readResponse, shardInfo: chunk.shardInfo } = getOrNotFoundError( - await shardedKvStore.readWithShardInfo(chunk.objectId.toBigInt(), { + await shardedKvStore.readWithShardInfo(chunk.objectId, { signal, }), )); @@ -555,7 +554,7 @@ async function fetchByUint64( kvStore: KvStoreWithPath; shardedKvStore: ShardedKvStore | undefined; }, - id: Uint64, + id: bigint, signal: AbortSignal, ): Promise { const { shardedKvStore } = chunkSource; @@ -565,7 +564,7 @@ async function fetchByUint64( signal, }); } else { - return shardedKvStore.read(id.toBigInt(), { signal }); + return shardedKvStore.read(id, { signal }); } } @@ -613,12 +612,11 @@ function parseAnnotations( ); } const idOffset = 8 + numBytes * countLow; - const id = new Uint64(); const ids = new Array(countLow); for (let i = 0; i < countLow; ++i) { - id.low = dv.getUint32(idOffset + i * 8, /*littleEndian=*/ true); - id.high = dv.getUint32(idOffset + i * 8 + 4, /*littleEndian=*/ true); - ids[i] = id.toString(); + ids[i] = dv + .getBigUint64(idOffset + i * 8, /*littleEndian=*/ true) + .toString(); } const geometryData = new AnnotationGeometryData(); const origData = new Uint8Array(buffer, 8, numBytes * countLow); @@ -705,7 +703,7 @@ function parseSingleAnnotation( (annotation.properties = new Array(parameters.properties.length)), ); let offset = baseNumBytes; - const relatedSegments: Uint64[][] = (annotation.relatedSegments = []); + const relatedSegments: BigUint64Array[] = (annotation.relatedSegments = []); relatedSegments.length = numRelationships; for (let i = 0; i < numRelationships; ++i) { const count = dv.getUint32(offset, /*littleEndian=*/ true); @@ -715,12 +713,9 @@ function parseSingleAnnotation( ); } offset += 4; - const segments: Uint64[] = (relatedSegments[i] = []); + const segments = (relatedSegments[i] = new BigUint64Array(count)); for (let j = 0; j < count; ++j) { - segments[j] = new Uint64( - dv.getUint32(offset, /*littleEndian=*/ true), - dv.getUint32(offset + 4, /*littleEndian=*/ true), - ); + segments[j] = dv.getBigUint64(offset, /*littleEndian=*/ true); offset += 8; } } @@ -824,7 +819,7 @@ export class PrecomputedAnnotationSourceBackend extends WithParameters( } async downloadMetadata(chunk: AnnotationMetadataChunk, signal: AbortSignal) { - const id = Uint64.parseString(chunk.key!); + const id = BigInt(chunk.key!); const response = await fetchByUint64(this, id, signal); if (response === undefined) { chunk.annotation = null; diff --git a/src/datasource/precomputed/frontend.ts b/src/datasource/precomputed/frontend.ts index 69393aca7e..d5ca9b1bd5 100644 --- a/src/datasource/precomputed/frontend.ts +++ b/src/datasource/precomputed/frontend.ts @@ -95,6 +95,7 @@ import { MultiscaleVolumeChunkSource, VolumeChunkSource, } from "#src/sliceview/volume/frontend.js"; +import type { TypedNumberArrayConstructor } from "#src/util/array.js"; import { transposeNestedArrays } from "#src/util/array.js"; import { DATA_TYPE_ARRAY_CONSTRUCTOR, DataType } from "#src/util/data_type.js"; import { mat4, vec3 } from "#src/util/geom.js"; @@ -115,11 +116,11 @@ import { verifyString, verifyStringArray, verifyOptionalBoolean, + parseUint64, } from "#src/util/json.js"; import * as matrix from "#src/util/matrix.js"; import type { ProgressOptions } from "#src/util/progress_listener.js"; import { ProgressSpan } from "#src/util/progress_listener.js"; -import { Uint64 } from "#src/util/uint64.js"; export class PrecomputedVolumeChunkSource extends WithParameters( WithSharedKvStoreContext(VolumeChunkSource), @@ -1132,21 +1133,16 @@ async function getMeshDataSource( function parseInlinePropertyMap(data: unknown): InlineSegmentPropertyMap { verifyObject(data); - const tempUint64 = new Uint64(); const ids = verifyObjectProperty(data, "ids", (idsObj) => { idsObj = verifyStringArray(idsObj); const numIds = idsObj.length; - const ids = new Uint32Array(numIds * 2); + const ids = new BigUint64Array(numIds); for (let i = 0; i < numIds; ++i) { - if (!tempUint64.tryParseString(idsObj[i])) { - throw new Error(`Invalid uint64 id: ${JSON.stringify(idsObj[i])}`); - } - ids[2 * i] = tempUint64.low; - ids[2 * i + 1] = tempUint64.high; + ids[i] = parseUint64(idsObj[i]); } return ids; }); - const numIds = ids.length / 2; + const numIds = ids.length; const properties = verifyObjectProperty(data, "properties", (propertiesObj) => parseArray(propertiesObj, (propertyObj): InlineSegmentProperty => { verifyObject(propertyObj); @@ -1221,7 +1217,11 @@ function parseInlinePropertyMap(data: unknown): InlineSegmentPropertyMap { `Expected ${numIds} values, but received: ${valuesObj.length}`, ); } - return DATA_TYPE_ARRAY_CONSTRUCTOR[dataType].from(valuesObj); + return ( + DATA_TYPE_ARRAY_CONSTRUCTOR[ + dataType + ] as TypedNumberArrayConstructor + ).from(valuesObj); }, ); let min = Infinity; diff --git a/src/datasource/state_share.ts b/src/datasource/state_share.ts index 40f48657fc..dc21cc0092 100644 --- a/src/datasource/state_share.ts +++ b/src/datasource/state_share.ts @@ -2,6 +2,7 @@ import { ReadableHttpKvStore } from "#src/kvstore/http/common.js"; import { joinBaseUrlAndPath } from "#src/kvstore/url.js"; import { StatusMessage } from "#src/status.js"; import { RefCounted } from "#src/util/disposable.js"; +import { bigintToStringJsonReplacer } from "#src/util/json.js"; import type { Viewer } from "#src/viewer.js"; import { makeIcon } from "#src/widget/icon.js"; @@ -91,7 +92,10 @@ export class StateShare extends RefCounted { .fetchOkImpl(joinBaseUrlAndPath(store.baseUrl, path), { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify(viewer.state.toJSON()), + body: JSON.stringify( + viewer.state.toJSON(), + bigintToStringJsonReplacer, + ), }) .then((response) => response.json()) .then((res) => { diff --git a/src/datasource/zarr/metadata/index.ts b/src/datasource/zarr/metadata/index.ts index 207fb1a2fb..0cfae9bb96 100644 --- a/src/datasource/zarr/metadata/index.ts +++ b/src/datasource/zarr/metadata/index.ts @@ -16,7 +16,6 @@ import type { CodecChainSpec } from "#src/datasource/zarr/codec/index.js"; import type { DataType } from "#src/util/data_type.js"; -import type { Uint64 } from "#src/util/uint64.js"; export enum ChunkKeyEncoding { DEFAULT = 0, @@ -34,7 +33,7 @@ export interface ArrayMetadata { shape: number[]; chunkShape: number[]; dataType: DataType; - fillValue: number | Uint64; + fillValue: number | bigint; dimensionNames: (string | null)[]; dimensionUnits: (string | null)[]; userAttributes: Record; diff --git a/src/gpu_hash/hash_table.benchmark.ts b/src/gpu_hash/hash_table.benchmark.ts index 3e778fffa4..74cffe1bf9 100644 --- a/src/gpu_hash/hash_table.benchmark.ts +++ b/src/gpu_hash/hash_table.benchmark.ts @@ -17,20 +17,16 @@ import { describe, bench } from "vitest"; import { HashSetUint64 } from "#src/gpu_hash/hash_table.js"; import { getRandomValues } from "#src/util/random.js"; -import { Uint64 } from "#src/util/uint64.js"; describe("gpu_hash/hash_table", () => { const ht = new HashSetUint64(); const numValues = 100; - const values = new Uint32Array(numValues * 2); - const temp = new Uint64(); + const values = new BigUint64Array(numValues); getRandomValues(values); bench("insert", () => { ht.clear(); - for (let i = 0, length = values.length; i < length; i += 2) { - temp.low = values[i]; - temp.high = values[i + 1]; - ht.add(temp); + for (const value of values) { + ht.add(value); } }); }); diff --git a/src/gpu_hash/hash_table.spec.ts b/src/gpu_hash/hash_table.spec.ts index f567615437..f0bab66855 100644 --- a/src/gpu_hash/hash_table.spec.ts +++ b/src/gpu_hash/hash_table.spec.ts @@ -16,22 +16,21 @@ import { describe, it, expect } from "vitest"; import { HashMapUint64, HashSetUint64 } from "#src/gpu_hash/hash_table.js"; -import { Uint64 } from "#src/util/uint64.js"; +import { randomUint64 } from "#src/util/bigint.js"; describe("gpu_hash/hash_table", () => { it("HashSetUint64", () => { const ht = new HashSetUint64(); - const set = new Set(); + const set = new Set(); function compareViaIterate() { - const htValues = new Set(); - for (const v of ht.unsafeKeys()) { - const s = v.toString(); - expect(htValues.has(s), `Duplicate key in hash table: ${s}`).toBe( + const htValues = new Set(); + for (const v of ht.keys()) { + expect(htValues.has(v), `Duplicate key in hash table: ${v}`).toBe( false, ); - expect(set.has(s), `Unexpected key ${s} in hash table`).toBe(true); - htValues.add(s); + expect(set.has(v), `Unexpected key ${v} in hash table`).toBe(true); + htValues.add(v); } for (const s of set) { expect(htValues.has(s), `Hash table is missing key ${s}`).toBe(true); @@ -40,8 +39,7 @@ describe("gpu_hash/hash_table", () => { function compareViaHas() { for (const s of set) { - const k = Uint64.parseString(s); - expect(ht.has(k), `Hash table is missing key ${s}`).toBe(true); + expect(ht.has(s), `Hash table is missing key ${s}`).toBe(true); } } @@ -51,70 +49,61 @@ describe("gpu_hash/hash_table", () => { } const numInsertions = 100; - function testInsert(k: Uint64) { - const s = "" + k; - set.add(s); - expect( - ht.has(k), - `Unexpected positive has result for ${[k.low, k.high]}`, - ).toBe(false); + function testInsert(k: bigint) { + set.add(k); + expect(ht.has(k), `Unexpected positive has result for ${k}`).toBe(false); ht.add(k); compare(); } - const empty0 = new Uint64(ht.emptyLow, ht.emptyHigh); + const empty0 = ht.empty; testInsert(empty0); for (let i = 0; i < numInsertions; ++i) { - let k: Uint64; - let s: string; + let k: bigint; while (true) { - k = Uint64.random(); - s = k.toString(); - if (!set.has(s)) { + k = randomUint64(); + if (!set.has(k)) { break; } } testInsert(k); } - const empty1 = new Uint64(ht.emptyLow, ht.emptyHigh); + const empty1 = ht.empty; testInsert(empty1); }); it("HashMapUint64", () => { const ht = new HashMapUint64(); - const map = new Map(); + const map = new Map(); function compareViaIterate() { - const htValues = new Map(); + const htValues = new Map(); for (const [key, value] of ht) { - const s = key.toString(); - expect(htValues.has(s), `Duplicate key in hash table: ${s}`).toBe( + expect(htValues.has(key), `Duplicate key in hash table: ${key}`).toBe( false, ); - expect(map.has(s), `Unexpected key ${s} in hash table`).toBe(true); - htValues.set(s, value.clone()); + expect(map.has(key), `Unexpected key ${key} in hash table`).toBe(true); + htValues.set(key, value); } - for (const [s, value] of map) { - const v = htValues.get(s); + for (const [key, value] of map) { + const v = htValues.get(key); expect( - v !== undefined && Uint64.equal(v, value), - `Hash table maps ${s} -> ${v} rather than -> ${value}`, + v !== undefined && v === value, + `Hash table maps ${key} -> ${v} rather than -> ${value}`, ).toBe(true); } } function compareViaGet() { - const value = new Uint64(); - for (const [s, expectedValue] of map) { - const key = Uint64.parseString(s); - const has = ht.get(key, value); + for (const [key, expectedValue] of map) { + const value = ht.get(key); expect( - has && Uint64.equal(value, expectedValue), - `Hash table maps ${key} -> ${has ? value : undefined} ` + + value, + `Hash table maps ${key} -> ${value} ` + `rather than -> ${expectedValue}`, - ).toBe(true); + ).toEqual(expectedValue); } } @@ -124,31 +113,28 @@ describe("gpu_hash/hash_table", () => { } const numInsertions = 100; - function testInsert(k: Uint64, v: Uint64) { - const s = "" + k; - map.set(s, v); - expect(ht.has(k), `Unexpected positive has result for ${s}`).toBe(false); + function testInsert(k: bigint, v: bigint) { + map.set(k, v); + expect(ht.has(k), `Unexpected positive has result for ${k}`).toBe(false); ht.set(k, v); compare(); } - const empty0 = new Uint64(ht.emptyLow, ht.emptyHigh); - testInsert(empty0, Uint64.random()); + const empty0 = ht.empty; + testInsert(empty0, randomUint64()); for (let i = 0; i < numInsertions; ++i) { - let k: Uint64; - let s: string; + let k: bigint; while (true) { - k = Uint64.random(); - s = k.toString(); - if (!map.has(s)) { + k = randomUint64(); + if (!map.has(k)) { break; } } - testInsert(k, Uint64.random()); + testInsert(k, randomUint64()); } - const empty1 = new Uint64(ht.emptyLow, ht.emptyHigh); - testInsert(empty1, Uint64.random()); + const empty1 = ht.empty; + testInsert(empty1, randomUint64()); }); }); diff --git a/src/gpu_hash/hash_table.ts b/src/gpu_hash/hash_table.ts index 122de83e86..672886aa3a 100644 --- a/src/gpu_hash/hash_table.ts +++ b/src/gpu_hash/hash_table.ts @@ -15,8 +15,8 @@ */ import { hashCombine } from "#src/gpu_hash/hash_function.js"; +import { randomUint64 } from "#src/util/bigint.js"; import { getRandomValues } from "#src/util/random.js"; -import { Uint64 } from "#src/util/uint64.js"; export const NUM_ALTERNATIVES = 3; @@ -28,30 +28,27 @@ const DEBUG = false; // Key that needs to be inserted. Temporary variables used during insert. These can safely be // global because control never leaves functions defined in this module while these are in use. -let pendingLow = 0; -let pendingHigh = 0; -let backupPendingLow = 0; -let backupPendingHigh = 0; +let pending = 0n; +let backupPending = 0n; export abstract class HashTableBase { loadFactor = DEFAULT_LOAD_FACTOR; size = 0; - table: Uint32Array; + table: BigUint64Array; tableSize: number; - emptyLow = 4294967295; - emptyHigh = 4294967295; + empty = 0xffffffffffffffffn; maxRehashAttempts = 5; maxAttempts = 5; capacity: number; /** - * Number of uint32 elements per entry in hash table. + * Number of uint64 elements per entry in hash table. */ declare entryStride: number; generation = 0; - mungedEmptyKey = -1; + mungedEmptyKey: bigint | undefined; constructor( public hashSeeds = HashTableBase.generateHashSeeds(NUM_ALTERNATIVES), @@ -67,35 +64,32 @@ export abstract class HashTableBase { private updateHashFunctions(numHashes: number) { this.hashSeeds = HashTableBase.generateHashSeeds(numHashes); - this.mungedEmptyKey = -1; + this.mungedEmptyKey = undefined; } /** * Invokes callback with a modified version of the hash table data array. * - * Replaces all slots that appear to be valid entries for (emptyLow, emptyHigh), i.e. slots that - * contain (emptyLow, emptyHigh) and to which (emptyLow, emptyHigh) hashes, with (mungedEmptyKey, - * mungedEmptyKey). + * Replaces all slots that appear to be valid entries for `empty`, i.e. slots that + * contain `empty` and to which `empty` hashes, with `mungedEmptyKey`. * - * mungedEmptyKey is chosen to be a 32-bit value with the property that the 64-bit value - * (mungedEmptyKey, mungedEmptyKey) does not hash to any of the same slots as (emptyLow, - * emptyHigh). + * mungedEmptyKey is chosen such that it does not to any of the same slots as `empty`. * * This allows the modified data array to be used for lookups without special casing the empty * key. */ - tableWithMungedEmptyKey(callback: (table: Uint32Array) => void) { + tableWithMungedEmptyKey(callback: (table: BigUint64Array) => void) { const numHashes = this.hashSeeds.length; const emptySlots = new Array(numHashes); for (let i = 0; i < numHashes; ++i) { - emptySlots[i] = this.getHash(i, this.emptyLow, this.emptyHigh); + emptySlots[i] = this.getHash(i, this.empty); } let { mungedEmptyKey } = this; - if (mungedEmptyKey === -1) { + if (mungedEmptyKey === undefined) { chooseMungedEmptyKey: while (true) { - mungedEmptyKey = (Math.random() * 0x1000000) >>> 0; + mungedEmptyKey = randomUint64(); for (let i = 0; i < numHashes; ++i) { - const h = this.getHash(i, mungedEmptyKey, mungedEmptyKey); + const h = this.getHash(i, mungedEmptyKey); for (let j = 0; j < numHashes; ++j) { if (emptySlots[j] === h) { continue chooseMungedEmptyKey; @@ -106,12 +100,11 @@ export abstract class HashTableBase { break; } } - const { table, emptyLow, emptyHigh } = this; + const { table, empty } = this; for (let i = 0; i < numHashes; ++i) { const h = emptySlots[i]; - if (table[h] === emptyLow && table[h + 1] === emptyHigh) { + if (table[h] === empty) { table[h] = mungedEmptyKey; - table[h + 1] = mungedEmptyKey; } } try { @@ -119,9 +112,8 @@ export abstract class HashTableBase { } finally { for (let i = 0; i < numHashes; ++i) { const h = emptySlots[i]; - if (table[h] === mungedEmptyKey && table[h + 1] === mungedEmptyKey) { - table[h] = emptyLow; - table[h + 1] = emptyHigh; + if (table[h] === mungedEmptyKey) { + table[h] = empty; } } } @@ -131,71 +123,45 @@ export abstract class HashTableBase { return getRandomValues(new Uint32Array(numAlternatives)); } - getHash(hashIndex: number, low: number, high: number) { + getHash(hashIndex: number, x: bigint) { let hash = this.hashSeeds[hashIndex]; - hash = hashCombine(hash, low); - hash = hashCombine(hash, high); + hash = hashCombine(hash, Number(x & 0xffffffffn)); + hash = hashCombine(hash, Number(x >> 32n)); return this.entryStride * (hash & (this.tableSize - 1)); } /** - * Iterates over the Uint64 keys contained in the hash set. - * - * Creates a new Uint64 object at every iteration (otherwise spread and Array.from() fail) + * Iterates over the uint64 keys contained in the hash set. */ - *keys(): IterableIterator { - const { emptyLow, emptyHigh, entryStride } = this; + *keys(): IterableIterator { + const { empty, entryStride } = this; const { table } = this; for (let i = 0, length = table.length; i < length; i += entryStride) { - const low = table[i]; - const high = table[i + 1]; - if (low !== emptyLow || high !== emptyHigh) { - yield new Uint64(low, high); + const key = table[i]; + if (key !== empty) { + yield key; } } } /** - * Iterates over the Uint64 keys contained in the hash set. - * - * The same temp value will be modified and yielded at every iteration. + * Returns the offset into the hash table of the specified element, or -1 if the element is not + * present. */ - *unsafeKeys(temp = new Uint64()): IterableIterator { - const { emptyLow, emptyHigh, entryStride } = this; - const { table } = this; - for (let i = 0, length = table.length; i < length; i += entryStride) { - const low = table[i]; - const high = table[i + 1]; - if (low !== emptyLow || high !== emptyHigh) { - temp.low = low; - temp.high = high; - yield temp; - } - } - } - - indexOfPair(low: number, high: number) { - const { table, emptyLow, emptyHigh } = this; - if (low === emptyLow && high === emptyHigh) { + indexOf(x: bigint) { + const { table, empty } = this; + if (x === empty) { return -1; } for (let i = 0, numHashes = this.hashSeeds.length; i < numHashes; ++i) { - const h = this.getHash(i, low, high); - if (table[h] === low && table[h + 1] === high) { + const h = this.getHash(i, x); + if (table[h] === x) { return h; } } return -1; } - /** - * Returns the offset into the hash table of the specified element, or -1 if the element is not - * present. - */ - indexOf(x: Uint64) { - return this.indexOfPair(x.low, x.high); - } - /** * Changes the empty key to a value that is not equal to the current empty key and is not present * in the table. @@ -203,29 +169,25 @@ export abstract class HashTableBase { * This is called when an attempt is made to insert the empty key. */ private chooseAnotherEmptyKey() { - const { emptyLow, emptyHigh, table, entryStride } = this; - let newLow: number; - let newHigh: number; + const { empty, table, entryStride } = this; + let newKey: bigint; while (true) { - newLow = (Math.random() * 0x100000000) >>> 0; - newHigh = (Math.random() * 0x100000000) >>> 0; - if (newLow === emptyLow && newHigh === emptyHigh) { + newKey = randomUint64(); + if (newKey === empty) { continue; } - if (this.hasPair(newLow, newHigh)) { + if (this.has(newKey)) { continue; } break; } - this.emptyLow = newLow; - this.emptyHigh = newHigh; + this.empty = newKey; // Replace empty keys in the table. for (let h = 0, length = table.length; h < length; h += entryStride) { - if (table[h] === emptyLow && table[h + 1] === emptyHigh) { - table[h] = newLow; - table[h + 1] = newHigh; + if (table[h] === empty) { + table[h] = newKey; } } } @@ -233,23 +195,15 @@ export abstract class HashTableBase { /** * Returns true iff the specified element is present. */ - has(x: Uint64) { + has(x: bigint) { return this.indexOf(x) !== -1; } - /** - * Returns true iff the specified element is present. - */ - hasPair(low: number, high: number) { - return this.indexOfPair(low, high) !== -1; - } - - delete(x: Uint64) { + delete(x: bigint) { const index = this.indexOf(x); if (index !== -1) { const { table } = this; - table[index] = this.emptyLow; - table[index + 1] = this.emptyHigh; + table[index] = this.empty; ++this.generation; this.size--; return true; @@ -258,13 +212,8 @@ export abstract class HashTableBase { } private clearTable() { - const { table, entryStride, emptyLow, emptyHigh } = this; - const length = table.length; - - for (let h = 0; h < length; h += entryStride) { - table[h] = emptyLow; - table[h + 1] = emptyHigh; - } + const { table, empty } = this; + table.fill(empty); } clear() { @@ -287,42 +236,37 @@ export abstract class HashTableBase { return false; } - protected swapPending(table: Uint32Array, offset: number) { - const tempLow = pendingLow; - const tempHigh = pendingHigh; + protected swapPending(table: BigUint64Array, offset: number) { + const temp = pending; this.storePending(table, offset); - table[offset] = tempLow; - table[offset + 1] = tempHigh; + table[offset] = temp; } - protected storePending(table: Uint32Array, offset: number) { - pendingLow = table[offset]; - pendingHigh = table[offset + 1]; + protected storePending(table: BigUint64Array, offset: number) { + pending = table[offset]; } protected backupPending() { - backupPendingLow = pendingLow; - backupPendingHigh = pendingHigh; + backupPending = pending; } protected restorePending() { - pendingLow = backupPendingLow; - pendingHigh = backupPendingHigh; + pending = backupPending; } private tryToInsert() { if (DEBUG) { - console.log(`tryToInsert: ${pendingLow}, ${pendingHigh}`); + console.log(`tryToInsert: ${pending}`); } let attempt = 0; - const { emptyLow, emptyHigh, maxAttempts, table } = this; + const { empty, maxAttempts, table } = this; const numHashes = this.hashSeeds.length; let tableIndex = Math.floor(Math.random() * numHashes); while (true) { - const h = this.getHash(tableIndex, pendingLow, pendingHigh); + const h = this.getHash(tableIndex, pending); this.swapPending(table, h); - if (pendingLow === emptyLow && pendingHigh === emptyHigh) { + if (pending === empty) { return true; } if (++attempt === maxAttempts) { @@ -338,24 +282,23 @@ export abstract class HashTableBase { private allocate(tableSize: number) { this.tableSize = tableSize; const { entryStride } = this; - this.table = new Uint32Array(tableSize * entryStride); + this.table = new BigUint64Array(tableSize * entryStride); this.maxAttempts = tableSize; this.clearTable(); this.capacity = tableSize * this.loadFactor; - this.mungedEmptyKey = -1; + this.mungedEmptyKey = undefined; } - private rehash(oldTable: Uint32Array, tableSize: number) { + private rehash(oldTable: BigUint64Array, tableSize: number) { if (DEBUG) { console.log("rehash begin"); } this.allocate(tableSize); this.updateHashFunctions(this.hashSeeds.length); - const { emptyLow, emptyHigh, entryStride } = this; + const { empty, entryStride } = this; for (let h = 0, length = oldTable.length; h < length; h += entryStride) { - const low = oldTable[h]; - const high = oldTable[h + 1]; - if (low !== emptyLow || high !== emptyHigh) { + const key = oldTable[h]; + if (key !== empty) { this.storePending(oldTable, h); if (!this.tryToInsert()) { if (DEBUG) { @@ -400,7 +343,7 @@ export abstract class HashTableBase { protected insertInternal() { ++this.generation; - if (pendingLow === this.emptyLow && pendingHigh === this.emptyHigh) { + if (pending === this.empty) { this.chooseAnotherEmptyKey(); } @@ -419,136 +362,95 @@ export abstract class HashTableBase { } export class HashSetUint64 extends HashTableBase { - add(x: Uint64) { - const { low, high } = x; - if (this.hasPair(low, high)) { + add(x: bigint) { + if (this.has(x)) { return false; } if (DEBUG) { - console.log(`add: ${low},${high}`); + console.log(`add: ${x}`); } - pendingLow = low; - pendingHigh = high; + pending = x; this.insertInternal(); return true; } /** * Iterates over the keys. - * Creates a new Uint64 object at every iteration (otherwise spread and Array.from() fail) */ [Symbol.iterator]() { - return this.unsafeKeys(); + return this.keys(); } } -HashSetUint64.prototype.entryStride = 2; +HashSetUint64.prototype.entryStride = 1; // Value that needs to be inserted. Temporary variables used during insert. These can safely be // global because control never leaves functions defined in this module while these are in use. -let pendingValueLow = 0; -let pendingValueHigh = 0; -let backupPendingValueLow = 0; -let backupPendingValueHigh = 0; +let pendingValue = 0n; +let backupPendingValue = 0n; export class HashMapUint64 extends HashTableBase { - set(key: Uint64, value: Uint64) { - const { low, high } = key; - if (this.hasPair(low, high)) { + set(key: bigint, value: bigint) { + if (this.has(key)) { return false; } if (DEBUG) { - console.log(`add: ${low},${high} -> ${value.low},${value.high}`); + console.log(`add: ${key} -> ${value}`); } - pendingLow = low; - pendingHigh = high; - pendingValueLow = value.low; - pendingValueHigh = value.high; + pending = key; + pendingValue = value; this.insertInternal(); return true; } - get(key: Uint64, value: Uint64): boolean { + get(key: bigint): bigint | undefined { const h = this.indexOf(key); if (h === -1) { - return false; + return undefined; } - const { table } = this; - value.low = table[h + 2]; - value.high = table[h + 3]; - return true; + return this.table[h + 1]; } - protected swapPending(table: Uint32Array, offset: number) { - const tempLow = pendingValueLow; - const tempHigh = pendingValueHigh; + protected swapPending(table: BigUint64Array, offset: number) { + const temp = pendingValue; super.swapPending(table, offset); - table[offset + 2] = tempLow; - table[offset + 3] = tempHigh; + table[offset + 1] = temp; } - protected storePending(table: Uint32Array, offset: number) { + protected storePending(table: BigUint64Array, offset: number) { super.storePending(table, offset); - pendingValueLow = table[offset + 2]; - pendingValueHigh = table[offset + 3]; + pendingValue = table[offset + 1]; } protected backupPending() { super.backupPending(); - backupPendingValueLow = pendingValueLow; - backupPendingValueHigh = pendingValueHigh; + backupPendingValue = pendingValue; } protected restorePending() { super.restorePending(); - pendingValueLow = backupPendingValueLow; - pendingValueHigh = backupPendingValueHigh; + pendingValue = backupPendingValue; } /** - * Iterates over entries. The same temporary value will be modified and yielded at every - * iteration. + * Iterates over entries. */ [Symbol.iterator]() { - return this.unsafeEntries(); + return this.entries(); } /** * Iterates over entries. - * Creates new Uint64 objects at every iteration (otherwise spread and Array.from() fail) */ *entries() { - const { emptyLow, emptyHigh, entryStride } = this; + const { empty, entryStride } = this; const { table } = this; for (let i = 0, length = table.length; i < length; i += entryStride) { - const low = table[i]; - const high = table[i + 1]; - if (low !== emptyLow || high !== emptyHigh) { - const key = new Uint64(low, high); - const value = new Uint64(table[i + 2], table[i + 3]); + const key = table[i]; + if (key !== empty) { + const value = table[i + 1]; yield [key, value]; } } } - - /** - * Iterates over entries. The same temporary value will be modified and yielded at every - * iteration. - */ - *unsafeEntries(temp: [Uint64, Uint64] = [new Uint64(), new Uint64()]) { - const { emptyLow, emptyHigh, entryStride } = this; - const { table } = this; - const [key, value] = temp; - for (let i = 0, length = table.length; i < length; i += entryStride) { - const low = table[i]; - const high = table[i + 1]; - if (low !== emptyLow || high !== emptyHigh) { - key.low = low; - key.high = high; - value.low = table[i + 2]; - value.high = table[i + 3]; - yield temp; - } - } - } } -HashMapUint64.prototype.entryStride = 4; +HashMapUint64.prototype.entryStride = 2; diff --git a/src/gpu_hash/shader.browser_test.ts b/src/gpu_hash/shader.browser_test.ts index d1fcfa826b..4baad45a94 100644 --- a/src/gpu_hash/shader.browser_test.ts +++ b/src/gpu_hash/shader.browser_test.ts @@ -22,9 +22,9 @@ import { HashMapShaderManager, HashSetShaderManager, } from "#src/gpu_hash/shader.js"; +import { randomUint64 } from "#src/util/bigint.js"; import { DataType } from "#src/util/data_type.js"; import { getRandomUint32 } from "#src/util/random.js"; -import { Uint64 } from "#src/util/uint64.js"; import { fragmentShaderTest } from "#src/webgl/shader_testing.js"; const COUNT = 100; @@ -65,11 +65,14 @@ describe("gpu_hash.shader", () => { "outputValue = hashCombine(hashSeed, inputValue);", ); for (let k = 0; k < 20; ++k) { - const inputValue = Uint64.random(); + const inputValue = randomUint64(); const hashSeed = getRandomUint32(); tester.execute({ hashSeed, inputValue }); - let expected = hashCombine(hashSeed, inputValue.low); - expected = hashCombine(expected, inputValue.high); + let expected = hashCombine( + hashSeed, + Number(inputValue & 0xffffffffn), + ); + expected = hashCombine(expected, Number(inputValue >> 32n)); expect(tester.values.outputValue).toEqual(expected); } }, @@ -91,27 +94,25 @@ describe("gpu_hash.shader", () => { const gpuHashTable = tester.registerDisposer( GPUHashTable.get(gl, hashTable), ); - const testValues = new Array(); + const testValues = new Array(); while (testValues.length < COUNT) { - const x = Uint64.random(); + const x = randomUint64(); if (hashTable.has(x)) { continue; } testValues.push(x); hashTable.add(x); } - const notPresentValues = new Array(); - notPresentValues.push( - new Uint64(hashTable.emptyLow, hashTable.emptyHigh), - ); + const notPresentValues = new Array(); + notPresentValues.push(hashTable.empty); while (notPresentValues.length < COUNT) { - const x = Uint64.random(); + const x = randomUint64(); if (hashTable.has(x)) { continue; } notPresentValues.push(x); } - function checkPresent(x: Uint64) { + function checkPresent(x: bigint) { hashTableShaderManager.enable(gl, shader, gpuHashTable); tester.execute({ inputValue: x }); return tester.values.outputValue; @@ -145,38 +146,33 @@ describe("gpu_hash.shader", () => { const gpuHashTable = tester.registerDisposer( GPUHashTable.get(gl, hashTable), ); - const testValues = new Array(); + const testValues = new Array(); while (testValues.length < COUNT) { - const x = Uint64.random(); + const x = randomUint64(); if (hashTable.has(x)) { continue; } testValues.push(x); - hashTable.set(x, Uint64.random()); + hashTable.set(x, randomUint64()); } - const notPresentValues = new Array(); - notPresentValues.push( - new Uint64(hashTable.emptyLow, hashTable.emptyHigh), - ); + const notPresentValues = new Array(); + notPresentValues.push(hashTable.empty); while (notPresentValues.length < COUNT) { - const x = Uint64.random(); + const x = randomUint64(); if (hashTable.has(x)) { continue; } notPresentValues.push(x); } - function checkPresent(x: Uint64) { + function checkPresent(x: bigint) { shaderManager.enable(gl, shader, gpuHashTable); tester.execute({ key: x }); const { values } = tester; - const expectedValue = new Uint64(); - const expectedHas = hashTable.get(x, expectedValue); + const value = hashTable.get(x); const has = values.isPresent; - expect(has, `x=${x}`).toBe(expectedHas); + expect(value !== undefined, `x=${x}`).toBe(values.isPresent); if (has) { - expect(values.outputValue.toString(), `x=${x}`).toBe( - expectedValue.toString(), - ); + expect(values.outputValue, `x=${x}`).toBe(value); } } testValues.forEach((x, i) => { diff --git a/src/kvstore/ocdbt/list_versions.ts b/src/kvstore/ocdbt/list_versions.ts index 2faf417986..4547e0eadc 100644 --- a/src/kvstore/ocdbt/list_versions.ts +++ b/src/kvstore/ocdbt/list_versions.ts @@ -24,12 +24,12 @@ import type { VersionQuery, } from "#src/kvstore/ocdbt/version_tree.js"; import { - bigintCompare, findLeafVersionIndexByLowerBound, findVersionNodeIndexByLowerBound, findVersionNodeIndexByUpperBound, validateVersionTreeNodeReference, } from "#src/kvstore/ocdbt/version_tree.js"; +import { bigintCompare } from "#src/util/bigint.js"; import type { ProgressOptions } from "#src/util/progress_listener.js"; const DEBUG = false; diff --git a/src/kvstore/ocdbt/version_tree.ts b/src/kvstore/ocdbt/version_tree.ts index 955687f36f..85c61bf769 100644 --- a/src/kvstore/ocdbt/version_tree.ts +++ b/src/kvstore/ocdbt/version_tree.ts @@ -39,6 +39,7 @@ import { import type { Config } from "#src/kvstore/ocdbt/manifest.js"; import type { VersionSpecifier } from "#src/kvstore/ocdbt/version_specifier.js"; import { binarySearch, binarySearchLowerBound } from "#src/util/array.js"; +import { bigintCompare } from "#src/util/bigint.js"; export type GenerationNumber = bigint; export type GenerationIndex = bigint; @@ -350,10 +351,6 @@ export function compareVersionSpecToVersion( : bigintCompare(versionSpec.commitTime, ref.commitTime); } -export function bigintCompare(a: bigint, b: bigint) { - return a < b ? -1 : a > b ? 1 : 0; -} - export function findLeafVersion( generationIndex: GenerationIndex, versions: BtreeGenerationReference[], diff --git a/src/layer/index.ts b/src/layer/index.ts index fea87063da..5c57be7c18 100644 --- a/src/layer/index.ts +++ b/src/layer/index.ts @@ -106,7 +106,6 @@ import { removeSignalBinding, } from "#src/util/signal_binding_updater.js"; import type { Trackable } from "#src/util/trackable.js"; -import { Uint64 } from "#src/util/uint64.js"; import { kEmptyFloat32Vec } from "#src/util/vector.js"; import type { WatchableVisibilityPriority } from "#src/visibility_priority/frontend.js"; import type { DependentViewContext } from "#src/widget/dependent_view_widget.js"; @@ -1076,7 +1075,7 @@ export class LayerManager extends RefCounted { export interface PickState { pickedRenderLayer: RenderLayer | null; - pickedValue: Uint64; + pickedValue: bigint; pickedOffset: number; pickedAnnotationLayer: AnnotationLayerState | undefined; pickedAnnotationId: string | undefined; @@ -1095,7 +1094,7 @@ export class MouseSelectionState implements PickState { active = false; displayDimensions: DisplayDimensions | undefined = undefined; pickedRenderLayer: RenderLayer | null = null; - pickedValue = new Uint64(0, 0); + pickedValue = 0n; pickedOffset = 0; pickedAnnotationLayer: AnnotationLayerState | undefined = undefined; pickedAnnotationId: string | undefined = undefined; diff --git a/src/layer/segmentation/index.ts b/src/layer/segmentation/index.ts index 8551e8c576..f524cf0d7c 100644 --- a/src/layer/segmentation/index.ts +++ b/src/layer/segmentation/index.ts @@ -120,13 +120,13 @@ import { RefCounted } from "#src/util/disposable.js"; import type { vec3, vec4 } from "#src/util/geom.js"; import { parseArray, + parseUint64, verifyFiniteNonNegativeFloat, verifyObjectAsMap, verifyOptionalObjectProperty, verifyString, } from "#src/util/json.js"; import { Signal } from "#src/util/signal.js"; -import { Uint64 } from "#src/util/uint64.js"; import { makeWatchableShaderError } from "#src/webgl/dynamic_shader.js"; import type { DependentViewContext } from "#src/widget/dependent_view_widget.js"; import { registerLayerShaderControlsTool } from "#src/widget/shader_controls.js"; @@ -221,7 +221,7 @@ export class SegmentationUserLayerGroupState if (hidden) { stringValue = stringValue.substring(1); } - const id = Uint64.parseString(stringValue, 10); + const id = parseUint64(stringValue); const segmentId = segmentEquivalences.get(id); selectedSegments.add(segmentId); if (!hidden) { @@ -322,8 +322,8 @@ export class SegmentationUserLayerColorGroupState parseRGBColorSpecification(String(x)), ); for (const [idStr, colorVec] of result) { - const id = Uint64.parseString(String(idStr)); - const color = new Uint64(packColor(colorVec)); + const id = parseUint64(idStr); + const color = BigInt(packColor(colorVec)); this.segmentStatedColors.set(id, color); } }, @@ -338,8 +338,8 @@ export class SegmentationUserLayerColorGroupState const { segmentStatedColors } = this; if (segmentStatedColors.size > 0) { const j: any = (x[json_keys.SEGMENT_STATED_COLORS_JSON_KEY] = {}); - for (const [key, value] of segmentStatedColors.unsafeEntries()) { - j[key.toString()] = serializeColor(unpackRGB(value.low)); + for (const [key, value] of segmentStatedColors) { + j[key.toString()] = serializeColor(unpackRGB(Number(value))); } } return x; @@ -534,15 +534,15 @@ class SegmentationUserLayerDisplayState implements SegmentationDisplayState { shaderError = makeWatchableShaderError(); renderScaleHistogram = new RenderScaleHistogram(); renderScaleTarget = trackableRenderScaleTarget(1); - selectSegment: (id: Uint64, pin: boolean | "toggle") => void; + selectSegment: (id: bigint, pin: boolean | "toggle") => void; transparentPickEnabled: TrackableBoolean; baseSegmentColoring = new TrackableBoolean(false, false); baseSegmentHighlighting = new TrackableBoolean(false, false); useTempSegmentStatedColors2d: SharedWatchableValue; - filterBySegmentLabel: (id: Uint64) => void; + filterBySegmentLabel: (id: bigint) => void; - moveToSegment = (id: Uint64) => { + moveToSegment = (id: bigint) => { this.layer.moveToSegment(id); }; @@ -589,18 +589,18 @@ export class SegmentationUserLayer extends Base { segmentQueryFocusTime = new WatchableValue(Number.NEGATIVE_INFINITY); - selectSegment = (id: Uint64, pin: boolean | "toggle") => { + selectSegment = (id: bigint, pin: boolean | "toggle") => { this.manager.root.selectionState.captureSingleLayerState( this, (state) => { - state.value = id.clone(); + state.value = id; return true; }, pin, ); }; - filterBySegmentLabel = (id: Uint64) => { + filterBySegmentLabel = (id: bigint) => { const augmented = augmentSegmentId(this.displayState, id); const { label } = augmented; if (!label) return; @@ -616,8 +616,8 @@ export class SegmentationUserLayer extends Base { displayState = new SegmentationUserLayerDisplayState(this); - anchorSegment = new TrackableValue(undefined, (x) => - x === undefined ? undefined : Uint64.parseString(x), + anchorSegment = new TrackableValue(undefined, (x) => + x === undefined ? undefined : parseUint64(x), ); constructor(managedLayer: Borrowed) { @@ -1075,8 +1075,7 @@ export class SegmentationUserLayer extends Base { if (value == null) { return value; } - // Must copy, because `value` may be a temporary Uint64 returned by PickIDManager. - return maybeAugmentSegmentId(this.displayState, value, /*mustCopy=*/ true); + return maybeAugmentSegmentId(this.displayState, value); } handleAction(action: string, context: SegmentationActionContext) { @@ -1120,13 +1119,12 @@ export class SegmentationUserLayer extends Base { } selectionStateFromJson(state: this["selectionState"], json: any) { super.selectionStateFromJson(state, json); - const v = new Uint64(); let { value } = state; if (typeof value === "number") value = value.toString(); - if (typeof value !== "string" || !v.tryParseString(value)) { + try { + state.value = parseUint64(value); + } catch { state.value = undefined; - } else { - state.value = v; } } selectionStateToJson(state: this["selectionState"], forPython: boolean): any { @@ -1142,7 +1140,7 @@ export class SegmentationUserLayer extends Base { } else { json.value = (value.value || value.key).toString(); } - } else if (value instanceof Uint64) { + } else if (typeof value === "bigint") { json.value = value.toString(); } return json; @@ -1154,15 +1152,18 @@ export class SegmentationUserLayer extends Base { context: DependentViewContext, ): boolean { const { value } = state; - let id: Uint64; + let id: bigint; if (typeof value === "number" || typeof value === "string") { - id = new Uint64(); - if (!id.tryParseString(value.toString())) return false; + try { + id = parseUint64(value); + } catch { + return false; + } } - if (value instanceof Uint64) { - id = value.clone(); + if (typeof value === "bigint") { + id = value; } else if (value instanceof Uint64MapEntry) { - id = value.key.clone(); + id = value.key; } else { return false; } @@ -1248,7 +1249,7 @@ export class SegmentationUserLayer extends Base { return displayed; } - moveToSegment(id: Uint64) { + moveToSegment(id: bigint) { for (const layer of this.renderLayers) { if ( !(layer instanceof MultiscaleMeshLayer || layer instanceof MeshLayer) diff --git a/src/mesh/backend.ts b/src/mesh/backend.ts index fe8191a1b5..7f81d4434d 100644 --- a/src/mesh/backend.ts +++ b/src/mesh/backend.ts @@ -53,7 +53,6 @@ import { verifyObjectProperty, verifyStringArray, } from "#src/util/json.js"; -import { Uint64 } from "#src/util/uint64.js"; import { zorder3LessThan } from "#src/util/zorder.js"; import { getBasePriority, @@ -72,13 +71,13 @@ export type FragmentId = string; // Chunk that contains the list of fragments that make up a single object. export class ManifestChunk extends Chunk { - objectId = new Uint64(); + objectId: bigint = 0n; fragmentIds: FragmentId[] | null; // We can't save a reference to objectId, because it may be a temporary // object. - initializeManifestChunk(key: string, objectId: Uint64) { + initializeManifestChunk(key: string, objectId: bigint) { super.initialize(key); - this.objectId.assign(objectId); + this.objectId = objectId; } freeSystemMemory() { @@ -384,7 +383,7 @@ export class MeshSource extends ChunkSource { fragmentSource.meshSource = this; } - getChunk(objectId: Uint64) { + getChunk(objectId: bigint) { const key = getObjectKey(objectId); let chunk = this.chunks.get(key); if (chunk === undefined) { @@ -498,13 +497,13 @@ export class MeshLayer extends withSegmentationLayerBackendState( // Chunk that contains the list of fragments that make up a single object. export class MultiscaleManifestChunk extends Chunk { - objectId = new Uint64(); + objectId: bigint = 0n; manifest: MultiscaleMeshManifest | undefined; // We can't save a reference to objectId, because it may be a temporary // object. - initializeManifestChunk(key: string, objectId: Uint64) { + initializeManifestChunk(key: string, objectId: bigint) { super.initialize(key); - this.objectId.assign(objectId); + this.objectId = objectId; } freeSystemMemory() { @@ -584,7 +583,7 @@ export class MultiscaleMeshSource extends ChunkSource { fragmentSource.meshSource = this; } - getChunk(objectId: Uint64) { + getChunk(objectId: bigint) { const key = getObjectKey(objectId); let chunk = this.chunks.get(key); if (chunk === undefined) { diff --git a/src/mesh/frontend.ts b/src/mesh/frontend.ts index bccb3c9ec6..73c146a2ab 100644 --- a/src/mesh/frontend.ts +++ b/src/mesh/frontend.ts @@ -66,7 +66,6 @@ import { vec3, } from "#src/util/geom.js"; import * as matrix from "#src/util/matrix.js"; -import type { Uint64 } from "#src/util/uint64.js"; import { GLBuffer } from "#src/webgl/buffer.js"; import type { GL } from "#src/webgl/context.js"; import { parameterizedEmitterDependentShaderGetter } from "#src/webgl/dynamic_shader.js"; @@ -587,7 +586,7 @@ export class MeshLayer extends PerspectiveViewRenderLayer> 32n); this.pickData[index] = data; return pickID; } @@ -101,9 +100,10 @@ export class PickIDManager { `offset=${pickedOffset}`, ); } - const { pickedValue } = mouseState; - pickedValue.low = values[valuesOffset + 1]; - pickedValue.high = values[valuesOffset + 2]; + const pickedValue = (mouseState.pickedValue = uint64FromLowHigh( + values[valuesOffset + 1], + values[valuesOffset + 2], + )); mouseState.pickedAnnotationId = undefined; mouseState.pickedAnnotationLayer = undefined; mouseState.pickedAnnotationBuffer = undefined; diff --git a/src/python_integration/api.ts b/src/python_integration/api.ts index fa28d27e7c..359c74bd1c 100644 --- a/src/python_integration/api.ts +++ b/src/python_integration/api.ts @@ -18,6 +18,7 @@ import { debounce, throttle } from "lodash-es"; import { StatusMessage } from "#src/status.js"; import { RefCounted } from "#src/util/disposable.js"; import { HttpError } from "#src/util/http_request.js"; +import { bigintToStringJsonReplacer } from "#src/util/json.js"; import { getRandomHexString } from "#src/util/random.js"; import type { Trackable } from "#src/util/trackable.js"; import { getCachedJson } from "#src/util/trackable.js"; @@ -85,7 +86,10 @@ export class ClientStateSynchronizer extends RefCounted { return; } const newStateJson = getCachedJson(this.state).value; - const newStateEncoded = JSON.stringify(newStateJson); + const newStateEncoded = JSON.stringify( + newStateJson, + bigintToStringJsonReplacer, + ); if (newStateEncoded === this.lastServerState) { // Avoid sending back the exact same state just received from or sent to the server. This // is also important for making things work in the presence of multiple simultaneous @@ -230,14 +234,14 @@ export class Client { sendActionNotification(action: string, state: any) { fetch(this.urls.action, { method: "POST", - body: JSON.stringify({ action, state }), + body: JSON.stringify({ action, state }, bigintToStringJsonReplacer), }); } sendVolumeInfoNotification(requestId: string, info: any) { fetch(`${this.urls.volumeInfo}/${requestId}/info`, { method: "POST", - body: JSON.stringify(info), + body: JSON.stringify(info, bigintToStringJsonReplacer), }); } @@ -245,7 +249,7 @@ export class Client { const { data, ...params } = info; fetch( `${this.urls.volumeInfo}/${requestId}/chunk?p=${encodeURIComponent( - JSON.stringify(params), + JSON.stringify(params, bigintToStringJsonReplacer), )}`, { method: "POST", body: data }, ); diff --git a/src/python_integration/remote_actions.ts b/src/python_integration/remote_actions.ts index 274ae9b9d6..035fc88af0 100644 --- a/src/python_integration/remote_actions.ts +++ b/src/python_integration/remote_actions.ts @@ -22,7 +22,10 @@ import { debounce } from "lodash-es"; import { TrackableValue } from "#src/trackable_value.js"; import { RefCounted } from "#src/util/disposable.js"; import { registerActionListener } from "#src/util/event_action_map.js"; -import { verifyStringArray } from "#src/util/json.js"; +import { + bigintToStringJsonReplacer, + verifyStringArray, +} from "#src/util/json.js"; import { Signal } from "#src/util/signal.js"; import { getCachedJson } from "#src/util/trackable.js"; import type { Viewer } from "#src/viewer.js"; @@ -78,7 +81,7 @@ export class RemoteActionHandler extends RefCounted { actionState.viewerState = getCachedJson(this.viewer.state).value; this.sendActionRequested.dispatch( action, - JSON.parse(JSON.stringify(actionState)), + JSON.parse(JSON.stringify(actionState, bigintToStringJsonReplacer)), ); } } diff --git a/src/python_integration/screenshots.ts b/src/python_integration/screenshots.ts index ce8233d948..c2d7ebe016 100644 --- a/src/python_integration/screenshots.ts +++ b/src/python_integration/screenshots.ts @@ -25,7 +25,10 @@ import { import { toBase64 } from "#src/util/base64.js"; import { RefCounted } from "#src/util/disposable.js"; import { convertEndian32, Endianness } from "#src/util/endian.js"; -import { verifyOptionalString } from "#src/util/json.js"; +import { + bigintToStringJsonReplacer, + verifyOptionalString, +} from "#src/util/json.js"; import { Signal } from "#src/util/signal.js"; import { getCachedJson } from "#src/util/trackable.js"; import { ScreenshotMode } from "#src/util/trackable_screenshot_mode.js"; @@ -138,7 +141,10 @@ export class ScreenshotHandler extends RefCounted { JSON.stringify(getCachedJson(this.viewer.state).value), ), selectedValues: JSON.parse( - JSON.stringify(this.viewer.layerSelectedValues), + JSON.stringify( + this.viewer.layerSelectedValues, + bigintToStringJsonReplacer, + ), ), screenshotStatistics: { id: requestId, chunkSources: rows, total }, }; @@ -207,7 +213,9 @@ export class ScreenshotHandler extends RefCounted { viewerState: JSON.parse( JSON.stringify(getCachedJson(this.viewer.state).value), ), - selectedValues: JSON.parse(JSON.stringify(layerSelectedValues)), + selectedValues: JSON.parse( + JSON.stringify(layerSelectedValues, bigintToStringJsonReplacer), + ), screenshot: { id: requestState, image, diff --git a/src/renderlayer.ts b/src/renderlayer.ts index 2fbea24894..583e2026ae 100644 --- a/src/renderlayer.ts +++ b/src/renderlayer.ts @@ -46,7 +46,6 @@ import { RefCounted } from "#src/util/disposable.js"; import { mat4 } from "#src/util/geom.js"; import { MessageList, MessageSeverity } from "#src/util/message_list.js"; import { NullarySignal, Signal } from "#src/util/signal.js"; -import type { Uint64 } from "#src/util/uint64.js"; import { VisibilityPriorityAggregator } from "#src/visibility_priority/frontend.js"; import type { RPC } from "#src/worker_rpc.js"; import { registerSharedObjectOwner, SharedObject } from "#src/worker_rpc.js"; @@ -95,7 +94,7 @@ export class RenderLayer extends RefCounted { */ updateMouseState( _mouseState: MouseSelectionState, - _pickedValue: Uint64, + _pickedValue: bigint, _pickedOffset: number, _data: any, ) {} diff --git a/src/segment_color.browser_test.ts b/src/segment_color.browser_test.ts index e8e34eff81..63accea892 100644 --- a/src/segment_color.browser_test.ts +++ b/src/segment_color.browser_test.ts @@ -19,8 +19,8 @@ import { SegmentColorHash, SegmentColorShaderManager, } from "#src/segment_color.js"; +import { randomUint64 } from "#src/util/bigint.js"; import { DataType } from "#src/util/data_type.js"; -import { Uint64 } from "#src/util/uint64.js"; import { fragmentShaderTest } from "#src/webgl/shader_testing.js"; describe("segment_color", () => { @@ -44,7 +44,7 @@ outB = color.b; shader.bind(); shaderManager.enable(gl, shader, colorHash.value); - function testValue(x: Uint64) { + function testValue(x: bigint) { tester.execute({ inputValue: x }); const expected = new Float32Array(3); colorHash.compute(expected, x); @@ -54,11 +54,11 @@ outB = color.b; expect(values.outB).toBeCloseTo(expected[2]); } - testValue(Uint64.parseString("0")); - testValue(Uint64.parseString("8")); + testValue(0n); + testValue(8n); const COUNT = 100; for (let iter = 0; iter < COUNT; ++iter) { - const x = Uint64.random(); + const x = randomUint64(); testValue(x); } }, diff --git a/src/segment_color.ts b/src/segment_color.ts index db966a9f4a..9cc2ff2f19 100644 --- a/src/segment_color.ts +++ b/src/segment_color.ts @@ -25,7 +25,6 @@ import { hsvToRgb } from "#src/util/colorspace.js"; import { getRandomUint32 } from "#src/util/random.js"; import { NullarySignal } from "#src/util/signal.js"; import type { Trackable } from "#src/util/trackable.js"; -import type { Uint64 } from "#src/util/uint64.js"; import type { GL } from "#src/webgl/context.js"; import type { ShaderBuilder, ShaderProgram } from "#src/webgl/shader.js"; import { glsl_hsvToRgb, glsl_uint64 } from "#src/webgl/shader_lib.js"; @@ -95,16 +94,16 @@ export class SegmentColorHash implements Trackable { } } - compute(out: Float32Array, x: Uint64) { - let h = hashCombine(this.hashSeed, x.low); - h = hashCombine(h, x.high); + compute(out: Float32Array, x: bigint) { + let h = hashCombine(this.hashSeed, Number(x & 0xffffffffn)); + h = hashCombine(h, Number(x >> 32n)); const c0 = (h & 0xff) / 255; const c1 = ((h >> 8) & 0xff) / 255; hsvToRgb(out, c0, 0.5 + 0.5 * c1, 1.0); return out; } - computeCssColor(x: Uint64) { + computeCssColor(x: bigint) { this.compute(tempColor, x); return getCssColor(tempColor); } diff --git a/src/segmentation_display_state/base.ts b/src/segmentation_display_state/base.ts index 6b09716531..ea37ea4f52 100644 --- a/src/segmentation_display_state/base.ts +++ b/src/segmentation_display_state/base.ts @@ -20,7 +20,6 @@ import type { SharedWatchableValue } from "#src/shared_watchable_value.js"; import type { Uint64OrderedSet } from "#src/uint64_ordered_set.js"; import type { Uint64Set } from "#src/uint64_set.js"; import type { RefCounted } from "#src/util/disposable.js"; -import type { Uint64 } from "#src/util/uint64.js"; export interface VisibleSegmentsState { visibleSegments: Uint64Set; @@ -73,15 +72,14 @@ export function onTemporaryVisibleSegmentsStateChanged( } /** - * Returns a string key for identifying a uint64 object id. This is faster than - * Uint64.prototype.toString(). + * Returns a string key for identifying a uint64 object id. */ -export function getObjectKey(objectId: Uint64): string { - return `${objectId.low},${objectId.high}`; +export function getObjectKey(objectId: bigint): string { + return objectId.toString(); } -function isHighBitSegment(segmentId: Uint64): boolean { - return segmentId.high >>> 31 ? true : false; +function isHighBitSegment(segmentId: bigint): boolean { + return (segmentId & 0x8000000000000000n) !== 0n; } export function getVisibleSegments(state: VisibleSegmentsState) { @@ -98,13 +96,13 @@ export function getSegmentEquivalences(state: VisibleSegmentsState) { export function forEachVisibleSegment( state: VisibleSegmentsState, - callback: (objectId: Uint64, rootObjectId: Uint64) => void, + callback: (objectId: bigint, rootObjectId: bigint) => void, ) { const visibleSegments = getVisibleSegments(state); const segmentEquivalences = getSegmentEquivalences(state); const equivalencePolicy = segmentEquivalences.disjointSets.visibleSegmentEquivalencePolicy.value; - for (const rootObjectId of visibleSegments.unsafeKeys()) { + for (const rootObjectId of visibleSegments.keys()) { if ( equivalencePolicy & VisibleSegmentEquivalencePolicy.NONREPRESENTATIVE_EXCLUDED diff --git a/src/segmentation_display_state/frontend.ts b/src/segmentation_display_state/frontend.ts index df36d12496..f9397cae52 100644 --- a/src/segmentation_display_state/frontend.ts +++ b/src/segmentation_display_state/frontend.ts @@ -46,14 +46,15 @@ import type { import { observeWatchable, registerNestedSync } from "#src/trackable_value.js"; import { isWithinSelectionPanel } from "#src/ui/selection_details.js"; import type { Uint64Map } from "#src/uint64_map.js"; +import { wrapSigned32BitIntegerToUint64 } from "#src/util/bigint.js"; import { setClipboard } from "#src/util/clipboard.js"; import { useWhiteBackground } from "#src/util/color.js"; import { RefCounted } from "#src/util/disposable.js"; import { measureElementClone } from "#src/util/dom.js"; import type { vec3 } from "#src/util/geom.js"; import { kOneVec, vec4 } from "#src/util/geom.js"; +import { parseUint64 } from "#src/util/json.js"; import { NullarySignal } from "#src/util/signal.js"; -import { Uint64 } from "#src/util/uint64.js"; import { withSharedVisibility } from "#src/visibility_priority/frontend.js"; import { makeCopyButton } from "#src/widget/copy_button.js"; import { makeEyeButton } from "#src/widget/eye_button.js"; @@ -62,8 +63,8 @@ import { makeStarButton } from "#src/widget/star_button.js"; export class Uint64MapEntry { constructor( - public key: Uint64, - public value?: Uint64, + public key: bigint, + public value?: bigint, public label?: string | undefined, ) {} toString() { @@ -80,8 +81,8 @@ export class Uint64MapEntry { } export class SegmentSelectionState extends RefCounted { - selectedSegment = new Uint64(); - baseSelectedSegment = new Uint64(); + selectedSegment = 0n; + baseSelectedSegment = 0n; hasSelectedSegment = false; changed = new NullarySignal(); @@ -94,36 +95,30 @@ export class SegmentSelectionState extends RefCounted { } set( - value: number | Uint64MapEntry | Uint64 | null | undefined, + value: number | Uint64MapEntry | bigint | null | undefined, hideSegmentZero = false, ) { const { selectedSegment, baseSelectedSegment } = this; - let newLow = 0; - let newHigh = 0; - let newBaseLow = 0; - let newBaseHigh = 0; + let newId = 0n; + let newBaseId = 0n; let hasSelectedSegment: boolean; if (value == null) { hasSelectedSegment = false; } else if (typeof value === "number") { - newLow = newBaseLow = value >>> 0; - newHigh = newBaseHigh = value < 0 ? 0xffffffff : 0; + newId = newBaseId = wrapSigned32BitIntegerToUint64(value); hasSelectedSegment = true; } else if (value instanceof Uint64MapEntry) { const valueMapped = value.value || value.key; - newLow = valueMapped.low; - newHigh = valueMapped.high; - newBaseLow = value.key.low; - newBaseHigh = value.key.high; + newId = valueMapped; + newBaseId = value.key; hasSelectedSegment = true; - } else if (value instanceof Uint64) { - newLow = newBaseLow = value.low; - newHigh = newBaseHigh = value.high; + } else if (typeof value === "bigint") { + newId = newBaseId = value; hasSelectedSegment = true; } else { hasSelectedSegment = false; } - if (hideSegmentZero && newLow === 0 && newHigh === 0) { + if (hideSegmentZero && newId === 0n) { hasSelectedSegment = false; } if (!hasSelectedSegment) { @@ -134,22 +129,18 @@ export class SegmentSelectionState extends RefCounted { } else if ( hasSelectedSegment && (!this.hasSelectedSegment || - selectedSegment.low !== newLow || - selectedSegment.high !== newHigh || - baseSelectedSegment.low !== newBaseLow || - baseSelectedSegment.high !== newBaseHigh) + selectedSegment !== newId || + baseSelectedSegment !== newBaseId) ) { - selectedSegment.low = newLow; - selectedSegment.high = newHigh; - baseSelectedSegment.low = newBaseLow; - baseSelectedSegment.high = newBaseHigh; + this.selectedSegment = newId; + this.baseSelectedSegment = newBaseId; this.hasSelectedSegment = true; this.changed.dispatch(); } } - isSelected(value: Uint64) { - return this.hasSelectedSegment && Uint64.equal(value, this.selectedSegment); + isSelected(value: bigint) { + return this.hasSelectedSegment && value === this.selectedSegment; } bindTo( @@ -200,9 +191,9 @@ export interface SegmentationDisplayState { segmentationGroupState: WatchableValueInterface; segmentationColorGroupState: WatchableValueInterface; - selectSegment: (id: Uint64, pin: boolean | "toggle") => void; - filterBySegmentLabel: (id: Uint64) => void; - moveToSegment: (id: Uint64) => void; + selectSegment: (id: bigint, pin: boolean | "toggle") => void; + filterBySegmentLabel: (id: bigint) => void; + moveToSegment: (id: bigint) => void; // Indirect properties hideSegmentZero: WatchableValueInterface; @@ -224,22 +215,21 @@ export function resetTemporaryVisibleSegmentsState( state.temporarySegmentEquivalences.clear(); } -/// Converts a segment id to a Uint64MapEntry or Uint64 (if Uint64MapEntry would add no additional +/// Converts a segment id to a Uint64MapEntry or uint64 (if Uint64MapEntry would add no additional /// information). export function maybeAugmentSegmentId( displayState: SegmentationDisplayState | undefined | null, - value: number | Uint64, - mustCopy = false, -): Uint64 | Uint64MapEntry { - let id: Uint64; - let mappedValue: Uint64; - let mapped: Uint64 | undefined; + value: number | bigint | string, +): bigint | Uint64MapEntry { + let id: bigint; + let mappedValue: bigint; + let mapped: bigint | undefined; if (typeof value === "number") { - id = new Uint64(value >>> 0, value < 0 ? 0xffffffff : 0); + id = wrapSigned32BitIntegerToUint64(value); } else if (typeof value === "string") { - id = Uint64.parseString(value); + id = parseUint64(value); } else { - id = mustCopy ? value.clone() : value; + id = value; } if (displayState == null) return id; const { @@ -248,7 +238,7 @@ export function maybeAugmentSegmentId( } = displayState.segmentationGroupState.value; if (segmentEquivalences.size !== 0) { mappedValue = segmentEquivalences.get(id); - if (Uint64.equal(mappedValue, id)) { + if (mappedValue === id) { mapped = undefined; } else { mapped = mappedValue; @@ -266,11 +256,11 @@ export function maybeAugmentSegmentId( /// Converts a plain segment id to a Uint64MapEntry. export function augmentSegmentId( displayState: SegmentationDisplayState | undefined | null, - value: number | Uint64 | Uint64MapEntry, + value: number | bigint | Uint64MapEntry, ): Uint64MapEntry { if (value instanceof Uint64MapEntry) return value; const newValue = maybeAugmentSegmentId(displayState, value); - if (newValue instanceof Uint64) { + if (typeof newValue === "bigint") { return new Uint64MapEntry(newValue); } return newValue; @@ -426,8 +416,7 @@ function makeRegisterSegmentWidgetEventHandlers( const onMouseEnter = (event: Event) => { const entryElement = event.currentTarget as HTMLElement; const idString = entryElement.dataset.id!; - const id = tempStatedColor; - id.tryParseString(idString); + const id = BigInt(idString); displayState.segmentSelectionState.set(id); if (!isWithinSelectionPanel(entryElement)) { displayState.selectSegment(id, false); @@ -437,8 +426,7 @@ function makeRegisterSegmentWidgetEventHandlers( const selectHandler = (event: Event) => { const entryElement = event.currentTarget as HTMLElement; const idString = entryElement.dataset.id!; - const id = tempStatedColor; - id.tryParseString(idString); + const id = BigInt(idString); displayState.selectSegment( id, isWithinSelectionPanel(entryElement) ? "toggle" : true, @@ -470,8 +458,7 @@ function makeRegisterSegmentWidgetEventHandlers( const visibleCheckboxHandler = (event: Event) => { const entryElement = getEntryElement(event); const idString = entryElement.dataset.id!; - const id = tempStatedColor; - id.tryParseString(idString); + const id = BigInt(idString); const { selectedSegments, visibleSegments } = displayState.segmentationGroupState.value; const shouldBeVisible = !visibleSegments.has(id); @@ -486,8 +473,7 @@ function makeRegisterSegmentWidgetEventHandlers( const filterHandler = (event: Event) => { const entryElement = getEntryElement(event); const idString = entryElement.dataset.id!; - const id = tempStatedColor; - id.tryParseString(idString); + const id = BigInt(idString); displayState.filterBySegmentLabel(id); event.stopPropagation(); }; @@ -504,8 +490,7 @@ function makeRegisterSegmentWidgetEventHandlers( } const entryElement = event.currentTarget as HTMLElement; const idString = entryElement.dataset.id!; - const id = tempStatedColor; - id.tryParseString(idString); + const id = BigInt(idString); displayState.moveToSegment(id); }; @@ -539,8 +524,7 @@ function makeRegisterSegmentWidgetEventHandlers( starButton.addEventListener("click", (event: MouseEvent) => { const entryElement = getEntryElement(event); const idString = entryElement.dataset.id!; - const id = tempStatedColor; - id.tryParseString(idString); + const id = BigInt(idString); const { selectedSegments } = displayState.segmentationGroupState.value; selectedSegments.set(id, !selectedSegments.has(id)); }); @@ -577,7 +561,7 @@ export class SegmentWidgetFactory