My version is a bit more sophisticated, but it works without having to modify all of the existing libs by hacking my own imp.wakeup function to replace the real imp.wakeup via methmethod override. Certainly not the nicest solution to the imp cloud (lots of extra, unnecessary processing) nor the most elegant, but its what I use to prevent the possibility of running out of timers on the agent…
`
// This is a really simple, reusable wrapper for any internal object to turn it into a class (courtesy of @ blindman2k).
// For memory and performance reasons, the built-in classes are not real classes so can’t
// be extended in the normal way.
class impWrapper {
_wrapped = null;
constructor(wrapped) {
_wrapped = wrapped;
}
// Pass through the original methods and properties
function _get(idx) {
if (idx in this) {
return this[idx];
} else if (idx in _wrapped && typeof _wrapped[idx] == "function") {
return _wrapped[idx].bindenv(_wrapped);
} else if (idx in _wrapped) {
return _wrapped[idx];
} else {
throw null;
}
}
}
class ImpWithMasterTimer extends impWrapper {
_wakeups = null; //Store the wakeup remaining durations and callback functions
_wakeupKeys = null; //
_lastWakeupTime = null;
// basic property of .wakeup() is that we want to call the callback function after the duration has expired, in the order that it was registered
constructor() {
this._wakeups = {}; // Key is a timestamp guid, value is an array of [time until wakeup occurs, callback]
this._wakeupKeys = []; //Sorted array of timestamp guids so that we always fire callbacks in the order they were registered if they expire at the same time. Since we are only appending large things to the end and removing things, the array is never unsorted, and therefore never needs .sort()!
base.constructor(imp);
this._lastWakeupTime = this._timestamp();
this._run();
}
// Override imp.wakeup
function wakeup(duration, callback){
local guid = this._timestamp()
if(duration < 0 )
throw "imp.wakeup duration must be >=0 - got " + duration
this._wakeups[guid] <- [duration.tofloat(), callback]; //by using our timestamp funciton, we can ensure that our GUIDs are in fact unique and will sort properly since squirrel is nice and single threaded
this._wakeupKeys.push(guid) //Whatever we are pushing is “bigger” than all others so no need for a sort here (since squirrel is single-threaded) - we do that when things are removed
return guid
}
// Override imp.cancelwakeup
function cancelwakeup(id){
if(id in this._wakeups){
delete this._wakeups[id]
this._wakeupKeys.remove(this._wakeupKeys.find(id))
}
}
function _timestamp() {
local ts = date();
return format("%d.%06d", ts.time, ts.usec);
}
function _run(){
local now = this._timestamp();
//local diff = this._diff(this._lastWakeupTime, now);
local t0 = split(this._lastWakeupTime, ".");
local t1 = split(now, ".");
local diff = (t1[0].tointeger() - t0[0].tointeger()) + (t1[1].tointeger() - t0[1].tointeger()) / 1000000.0;
//server.log("Diff = " + diff)
this._lastWakeupTime = now
local deleteIDs = [];
local activeTimers = 1; //This one is reserved for our main _run loop
for(local i=0; i < this._wakeupKeys.len(); i++) {
local id = this._wakeupKeys[i];
local wu = this._wakeups[id];
wu[0] -= diff; //update the time until this timer should fire
if(wu[0] <= 0.0) {
activeTimers++
//prevent the callstack from getting too deep and running us out of memory with imp.wakeup
_wrapped.wakeup(0, wu[1]);
deleteIDs.push(id) // We don't delete here because modifying the thing we are looping through causes bad things to happen
if(activeTimers == 20) //Only allow up to 20 active Timers (since that's all the Agent will currently provide!)
break;
}
}
for(local i = 0; i<deleteIDs.len(); i++) {
delete this._wakeups[deleteIDs[i]];
this._wakeupKeys.remove(this._wakeupKeys.find(deleteIDs[i]))
}
_wrapped.wakeup(0.0, this._run.bindenv(this)); //Back to MasterTimer.wakeup
}
}
/**
- We override our imp class so that we can limit our number of “real” imp.wakeup timers to 20, which is all that the agent will provide
*/
imp <- ImpWithMasterTimer();
`