PDA

View Full Version : Detecting changed values on dynamic objects



IQAndreas
October 8th, 2009, 03:15 PM
I was wondering if it is possible to capture whenever properties are set/accessed/created on dynamic objects?

Basically, among others, I want to create a little quick system which acts as an Array and allows you to set the same value of all items inside of it.

For example:

var dynamicObjArray:DynamicObjectArray = new DynamicObjectArray();
dynamicObjArray.push(mc1, mc2, mc3, mc4, mc5, mc6);

//Sets all of the items inside the array to an alpha value of 0
dynamicObjArray.alpha = 0;

//Tweens all of the values in the array to an alpha value of back to 1
//Instead of 6 different tweens running, this one tween will
//act on all 6 and set them to the same value.
//Of course, TweenLite doesn't know this. All it sees is that one object's property.
TweenLite.to(dynamicObjArray, 1, {alpha:1});
There are several workarounds, (including checking the current value each frame) but I want to know if there is any way of detecting changed properties at all.

efos
October 8th, 2009, 05:32 PM
DataChangeEvent is only used by certain components. If you look at the Object class you'll see a "watch" method, but you can't access it.

In Flex this is handled pretty seamlessly with the [Bindable] tag. In order to make a Bindable variable in Flash (and make use of it) I think you would need to extend the parent object to dispatch a change event, and the then extend the child objects to listen for it.

This would seem less painful:

On DynamicObjectArray
private var _alpha:Number
public function set alpha(n:Number):void{
_alpha = n
for(var i:int = 0; i < this.length; i++){
this[i].alpha = _alpha
}

}
public function get alpha():Number{
return _alpha
}

IQAndreas
October 8th, 2009, 05:54 PM
The thing is, DynamicObjectArray should be, just as it says, a dynamic class.

That means that people can add or remove properties to and from it in any way they like.

I'm considering adding the code you suggested, yes, first but I want to know if ANY value on DynamicObjectArray changes or is created. Since the object is dynamic, users can add any value they want, even if I didn't originally predict it.


dynamicObjArray.orderTrippleFudgeMuffin = true;
dynamicObjArray.doTheZombieBoogie();
dynamicObjArray.temperatureOnTheMoon = -400;

senocular
October 8th, 2009, 06:08 PM
Nada, unless you're using a Proxy

IQAndreas
October 8th, 2009, 07:29 PM
Nada, unless you're using a Proxy
Hm... I have heard of the Proxy class before, and I found one site that explains it a bit. But I still don't quite get it, and the language reference isn't really beginner friendly when explaining that class.

Could you clarify exactly what it is for a little more?
Why would someone want to use the Proxy class to get/set/delete properties on objects instead of just doing so manually?

Krilnon
October 8th, 2009, 08:49 PM
Didn't you basically ask this question already in another thread?: http://www.kirupa.com/forum/showthread.php?t=334671

I mean, you were talking about dynamic property creation detection instead of detecting when they were changed… but those two topics are closely related. Anyway… it's not a big deal. :P


I was wondering if it is possible to capture whenever properties are set/accessed/created on dynamic objects?

Not in general. I'm guessing that a reason for not including this feature is that it's computationally expensive to do so. It may even have imposed an expense on dynamic object property access in general, even if you weren't listening for changes.

ActionScript 1 and 2 have Object#watch, which is pretty much exactly what you want. However, it wasn't included in AS3, perhaps for performance reasons, I haven't heard any official reason.

Like senocular mentioned in your other thread, you can use flash.utils.Proxy to detect most things on instances of a Proxy subclass. One caveat is that when you extend Proxy, that class can no longer extend any other built-in class, so you can't really have your Proxy instance pretend to be a MovieClip if you're passing it to a strictly-typed function. Here's how I might implement something like your DynamicObjectArray (but I'm calling in ArrayMap here):

Maybe a test FLA or something, to show what this class does:

var am:ArrayMap = new ArrayMap(new MovieClip(), new MovieClip(), new MovieClip());

trace(am.array::length); // 3
am.array::push(new MovieClip());
trace(am.array::length); // 4, after some trickery

trace(am.name); // instance1
trace(am.each::name); // instance1,instance2,instance3,instance4

am.name = 'identical';
trace(am.each::name); // identical,identical,identical,identical

// do something more complicated here
am.someFunc = function(arg1, arg2){ trace(this[arg1], this[arg2]) };
am.each::someFunc('name', 'x');

/* the output isn't interesting, really, but hypothetically it could be
identical 0
identical 0
identical 0
identical 0
*/

array.as:

package {
// a global-ish namespace to access the array internal to the ArrayMap
// it's in the default package for convenience…
public var array:Namespace = new Namespace('ArrayMap.array');
}

each.as

package {
// you can choose what sort of values you get back with this…
public var each:Namespace = new Namespace('ArrayMap.each');
}

ArrayMap.as:

package {
import flash.utils.*;

dynamic public class ArrayMap extends Proxy {
private var _arr:Array;
public function ArrayMap(...args){
super();
_init.apply(this, args);
}

// give this a constructor like Array's
private function _init(...args):void {
if(args.length == 1 && args[0] is int){
_arr = new Array(args[0]);
} else {
_arr = new Array();
_arr.push.apply(_arr, args);
}
}

// maybe this will be useful for debugging
public function get __sourceArray():Array {
return _arr;
}

override flash_proxy function getProperty(name:*):* {
if(name is QName){
var qname:QName = name;
if(qname.uri == array.uri){
var val:* = _arr[qname.localName];
if(val is Function){
/*
a trick to bypass the (dumb, IMO) semantics of AS3… I think
see: https://bugs.adobe.com/jira/browse/ASC-2812
*/
return function(...args){ return val.apply(_arr, args) };
}
return val;
} else if(qname.uri == each.uri){
// maybe this can return an array of that property for each element

/*
however, properties that are functions return a single function instead,
due to the same semantic issue as before, and because it's convenient
*/

if(_arr[0][qname.localName] is Function){
return function(...args){
_arr.map(mapCall);
function mapCall(e:*, i:int, arr:Array):* {
trace(e, i, '--', arr, qname.localName, args.length);
if(args.length > 0){
return e[qname.localName].apply(e, args);
} else {
return e[qname.localName].call(e);
}
}
}

}

return _arr.map(mapGetProp);

function mapGetProp(e:*, i:int, arr:Array):* {
return e[qname.localName];
}
}
name = qname.localName;
}

// get this property for the first array element
if(!_arr.length) return undefined;
return _arr[0][name];
}

override flash_proxy function setProperty(name:*, value:*):void {
if(name is QName){
var qname:QName = name;
if(qname.uri == array.uri){
_arr[qname.localName] = value;
}
name = qname.localName;
}

// assign the property for each element
_arr.forEach(forEachAssign);

function forEachAssign(e:*, i:int, arr:Array):void {
e[name] = value;
}
}

override flash_proxy function callProperty(name:*, ...args):* {
if(name is QName){
var qname:QName = name;
if(qname.uri == array.uri){
if(args.length){
return _arr[qname.localName].apply(_arr, args);
} else {
return _arr[qname.localName].call(_arr);
}
} else if(qname.uri == each.uri){
// call on each element and collect the results
return _arr.map(mapCall);

function mapCall(e:*, i:int, arr:Array):* {
if(args.length){
return e[name].apply(e, args);
} else {
return e[name]();
}
}
}
name = qname.localName;
}

// call on the first array element because the caller didn't specify 'each'
if(!_arr.length) return undefined;
if(args.length){
_arr[0][name].apply(_arr[0], args);
} else {
_arr[0][name].call(_arr[0]);
}
}
}
}

Of course, you can argue over how you would want to implement things for your specific use case, but this should give you a general idea of what you can do. It's also not heavily tested. :P (and I know that some things may not work as expected… but whatever)

Oh, the reason that I'm using two custom namespaces is so that the special things that you can do don't collide with other property names. It also looks a bit clearer to me. The reason that I'm returning the first array element (or result) in getProperty and callProperty is so that it can be treated like a single object by an unknowing user (so a tweener could write and read the number).

greensock
October 9th, 2009, 02:58 AM
Yep, Proxy is the only option I can think of too, but if you're just trying to clean up your tweening code so that you don't have to write out a bunch of tweens that go to the same value(s), you might want to consider using TweenMax's allTo(), allFrom(), or allFromTo() methods (new in v11). Simply pass in an array of targets, like so:


TweenMax.allTo([mc1, mc2, mc3, mc4, mc5], 1, {alpha:0.5});

You can even have it stagger the start times of the tweens if you want. http://blog.greensock.com/v11beta/

I realize your question likely related to a much broader objective (beyond tweening) so this post may not be worth much, but I figured I'd chime in and point out allTo() in case you found it helpful.

IQAndreas
October 10th, 2009, 06:52 PM
Didn't you basically ask this question already in another thread?: http://www.kirupa.com/forum/showthread.php?t=334671
Oh... Funny, I have been thinking about writing this topic for a while. Guess I did write it once and then forgot about it... :eyeup:

Anyway, thank you so much. The Proxy class is right on target what I am looking for! :) Now I finally understand it. And now I also finally understand why someone would want to use namespaces.

And thanks greensock for pointing out the TweenMax feature. I'm liking you and your code more and more.


Many thanks, guys! :D

EDIT: So, how much does the Proxy class bog down on run time? Is it a huge difference?

Krilnon
October 11th, 2009, 03:00 AM
Proxy instances can introduce arbitrary amounts of lag into your program because you can have infinite while loops and such.

In the most bare cases, I think (without having tested this myself) that the cost is about the same as an accessor method, since in either case you're incurring the cost of a function call instead of a property access.