Index: ps/trunk/binaries/data/mods/public/simulation/components/Timer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Timer.js (revision 23677) +++ ps/trunk/binaries/data/mods/public/simulation/components/Timer.js (revision 23678) @@ -1,145 +1,166 @@ function Timer() {} Timer.prototype.Schema = ""; Timer.prototype.Init = function() { this.id = 0; this.time = 0; this.timers = new Map(); this.turnLength = 0; }; /** * @returns {number} - The elapsed time in milliseconds since the game was started. */ Timer.prototype.GetTime = function() { return this.time; }; /** * @returns {number} - The duration of the latest turn in milliseconds. */ Timer.prototype.GetLatestTurnLength = function() { return this.turnLength; }; /** * Create a new timer, which will call the 'funcname' method with arguments (data, lateness) * on the 'iid' component of the 'ent' entity, after at least 'time' milliseconds. * 'lateness' is how late the timer is executed after the specified time (in milliseconds). * @param {number} ent - The entity id to which the timer will be assigned to. * @param {number} iid - The component iid of the timer. * @param {string} funcname - The name of the function to be called in the component. * @param {number} time - The delay before running the function for the first time. * @param {any} data - The data to pass to the function. * @returns {number} - A non-zero id that can be passed to CancelTimer. */ Timer.prototype.SetTimeout = function(ent, iid, funcname, time, data) { return this.SetInterval(ent, iid, funcname, time, 0, data); }; /** * Create a new repeating timer, which will call the 'funcname' method with arguments (data, lateness) * on the 'iid' component of the 'ent' entity, after at least 'time' milliseconds. * 'lateness' is how late the timer is executed after the specified time (in milliseconds) * and then every 'repeattime' milliseconds thereafter. * @param {number} ent - The entity the timer will be assigned to. * @param {number} iid - The component iid of the timer. * @param {string} funcname - The name of the function to be called in the component. * @param {number} time - The delay before running the function for the first time. * @param {number} repeattime - If non-zero, the interval between each execution of the function. * @param {any} data - The data to pass to the function. * @returns {number} - A non-zero id that can be passed to CancelTimer. */ Timer.prototype.SetInterval = function(ent, iid, funcname, time, repeattime, data) { let id = ++this.id; this.timers.set(id, { "entity": ent, "iid": iid, "functionName": funcname, "time": this.time + time, "repeatTime": repeattime, "data": data }); return id; }; /** + * Updates the repeat time of a timer. + * Note that this will take only effect after the next update. + * + * @param {number} timerID - The timer to update. + * @param {number} newRepeatTime - The new repeat time to use. + */ +Timer.prototype.UpdateRepeatTime = function(timerID, newRepeatTime) +{ + let timer = this.timers.get(timerID); + if (timer) + this.timers.set(timerID, { + "entity": timer.entity, + "iid": timer.iid, + "functionName": timer.functionName, + "time": timer.time, + "repeatTime": newRepeatTime, + "data": timer.data + }); +}; + +/** * Cancels an existing timer that was created with SetTimeout/SetInterval. * @param {number} id - The timer's ID returned by either SetTimeout or SetInterval. */ Timer.prototype.CancelTimer = function(id) { this.timers.delete(id); }; /** * @param {{ "turnLength": number }} msg - A message containing the turn length in seconds. */ Timer.prototype.OnUpdate = function(msg) { this.turnLength = Math.round(msg.turnLength * 1000); this.time += this.turnLength; // Collect the timers that need to run // (We do this in two stages to avoid deleting from the timer list while // we're in the middle of iterating through it) let run = []; for (let [id, timer] of this.timers) if (timer.time <= this.time) run.push(id); for (let id of run) { let timer = this.timers.get(id); // An earlier timer might have cancelled this one, so skip it if (!timer) continue; // The entity was probably destroyed; clean up the timer let timerTargetComponent = Engine.QueryInterface(timer.entity, timer.iid); if (!timerTargetComponent) { this.timers.delete(id); continue; } try { timerTargetComponent[timer.functionName](timer.data, this.time - timer.time); } catch (e) { error( "Error in timer on entity " + timer.entity + ", " + "IID" + timer.iid + ", " + "function " + timer.functionName + ": " + e + "\n" + // Indent the stack trace e.stack.trimRight().replace(/^/mg, ' ') + "\n"); } if (!timer.repeatTime) { this.timers.delete(id); continue; } timer.time += timer.repeatTime; // Add it to the list to get re-executed if it's soon enough if (timer.time <= this.time) run.push(id); } }; Engine.RegisterSystemComponentType(IID_Timer, "Timer", Timer); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Timer.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Timer.js (revision 23677) +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Timer.js (revision 23678) @@ -1,80 +1,97 @@ Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("Timer.js"); Engine.RegisterInterface("Test"); var cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer"); var fired = []; AddMock(10, IID_Test, { Callback: function(data, lateness) { fired.push([data, lateness]); } }); var cancelId; AddMock(20, IID_Test, { Callback: function(data, lateness) { fired.push([data, lateness]); cmpTimer.CancelTimer(cancelId); } }); TS_ASSERT_EQUALS(cmpTimer.GetTime(), 0); cmpTimer.OnUpdate({ "turnLength": 1/3 }); TS_ASSERT_EQUALS(cmpTimer.GetTime(), 333); cmpTimer.SetTimeout(10, IID_Test, "Callback", 1000, "a"); cmpTimer.SetTimeout(10, IID_Test, "Callback", 1200, "b"); cmpTimer.OnUpdate({ "turnLength": 0.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, []); cmpTimer.OnUpdate({ "turnLength": 0.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["a",0]]); cmpTimer.OnUpdate({ "turnLength": 0.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["a",0], ["b",300]]); cmpTimer.OnUpdate({ "turnLength": 0.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["a",0], ["b",300]]); fired = []; var c = cmpTimer.SetTimeout(10, IID_Test, "Callback", 1000, "c"); var d = cmpTimer.SetTimeout(10, IID_Test, "Callback", 1000, "d"); var e = cmpTimer.SetTimeout(10, IID_Test, "Callback", 1000, "e"); cmpTimer.CancelTimer(d); cmpTimer.OnUpdate({ "turnLength": 1.0 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["c",0], ["e",0]]); fired = []; var r = cmpTimer.SetInterval(10, IID_Test, "Callback", 500, 1000, "r"); cmpTimer.OnUpdate({ "turnLength": 0.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["r",0]]); cmpTimer.OnUpdate({ "turnLength": 0.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["r",0]]); cmpTimer.OnUpdate({ "turnLength": 0.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["r",0], ["r",0]]); cmpTimer.OnUpdate({ "turnLength": 3.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["r",0], ["r",0], ["r",2500], ["r",1500], ["r",500]]); cmpTimer.CancelTimer(r); cmpTimer.OnUpdate({ "turnLength": 3.5 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["r",0], ["r",0], ["r",2500], ["r",1500], ["r",500]]); fired = []; cancelId = cmpTimer.SetInterval(20, IID_Test, "Callback", 500, 1000, "s"); cmpTimer.OnUpdate({ "turnLength": 3.0 }); TS_ASSERT_UNEVAL_EQUALS(fired, [["s",2500]]); + +fired = []; +let f = cmpTimer.SetInterval(10, IID_Test, "Callback", 1000, 1000, "f"); + +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_UNEVAL_EQUALS(fired, [["f", 0]]); + +cmpTimer.OnUpdate({ "turnLength": 1 }); +TS_ASSERT_UNEVAL_EQUALS(fired, [["f", 0], ["f", 0]]); + +cmpTimer.UpdateRepeatTime(f, 500); +cmpTimer.OnUpdate({ "turnLength": 1.5 }); +// Interval updated at next updated, so expecting latency here. +TS_ASSERT_UNEVAL_EQUALS(fired, [["f", 0], ["f", 0], ["f", 500], ["f", 0]]); + +cmpTimer.OnUpdate({ "turnLength": 0.5 }); +TS_ASSERT_UNEVAL_EQUALS(fired, [["f", 0], ["f", 0], ["f", 500], ["f", 0], ["f", 0]]);