How can I patch actionscript without constantly rebuilding sfw?
There is a fairly large actionscript project that I need to modify and resulting swf is used on a live site. The problem I have is that I need to make quick small updates to the swf and it's not acceptable to update the swf on live site ten time a day (I don't control that part, I need to ask another person to put the result on live site).
What options do I have to workaround that issue? I'm a complete noob when it comes to actionscript and all flash related stuff and I'm not even sure what is possible and what isn't. I'm thinking about the following approaches, which ones are possible/acceptable?
Imagine that live site is on www.livesite.com/game.html and this page loads www.livesite.com/flashgame.swf. In that flashgame.swf among many others there is a class com/livesite/Magic.as that gets instantiated and instance of that class has a member variable xxx123 of class com/livesite/MagicWork.as. I only need to modify this MagicWork class. Now, I simply modify it, build and ask to put updated flashgame.swf live. So, I want to avoid that manual step.
All my ideas can be split in two basic approaches: 1) keep flashgame.swf totally unmodified and then load flashgame.mod.swf that contains alternative implementation of that MagicWork class, then using javascript access internals of instance of that Magic class and update its xxx123 member to be an instance of MagicWork class from flashgame.mode.swf. I'd need to modify game.html to load my javascript so that my js file would load flashgame.mod.swf and patch code inside flashgame.swf. By patching I mean javascript-style overwriting of Magic.xxx123 to a new value. flashgame.mode.swf would ideally reside on my own host that I control. Is that kind of stuff possible, if not what's not possible?
2) I could make one-time change in flashgame.swf so that it would effectively load itself my own code at runtime and patch it's xxx123 member. Is that possible?
I had already written a note about loading runtime shared libraries previously. I'll put the most essential parts of the process here, and add a link to the full article at the end.
You need to tag your main application entry point in the following manner.
[Frame(factoryClass="Preloader")]
public class Main extends Sprite
{
}
Then create a class called Preloader.
public class Preloader
{
public function Preloader()
{
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, this.loader_completeHandler);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, this.loader_ioErrorHandler);
var request:URLRequest = new URLRequest("math.swf");
var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
loader.load(request, context);
}
private function loader_completeHandler(event:Event):void
{
var mainClass:Class = getDefinitionByName("Main") as Class;
var mainInstance:Main = new mainClass();
this.addChild(mainInstance);
}
}
The full implementation of the Main class is like this.
[Frame(factoryClass="Preloader")]
public function Main()
{
var integer:IntegerArithmetic = new IntegerArithmetic(); // Type declared in math.swf
var operand1:int = 10;
var operand2:int = 10;
var result:int = integer.add(operand1, operand2);
}
Deploying Runtime Shared Libraries
The confusing bit about using a runtime shared library is realizing that the SWF has to be extracted from the SWC at the time of deploying the application. This was not immediately obvious and I ended up spending days placing a compiled SWC file in various locations and wondering why the application was unable to load it at runtime. An obscure article on the Adobe website made explicit this particular step and set things straight.
The full article along with the same example is available at http://www.notadesigner.com/runtime-shared-libraries-with-plain-actionscript/.
Related
I need to read a part of two files, and once both parts of both files are loaded to trigger a function that does some work.
What is the best way to approach this problem.
I am currently triggering the second file load once the first is loaded, however it seems poor coding style, I think this because my OO code starts looking procedural.
EDIT: So its an Air based app so using filestream.
In the end I found I actually needed to read each file in a different way. Because I need the same part of both files, and I dont know the file size, I need to read one file first and once I have fileStream.bytesAvailable and position, I can then look for the same data from the second file. I found I must handle files smaller than my read size and the end of files beyond multiples of my read size.
You don't specified what file and from where you wont to load the file but you can actually load multiples files in parallel.
If you want to read only part of file from local machine you can use AIR's FileStream class - very easy and you don't have to load whole few hundreds MB file:
import flash.filesystem.*;
var file:File = File.documentsDirectory;
file = file.resolvePath("Apollo Test/test.txt");
var fileStream:FileStream = new FileStream();
fileStream.open(file, FileMode.READ);
var str:String = fileStream.readMultiByte(file.size, File.systemCharset);
trace(str);
fileStream.close();
Another option is to use URLStream and listen for ProgressEvent.PROGRESSevents to read data of partially loaded file.
You may also want to see NetStream class which is used to stream video.
there is many options, using File, FileStream is only available on air applications.
The File class extends the FileReference class. The FileReference
class, which is available in Flash® Player as well as Adobe® AIR®,
represents a pointer to a file, but the File class adds properties and
methods that are not exposed in Flash Player (in a SWF running in a
browser), due to security considerations.
as noted above, if you are creating a non-AIR application, FileReference should be used instead of FileStream and File classes, as you dont tagged AIR in your question.
FileReference does not provide any open("path") to you (due to security considerations), but a browse method will be available and ask's your client's for selecting a file. here is an example, which also explain how to trigger a function when opening is done:
var filereference:FileReference = new FileReference();
filereference.addEventListener(Event.SELECT, onFileSelected);
var text_files:FileFilter = new FileFilter("Text Files","*.txt; *.html;*.htm;*.php");
var all_files:FileFilter = new FileFilter("All Files (*.*)","*.*");
filereference.browse([text_files, all_files]);
// triggered when a file is selected by user
function onFileSelected(e:Event):void {
filereference.removeEventListener(Event.SELECT, onFileSelected);
filereference.addEventListener(Event.COMPLETE, onFileLoaded);
filereference.load();
}
// triggered when file loading is complete
function onFileSelected(e:Event):void {
var data:ByteArray = fileReference["data"];
filereference.removeEventListener(Event.COMPLETE, onFileSelected);
}
two more events to be listened for suddenly error's occurred and displaying a progress bar for loading progress (its sync):
IOErrorEvent.IO_ERROR and ProgressEvent.PROGRESS
I'm loading an external SWF as you normally would and I am handling the COMPLETE listener with:
var documentClass:Object;
function onComplete(loadEvent:Event)
{
documentClass = Object(loadEvent.currentTarget.content);
}
This works perfectly and I can access variables and functions from the document class of the external SWF. However not all the other classes in the SWF's library aren't instantiated in the document class. I would also like to access variables and functions in these other classes, and I am currently using, for example:
var documentClass:Object;
var classOne:Class;
function onComplete(loadEvent:Event)
{
documentClass = Object(loadEvent.currentTarget.content);
classOne = loadEvent.target.applicationDomain.getDefinition("ClassName") as Class;
}
This also works. However, there are multiple other classes in the library which I want to access and it is extremely tedious to go through each of them using this method. I was hoping I could use getQualifiedDefinitionNames() (I'm using Flash CC and player 11.3 so it is available) but when I trace it, it doesn't seem to be working.
There has to be an easier way to access the other classes which I don't know of. Can anyone help?
Thank you,
James
I have an application which will be using large numbers of assets. In order to better handle that I chose to use a registry to hold all the assets so they are accessible across the entire application:
package
{
public class SpriteRegistry
{
public static var SPRITENAME = "link to image file";
public function SpriteRegistry()
{
}
}
}
What I would like to do is create an XML document and list off the file name and link so that when the application starts, this registry creates its variables which are freely accessible from that list without me needing to hard code any content directly into it.
Specifically what I need to know is how to get the "public static" effect or how to get an equivalent effect for variables that I CAN dynamically produce.
More info:
I am using a function that loads a sprite texture into a sprite object based on a string variable called mouseAttribute:
loadGraphic(SpriteRegistry[currentAttribute+"Texture"]);
Basically it's like a painting program but for a level editor for a video game.
The problem is that I'm eventually going to have 100+ sprites that I need to application to load and then I need the loadGraphic function to still be able to point effectively to the target sprite.
The library I'm using also needs me to embed the source into a class before I can pull it into the sprite object:
[Embed(source = "/Images/GridTile.png")]
public static var gridTileTexture:Class;
The reason I'm trying to avoid an array is because it means that I will have to search through an array of 100+ objects to find one sprite every time I click a single grid on the editor. That is going to chug.
It's very simple - just use a static function, which will return the XML. So you will need to load the XML file somehow (you decide where, but your registry class should have reference to it). Something similar to this:
private static var _xml:XML;
public static function initialize(xml:XML):void {
_xml = xml;
}
public static function getXML():XML {
return _xml;
}
So you will use it like that:
SpriteRegistry.initialize(loadedXML); // done only once when you initialize your app
trace(SpriteRegistry.getXML().someValue); // someValue is directly from the XML
It's a common used strategy and most of the times you would have something like an app initializer - something to load and instantiate all the things, then pass them to some registries that keep them stored for faster and global usage.
Edit:
After reading your further comments, I can't see any big change - everything would be ok with this resolution.
If you are worried about the 'need to search through array' - just do it as an object! This way you will be able to directly access the proper one using a key exactly like you pointed:
private static var _registry:Object;
public static function initialize(xml:XML):void {
// loop through xml and insert items
_registry[key] = resource;
}
public static function getResource(id):Object {
return _registry[id];
}
This way you can use it like:
SpriteRegistry.getResource(currentAttribute+"Texture");
My personal opinion is that you should avoid statics wherever possible. Instead, you should just create a single instance and provide it through dependency injection where needed.
If you were to go with that approach, you could do something like:
public function getSprite(spriteName:String):Class{
return this[spriteName];
}
or
public function getSprite(spriteName:String):Class{
return yourDictionaryOrObject[spriteName];//I'd implement it this way
}
Otherwise you could go with something like:
public static function getSprite(spriteName):Class{
return ThisHonkingBigUnnchangeableClassname[spriteName];
}
What I would not do is create a Dictionary in a static-only Class, because you're almost inevitably going to wind up with global mutable state.
Discussion, per request
Why would you want to create an instance and pass it, rather than hard-code a reference to a specific Class? A lot of the answers are covered in the global mutable state link above, but here are some that are specific to this kind of problem:
Flexibility. Say you build everything with the idea that you'd only have one set of resources being used in parallel, then you discover you need more than one--for example you might need one for color blind users, or multiple languages, or thumbnails vs. full-sized. If you hard-code to a static, then you'll have to go in every place that was hard-coded and make some sort of change to use a different set, whereas if you use DI, you just supply a different instance loaded with different resources, and done.
Testability. This is actually covered in the link, but I think it bears pulling out. If you want to run a quick test on something that needs a resource, you have to have that static "thing" and you can't change anything about it. It then becomes very difficult to know if the thing you're actually testing is working or if it just appears to be working based on the current implementation of the "thing."
Resource use: everything about an all-static Class exists from the time the swf loads to the time it unloads. Instances only exist from when you instantiate them until they are garbage collected. This can be especially important with resource files that contain embedded assets.
I think the important thing about Frameworks is to realize how they work. The major ones used in ActionScript work the same way, which is they have a central event dispatcher (event bus) that anything loaded to the framework can get a reference to by declaring an interest in it by asking for it to be injected. Additionally, they watch the stage for an event that says that something has been added (in RL it's ADDED_TO_STAGE, whereas in Mate it's the Flex event CREATION_COMPLETE). Once you understand these principles, you can actually apply them yourself with a very light hand without necessarily needing everything that comes along with a framework.
TL;DR
I usually try to avoid answering questions that weren't asked, but in this case I think it would be helpful to discuss an entirely different approach to this problem. At root, the solution comes down not to injecting an entire resource instance, but instead just injecting the resource that's needed.
I don't know what the OP's code is like, but this solution should be general enough that it would work to pass named BitmapDatas to anything that implements our Interface that is capable of dispatching against whatever IEventDispatcher we set as the eventBus (this could be the stage, a particular DisplayObject, or an EventDispatcher that is created just for the purpose.
Note that this code is strikingly similar to code I have in production ;).
public class ResourceManager {
//this can be loaded dynamically, or you can create subclasses that fill the registry
//with embedded Classes in the constructor
protected var registry:Dictionary = new Dictionary();
protected var _eventBus:IeventDispatcher;
public function registerResource(resourceName:String, resourceClass:Class):void {
var bitmap:BitmapData = new resourceClass as BitmapData;
if (resourceClass) {
registry[resourceName] = bitmap;
} else {
trace('Class didn\'t make a BitmapData');
}
}
public function getResource(resourceName:String):BitmapData {
var resource:BitmapData = registry[resourceName];
if (!resource) trace('there was no resource registered for', resourceName);
}
public function get eventBus():IEventDispatcher {
return _eventBus;
}
public function set eventBus(value:IEventDispatcher):void {
if (value != _eventBus){
if (_eventBus) {
_eventBus.removeEventListener(YourCustomEvent.GET_RESOURCE, provideResource);
}
_eventBus = value;
if (_eventBus) {
_eventBus.addEventListener(YourCustomEvent.GET_RESOURCE, provideResource);
}
}
}
protected function provideResource(e:YourCustomEvent):void {
var client:IBitmapResourceClient = e.target as IBitmapResourceClient;
if (client) {
client.resource = getResource(e.resourceName);//your custom event has a resourceName property that you populated when you dispatched the event.
}
}
}
Note that I didn't provide the Interface or the custom event or an example implementation of the Interface due to the fact I am on my lunch break, but if anyone needs that to understand the code please post back and I'll fill that in.
I've had a similar issue to this, but the means that I solved the last one are not working here.
I have a custom class that consists of 12 separate .as modules. They're declared in the document class as follows:
import trailcrest.v1.s3.averta;
import trailcrest.v1.s3.chronos;
import trailcrest.v1.s3.eripio;
import trailcrest.v1.s3.fabrilla;
import trailcrest.v1.s3.gradua;
import trailcrest.v1.s3.lingua;
import trailcrest.v1.s3.navigare;
import trailcrest.v1.s3.pedem;
import trailcrest.v1.s3.praeferre;
import trailcrest.v1.s3.scriba;
import trailcrest.v1.s3.securos;
import trailcrest.v1.s3.sonus;
public static var Averta:averta = new averta();
public static var Chronos:chronos = new chronos();
public static var Eripio:eripio = new eripio();
public static var Fabrilla:fabrilla = new fabrilla();
public static var Gradua:gradua = new gradua();
public static var Lingua:lingua = new lingua();
public static var Navigare:navigare = new navigare();
public static var Pedem:pedem = new pedem();
public static var Praeferre:praeferre = new praeferre();
public static var Scriba:scriba = new scriba();
public static var Securos:securos = new securos();
public static var Sonus:sonus = new sonus();
This is a new version of the code. I am able to successfully refer to all of these classes and the public variables and functions inside in the "osr.as" document class. I can also SEE one module from another (i.e. Sonus can see Scriba using "osr.Scriba."
Where I'm having trouble is that, while the various modules used to be able to access all of each other's public functions and variables perfectly, after I added some new modules and variables and removed some old ones, Flash Professional is still literally USING the old version. Inside of any module, the code hints are showing all of the old public functions and variables, and none of the new ones.
I am guessing that this has something to do with some sort of temporary file that I can't get to. I absolutely need this working this week!
My .fla is "Tester.fla," and the document class is "osr.as." They're both in the same directory. Also in the same directory is the folder structure "/trailcrest/v1/s3/" which contains all of the Trailcrest modules.
Help??
EDIT: Whenever I try to reference one Trailcrest class from another Trailcrest class (i.e. osr.Sonus.foo), I get...
TypeError: Error #1009: Cannot access a property or method of a null
object reference.
I have confirmed beyond a shadow of a doubt all references.
This is the age old problem of what ultimately boils down to is the Verify Error. It happens when you embed "Class A" in one or more applications, modules, swfs, etc. Ultimately every output swf that uses "Class A" must be recompiled when "Class A" is changed. Otherwise you end up with a situation where 1 module has the newer implementation but others don't. This problem is compounded by the fact that the application domain is defined as a "first in wins" when it encounters a Class of the same name / package - meaning if the old one is referenced first, the newer one loaded later gets ignored.
The more permanent solution is to use class promotion to an RSL that ultimately allows the RSL to control the actual "Class A" reference in which it also implements an "IClassAImpl" interface that all modules use. This allows the compiler to cross link the reference with a signature it knows about without actually embedding the actual class itself.
Well, I finally figured it out. Here's the skinny on what was happening:
#1: Flash was apparently pulling an old version of the Trailcrest modules. To remedy this, I backed up everything and then removed all old instances of Trailcrest from my entire computer. Then, I put only the new modules back. That fixed the problem with Code Hints showing the old modules and variables.
#2: I had been experiencing Error #1009 whenever one Trailcrest class tried to access any component of another Trailcrest class, even though the references were all correct.
The cause was that I was calling functions on the various modules directly from the document class osr.as, outside of a function. This, of course, executes on the program start.
However, all the code within one Trailcrest class that called another Trailcrest class (i.e. osr.Sonus.foo) would not be able to access "foo" because osr.as for some reason or another hadn't finished initializing the classes before it ran the code that called them. This occurred, even though the problem code was well below the code that initialized the classes (see my question).
To fix this, I simply had to wrap the problem code into a public static function in the document class, and then call it from the Timeline. That ensured that all the classes were initialized before they tried referencing each other.
Needless to say, everything is running like a well-oiled machine now. How weird.
I'd welcome any explanation as to WHY this fixed the problem.
I have two SWF files which I shall call container and slave. The slave file contains a movieclip that extends from a class I shall call base. base extends MovieClip and is compiled into an SWC. slave includes this SWC as a runtime library, while container includes it as merged (and does reference it so it should be compiled into the container SWF).
The container loads the slave like so:
bgURLRequest = new URLRequest(slaveUrl);
var context:LoaderContext = new LoaderContext(false, new ApplicationDomain( null ), SecurityDomain.currentDomain);
bgLoader.load(bgURLRequest, context);
When this loading happens, I get the error that class base was not found. I suspect this has something to do with the presence of the ApplicationDomain in there. I'm not sure what it does exactly, since I didn't write this loading code myself (but I do know that it's there for a reason so it can't be simply removed).
How to fix?
This is the solution
new LoaderContext(false, new ApplicationDomain( ApplicationDomain.currentDomain ), SecurityDomain.currentDomain);
Use ApplicationDomain.currentDomain and track the available classes with the super getDefinitionNames available at: http://etcs.ru/pre/getDefinitionNamesSource/
At least you'll know whats available in the loaded swf.