{"record":{"id":"b615acadd3da3b2e","repo":"phaserjs/phaser","slug":"timerevent-infinite-loop-created-via-zero-delay","errorCode":null,"errorMessage":"TimerEvent infinite loop created via zero delay","messagePattern":"TimerEvent infinite loop created via zero delay","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/time/Clock.js","lineNumber":216,"sourceCode":"     * @return {Phaser.Time.TimerEvent} The Timer Event which was created, or passed in.\n     */\n    addEvent: function (config)\n    {\n        var event;\n\n        if (config instanceof TimerEvent)\n        {\n            event = config;\n\n            this.removeEvent(event);\n\n            event.elapsed = event.startAt;\n            event.hasDispatched = false;\n            event.repeatCount = (event.repeat === -1 || event.loop) ? 999999999999 : event.repeat;\n\n            if (event.delay <= 0 && event.repeatCount > 0)\n            {\n                throw new Error('TimerEvent infinite loop created via zero delay');\n            }\n        }\n        else\n        {\n            event = new TimerEvent(config);\n        }\n\n        this._pendingInsertion.push(event);\n\n        return event;\n    },\n\n    /**\n     * Creates a Timer Event and adds it to the Clock at the start of the frame.\n     *\n     * This is a shortcut for {@link #addEvent} which can be shorter and is compatible with the syntax of the GreenSock Animation Platform (GSAP).\n     *\n     * @method Phaser.Time.Clock#delayedCall","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/phaserjs/phaser/blob/41be1e462bc600064e498cba370bfa8c5c055a22/src/time/Clock.js#L198-L234","documentation":"Thrown by Phaser.Time.Clock#addEvent when you re-add an existing TimerEvent instance whose delay is 0 (or negative) while it is configured to repeat. The guard prevents a timer that would fire every frame forever (repeatCount is 999999999999 for loop:true or repeat:-1), which would lock the game loop. The library treats zero-delay repeating timers as a programmer error rather than silently hanging.","triggerScenarios":"Calling this.time.addEvent(existingTimerEvent) where existingTimerEvent.delay <= 0 AND (existingTimerEvent.loop === true OR existingTimerEvent.repeat === -1 OR existingTimerEvent.repeat > 0). The branch only runs when the config argument is already a TimerEvent instance (not a plain config object); the same object passed as a config would instead hit TimerEvent.reset (error 41).","commonSituations":"Re-using/recycling a TimerEvent instance that was previously created with delay:0 and loop:true (common for a per-frame tick). Copy-pasting a config object into a TimerEvent via `new Phaser.Time.TimerEvent(config)` then passing it to addEvent. Migrating from delayedCall (which allows delay 0 for a one-shot) to a looping addEvent and forgetting to bump the delay.","solutions":["Set a positive delay (e.g. delay: 16 for ~60fps, or the frame time in ms) on the TimerEvent before calling addEvent.","If you genuinely want a single immediate callback, set loop:false and repeat:0 (or omit them) so repeatCount is 0 and the guard is skipped.","If you want a per-frame callback, use scene.events.on('preupdate', fn) or scene.sys.events instead of a zero-delay looping timer.","If re-adding an existing instance, ensure its delay/startAt were not zeroed out by a prior reset."],"exampleFix":"// before\nthis.time.addEvent(new Phaser.Time.TimerEvent({ delay: 0, loop: true, callback: tick }));\n\n// after\nthis.time.addEvent(new Phaser.Time.TimerEvent({ delay: 16, loop: true, callback: tick }));","handlingStrategy":"validation","validationCode":"function safeReAddEvent(clock, event) {\n  var repeatCount = (event.repeat === -1 || event.loop) ? Infinity : event.repeat;\n  if (event.delay <= 0 && repeatCount > 0) {\n    event.delay = clock.scene.sys.game.loop.targetFps ? 1000 / clock.scene.sys.game.loop.targetFps : 16;\n  }\n  return clock.addEvent(event);\n}","typeGuard":"function isValidTimerEvent(event) {\n  var rc = (event.repeat === -1 || event.loop) ? 1 : event.repeat;\n  return event.delay > 0 || rc === 0;\n}","tryCatchPattern":"try {\n  this.time.addEvent(existingEvent);\n} catch (e) {\n  if (e.message === 'TimerEvent infinite loop created via zero delay') {\n    existingEvent.delay = 16;\n    this.time.addEvent(existingEvent);\n  } else { throw e; }\n}","preventionTips":["Never set delay:0 on a looping or repeating timer; default delay to a positive ms value when loop/repeat is set.","Reuse TimerEvent instances only after resetting delay to a positive value.","For per-frame work, prefer scene events ('preupdate'/'update') over zero-delay timers.","Centralize timer creation behind a helper that validates delay>0 when repeat>0."],"tags":["timer","clock","infinite-loop","game-loop","time"],"backgroundTag":null,"analyzedSha":"41be1e462bc600064e498cba370bfa8c5c055a22","analyzedAt":"2026-08-13T04:23:39.729Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}