Is there any way for me to define implicit or explicit type conversions in ActionScript?
For instance, I want to define a conversion such that Array can cast into MyClass implicitly. If not, an explicit cast would be useful. I know I can always pass it into my constructor, but I am dealing with semantics here, and I am most interested in a conversion solution if it exists.
Type casting in ActionScript 3
Object(instanceOfOtherObject);
Works based on the valueOf property of the given class (if defined). Therefore, you can define your class MyClass as such:
package {
public class MyClass {
private var myArray:Array;
public function MyClass(inputArray:Array = null) {
myArray = (inputArray ? inputArray : new Array());
}
public function valueOf():Array {
return myArray;
}
}
}
Then you will be able to perform this typecasting:
var mc:myClass = new MyClass();
var arr:Array = Array(myClass);
To my knowledge, the reverse is not an option because the valueOf function of Array does not return an object of type MyClass. There is nothing stopping you from creating a CastableArray that extends and overrides the valueOf function of Array to make it return an instance of MyClass using the constructor I defined above, though you may run into other issues with other fundamental language components that expect an Array to return an Array in its valueOf property (comparison of objects comes to mind).
I have not done any particular testing with this next suggestion, but if MyClass extends from Array and does not define a valueOf function, it may still be possible to do the type conversion depending on the constructor of MyClass and what Flash does in circumstances when valueOf is not defined.
Related
I have some code that I am using to convert arbitrary QObject subclasses to JSON. I able to convert them if they are pointers to a subclass, but am curious whether it is possible to convert instances (provided the subclass implements a copy constructor). Is there some crazy way to use something like templates or the type information provided by QMetaType to copy an instance of a QObject subclass without knowing what it is? The ToJson code is in a class that has no knowledge of the subclass.
I think it might be possible with QMetaType::create or something similar but I haven't been able to figure out how to actually copy the properties of the subclass instance.
Here's my code for converting:
QJsonValue ToJson(QVariant value){
switch(value.type()){
case QVariant::Int:
case QVariant::Double:
return value.toDouble();
////Other cases, etc...
case QVariant::UserType:
QObject* obj_ptr = qvariant_cast<QObject*>(value);
if(obj_ptr) // value was originally a pointer to a QObject, works correctly
return ToJson(obj_ptr);
else { // value was orginally an instance of a QObject subclass
std::string t = value.typeName(); //returns "MyQObject"
int id = QMetaType::type(t.c_str()); //returns the id of the derived class
void* v = QMetaType::create(id, &value); //passing &value does nothing
obj_ptr = static_cast<QObject*>(v);
return ToJson(obj_ptr); //works, but resulting fields are all default
}
}
}
QJsonObject ToJson(QObject* o){
QJsonObject obj;
auto mo = o->metaObject();
for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i){
QVariant value = o->property(mo->property(i).name());
obj[mo->property(i).name()] = ToJson(value);
}
return obj;
}
Sample code use case:
qRegisterMetaType<MyQObject>();
MyQObject obj;
obj.db = 11.1;
QVariant test1 = QVariant::fromValue(obj);
QVariant test2 = QVariant::fromValue(&obj);
QJsonValue v1 = ToJson(test1); // default constructed values
QJsonValue v2 = ToJson(test2); // db = 11.1
Sample QObject subclass:
class MyQObject : public QObject {
Q_OBJECT
Q_PROPERTY(double DB MEMBER db)
Q_PROPERTY(int I MEMBER i)
public:
MyQObject();
MyQObject(const MyQObject& other) : QObject() {
i = other.i;
db = other.db;
}
int i = 50;
double db = 1.5;
};
Q_DECLARE_METATYPE(MyQObject)
Is there any way to handle the case illustrated by test1 above?
Long-story-short: nope. There is no way to store QObjects by value in containers or QVariant.
Qt forbids the copy of QObjects and all inheriting classes. The mandatory the Q_OBJECT macro will disable any copy constructor also in newly defined classes.
The copy constructor that you are defining in the MyObject class is missing the base class constructor call. If QObject had a copy constructor it would be something like this:
MyQObject(const MyQObject& other) :
QObject(other) // this will NEVER compile
{
i = other.i;
db = other.db;
}
Probably, the compiler is giving you a warning, but allows you to have such a constructor, even if it will result in undefined behavior or slicing an instance of MyObject every time it is passed by value.
Furthermore, the Qt docs states the following:
The values stored in the various containers can be of any assignable
data type. To qualify, a type must provide a default constructor, a
copy constructor, and an assignment operator. This covers most data
types you are likely to want to store in a container, including basic
types such as int and double, pointer types, and Qt data types such as
QString, QDate, and QTime, but it doesn't cover QObject or any QObject
subclass (QWidget, QDialog, QTimer, etc.).
So you can't store QObject and derived classes inside a Qt container unless you store them as pointers, as copy of QObjects is disabled by design.
Furthermore, if you want to exploit polymorphic behavior you must use pointers, even if there is no explicit need to cast to derived classes in your code, as far as I can see. If you really need to resort to casting in some place, you could consider making your ToJson a template function.
There is a solution, but use caution as it is only reasonable/applicable in the following scenario:
Classes in question are primarily data storage classes
The classes in question would be entirely copy-able if they didn't inherit from QObject
Most importantly, the ONLY reason you have the class inherit from QObject is so that it can have meta properties.
If your code uses the class as a QObject for any reason other than to get meta information, you are almost certainly using it incorrectly if you are trying to store it by value (as explained by G. Giordano in their answer).
Misuse considerations aside, in order to JSON-ify a QVariant that stores a QObject subclass by value, you can use the QMetaType::create method and pass it the user type id and yourQVariant.constData().
Example:
MyQObject obj;
obj.db = 11.1;
QVariant value = QVariant::fromValue(obj);
std::string t = value.typeName();
int id = QMetaType::type(t.c_str());
void* v = QMetaType::create(id, value.constData());
obj_ptr = static_cast<QObject*>(v);
QJsonValue json = ToJson(obj_ptr); //json contains db = 11.1
I've got a method that accepts a parameter of type Class, and I want to only accept classes that extend SuperClass. Right now, all I can figure out to do is this, which does a run-time check on an instance:
public function careless(SomeClass:Class):void {
var instance:SomeClass = new SomeClass();
if (instance as SuperClass) {
// great, i guess
} else {
// damn, wish i'd have known this at compile time
}
}
Is there any way to do something like this, so I can be assured that a Class instance extends some super class?
public function careful(SomeClass:[Class extends SuperClass]):void {
var instance:SuperClass = new SomeClass();
// all is good
}
If you are going to instantiate it anyway, why not accept an object instead which allows you to type it to :SuperClass?
careless(SomeClass);
//vs.
careless(new SomeClass);
Not too much of a problem there as far as your code goes.
There are a few differences though:
The object has to be created, because an object is required. If your function does not instantiate the class under some circumstances, this can be a problem. Additional logic to pass either an object or null can bloat the function call.
If you cannot call the constructor outside that function, it won't
work either.
All that is solved by the factory pattern. Pass a factory as the parameter that produces SuperClass objects.
function careful(factory:SuperClassFactory)
Your requirements:
I want to only accept classes that extend SuperClass
and
I need to pass in a Class so that it can be instantiated many times
by other objects later
Can be met by passing in an instance of the class you need, and using the Object.constructor() method.
public function careful(someInstance:SuperClass):void {
//you probably want to store classRef in a member variable
var classRef: Class = someInstance.constructor();
//the following is guaranteed to cast correctly,
//since someInstance will always be a descendant of SuperClass
var myInst:SuperClass = new classRef() as SuperClass;
}
More reading here.
You can't do that in ActionScript 3. In languages like C# you can do something like (forgive me if the syntax is off):
public void Careless<T>() where T : SuperClass
But AS3 does not have 'generics'. Unfortunately the only way I know how to do what you want is the way you have already done.
A pattern that might be more suitable for your use case might be something like:
class SuperClass
{
public static function careless():void
{
var instance:SuperClass = new SuperClass();
// ...
}
}
The only way to have static type checking in ActionScript 3 is to provide an instance of a class.
It is possible but it's expensive. You can use on a Class (not instance) the:
flash.utils.describeType
You then get an XML with a bunch of information including inheritance for that class. Like I said it's an expensive process and probably creating an instance and checking it will be in most cases faster.
In languages like Java, C++ and etc there is the ability to provide, for example, a toInt() function to allow your code to be converted neatly by language features into a given primitive type. (In this example, an Int.)
That is, if you had myObject() with the standard casting function toInt() declared, then calls like Int(myObject) would just work. This is much more relevant to situations where you just want to forget about the cast altogether and just get something done - someVar:Int = myObject + 3 ... for an arbitrary example.
I've searched through AS3 docs and done some searching outside that but it appears there is no such sets of functions, interfaces, or other such things easily accessible in AS3. Does anyone know such a thing? It seems like essential knowledge in any language that supports such casting features and I'm at my wit's end with the verbosity of writing a partially qualified name like myObject.toInt() in the midst of mathematical work.
It's a common misconception that operator overloading in AS3 is impossible. It's not, but it's not entirely common practice, and it doesn't work as in other languages.
AS3 is "gradually typed". This means that you can specify type when you want to, you don't have to, and when performing operations on two different types it'll infer/cast for you in a logical way.
For objects, AS3 provides the valueOf():Object and toString():String functions which allow you to define the automatic handling of casting. The former provides the "primitive value of the object" while the latter defines the "String representation of the Object".
The default value for both is the String "[object ClassName]", but you can override this default. Here's an example:
package
{
import flash.display.Sprite;
import flash.utils.getQualifiedClassName;
public class Main extends Sprite
{
public function Main():void
{
trace("-----------------------------");
var foo = new MyClass();
trace("foo is: ");
trace(foo);
trace("foo+foo is:");
trace(foo+foo);
trace("foo+foo+'--' is:");
trace(foo+foo+"--");
trace("'--'+foo+foo is:");
trace("--"+foo+foo);
trace("Math.PI/foo:");
trace(Math.PI/foo);
trace("'5'+foo is:");
trace('5'+foo);
trace("false || foo is:");
trace((false || foo));
trace("foo | 0xC is:");
trace(foo | 0xC);
trace("typeof(foo) is:");
trace(typeof(foo));
trace("getQualifiedClassName(foo) is:");
trace(getQualifiedClassName(foo));
}
}
}
class MyClass {
public function valueOf():Object { return 3; }
public function toString():String { return "three"; }
}
And the trace output is:
-----------------------------
foo is:
three
foo+foo is:
6
foo+foo+'--' is:
6--
'--'+foo+foo is:
--threethree
Math.PI/foo:
1.0471975511965976
'5'+foo is:
5three
false || foo is:
three
foo | 0xC is:
15
typeof(foo) is:
object
getQualifiedClassName(foo) is:
Main.as$30::MyClass
The Boolean interpretation is interesting, but any non-null Object (or String) is true, so actually it works out. Whether the runtime calls valueOf() or toString() appears to be dependent on the types of the other arguments to the operators.
I got this class, let's call it: Klass.
The class Klass has a parameter for its constructor that is an array with a default value of null.
I create an object of the class Klass inside another class, lets call it: "MotherClass".
Now is when it gets weird, after I instance the object Klass, the constructor of Klass gets called again, just right after the constructor of the MotherClass ends (I placed a breakpoint and I followed step by step), with null constructor parameters.
The thing is, I don't have the stack trace of anything that is calling the constructor of Klass for the second time, no clue what could be calling that constructor again...
Any ideas?
Thanks.
(Klass implements an interface, and I'm making an instance using an array, not sure if that is affecting anything)
added code as requested:
this is the class MotherKlass:
public class Unit extends EntityVO{
public function Unit(level:int = 1)
{
//init vars and stuff
//...
//
initLevelData();
applyLevel = level;
}
private function initLevelData():void {
levelData[1] = [500, [[Spawn, this.entityToSpawn.type, this.entityToSpawn.level, 120]], "unit_level1"];
levelData[2] = [1000, [[Spawn, this.entityToSpawn.type, this.entityToSpawn.level, 90]], "unit_level2"];
levelData[3] = [2000, [[Spawn, this.entityToSpawn.type, this.entityToSpawn.level, 80]], "unit_level3"];
levelData[4] = [5000, [[Spawn, this.entityToSpawn.type, this.entityToSpawn.level, 60]], "unit_level4"];
}
override public function set applyLevel(level:int):void {
power = power / maxPower * levelData[level][0];
maxPower = levelData[level][0];
behavior = levelData[level][1];
for (var i:int = 0; i < behavior.length; i ++){
_behaviorSteps[i] = new behavior[i][0](behavior[i].slice(1));
_behaviorReqs.push(_behaviorSteps[i].req);
}
}
}
}
}
and this is Klass:
public class Spawn {
public class Spawn implements IBehavior
{
private var _entityType:String;
private var _entityLevel:int;
private var _spawnRate:int;
public function Spawn(params:Array = null){
//had to put the if because of the second weird call to the constructor with null
if(params){
_entityType = params[0];
_entityLevel = params[1];
_spawnRate = params[2];
}
}
}
}
}
Ok, this is what appears to be happening:
You have a setter for applyLevel which is overriding the super class' setter. I think your super class setter is also getting executed. Some observations that lead me to this conclusion:
your setter method overrides the super class method, but no where does it do super.applyLevel = level. This means that in the super class, the storage variable that should be keeping the level value is uninitialized. An uninitialized integer in Actionscript defaults to 0.
you mention that the second invocation of Spawn's constructor has no parameters. This occurs because your array of levelData starts out at index 1. So index 0 of this array is null.
so the super class version of the applyLevel setter method is passing in the null element from above.
I think these facts are 100% correct.
What's not clear to me is who is calling the super class' version of applyLevel Perhaps the super class (EntityVO) is doing that in it's constructor ... Hopefully this should be enough to point you in the right direction.
I'm receiving an instance of the class Unit throught Cirrus, and using RegisterClassAlias to deserialize the object. When I do that, seems like an instance of Spawn must be automatically instantiated for some reason. That is why you cant pass ojbects without default arguments. In this case, it throws an error because I'm using (params[0]) the argument that is null. I use Cirrus in other parts of my code, and it works ok because there are all ints and strings in their parameters constructors.
In ActionScript 3, there are some classes that will represent a value rather than the class itself. It's hard to explain properly what I mean, so take this example:
var str:String = "something";
var mc:MovieClip = new MovieClip();
trace(str); // something
trace(mc); // [object MovieClip]
You'll notice that the first trace outputs a value, rather than [object String]. Ontop of this, I can still make use of methods of String, like this:
var ar:Array = str.split('s');
Even though in a way you could almost read the above as:
"something".split('s');
I have a class AvLevelData that has some methods that deal with level data (which is essentially a String). At the moment there is a property data:String which represents the core level data.
The question I have is - can I replicate the behaviour of String in that when I trace or assign an instance of AvLevelData, the result is actually the String data.
For example, at the moment I need to go:
var levelData:AvLevelData = new AvLevelData();
trace(levelData.data);
To get the data. I instead want to be able to simply do the following:
var levelData:AvLevelData = new AvLevelData();
trace(levelData); // some level data string
Is this possible?
If you wan't your object to trace out your own fabricated string then you must implement a toString() function on your AvLevelData class.
In your example above, the MovieClip trace outputs: [Object MovieClip]; this comes from the default toString() implementation for Object (found on Object.prototype) . Note, you cannot override toString() as it only exists on the prototype of Object (remnants of the AS2/Javascript world), all you need to do is provide your own implementation with the same name. For instance:
public function toString():String {
return "MyCustomObjectString";
}
Some of the most basic types - String, int, Number, uint, Boolean, to name a few - are not classes / objects per se, they are primitives. In some languages there is a wrapper class available for some of these so they can be treated like objects, though Flash doesn't do this so much from my experience.
Probably the best way to answer your question is to make a toString() method for your AvLevelData class:
public function toString():String {
return data;
}
Any time you treat a class as a string (such as by putting it in trace()), flash (and many other languages) try to call toString() on the object. Typically this results in a string that's not helpful. But if you define your own toString() method, you can control what string gets output.
Another option is to simply do:
trace(AvLevelData.data);
Since that variable is a string, it should trace just fine.