[haXe] Playing with hxformat (take 2)
Mister Sketch
mister.sketch+public at gmail.com
Tue Jul 7 17:07:55 CEST 2009
[Previous post didn't come out so nice, this is take 2, sorry, my
first time posting to this list]
I played with the format library a little this weekend trying to
create a basic Hello World swf that when ran would display Hello
World. This was rather involved in the sense that the
format.abc.Context class (while great) was lacking a few necessary
things I need. Namely, it wouldn't let me define a constructor method
for a class and it wouldn't let me define a superclass hierarchy
(which seems to be necessary for instantiating an object derived from
flash.display.MovieClip.
The reason behind this was to create a self-running swf, not just an
swf that had to be loaded by another swf and executed. The updated
format.abc.Context.hx file and the Hello.hx to use the new
capabilities are attached (Hopefully. If they don't come through,
I'll paste the code in a reply). The Hello.hx should work on flash10
targets since it requires saving the swf to the local machine, if you
want to modify it to just use the Loader and replace the
flash.Lib.current MovieClip with the created one, you can do that in
flash9.
The changes to the Context class were fairly minor, one was to allow
specifying the super classes up the hierarchy to the class I'm
creating instead of just one level above 'Object'. The other change
was to allow making a method a constructor which just involved adding
a bool to beginMethod to tell it not to bind the method to the class
definition (since we will be binding it later to the constructor slot
in the ClassDef).
With these changes we can do:
var cl = ctx.beginClass("Main");
cl.superclass = ctx.type("flash.display.MovieClip");
// In addition to setting the superclass, we need to define the entire class
// hierarchy (in order) up to and including the superclass (we can skip
// Object since Context handles that for us)
ctx.addClassSuper("flash.events.EventDispatcher");
ctx.addClassSuper("flash.display.DisplayObject");
ctx.addClassSuper("flash.display.InteractiveObject");
ctx.addClassSuper("flash.display.DisplayObjectContainer");
ctx.addClassSuper("flash.display.Sprite");
ctx.addClassSuper("flash.display.MovieClip");
var ctor = ctx.beginMethod("Main", [], null,
false/*static?*/,
false/*override?*/,
false/*final?*/,
true/*willAddLater? true b/c we will set as ctor*/);
cl.constructor = ctor.type;
Without the attached changes, we will get runtime errors that the
function Main is already bound to the class. The changes will not
bind the function to the class if the 'willAddLater' flag is true.
This ability also allows us to create a function for static
initializations as well and set it to the cl.statics member.
I'm just putting this out there in case anyone else runs into the same
issues I did with creating a self-running swf file. Maybe there is
another way to do it, but this was the only way I saw. Also, it would
be nice if Context could keep track of how large our stack was and how
many scopes we created, and how many registers we used in the function
and automatically update .maxStack, .maxScope, and .nRegs for us.
Granted it would incur some overhead and branches could be tricky, but
it would be convenient.
The attached example demonstrates:
1) Creating a class with a custom constructor function
2) Creating a class derived from flash.display.MovieClip
3) Adding a field to that class (the TextField we will write to)
4) Calling another class function from the constructor
5) Creating a swf that instantiates that class and adds it to the stage
The .hxml to compile is just:
-swf9 Hello.swf
-swf-version 10
-main Hello
-lib format
Note that there is code in there to handle neko, php, and cpp targets,
but they don't work yet. Neko crashes (segfault) on the Type.enumEq
(even with latest haxe and neko cvs, but maybe I'm doing something
wrong since I've never used neko), php doesn't quite produce valid php
code (php 5.2.10 fails to execute it), and cpp is still a work in
progress so I'm not surprised the C++ it produces doesn't compile.
Enjoy!
-S-
-------------- next part --------------
/*
* format - haXe File Formats
* ABC and SWF support by Nicolas Cannasse
*
* Copyright (c) 2008, The haXe Project Contributors
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package format.abc;
import format.abc.Data;
private class NullOutput extends haxe.io.Output {
public var n : Int;
public function new() {
n = 0;
}
override function writeByte(c) {
n++;
}
override function writeBytes(b,pos,len) {
n += len;
return len;
}
}
class Context {
var data : ABCData;
var hstrings : Hash<Int>;
var curClass : ClassDef;
var curFunction : { f : Function, ops : Array<OpCode> };
var classes : Array<Field>;
var init : { f : Function, ops : Array<OpCode> };
var fieldSlot : Int;
var registers : Array<Bool>;
var bytepos : NullOutput;
var opw : OpWriter;
var classSupers: List<Index<Name>>;
public var emptyString(default,null) : Index<String>;
public var nsPublic(default,null) : Index<Namespace>;
public var arrayProp(default,null) : Index<Name>;
public function new() {
classSupers = new List();
bytepos = new NullOutput();
opw = new OpWriter(bytepos);
hstrings = new Hash();
data = new ABCData();
data.ints = new Array();
data.uints = new Array();
data.floats = new Array();
data.strings = new Array();
data.namespaces = new Array();
data.nssets = new Array();
data.metadatas = new Array();
data.methodTypes = new Array();
data.names = new Array();
data.classes = new Array();
data.functions = new Array();
emptyString = string("");
nsPublic = namespace(NPublic(emptyString));
arrayProp = name(NMultiLate(nsset([nsPublic])));
beginFunction([],null);
ops([OThis,OScope]);
init = curFunction;
init.f.maxStack = 2;
init.f.maxScope = 2;
classes = new Array();
data.inits = [{ method : init.f.type, fields : classes }];
}
public function int(i) {
return lookup(data.ints,i);
}
public function uint(i) {
return lookup(data.uints,i);
}
public function float(f) {
return lookup(data.floats,f);
}
public function string( s : String ) : Index<String> {
var n = hstrings.get(s);
if( n == null ) {
data.strings.push(s);
n = data.strings.length;
hstrings.set(s,n);
}
return Idx(n);
}
public function namespace(n) {
//return lookup(data.namespaces,n);
return elookup(data.namespaces,n);
}
public function nsset( ns : NamespaceSet ) : Index<NamespaceSet> {
for( i in 0...data.nssets.length ) {
var s = data.nssets[i];
if( s.length != ns.length )
continue;
var ok = true;
for( j in 0...s.length )
if( !Type.enumEq(s[j],ns[j]) ) {
ok = false;
break;
}
if( ok )
return Idx(i + 1);
}
data.nssets.push(ns);
return Idx(data.nssets.length);
}
public function name(n) {
//return lookup(data.names,n);
return elookup(data.names,n);
}
public function type(path) : Null<Index<Name>> {
if( path == "*" )
return null;
var patharr = path.split(".");
var cname = patharr.pop();
var ns = patharr.join(".");
var pid = string(ns);
var nameid = string(cname);
var pid = namespace(NPublic(pid));
var tid = name(NName(nameid,pid));
return tid;
}
public function property(pname, ?ns) {
var pid = string("");
var nameid = string(pname);
var pid = if( ns == null ) namespace(NPublic(pid)) else ns;
var tid = name(NName(nameid,pid));
return tid;
}
public function methodType(m) : Index<MethodType> {
data.methodTypes.push(m);
return Idx(data.methodTypes.length - 1);
}
function lookup<T>( arr : Array<T>, n : T ) : Index<T> {
for( i in 0...arr.length )
if( arr[i] == n )
return Idx(i + 1);
arr.push(n);
return Idx(arr.length);
}
function elookup<T>( arr : Array<T>, n : T ) : Index<T> {
for( i in 0...arr.length )
if( Type.enumEq(arr[i],n) )
return Idx(i + 1);
arr.push(n);
return Idx(arr.length);
}
public function getData() {
return data;
}
function beginFunction(args,ret,?extra) : Index<Function> {
endFunction();
var f = {
type : methodType({ args : args, ret : ret, extra : extra }),
nRegs : args.length + 1,
initScope : 0,
maxScope : 0,
maxStack : 0,
code : null,
trys : [],
locals : [],
};
curFunction = { f : f, ops : [] };
data.functions.push(f);
registers = new Array();
for( x in 0...f.nRegs )
registers.push(true);
return Idx(data.functions.length - 1);
}
function endFunction() {
if( curFunction == null )
return;
var old = opw.o;
var bytes = new haxe.io.BytesOutput();
opw.o = bytes;
for( op in curFunction.ops )
opw.write(op);
curFunction.f.code = bytes.getBytes();
opw.o = old;
curFunction = null;
}
public function allocRegister() {
for( i in 0...registers.length )
if( !registers[i] ) {
registers[i] = true;
return i;
}
registers.push(true);
curFunction.f.nRegs++;
return registers.length - 1;
}
public function freeRegister(i) {
registers[i] = false;
}
public function beginClass( path : String ) {
endClass();
var tpath = this.type(path);
beginFunction([],null);
var st = curFunction.f.type;
op(ORetVoid);
endFunction();
beginFunction([],null);
var cst = curFunction.f.type;
op(ORetVoid);
endFunction();
fieldSlot = 1;
curClass = {
name : tpath,
superclass : this.type("Object"),
interfaces : [],
isSealed : false,
isInterface : false,
isFinal : false,
namespace : null,
constructor : cst,
statics : st,
fields : [],
staticFields : [],
};
data.classes.push(curClass);
classes.push({
name: tpath,
slot: 0,
kind: FClass(Idx(data.classes.length - 1)),
metadatas: null,
});
curFunction = null;
return curClass;
}
public function endClass() {
if( curClass == null )
return;
endFunction();
curFunction = init;
ops([
OGetGlobalScope,
OGetLex( this.type("Object") ),
]);
// Add all class supers (if any)
for (sup in classSupers)
ops([OScope, OGetLex(sup)]);
// Add final super class
ops([
OScope,
OGetLex( curClass.superclass ),
OClassDef( Idx(data.classes.length - 1) ),
OPopScope,
]);
// Restore the scope
for (sup in classSupers)
op(OPopScope);
// Add additional ops
ops([
OInitProp( curClass.name ),
]);
// Update our maxScope
curFunction.f.maxScope += classSupers.length;
curFunction = null;
curClass = null;
}
public function addClassSuper(sup: String): Void {
if (curClass == null)
return;
classSupers.add(this.type(sup));
}
public function beginMethod( mname : String, targs, tret, ?isStatic, ?isOverride, ?isFinal, ?willAddLater ) {
var m = beginFunction(targs,tret);
if (willAddLater != true)
{
var fl = if( isStatic ) curClass.staticFields else curClass.fields;
fl.push({
name : property(mname),
slot : 0,
kind : FMethod(curFunction.f.type,KNormal,isFinal,isOverride),
metadatas : null,
});
}
return curFunction.f;
}
public function endMethod() {
endFunction();
}
public function defineField( fname : String, t, ?isStatic ) : Slot {
var fl = if( isStatic ) curClass.staticFields else curClass.fields;
var slot = fieldSlot++;
fl.push({
name : property(fname),
slot : slot,
kind : FVar(t),
metadatas : null,
});
return slot;
}
public function op(o) {
curFunction.ops.push(o);
opw.write(o);
}
public function ops( ops : Array<OpCode> ) {
for( o in ops )
op(o);
}
public function backwardJump() {
var start = bytepos.n;
var me = this;
op(OLabel);
return function(jcond) {
me.op(OJump(jcond,start - me.bytepos.n - 4));
};
}
public function jump( jcond ) {
var ops = curFunction.ops;
var pos = ops.length;
op(OJump(JTrue,-1));
var start = bytepos.n;
var me = this;
return function() {
ops[pos] = OJump(jcond,me.bytepos.n - start);
};
}
public function finalize() {
endClass();
curFunction = init;
op(ORetVoid);
endFunction();
curClass = null;
}
}
-------------- next part --------------
import format.swf.Data;
import format.abc.Data;
import haxe.io.BytesOutput;
import haxe.io.BytesInput;
import haxe.io.Bytes;
#if php
import php.io.File;
#elseif neko
import neko.io.File;
#elseif cpp
import cpp.io.File;
#elseif flash9
import flash.net.FileReference;
import flash.events.MouseEvent;
import flash.events.Event;
#end
class Hello
{
public function new()
{
}
#if flash9
var bts: Bytes;
#end
public function run()
{
// Create actionscript
var ctx = new format.abc.Context();
var c = ctx.beginClass("Main");
c.superclass = ctx.type("flash.display.MovieClip");
// In addition to setting the superclass, we need to define the entire class
// hierarchy (in order) up to and including the superclass (we can skip
// Object since Context handles that for us)
ctx.addClassSuper("flash.events.EventDispatcher");
ctx.addClassSuper("flash.display.DisplayObject");
ctx.addClassSuper("flash.display.InteractiveObject");
ctx.addClassSuper("flash.display.DisplayObjectContainer");
ctx.addClassSuper("flash.display.Sprite");
ctx.addClassSuper("flash.display.MovieClip");
// Define member variable 'tf' as a TextField
ctx.defineField("tf", ctx.type("flash.text.TextField"));
// Define Main() that we will set as a ctor
var m = ctx.beginMethod("Main", [], null,
false/*static?*/,
false/*override?*/,
false/*final?*/,
true/*willAddLater? true b/c we will set as ctor*/);
m.maxStack = 2;
c.constructor = m.type;
ctx.ops( [
OThis,
OConstructSuper(0), // this.super()
OThis, // Load for OSetProp below
OFindPropStrict(ctx.type("flash.text.TextField")), // Lookup TextField def
OConstructProperty(ctx.type("flash.text.TextField"),0),//Create a TextField
OSetProp(ctx.type("tf")), // this.tf = new flash.text.TextField()
OThis,
OCallPropVoid(ctx.type("run"), 0), // this.run()
ORetVoid,
] );
// Define run()
var r = ctx.beginMethod("run", [], null);
r.maxStack = 2;
ctx.ops([
OThis,
OGetProp(ctx.type("tf")), // Load this.tf (for OSetProp below)
OString(ctx.string("Hello World")), // Load "Hello World"
OSetProp(ctx.type("text")), // this.tf.text = "Hello World"
OThis,
OThis,
OGetProp(ctx.type("tf")),
OCallPropVoid(ctx.type("addChild"), 1), // this.addChild(this.tf)
ORetVoid,
]);
ctx.finalize();
var asOut = new BytesOutput();
var abcData = ctx.getData();
format.abc.Writer.write(asOut, abcData);
var abcBytes = asOut.getBytes();
var s : SWF = {header: {
version: 9,
compressed: true,
width: 512,
height: 480,
fps: 60,
nframes: 1} ,
tags: [
TSandBox(25),
TActionScript3(abcBytes, null),
// Create instance of Main and add to stage
TSymbolClass([{className: "Main", cid: 0}]),
TShowFrame,
] };
#if flash9
var f = new BytesOutput();
#else
var f = File.write("out.swf", true);
#end
var writer = new format.swf.Writer(f);
writer.write(s);
f.close();
#if flash9
bts = f.getBytes();
trace("Click to save generated swf");
flash.Lib.current.stage.addEventListener(MouseEvent.CLICK, onClick);
#end
}
#if flash9
public function onClick(e: MouseEvent)
{
var ref = new FileReference();
ref.addEventListener(Event.COMPLETE, onSave);
ref.save(bts.getData(), "HelloWorld.swf");
}
public function onSave(e: Event)
{
trace("Save complete");
}
#end
public static function main()
{
var s = new Hello();
s.run();
}
}
More information about the Haxe
mailing list