@woosh/meep-engine 2.48.22 → 2.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/editor/ecs/component/editors/three/BufferGeometryEditor.js +1 -1
- package/editor/ecs/component/editors/three/MeshEditor.js +1 -1
- package/editor/tools/GridPaintTool.js +1 -1
- package/editor/tools/SelectionTool.js +1 -1
- package/editor/tools/paint/TerrainPaintTool.js +1 -1
- package/editor/view/GridPickCoordinateView.js +1 -1
- package/editor/view/library/MeshLibraryView.js +1 -1
- package/package.json +1 -1
- package/src/core/UUID.js +2 -0
- package/src/core/assert.js +4 -1
- package/src/core/binary/32BitEncoder.js +1 -1
- package/src/core/binary/ctz32.js +1 -1
- package/src/core/binary/operations/bitCount.spec.js +19 -0
- package/src/core/binary/uint82float.spec.js +7 -0
- package/src/core/bvh2/LeafNode.js +2 -2
- package/src/core/bvh2/Node.d.ts +1 -1
- package/src/core/bvh2/Node.js +1 -1
- package/src/core/bvh2/NodeValidator.js +1 -1
- package/src/core/bvh2/bvh3/EBBVHLeafProxy.js +3 -0
- package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.js +5 -4
- package/src/core/bvh2/serialization/deserializeBinaryNode.js +1 -1
- package/src/core/bvh2/serialization/deserializeBinaryNodeFromBinaryBuffer.js +2 -2
- package/src/core/bvh2/serialization/serializeBinaryNode.js +1 -1
- package/src/core/bvh2/serialization/serializeBinaryNodeToBinaryBuffer.js +2 -2
- package/src/core/bvh2/transform/RotationOptimizer.spec.js +161 -155
- package/src/core/cache/Cache.js +4 -2
- package/src/core/codegen/LineBuilder.js +15 -3
- package/src/core/codegen/LineBuilder.spec.js +7 -0
- package/src/core/collection/HashMap.js +491 -236
- package/src/core/collection/HashMap.spec.js +110 -1
- package/src/core/collection/array/{typedArrayToDataType.js → typed/typedArrayToDataType.js} +1 -1
- package/src/core/collection/array/weightedRandomFromArray.spec.js +20 -0
- package/src/core/collection/heap/Uint32Heap.js +1 -0
- package/src/core/debug/matchers/AnyOf.js +1 -2
- package/src/core/events/signal/Signal.js +7 -5
- package/src/core/events/signal/SignalBinding.js +56 -54
- package/src/core/events/signal/SignalFlags.js +7 -0
- package/src/core/function/Functions.js +1 -1
- package/src/core/geom/{Rectangle.js → 2d/Rectangle.js} +5 -5
- package/src/core/geom/{AABB2.js → 2d/aabb/AABB2.js} +10 -78
- package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +19 -0
- package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +42 -0
- package/src/core/geom/2d/aabb/aabb2_contains.js +23 -0
- package/src/core/geom/2d/aabb/aabb2_distance_sqr_to_point.js +25 -0
- package/src/core/geom/2d/aabb/aabb2_distance_to_point.js +17 -0
- package/src/core/geom/2d/aabb/aabb2_distance_to_point.spec.js +17 -0
- package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +18 -0
- package/src/core/geom/2d/aabb/aabb2_signed_distance_sqr_to_point.js +45 -0
- package/src/core/geom/2d/aabb/aabb2_signed_distance_sqr_to_point.spec.js +40 -0
- package/src/core/geom/2d/aabb/aabb2_signed_distance_to_point.js +27 -0
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +4 -0
- package/src/core/geom/2d/quad-tree/PointQuadTree.js +3 -0
- package/src/core/geom/2d/quad-tree/QuadTreeDatum.js +1 -1
- package/src/core/geom/2d/quad-tree/QuadTreeNode.js +3 -3
- package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +6 -8
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +3 -3
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +8 -10
- package/src/core/{bvh2/aabb3 → geom/3d/aabb}/AABB3.d.ts +1 -1
- package/src/core/{bvh2/aabb3 → geom/3d/aabb}/AABB3.js +13 -13
- package/src/core/{bvh2/aabb3 → geom/3d/aabb}/AABB3.spec.js +1 -1
- package/src/core/geom/3d/aabb/aabb3_compute_plane_side.js +17 -15
- package/src/core/geom/3d/aabb/aabb3_compute_plane_side.spec.js +25 -0
- package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +1 -1
- package/src/core/geom/3d/aabb/aabb3_from_v3_array.js +3 -0
- package/src/core/geom/3d/aabb/aabb3_from_v3_array.spec.js +32 -0
- package/src/core/geom/3d/aabb/aabb3_intersects_aabb3.spec.js +115 -0
- package/src/core/geom/3d/aabb/aabb3_raycast.js +6 -1
- package/src/core/geom/3d/aabb/aabb3_signed_distance_sqr_to_point.js +13 -9
- package/src/core/{bvh2/aabb3 → geom/3d/aabb}/serializeAABB3Encoded_v0.js +6 -6
- package/src/core/geom/3d/{CircleMath.js → compute_circle_bounding_box.js} +1 -1
- package/src/core/geom/3d/decompose_matrix_4_array.js +18 -19
- package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +1 -1
- package/src/{engine/graphics/ecs/mesh-v2 → core/geom/3d/matrix}/allocate_transform_m4.js +1 -1
- package/src/core/geom/3d/normal/hemioct/decode_hemioct_to_unit.js +26 -0
- package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +0 -26
- package/src/core/geom/3d/normal/hemioct/unit_hemioct.spec.js +2 -1
- package/src/core/geom/3d/plane/computePlaneLineIntersection.js +51 -0
- package/src/core/geom/3d/plane/computePlanePlaneIntersection.js +77 -0
- package/src/core/geom/3d/plane/computePlaneRayIntersection.js +55 -0
- package/src/core/geom/3d/plane/plane3_computeLineSegmentIntersection.js +50 -0
- package/src/core/geom/3d/plane/planeRayIntersection.js +14 -0
- package/src/core/geom/3d/{tetrahedra/in_sphere_fast.js → sphere/in_sphere3d_fast.js} +1 -1
- package/src/core/geom/3d/{tetrahedra/in_sphere_robust.js → sphere/in_sphere3d_robust.js} +1 -1
- package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +2 -2
- package/src/core/geom/3d/sphere/{sphereIntersectsPoint.js → sphere_intersects_point.js} +7 -4
- package/src/core/geom/3d/sphere/sphere_intersects_point.spec.js +134 -0
- package/src/core/geom/3d/sphere/sphere_intersects_ray.spec.js +49 -0
- package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +11 -7
- package/src/core/geom/3d/tetrahedra/delaunay/{debug_validate_mesh.js → debug/debug_validate_mesh.js} +1 -1
- package/src/core/geom/3d/tetrahedra/delaunay/{push_boundary_with_validation.js → debug/push_boundary_with_validation.js} +1 -1
- package/src/core/geom/3d/tetrahedra/delaunay/{validate_cavity_boundary.js → debug/validate_cavity_boundary.js} +2 -2
- package/src/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +2 -2
- package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +0 -164
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +87 -0
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +81 -0
- package/src/core/geom/{GeometryMath.js → 3d/triangle/rayTriangleIntersection.js} +7 -3
- package/src/core/geom/ConicRay.js +160 -152
- package/src/core/geom/Matrix4.js +2 -0
- package/src/core/geom/Quaternion.js +19 -1
- package/src/core/geom/packing/max-rect/MaxRectangles.js +5 -215
- package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +11 -0
- package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +14 -0
- package/src/core/geom/packing/max-rect/cutArea.js +79 -0
- package/src/core/geom/packing/max-rect/findBestContainer.js +58 -0
- package/src/core/geom/packing/max-rect/packOneBox.js +49 -0
- package/src/core/geom/v3_dot.js +1 -1
- package/src/core/graph/GraphUtils.js +1 -1
- package/src/core/graph/build_face_graph_from_mesh.js +1 -3
- package/src/core/graph/layout/CircleLayout.js +3 -3
- package/src/core/graph/layout/{BoxLayouter.js → box/BoxLayouter.js} +6 -50
- package/src/core/graph/layout/box/applyCentralGravityAABB2.js +29 -0
- package/src/core/json/resolvePath.spec.js +14 -0
- package/src/core/land/reactive/{compiler/ReactiveCompiler.spec.js → compileReactiveExpression.spec.js} +17 -17
- package/src/core/math/random/MersenneTwister.spec.js +19 -0
- package/src/core/math/random/randomGaussian.spec.js +9 -0
- package/src/core/math/statistics/computeStatisticalMean.js +2 -2
- package/src/core/model/node-graph/visual/NodeVisualData.js +1 -1
- package/src/core/model/reactive/model/arithmetic/ReactiveAdd.js +1 -1
- package/src/core/model/reactive/model/arithmetic/ReactiveDivide.js +3 -1
- package/src/core/model/reactive/model/arithmetic/ReactiveMultiply.js +1 -1
- package/src/core/model/reactive/model/arithmetic/ReactiveNegate.js +3 -1
- package/src/core/model/reactive/model/arithmetic/ReactiveSubtract.js +1 -1
- package/src/core/model/reactive/model/comparative/ReactiveEquals.js +1 -1
- package/src/core/model/reactive/model/comparative/ReactiveGreaterThan.js +3 -1
- package/src/core/model/reactive/model/comparative/ReactiveGreaterThanOrEqual.js +3 -1
- package/src/core/model/reactive/model/comparative/ReactiveLessThan.js +3 -1
- package/src/core/model/reactive/model/comparative/ReactiveLessThanOrEqual.js +3 -1
- package/src/core/model/reactive/model/comparative/ReactiveNotEquals.js +1 -1
- package/src/core/model/reactive/model/logic/ReactiveAnd.js +1 -1
- package/src/core/model/reactive/model/logic/ReactiveNot.js +3 -1
- package/src/core/model/reactive/model/logic/ReactiveOr.js +1 -1
- package/src/core/primitives/numbers/computeHashFloat.spec.js +7 -0
- package/src/core/process/task/util/iteratorTask.js +3 -1
- package/src/engine/animation/curve/AnimationCurve.js +34 -5
- package/src/engine/animation/curve/AnimationCurve.spec.js +100 -0
- package/src/engine/asset/AssetTransformer.js +1 -0
- package/src/engine/asset/loaders/GLTFAssetLoader.js +1 -1
- package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +2 -2
- package/src/engine/asset/preloader/Preloader.js +3 -3
- package/src/engine/computeStridedIntegerArrayHash.js +4 -2
- package/src/engine/ecs/components/Renderable.d.ts +2 -2
- package/src/engine/ecs/components/Renderable.js +1 -1
- package/src/{ecs → engine/ecs}/grid/pick.js +4 -4
- package/src/engine/ecs/gui/position/ViewportPositionSystem.js +1 -1
- package/src/engine/ecs/parent/entity_node_compute_bounding_box.js +1 -1
- package/src/engine/ecs/storage/binary/collection/BinaryCollectionSerializer.js +1 -18
- package/src/engine/ecs/systems/MotionSystem.js +7 -1
- package/src/engine/ecs/systems/SynchronizePositionSystem.js +8 -2
- package/src/engine/ecs/terrain/tiles/TerrainTile.js +1 -1
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +2 -2
- package/src/engine/ecs/tooltip/TooltipComponentSystem.js +1 -1
- package/src/engine/ecs/transform/Transform.js +1 -1
- package/src/engine/graphics/camera/makeScreenScissorFrustum.d.ts +1 -1
- package/src/engine/graphics/camera/makeScreenScissorFrustum.js +3 -3
- package/src/engine/graphics/camera/testClippingPlaneComputation.js +13 -13
- package/src/engine/graphics/ecs/camera/Camera.js +1 -1
- package/src/engine/graphics/ecs/light/LightSystem.js +1 -1
- package/src/engine/graphics/ecs/mesh/Mesh.d.ts +1 -1
- package/src/engine/graphics/ecs/mesh/Mesh.js +1 -1
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.d.ts +1 -1
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +1 -1
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.d.ts +1 -1
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +1 -1
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.js +9 -0
- package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.d.ts +1 -1
- package/src/engine/graphics/geometry/MikkT/MikkTSpace.js +1 -1
- package/src/engine/graphics/geometry/MikkT/STSpace.js +1 -1
- package/src/engine/graphics/geometry/bvh/buffered/BVHGeometryRaycaster.js +1 -1
- package/src/engine/graphics/geometry/skining/computeSkinnedMeshBoundingVolumes.js +1 -1
- package/src/engine/graphics/material/optimization/MaterialOptimizationContext.js +1 -1
- package/src/engine/graphics/particles/particular/engine/MovingBoundingBox.js +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleLayer.js +1 -1
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +1 -0
- package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
- package/src/engine/graphics/postprocess/threejs/postprocessing/TexturePass.js +2 -2
- package/src/engine/graphics/render/forward_plus/debug/createScreenGrid.js +1 -1
- package/src/engine/graphics/render/view/CameraView.js +1 -1
- package/src/engine/graphics/sh3/path_tracer/GeometryBVHBatched.js +2 -2
- package/src/engine/graphics/texture/atlas/AtlasPatch.js +2 -2
- package/src/engine/graphics/texture/atlas/TextureAtlas.spec.js +2 -2
- package/src/engine/graphics/texture/sampler/Sampler2D.js +1 -1
- package/src/engine/graphics/texture/sampler/sampler2d_compute_texel_value_conversion_scale_to_uint8.js +1 -1
- package/src/engine/graphics/util/makeMeshPreviewScene.js +1 -1
- package/src/engine/graphics/util/renderObjectToSampler2D.js +1 -1
- package/src/engine/intelligence/behavior/Behavior.spec.js +15 -0
- package/src/engine/intelligence/mcts/MoveEdge.js +1 -1
- package/src/engine/reference/v1/ReferenceManager.js +3 -0
- package/src/engine/reference/v2/Reference.js +33 -37
- package/src/engine/sound/sopra/README.md +6 -0
- package/src/engine/ui/tiles2d/computeTileGridMove.js +3 -2
- package/src/engine/ui/tiles2d/computeTileGridMove.spec.js +1 -1
- package/src/generation/automata/CaveGeneratorCellularAutomata.js +10 -7
- package/src/generation/automata/CaveGeneratorCellularAutomata.spec.js +12 -0
- package/src/generation/automata/CellularAutomata.js +5 -4
- package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.js +25 -9
- package/src/generation/theme/AreaMask.js +1 -1
- package/src/view/View.js +1 -1
- package/src/view/elements/progress/RectangularPieProgressView.js +1 -1
- package/src/view/minimap/Minimap.js +1 -1
- package/src/view/minimap/dom/MinimapCameraView.js +1 -1
- package/src/view/minimap/gl/MinimapFogOfWar.js +1 -1
- package/src/view/tooltip/DomTooltipObserver.js +1 -1
- package/src/view/tooltip/TooltipManager.js +1 -1
- package/src/view/tooltip/TooltipView.js +1 -1
- package/src/view/util/DomSizeObserver.js +2 -2
- package/src/core/geom/2d/AABB2Math.js +0 -40
- package/src/core/geom/2d/AABB2Math.spec.js +0 -17
- package/src/core/geom/Plane.js +0 -250
- package/src/core/land/reactive/ReactiveLexer.js +0 -158
- package/src/core/land/reactive/ReactiveLexer.ts +0 -181
- package/src/core/land/reactive/ReactiveListener.ts +0 -323
- package/src/core/land/reactive/ReactiveParser.js +0 -1573
- package/src/core/land/reactive/ReactiveParser.ts +0 -1776
- package/src/core/land/reactive/ReactiveVisitor.js +0 -1
- package/src/core/land/reactive/ReactiveVisitor.ts +0 -218
- package/src/core/land/reactive/compiler/ReactiveCompiler.js +0 -350
- package/src/core/land/reactive/compiler/ReactiveNearlyCompiler.js +0 -166
- package/src/core/land/reactive/compiler/ReactiveParser.js +0 -34
- package/src/core/land/reactive/nearley/ReactiveNearley.js +0 -187
- /package/src/core/geom/{LineSegment2.js → 2d/LineSegment2.js} +0 -0
- /package/src/core/geom/{Rectangle.spec.js → 2d/Rectangle.spec.js} +0 -0
- /package/src/core/geom/{AABB2.d.ts → 2d/aabb/AABB2.d.ts} +0 -0
- /package/src/core/geom/{AABB2.spec.js → 2d/aabb/AABB2.spec.js} +0 -0
- /package/src/core/{bvh2/aabb3 → geom/3d/aabb}/deserializeAABB3.js +0 -0
- /package/src/core/{bvh2/aabb3 → geom/3d/aabb}/deserializeAABB3Encoded_v0.js +0 -0
- /package/src/core/{bvh2/aabb3 → geom/3d/aabb}/deserializeAABB3Quantized16Uint.js +0 -0
- /package/src/core/{bvh2/aabb3 → geom/3d/aabb}/serializeAABB3.js +0 -0
- /package/src/core/{bvh2/aabb3 → geom/3d/aabb}/serializeAABB3Quantized16Uint.js +0 -0
- /package/src/{engine/graphics/ecs/mesh-v2 → core/geom/3d/vector}/allocate_v3.js +0 -0
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import { assert } from "../assert.js";
|
|
2
2
|
import { invokeObjectEquals } from "../model/object/invokeObjectEquals.js";
|
|
3
3
|
import { invokeObjectHash } from "../model/object/invokeObjectHash.js";
|
|
4
|
+
import { isPowerOfTwo } from "../math/isPowerOrTwo.js";
|
|
5
|
+
import { ctz32 } from "../binary/ctz32.js";
|
|
6
|
+
import { ceilPowerOfTwo } from "../binary/operations/ceilPowerOfTwo.js";
|
|
7
|
+
import { UintArrayForCount } from "./array/typed/uint_array_for_count.js";
|
|
8
|
+
import { array_copy } from "./array/copyArray.js";
|
|
9
|
+
import { min2 } from "../math/min2.js";
|
|
10
|
+
import { arraySwapElements } from "./array/arraySwapElements.js";
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Heavily inspired by ruby's "new" (circa 2016) hash table implementation
|
|
14
|
+
* @see https://github.com/ruby/ruby/blob/82995d4615e993f1d13f3e826b93fbd65c47e19e/st.c
|
|
15
|
+
* @see https://blog.heroku.com/ruby-2-4-features-hashes-integers-rounding#hash-changes
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Formula: Xn+1 = (a * Xn + c ) % m
|
|
20
|
+
*
|
|
21
|
+
* According the Hull-Dobell theorem a generator
|
|
22
|
+
* "Xnext = (a*Xprev + c) mod m" is a full cycle generator if and only if
|
|
23
|
+
* o m and c are relatively prime
|
|
24
|
+
* o a-1 is divisible by all prime factors of m
|
|
25
|
+
* o a-1 is divisible by 4 if m is divisible by 4.
|
|
26
|
+
* For our case a is 5, c is 1, and m is a power of two.
|
|
27
|
+
* @param index
|
|
28
|
+
* @param mask used to execute division, this is a number equal to (2^K - 1) where 2^K is the size of the "bins" array
|
|
29
|
+
* @see https://en.wikipedia.org/wiki/Linear_congruential_generator
|
|
30
|
+
*/
|
|
31
|
+
export function generate_next_linear_congruential_index(index, mask) {
|
|
32
|
+
const index5 = (index << 2) + index; // this is just a faster version of index*5
|
|
33
|
+
|
|
34
|
+
return (index5 + 1) & mask;
|
|
35
|
+
}
|
|
4
36
|
|
|
5
37
|
|
|
6
38
|
/**
|
|
@@ -40,28 +72,151 @@ class MapEntry {
|
|
|
40
72
|
* @readonly
|
|
41
73
|
* @type {number}
|
|
42
74
|
*/
|
|
43
|
-
const
|
|
75
|
+
const DEFAULT_INITIAL_CAPACITY_POWER = 4;
|
|
44
76
|
|
|
45
77
|
/**
|
|
46
78
|
* @readonly
|
|
47
79
|
* @type {number}
|
|
48
80
|
*/
|
|
49
|
-
const
|
|
81
|
+
const DEFAULT_INITIAL_CAPACITY = 2 ** DEFAULT_INITIAL_CAPACITY_POWER;
|
|
50
82
|
|
|
51
83
|
/**
|
|
52
|
-
* Multiply previous size by this number when growing the table
|
|
53
84
|
* @readonly
|
|
54
85
|
* @type {number}
|
|
55
86
|
*/
|
|
56
|
-
const
|
|
87
|
+
const DEFAULT_LOAD_FACTOR = 0.75;
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Reserved value that we store in "bins" array to indicate an empty slot
|
|
92
|
+
* @type {number}
|
|
93
|
+
*/
|
|
94
|
+
const BIN_RESERVED_VALUE_EMPTY = 0;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Reserved value that we store in "bins" array to indicate a deleted entry
|
|
98
|
+
* @type {number}
|
|
99
|
+
*/
|
|
100
|
+
const BIN_RESERVED_VALUE_DELETED = 1;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Real index offset into entry array
|
|
104
|
+
* @type {number}
|
|
105
|
+
*/
|
|
106
|
+
const ENTRY_BASE = 2;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Special hash value used to indicate "dead" entries
|
|
110
|
+
* If key hashes to this value - we will replace it
|
|
111
|
+
* @type {number}
|
|
112
|
+
*/
|
|
113
|
+
const RESERVED_HASH = 4294967295;
|
|
114
|
+
/**
|
|
115
|
+
* Used as a replacement for reserved hash
|
|
116
|
+
* @type {number}
|
|
117
|
+
*/
|
|
118
|
+
const RESERVED_HASH_SUBSTITUTE = 0;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
*
|
|
122
|
+
* @type {number}
|
|
123
|
+
*/
|
|
124
|
+
const UNDEFINED_BIN_INDEX = ~0;
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @template K,V
|
|
129
|
+
* @param {MapEntry<K,V>} record
|
|
130
|
+
* @param {number} hash
|
|
131
|
+
* @param {K} key
|
|
132
|
+
* @param {function(a:K,b:K):boolean} equality_op
|
|
133
|
+
*/
|
|
134
|
+
function entry_equality_check(record, hash, key, equality_op) {
|
|
135
|
+
if (record.hash !== hash) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (record.key === key) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const result = equality_op(record.key, key);
|
|
144
|
+
|
|
145
|
+
assert.isBoolean(result, `result(a=${record.key},b=${key})`);
|
|
146
|
+
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
const EMPTY_BINS = new Uint32Array(0);
|
|
57
152
|
|
|
58
153
|
/**
|
|
59
154
|
* Implements part of {@link Map} interface
|
|
155
|
+
* NOTE: as with any hash-based data structure, keys are assumed to be immutable. If you modified keys after inserting them into the map, it will cause the hash table to become invalid. You can fix this by forcing rehashing, but generally - try to avoid changing keys in the first place.
|
|
156
|
+
*
|
|
60
157
|
* @copyright Alex Goldring (c) 2023
|
|
61
158
|
* @template K,V
|
|
62
159
|
* @extends Map<K,V>
|
|
63
160
|
*/
|
|
64
161
|
export class HashMap {
|
|
162
|
+
/**
|
|
163
|
+
* Index pointers to entries array,
|
|
164
|
+
* number of bins is always power or two
|
|
165
|
+
* @type {Uint32Array}
|
|
166
|
+
*/
|
|
167
|
+
#bins = EMPTY_BINS;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Note that dead entries are marked as such with a special reserved hash values, so records can be reused for new entries
|
|
171
|
+
* @type {MapEntry<K,V>[]}
|
|
172
|
+
*/
|
|
173
|
+
#entries = new Array(0);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Pointer to the end of allocated entries segment
|
|
177
|
+
* @type {number}
|
|
178
|
+
*/
|
|
179
|
+
#entries_bound = 0;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
*
|
|
183
|
+
* @type {number}
|
|
184
|
+
*/
|
|
185
|
+
#entries_start = 0;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* number of records in the map
|
|
189
|
+
* @type {number}
|
|
190
|
+
*/
|
|
191
|
+
#size = 0;
|
|
192
|
+
|
|
193
|
+
#bin_count = 0;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Always exactly half of the number of bins
|
|
197
|
+
* @type {number}
|
|
198
|
+
*/
|
|
199
|
+
#entries_allocated_count = 0;
|
|
200
|
+
|
|
201
|
+
#bin_count_power_of_two = 0;
|
|
202
|
+
|
|
203
|
+
#entries_count_power_of_two = 0;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Mask used to map from hash to a bin index
|
|
207
|
+
* @type {number}
|
|
208
|
+
*/
|
|
209
|
+
#bin_count_mask = 0;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* How full the table can get before number of buckets is increased
|
|
213
|
+
* @type {number}
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
#load_factor = DEFAULT_LOAD_FACTOR;
|
|
217
|
+
|
|
218
|
+
#version = 0;
|
|
219
|
+
|
|
65
220
|
/**
|
|
66
221
|
* @template K, V
|
|
67
222
|
* @param {function(K):number} [keyHashFunction]
|
|
@@ -81,45 +236,31 @@ export class HashMap {
|
|
|
81
236
|
assert.isNonNegativeInteger(capacity, 'capacity');
|
|
82
237
|
|
|
83
238
|
assert.isNumber(loadFactor, 'loadFactor');
|
|
239
|
+
assert.notNaN(loadFactor, 'loadFactor');
|
|
84
240
|
assert.greaterThan(loadFactor, 0, 'loadFactor must be > 0');
|
|
85
241
|
|
|
86
242
|
/**
|
|
87
243
|
*
|
|
88
244
|
* @type {function(K): number}
|
|
245
|
+
* @readonly
|
|
246
|
+
* @private
|
|
89
247
|
*/
|
|
90
248
|
this.keyHashFunction = keyHashFunction;
|
|
91
249
|
/**
|
|
92
250
|
*
|
|
93
251
|
* @type {function(K, K): boolean}
|
|
252
|
+
* @readonly
|
|
253
|
+
* @private
|
|
94
254
|
*/
|
|
95
255
|
this.keyEqualityFunction = keyEqualityFunction;
|
|
96
256
|
|
|
97
|
-
|
|
98
|
-
* Sparse array of map entries
|
|
99
|
-
* @type {MapEntry[][]}
|
|
100
|
-
*/
|
|
101
|
-
this.records = [];
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
*
|
|
105
|
-
* @type {number}
|
|
106
|
-
*/
|
|
107
|
-
this.size = 0;
|
|
257
|
+
this.#load_factor = loadFactor;
|
|
108
258
|
|
|
259
|
+
this.#setBinCount(ceilPowerOfTwo(capacity));
|
|
260
|
+
}
|
|
109
261
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* @type {number}
|
|
113
|
-
* @private
|
|
114
|
-
*/
|
|
115
|
-
this.__bucket_count = capacity;
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* How full the table can get before number of buckets is increased
|
|
119
|
-
* @type {number}
|
|
120
|
-
* @private
|
|
121
|
-
*/
|
|
122
|
-
this.__load_factor = loadFactor;
|
|
262
|
+
get size() {
|
|
263
|
+
return this.#size;
|
|
123
264
|
}
|
|
124
265
|
|
|
125
266
|
/**
|
|
@@ -127,61 +268,43 @@ export class HashMap {
|
|
|
127
268
|
* @returns {number}
|
|
128
269
|
*/
|
|
129
270
|
getCurrentLoad() {
|
|
130
|
-
return this
|
|
271
|
+
return this.#size / this.#bin_count;
|
|
131
272
|
}
|
|
132
273
|
|
|
133
274
|
/**
|
|
134
|
-
*
|
|
135
|
-
* @
|
|
275
|
+
* Note: this method is not intended for public use
|
|
276
|
+
* @param {number} count
|
|
136
277
|
*/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
for (let i = 0; i < c; i++) {
|
|
142
|
-
const bucket = this.records[i];
|
|
278
|
+
#setBinCount(count) {
|
|
279
|
+
assert.greaterThanOrEqual(count, 1, 'bucket count must be at least 1');
|
|
280
|
+
assert.isNonNegativeInteger(count, 'count');
|
|
281
|
+
assert.equal(isPowerOfTwo(count), true, `count must be a power of two, instead was ${count}`);
|
|
143
282
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
283
|
+
if (count < this.#size) {
|
|
284
|
+
throw new Error(`count must be at least equal to must of records in the map (=${this.#size}), instead was ${count}`);
|
|
147
285
|
}
|
|
148
286
|
|
|
149
|
-
return occupied_bucket_count / this.__bucket_count;
|
|
150
|
-
}
|
|
151
287
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
* @returns {number}
|
|
155
|
-
*/
|
|
156
|
-
getCollisionCount() {
|
|
157
|
-
let count = 0;
|
|
288
|
+
this.#entries_count_power_of_two = ctz32(count);
|
|
289
|
+
this.#bin_count_power_of_two = this.#entries_count_power_of_two + 1;
|
|
158
290
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const bucket = this.records[i];
|
|
291
|
+
this.#bin_count = 2 ** this.#bin_count_power_of_two;
|
|
292
|
+
this.#bin_count_mask = this.#bin_count - 1;
|
|
162
293
|
|
|
163
|
-
|
|
164
|
-
const occupancy = bucket.length;
|
|
165
|
-
const collisions = occupancy - 1;
|
|
166
|
-
if (collisions > 0) {
|
|
167
|
-
count += collisions;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
294
|
+
this.#entries_allocated_count = 2 ** this.#entries_count_power_of_two;
|
|
171
295
|
|
|
172
|
-
|
|
173
|
-
}
|
|
296
|
+
const BinsArray = UintArrayForCount(this.#entries_allocated_count + ENTRY_BASE);
|
|
174
297
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
298
|
+
this.#bins = new BinsArray(this.#bin_count);
|
|
299
|
+
|
|
300
|
+
const new_entries = new Array(this.#entries_allocated_count);
|
|
301
|
+
const old_entries = this.#entries;
|
|
302
|
+
|
|
303
|
+
this.#entries = new_entries;
|
|
304
|
+
|
|
305
|
+
array_copy(old_entries, 0, new_entries, 0, min2(old_entries.length, this.#entries_allocated_count));
|
|
182
306
|
|
|
183
|
-
this
|
|
184
|
-
if (this.size > 0) {
|
|
307
|
+
if (this.#size > 0) {
|
|
185
308
|
// re-hash
|
|
186
309
|
this.rebuild();
|
|
187
310
|
}
|
|
@@ -193,7 +316,7 @@ export class HashMap {
|
|
|
193
316
|
* @returns {number}
|
|
194
317
|
* @private
|
|
195
318
|
*/
|
|
196
|
-
|
|
319
|
+
#compute_bin_index(hash) {
|
|
197
320
|
assert.isInteger(hash, 'hash');
|
|
198
321
|
|
|
199
322
|
// mix the input hash to minimize potential impact of poor hash function spread
|
|
@@ -202,76 +325,141 @@ export class HashMap {
|
|
|
202
325
|
// force index to unsigned integer
|
|
203
326
|
const index = mixed_hash >>> 0;
|
|
204
327
|
|
|
205
|
-
return index
|
|
328
|
+
return index & this.#bin_count_mask;
|
|
206
329
|
}
|
|
207
330
|
|
|
208
331
|
/**
|
|
209
332
|
*
|
|
210
333
|
* @param {K} key
|
|
211
|
-
* @
|
|
334
|
+
* @return {number}
|
|
212
335
|
*/
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
const bucket_index = this.__compute_bucket_index(raw_hash);
|
|
336
|
+
#build_key_hash(key) {
|
|
337
|
+
const original = this.keyHashFunction(key);
|
|
216
338
|
|
|
217
|
-
|
|
339
|
+
return original === RESERVED_HASH ? RESERVED_HASH_SUBSTITUTE : original;
|
|
340
|
+
}
|
|
218
341
|
|
|
219
|
-
|
|
342
|
+
/**
|
|
343
|
+
*
|
|
344
|
+
* @param {K} k
|
|
345
|
+
* @param {V} v
|
|
346
|
+
* @param {number} hash
|
|
347
|
+
* @return {number}
|
|
348
|
+
*/
|
|
349
|
+
#allocate_entry(k, v, hash) {
|
|
220
350
|
|
|
221
|
-
|
|
351
|
+
const i = this.#entries_bound;
|
|
222
352
|
|
|
223
|
-
|
|
353
|
+
this.#entries_bound++;
|
|
224
354
|
|
|
225
|
-
|
|
355
|
+
if (this.#entries[i] !== undefined) {
|
|
356
|
+
const entry = this.#entries[i];
|
|
357
|
+
entry.hash = hash;
|
|
358
|
+
entry.key = k;
|
|
359
|
+
entry.value = v;
|
|
360
|
+
} else {
|
|
361
|
+
this.#entries[i] = new MapEntry(k, v, hash);
|
|
362
|
+
}
|
|
226
363
|
|
|
227
|
-
|
|
364
|
+
return i;
|
|
365
|
+
}
|
|
228
366
|
|
|
229
|
-
|
|
367
|
+
/**
|
|
368
|
+
*
|
|
369
|
+
* @param {MapEntry<K,V>} entry
|
|
370
|
+
*/
|
|
371
|
+
#deallocate(entry) {
|
|
230
372
|
|
|
231
|
-
|
|
373
|
+
// clear out entry to allow values/keys to be garbage collected
|
|
374
|
+
entry.key = null;
|
|
375
|
+
entry.value = null;
|
|
376
|
+
entry.hash = RESERVED_HASH; // mark as dead via hash
|
|
232
377
|
|
|
233
|
-
|
|
378
|
+
}
|
|
234
379
|
|
|
235
|
-
|
|
380
|
+
#rebuild_if_necessary() {
|
|
381
|
+
if (this.#entries_bound === this.#entries_allocated_count) {
|
|
382
|
+
if (this.#size === this.#entries_allocated_count) {
|
|
383
|
+
// used up all allocated entries
|
|
384
|
+
// bin count must always be larger than end of the entries table
|
|
385
|
+
this.#grow();
|
|
386
|
+
} else {
|
|
387
|
+
// exhausted entries array, perform compaction
|
|
388
|
+
this.rebuild();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
236
392
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
393
|
+
/**
|
|
394
|
+
*
|
|
395
|
+
* @param {K} key
|
|
396
|
+
* @param {V} value
|
|
397
|
+
*/
|
|
398
|
+
set(key, value) {
|
|
399
|
+
this.#rebuild_if_necessary();
|
|
240
400
|
|
|
241
|
-
|
|
401
|
+
const raw_hash = this.#build_key_hash(key);
|
|
402
|
+
let bin_index = this.#compute_bin_index(raw_hash);
|
|
403
|
+
assert.isFiniteNumber(bin_index, 'hash');
|
|
242
404
|
|
|
405
|
+
let first_deleted_bin_index = UNDEFINED_BIN_INDEX;
|
|
243
406
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
&& (entryKey === key || this.keyEqualityFunction(entryKey, key))
|
|
247
|
-
) {
|
|
407
|
+
for (; ;) {
|
|
408
|
+
const bin = this.#bins[bin_index];
|
|
248
409
|
|
|
249
|
-
|
|
410
|
+
if (bin > BIN_RESERVED_VALUE_DELETED) {
|
|
411
|
+
// bin is occupied
|
|
250
412
|
|
|
251
|
-
|
|
413
|
+
// check if it's the entry that we're looking for
|
|
414
|
+
const entry = this.#entries[bin - ENTRY_BASE];
|
|
252
415
|
|
|
253
|
-
|
|
416
|
+
if (entry_equality_check(entry, raw_hash, key, this.keyEqualityFunction)) {
|
|
417
|
+
// found the right entry
|
|
418
|
+
entry.value = value;
|
|
254
419
|
return;
|
|
420
|
+
}
|
|
421
|
+
} else if (bin === BIN_RESERVED_VALUE_EMPTY) {
|
|
422
|
+
// bin is empty
|
|
255
423
|
|
|
424
|
+
if (first_deleted_bin_index !== UNDEFINED_BIN_INDEX) {
|
|
425
|
+
// reused bin of deleted entity
|
|
426
|
+
bin_index = first_deleted_bin_index;
|
|
256
427
|
}
|
|
257
428
|
|
|
429
|
+
const entry_index = this.#allocate_entry(key, value, raw_hash);
|
|
430
|
+
|
|
431
|
+
assert.defined(this.#entries[entry_index], 'entry');
|
|
432
|
+
|
|
433
|
+
assert.equal(this.#entries[entry_index].hash, raw_hash, 'entry.hash');
|
|
434
|
+
assert.equal(this.#entries[entry_index].value, value, 'entry.value');
|
|
435
|
+
assert.equal(this.#entries[entry_index].key, key, 'entry.key');
|
|
436
|
+
|
|
437
|
+
this.#bins[bin_index] = entry_index + ENTRY_BASE;
|
|
438
|
+
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
} else if (first_deleted_bin_index === UNDEFINED_BIN_INDEX) {
|
|
442
|
+
// bin is deleted
|
|
443
|
+
first_deleted_bin_index = bin_index;
|
|
444
|
+
|
|
258
445
|
}
|
|
259
446
|
|
|
260
|
-
|
|
447
|
+
// perform secondary hashing
|
|
448
|
+
bin_index = generate_next_linear_congruential_index(bin_index, this.#bin_count_mask);
|
|
261
449
|
|
|
262
|
-
|
|
450
|
+
}
|
|
263
451
|
|
|
264
|
-
const old_size = this
|
|
452
|
+
const old_size = this.#size;
|
|
265
453
|
const new_size = old_size + 1;
|
|
266
|
-
this
|
|
454
|
+
this.#size = new_size;
|
|
267
455
|
|
|
268
456
|
// compute actual current load
|
|
269
|
-
const bucket_count = this
|
|
457
|
+
const bucket_count = this.#bin_count;
|
|
270
458
|
const load = new_size / bucket_count;
|
|
271
459
|
|
|
272
|
-
if (load > this
|
|
460
|
+
if (load > this.#load_factor) {
|
|
273
461
|
// current load is too high, increase table size
|
|
274
|
-
this
|
|
462
|
+
this.#grow();
|
|
275
463
|
}
|
|
276
464
|
}
|
|
277
465
|
|
|
@@ -281,33 +469,34 @@ export class HashMap {
|
|
|
281
469
|
* @returns {V|undefined}
|
|
282
470
|
*/
|
|
283
471
|
get(key) {
|
|
284
|
-
const raw_hash = this
|
|
285
|
-
const bucket_index = this.__compute_bucket_index(raw_hash);
|
|
286
|
-
const bucket = this.records[bucket_index];
|
|
472
|
+
const raw_hash = this.#build_key_hash(key);
|
|
287
473
|
|
|
288
|
-
|
|
474
|
+
let bin_index = this.#compute_bin_index(raw_hash);
|
|
289
475
|
|
|
290
|
-
|
|
476
|
+
for (; ;) {
|
|
477
|
+
const bin = this.#bins[bin_index];
|
|
291
478
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const entry = bucket[i];
|
|
479
|
+
if (bin > BIN_RESERVED_VALUE_DELETED) {
|
|
480
|
+
// bin is occupied
|
|
295
481
|
|
|
296
|
-
|
|
482
|
+
// check if the entry is what we're looking for
|
|
483
|
+
const entry = this.#entries[bin - ENTRY_BASE];
|
|
297
484
|
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
&& (entryKey === key || this.keyEqualityFunction(entryKey, key))
|
|
301
|
-
) {
|
|
485
|
+
if (entry_equality_check(entry, raw_hash, key, this.keyEqualityFunction)) {
|
|
486
|
+
// found the right entry
|
|
302
487
|
return entry.value;
|
|
303
|
-
|
|
304
488
|
}
|
|
489
|
+
|
|
490
|
+
} else if (bin === BIN_RESERVED_VALUE_EMPTY) {
|
|
491
|
+
// bin is empty
|
|
492
|
+
return undefined;
|
|
305
493
|
}
|
|
306
494
|
|
|
495
|
+
// perform secondary hashing
|
|
496
|
+
bin_index = generate_next_linear_congruential_index(bin_index, this.#bin_count_mask);
|
|
497
|
+
|
|
307
498
|
}
|
|
308
499
|
|
|
309
|
-
//not found
|
|
310
|
-
return undefined;
|
|
311
500
|
}
|
|
312
501
|
|
|
313
502
|
/**
|
|
@@ -351,6 +540,26 @@ export class HashMap {
|
|
|
351
540
|
return value;
|
|
352
541
|
}
|
|
353
542
|
|
|
543
|
+
/**
|
|
544
|
+
*
|
|
545
|
+
* @param {number} bin
|
|
546
|
+
*/
|
|
547
|
+
#update_range_for_deleted(bin) {
|
|
548
|
+
if (this.#entries_start === bin) {
|
|
549
|
+
let start = bin + 1;
|
|
550
|
+
let bound = this.#entries_bound;
|
|
551
|
+
|
|
552
|
+
const entries = this.#entries;
|
|
553
|
+
|
|
554
|
+
while (start < bound && entries[start].hash === RESERVED_HASH) {
|
|
555
|
+
start++;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
assert.greaterThanOrEqual(bound - start, this.#size, `live entity bounds must span at least number of entries equal to map size(=${this.#size}), instead got start(=${start}), and end(=${bound})`)
|
|
559
|
+
|
|
560
|
+
this.#entries_start = start;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
354
563
|
|
|
355
564
|
/**
|
|
356
565
|
*
|
|
@@ -359,38 +568,49 @@ export class HashMap {
|
|
|
359
568
|
*/
|
|
360
569
|
delete(key) {
|
|
361
570
|
|
|
362
|
-
const raw_hash = this
|
|
363
|
-
|
|
364
|
-
const bucket = this.records[bucket_index];
|
|
571
|
+
const raw_hash = this.#build_key_hash(key);
|
|
572
|
+
let bin_index = this.#compute_bin_index(raw_hash);
|
|
365
573
|
|
|
366
|
-
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
574
|
+
assert.isFiniteNumber(bin_index, 'hash');
|
|
369
575
|
|
|
576
|
+
const bins = this.#bins;
|
|
577
|
+
const entries = this.#entries;
|
|
370
578
|
|
|
371
|
-
|
|
579
|
+
for (; ;) {
|
|
580
|
+
const bin = bins[bin_index];
|
|
372
581
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const entry = bucket[i];
|
|
582
|
+
if (bin > BIN_RESERVED_VALUE_DELETED) {
|
|
583
|
+
// bin is occupied
|
|
376
584
|
|
|
377
|
-
|
|
378
|
-
|
|
585
|
+
// check if the entry is what we're looking for
|
|
586
|
+
const entry_index = bin - ENTRY_BASE;
|
|
587
|
+
const entry = entries[entry_index];
|
|
379
588
|
|
|
380
|
-
if (
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
589
|
+
if (entry_equality_check(entry, raw_hash, key, this.keyEqualityFunction)) {
|
|
590
|
+
// found the right entry
|
|
591
|
+
|
|
592
|
+
// record entry as dead
|
|
593
|
+
this.#deallocate(entry);
|
|
594
|
+
|
|
595
|
+
// mark slot as removed
|
|
596
|
+
bins[bin_index] = BIN_RESERVED_VALUE_DELETED;
|
|
597
|
+
|
|
598
|
+
this.#size--;
|
|
599
|
+
|
|
600
|
+
this.#update_range_for_deleted(entry_index);
|
|
601
|
+
|
|
602
|
+
return true;
|
|
385
603
|
}
|
|
386
604
|
|
|
387
|
-
|
|
388
|
-
|
|
605
|
+
} else if (bin === BIN_RESERVED_VALUE_EMPTY) {
|
|
606
|
+
// bin is empty
|
|
607
|
+
return false;
|
|
389
608
|
}
|
|
390
|
-
}
|
|
391
609
|
|
|
392
|
-
|
|
393
|
-
|
|
610
|
+
// perform secondary hashing
|
|
611
|
+
bin_index = generate_next_linear_congruential_index(bin_index, this.#bin_count_mask);
|
|
612
|
+
|
|
613
|
+
}
|
|
394
614
|
}
|
|
395
615
|
|
|
396
616
|
/**
|
|
@@ -402,130 +622,134 @@ export class HashMap {
|
|
|
402
622
|
verifyHashes(callback, thisArg) {
|
|
403
623
|
let all_hashes_valid = true;
|
|
404
624
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
625
|
+
const count = this.#bin_count;
|
|
626
|
+
for (let j = 0; j < count; j++) {
|
|
627
|
+
const bin = this.#bins[j];
|
|
408
628
|
|
|
409
|
-
|
|
410
|
-
|
|
629
|
+
if (bin <= BIN_RESERVED_VALUE_DELETED) {
|
|
630
|
+
// unoccupied
|
|
411
631
|
continue;
|
|
412
632
|
}
|
|
413
633
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
//check if key already exists
|
|
419
|
-
for (i = 0; i < bucketSize; i++) {
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* @type {MapEntry<K,V>}
|
|
423
|
-
*/
|
|
424
|
-
const entry = bucket[i];
|
|
634
|
+
/**
|
|
635
|
+
* @type {MapEntry<K,V>}
|
|
636
|
+
*/
|
|
637
|
+
const entry = this.#entries[bin - ENTRY_BASE];
|
|
425
638
|
|
|
426
|
-
|
|
427
|
-
|
|
639
|
+
//check hash
|
|
640
|
+
const raw_hash = this.#build_key_hash(entry.key);
|
|
428
641
|
|
|
429
|
-
|
|
430
|
-
|
|
642
|
+
if (entry.hash !== raw_hash) {
|
|
643
|
+
callback.call(thisArg, `Hash stored on the entry(=${entry.hash}) is different from the computed key hash(=${raw_hash}).`, entry.key, entry.value)
|
|
431
644
|
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const actual_bucket_index = this.__compute_bucket_index(raw_hash);
|
|
436
|
-
|
|
437
|
-
const stored_bucket_index = parseInt(h);
|
|
438
|
-
|
|
439
|
-
if (actual_bucket_index !== stored_bucket_index) {
|
|
440
|
-
callback.call(thisArg, `Hash of key has changed. old=${stored_bucket_index}, new=${actual_bucket_index}`, entry.key, entry.value);
|
|
441
|
-
|
|
442
|
-
all_hashes_valid = false;
|
|
443
|
-
}
|
|
645
|
+
all_hashes_valid = false;
|
|
444
646
|
}
|
|
647
|
+
|
|
445
648
|
}
|
|
446
649
|
|
|
447
650
|
return all_hashes_valid;
|
|
448
651
|
}
|
|
449
652
|
|
|
653
|
+
#grow() {
|
|
654
|
+
this.#setBinCount(this.#entries_allocated_count * 2);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
|
|
450
658
|
/**
|
|
451
|
-
* Rebuild table, useful for when table is resized
|
|
659
|
+
* Rebuild table, useful for when table is resized
|
|
452
660
|
*/
|
|
453
661
|
rebuild() {
|
|
662
|
+
const entries_bound = this.#entries_bound;
|
|
663
|
+
const entries = this.#entries;
|
|
664
|
+
|
|
665
|
+
// reset all bins
|
|
666
|
+
const bins = this.#bins;
|
|
667
|
+
bins.fill(BIN_RESERVED_VALUE_EMPTY);
|
|
454
668
|
|
|
455
|
-
let
|
|
669
|
+
let written_entries = 0;
|
|
456
670
|
|
|
457
|
-
|
|
671
|
+
for (let existing_entry_index = this.#entries_start; existing_entry_index < entries_bound; existing_entry_index++) {
|
|
672
|
+
const entry = entries[existing_entry_index];
|
|
458
673
|
|
|
459
|
-
|
|
460
|
-
const stored_bucket_index = parseInt(string_bucket_index);
|
|
674
|
+
const hash = entry.hash;
|
|
461
675
|
|
|
462
|
-
if (
|
|
676
|
+
if (hash === RESERVED_HASH) {
|
|
677
|
+
// entry is dead
|
|
463
678
|
continue;
|
|
464
679
|
}
|
|
465
680
|
|
|
466
|
-
const
|
|
681
|
+
const new_index = written_entries;
|
|
682
|
+
written_entries++;
|
|
467
683
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const entry = bucket[i];
|
|
684
|
+
if (new_index !== existing_entry_index) {
|
|
685
|
+
// move entries to the new position, compacting holes
|
|
686
|
+
arraySwapElements(entries, new_index, existing_entry_index);
|
|
687
|
+
}
|
|
473
688
|
|
|
474
|
-
|
|
475
|
-
const raw_hash = this.keyHashFunction(entry.key);
|
|
689
|
+
let bin_index = this.#compute_bin_index(hash);
|
|
476
690
|
|
|
477
|
-
|
|
691
|
+
for (; ;) {
|
|
692
|
+
const bin = bins[bin_index];
|
|
478
693
|
|
|
479
|
-
|
|
694
|
+
if (bin === BIN_RESERVED_VALUE_EMPTY) {
|
|
695
|
+
// empty slot, take it
|
|
696
|
+
bins[bin_index] = new_index + ENTRY_BASE;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
480
699
|
|
|
700
|
+
// perform secondary hashing
|
|
701
|
+
bin_index = generate_next_linear_congruential_index(bin_index, this.#bin_count_mask);
|
|
481
702
|
|
|
482
|
-
|
|
483
|
-
|
|
703
|
+
}
|
|
704
|
+
}
|
|
484
705
|
|
|
485
|
-
|
|
486
|
-
//update iterator
|
|
487
|
-
l--;
|
|
488
|
-
i--;
|
|
706
|
+
assert.equal(written_entries, this.#size, `live entries(=${written_entries}) should match size(=${this.#size})`);
|
|
489
707
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
708
|
+
this.#entries_start = 0;
|
|
709
|
+
this.#entries_bound = this.#size;
|
|
710
|
+
this.#version++;
|
|
711
|
+
}
|
|
494
712
|
|
|
495
|
-
|
|
713
|
+
#count_live_entities() {
|
|
714
|
+
let count = 0;
|
|
715
|
+
for (let i = this.#entries_start; i < this.#entries_bound; i++) {
|
|
716
|
+
const entry = this.#entries[i];
|
|
496
717
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
} else {
|
|
500
|
-
newBucket.push(entry);
|
|
501
|
-
}
|
|
718
|
+
if (entry.hash !== RESERVED_HASH) {
|
|
719
|
+
count++;
|
|
502
720
|
|
|
503
|
-
|
|
721
|
+
assert.equal(entry.hash, this.#build_key_hash(entry.key));
|
|
722
|
+
} else {
|
|
723
|
+
assert.isNull(entry.key, 'key');
|
|
504
724
|
}
|
|
505
725
|
}
|
|
726
|
+
|
|
727
|
+
return count;
|
|
506
728
|
}
|
|
507
729
|
|
|
508
730
|
forEach(callback, thisArg) {
|
|
509
|
-
|
|
731
|
+
const count = this.#bin_count;
|
|
732
|
+
const entries = this.#entries;
|
|
733
|
+
const bins = this.#bins;
|
|
734
|
+
const start_version = this.#version;
|
|
735
|
+
|
|
736
|
+
for (let j = 0; j < count; j++) {
|
|
737
|
+
assert.equal(start_version, this.#version, 'HashMap modified during traversal');
|
|
510
738
|
|
|
511
|
-
|
|
739
|
+
const bin = bins[j];
|
|
512
740
|
|
|
513
|
-
|
|
514
|
-
|
|
741
|
+
if (bin <= BIN_RESERVED_VALUE_DELETED) {
|
|
742
|
+
// unoccupied
|
|
515
743
|
continue;
|
|
516
744
|
}
|
|
517
745
|
|
|
518
|
-
|
|
746
|
+
/**
|
|
747
|
+
* @type {MapEntry<K,V>}
|
|
748
|
+
*/
|
|
749
|
+
const entry = entries[bin - ENTRY_BASE];
|
|
519
750
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
* @type {MapEntry<K,V>}
|
|
523
|
-
*/
|
|
524
|
-
const entry = bucket[i];
|
|
525
|
-
|
|
526
|
-
// Signature based on MDN docs of Map.prototype.forEach()
|
|
527
|
-
callback.call(thisArg, entry.value, entry.key, this);
|
|
528
|
-
}
|
|
751
|
+
// Signature based on MDN docs of Map.prototype.forEach()
|
|
752
|
+
callback.call(thisArg, entry.value, entry.key, this);
|
|
529
753
|
}
|
|
530
754
|
}
|
|
531
755
|
|
|
@@ -542,30 +766,61 @@ export class HashMap {
|
|
|
542
766
|
* Remove all data from the Map
|
|
543
767
|
*/
|
|
544
768
|
clear() {
|
|
545
|
-
|
|
546
|
-
|
|
769
|
+
|
|
770
|
+
// clear out all
|
|
771
|
+
const bins = this.#bins;
|
|
772
|
+
const count = this.#bin_count;
|
|
773
|
+
|
|
774
|
+
for (let i = 0; i < count; i++) {
|
|
775
|
+
const bin = bins[i];
|
|
776
|
+
|
|
777
|
+
if (bin !== BIN_RESERVED_VALUE_EMPTY) {
|
|
778
|
+
|
|
779
|
+
if (bin !== BIN_RESERVED_VALUE_DELETED) {
|
|
780
|
+
// occupied, move to deleted
|
|
781
|
+
const entry_index = bin - ENTRY_BASE;
|
|
782
|
+
|
|
783
|
+
this.#deallocate(this.#entries[entry_index]);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// mark as empty
|
|
787
|
+
bins[i] = BIN_RESERVED_VALUE_EMPTY;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
this.#size = 0;
|
|
794
|
+
|
|
795
|
+
this.#entries_start = 0;
|
|
796
|
+
this.#entries_bound = 0;
|
|
547
797
|
}
|
|
548
798
|
|
|
549
799
|
* [Symbol.iterator]() {
|
|
550
|
-
let h, i, l;
|
|
551
800
|
|
|
552
|
-
const
|
|
801
|
+
const count = this.#bin_count;
|
|
553
802
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
803
|
+
const bins = this.#bins;
|
|
804
|
+
const entries = this.#entries;
|
|
805
|
+
|
|
806
|
+
const start_version = this.#version;
|
|
558
807
|
|
|
559
|
-
|
|
808
|
+
for (let j = 0; j < count; j++) {
|
|
809
|
+
assert.equal(start_version, this.#version, 'HashMap modified during traversal');
|
|
560
810
|
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* @type {MapEntry<K,V>}
|
|
564
|
-
*/
|
|
565
|
-
const entry = bucket[i];
|
|
811
|
+
const bin = bins[j];
|
|
566
812
|
|
|
567
|
-
|
|
813
|
+
if (bin <= BIN_RESERVED_VALUE_DELETED) {
|
|
814
|
+
// unoccupied
|
|
815
|
+
continue;
|
|
568
816
|
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* @type {MapEntry<K,V>}
|
|
820
|
+
*/
|
|
821
|
+
const entry = entries[bin - ENTRY_BASE];
|
|
822
|
+
|
|
823
|
+
yield [entry.key, entry.value];
|
|
569
824
|
}
|
|
570
825
|
|
|
571
826
|
}
|