@pirireis/webglobeplugins 1.0.7 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pirireis/webglobeplugins",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "main": "index.js",
5
5
  "author": "Toprak Nihat Deniz Ozturk",
6
6
  "license": "MIT",
@@ -15,4 +15,4 @@
15
15
  "peerDependencies": {
16
16
  "@pirireis/webglobe": "6.2.51^"
17
17
  }
18
- }
18
+ }
@@ -212,7 +212,7 @@ export class TextureDemTriangles {
212
212
  if (this.globe.api_GetCurrentLODWithDecimal() < 14.5) {
213
213
  drawOnTopBegin(gl);
214
214
  }
215
- drawArrays(gl, gl.TRIANGLES, drawOptions);
215
+ drawArrays(gl, drawOptions.drawMode ?? gl.TRIANGLES, drawOptions);
216
216
  if (this.globe.api_GetCurrentLODWithDecimal() < 14.5) {
217
217
  drawOnTopEnd(gl);
218
218
  }
@@ -4,6 +4,7 @@ import { noRegisterGlobeProgramCache } from "../../../programs/programcache";
4
4
  import { PickerDisplayer } from "../../../util/picking/picker-displayer";
5
5
  import { IndexPolygonMap } from "./data/index-polygon-map";
6
6
  import { TestRecords } from "./test-records";
7
+ import { defaultblendfunction } from "../../../util/globe-default-gl-states";
7
8
  export class TerrainPolygonSemiPlugin {
8
9
  id;
9
10
  globe = null;
@@ -218,6 +219,8 @@ export class TerrainPolygonSemiPlugin {
218
219
  draw3D() {
219
220
  const gl = this.globe.gl;
220
221
  gl.disable(gl.DEPTH_TEST);
222
+ gl.enable(gl.BLEND);
223
+ defaultblendfunction(gl);
221
224
  // drawPoints
222
225
  if (this._options.showTesselationPoints) {
223
226
  this._program.draw(this._vao, this._drawPointsRangeIndexParams, this._uboHandler);
@@ -280,3 +283,23 @@ function inputCheck(input) {
280
283
  }
281
284
  return true;
282
285
  }
286
+ function getGlStates(gl) {
287
+ return {
288
+ depthTest: gl.isEnabled(gl.DEPTH_TEST),
289
+ cullFace: gl.isEnabled(gl.CULL_FACE),
290
+ blend: gl.isEnabled(gl.BLEND),
291
+ scissorTest: gl.isEnabled(gl.SCISSOR_TEST),
292
+ depthMask: gl.getParameter(gl.DEPTH_WRITEMASK),
293
+ colorMask: gl.getParameter(gl.COLOR_WRITEMASK),
294
+ frontFace: gl.getParameter(gl.FRONT_FACE),
295
+ viewport: gl.getParameter(gl.VIEWPORT),
296
+ scissorBox: gl.getParameter(gl.SCISSOR_BOX),
297
+ currentProgram: gl.getParameter(gl.CURRENT_PROGRAM),
298
+ vao: gl.getParameter(gl.VERTEX_ARRAY_BINDING),
299
+ arrayBuffer: gl.getParameter(gl.ARRAY_BUFFER_BINDING),
300
+ elementArrayBuffer: gl.getParameter(gl.ELEMENT_ARRAY_BUFFER_BINDING),
301
+ framebuffer: gl.getParameter(gl.FRAMEBUFFER_BINDING),
302
+ activeTexture: gl.getParameter(gl.ACTIVE_TEXTURE),
303
+ texture2DArray: gl.getParameter(gl.TEXTURE_BINDING_2D_ARRAY),
304
+ };
305
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * add implicit texture display program for color
3
+ * add fence on query return and return id.
4
+ */
5
+ import { textureOnCanvasProgramCache } from "../programs/draw-texture-on-canvas";
6
+ import { fence } from "./fence";
7
+ const ESCAPE_VALUE = -1;
8
+ class PickerDisplayer {
9
+ globe;
10
+ gl;
11
+ colorTexture;
12
+ indexTexture;
13
+ fbo;
14
+ _pbo;
15
+ _pboSize;
16
+ displayer;
17
+ _inProgress;
18
+ _indexType; // R32I for Integer, R32F for Float
19
+ _typedArrayConstructor;
20
+ constructor(globe, indexType = "R32I") {
21
+ this.globe = globe;
22
+ this.gl = globe.gl;
23
+ const gl = this.gl;
24
+ this.colorTexture = gl.createTexture(); // bind to color attachment 0
25
+ this.indexTexture = gl.createTexture(); // bind to color attachment 1
26
+ this.fbo = gl.createFramebuffer();
27
+ if (indexType === "R32F" && this.gl.getExtension("EXT_color_buffer_float") === null) {
28
+ throw new Error("EXT_color_buffer_float extension is required for R32F index type.");
29
+ }
30
+ this._indexType = indexType;
31
+ if (indexType === "R32I") {
32
+ this._typedArrayConstructor = Int32Array;
33
+ }
34
+ else if (indexType === "R32F") {
35
+ this._typedArrayConstructor = Float32Array;
36
+ }
37
+ else {
38
+ throw new Error("Invalid index type. Must be 'R32I' or 'R32F'.");
39
+ }
40
+ this._pbo = undefined;
41
+ this._pboSize = 0;
42
+ this._inProgress = false;
43
+ this.resize();
44
+ this.displayer = textureOnCanvasProgramCache.get(this.gl);
45
+ }
46
+ resize(width = null, height = null) {
47
+ if (width === null || height === null) {
48
+ width = this.globe.api_ScrW();
49
+ height = this.globe.api_ScrH();
50
+ }
51
+ const { gl, colorTexture, indexTexture } = this;
52
+ gl.deleteTexture(colorTexture);
53
+ gl.deleteTexture(indexTexture);
54
+ const colorTextureNew = gl.createTexture();
55
+ gl.activeTexture(gl.TEXTURE0);
56
+ gl.bindTexture(gl.TEXTURE_2D, colorTextureNew);
57
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, width, height);
58
+ const indexTextureNew = gl.createTexture();
59
+ gl.activeTexture(gl.TEXTURE1);
60
+ gl.bindTexture(gl.TEXTURE_2D, indexTextureNew);
61
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl[this._indexType], width, height); // gl.R32I or gl.R32F
62
+ gl.bindTexture(gl.TEXTURE_2D, null);
63
+ this.colorTexture = colorTextureNew;
64
+ this.indexTexture = indexTextureNew;
65
+ }
66
+ clearTextures() {
67
+ const { gl, colorTexture, indexTexture } = this;
68
+ gl.activeTexture(gl.TEXTURE1);
69
+ gl.bindTexture(gl.TEXTURE_2D, indexTexture);
70
+ if (this._indexType === "R32I") {
71
+ gl.clearBufferiv(gl.COLOR, 1, new Int32Array([-1, -1, -1, -1]));
72
+ }
73
+ else {
74
+ gl.clearBufferfv(gl.COLOR, 1, new Float32Array([-1, -1, -1, -1]));
75
+ }
76
+ gl.activeTexture(gl.TEXTURE0);
77
+ gl.bindTexture(gl.TEXTURE_2D, colorTexture);
78
+ gl.clearBufferfv(gl.COLOR, 0, new Float32Array([0, 0, 0, 0]));
79
+ }
80
+ // call before drawing the scene with gl picker shader
81
+ bindFBO() {
82
+ const { gl, colorTexture, indexTexture, fbo } = this;
83
+ gl.activeTexture(gl.TEXTURE1);
84
+ gl.bindTexture(gl.TEXTURE_2D, indexTexture);
85
+ gl.activeTexture(gl.TEXTURE0);
86
+ gl.bindTexture(gl.TEXTURE_2D, colorTexture);
87
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
88
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.colorTexture, 0);
89
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.indexTexture, 0);
90
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
91
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
92
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
93
+ throw new Error(`Framebuffer is not complete: ${status.toString(16)}`);
94
+ }
95
+ }
96
+ // call after drawing the scene with gl picker shader
97
+ drawColorTexture() {
98
+ const { gl, colorTexture } = this;
99
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
100
+ gl.disable(gl.DEPTH_TEST);
101
+ this.displayer.draw(colorTexture);
102
+ gl.enable(gl.DEPTH_TEST);
103
+ }
104
+ pickXY(x, y, selectionPointFilling = 1, callback = () => { }) {
105
+ const size = Math.pow(1 + 2 * selectionPointFilling, 2);
106
+ return this._pick(size, x - selectionPointFilling, y - selectionPointFilling, 1 + 2 * selectionPointFilling, 1 + 2 * selectionPointFilling, callback);
107
+ }
108
+ pickBbox(left, top, right, bottom, callback) {
109
+ const size = (right - left) * (bottom - top);
110
+ return this._pick(size, left, top, right - left, bottom - top, callback);
111
+ }
112
+ _pick(size, startX, startY, lengthX, lengthY, callback) {
113
+ if (this._inProgress)
114
+ return false;
115
+ this._inProgress = true;
116
+ this._initHoldBuffer(size * 4);
117
+ const { gl, _pbo } = this;
118
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, _pbo);
119
+ this.bindFBO();
120
+ gl.readBuffer(gl.COLOR_ATTACHMENT1); // This is the attachment we want to read
121
+ const format = this._indexType === "R32I" ? gl.RED_INTEGER : gl.RED;
122
+ const type = this._indexType === "R32I" ? gl.INT : gl.FLOAT;
123
+ gl.readPixels(// This will read the pixels to the buffer asynchronously
124
+ startX, startY, lengthX, lengthY, format, type, 0);
125
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
126
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
127
+ fence(this.gl).then(() => {
128
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, _pbo);
129
+ // const data = new Int16Array(size);
130
+ const data = new this._typedArrayConstructor(size); // Int32Array or Float32Array
131
+ gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data);
132
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
133
+ const result = this._pickFromBuffer(data, size);
134
+ callback(result);
135
+ // gl.deleteBuffer(pbo);
136
+ this._inProgress = false;
137
+ });
138
+ return true;
139
+ }
140
+ _pickFromBuffer(array, size) {
141
+ const selectedObjects = new Set();
142
+ for (let i = 0; i < size; i += 1) {
143
+ const id = array[i];
144
+ if (id !== ESCAPE_VALUE) {
145
+ selectedObjects.add(id);
146
+ }
147
+ }
148
+ return selectedObjects;
149
+ }
150
+ _initHoldBuffer(size) {
151
+ if (this._pbo && this._pboSize >= size) {
152
+ return;
153
+ }
154
+ const { gl } = this;
155
+ const pbo = gl.createBuffer();
156
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
157
+ gl.bufferData(gl.PIXEL_PACK_BUFFER, size, gl.STREAM_READ);
158
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
159
+ this._pboSize = size;
160
+ if (this._pbo !== undefined) {
161
+ gl.deleteBuffer(this._pbo);
162
+ }
163
+ this._pbo = pbo;
164
+ }
165
+ free() {
166
+ const { gl, colorTexture, indexTexture, fbo } = this;
167
+ gl.deleteTexture(colorTexture);
168
+ gl.deleteTexture(indexTexture);
169
+ gl.deleteFramebuffer(fbo);
170
+ if (this._pbo) {
171
+ gl.deleteBuffer(this._pbo);
172
+ }
173
+ textureOnCanvasProgramCache.release(this.gl);
174
+ }
175
+ }
176
+ export { PickerDisplayer };
@@ -17,6 +17,7 @@ class PickerDisplayer {
17
17
  _inProgress;
18
18
  _indexType; // R32I for Integer, R32F for Float
19
19
  _typedArrayConstructor;
20
+ _lastWidthHeight = null;
20
21
  constructor(globe, indexType = "R32I") {
21
22
  this.globe = globe;
22
23
  this.gl = globe.gl;
@@ -43,48 +44,109 @@ class PickerDisplayer {
43
44
  this.resize();
44
45
  this.displayer = textureOnCanvasProgramCache.get(this.gl);
45
46
  }
47
+ _saveState() {
48
+ const gl = this.gl;
49
+ const framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
50
+ const pixelPackBuffer = gl.getParameter(gl.PIXEL_PACK_BUFFER_BINDING);
51
+ const readBuffer = gl.getParameter(gl.READ_BUFFER);
52
+ const depthTestEnabled = gl.isEnabled(gl.DEPTH_TEST);
53
+ const activeTexture = gl.getParameter(gl.ACTIVE_TEXTURE);
54
+ gl.activeTexture(gl.TEXTURE0);
55
+ const tex2D_unit0 = gl.getParameter(gl.TEXTURE_BINDING_2D);
56
+ gl.activeTexture(gl.TEXTURE1);
57
+ const tex2D_unit1 = gl.getParameter(gl.TEXTURE_BINDING_2D);
58
+ // Restore active texture last
59
+ gl.activeTexture(activeTexture);
60
+ const drawBuffer0 = gl.getParameter(gl.DRAW_BUFFER0);
61
+ const drawBuffer1 = gl.getParameter(gl.DRAW_BUFFER1);
62
+ return {
63
+ framebuffer,
64
+ pixelPackBuffer,
65
+ readBuffer,
66
+ depthTestEnabled,
67
+ activeTexture,
68
+ tex2D_unit0,
69
+ tex2D_unit1,
70
+ drawBuffer0,
71
+ drawBuffer1,
72
+ };
73
+ }
74
+ _restoreState(s) {
75
+ const gl = this.gl;
76
+ gl.bindFramebuffer(gl.FRAMEBUFFER, s.framebuffer);
77
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, s.pixelPackBuffer);
78
+ gl.readBuffer(s.readBuffer);
79
+ if (s.depthTestEnabled)
80
+ gl.enable(gl.DEPTH_TEST);
81
+ else
82
+ gl.disable(gl.DEPTH_TEST);
83
+ gl.activeTexture(gl.TEXTURE0);
84
+ gl.bindTexture(gl.TEXTURE_2D, s.tex2D_unit0);
85
+ gl.activeTexture(gl.TEXTURE1);
86
+ gl.bindTexture(gl.TEXTURE_2D, s.tex2D_unit1);
87
+ gl.activeTexture(s.activeTexture);
88
+ // drawBuffers restore: default framebuffer expects BACK/NONE (implementation details vary),
89
+ // so only restore the number of buffers that makes sense for the bound FB.
90
+ if (s.framebuffer === null) {
91
+ gl.drawBuffers([s.drawBuffer0]);
92
+ }
93
+ else {
94
+ gl.drawBuffers([s.drawBuffer0, s.drawBuffer1]);
95
+ }
96
+ }
46
97
  resize(width = null, height = null) {
47
98
  if (width === null || height === null) {
48
99
  width = this.globe.api_ScrW();
49
100
  height = this.globe.api_ScrH();
50
101
  }
51
- const { gl, colorTexture, indexTexture } = this;
102
+ if (this._lastWidthHeight &&
103
+ this._lastWidthHeight.width === width &&
104
+ this._lastWidthHeight.height === height) {
105
+ return;
106
+ }
107
+ this._lastWidthHeight = { width, height };
108
+ const gl = this.gl;
109
+ const { colorTexture, indexTexture } = this;
52
110
  gl.deleteTexture(colorTexture);
53
111
  gl.deleteTexture(indexTexture);
54
112
  const colorTextureNew = gl.createTexture();
55
- gl.activeTexture(gl.TEXTURE0);
56
113
  gl.bindTexture(gl.TEXTURE_2D, colorTextureNew);
57
114
  gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, width, height);
115
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
116
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
58
117
  const indexTextureNew = gl.createTexture();
59
- gl.activeTexture(gl.TEXTURE1);
60
118
  gl.bindTexture(gl.TEXTURE_2D, indexTextureNew);
61
- gl.texStorage2D(gl.TEXTURE_2D, 1, gl[this._indexType], width, height); // gl.R32I or gl.R32F
119
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl[this._indexType], width, height); // gl.R32I or gl.R32F
120
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
121
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
62
122
  gl.bindTexture(gl.TEXTURE_2D, null);
63
123
  this.colorTexture = colorTextureNew;
64
124
  this.indexTexture = indexTextureNew;
65
125
  }
66
126
  clearTextures() {
67
- const { gl, colorTexture, indexTexture } = this;
68
- gl.activeTexture(gl.TEXTURE1);
69
- gl.bindTexture(gl.TEXTURE_2D, indexTexture);
70
- if (this._indexType === "R32I") {
71
- gl.clearBufferiv(gl.COLOR, 1, new Int32Array([-1, -1, -1, -1]));
127
+ const gl = this.gl;
128
+ const saved = this._saveState();
129
+ try {
130
+ // Must clear the *FBO attachments*, not "textures".
131
+ this.bindFBO();
132
+ if (this._indexType === "R32I") {
133
+ gl.clearBufferiv(gl.COLOR, 1, new Int32Array([-1, -1, -1, -1]));
134
+ }
135
+ else {
136
+ gl.clearBufferfv(gl.COLOR, 1, new Float32Array([-1, -1, -1, -1]));
137
+ }
138
+ gl.clearBufferfv(gl.COLOR, 0, new Float32Array([0, 0, 0, 0]));
72
139
  }
73
- else {
74
- gl.clearBufferfv(gl.COLOR, 1, new Float32Array([-1, -1, -1, -1]));
140
+ finally {
141
+ this._restoreState(saved);
75
142
  }
76
- gl.activeTexture(gl.TEXTURE0);
77
- gl.bindTexture(gl.TEXTURE_2D, colorTexture);
78
- gl.clearBufferfv(gl.COLOR, 0, new Float32Array([0, 0, 0, 0]));
79
143
  }
80
144
  // call before drawing the scene with gl picker shader
81
145
  bindFBO() {
82
- const { gl, colorTexture, indexTexture, fbo } = this;
83
- gl.activeTexture(gl.TEXTURE1);
84
- gl.bindTexture(gl.TEXTURE_2D, indexTexture);
85
- gl.activeTexture(gl.TEXTURE0);
86
- gl.bindTexture(gl.TEXTURE_2D, colorTexture);
146
+ const { gl, fbo } = this;
147
+ this.resize();
87
148
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
149
+ // No need to bind textures or change ACTIVE_TEXTURE for framebufferTexture2D.
88
150
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.colorTexture, 0);
89
151
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.indexTexture, 0);
90
152
  gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
@@ -95,11 +157,16 @@ class PickerDisplayer {
95
157
  }
96
158
  // call after drawing the scene with gl picker shader
97
159
  drawColorTexture() {
98
- const { gl, colorTexture } = this;
99
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
100
- gl.disable(gl.DEPTH_TEST);
101
- this.displayer.draw(colorTexture);
102
- gl.enable(gl.DEPTH_TEST);
160
+ const gl = this.gl;
161
+ const saved = this._saveState();
162
+ try {
163
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
164
+ gl.disable(gl.DEPTH_TEST);
165
+ this.displayer.draw(this.colorTexture);
166
+ }
167
+ finally {
168
+ this._restoreState(saved);
169
+ }
103
170
  }
104
171
  pickXY(x, y, selectionPointFilling = 1, callback = () => { }) {
105
172
  const size = Math.pow(1 + 2 * selectionPointFilling, 2);
@@ -113,29 +180,44 @@ class PickerDisplayer {
113
180
  if (this._inProgress)
114
181
  return false;
115
182
  this._inProgress = true;
116
- this._initHoldBuffer(size * 4);
117
- const { gl, _pbo } = this;
118
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, _pbo);
119
- this.bindFBO();
120
- gl.readBuffer(gl.COLOR_ATTACHMENT1); // This is the attachment we want to read
121
- const format = this._indexType === "R32I" ? gl.RED_INTEGER : gl.RED;
122
- const type = this._indexType === "R32I" ? gl.INT : gl.FLOAT;
123
- gl.readPixels(// This will read the pixels to the buffer asynchronously
124
- startX, startY, lengthX, lengthY, format, type, 0);
125
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
126
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
127
- fence(this.gl).then(() => {
183
+ const gl = this.gl;
184
+ const saved = this._saveState();
185
+ try {
186
+ this._initHoldBuffer(size * 4);
187
+ const { _pbo } = this;
128
188
  gl.bindBuffer(gl.PIXEL_PACK_BUFFER, _pbo);
129
- // const data = new Int16Array(size);
130
- const data = new this._typedArrayConstructor(size); // Int32Array or Float32Array
131
- gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data);
132
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
133
- const result = this._pickFromBuffer(data, size);
134
- callback(result);
135
- // gl.deleteBuffer(pbo);
189
+ this.bindFBO();
190
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
191
+ const format = this._indexType === "R32I" ? gl.RED_INTEGER : gl.RED;
192
+ const type = this._indexType === "R32I" ? gl.INT : gl.FLOAT;
193
+ gl.readPixels(startX, startY, lengthX, lengthY, format, type, 0);
194
+ // Restore state immediately after issuing readPixels (PBO keeps data for later getBufferSubData)
195
+ this._restoreState(saved);
196
+ fence(this.gl)
197
+ .then(() => {
198
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, _pbo);
199
+ const data = new this._typedArrayConstructor(size);
200
+ gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data);
201
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
202
+ const result = this._pickFromBuffer(data, size);
203
+ callback(result);
204
+ })
205
+ .catch(() => {
206
+ // Swallow or rethrow based on your app needs; important is not to wedge _inProgress.
207
+ })
208
+ .finally(() => {
209
+ this._inProgress = false;
210
+ });
211
+ return true;
212
+ }
213
+ catch {
214
+ this._restoreState(saved);
136
215
  this._inProgress = false;
137
- });
138
- return true;
216
+ throw new Error("Picking failed.");
217
+ }
218
+ finally {
219
+ this._restoreState(saved);
220
+ }
139
221
  }
140
222
  _pickFromBuffer(array, size) {
141
223
  const selectedObjects = new Set();