Blog
Posts tagged with “code”
Object Pool Class for AS3
Object pools aren't really rocket science. At their simplest, they can be implemented with an array, as follows:
var pool:Array = new Array();
function getFromPool():* {
return (pool.length?pool.pop():new [YourPooledObjectClass]());
}
function returnToPool(o:*):void {
if (o is [YourPooledObjectClass]) {
pool.push(o);
}
}
… and that's it really. The only problem is that you need to ensure that your code then manually resets any default property values of an aquired object. It also means you need to "one off" each pool you use. To that end, I created the following Object pool class.
Its main features:
- keeps an internal list of default properties to be set at aquisition time.
- ensures that only declared properties can be set (this can be turned of if desired)
- allows for per-instance property overrides as needed
- allows a max count to be set and pool flushing for memory management
- allows pool pre-population so that potentially expensive creation routines can be managed
The only caveat is that any poolable object cannot require arguments at creation time — unless someone can point me to the class constuctor equivilent of funtion.apply();
package ca.atomicnoggin.collection {
/**
*
* @author Patrick Denny heythere@atomicnoggin.ca
*
*/
public class ObjectPool {
private var objType:Class;
private var props:Object = {};
private var pool:Array = [];
private var max:uint;
private var dyno:Boolean;
/**
* Constructor
*
* @param type The object class to use in the pool
*
* @param defaultProperties A simple name:value object that
* lists any property presets for objects taken from the pool.
*
* @param allowDynamic Whether or not to allow default properties that
* are not explicitly defined within the Class.
*
* @param initialCount Number of objects to pre-populate the pool with.
*
* @param maxCount Maximum number of objects allowed to populate the pool.
*/
public function ObjectPool(type:Class = null,
defaultProperties:Object = null,
allowDynamic:Boolean=false,
initialCount:uint=0,
maxCount:uint=uint.MAX_VALUE)
{
objType = (type is Class)?type:Object;
dyno = allowDynamic;
max = maxCount;
if ((defaultProperties is Object)) {
loadDefaultProperties(defaultProperties);
}
this.length = initialCount;
}
private function getNextObj(removeFromPool:Boolean=false):* {
//either grab the first object added or create a new one.
var obj = pool.length?pool[0]:(new objType());
if (removeFromPool) pool.shift();
return obj;
}
/**
* Get an object from the pool
*
* @param properties A simple name:value object that
* lists any instance specific properties to be set for
* the object taken from the pool. If allowDynamic is
* false, Properties not explicitly defined within the Class
* will be ignored.
*
* @return an instance of the Class specified in the constructor
*
*/
public function pop(properties:Object = null):* {
var newObj = getNextObj(true);
//make a copy of the instance specific properties provided (if any)
var newP:Object = (properties is Object?properties?{});
//track instance properties already set
var used:Object = {};
//iterate through the default properties
for (var p:String in props) {
if (dyno || newObj.hasOwnProperty(p)) {
//if an instance property name matches the default property
if (newP.hasOwnProperty(p)) {
// use the provided property
newObj[p] = newP[p];
// and add it to the used list
used[p] = true;
}
//otherwise use the default property
else {
newObj[p] = props[p];
}
}
}
//iterate through the remaining instance properties and apply them.
for (p in newP) {
if ((dyno || newObj.hasOwnProperty(p)) && !used[p]) {
newObj[p] = newP[p];
}
}
return newObj;
}
/**
* Return an object to the pool
*
* @param object The object being returned. Any object provided
* that is not an instance of the Class will be ignored.
*
*/
public function push(object:*):void {
if ((object is objType)) {
pool.push(object);
//this forces the max value check
this.length = pool.length;
}
else {
//should throw error
}
}
/**
* The Class used to create pool objects
*
* setting this property will also clear the pool.
*/
public function get poolClass():Class {
return objType;
}
public function set poolClass(value:Class):void {
this.length = 0;
objType = value;
}
public function loadDefaultProperties(defaultProperties:Object, clearPrevious:Boolean=true) {
if (clearPrevious) {
props = {};
}
var obj = getNextObj();
for (var p:* in defaultProperties) {
if (dyno || obj.hasOwnProperty(p)) {
props[String(p)] = defaultProperties[p];
}
}
}
/**
* Get the value of a default property
*
* @param name The property to be looked up.
*
* @return The default value of the named property.
* If the property hasn't been set in the pool's default
* property list, then 'undefined' will be returned instead.
*/
public function getDefaultProperty(name:String):* {
var obj = getNextObj();
if (dyno || obj.hasOwnProperty(name)) {
return props[name];
}
else {
return undefined
}
}
/**
* Set the value of a default property
*
* @param name The name of the property to be set.
*
* @param value The value to be used. If allowDynamic is
* false, Properties not explicitly defined within the Class
* will be ignored.
*/
public function setDefaultProperty(name:String,value:*):void {
var obj = getNextObj();
if (dyno || obj.hasOwnProperty(name)) {
props[name] = value;
}
else {
//should throw error
}
}
/**
* Remove a property from the default properties list
*
* @param name The name of the default property to remove.
*
*/
public function removeDefaultProperty(name:String):void {
delete props[name];
}
/**
* Whether or not dynamic properties will be allowed to
* be added to the pool's objects. Setting this to true
* may cause Errors.
*
* default is false
*/
public function get allowDynamic():Boolean {
return dyno;
}
public function set allowDynamic(value:Boolean):void {
dyno = value;
if(!dyno) {
//reload properties to clear out dynamic ones
loadDefaultProperties(Object(props));
}
}
/**
* The maximum number of objects allowed to populate the pool
*
* default is uint.MAX_VALUE
*/
public function get maxCount():uint {
return max;
}
public function set maxCount(value:uint):void {
max = value;
if (pool.length > max) {
this.length = max;
}
}
/**
* The number of objects currently in the pool.
*
* If length is set to a value less than what is currently available,
* any objects beyond the new value will be deleted. If set to a value
* greater than what is available, new objects will be created
* to pre-populate the pool to match the amount provided.
*
*/
public function get length():uint {
return pool.length;
}
/**
*
*/
public function set length(value:uint):void {
var v:uint = (value > max)?max:value;
if (v < pool.length) {
delete pool.splice(v);
}
else if (v > pool.length) {
for(var a:uint = v - pool.length;a--;) {
pool.push(new objType());
}
}
}
}
}
The simplest usage would be:
var myPool:ObjectPool = new ObjectPool(Sprite);
To create a Sprite pool.
And as a bonus, you can even attach a pool directly to an object to create a factory, as follows:
package {
import ca.atomicnoggin.collections.ObjectPool;
public class MyClass {
static private var _pool:ObjectPool;
static public function get pool():ObjectPool {
if (!(_pool is ObjectPool)) {
_pool = new ObjectPool(MyClass);
}
return _pool;
}
public function MyClass() {
//constuctor code
}
}
}
Stupid CSS Tricks: The Simple Sticky Footer
Ok, I've seen a lot of techniques for Sticky footers, but haven't seen one as simple as mine. It uses the bare minimum mark-up and CSS possible.
First, here is the HTML required:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Sticky Footers Rock</title>
<link rel="stylesheet" type="text/css" href="[where you store css]" />
<!--[if lte IE 6]>
<link rel="stylesheet" type="text/css" href="[where you store IE6 specific css]" />
<![endif]-->
</head>
<body>
<div id="header"></div>
<div id="content"></div>
<div id="footer"></div>
</body>
</html>
As you can see, there's nothing special in there. Besides the conditionally commented IE6 stylesheet, there are three DIVs to split the layout, and that's it. Nothing a decent web designer wouldn't already have in a page anyway. In fact, only two of those DIVs are required. The "header" DIV is only there because it usually exists in a design. It's superflous to this example. One thing that is vitally important though — assuming you care about IE6 — is the <!DOCTYPE> decleration. What doctype you use is up to you, so long as it triggers "almost standards mode" in IE6.
Next we will look at the base CSS required. You can add more as you see fit. I've commented the code to explain why each line needed:
HTML {
height:100%;
/* allows body to grow to full window height */
}
BODY {
position:relative;
/* otherwise footer will position itself in relation to window height */
min-height:100%;
/* makes page start out at least as tall as the browser window */
margin:0;
padding:0;
/* not strictly required but useful. reset to push body to edges of browser window */
}
#footer {
position:absolute;
bottom:0px;
/* these two lines will force footer to the bottom of the page. */
height:XXX;
/*set to whatever you want */
}
#content {
padding-bottom:XXX;
/* must match or exceed the footer height, so bottom-most content doesn't float bellow footer */
overflow:hidden;
/* this will contain any floated elements and push the footer down below them
NOTE: do not add a height to this element, or this will fail */
}
Next, we add a single rule to the conditionally commented IE6 stylesheet:
BODY {
height:100%;
/* hack for IE6 that doesn't recognize min-height. */
}
That's it. We're done. Here's a demo with some minimal extra CSS and content to illustrate the technique.
One caveat with IE6: this will work as is on static content pages, but IE6 will not move the footer down when extra content is added or removed dynamically. Instead it will float where it originally sat regarless of the new content size. To work around this, simply pop the footer's style position to 'static' and back (either directly with <element>.style.position, or by adding then removing a new class) after making the dynamic changes and IE6 will behave as expected.
Also, some more knowledgeable of you may be tempted to skip the conditionally commented CSS and instead "hack" the original BODY rule decleration as follows:
BODY {
position:relative;
/* otherwise footer will position itself in relation to window height */
min-height:100%;
/* makes page start out at least as tall as the browser window */
height:auto !important;
/* make non-IE6 browsers ignore the next line */
height:100%;
/* hack for IE6 that doesn't recognize min-height. */
margin:0;
padding:0;
/* not strictly required but useful. reset to push body to edges of browser window */
}
While this will work, it does add unneeded CSS that other browsers need to parse. It's my personal oppinion that IE6 should be the only one doing heavy lifting, CSS wise, since it's the one we're having to work around.
Quick date validation with a Regular Expression
/(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:29|30))|(?:(?:0[13578]|1[02])-31))/
Here's the breakdown of what it does:
Given a date format of YYYY-MM-DD (standard MySQL date format and easiest format for sorting) it makes sure that
- the year is numeric and starts with 20 or 19, and
- the month is numeric and is either
- between 01 - 12 and followed by a numeric day value between 01-28;
- between 01 - 12 but not 02 and followed by a day value of 29 or 30; or
- one of 01,03,05,07,08,10,12 and followed by a day value of 31
I have left out Feb. 29th so that you are forced to do a secondary leap year check.
Four quick PHP filters to reduce contact form spam.
I maintain the code for a fairly popular, if localized, blog about the Hintonburg area called Miss Vicky's Offhand Remarks. It's been around for over 5 years now. It has a contact form to allow neighbourhood residents, and anyone else, to send in tips and requests. I've resisted attaching a captcha to it, as I find them annoying. As a result we get occasional waves of bot spam. I have found that by studying the spam, I have been able to cut down on most of the seriously egregious scripts out there. Now I've pulled out (and simplified) the actual code I use, so these snippets aren't going to work as is, but they should be enough to illustrate the methodology. First, I do filter the referrer to ensure any form posted on the site appears to come from my server. Yes, this is easily faked, but if does cut down on a surprising amount of poorly written (lazy) scripts.
...
if($_SERVER['REQUEST_METHOD'] == 'POST')) {
$srv_rx = '/^http';
$srv_rx .= ($_SERVER['HTTPS'])?('s'):('');
$srv_rx .= ":\/\/".str_replace('.','\.',$_SERVER['SERVER_NAME']).'/';
if (!preg_match($srv_rx ,$_SERVER['HTTP_REFERER'])) {
//should track this, since it's probably a hacker/script
//instead, i will simply die.
$action = 'return';
}
}
...
Then I do three comment form specific checks. The first thing I look for is an inordinate amount of links. If the text is comprised of more than half urls, I throw it back. Again, easily worked around, but this seems to catch most scripts. I do let a legitimate user know that their message has failed to send, in case they want to reformat the message. Again, most scripts don't really care if you've returned anything, so I'm not giving away a trade secret here.
...
if (strlen(preg_replace('/(\W|\s)(?:(?:ht|f)tp(?:s?)\:\/\/)?(?:\w+:\w+@)?'
. '(?:(?:[-\w]+\.)+'
. '(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))'
. '(?::[\d]{1,5})?(?:\/(?:[-\w~!$+|.,\:\*\/&?#=]|%[a-f\d]{2})*)?(\W|\s)/',
'$1$2',$email_text))/strlen($email_text) < (1/2)) {
//text is more than 1/2 urls. probably a bot.
$problem = 'Email not sent because the text looked too spammy'
. ' (url to "real text" ratio too high).<br />Sorry... sort of';
break;
}
...
Since this is a contact form I'm not expecting any formatting. It should just be text. If the email is more than one third HTML, I throw it back. Again, I let the user know, since I have had users send me bits of info and code about the site itself, when they've found bugs.
if (strlen(strip_tags($email_text))/strlen($email_text) < (2/3)) {
//text is more than 1/3 html. probably a bot.
$problem = 'Email not sent because the text looked too spammy'
. ' (HTML to "real text" ratio too high).<br />Sorry... sort of';
break;
}
...
Next, look for both an HTML anchor, and a BBCode url or link tag. If they both exist, it's spam. Again, I send it back, because you never know. Some people are confused.
...
if (preg_match('/<a(?:[^>])*href/',$email_text)
&& preg_match('/\[(?:url|link)=/',$email_text)) {
//text contains both anchor tag and bbcode link. probably a bot.
$problem = 'Email not sent because the text looked too spammy'
. ' (wacky linking).<br />Sorry... sort of';
break;
}
...
And that cuts down on the majority of our contact form spam, and the rest will have to wait untill I can figure out how to write a regular expression that detects 'crazy-talk'.
Fun with static methods in Flash AS3 : controlling instances
Every once and a while there are times, especially when creating a public API, when you want to be able to hide settings and actions from plain view. Here's a fun little trick: using public static methods to control instances of a class. By creating internal interfaces, you can use static methods to control various aspects of a class that wouldn't be accessible through "normal means". Now, the following code is obviously an over simplified example, but it does show the concept. It shows how to access normally inaccessible properties, do extended actions during set up, or even simulate Constructor overloading.
package {
public class TestStatic {
private var _readOnly:boolean = false;
private var _name:String;
private var _color:String;
public function TestStatic(name:String,color:String) {
_name = name;
_color = color;
}
//secondary constructors
public static function BlueTestStatic(name:String):TestStatic {
return new StaticTest(name,'Blue');
}
//access advanced settings
public static function makeReadOnly(instance:TestStatic):void {
instance.readOnly = true;
}
//change 'read only' properties
public static function rename(instance:TestStatic,name:String):void {
instance.name = name;
}
public function get color():String {
return _color;
}
public function set color(value:String):void {
if(!_readOnly) {
_color = value;
}
else {
throw new ReferenceError("this property is read only");
}
}
public function get name():String {
return _name;
}
internal function set name(value:String):void {
if(!_readOnly) {
_name = value;
}
}
internal function set readOnly(value:Boolean):void {
_readOnly = value;
}
}
}
var ts:TestStatic = new TestStatic("Henry","Orange");
trace(tsN.name); //returns "Henry";
TestStatic.rename("Hank");
trace(tsN.name); //returns "Hank";
