Wednesday, January 19, 2011

AS3 class name collision - a story of one bug

Recently I spent almost a day fighting with a mysterious Flash error.

Here is the situation. The system is structured in the following way: One SWF (a wrapper) loads other SWFs (let's call them “modules”). One of the modules, in turn, loads another SWF, which we will call “content”. The wrapper is being developed by one programmer, the content – by another and I am working on the module (a typical setup for a virtual world project).

The wrapper is tied to the server in many ways, so I prefer to test locally only the module and the content (besides, the interaction between the wrapper and the module is minimal, and was already tested).

So, I launched the module locally, and the system run fine. The content is loaded, the module cast it to the required interface and called certain methods on the content – everything is perfect. Then I uploaded the module and the game to the server, launched the entire system , and the flash player reported an exception. I checked the source and quickly realized that the exception happened because the module could not cast the loaded object to the required interface.

My first thought was that the SWF for some reason cannot be loaded from the server. I've added all possible error handlers, and found nothing. The content was loaded in the following way:

_loader = new Loader();
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete, false, 0, true);
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onErrorLoading, false, 0, true);
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.NETWORK_ERROR, onErrorLoading, false, 0, true);
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.VERIFY_ERROR, onErrorLoading, false, 0, true);
_loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityErrorLoading, false, 0, true);
_loader.load(new URLRequest(fileLocation));

Here is what happened in the onLoadComplete method:
log.debug("loader info content type:"+_loader.contentLoaderInfo.contentType);
log.debug("loader info URL:"+_loader.contentLoaderInfo.url);
log.debug("loader content:"+_loader.content);
myContent = _loader.content as MyContentInterface;
The onErrorLoading method was never called; the only method executed was onLoadComplete. When running module locally, I was getting the following output in the log:
10:33:00:453 [DEBUG] loader info content type:application/x-shockwave-flash
10:33:00:453 [DEBUG] loader info URL:file:///C|/Projects/[... some correct path here ...]/Main.swf
10:33:00:453 [DEBUG] loader content:[object Main]
And here is what I was getting on the server:
12:01:03:375 [DEBUG] loader info content type:application/x-shockwave-flash
12:01:03:375 [DEBUG] loader info URL:[... correct URL...]
12:01:03:375 [DEBUG] loader content:instance43942.instance43943
It is obvious that the difference is in the loader content: in one case it is an object, and in the other case it contains some strange instances.

At this point I got stuck. I tortured the developer of the content SWF, asked him to try different compile modes (with/without network support etc...), checked file versions and paths on the server, checked security settings – all without any success or any clue.

After many painful hours, all of a sudden I got an insight. I run and checked the source code for the wrapper and for the content SWFs. Turned out that the developers of both wrapper and content SWFs called their main classes Main, and put them in the default package. Of course, the content SWF was loading fine, but its Main class could not be loaded because of the name collision. When the content developer moved his Main class into the proper package the problem was solved and the whole system worked like a charm.

The most curious thing here is that the Flash player did not complain about the name collision in any way – it just silently ignored the problem.

Hopefully my story will help someone and save their time.