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]]);