• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 11648280104

03 Nov 2024 03:55AM UTC coverage: 90.187% (-0.03%) from 90.219%
11648280104

push

github

web-flow
fix: [#3078] Pointer lastWorldPosition updates when the camera does (#3245)


Closes #3078

5863 of 7461 branches covered (78.58%)

15 of 17 new or added lines in 5 files covered. (88.24%)

13 existing lines in 2 files now uncovered.

12848 of 14246 relevant lines covered (90.19%)

25261.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.66
/src/engine/Input/PointerEventReceiver.ts
1
import { Engine, ScrollPreventionMode } from '../Engine';
2
import { GlobalCoordinates } from '../Math/global-coordinates';
3
import { vec, Vector } from '../Math/vector';
4
import { PointerEvent } from './PointerEvent';
5
import { WheelEvent } from './WheelEvent';
6
import { PointerAbstraction } from './PointerAbstraction';
7

8
import { WheelDeltaMode } from './WheelDeltaMode';
9
import { PointerSystem } from './PointerSystem';
10
import { NativePointerButton } from './NativePointerButton';
11
import { PointerButton } from './PointerButton';
12
import { fail } from '../Util/Util';
13
import { PointerType } from './PointerType';
14
import { isCrossOriginIframe } from '../Util/IFrame';
15
import { EventEmitter, EventKey, Handler, Subscription } from '../EventEmitter';
16

17
export type NativePointerEvent = globalThis.PointerEvent;
18
export type NativeMouseEvent = globalThis.MouseEvent;
19
export type NativeTouchEvent = globalThis.TouchEvent;
20
export type NativeWheelEvent = globalThis.WheelEvent;
21

22
export type PointerEvents = {
23
  move: PointerEvent;
24
  down: PointerEvent;
25
  up: PointerEvent;
26
  wheel: WheelEvent;
27
};
28

29
export const PointerEvents = {
1✔
30
  Move: 'move',
31
  Down: 'down',
32
  Up: 'up',
33
  Wheel: 'wheel'
34
};
35

36
/**
37
 * Is this event a native touch event?
38
 */
39
function isTouchEvent(value: any): value is NativeTouchEvent {
40
  // Guard for Safari <= 13.1
41
  return globalThis.TouchEvent && value instanceof globalThis.TouchEvent;
232✔
42
}
43

44
/**
45
 * Is this event a native pointer event
46
 */
47
function isPointerEvent(value: any): value is NativePointerEvent {
48
  // Guard for Safari <= 13.1
49
  return globalThis.PointerEvent && value instanceof globalThis.PointerEvent;
232✔
50
}
51

52
export interface PointerInitOptions {
53
  grabWindowFocus?: boolean;
54
}
55

56
/**
57
 * The PointerEventProcessor is responsible for collecting all the events from the canvas and transforming them into GlobalCoordinates
58
 */
59
export class PointerEventReceiver {
60
  public events = new EventEmitter<PointerEvents>();
1,328✔
61
  public primary: PointerAbstraction = new PointerAbstraction();
1,328✔
62

63
  private _activeNativePointerIdsToNormalized = new Map<number, number>();
1,328✔
64
  public lastFramePointerCoords = new Map<number, GlobalCoordinates>();
1,328✔
65
  public currentFramePointerCoords = new Map<number, GlobalCoordinates>();
1,328✔
66

67
  public currentFramePointerDown = new Map<number, boolean>();
1,328✔
68
  public lastFramePointerDown = new Map<number, boolean>();
1,328✔
69

70
  public currentFrameDown: PointerEvent[] = [];
1,328✔
71
  public currentFrameUp: PointerEvent[] = [];
1,328✔
72
  public currentFrameMove: PointerEvent[] = [];
1,328✔
73
  public currentFrameCancel: PointerEvent[] = [];
1,328✔
74
  public currentFrameWheel: WheelEvent[] = [];
1,328✔
75

76
  private _enabled = true;
1,328✔
77

78
  constructor(
79
    public readonly target: GlobalEventHandlers & EventTarget,
1,328✔
80
    public engine: Engine
1,328✔
81
  ) {}
82

83
  public toggleEnabled(enabled: boolean) {
84
    this._enabled = enabled;
1,752✔
85
  }
86

87
  /**
88
   * Creates a new PointerEventReceiver with a new target and engine while preserving existing pointer event
89
   * handlers.
90
   * @param target
91
   * @param engine
92
   */
93
  public recreate(target: GlobalEventHandlers & EventTarget, engine: Engine) {
94
    const eventReceiver = new PointerEventReceiver(target, engine);
1✔
95
    eventReceiver.primary = this.primary;
1✔
96
    eventReceiver._pointers = this._pointers;
1✔
97
    return eventReceiver;
1✔
98
  }
99

100
  private _pointers: PointerAbstraction[] = [this.primary];
1,328✔
101
  /**
102
   * Locates a specific pointer by id, creates it if it doesn't exist
103
   * @param index
104
   */
105
  public at(index: number): PointerAbstraction {
106
    if (index >= this._pointers.length) {
89!
107
      // Ensure there is a pointer to retrieve
108
      for (let i = this._pointers.length - 1, max = index; i < max; i++) {
×
109
        this._pointers.push(new PointerAbstraction());
×
110
      }
111
    }
112
    return this._pointers[index];
89✔
113
  }
114

115
  /**
116
   * The number of pointers currently being tracked by excalibur
117
   */
118
  public count(): number {
119
    return this._pointers.length;
×
120
  }
121

122
  /**
123
   * Is the specified pointer id down this frame
124
   * @param pointerId
125
   */
126
  public isDown(pointerId: number) {
127
    if (!this._enabled) {
91!
128
      return false;
×
129
    }
130
    return this.currentFramePointerDown.get(pointerId) ?? false;
91✔
131
  }
132

133
  /**
134
   * Was the specified pointer id down last frame
135
   * @param pointerId
136
   */
137
  public wasDown(pointerId: number) {
138
    if (!this._enabled) {
35!
139
      return false;
×
140
    }
141
    return this.lastFramePointerDown.get(pointerId) ?? false;
35✔
142
  }
143

144
  /**
145
   * Whether the Pointer is currently dragging.
146
   */
147
  public isDragging(pointerId: number): boolean {
148
    if (!this._enabled) {
55!
149
      return false;
×
150
    }
151
    return this.isDown(pointerId);
55✔
152
  }
153

154
  /**
155
   * Whether the Pointer just started dragging.
156
   */
157
  public isDragStart(pointerId: number): boolean {
158
    if (!this._enabled) {
27!
159
      return false;
×
160
    }
161
    return this.isDown(pointerId) && !this.wasDown(pointerId);
27✔
162
  }
163

164
  /**
165
   * Whether the Pointer just ended dragging.
166
   */
167
  public isDragEnd(pointerId: number): boolean {
168
    if (!this._enabled) {
9!
169
      return false;
×
170
    }
171
    return !this.isDown(pointerId) && this.wasDown(pointerId);
9✔
172
  }
173

174
  public emit<TEventName extends EventKey<PointerEvents>>(eventName: TEventName, event: PointerEvents[TEventName]): void;
175
  public emit(eventName: string, event?: any): void;
176
  public emit<TEventName extends EventKey<PointerEvents> | string>(eventName: TEventName, event?: any): void {
177
    this.events.emit(eventName, event);
86✔
178
  }
179

180
  public on<TEventName extends EventKey<PointerEvents>>(eventName: TEventName, handler: Handler<PointerEvents[TEventName]>): Subscription;
181
  public on(eventName: string, handler: Handler<unknown>): Subscription;
182
  public on<TEventName extends EventKey<PointerEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
183
    return this.events.on(eventName, handler);
8✔
184
  }
185

186
  public once<TEventName extends EventKey<PointerEvents>>(eventName: TEventName, handler: Handler<PointerEvents[TEventName]>): Subscription;
187
  public once(eventName: string, handler: Handler<unknown>): Subscription;
188
  public once<TEventName extends EventKey<PointerEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
189
    return this.events.once(eventName, handler);
×
190
  }
191

192
  public off<TEventName extends EventKey<PointerEvents>>(eventName: TEventName, handler: Handler<PointerEvents[TEventName]>): void;
193
  public off(eventName: string, handler: Handler<unknown>): void;
194
  public off(eventName: string): void;
195
  public off<TEventName extends EventKey<PointerEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
196
    this.events.off(eventName, handler);
×
197
  }
198

199
  /**
200
   * Called internally by excalibur
201
   *
202
   * Updates the current frame pointer info and emits raw pointer events
203
   *
204
   * This does not emit events to entities, see PointerSystem
205
   * @internal
206
   */
207
  public update() {
208
    this.lastFramePointerDown = new Map(this.currentFramePointerDown);
3,338✔
209
    this.lastFramePointerCoords = new Map(this.currentFramePointerCoords);
3,338✔
210

211
    for (const event of this.currentFrameDown) {
3,338✔
212
      if (!event.active) {
47✔
213
        continue;
2✔
214
      }
215
      this.emit('down', event);
45✔
216
      const pointer = this.at(event.pointerId);
45✔
217
      pointer.emit('down', event);
45✔
218
      this.primary.emit('pointerdown', event);
45✔
219
    }
220

221
    for (const event of this.currentFrameUp) {
3,338✔
222
      if (!event.active) {
15✔
223
        continue;
1✔
224
      }
225
      this.emit('up', event);
14✔
226
      const pointer = this.at(event.pointerId);
14✔
227
      pointer.emit('up', event);
14✔
228
    }
229

230
    for (const event of this.currentFrameMove) {
3,338✔
231
      if (!event.active) {
20✔
232
        continue;
1✔
233
      }
234
      this.emit('move', event);
19✔
235
      const pointer = this.at(event.pointerId);
19✔
236
      pointer.emit('move', event);
19✔
237
    }
238

239
    for (const event of this.currentFrameCancel) {
3,338✔
240
      if (!event.active) {
2!
241
        continue;
×
242
      }
243
      this.emit('cancel', event);
2✔
244
      const pointer = this.at(event.pointerId);
2✔
245
      pointer.emit('cancel', event);
2✔
246
    }
247

248
    for (const event of this.currentFrameWheel) {
3,338✔
249
      if (!event.active) {
4✔
250
        continue;
1✔
251
      }
252
      this.emit('pointerwheel', event);
3✔
253
      this.emit('wheel', event);
3✔
254
      this.primary.emit('pointerwheel', event);
3✔
255
      this.primary.emit('wheel', event);
3✔
256
    }
257

258
    if (this.engine.currentScene.camera.hasChanged()) {
3,338✔
259
      for (const pointer of this._pointers) {
974✔
260
        pointer._updateWorldPosition(this.engine);
974✔
261
      }
262
    }
263
  }
264

265
  /**
266
   * Clears the current frame event and pointer data
267
   */
268
  public clear() {
269
    for (const event of this.currentFrameUp) {
3,374✔
270
      this.currentFramePointerCoords.delete(event.pointerId);
15✔
271
      const ids = this._activeNativePointerIdsToNormalized.entries();
15✔
272
      for (const [native, normalized] of ids) {
15✔
273
        if (normalized === event.pointerId) {
11!
274
          this._activeNativePointerIdsToNormalized.delete(native);
11✔
275
        }
276
      }
277
    }
278
    this.currentFrameDown.length = 0;
3,374✔
279
    this.currentFrameUp.length = 0;
3,374✔
280
    this.currentFrameMove.length = 0;
3,374✔
281
    this.currentFrameCancel.length = 0;
3,374✔
282
    this.currentFrameWheel.length = 0;
3,374✔
283
  }
284

285
  private _boundHandle = this._handle.bind(this);
1,328✔
286
  private _boundWheel = this._handleWheel.bind(this);
1,328✔
287
  /**
288
   * Initializes the pointer event receiver so that it can start listening to native
289
   * browser events.
290
   */
291
  public init(options?: PointerInitOptions) {
292
    if (this.engine.isDisposed()) {
1,328!
UNCOV
293
      return;
×
294
    }
295
    // Disabling the touch action avoids browser/platform gestures from firing on the canvas
296
    // It is important on mobile to have touch action 'none'
297
    // https://stackoverflow.com/questions/48124372/pointermove-event-not-working-with-touch-why-not
298
    if (this.target === this.engine.canvas) {
1,328✔
299
      this.engine.canvas.style.touchAction = 'none';
1,298✔
300
    } else {
301
      document.body.style.touchAction = 'none';
30✔
302
    }
303
    // Preferred pointer events
304
    if (window.PointerEvent) {
1,328!
305
      this.target.addEventListener('pointerdown', this._boundHandle);
1,328✔
306
      this.target.addEventListener('pointerup', this._boundHandle);
1,328✔
307
      this.target.addEventListener('pointermove', this._boundHandle);
1,328✔
308
      this.target.addEventListener('pointercancel', this._boundHandle);
1,328✔
309
    } else {
310
      // Touch Events
311
      this.target.addEventListener('touchstart', this._boundHandle);
×
312
      this.target.addEventListener('touchend', this._boundHandle);
×
313
      this.target.addEventListener('touchmove', this._boundHandle);
×
314
      this.target.addEventListener('touchcancel', this._boundHandle);
×
315

316
      // Mouse Events
317
      this.target.addEventListener('mousedown', this._boundHandle);
×
318
      this.target.addEventListener('mouseup', this._boundHandle);
×
319
      this.target.addEventListener('mousemove', this._boundHandle);
×
320
    }
321

322
    // MDN MouseWheelEvent
323
    const wheelOptions = {
1,328✔
324
      passive: !(
325
        this.engine.pageScrollPreventionMode === ScrollPreventionMode.All ||
2,656✔
326
        this.engine.pageScrollPreventionMode === ScrollPreventionMode.Canvas
327
      )
328
    };
329
    if ('onwheel' in document.createElement('div')) {
1,328!
330
      // Modern Browsers
331
      this.target.addEventListener('wheel', this._boundWheel, wheelOptions);
1,328✔
332
    } else if (document.onmousewheel !== undefined) {
×
333
      // Webkit and IE
334
      this.target.addEventListener('mousewheel', this._boundWheel, wheelOptions);
×
335
    } else {
336
      // Remaining browser and older Firefox
337
      this.target.addEventListener('MozMousePixelScroll', this._boundWheel, wheelOptions);
×
338
    }
339

340
    const grabWindowFocus = options?.grabWindowFocus ?? true;
1,328✔
341
    // Handle cross origin iframe
342
    if (grabWindowFocus && isCrossOriginIframe()) {
1,328!
343
      const grabFocus = () => {
×
344
        window.focus();
×
345
      };
346
      // Preferred pointer events
347
      if (window.PointerEvent) {
×
348
        this.target.addEventListener('pointerdown', grabFocus);
×
349
      } else {
350
        // Touch Events
351
        this.target.addEventListener('touchstart', grabFocus);
×
352

353
        // Mouse Events
354
        this.target.addEventListener('mousedown', grabFocus);
×
355
      }
356
    }
357
  }
358

359
  public detach() {
360
    // Preferred pointer events
361
    if (window.PointerEvent) {
1!
362
      this.target.removeEventListener('pointerdown', this._boundHandle);
1✔
363
      this.target.removeEventListener('pointerup', this._boundHandle);
1✔
364
      this.target.removeEventListener('pointermove', this._boundHandle);
1✔
365
      this.target.removeEventListener('pointercancel', this._boundHandle);
1✔
366
    } else {
367
      // Touch Events
368
      this.target.removeEventListener('touchstart', this._boundHandle);
×
369
      this.target.removeEventListener('touchend', this._boundHandle);
×
370
      this.target.removeEventListener('touchmove', this._boundHandle);
×
371
      this.target.removeEventListener('touchcancel', this._boundHandle);
×
372

373
      // Mouse Events
374
      this.target.removeEventListener('mousedown', this._boundHandle);
×
375
      this.target.removeEventListener('mouseup', this._boundHandle);
×
376
      this.target.removeEventListener('mousemove', this._boundHandle);
×
377
    }
378

379
    if ('onwheel' in document.createElement('div')) {
1!
380
      // Modern Browsers
381
      this.target.removeEventListener('wheel', this._boundWheel);
1✔
382
    } else if (document.onmousewheel !== undefined) {
×
383
      // Webkit and IE
384
      this.target.addEventListener('mousewheel', this._boundWheel);
×
385
    } else {
386
      // Remaining browser and older Firefox
387
      this.target.addEventListener('MozMousePixelScroll', this._boundWheel);
×
388
    }
389
  }
390

391
  /**
392
   * Take native pointer id and map it to index in active pointers
393
   * @param nativePointerId
394
   */
395
  private _normalizePointerId(nativePointerId: number) {
396
    // Add to the the native pointer set id
397
    this._activeNativePointerIdsToNormalized.set(nativePointerId, -1);
232✔
398

399
    // Native pointer ids in ascending order
400
    const currentPointerIds = Array.from(this._activeNativePointerIdsToNormalized.keys()).sort((a, b) => a - b);
232✔
401

402
    // The index into sorted ids will be the new id, will always have an id
403
    const id = currentPointerIds.findIndex((p) => p === nativePointerId);
232✔
404

405
    // Save the mapping so we can reverse it later
406
    this._activeNativePointerIdsToNormalized.set(nativePointerId, id);
232✔
407

408
    // ignore pointer because game isn't watching
409
    return id;
232✔
410
  }
411

412
  /**
413
   * Responsible for handling and parsing pointer events
414
   */
415
  private _handle(ev: NativeTouchEvent | NativePointerEvent | NativeMouseEvent) {
416
    if (!this._enabled) {
380✔
417
      return;
148✔
418
    }
419
    ev.preventDefault();
232✔
420
    const eventCoords = new Map<number, GlobalCoordinates>();
232✔
421
    let button: PointerButton;
422
    let pointerType: PointerType;
423
    if (isTouchEvent(ev)) {
232!
424
      button = PointerButton.Unknown;
×
425
      pointerType = PointerType.Touch;
×
426
      // https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent
427
      for (let i = 0; i < ev.changedTouches.length; i++) {
×
428
        const touch = ev.changedTouches[i];
×
429
        const coordinates = GlobalCoordinates.fromPagePosition(touch.pageX, touch.pageY, this.engine);
×
430
        const nativePointerId = i + 1;
×
431
        const pointerId = this._normalizePointerId(nativePointerId);
×
432
        this.currentFramePointerCoords.set(pointerId, coordinates);
×
433
        eventCoords.set(pointerId, coordinates);
×
434
      }
435
    } else {
436
      button = this._nativeButtonToPointerButton(ev.button);
232✔
437
      pointerType = PointerType.Mouse;
232✔
438
      const coordinates = GlobalCoordinates.fromPagePosition(ev.pageX, ev.pageY, this.engine);
232✔
439
      let nativePointerId = 1;
232✔
440
      if (isPointerEvent(ev)) {
232!
441
        nativePointerId = ev.pointerId;
232✔
442
        pointerType = this._stringToPointerType(ev.pointerType);
232✔
443
      }
444
      const pointerId = this._normalizePointerId(nativePointerId);
232✔
445
      this.currentFramePointerCoords.set(pointerId, coordinates);
232✔
446
      eventCoords.set(pointerId, coordinates);
232✔
447
    }
448

449
    for (const [pointerId, coord] of eventCoords.entries()) {
232✔
450
      switch (ev.type) {
232✔
451
        case 'mousedown':
462!
452
        case 'pointerdown':
453
        case 'touchstart':
454
          this.currentFrameDown.push(new PointerEvent('down', pointerId, button, pointerType, coord, ev));
138✔
455
          this.currentFramePointerDown.set(pointerId, true);
138✔
456
          break;
138✔
457
        case 'mouseup':
458
        case 'pointerup':
459
        case 'touchend':
460
          this.currentFrameUp.push(new PointerEvent('up', pointerId, button, pointerType, coord, ev));
29✔
461
          this.currentFramePointerDown.set(pointerId, false);
29✔
462
          break;
29✔
463
        case 'mousemove':
464
        case 'pointermove':
465
        case 'touchmove':
466
          this.currentFrameMove.push(new PointerEvent('move', pointerId, button, pointerType, coord, ev));
63✔
467
          break;
63✔
468
        case 'touchcancel':
469
        case 'pointercancel':
470
          this.currentFrameCancel.push(new PointerEvent('cancel', pointerId, button, pointerType, coord, ev));
2✔
471
          break;
2✔
472
      }
473
    }
474
  }
475

476
  private _handleWheel(ev: NativeWheelEvent) {
477
    if (!this._enabled) {
28✔
478
      return;
12✔
479
    }
480
    // Should we prevent page scroll because of this event
481
    if (
16!
482
      this.engine.pageScrollPreventionMode === ScrollPreventionMode.All ||
48✔
483
      (this.engine.pageScrollPreventionMode === ScrollPreventionMode.Canvas && ev.target === this.engine.canvas)
484
    ) {
485
      ev.preventDefault();
×
486
    }
487
    const screen = this.engine.screen.pageToScreenCoordinates(vec(ev.pageX, ev.pageY));
16✔
488
    const world = this.engine.screen.screenToWorldCoordinates(screen);
16✔
489

490
    /**
491
     * A constant used to normalize wheel events across different browsers
492
     *
493
     * This normalization factor is pulled from
494
     * https://developer.mozilla.org/en-US/docs/Web/Events/wheel#Listening_to_this_event_across_browser
495
     */
496
    const ScrollWheelNormalizationFactor = -1 / 40;
16✔
497

498
    const deltaX = ev.deltaX || ev.wheelDeltaX * ScrollWheelNormalizationFactor || 0;
16✔
499
    const deltaY =
500
      ev.deltaY || ev.wheelDeltaY * ScrollWheelNormalizationFactor || ev.wheelDelta * ScrollWheelNormalizationFactor || ev.detail || 0;
16✔
501
    const deltaZ = ev.deltaZ || 0;
16✔
502
    let deltaMode = WheelDeltaMode.Pixel;
16✔
503

504
    if (ev.deltaMode) {
16!
505
      if (ev.deltaMode === 1) {
×
506
        deltaMode = WheelDeltaMode.Line;
×
507
      } else if (ev.deltaMode === 2) {
×
508
        deltaMode = WheelDeltaMode.Page;
×
509
      }
510
    }
511

512
    const we = new WheelEvent(world.x, world.y, ev.pageX, ev.pageY, screen.x, screen.y, 0, deltaX, deltaY, deltaZ, deltaMode, ev);
16✔
513
    this.currentFrameWheel.push(we);
16✔
514
  }
515

516
  /**
517
   * Triggers an excalibur pointer event in a world space pos
518
   *
519
   * Useful for testing pointers in excalibur
520
   * @param type
521
   * @param pos
522
   */
523
  public triggerEvent(type: 'down' | 'up' | 'move' | 'cancel', pos: Vector) {
524
    const page = this.engine.screen.worldToPageCoordinates(pos);
26✔
525
    // Send an event to the event receiver
526
    if (window.PointerEvent) {
26!
527
      this._handle(
26✔
528
        new window.PointerEvent('pointer' + type, {
529
          pointerId: 0,
530
          clientX: page.x,
531
          clientY: page.y
532
        })
533
      );
534
    } else {
535
      // Safari hack
536
      this._handle(
×
537
        new window.MouseEvent('mouse' + type, {
538
          clientX: page.x,
539
          clientY: page.y
540
        })
541
      );
542
    }
543

544
    // Force update pointer system
545
    const pointerSystem = this.engine.currentScene.world.get(PointerSystem);
26✔
546
    pointerSystem.preupdate(this.engine.currentScene, 1);
26✔
547
    pointerSystem.update(1);
26✔
548
  }
549

550
  private _nativeButtonToPointerButton(s: NativePointerButton): PointerButton {
551
    switch (s) {
232✔
552
      case NativePointerButton.NoButton:
232!
553
        return PointerButton.NoButton;
×
554
      case NativePointerButton.Left:
555
        return PointerButton.Left;
210✔
556
      case NativePointerButton.Middle:
557
        return PointerButton.Middle;
11✔
558
      case NativePointerButton.Right:
559
        return PointerButton.Right;
11✔
560
      case NativePointerButton.Unknown:
561
        return PointerButton.Unknown;
×
562
      default:
563
        return fail(s);
×
564
    }
565
  }
566

567
  private _stringToPointerType(s: string) {
568
    switch (s) {
232✔
569
      case 'touch':
232!
570
        return PointerType.Touch;
×
571
      case 'mouse':
572
        return PointerType.Mouse;
×
573
      case 'pen':
574
        return PointerType.Pen;
×
575
      default:
576
        return PointerType.Unknown;
232✔
577
    }
578
  }
579
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc