Skip to content

Commit b254d84

Browse files
committed
feat: add new “animate” event for distance based animation throttling
Works the same way “update” does, but apps that are far away from the camera will have “animate” called much less often, for a big performance boost.
1 parent 7ec1b55 commit b254d84

File tree

5 files changed

+88
-11
lines changed

5 files changed

+88
-11
lines changed

src/client/public/ai-docs.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,26 +202,28 @@ const box = app.create('prim', {
202202

203203
## Animation
204204

205-
Only when requested, you can make things move or change over time by hooking into the update cycle:
205+
Only when requested, you can make things move or change over time by hooking into the animation cycle:
206206

207207
```jsx
208-
app.on('update', delta => {
208+
app.on('animate', delta => {
209209
box.rotation.y += 45 * DEG2RAD * delta // rotate around Y axis each frame 45 degrees per second
210210
})
211211
```
212212

213-
If something needs to happen in response to interaction (eg triggers or actions), subscribe and unsubscribe only as needed for performance:
213+
The `animate` rate is dynamic based on how far away the app is from the camera, so be sure to use `delta` time to normalize speeds.
214+
215+
If animations start in response to triggers or actions and have an end time, subscribe and unsubscribe for performance:
214216

215217
```jsx
216-
const update = delta => {
218+
const animate = delta => {
217219
// do things
218220
}
219221

220222
// subscribe when something needs to happen
221-
app.on('update', update)
223+
app.on('animate', animate)
222224

223-
// unsubscribe when idle to save resources
224-
app.off('update', update)
225+
// unsubscribe when finsihed to save resources
226+
app.off('animate', animate)
225227
```
226228

227229
## Bloom

src/core/World.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Collections } from './systems/Collections'
66
import { Apps } from './systems/Apps'
77
import { Anchors } from './systems/Anchors'
88
import { Avatars } from './systems/Avatars'
9+
import { Animation } from './systems/Animation'
910
import { Events } from './systems/Events'
1011
import { Chat } from './systems/Chat'
1112
import { Blueprints } from './systems/Blueprints'
@@ -40,6 +41,7 @@ export class World extends EventEmitter {
4041
this.register('apps', Apps)
4142
this.register('anchors', Anchors)
4243
this.register('avatars', Avatars)
44+
this.register('animation', Animation)
4345
this.register('events', Events)
4446
this.register('scripts', Scripts)
4547
this.register('chat', Chat)

src/core/entities/App.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Layers } from '../extras/Layers'
1212
import { createPlayerProxy } from '../extras/createPlayerProxy'
1313
import { serializeError } from '../extras/serializeError'
1414

15-
const hotEventNames = ['fixedUpdate', 'update', 'lateUpdate']
15+
const hotEventNames = ['fixedUpdate', 'update', 'animate', 'lateUpdate']
1616

1717
const Modes = {
1818
ACTIVE: 'active',
@@ -42,6 +42,8 @@ export class App extends Entity {
4242
this.target = null
4343
this.projectLimit = Infinity
4444
this.resetOnMove = false
45+
this.animateDelta = 0
46+
this.animateRate = 0.001
4547
this.playerProxies = new Map()
4648
this.hitResultsPool = []
4749
this.hitResults = []
@@ -226,10 +228,17 @@ export class App extends Entity {
226228
this.networkQuat.update(delta)
227229
this.networkSca.update(delta)
228230
}
229-
// script update()
231+
// script update/animate
230232
if (this.script) {
231233
try {
234+
// update
232235
this.emit('update', delta)
236+
// animate
237+
this.animateDelta += delta
238+
while (this.animateDelta >= this.animateRate) {
239+
this.emit('animate', this.animateDelta)
240+
this.animateDelta = 0
241+
}
233242
} catch (err) {
234243
this.scriptError = serializeError(err)
235244
console.error('script update() crashed', this)

src/core/systems/Animation.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { System } from './System'
2+
import * as THREE from 'three'
3+
4+
const v1 = new THREE.Vector3()
5+
6+
const BATCH_SIZE = 10
7+
8+
/**
9+
* Animation System
10+
*
11+
* - Updates app animation rates based on distance to camera
12+
*
13+
*/
14+
export class Animation extends System {
15+
constructor(world) {
16+
super(world)
17+
this.apps = []
18+
this.cursor = 0
19+
}
20+
21+
init() {
22+
this.world.entities.on('added', this.onAdded)
23+
this.world.entities.on('removed', this.onRemoved)
24+
}
25+
26+
onAdded = entity => {
27+
if (!entity.isApp) return
28+
this.apps.push(entity)
29+
}
30+
31+
onRemoved = entity => {
32+
if (!entity.isApp) return
33+
const idx = this.apps.indexOf(entity)
34+
if (idx === -1) return
35+
this.apps.splice(idx, 1)
36+
}
37+
38+
update() {
39+
if (!this.apps.length) return
40+
const camPos = v1.setFromMatrixPosition(this.world.camera.matrixWorld)
41+
const batch = Math.min(BATCH_SIZE, this.apps.length)
42+
for (let i = 0; i < batch; i++) {
43+
const app = this.apps[this.cursor % this.apps.length]
44+
if (!app.root) {
45+
this.cursor++
46+
continue
47+
}
48+
const appPos = app.root.position
49+
const distance = camPos.distanceTo(appPos)
50+
if (distance < 30) {
51+
app.animateRate = 0.001 // max fps
52+
} else if (distance < 80) {
53+
app.animateRate = 1 / 30
54+
} else {
55+
app.animateRate = 1 / 20
56+
}
57+
this.cursor++
58+
}
59+
}
60+
61+
destroy() {
62+
this.apps = []
63+
}
64+
}

src/core/systems/Entities.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class Entities extends System {
5858
this.player = entity
5959
this.world.emit('player', entity)
6060
}
61-
this.emit('added')
61+
this.emit('added', entity)
6262
return entity
6363
}
6464

@@ -69,7 +69,7 @@ export class Entities extends System {
6969
entity.destroy()
7070
this.items.delete(id)
7171
this.removed.push(id)
72-
this.emit('removed')
72+
this.emit('removed', entity)
7373
}
7474

7575
setHot(entity, hot) {

0 commit comments

Comments
 (0)