- forEach was returning the ExpiringElements, not the data.

- We shouldn't need to run checkTimer() on add(), generally.
  If adding the sole element, we can just schedule the expiration
  directly.
- Simplified and functionalized some other bits.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5778 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2009-05-12 23:09:42 +00:00
parent bba179b372
commit 305de912ff
+44 -74
View File
@@ -35,6 +35,8 @@ public class ExpiringSet extends EventDispatcher
public function ExpiringSet (ttl :Number) public function ExpiringSet (ttl :Number)
{ {
_ttl = Math.round(ttl * 1000); _ttl = Math.round(ttl * 1000);
_timer = new Timer(_ttl, 1);
_timer.addEventListener(TimerEvent.TIMER, checkTimer);
} }
/** /**
@@ -46,12 +48,6 @@ public class ExpiringSet extends EventDispatcher
return _ttl / 1000; return _ttl / 1000;
} }
// from Set
public function isEmpty () :Boolean
{
return size() == 0;
}
/** /**
* Calling this function will not expire the elements, it simply removes them. No * Calling this function will not expire the elements, it simply removes them. No
* ValueEvent will be dispatched. * ValueEvent will be dispatched.
@@ -66,7 +62,7 @@ public class ExpiringSet extends EventDispatcher
public function forEach (fn :Function) :void public function forEach (fn :Function) :void
{ {
for each (var e :ExpiringElement in _data) { for each (var e :ExpiringElement in _data) {
fn(e); fn(e.element);
} }
} }
@@ -76,15 +72,18 @@ public class ExpiringSet extends EventDispatcher
return _data.length; return _data.length;
} }
// from Set
public function isEmpty () :Boolean
{
return size() == 0;
}
// from Set // from Set
public function contains (o :Object) :Boolean public function contains (o :Object) :Boolean
{ {
for each (var e :ExpiringElement in _data) { return _data.some(function (e :ExpiringElement, ... ignored) :Boolean {
if (e.objectEquals(o)) { return e.objectEquals(o);
return true; });
}
}
return false;
} }
/** /**
@@ -95,13 +94,9 @@ public class ExpiringSet extends EventDispatcher
public function add (o :Object) :Boolean public function add (o :Object) :Boolean
{ {
var added :Boolean = true; var added :Boolean = true;
var element :ExpiringElement;
var expire :int = getTimer() + _ttl;
for (var ii :int = 0; ii < _data.length; ii++) { for (var ii :int = 0; ii < _data.length; ii++) {
if ((_data[ii] as ExpiringElement).objectEquals(o)) { if (ExpiringElement(_data[ii]).objectEquals(o)) {
// already contained - update expire time and remove from current position. // already contained, remove this one and re-add at end
element = _data[ii] as ExpiringElement;
element.expirationTime = expire;
_data.splice(ii, 1); _data.splice(ii, 1);
added = false; added = false;
break; break;
@@ -110,8 +105,13 @@ public class ExpiringSet extends EventDispatcher
// push the item onto the queue. since each element has the same TTL, elements end up // push the item onto the queue. since each element has the same TTL, elements end up
// being ordered by their expiration time. // being ordered by their expiration time.
_data.push(element || new ExpiringElement(o, expire)); _data.push(new ExpiringElement(o, getTimer() + _ttl));
checkTimer(); if (_data.length == 1) {
// set up the timer to remove this one element, otherwise it was already running
_timer.reset();
_timer.delay = _ttl;
_timer.start();
}
return added; return added;
} }
@@ -121,13 +121,11 @@ public class ExpiringSet extends EventDispatcher
// pull the item from anywhere in the queue. If we remove the first element, the timer // pull the item from anywhere in the queue. If we remove the first element, the timer
// will harmlessly NOOP when it wakes up // will harmlessly NOOP when it wakes up
for (var ii :int = 0; ii < _data.length; ii++) { for (var ii :int = 0; ii < _data.length; ii++) {
var e :ExpiringElement = _data[ii] as ExpiringElement; if (ExpiringElement(_data[ii]).objectEquals(o)) {
if (e.objectEquals(o)) {
_data.splice(ii, 1); _data.splice(ii, 1);
return true; return true;
} }
} }
return false; return false;
} }
@@ -137,56 +135,33 @@ public class ExpiringSet extends EventDispatcher
*/ */
public function toArray () :Array public function toArray () :Array
{ {
var elements :Array = []; return _data.map(function (e :ExpiringElement, ... ignored) :Object {
for each (var element :ExpiringElement in _data) { return e.element;
elements.push(element.element); });
}
return elements;
} }
protected function checkTimer (...ignored) :void /**
* Remove probably just one element from the front of the expiration queue, and
* schedule a wakeup for the time to the new head's expiration time.
*/
protected function checkTimer (... ignored) :void
{ {
// expiration check
var now :int = getTimer(); var now :int = getTimer();
while (headIsExpired(now)) { while (_data.length > 0) {
// pop the head off the queue and dispatch an event var e :ExpiringElement = ExpiringElement(_data[0]);
var head :ExpiringElement = _data.shift() as ExpiringElement; var timeToExpire :int = e.expirationTime - now;
dispatchEvent(new ValueEvent(ELEMENT_EXPIRED, head.element)); if (timeToExpire <= 0) {
} _data.shift(); // remove it
dispatchEvent(new ValueEvent(ELEMENT_EXPIRED, e.element)); // notify
// empty check } else {
if (isEmpty()) { // the head element is not yet expired
return; _timer.reset();
_timer.delay = timeToExpire;
_timer.start();
break;
}
} }
// if the timer is already running, and we're not empty, we want to just let it finish
// its current delay, and set up a new one then
if (_timer != null && _timer.running) {
return;
}
// sanity check
var delay :int = (_data[0] as ExpiringElement).expirationTime - now;
if (delay <= 0) {
// blow up
log.warning("calculated delay after expiring elements is invalid! [" + delay + "]");
return;
}
// set up next delay
if (_timer == null) {
_timer = new Timer(delay, 1);
_timer.addEventListener(TimerEvent.TIMER, checkTimer);
} else {
_timer.delay = delay;
_timer.reset();
}
_timer.start();
}
protected function headIsExpired (now :int) :Boolean
{
return isEmpty() ? false : (_data[0] as ExpiringElement).isExpired(now);
} }
protected static const log :Log = Log.getLog(ExpiringSet); protected static const log :Log = Log.getLog(ExpiringSet);
@@ -195,7 +170,7 @@ public class ExpiringSet extends EventDispatcher
protected /* final */ var _ttl :int; protected /* final */ var _ttl :int;
/** Array of ExpiringElement instances, sorted by expiration time. */ /** Array of ExpiringElement instances, sorted by expiration time. */
protected var _data :Array = new Array(); protected var _data :Array = [];
protected var _timer :Timer; protected var _timer :Timer;
} }
@@ -214,11 +189,6 @@ class ExpiringElement
this.expirationTime = expiration; this.expirationTime = expiration;
} }
public function isExpired (now :int) :Boolean
{
return expirationTime <= now;
}
public function objectEquals (element :Object) :Boolean public function objectEquals (element :Object) :Boolean
{ {
return Util.equals(element, this.element); return Util.equals(element, this.element);