@@ -245,6 +245,56 @@ var World = new Class({
245245 right : GetValue ( config , 'checkCollision.right' , true )
246246 } ;
247247
248+ /**
249+ * [description]
250+ *
251+ * @name Phaser.Physics.Arcade.World#desiredFps
252+ * @readOnly
253+ * @type {number }
254+ * @since 3.10.0
255+ */
256+ this . desiredFps = GetValue ( config , 'desiredFps' , 60 ) ;
257+
258+ /**
259+ * [description]
260+ *
261+ * @name Phaser.Physics.Arcade.World#_elapsed
262+ * @private
263+ * @type {number }
264+ * @since 3.10.0
265+ */
266+ this . _elapsed = 0 ;
267+
268+ this . _desiredFpsMult = 1 / this . desiredFps ;
269+
270+ /**
271+ * @property {number } _lastCount - Remember how many 'catch-up' iterations were used on the logicUpdate last frame.
272+ * @private
273+ */
274+ this . _lastCount = 0 ;
275+
276+ /**
277+ * @property {number } _spiraling - If the 'catch-up' iterations are spiraling out of control, this counter is incremented.
278+ * @private
279+ */
280+ this . _spiraling = 0 ;
281+
282+ this . _nextFpsNotification = 0 ;
283+
284+ /**
285+ * Scaling factor to make the game move smoothly in slow motion (or fast motion)
286+ *
287+ * - 1.0 = normal speed
288+ * - 2.0 = half speed
289+ * - 0.5 = double speed
290+ *
291+ * You likely need to adjust {@link #desiredFps} as well such that `desiredFps / timeScale === 60`.
292+ *
293+ * @property {number } timeScale
294+ * @default
295+ */
296+ this . timeScale = 1.0 ;
297+
248298 /**
249299 * The maximum absolute difference of a Body's per-step velocity and its overlap with another Body that will result in separation on *each axis*.
250300 * Larger values favor separation.
@@ -436,7 +486,7 @@ var World = new Class({
436486 */
437487 enableBody : function ( object , bodyType )
438488 {
439- if ( object . body === null )
489+ if ( ! object . body )
440490 {
441491 if ( bodyType === CONST . DYNAMIC_BODY )
442492 {
@@ -767,13 +817,73 @@ var World = new Class({
767817 return ;
768818 }
769819
770- // this.delta = Math.min(delta / 1000, this.maxStep) * this.timeScale;
771- delta /= 1000 ;
820+ // if the logic time is spiraling upwards, skip a frame entirely
821+ if ( this . _spiraling > 1 )
822+ {
823+ // cause an event to warn the program that this CPU can't keep up with the current desiredFps rate
824+ if ( time > this . _nextFpsNotification )
825+ {
826+ // only permit one fps notification per 10 seconds
827+ this . _nextFpsNotification = time + 10000 ;
772828
773- this . delta = delta ;
829+ console . log ( 'fps warning' ) ;
774830
775- // Update all active bodies
831+ // dispatch the notification signal
832+ // this.fpsProblemNotifier.dispatch();
833+ }
834+
835+ // reset the _deltaTime accumulator which will cause all pending late updates to be permanently skipped
836+ // console.log('skipped', this._spiraling);
837+
838+ this . _elapsed = 0 ;
839+ this . _spiraling = 0 ;
840+ }
841+ else
842+ {
843+ // stepsThisFrame
844+ //
845+ // TODO - Allow you to run the update at a HIGHER frame rate, if required
846+ // TODO - Disable RTree entirely
847+ // TODO - Option to load the RTree so it doesn't get cleared every frame
848+
849+ // Step size taking into account the slow motion speed
850+ var slowStep = this . timeScale * 1000.0 / this . desiredFps ;
776851
852+ // Accumulate time until the slowStep threshold is met or exceeded... up to a limit of 3 catch-up frames at slowStep intervals
853+ this . _elapsed += Math . max ( Math . min ( slowStep * 3 , delta ) , 0 ) ;
854+
855+ // Run the update logic multiple times if necessary to "catch up" with dropped frames
856+ var count = 0 ;
857+
858+ while ( this . _elapsed >= slowStep )
859+ {
860+ this . _elapsed -= slowStep ;
861+
862+ this . updateLogic ( this . _desiredFpsMult ) ;
863+
864+ count ++ ;
865+ }
866+
867+ // detect spiraling (if the catch-up loop isn't fast enough, the number of iterations will increase constantly)
868+ if ( count > this . _lastCount )
869+ {
870+ this . _spiraling ++ ;
871+ // console.log('spiral++', this._spiraling, 'count', count, 'last', this._lastCount);
872+ }
873+ else if ( count < this . _lastCount )
874+ {
875+ // looks like it caught up successfully, reset the spiral alert counter
876+ // console.log('spiral solved', this._spiraling, 'count', count, 'last', this._lastCount);
877+ this . _spiraling = 0 ;
878+ }
879+
880+ this . _lastCount = count ;
881+ }
882+ } ,
883+
884+ updateLogic : function ( delta )
885+ {
886+ // Update all active bodies
777887 var i ;
778888 var body ;
779889 var bodies = this . bodies . entries ;
@@ -790,6 +900,10 @@ var World = new Class({
790900 }
791901
792902 // Populate our dynamic collision tree
903+ //
904+ // If we skip the tree clear + load we can
905+ // go from 10,000 bodies updating per frame to 75,000
906+
793907 this . tree . clear ( ) ;
794908 this . tree . load ( bodies ) ;
795909
@@ -911,7 +1025,7 @@ var World = new Class({
9111025 var velocityDelta = this . computeVelocity ( 0 , body , body . angularVelocity , body . angularAcceleration , body . angularDrag , body . maxAngular ) - body . angularVelocity ;
9121026
9131027 body . angularVelocity += velocityDelta ;
914- body . rotation += ( body . angularVelocity * this . delta ) ;
1028+ body . rotation += ( body . angularVelocity * this . _desiredFpsMult ) ;
9151029 }
9161030
9171031 body . velocity . x = this . computeVelocity ( 1 , body , body . velocity . x , body . acceleration . x , body . drag . x , body . maxVelocity . x ) ;
@@ -937,22 +1051,24 @@ var World = new Class({
9371051 {
9381052 if ( max === undefined ) { max = 10000 ; }
9391053
1054+ var delta = this . _desiredFpsMult ;
1055+
9401056 if ( axis === 1 && body . allowGravity )
9411057 {
942- velocity += ( this . gravity . x + body . gravity . x ) * this . delta ;
1058+ velocity += ( this . gravity . x + body . gravity . x ) * delta ;
9431059 }
9441060 else if ( axis === 2 && body . allowGravity )
9451061 {
946- velocity += ( this . gravity . y + body . gravity . y ) * this . delta ;
1062+ velocity += ( this . gravity . y + body . gravity . y ) * delta ;
9471063 }
9481064
9491065 if ( acceleration )
9501066 {
951- velocity += acceleration * this . delta ;
1067+ velocity += acceleration * delta ;
9521068 }
9531069 else if ( drag && body . allowDrag )
9541070 {
955- drag *= this . delta ;
1071+ drag *= delta ;
9561072
9571073 if ( velocity - drag > 0 )
9581074 {
@@ -1263,16 +1379,18 @@ var World = new Class({
12631379 }
12641380 }
12651381
1382+ var delta = this . _desiredFpsMult ;
1383+
12661384 if ( ! body1 . immovable )
12671385 {
1268- body1 . x += ( body1 . velocity . x * this . delta ) - overlap * Math . cos ( angleCollision ) ;
1269- body1 . y += ( body1 . velocity . y * this . delta ) - overlap * Math . sin ( angleCollision ) ;
1386+ body1 . x += ( body1 . velocity . x * delta ) - overlap * Math . cos ( angleCollision ) ;
1387+ body1 . y += ( body1 . velocity . y * delta ) - overlap * Math . sin ( angleCollision ) ;
12701388 }
12711389
12721390 if ( ! body2 . immovable )
12731391 {
1274- body2 . x += ( body2 . velocity . x * this . delta ) + overlap * Math . cos ( angleCollision ) ;
1275- body2 . y += ( body2 . velocity . y * this . delta ) + overlap * Math . sin ( angleCollision ) ;
1392+ body2 . x += ( body2 . velocity . x * delta ) + overlap * Math . cos ( angleCollision ) ;
1393+ body2 . y += ( body2 . velocity . y * delta ) + overlap * Math . sin ( angleCollision ) ;
12761394 }
12771395
12781396 if ( body1 . onCollide || body2 . onCollide )
@@ -1630,6 +1748,9 @@ var World = new Class({
16301748 minMax . maxX = bodyA . right ;
16311749 minMax . maxY = bodyA . bottom ;
16321750
1751+ // TODO - Optional tree
1752+ // TODO - Can we just get the body from the results array, rather than doing an indexOf???
1753+
16331754 var results = ( group . physicsType === CONST . DYNAMIC_BODY ) ? this . tree . search ( minMax ) : this . staticTree . search ( minMax ) ;
16341755
16351756 if ( results . length === 0 )
0 commit comments