function RenderKit() {
	var unicast = new ActionKit.UnicastDispatcher();
	var multicast = new ActionKit.MulticastDispatcher();
	var path = [];
	this.len = function() {
		return unicast.invoke('len',this);
	}
	this.ins = function(label,action,state,position) {
		multicast.invoke('onInsert',{label:label,action:action,state:state,position:position});
		multicast.invoke('onChange',this);
	};
	this.get = function(position) {
		return unicast.invoke('get',position);
	};
	this.del = function(position) {
		multicast.invoke('onDelete',position);
		multicast.invoke('onPosition',this);
		multicast.invoke('onChange',this);
		return ret;
	};
	this.set = function(label,action,state,position) {
		multicast.invoke('onSet',{label:label,action:action,state:state,position:position});
		multicast.invoke('onChange',this);
	};
	this.move = function(from,to,changeui) {
		if(from == to) return;
		var diff = to>from?1:-1;
		var item = this.get(from);
		item.position = to;
		if(changeui) {
			if(from < to) {
				multicast.invoke('onInsert',item);
				multicast.invoke('onDelete',from);
			} else {
				multicast.invoke('onDelete',from);
				multicast.invoke('onInsert',item);
			}
		} else multicast.invoke('onPosition',this);
		multicast.invoke('onChange',this);
	};
	this.clear = function() {
		multicast.invoke('onClear',this);
		multicast.invoke('onRender','');
	};
	this.render = function() {
		var path = unicast.invoke('getall',this);
		if(path.length <= 0) {
			multicast.invoke('onRender','');
			return;
		}
		var script = [];
		for(var i = 0; i < path.length; i++) if(path[i].state) {
			var action = new ActionKit.Action(actionkit,path[i].action);
			action.data.method = 'render';
			action.data.position = i;
			script.push(action.call());
		}
		script.push(['name']);
		$('#throbber').html('<iframe style="display: none;" src="throb.php?throb=throb"></iframe>');
		$.ajax({
			url: 'img.svm.php',
			type: 'POST',
			dataType: 'json',
			data: {s:JSON.stringify(script)},
			success: function(d) {
				$('#throbber').html('');
				multicast.invoke('onRender',d);
			},
			error: function(xhr,status,error) {
				$('#throbber').html('');
				switch(status) {
					case 'timeout':
						$.prompt('<h1>the last request timed out</h1><p>the server may be busy</p>');
						break;
					case 'error':
						$.prompt('<h1>the last request failed</h1><p>the server may be down</p>');
						break;
					case 'notmodified':
						$.prompt('<h1>the last request was cached</h1><p>oof, you may have stumbled upon an application bug</p>');
						break;
					case 'parsererror':
						if(xhr.responseText.charAt(0) == '<') $.prompt('<h1>oops! says the server...</h1>'+xhr.responseText);
						else $.prompt('<h1>error parsing response</h1><p>the server may be confused</p>');
						break;
					default:
						console.log(arguments);
				}
			}
		});
	};
	this.registerHandler = function(event,handler) {
		unicast.register(event,handler);
	};
	this.registerListener = function(event,listener) {
		multicast.register(event,listener);
	};
	this.unregisterListener = function(event,listener) {
		multicast.unregister(event,listener);
	};
	this.registerHandler('len',function(data,e) { return 0; });
	this.registerHandler('get',function(data,e) { return undefined; });
	this.registerHandler('getall',function(data,e) { return []; });
	return true;
}
