0ad/binaries/data/mods/public/simulation/components/Timer.js
wraitii 3991c5c857 Speed up timer update by using Map.forEach
According to JIT profiling on SM 115, using ForEach is faster than
iterating.
This is a micro optimisation, maybe 2% faster for OnUpdate, which means
we might see an overall improvement < 0.4%.

The reason this is faster here is that Iterating needs to construct an
array to store the result, whereas ForEach just uses the values
directly. This means this probably doesn't apply to iterating if we
don't need both the key/value pair.

Differential Revision: https://code.wildfiregames.com/D5010
This was SVN commit r27674.
2023-06-13 15:57:18 +00:00

167 lines
4.7 KiB
JavaScript

function Timer() {}
Timer.prototype.Schema =
"<a:component type='system'/><empty/>";
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 = [];
this.timers.forEach((timer, id) => {
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);