Skip to content

Commit 9975c04

Browse files
phoenixbfgkjohnson
andauthored
AF Manager class for dynamic animation frame request/cancel (#1407)
* AFManager class for dynamic animation frame request/cancel * spacing * spacing * rename class * Add pending AF management * minor comment * spacing * FrameScheduler referenced by LRUCache,PriorityQueue, throttle and QueryManager; updated Dingo Gap VR sample * camel casing; intialization in LRU, PriorityQueue and QueryManager * - replace "removeXRSession" with "setXRSession( null )" - remove unused function - remove use of "window" - ensure "flushPending" clears all pending handles - code style update * Add tests for FrameScheduler * small cleanup * comment * variable rename * initial commit for singleton framescheduler * fix spaces * Make the FrameScheduler a singleton * Fix tsts * Update docs * FrameScheduler -> Scheduler * Update the vrDemo * Add d.ts --------- Co-authored-by: Garrett Johnson <garrett.kjohnson@gmail.com>
1 parent f20e37f commit 9975c04

10 files changed

Lines changed: 300 additions & 40 deletions

File tree

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export default [
6767
'@typescript-eslint/no-unused-vars': [ 'error', { args: 'none' } ],
6868
'@typescript-eslint/no-explicit-any': 'off',
6969
'@typescript-eslint/no-empty-object-type': 'off',
70+
'@typescript-eslint/no-unsafe-function-type': 'off',
7071
},
7172
},
7273

example/three/vr.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TilesRenderer } from '3d-tiles-renderer';
1+
import { TilesRenderer, Scheduler } from '3d-tiles-renderer';
22
import {
33
Scene,
44
DirectionalLight,
@@ -98,18 +98,6 @@ function init() {
9898
tiles.setCamera( camera );
9999
tiles.setResolutionFromRenderer( camera, renderer );
100100

101-
102-
// We define a custom scheduling callback to handle also active WebXR sessions
103-
const tilesSchedulingCB = func => {
104-
105-
tasks.push( func );
106-
107-
};
108-
109-
// We set our scheduling callback for tiles downloading and parsing
110-
tiles.downloadQueue.schedulingCallback = tilesSchedulingCB;
111-
tiles.parseQueue.schedulingCallback = tilesSchedulingCB;
112-
113101
tiles.lruCache.maxSize = 1200;
114102
tiles.lruCache.minSize = 900;
115103

@@ -230,6 +218,8 @@ function handleCamera() {
230218

231219
xrSession = renderer.xr.getSession();
232220

221+
Scheduler.setXRSession( xrSession );
222+
233223
}
234224

235225
} else {
@@ -246,6 +236,8 @@ function handleCamera() {
246236

247237
xrSession = null;
248238

239+
Scheduler.setXRSession( null );
240+
249241
}
250242

251243
}

src/core/renderer/API.md

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,15 @@ Priority queue for scheduling async work with a concurrency limit. Items are
539539
sorted by `priorityCallback` and dispatched up to `maxJobs` at a time.
540540

541541

542+
### .running
543+
544+
```js
545+
readonly running: boolean
546+
```
547+
548+
returns whether tasks are queued or actively running
549+
550+
542551
### .maxJobs
543552

544553
```js
@@ -567,18 +576,6 @@ Comparator used to sort queued items. Higher-priority items should sort last
567576
(i.e. return positive when `itemA` should run before `itemB`). Defaults to `null`.
568577

569578

570-
### .schedulingCallback
571-
572-
```js
573-
schedulingCallback: ( func: function ) => void
574-
```
575-
576-
Callback used to schedule when to run jobs next, so more work doesn't happen in a
577-
single frame than there is time for. Defaults to `requestAnimationFrame`. Should be
578-
overridden in scenarios where `requestAnimationFrame` is not reliable, such as when
579-
running in WebXR.
580-
581-
582579
### .sort
583580

584581
```js
@@ -651,6 +648,48 @@ Error thrown when a queued item's promise is rejected because the item was remov
651648
before its callback could run.
652649

653650

651+
## Scheduler
652+
653+
Class used within TilesRenderer for scheduling requestAnimationFrame events and
654+
toggling between XRSession rAF and window rAF toggles.
655+
656+
657+
### static .setXRSession
658+
659+
```js
660+
static setXRSession( session: XRSession ): void
661+
```
662+
663+
Sets the active "XRSession" value to use to scheduling rAF callbacks.
664+
665+
666+
### static .requestAnimationFrame
667+
668+
```js
669+
static requestAnimationFrame( cb: function ): number
670+
```
671+
672+
Request animation frame (defer to XR session if set)
673+
674+
675+
### static .cancelAnimationFrame
676+
677+
```js
678+
static cancelAnimationFrame( handle: number ): void
679+
```
680+
681+
Cancel animation frame via handle (defer to XR session if set)
682+
683+
684+
### static .flushPending
685+
686+
```js
687+
static flushPending(): void
688+
```
689+
690+
Flush and complete pending AFs (defer to XR session if set)
691+
692+
654693
## TilesRendererBase
655694

656695
Base class for 3D Tiles renderers. Manages tile loading, caching, traversal,

src/core/renderer/utilities/LRUCache.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Scheduler } from './Scheduler.js';
2+
13
const GIGABYTE_BYTES = 2 ** 30;
24

35
/**
@@ -450,7 +452,7 @@ class LRUCache {
450452

451453
if ( needsRerun ) {
452454

453-
this.unloadingHandle = requestAnimationFrame( () => this.scheduleUnload() );
455+
this.unloadingHandle = Scheduler.requestAnimationFrame( () => this.scheduleUnload() );
454456

455457
}
456458

@@ -461,7 +463,7 @@ class LRUCache {
461463
*/
462464
scheduleUnload() {
463465

464-
cancelAnimationFrame( this.unloadingHandle );
466+
Scheduler.cancelAnimationFrame( this.unloadingHandle );
465467

466468
if ( ! this.scheduled ) {
467469

src/core/renderer/utilities/PriorityQueue.js

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Scheduler } from './Scheduler.js';
2+
13
/**
24
* Error thrown when a queued item's promise is rejected because the item was removed
35
* before its callback could run.
@@ -45,13 +47,38 @@ export class PriorityQueueItemRemovedError extends Error {
4547
*/
4648
export class PriorityQueue {
4749

48-
// returns whether tasks are queued or actively running
50+
/**
51+
* returns whether tasks are queued or actively running
52+
* @readonly
53+
* @type {boolean}
54+
*/
4955
get running() {
5056

5157
return this.items.length !== 0 || this.currJobs !== 0;
5258

5359
}
5460

61+
/**
62+
* Callback used to schedule when to run jobs next, so more work doesn't happen in a
63+
* single frame than there is time for. Defaults to `requestAnimationFrame`. Should be
64+
* overridden in scenarios where `requestAnimationFrame` is not reliable, such as when
65+
* running in WebXR.
66+
* @type {SchedulingCallback}
67+
* @deprecated
68+
*/
69+
get schedulingCallback() {
70+
71+
return this._schedulingCallback;
72+
73+
}
74+
75+
set schedulingCallback( cb ) {
76+
77+
console.log( 'PriorityQueue: Setting "schedulingCallback" has been deprecated. Use Scheduler to switch to an XRSession rAF, instead.' );
78+
this._schedulingCallback = cb;
79+
80+
}
81+
5582
constructor() {
5683

5784
/**
@@ -78,16 +105,9 @@ export class PriorityQueue {
78105
*/
79106
this.priorityCallback = null;
80107

81-
/**
82-
* Callback used to schedule when to run jobs next, so more work doesn't happen in a
83-
* single frame than there is time for. Defaults to `requestAnimationFrame`. Should be
84-
* overridden in scenarios where `requestAnimationFrame` is not reliable, such as when
85-
* running in WebXR.
86-
* @type {SchedulingCallback}
87-
*/
88-
this.schedulingCallback = func => {
108+
this._schedulingCallback = func => {
89109

90-
requestAnimationFrame( func );
110+
Scheduler.requestAnimationFrame( func );
91111

92112
};
93113

@@ -289,7 +309,7 @@ export class PriorityQueue {
289309

290310
if ( ! this.scheduled ) {
291311

292-
this.schedulingCallback( this._runjobs );
312+
this._schedulingCallback( this._runjobs );
293313

294314
this.scheduled = true;
295315

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export class Scheduler {
2+
3+
static setXRSession( session: XRSession ): void;
4+
static requestAnimationFrame( cb: Function ): number;
5+
static cancelAnimationFrame( handle: number ): void;
6+
static flushPending(): void;
7+
8+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Class used within TilesRenderer for scheduling requestAnimationFrame events and
3+
* toggling between XRSession rAF and window rAF toggles.
4+
*/
5+
class Scheduler {
6+
7+
static pending = new Map();
8+
static session = null;
9+
10+
/**
11+
* Sets the active "XRSession" value to use to scheduling rAF callbacks.
12+
* @param {XRSession} session
13+
*/
14+
static setXRSession( session ) {
15+
16+
// call "flush" before assigning xr session to ensure callbacks are
17+
// cancelled on the previous handle
18+
if ( session !== this.session ) {
19+
20+
this.flushPending();
21+
this.session = session;
22+
23+
}
24+
25+
}
26+
27+
/**
28+
* Request animation frame (defer to XR session if set)
29+
* @param {Function} cb
30+
* @returns {number}
31+
*/
32+
static requestAnimationFrame( cb ) {
33+
34+
const { session, pending } = this;
35+
let handle;
36+
37+
const func = () => {
38+
39+
pending.delete( handle );
40+
cb();
41+
42+
};
43+
44+
if ( ! session ) {
45+
46+
handle = requestAnimationFrame( func );
47+
48+
} else {
49+
50+
handle = session.requestAnimationFrame( func );
51+
52+
}
53+
54+
pending.set( handle, cb );
55+
56+
return handle;
57+
58+
}
59+
60+
/**
61+
* Cancel animation frame via handle (defer to XR session if set)
62+
* @param {number} handle
63+
*/
64+
static cancelAnimationFrame( handle ) {
65+
66+
const { pending, session } = this;
67+
pending.delete( handle );
68+
69+
if ( ! session ) {
70+
71+
cancelAnimationFrame( handle );
72+
73+
} else {
74+
75+
session.cancelAnimationFrame( handle );
76+
77+
}
78+
79+
}
80+
81+
82+
/**
83+
* Flush and complete pending AFs (defer to XR session if set)
84+
*/
85+
static flushPending() {
86+
87+
this.pending.forEach( ( cb, handle ) => {
88+
89+
cb();
90+
this.cancelAnimationFrame( handle );
91+
92+
} );
93+
94+
}
95+
96+
}
97+
98+
export { Scheduler };

src/core/renderer/utilities/throttle.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Scheduler } from './Scheduler.js';
2+
13
// function that rate limits the amount of time a function can be called to once
24
// per frame, initially queuing a new call for the next frame.
35
export function throttle( callback ) {
@@ -7,7 +9,7 @@ export function throttle( callback ) {
79

810
if ( handle === null ) {
911

10-
handle = requestAnimationFrame( () => {
12+
handle = Scheduler.requestAnimationFrame( () => {
1113

1214
handle = null;
1315
callback();

src/r3f/utilities/QueryManager.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from 'three';
1010
import { SceneObserver } from './SceneObserver.js';
1111
import { Ellipsoid } from '3d-tiles-renderer/three';
12+
import { Scheduler } from '../../core/renderer/utilities/Scheduler.js';
1213

1314
const _raycaster = /* @__PURE__ */ new Raycaster();
1415
const _line0 = /* @__PURE__ */ new Line3();
@@ -201,7 +202,7 @@ export class QueryManager extends EventDispatcher {
201202
if ( this.autoRun && ! this.scheduled ) {
202203

203204
this.scheduled = true;
204-
requestAnimationFrame( () => {
205+
Scheduler.requestAnimationFrame( () => {
205206

206207
this.scheduled = false;
207208
this._runJobs();

0 commit comments

Comments
 (0)