package com.pblabs.core
{
import com.pblabs.pb_internal;
import com.pblabs.property.PropertyManager;
import com.pblabs.util.TypeUtility;
import flash.utils.Dictionary;
import com.pblabs.eh.*;
use namespace pb_internal;
/**
* Container class for PBComponent. Most game objects are made by
* instantiating PBGameObject and filling it with one or more PBComponent
* instances.
*/
public class PBGameObject extends PBObject
{
private var _deferring:Boolean = true;
private var _components:Dictionary = new Dictionary();
private var componentHead:PBComponent;
/**
* [CH] The core that this group is a part of. While its possible for
* a group such as a prefab to have multiple cores, there is always only
* one 'owning' core.
**/
public var core:PBCore;
public function PBGameObject(_name:String = null)
{
super(_name);
}
public function get deferring():Boolean
{
return _deferring;
}
/**
* If true, then components that are added aren't registered until
* deferring is set to false. This is used when you are adding a lot of
* components, or you are adding components with cyclical dependencies
* and need them to all be present on the PBGameObject before their
* onAdd methods are called.
*/
public function set deferring(value:Boolean):void
{
/**
* [CH] I'm commenting this out and removing the deferral code in
* general. I'm not really sure what value this presents at this time.
* I think that the 'initialize' code here initializes the components
* without the addition behaviors with ! in front of them, etc.
**/
/*
if(_deferring && value == false)
{
// Loop as long as we keep finding deferred stuff, the
// dictionary delete operations can mess up ordering so we have
// to check to avoid missing stuff. This is a little lame but
// our previous implementation involved allocating lots of
// temporary helper objects, which this avoids, so there you go.
var foundDeferred:Boolean = true;
while(foundDeferred)
{
foundDeferred = false;
// Initialize deferred components.
for(var key:String in _components)
{
// Normal entries just have alphanumeric.
if(key.charAt(0) != "!")
continue;
// It's a deferral, so init it...
doInitialize(_components[key] as PBComponent);
// ... and nuke the entry.
_components[key] = null;
delete _components[key];
// Indicate we found stuff so keep looking. Otherwise
// we may miss some.
foundDeferred = true;
}
}
}
*/
_deferring = value;
}
/**
* [CH] Not really proud of this code. Perhaps there's a better way.
* Essentially this is a hairy linked list priority queue. Why didn't I
* use simple priority queue? I thought the allocs would be too much.
* I mean, do I need to alloc an obj, dictionary, *AND* array in order
* to create a linked priority queue? Hell no!
*
**/
public function sortComponents():void
{
//trace("[CH] sortComponents "+this.name+" ---------------");
componentHead = null;
for(var key:String in _components){
// [CH] only sort items without a ! in front...lame.
//if(key.charAt(0) != "!")
// continue;
var component:PBComponent = _components[key];
//trace("[CH] attempting to insert:"+component.name);
if(componentHead == null){
// [CH] Init our head.
//trace("[CH] setting the head to "+component.name);
componentHead = component;
componentHead.next = null;
}else{
// [CH] Find our spot in the list.
var done:Boolean = false;
var c2:PBComponent = componentHead;
var c1:PBComponent = null;
while(!done){
if(component.priority <= c2.priority)
{
//trace("[CH] adding before "+c2.name);
component.next = c2;
if(c1){
//trace("[CH] adding in front of "+c1.name);
c1.next = component;
}
if(c2 == componentHead){
//trace("[CH] setting to component head.");
componentHead = component;
}
break;
}else{
// [CH] priority is higher. are we at the end?
if(c2.next == null){
//trace("[CH] end of list, adding as tail");
// [CH] we're the highest.
c2.next = component;
component.next = null;
break;
}
}
c1 = c2;
c2 = c2.next;
if(c2 == c2.next){
done = true;
//trace("[CH] We set a circular loop!"+c2);
}
//trace("[CH] moving on, next item: "+c2.name);
if(c2 == null){
//trace("[CH] end of list??");
break;
}
}
}
}
/**
* [CH] This can be pretty useful for debugging but ridiculously
* spammey.
**/
//this.outputComponentList();
}
public function outputComponentList():void
{
var c:PBComponent = componentHead;
var str:String = "";
while(c != null){
str += c.name + ":"+c.priority+",";
c = c.next;
}
trace("[CH] components by priority:"+str);
}
protected function doInitialize(component:PBComponent):void
{
component._owner = this;
owningGroup.injectInto(component);
component.doAdd();
}
/**
* Add a component to the PBGameObject. Subject to the deferring flag,
* the component will be initialized immediately.
*
* If there is a public var on this PBGameObject (ie, you've subclassed
* PBGameObject) with the same name as the component has, it will be
* populated with a reference to the component. This way you can get
* typed access to components on your game objects.
*/
public function addComponent(component:PBComponent, name:String = null):void
{
if(name)
component.name = name;
if(component.name == null || component.name == "")
throw new Error("Can't add component with no name.");
// Stuff in dictionary.
_components[component.name] = component;
// Set component owner.
component._owner = this;
// Directly set field if present.
if(hasOwnProperty(component.name))
this[component.name] = component;
// Defer or add now.
if(_deferring){
//_components["!" + component.name] = component;
}
else{
doInitialize(component);
}
}
/**
* Remove a component from this game object.
*/
public function removeComponent(component:PBComponent):void
{
if(component.owner != this)
throw new Error("Tried to remove a component that does not belong to this PBGameObject.");
if(this.hasOwnProperty(component.name) && this[component.name] == component)
this[component.name] = null;
_components[component.name] = null;
delete _components[component.name];
component.doRemove();
component._owner = null;
}
/**
* Look up a component by name.
*/
public function lookupComponent(name:String):*
{
return _components[name] as PBComponent;
}
/**
* Get a fresh Vector with references to all the components in this
* game object.
*/
public function getAllComponents():Vector.<PBComponent>
{
var out:Vector.<PBComponent> = new Vector.<PBComponent>();
for(var key:String in _components)
out.push(_components[key]);
return out;
}
/**
* Initialize the game object! This is done in a couple of stages.
*
* First, the PBObject initialization is performed.
* Second, we look for any components in public vars on the PBGameObject.
* This allows you to get at them by direct typed references instead of
* doing lookups. If we find any, we add them to the game object.
* Third, we turn off the deferring flag, so any components you've added
* via addComponent get initialized.
* Fourth, dependency injection is performed on ourselves and our components.
* Finally, we call applyBindings to make sure we have the latest data
* for any registered data bindings.
*/
public override function initialize():void
{
super.initialize();
/**
* [CH] We're assuming that we've got to sort the components and
* initialize based upon their priority. After initialization, priority
* really doesn't mean anything.
**/
this.sortComponents();
var nc:PBComponent = this.componentHead;
//for each(var key:String in TypeUtility.getListOfPublicFields(this))
while(nc != null)
{
// Only consider components.
//if(!(this[key] is PBComponent))
// continue;
// Don't double initialize.
//if(this[key].owner != null)
// continue;
// OK, add the component.
//const nc:PBComponent = this[key] as PBComponent;
//if(nc.name != null && nc.name != "" && nc.name != key)
// throw new Error("PBComponent has name '" + nc.name + "' but is set into field named '" + key + "', these need to match!");
//nc.name = key;
doInitialize(nc);
nc = nc.next;
}
/**
* [CH] The following is currently turned off, with the expectation that
* the PBGroup object will be used, and this object is an abstract
* base class.
**/
// Inject ourselves.
//owningGroup.injectInto(this);
// Stop deferring and let init happen.
deferring = false;
// Propagate bindings on everything.
nc = componentHead;
while(nc != null)
//for(var key2:String in _components)
{
if(!nc.propertyManager)
throw new Error("Failed to inject component properly.");
nc.applyBindings();
nc = nc.next;
}
}
/**
* Removes any components on this game object, then does normal PBObject
* destruction (ie, remove from any groups or sets).
*/
public override function destroy():void
{
for(var key:String in _components)
removeComponent(_components[key]);
super.destroy();
}
/**
* Get a value from this game object in a data driven way.
* @param property Property string to look up, ie "@componentName.fieldName"
* @param defaultValue A default value to return if the desired property is absent.
*/
public function getProperty(property:String, defaultValue:* = null):*
{
return owningGroup.getManager(PropertyManager).getProperty(this, property, defaultValue);
}
/**
* Set a value on this game object in a data driven way.
* @param property Property string to look up, ie "@componentName.fieldName"
* @param value Value to set if the property is found.
*/
public function setProperty(property:String, value:*):void
{
owningGroup.getManager(PropertyManager).setProperty(this, property, value);
}
}
}