@woosh/meep-engine 2.48.23 → 2.49.1
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/tools/GridPaintTool.js +1 -1
- package/editor/tools/paint/TerrainPaintTool.js +1 -1
- package/editor/view/GridPickCoordinateView.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/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/binary/IndexedBinaryBVH.spec.js +7 -0
- 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/transform/RotationOptimizer.spec.js +161 -155
- package/src/core/codegen/LineBuilder.js +12 -3
- package/src/core/codegen/LineBuilder.spec.js +7 -0
- package/src/core/collection/CuckooFilter.js +12 -12
- package/src/core/collection/HashMap.js +486 -237
- 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/debug/matchers/AnyOf.js +1 -2
- package/src/core/geom/2d/aabb/AABB2.spec.js +1 -1
- package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +19 -0
- package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +3 -3
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -9
- 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/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/{rayTriangleIntersection.js → 3d/triangle/rayTriangleIntersection.js} +2 -2
- 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 +4 -214
- 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/packing/miniball/Miniball.js +12 -12
- package/src/core/geom/packing/miniball/Quality.js +2 -2
- package/src/core/geom/packing/miniball/Subspan.js +13 -13
- package/src/core/geom/v3_dot.js +1 -1
- package/src/core/graph/layout/CircleLayout.js +1 -1
- 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/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/computeStridedIntegerArrayHash.js +4 -2
- package/src/engine/ecs/components/Renderable.d.ts +1 -1
- package/src/{ecs → engine/ecs}/grid/pick.js +4 -4
- 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/transform/Transform.js +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/mesh-v2/aggregate/SGMesh.js +1 -1
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.js +9 -0
- 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/material/optimization/MaterialOptimizationContext.js +1 -1
- package/src/engine/graphics/particles/particular/engine/MovingBoundingBox.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/sh3/path_tracer/GeometryBVHBatched.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/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/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/view/minimap/dom/MinimapCameraView.js +1 -1
- 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/{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]
|
|
@@ -99,33 +254,13 @@ export class HashMap {
|
|
|
99
254
|
*/
|
|
100
255
|
this.keyEqualityFunction = keyEqualityFunction;
|
|
101
256
|
|
|
102
|
-
|
|
103
|
-
* Sparse array of map entries
|
|
104
|
-
* @type {MapEntry[][]}
|
|
105
|
-
* @private
|
|
106
|
-
*/
|
|
107
|
-
this.records = [];
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
*
|
|
111
|
-
* @type {number}
|
|
112
|
-
*/
|
|
113
|
-
this.size = 0;
|
|
114
|
-
|
|
257
|
+
this.#load_factor = loadFactor;
|
|
115
258
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
* @type {number}
|
|
119
|
-
* @private
|
|
120
|
-
*/
|
|
121
|
-
this.__bucket_count = capacity;
|
|
259
|
+
this.#setBinCount(ceilPowerOfTwo(capacity));
|
|
260
|
+
}
|
|
122
261
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
* @type {number}
|
|
126
|
-
* @private
|
|
127
|
-
*/
|
|
128
|
-
this.__load_factor = loadFactor;
|
|
262
|
+
get size() {
|
|
263
|
+
return this.#size;
|
|
129
264
|
}
|
|
130
265
|
|
|
131
266
|
/**
|
|
@@ -133,61 +268,43 @@ export class HashMap {
|
|
|
133
268
|
* @returns {number}
|
|
134
269
|
*/
|
|
135
270
|
getCurrentLoad() {
|
|
136
|
-
return this
|
|
271
|
+
return this.#size / this.#bin_count;
|
|
137
272
|
}
|
|
138
273
|
|
|
139
274
|
/**
|
|
140
|
-
*
|
|
141
|
-
* @
|
|
275
|
+
* Note: this method is not intended for public use
|
|
276
|
+
* @param {number} count
|
|
142
277
|
*/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
for (let i = 0; i < c; i++) {
|
|
148
|
-
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}`);
|
|
149
282
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
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}`);
|
|
153
285
|
}
|
|
154
286
|
|
|
155
|
-
return occupied_bucket_count / this.__bucket_count;
|
|
156
|
-
}
|
|
157
287
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
* @returns {number}
|
|
161
|
-
*/
|
|
162
|
-
getCollisionCount() {
|
|
163
|
-
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;
|
|
164
290
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
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;
|
|
168
293
|
|
|
169
|
-
|
|
170
|
-
const occupancy = bucket.length;
|
|
171
|
-
const collisions = occupancy - 1;
|
|
172
|
-
if (collisions > 0) {
|
|
173
|
-
count += collisions;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
294
|
+
this.#entries_allocated_count = 2 ** this.#entries_count_power_of_two;
|
|
177
295
|
|
|
178
|
-
|
|
179
|
-
}
|
|
296
|
+
const BinsArray = UintArrayForCount(this.#entries_allocated_count + ENTRY_BASE);
|
|
180
297
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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));
|
|
188
306
|
|
|
189
|
-
this
|
|
190
|
-
if (this.size > 0) {
|
|
307
|
+
if (this.#size > 0) {
|
|
191
308
|
// re-hash
|
|
192
309
|
this.rebuild();
|
|
193
310
|
}
|
|
@@ -199,7 +316,7 @@ export class HashMap {
|
|
|
199
316
|
* @returns {number}
|
|
200
317
|
* @private
|
|
201
318
|
*/
|
|
202
|
-
|
|
319
|
+
#compute_bin_index(hash) {
|
|
203
320
|
assert.isInteger(hash, 'hash');
|
|
204
321
|
|
|
205
322
|
// mix the input hash to minimize potential impact of poor hash function spread
|
|
@@ -208,76 +325,141 @@ export class HashMap {
|
|
|
208
325
|
// force index to unsigned integer
|
|
209
326
|
const index = mixed_hash >>> 0;
|
|
210
327
|
|
|
211
|
-
return index
|
|
328
|
+
return index & this.#bin_count_mask;
|
|
212
329
|
}
|
|
213
330
|
|
|
214
331
|
/**
|
|
215
332
|
*
|
|
216
333
|
* @param {K} key
|
|
217
|
-
* @
|
|
334
|
+
* @return {number}
|
|
218
335
|
*/
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
const bucket_index = this.__compute_bucket_index(raw_hash);
|
|
336
|
+
#build_key_hash(key) {
|
|
337
|
+
const original = this.keyHashFunction(key);
|
|
222
338
|
|
|
223
|
-
|
|
339
|
+
return original === RESERVED_HASH ? RESERVED_HASH_SUBSTITUTE : original;
|
|
340
|
+
}
|
|
224
341
|
|
|
225
|
-
|
|
342
|
+
/**
|
|
343
|
+
*
|
|
344
|
+
* @param {K} k
|
|
345
|
+
* @param {V} v
|
|
346
|
+
* @param {number} hash
|
|
347
|
+
* @return {number}
|
|
348
|
+
*/
|
|
349
|
+
#allocate_entry(k, v, hash) {
|
|
226
350
|
|
|
227
|
-
|
|
351
|
+
const i = this.#entries_bound;
|
|
228
352
|
|
|
229
|
-
|
|
353
|
+
this.#entries_bound++;
|
|
230
354
|
|
|
231
|
-
|
|
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
|
+
}
|
|
232
363
|
|
|
233
|
-
|
|
364
|
+
return i;
|
|
365
|
+
}
|
|
234
366
|
|
|
235
|
-
|
|
367
|
+
/**
|
|
368
|
+
*
|
|
369
|
+
* @param {MapEntry<K,V>} entry
|
|
370
|
+
*/
|
|
371
|
+
#deallocate(entry) {
|
|
236
372
|
|
|
237
|
-
|
|
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
|
|
238
377
|
|
|
239
|
-
|
|
378
|
+
}
|
|
240
379
|
|
|
241
|
-
|
|
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
|
+
}
|
|
242
392
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
393
|
+
/**
|
|
394
|
+
*
|
|
395
|
+
* @param {K} key
|
|
396
|
+
* @param {V} value
|
|
397
|
+
*/
|
|
398
|
+
set(key, value) {
|
|
399
|
+
this.#rebuild_if_necessary();
|
|
246
400
|
|
|
247
|
-
|
|
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');
|
|
248
404
|
|
|
405
|
+
let first_deleted_bin_index = UNDEFINED_BIN_INDEX;
|
|
249
406
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
&& (entryKey === key || this.keyEqualityFunction(entryKey, key))
|
|
253
|
-
) {
|
|
407
|
+
for (; ;) {
|
|
408
|
+
const bin = this.#bins[bin_index];
|
|
254
409
|
|
|
255
|
-
|
|
410
|
+
if (bin > BIN_RESERVED_VALUE_DELETED) {
|
|
411
|
+
// bin is occupied
|
|
256
412
|
|
|
257
|
-
|
|
413
|
+
// check if it's the entry that we're looking for
|
|
414
|
+
const entry = this.#entries[bin - ENTRY_BASE];
|
|
258
415
|
|
|
259
|
-
|
|
416
|
+
if (entry_equality_check(entry, raw_hash, key, this.keyEqualityFunction)) {
|
|
417
|
+
// found the right entry
|
|
418
|
+
entry.value = value;
|
|
260
419
|
return;
|
|
420
|
+
}
|
|
421
|
+
} else if (bin === BIN_RESERVED_VALUE_EMPTY) {
|
|
422
|
+
// bin is empty
|
|
261
423
|
|
|
424
|
+
if (first_deleted_bin_index !== UNDEFINED_BIN_INDEX) {
|
|
425
|
+
// reused bin of deleted entity
|
|
426
|
+
bin_index = first_deleted_bin_index;
|
|
262
427
|
}
|
|
263
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
|
+
|
|
264
445
|
}
|
|
265
446
|
|
|
266
|
-
|
|
447
|
+
// perform secondary hashing
|
|
448
|
+
bin_index = generate_next_linear_congruential_index(bin_index, this.#bin_count_mask);
|
|
267
449
|
|
|
268
|
-
|
|
450
|
+
}
|
|
269
451
|
|
|
270
|
-
const old_size = this
|
|
452
|
+
const old_size = this.#size;
|
|
271
453
|
const new_size = old_size + 1;
|
|
272
|
-
this
|
|
454
|
+
this.#size = new_size;
|
|
273
455
|
|
|
274
456
|
// compute actual current load
|
|
275
|
-
const bucket_count = this
|
|
457
|
+
const bucket_count = this.#bin_count;
|
|
276
458
|
const load = new_size / bucket_count;
|
|
277
459
|
|
|
278
|
-
if (load > this
|
|
460
|
+
if (load > this.#load_factor) {
|
|
279
461
|
// current load is too high, increase table size
|
|
280
|
-
this
|
|
462
|
+
this.#grow();
|
|
281
463
|
}
|
|
282
464
|
}
|
|
283
465
|
|
|
@@ -287,33 +469,34 @@ export class HashMap {
|
|
|
287
469
|
* @returns {V|undefined}
|
|
288
470
|
*/
|
|
289
471
|
get(key) {
|
|
290
|
-
const raw_hash = this
|
|
291
|
-
const bucket_index = this.__compute_bucket_index(raw_hash);
|
|
292
|
-
const bucket = this.records[bucket_index];
|
|
472
|
+
const raw_hash = this.#build_key_hash(key);
|
|
293
473
|
|
|
294
|
-
|
|
474
|
+
let bin_index = this.#compute_bin_index(raw_hash);
|
|
295
475
|
|
|
296
|
-
|
|
476
|
+
for (; ;) {
|
|
477
|
+
const bin = this.#bins[bin_index];
|
|
297
478
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const entry = bucket[i];
|
|
479
|
+
if (bin > BIN_RESERVED_VALUE_DELETED) {
|
|
480
|
+
// bin is occupied
|
|
301
481
|
|
|
302
|
-
|
|
482
|
+
// check if the entry is what we're looking for
|
|
483
|
+
const entry = this.#entries[bin - ENTRY_BASE];
|
|
303
484
|
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
&& (entryKey === key || this.keyEqualityFunction(entryKey, key))
|
|
307
|
-
) {
|
|
485
|
+
if (entry_equality_check(entry, raw_hash, key, this.keyEqualityFunction)) {
|
|
486
|
+
// found the right entry
|
|
308
487
|
return entry.value;
|
|
309
|
-
|
|
310
488
|
}
|
|
489
|
+
|
|
490
|
+
} else if (bin === BIN_RESERVED_VALUE_EMPTY) {
|
|
491
|
+
// bin is empty
|
|
492
|
+
return undefined;
|
|
311
493
|
}
|
|
312
494
|
|
|
495
|
+
// perform secondary hashing
|
|
496
|
+
bin_index = generate_next_linear_congruential_index(bin_index, this.#bin_count_mask);
|
|
497
|
+
|
|
313
498
|
}
|
|
314
499
|
|
|
315
|
-
//not found
|
|
316
|
-
return undefined;
|
|
317
500
|
}
|
|
318
501
|
|
|
319
502
|
/**
|
|
@@ -357,6 +540,26 @@ export class HashMap {
|
|
|
357
540
|
return value;
|
|
358
541
|
}
|
|
359
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
|
+
}
|
|
360
563
|
|
|
361
564
|
/**
|
|
362
565
|
*
|
|
@@ -365,38 +568,49 @@ export class HashMap {
|
|
|
365
568
|
*/
|
|
366
569
|
delete(key) {
|
|
367
570
|
|
|
368
|
-
const raw_hash = this
|
|
369
|
-
|
|
370
|
-
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);
|
|
371
573
|
|
|
372
|
-
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
574
|
+
assert.isFiniteNumber(bin_index, 'hash');
|
|
375
575
|
|
|
576
|
+
const bins = this.#bins;
|
|
577
|
+
const entries = this.#entries;
|
|
376
578
|
|
|
377
|
-
|
|
579
|
+
for (; ;) {
|
|
580
|
+
const bin = bins[bin_index];
|
|
378
581
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const entry = bucket[i];
|
|
582
|
+
if (bin > BIN_RESERVED_VALUE_DELETED) {
|
|
583
|
+
// bin is occupied
|
|
382
584
|
|
|
383
|
-
|
|
384
|
-
|
|
585
|
+
// check if the entry is what we're looking for
|
|
586
|
+
const entry_index = bin - ENTRY_BASE;
|
|
587
|
+
const entry = entries[entry_index];
|
|
385
588
|
|
|
386
|
-
if (
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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;
|
|
391
603
|
}
|
|
392
604
|
|
|
393
|
-
|
|
394
|
-
|
|
605
|
+
} else if (bin === BIN_RESERVED_VALUE_EMPTY) {
|
|
606
|
+
// bin is empty
|
|
607
|
+
return false;
|
|
395
608
|
}
|
|
396
|
-
}
|
|
397
609
|
|
|
398
|
-
|
|
399
|
-
|
|
610
|
+
// perform secondary hashing
|
|
611
|
+
bin_index = generate_next_linear_congruential_index(bin_index, this.#bin_count_mask);
|
|
612
|
+
|
|
613
|
+
}
|
|
400
614
|
}
|
|
401
615
|
|
|
402
616
|
/**
|
|
@@ -408,130 +622,134 @@ export class HashMap {
|
|
|
408
622
|
verifyHashes(callback, thisArg) {
|
|
409
623
|
let all_hashes_valid = true;
|
|
410
624
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
625
|
+
const count = this.#bin_count;
|
|
626
|
+
for (let j = 0; j < count; j++) {
|
|
627
|
+
const bin = this.#bins[j];
|
|
414
628
|
|
|
415
|
-
|
|
416
|
-
|
|
629
|
+
if (bin <= BIN_RESERVED_VALUE_DELETED) {
|
|
630
|
+
// unoccupied
|
|
417
631
|
continue;
|
|
418
632
|
}
|
|
419
633
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
//check if key already exists
|
|
425
|
-
for (i = 0; i < bucketSize; i++) {
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* @type {MapEntry<K,V>}
|
|
429
|
-
*/
|
|
430
|
-
const entry = bucket[i];
|
|
634
|
+
/**
|
|
635
|
+
* @type {MapEntry<K,V>}
|
|
636
|
+
*/
|
|
637
|
+
const entry = this.#entries[bin - ENTRY_BASE];
|
|
431
638
|
|
|
432
|
-
|
|
433
|
-
|
|
639
|
+
//check hash
|
|
640
|
+
const raw_hash = this.#build_key_hash(entry.key);
|
|
434
641
|
|
|
435
|
-
|
|
436
|
-
|
|
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)
|
|
437
644
|
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const actual_bucket_index = this.__compute_bucket_index(raw_hash);
|
|
442
|
-
|
|
443
|
-
const stored_bucket_index = parseInt(h);
|
|
444
|
-
|
|
445
|
-
if (actual_bucket_index !== stored_bucket_index) {
|
|
446
|
-
callback.call(thisArg, `Hash of key has changed. old=${stored_bucket_index}, new=${actual_bucket_index}`, entry.key, entry.value);
|
|
447
|
-
|
|
448
|
-
all_hashes_valid = false;
|
|
449
|
-
}
|
|
645
|
+
all_hashes_valid = false;
|
|
450
646
|
}
|
|
647
|
+
|
|
451
648
|
}
|
|
452
649
|
|
|
453
650
|
return all_hashes_valid;
|
|
454
651
|
}
|
|
455
652
|
|
|
653
|
+
#grow() {
|
|
654
|
+
this.#setBinCount(this.#entries_allocated_count * 2);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
|
|
456
658
|
/**
|
|
457
|
-
* Rebuild table, useful for when table is resized
|
|
659
|
+
* Rebuild table, useful for when table is resized
|
|
458
660
|
*/
|
|
459
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);
|
|
460
668
|
|
|
461
|
-
let
|
|
669
|
+
let written_entries = 0;
|
|
462
670
|
|
|
463
|
-
|
|
671
|
+
for (let existing_entry_index = this.#entries_start; existing_entry_index < entries_bound; existing_entry_index++) {
|
|
672
|
+
const entry = entries[existing_entry_index];
|
|
464
673
|
|
|
465
|
-
|
|
466
|
-
const stored_bucket_index = parseInt(string_bucket_index);
|
|
674
|
+
const hash = entry.hash;
|
|
467
675
|
|
|
468
|
-
if (
|
|
676
|
+
if (hash === RESERVED_HASH) {
|
|
677
|
+
// entry is dead
|
|
469
678
|
continue;
|
|
470
679
|
}
|
|
471
680
|
|
|
472
|
-
const
|
|
681
|
+
const new_index = written_entries;
|
|
682
|
+
written_entries++;
|
|
473
683
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
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
|
+
}
|
|
479
688
|
|
|
480
|
-
|
|
481
|
-
const raw_hash = this.keyHashFunction(entry.key);
|
|
689
|
+
let bin_index = this.#compute_bin_index(hash);
|
|
482
690
|
|
|
483
|
-
|
|
691
|
+
for (; ;) {
|
|
692
|
+
const bin = bins[bin_index];
|
|
484
693
|
|
|
485
|
-
|
|
694
|
+
if (bin === BIN_RESERVED_VALUE_EMPTY) {
|
|
695
|
+
// empty slot, take it
|
|
696
|
+
bins[bin_index] = new_index + ENTRY_BASE;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
486
699
|
|
|
700
|
+
// perform secondary hashing
|
|
701
|
+
bin_index = generate_next_linear_congruential_index(bin_index, this.#bin_count_mask);
|
|
487
702
|
|
|
488
|
-
|
|
489
|
-
|
|
703
|
+
}
|
|
704
|
+
}
|
|
490
705
|
|
|
491
|
-
|
|
492
|
-
//update iterator
|
|
493
|
-
l--;
|
|
494
|
-
i--;
|
|
706
|
+
assert.equal(written_entries, this.#size, `live entries(=${written_entries}) should match size(=${this.#size})`);
|
|
495
707
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
708
|
+
this.#entries_start = 0;
|
|
709
|
+
this.#entries_bound = this.#size;
|
|
710
|
+
this.#version++;
|
|
711
|
+
}
|
|
500
712
|
|
|
501
|
-
|
|
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];
|
|
502
717
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
} else {
|
|
506
|
-
newBucket.push(entry);
|
|
507
|
-
}
|
|
718
|
+
if (entry.hash !== RESERVED_HASH) {
|
|
719
|
+
count++;
|
|
508
720
|
|
|
509
|
-
|
|
721
|
+
assert.equal(entry.hash, this.#build_key_hash(entry.key));
|
|
722
|
+
} else {
|
|
723
|
+
assert.isNull(entry.key, 'key');
|
|
510
724
|
}
|
|
511
725
|
}
|
|
726
|
+
|
|
727
|
+
return count;
|
|
512
728
|
}
|
|
513
729
|
|
|
514
730
|
forEach(callback, thisArg) {
|
|
515
|
-
|
|
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');
|
|
516
738
|
|
|
517
|
-
|
|
739
|
+
const bin = bins[j];
|
|
518
740
|
|
|
519
|
-
|
|
520
|
-
|
|
741
|
+
if (bin <= BIN_RESERVED_VALUE_DELETED) {
|
|
742
|
+
// unoccupied
|
|
521
743
|
continue;
|
|
522
744
|
}
|
|
523
745
|
|
|
524
|
-
|
|
746
|
+
/**
|
|
747
|
+
* @type {MapEntry<K,V>}
|
|
748
|
+
*/
|
|
749
|
+
const entry = entries[bin - ENTRY_BASE];
|
|
525
750
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
* @type {MapEntry<K,V>}
|
|
529
|
-
*/
|
|
530
|
-
const entry = bucket[i];
|
|
531
|
-
|
|
532
|
-
// Signature based on MDN docs of Map.prototype.forEach()
|
|
533
|
-
callback.call(thisArg, entry.value, entry.key, this);
|
|
534
|
-
}
|
|
751
|
+
// Signature based on MDN docs of Map.prototype.forEach()
|
|
752
|
+
callback.call(thisArg, entry.value, entry.key, this);
|
|
535
753
|
}
|
|
536
754
|
}
|
|
537
755
|
|
|
@@ -548,30 +766,61 @@ export class HashMap {
|
|
|
548
766
|
* Remove all data from the Map
|
|
549
767
|
*/
|
|
550
768
|
clear() {
|
|
551
|
-
|
|
552
|
-
|
|
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;
|
|
553
797
|
}
|
|
554
798
|
|
|
555
799
|
* [Symbol.iterator]() {
|
|
556
|
-
let h, i, l;
|
|
557
800
|
|
|
558
|
-
const
|
|
801
|
+
const count = this.#bin_count;
|
|
559
802
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
803
|
+
const bins = this.#bins;
|
|
804
|
+
const entries = this.#entries;
|
|
805
|
+
|
|
806
|
+
const start_version = this.#version;
|
|
564
807
|
|
|
565
|
-
|
|
808
|
+
for (let j = 0; j < count; j++) {
|
|
809
|
+
assert.equal(start_version, this.#version, 'HashMap modified during traversal');
|
|
566
810
|
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* @type {MapEntry<K,V>}
|
|
570
|
-
*/
|
|
571
|
-
const entry = bucket[i];
|
|
811
|
+
const bin = bins[j];
|
|
572
812
|
|
|
573
|
-
|
|
813
|
+
if (bin <= BIN_RESERVED_VALUE_DELETED) {
|
|
814
|
+
// unoccupied
|
|
815
|
+
continue;
|
|
574
816
|
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* @type {MapEntry<K,V>}
|
|
820
|
+
*/
|
|
821
|
+
const entry = entries[bin - ENTRY_BASE];
|
|
822
|
+
|
|
823
|
+
yield [entry.key, entry.value];
|
|
575
824
|
}
|
|
576
825
|
|
|
577
826
|
}
|