@@ -3,16 +3,13 @@ import { PBTween, TweenLoop, TweenStateStatus } from '../components'
33import { Entity , IEngine } from '../engine'
44import { ReadWriteByteBuffer } from '../serialization/ByteBuffer'
55import { dataCompare } from './crdt/utils'
6-
76export type TweenSystem = {
87 tweenCompleted ( entity : Entity ) : boolean
98}
10-
119/**
1210 * Avoid creating multiple tween systems
1311 */
1412const cacheTween : Map < number , TweenSystem > = new Map ( )
15-
1613/**
1714 * @public
1815 * @returns tween helper to be used on the scene
@@ -24,140 +21,151 @@ export function createTweenSystem(engine: IEngine): TweenSystem {
2421 const Tween = components . Tween ( engine )
2522 const TweenState = components . TweenState ( engine )
2623 const TweenSequence = components . TweenSequence ( engine )
27-
2824 const cache = new Map <
2925 Entity ,
3026 {
3127 // Used to detect new tweens for the same entity
3228 tween : Uint8Array
33- // Avoid updaing again the tween in the case we receieve a network tween from other client
34- frames : number
3529 // Trigger the isCompleted only once per tween
3630 completed : boolean
3731 // Tween has changed on this frame
3832 changed : boolean
3933 }
4034 > ( )
41-
4235 function isCompleted ( entity : Entity ) {
4336 const tweenState = TweenState . getOrNull ( entity )
4437 const tween = Tween . getOrNull ( entity )
4538 const tweenCache = cache . get ( entity )
46- if ( ! tweenState || ! tween ) return false
39+ if ( ! tweenState || ! tween || ! tweenCache ) return false
4740 /* istanbul ignore next */
4841 if (
4942 // Renderer notified that the tween is completed
50- ( tweenChanged ( entity ) || tweenState . state === TweenStateStatus . TS_COMPLETED ) &&
43+ // Only consider it completed if the tween hasn't changed this frame (to avoid false positives after YOYO/sequence processing)
44+ ( ( tweenState . state === TweenStateStatus . TS_COMPLETED && ! tweenCache . changed ) ||
45+ ( tweenChanged ( entity ) && ! tweenCache . changed ) ) &&
5146 // Avoid sending isCompleted multiple times
52- ! tweenCache ?. completed &&
53- // Amount of frames needed to consider a tween completed
54- ( tweenCache ?. frames ?? 0 ) > 2
47+ ! tweenCache . completed
5548 ) {
5649 return true
5750 }
58-
5951 return false
6052 }
61-
6253 function tweenChanged ( entity : Entity ) {
6354 const currentTween = Tween . getOrNull ( entity )
6455 const prevTween = cache . get ( entity ) ?. tween
65-
6656 /* istanbul ignore next */
6757 if ( ( currentTween && ! prevTween ) || ( ! currentTween && prevTween ) ) {
6858 return true
6959 }
70-
60+ if ( ! currentTween || ! prevTween ) return false
7161 const currentBuff = new ReadWriteByteBuffer ( )
72- Tween . schema . serialize ( currentTween ! , currentBuff )
73- const equal = dataCompare ( currentBuff . toBinary ( ) , prevTween )
74-
75- return equal
62+ Tween . schema . serialize ( currentTween , currentBuff )
63+ const compareResult = dataCompare ( currentBuff . toBinary ( ) , prevTween )
64+ return compareResult !== 0
7665 }
7766
78- const restartTweens : ( ( ) => void ) [ ] = [ ]
79- // Logic for sequence tweens
67+ // System to manage cache (needed for tweenSystem.tweenCompleted() to work)
8068 engine . addSystem ( ( ) => {
81- for ( const restart of restartTweens ) {
82- restart ( )
83- }
84- restartTweens . length = 0
8569 for ( const [ entity , tween ] of engine . getEntitiesWith ( Tween ) ) {
8670 if ( tweenChanged ( entity ) ) {
8771 const buffer = new ReadWriteByteBuffer ( )
8872 Tween . schema . serialize ( tween , buffer )
8973 cache . set ( entity , {
9074 tween : buffer . toBinary ( ) ,
91- frames : 0 ,
9275 completed : false ,
9376 changed : true
9477 } )
9578 continue
9679 }
97- const tweenCache = cache . get ( entity ) !
98- tweenCache . frames += 1
99- tweenCache . changed = false
100- if ( isCompleted ( entity ) ) {
101- // Reset tween frames.
102- tweenCache . frames = 0
103- // set the tween completed to avoid calling this again for the same tween
104- tweenCache . completed = true
105-
106- const tweenSequence = TweenSequence . getOrNull ( entity )
107- if ( ! tweenSequence ) continue
108- const { sequence } = tweenSequence
109-
110- if ( sequence && sequence . length ) {
111- const [ nextTweenSequence , ...otherTweens ] = sequence
112- Tween . createOrReplace ( entity , nextTweenSequence )
113- const mutableTweenHelper = TweenSequence . getMutable ( entity )
114- mutableTweenHelper . sequence = otherTweens
115- if ( tweenSequence . loop === TweenLoop . TL_RESTART ) {
116- mutableTweenHelper . sequence . push ( tween )
117- }
118- } else if ( tweenSequence . loop === TweenLoop . TL_YOYO ) {
119- Tween . createOrReplace ( entity , backwardsTween ( tween ) )
120- } else if ( tweenSequence . loop === TweenLoop . TL_RESTART ) {
121- Tween . deleteFrom ( entity )
122- cache . delete ( entity )
123-
124- restartTweens . push ( ( ) => {
125- Tween . createOrReplace ( entity , tween )
126- } )
80+ const tweenCache = cache . get ( entity )
81+ if ( tweenCache ) {
82+ tweenCache . changed = false
83+ if ( isCompleted ( entity ) ) {
84+ // set the tween completed to avoid calling this again for the same tween
85+ tweenCache . completed = true
12786 }
12887 }
12988 }
13089 } , Number . NEGATIVE_INFINITY )
13190
132- function backwardsTween ( tween : PBTween ) : PBTween {
133- if ( tween . mode ?. $case === 'move' && tween . mode . move ) {
134- return { ...tween , mode : { ...tween . mode , move : { start : tween . mode . move . end , end : tween . mode . move . start } } }
135- }
136- if ( tween . mode ?. $case === 'rotate' && tween . mode . rotate ) {
137- return {
138- ...tween ,
139- mode : { ...tween . mode , rotate : { start : tween . mode . rotate . end , end : tween . mode . rotate . start } }
91+ function initializeTweenSequenceSystem ( ) {
92+ const restartTweens : ( ( ) => void ) [ ] = [ ]
93+ function backwardsTween ( tween : PBTween ) : PBTween {
94+ if ( tween . mode ?. $case === 'move' && tween . mode . move ) {
95+ return { ...tween , mode : { ...tween . mode , move : { start : tween . mode . move . end , end : tween . mode . move . start } } }
14096 }
141- }
142- if ( tween . mode ?. $case === 'scale' && tween . mode . scale ) {
143- return { ...tween , mode : { ...tween . mode , scale : { start : tween . mode . scale . end , end : tween . mode . scale . start } } }
144- }
145- if ( tween . mode ?. $case === 'textureMove' && tween . mode . textureMove ) {
146- return {
147- ...tween ,
148- mode : { ...tween . mode , textureMove : { start : tween . mode . textureMove . end , end : tween . mode . textureMove . start } }
97+ if ( tween . mode ?. $case === 'rotate' && tween . mode . rotate ) {
98+ return {
99+ ...tween ,
100+ mode : { ...tween . mode , rotate : { start : tween . mode . rotate . end , end : tween . mode . rotate . start } }
101+ }
102+ }
103+ if ( tween . mode ?. $case === 'scale' && tween . mode . scale ) {
104+ return { ...tween , mode : { ...tween . mode , scale : { start : tween . mode . scale . end , end : tween . mode . scale . start } } }
105+ }
106+ if ( tween . mode ?. $case === 'textureMove' && tween . mode . textureMove ) {
107+ return {
108+ ...tween ,
109+ mode : { ...tween . mode , textureMove : { start : tween . mode . textureMove . end , end : tween . mode . textureMove . start } }
110+ }
149111 }
112+ /* istanbul ignore next */
113+ throw new Error ( 'Invalid tween' )
150114 }
151115
152- /* istanbul ignore next */
153- throw new Error ( 'Invalid tween' )
116+ // Logic for sequence tweens
117+ engine . addSystem ( ( ) => {
118+ for ( const restart of restartTweens ) {
119+ restart ( )
120+ }
121+ restartTweens . length = 0
122+ for ( const [ entity , tween ] of engine . getEntitiesWith ( Tween ) ) {
123+ const tweenCache = cache . get ( entity )
124+ if ( ! tweenCache ) continue
125+
126+ // Only process tween sequences if the tween is completed
127+ if ( tweenCache . completed ) {
128+ const tweenSequence = TweenSequence . getOrNull ( entity )
129+ if ( ! tweenSequence ) continue
130+ const { sequence } = tweenSequence
131+ if ( sequence && sequence . length ) {
132+ const [ nextTweenSequence , ...otherTweens ] = sequence
133+ Tween . createOrReplace ( entity , nextTweenSequence )
134+ const mutableTweenHelper = TweenSequence . getMutable ( entity )
135+ mutableTweenHelper . sequence = otherTweens
136+ if ( tweenSequence . loop === TweenLoop . TL_RESTART ) {
137+ mutableTweenHelper . sequence . push ( tween )
138+ }
139+ // Reset completed flag for the next tween in sequence
140+ // Mark as changed so the cache system will detect the change and reset the cache properly
141+ tweenCache . completed = false
142+ tweenCache . changed = true
143+ } else if ( tweenSequence . loop === TweenLoop . TL_YOYO ) {
144+ Tween . createOrReplace ( entity , backwardsTween ( tween ) )
145+ // Reset completed flag for the backwards tween
146+ // Mark as changed so the cache system will detect the change and reset the cache properly
147+ tweenCache . completed = false
148+ tweenCache . changed = true
149+ } else if ( tweenSequence . loop === TweenLoop . TL_RESTART ) {
150+ Tween . deleteFrom ( entity )
151+ cache . delete ( entity )
152+ restartTweens . push ( ( ) => {
153+ Tween . createOrReplace ( entity , tween )
154+ } )
155+ }
156+ }
157+ }
158+ } , Number . NEGATIVE_INFINITY )
154159 }
155160
161+ // Some Explorers may not inject the flag and TweenSequence logic must be enabled in that case
162+ const enableTweenSequenceLogic = ( globalThis as any ) . ENABLE_SDK_TWEEN_SEQUENCE
163+ if ( enableTweenSequenceLogic !== false ) initializeTweenSequenceSystem ( )
164+
156165 const tweenSystem : TweenSystem = {
157166 // This event is fired only once per tween
158167 tweenCompleted : isCompleted
159168 }
160-
161169 cacheTween . set ( engine . _id , tweenSystem )
162170 return tweenSystem
163171}
0 commit comments