diff --git a/binaries/data/mods/public/simulation/components/Timer.js b/binaries/data/mods/public/simulation/components/Timer.js index eac2f154ea..d1e61872fb 100644 --- a/binaries/data/mods/public/simulation/components/Timer.js +++ b/binaries/data/mods/public/simulation/components/Timer.js @@ -10,11 +10,54 @@ Timer.prototype.Init = function() this.timers = {}; }; +/** + * Returns time since the start of the game, in integer milliseconds. + */ Timer.prototype.GetTime = function() { return this.time; } +/** + * 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). + * Returns a non-zero id that can be passed to CancelTimer. + */ +Timer.prototype.SetTimeout = function(ent, iid, funcname, time, data) +{ + var id = ++this.id; + this.timers[id] = [ent, iid, funcname, this.time + time, 0, data]; + return id; +}; + +/** + * 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 + * and then every 'repeattime' milliseconds thereafter. + * It will run multiple times per simulation turn if necessary. + * 'repeattime' must be non-zero. + * 'lateness' is how late the timer is executed after the specified time (in milliseconds). + * Returns a non-zero id that can be passed to CancelTimer. + */ +Timer.prototype.SetInterval = function(ent, iid, funcname, time, repeattime, data) +{ + if (typeof repeattime != "number" || !(repeattime > 0)) + error("Invalid repeattime to SetInterval of "+funcname); + var id = ++this.id; + this.timers[id] = [ent, iid, funcname, this.time + time, repeattime, data]; + return id; +}; + +/** + * Cancels an existing timer that was created with SetTimeout/SetInterval. + */ +Timer.prototype.CancelTimer = function(id) +{ + delete this.timers[id]; +}; + + Timer.prototype.OnUpdate = function(msg) { var dt = Math.round(msg.turnLength * 1000); @@ -29,8 +72,10 @@ Timer.prototype.OnUpdate = function(msg) if (this.timers[id][3] <= this.time) run.push(id); } - for each (var id in run) + for (var i = 0; i < run.length; ++i) { + var id = run[i]; + var t = this.timers[id]; if (!t) continue; // an earlier timer might have cancelled this one, so skip it @@ -41,32 +86,27 @@ Timer.prototype.OnUpdate = function(msg) try { var lateness = this.time - t[3]; - cmp[t[2]](t[4], lateness); + cmp[t[2]](t[5], lateness); } catch (e) { var stack = e.stack.trimRight().replace(/^/mg, ' '); // indent the stack trace error("Error in timer on entity "+t[0]+", IID "+t[1]+", function "+t[2]+": "+e+"\n"+stack+"\n"); } - delete this.timers[id]; + + // Handle repeating timers + if (t[4]) + { + // Add the repeat time to the execution time + t[3] += t[4]; + // Add it to the list to get re-executed if it's soon enough + if (t[3] <= this.time) + run.push(id); + } + else + { + // Non-repeating time - delete it + delete this.timers[id]; + } } } -/** - * 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). - * Returns a non-zero id that can be passed to CancelTimer. - */ -Timer.prototype.SetTimeout = function(ent, iid, funcname, time, data) -{ - var id = ++this.id; - this.timers[id] = [ent, iid, funcname, this.time + time, data]; - return id; -}; - -Timer.prototype.CancelTimer = function(id) -{ - delete this.timers[id]; -}; - Engine.RegisterComponentType(IID_Timer, "Timer", Timer); - diff --git a/binaries/data/mods/public/simulation/components/tests/test_Timer.js b/binaries/data/mods/public/simulation/components/tests/test_Timer.js new file mode 100644 index 0000000000..77513b784e --- /dev/null +++ b/binaries/data/mods/public/simulation/components/tests/test_Timer.js @@ -0,0 +1,80 @@ +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]]);