-
Notifications
You must be signed in to change notification settings - Fork 197
Using C code for path finding in your ActionScript game
If you’ve been wondering how to use C++ code in a Flash game, you’ll be happy to hear that Adobe has introduced Flash C++ Compiler (FlasCC). Flascc is a complete BSD-like C/C++ development environment based on GCC that lets you compile your C/C++ code and create full SWFs or SWCs to be used in the development of Flash games.
This article introduces ActionScript 3 developers to flascc by walking through the development of a C++ path finding library for a simple Flash game. If you are going to use C++ code in your Flash game or if you just want to learn how flascc fits into the Flash ecosystem, this article is a useful guide for getting started based on a real world use case.
To see the chasing game that you will build in this article live, visit http://renaun.com/flash/pathfinder. The article will cover creating, debugging, and optimizing SWCs with flascc, as well as how to integrate an SWC into your Adobe Flash Builder ActionScript 3 project.
The flascc SDK comes with helpful documentation for getting started. Installation instructions for both Windows and Mac OS are found in the README.html file at the root of the flascc package. The following are some installation notes for following along with this article. The files you are interested in are located at sdk/usr/bin/g++ and sdk/usr/lib/asc2.jar
, both relative to the flascc installation root folder.
Note: The recommended installation root folders for flascc, C:\flascc\
on Windows and ~/flascc/
on Mac OS, are used for this article.
The flascc samples and the sample files of this article include makefiles. On Mac OS you might need Xcode to get the required make application, so for this article I’ll also show you how to use the command line instead of make. If you’re using Window you can get a version of make by installing Cygwin. I recommend using the included run.bat file to install the flascc-specific Cygwin version that includes all the required packages.
To simplify instructions for the remainder of this article, I’m going to prescribe a location for saving and building the samples for this article.
1.Go to the flascc samples
folder and create a new folder named PathFinder
.
2.Download the sample files for this article.
3.Unzip the sample files in the samples/PathFinder
folder you just created.
You should see subfolders named Step1 through Step10; these contain the code that is shown throughout the article. You can use them to follow along if you don’t want to write code or commands by hand. The compile.sh, debug.sh, or optimize.sh files in the Step folders also contain the commands to run the specific steps in the command-line shell. For more explanation of the files and folders in the sample files refer to the README file that is included in the root folder of this article's sample files.
Note: The article references paths relative to the samples/PathFinder folder. If you unzip the sample files in another location you will need to change the path values and commands given in this article.
Compiling a SWF from a C app (Step 1) Follow these steps to compile a SWF from a C application:
- Open a command-line shell to execute commands. This is the Cygwin command prompt on Windows or Terminal on Mac OS.
- Change to the samples/PathFinder/sandbox folder that you created above.
- Create a text file with your favorite editor and name it hello.c. Insert the following simple C code (found in the Step1 folder):
#include <stdio.h>
int main()
{
printf("Hello World!\n");
}
- Save the file and return to the command line.
- Now you are ready to compile this application with the special flascc compiler tools. Use the appropriate command below in your command-line shell:
Mac> ~/flascc/sdk/usr/bin/gcc hello.c -emit-swf -o hello.swf
Win> /cygdrive/c/flascc/sdk/usr/bin/gcc.exe hello.c -emit-swf -o hello.swf
You can run the SWF in a standalone Flash Player and see the Hello World text display in the Flash application. Notice the use of the –emit-swf
flag, which is not commonly used with GCC. Typically GCC creates an executable; flascc supports this option as well with the ability to create a Flash projector application with a copy of the ActionScript Virtual Machine (AVM) runtime. For a list of flascc specific GCC compiler flags see the reference documentation under "FlasCC-Specific GCC Command Line Options". You will learn more of these flascc specific GCC command-line options as you progress through the steps in this article.
Compiling a C++ class with flascc (Step 2)
Now that you’ve compiled a simple C application, you are ready for something a little more advanced: compiling an application with a C++ class.
1.In the samples/PathFinder/sandbox
folder create three files named pathfinder.h, pathfinder.cpp, and main.cpp (you can find sample copies in the Step2 folder). You will create a basic C++ class with two properties x and y and one method called move.
2.Declare the C++ class in the pathfinder.h file:
class Pather {
public:
int x, y;
void move(int dx, int dy);
};
- For the implementation, open pathfinder.cpp and add this code:
#include "pathfinder.h"
void Pather::move(int dx, int dy) {
x += dx;
y += dy;
}
The move method is implemented by adding deltas to the x and y properties of the class.
To try out the class you’ll need to compile a SWF. But this class by itself is not an application so you need to create one in the main.cpp file.
4. Add the following code to main.cpp:
#include <stdio.h>
#include "pathfinder.h"
int main()
{
Pather *p = new Pather();
p->x = 10;
p->y = 15;
printf("Pather x/y: %d/%d\n", p->x, p->y);
p->move(5, 10);
printf("Pather x/y: %d/%d\n", p->x, p->y);
}
The application will create an instance of the Pather class, set the x and y properties, and then call move to update the x and y values.
5.Compile this application with one of the following commands:
Mac> ~/flascc/sdk/usr/bin/g++ pathfinder.cpp main.cpp -emit-swf -o main.swf
Win> /cygdrive/c/flascc/sdk/usr/bin/g++.exe pathfinder.cpp main.cpp -emit-swf -o main.swf
Notice that you are using g++ instead of gcc now since you are compiling a C++ class. Also you need to specify all the classes to compile, which means you include both main.cpp and pathfinder.cpp file in the compiler arguments.
Run the resulting main.swf file and inspect the output.
You have created a full SWF application from C++ with no ActionScript 3 code. The goal, however, is to create a SWC library from the C++ code with flascc and use it in a normal ActionScript 3 project.
Flascc relies on the Low Level Virtual Machine (LLVM) compiler infrastructure to turn C/C++ code into an intermediate representation (IR) format. The flascc compiling tools take the IR and generate ActionScript bytecode. As you may know, code in the ActionScript Byte Code (abc) format runs in the AVM when a SWF container file is run in Flash Player. Traditionally, the Flex compiler (mxmlc) or Flash compilers (asc and asc2) produce SWF files but some of the tools can create abc code directly, which I’ll cover later.
The magic of the tools goes only so far. It doesn’t automatically make your C++ code work as Flash Player classes. It also doesn’t magically know all the Flash Player APIs and hook up everything for you. This is important to understand because it means you have to help bring the two sides together. This is done in a few ways and there are a few options on the C++ side to bring ActionScript 3 into your code through the C++ inline feature.
There are several basic changes that need to be addressed. The big one is memory. Flash Player has a garbage collector, whereas in C++ you have to manage all memory allocation and deallocation explicitly. To facilitate manual memory management Flash Player added a feature called domainMemory. It allows direct access to a block of memory. This allows the IR code for malloc
and new
in C/C++ to work inside Flash Player.
This means the Pather class you created in the last section needs to be created and destroyed manually inside the domain memory, not automatically like a normal ActionScript object. From an ActionScript 3 developer standpoint this is very important to understand, because you will have to manage code generated by flascc in the C/C++ memory mindset.
Compiling a C++ class into a SWC with flascc (Step 3) The next step is to create a SWC to use in a normal ActionScript project. C++ is all about pointers. The main.cpp file from Step 2 includes this line of code:
Pather *p = new Pather();
This defines p as a pointer to the memory that holds the new instance of the Pather class. The AVM doesn’t know what a C++ Pather class is. The abc code produced by flascc does, but it knows it in terms of how C++ handles classes. This means all communication between ActionScript and flascc generated abc code has to work on some common level. This common level is pointers. Some basic types like strings and integers can be converted by flascc helper functions, but generally you should think of the interface between flascc abc code and ActionScript 3 in terms of passing pointers.
For the Pather example, in the SWC you want to be able to create a Pather class and access the x, y, and move class members. The ActionScript 3 side can’t create a Pather class directly so it has to ask the flascc abc code to create the class and pass back a pointer. Pointers are int values that hold the value of the memory address. Then, when the ActionScript 3 side wants to interact with the Pather class it passes in this pointer value, which enables the abc code to pull up the class and access the properties and methods on that instance. These helper methods are your ActionScript 3 interface to the flascc abc code.
Next you’ll create some of these interface methods to get a better understanding of how they work. In the GCC spec, the __attribute__
feature enables developers to decorate functions to help the compiler do something with them. Using the __attribute__
feature and the LLVM annotate() feature you can help the IR process along to define an interface API in C++ that ActionScript will understand; for example:
void someFunction() __attribute__((used, annotate("as3sig:public function someFunctionAS3():int"), annotate("as3package:com.renaun.flascc_interface")));
The first parameter of __attribute__
is used; this tells the compiler to keep this function in the generated object file even though it might not be referenced by the code. This is needed because the API functions are not going to be called on the C++ side, but rather on the ActionScript 3 side. The annotate method is used by LLVM to generate from the IR a method signature that ActionScript 3 understands.
Now you can put it all together and create an interface for your Pather class. You’ll want to be able to create a Pather class, destroy a Pather class, set and get x and y, and call the move method on a Pather instance from ActionScript 3.
Begin by creating a new file called as3api.cpp (an example is in the Step3 folder) with the following code:
#include "pathfinder.h"
#include "AS3/AS3.h"
// First we mark the function declaration with a GCC attribute specifying the
// AS3 signature
////** flascc_createPather **////
void flascc_createPather() __attribute__((used,
annotate("as3sig:public function flascc_createPather():int"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_createPather()
{
// Create an instance of the Class
Pather *result;
result = (Pather *)new Pather();
// Create int for the pointer into memory for this object instance
AS3_DeclareVar(asresult, int);
AS3_CopyScalarToVar(asresult, result);
// return the pointer value of this object in memory
AS3_ReturnAS3Var(asresult);
}
////** flascc_deletePather **////
void flascc_deletePather() __attribute__((used,
annotate("as3sig:public function flascc_deletePather(self):void"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_deletePather()
{
Pather *arg1 = (Pather *) 0 ;
AS3_GetScalarFromVar(arg1, self);
delete arg1;
AS3_ReturnAS3Var(undefined);
}
////** flascc_set_x **////
void flascc_set_x() __attribute__((used,
annotate("as3sig:public function flascc_set_x(self, x:Number):void"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_set_x()
{
Pather *arg1 = (Pather *) 0 ;
double arg2 ;
// convert arguments
AS3_GetScalarFromVar(arg1, self);
AS3_GetScalarFromVar(arg2, x);
// apply to object
if (arg1) (arg1)->x = arg2;
// return void
AS3_ReturnAS3Var(undefined);
}
////** flascc_get_x **////
void flascc_get_x() __attribute__((used,
annotate("as3sig:public function flascc_get_x(self):Number"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_get_x()
{
Pather *arg1 = (Pather *) 0 ;
double result ;
// convert arguments
AS3_GetScalarFromVar(arg1, self);
// apply to object
result = (double) ((arg1)->x);
// return Number
AS3_DeclareVar(asresult, Number);
AS3_CopyScalarToVar(asresult, result);
AS3_ReturnAS3Var(asresult);
}
////** flascc_set_y **////
void flascc_set_y() __attribute__((used,
annotate("as3sig:public function flascc_set_y(self, y:Number):void"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_set_y()
{
Pather *arg1 = (Pather *) 0 ;
double arg2 ;
// convert arguments
AS3_GetScalarFromVar(arg1, self);
AS3_GetScalarFromVar(arg2, y);
// apply to object
if (arg1) (arg1)->y = arg2;
// return void
AS3_ReturnAS3Var(undefined);
}
////** flascc_get_y **////
void flascc_get_y() __attribute__((used,
annotate("as3sig:public function flascc_get_y(self):Number"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_get_y()
{
Pather *arg1 = (Pather *) 0 ;
double result ;
// convert arguments
AS3_GetScalarFromVar(arg1, self);
// apply to object
result = (double) ((arg1)->y);
// return Number
AS3_DeclareVar(asresult, Number);
AS3_CopyScalarToVar(asresult, result);
AS3_ReturnAS3Var(asresult);
}
////** flascc_move **////
void flascc_move() __attribute__((used,
annotate("as3sig:public function flascc_move(self, dx:int, dy:int):void"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_move()
{
Pather *arg1 = (Pather *) 0 ;
int arg2 ;
int arg3 ;
// convert arguments
AS3_GetScalarFromVar(arg1, self);
AS3_GetScalarFromVar(arg2, dx);
AS3_GetScalarFromVar(arg3, dy);
// apply to object
(arg1)->move(arg2,arg3);
// return void
AS3_ReturnAS3Var(undefined);
}
I use the flascc_ prefix as a way to help me keep track of what methods are my interface between flascc and ActionScript 3 code. I also picked an arbitrary namespace, com.renaun.flascc_interface
, for the methods. The main point is to pick something that makes it easy to identify the ActionScript 3 and flascc interface methods.
These methods are still C/C++ methods, they just have special annotations to help with the cross compiling. This is also where the flascc-specific helper methods come into play to do any marshalling of method parameters and return types. Take a look at the flascc_createPather
method and the comments in uppercase:
///** flascc_createPather **////
void flascc_createPather() __attribute__((used,
// ActionScript 3 WILL EXPECT A RETURN VALUE OF “int”
annotate("as3sig:public function flascc_createPather():int"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_createPather()
{
// Create an instance of the Class
Pather *result;
result = (Pather *)new Pather();
// Create int for the pointer into memory for this object instance
// CREATING A NEW VARIABLE THAT ActionScript 3 UNDERSTANDS
AS3_DeclareVar(asresult, int);
// CONVERTING TO A VALUE THAT ActionScript 3 UNDERSTANDS
AS3_CopyScalarToVar(asresult, result);
// return the pointer value of this object in memory
AS3_ReturnAS3Var(asresult);
}
When flascc_createPather is called in ActionScript 3 it will return the pointer value of the newly instantiated Pather class. This pointer is later passed into the other methods, for example flascc_set_x:
////** flascc_set_x **////
void flascc_set_x() __attribute__((used,
// ActionScript 3 WILL PASS IN THE POINTER TO THE Pather INSTANCE AND THE NEW “x” VALUE, THEN EXPECT A RETURN VALUE OF “void”
annotate("as3sig:public function flascc_set_x(self, x:Number):void"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_set_x()
{
Pather *arg1 = (Pather *) 0 ;
double arg2 ;
// convert arguments
// CONVERTING PARAMETERS TO VALUES THAT C++ UNDERSTANDS
AS3_GetScalarFromVar(arg1, self);
AS3_GetScalarFromVar(arg2, x);
// apply to object
if (arg1) (arg1)->x = arg2;
// return void
AS3_ReturnAS3Var(undefined);
}
The next step is to compile the interface code into a SWC instead of a SWF using the flascc-specific GCC compiler argument –emit-swc
. Although similar to –emit-swf
, –emit-swc
requires a namespace; for example, –emit-swc=com.renaun.flascc
. You can use this to ensure multiple flascc generated SWCs do not overlap.
When creating any flascc code you still have to have a main C run loop. For this SWC, you want to break the C main loop to be able to load control when the code is started in your ActionScript 3 project. There are three ActionScript 3 flascc helper functions on the com.adobe.flascc.CModule
class that help manage starting the main C run loop: CModule.start()
, CModule.startAsync()
, and CModule.startBackground()
. They are used for a standard start, starting a broken main C run loop, and starting a main C run loop in a Flash background worker respectively. To break the main C run loop on the C side you call the AS3_GoAsync()
, defined in AS3/AS3.h
.
Open main.cpp (a copy can be found in the Step3 folder) and replace the contents of the file with the following code to break the main C run loop:
#include "AS3/AS3.h"
int main()
{
AS3_GoAsync();
}
The main C run loop is broken before any static initializers, which can put the library in a bad state, are called. To compile the SWC use one of the following commands:
Mac> ~/flascc/sdk/usr/bin/g++ pathfinder.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
Win> /cygdrive/c/flascc/sdk/usr/bin/g++.exe pathfinder.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
These commands produce a PathFinder.swc file that can be used in an ActionScript 3 project.
Consider using SWIG to generate interface code
In this article you will continue to create your interface code by hand. However, the Simplified Wrapper and Interface Generator (SWIG) tool can help you generate interface code from classes. There is an ActionScript 3 implementation of SWIG included with flascc (sdk/usr/bin/swig)
. Use of SWIG is beyond the scope of this article, but there are some samples found in the flascc SDK that show how to use it. It’s worth exploring if you are planning on using an existing library. If you do use SWIG, be prepared to learn how to use SWIG configuration files to optimize your interface by ignoring some methods and including only the methods that you want to expose as your interface.
Why would you want make a SWC more AS3 friendly? After you create a project and use the current version of PathFinder.swc the answer to this question will be clear.
1.Create a folder named samples/PathFinder/sandbox/PathFinderAS3Project
.
2.Open Adobe Flash Builder 4.7 and create an ActionScript Project named PathFinderAS3Project at the samples/PathFinder/sandbox/PathFinderAS3Project
location.
3.With the PathFinderAS3Project selected in the Package Explorer choose Project > Properties.
4.Select ActionScript Build Path and click Add SWC. Navigate to samples/PathFinder/sandbox/PathFinder.swc
and add the SWC to your library path.
5.Open the main ActionScript 3 file, PathFinderAS3Project/src/ PathFinderAS3Project.as
, and start typing flascc_. You should see the functions declared in as3api.cpp.
Using PathFinder.SWC (Step 4)
Follow these steps to use PathFinder.SWC in your project:
1.Open PathFinderAS3Project.as and copy the following code (which can be found in the Step4 folder)into it:
package
{
import com.renaun.flascc.CModule;
import com.renaun.flascc_interface.*;
import flash.display.Sprite;
import flash.text.TextField;
public class PathFinderAS3Project extends Sprite
{
public function PathFinderAS3Project()
{
CModule.startAsync(this);
var tf:TextField = new TextField();
tf.multiline = true;
tf.width = tf.height = 600;
addChild(tf);
var patherPtr:int = flascc_createPather();
flascc_set_x(patherPtr, 10);
flascc_set_y(patherPtr, 15);
tf.appendText("Pather x/y: " + flascc_get_x(patherPtr + "/" + flascc_get_y(patherPtr) + "\n");
flascc_move(patherPtr, 5, 10);
tf.appendText("Pather x/y: " + flascc_get_x(patherPtr)
+ "/" + flascc_get_y(patherPtr) + "\n");
}
}
}
The CModule.startAsync(this)
tells the SWC’s flascc code to continue its main C run loop. Although you don’t have anything in main.cpp, it kept the static initializers from loading until this point.
Run the project and examine the results in the browser.
If you look at the code above, you’ll notice that it doesn’t look like what you expect when using a well structured ActionScript 3 object-oriented library. You really want code that looks like this:
var pather:PathFinder = new PathFinder();
pather.x = 10;
pather.y = 15;
pather.move(5, 10);
Adding an ActionScript 3 wrapper class to the SWC (Step 5)
You can create your own ActionScript 3 classes and add them to the SWC by including the abc format in the compile process.
1.First create your wrapper ActionScript 3 class in samples/PathFinder/sandbox/PathFinderWrapper.as. Here is the code for PathFinderWrapper.as (which can be found in the Step5 folder):
package com.renaun
{
import com.renaun.flascc_interface.*;
import com.renaun.flascc.CModule;
public class PathFinder
{
public function PathFinder():void
{
objectPtr = flascc_createPather();
}
private var objectPtr:int;
public function get x():Number
{
return flascc_get_x(objectPtr);
}
public function set x(value:Number):void
{
flascc_set_x(objectPtr, value);
}
public function get y():Number
{
return flascc_get_y(objectPtr);
}
public function set y(value:Number):void
{
flascc_set_y(objectPtr, value);
}
public function move(dx:Number, dy:Number):void
{
flascc_move(objectPtr, dx, dy);
}
public function destroy():void
{
flascc_deletePather(objectPtr);
}
}
}
This is a straightforward ActionScript 3 class. In the constructor it calls the flascc_createPather() method and then keeps the pointer value as a private variable to use in the other methods.
2.To add this to your SWC you first need to compile the PathFinderWrapper.as to ActionScript Byte Code, or an ABC file. Execute one of the following commands (you’ll need java to be in your command-line shell path):
Mac> java -jar ~/flascc/sdk/usr/lib/asc2.jar -merge -md -abcfuture -AS3 -import ~/flascc/sdk/usr/lib/builtin.abc –import ~/flascc/sdk/usr/lib/playerglobal.abc PathFinderWrapper.as
Win> java -jar /cygdrive/c/flascc/sdk/lib/asc2.jar -merge -md -abcfuture -AS3 –import /cygdrive/c/flascc/sdk/lib/builtin.abc –import /cygdrive/c/flascc/sdk/lib/playerglobal.abc PathFinderWrapper.as
This creates a PathFinderWrapper.abc
file in the same location.
3.Include this file in the SWC compile command:
Mac> ~/flascc/sdk/usr/bin/g++ PathFinderWrapper.abc pathfinder.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
Win> /cygdrive/c/flascc/sdk/usr/bin/g++.exe PathFinderWrapper.abc pathfinder.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
Now PathFinder.swc has the com.renaun.PathFinder ActionScript 3 wrapper class, which is much easier to use than the methods defined in as3api.cpp.
4.To test the new wrapper class go into the project’s main class PathFinderAS3Project/src/PathFinderAS3Project.as (found in Step5 folder) and change the bottom section of code to:
var pather:PathFinder = new PathFinder();
pather.x = 10;
pather.y = 15;
tf.appendText("PathFinder x/y: " + pather.x + "/" + pather.y + "\n");
pather.move(5, 10);
tf.appendText("PathFinder x/y: " + pather.x + "/" + pather.y + "\n");
- Run the application with the new changes and you should see the same result.
Now that you understand the basics of how to create a SWC out of C++ files with flascc its time to do the actual path finding implementation. I chose to use an open source project implementation of the A* path finding algorithm called MicroPather. It’s really only two classes but it is a good exercise in reusing existing code. The micropather.h and micropather.cpp files are included in the sample files (in the Step6 folder) for this article. Alternatively, you can get the source from http://www.grinninglizard.com/MicroPather/.
As a first step, copy these two files into samples/PathFinder/sandbox/
.
Understanding the MicroPather class
The MicroPather class works by instantiating the class with a reference to a class that extends Graph. Your goal is to extend the Pather class and implement the abstract Graph class. There are three virtual methods that need to be implemented for the abstract Graph class:
virtual float LeastCostEstimate(void* nodeStart, void* nodeEnd);
virtual void AdjacentCost(void* node, std::vector< StateCost > *neighbors);
virtual void PrintStateInfo( void* node );
You won’t need to print any state from the C++ side so PrintStateInfo
will not implement anything. The other two methods are where the specific A* implementation defines tile weights to be used to make decisions about paths are calculated. For example, you could weigh different terrain on a map, hill versus valley, with different values. In this case you are going to do basic distance-based weighting of 1 for up, down, left, and right and the cost of a diagonal move as 1.4. MicroPather will not handle the map itself, which means you’ll need a way to know the size of the map and what locations on the map are passable. You also need some helper methods to translate between the map array indexing (0…col*row) and finding a location on the map with X and Y values. With all those implementation methods you want two methods that will be called from the ActionScript 3 side. There needs to be a way to set the map in memory and after it is set to ask for a path between two points.
Implementing the MicroPather class in the Pather class
Follow these steps to begin implementing the MicroPather class in the Pather class:
- Open samples/PathFinder/sandbox/patherfinder.h and insert the following code (which can be found in the Step6 folder):
#include <math.h>
#include <vector>
#include "micropather.h"
using namespace micropather;
class Pather : public Graph {
private:
unsigned char *map;
MicroPather* pather;
int mapColSize;
int mapRowSize;
public:
Pather() {
}
virtual ~Pather() {
delete pather;
};
void setMap(unsigned char* buffer, int colSize, int rowSize);
int* getPath(int sx, int sy, int nx, int ny);
int Passable(int nx, int ny);
void NodeToXY(void* node, int* x, int* y);
void* XYToNode(int x, int y);
virtual float LeastCostEstimate(void* nodeStart, void* nodeEnd);
virtual void AdjacentCost(void* node, std::vector< StateCost > *neighbors);
virtual void PrintStateInfo( void* node );
};
- For the implementation of the actual functions, open samples/PathFinder/sandbox/patherfinder.cpp and replace the contents with the following code:
#include "pathfinder.h"
void Pather::setMap(unsigned char* buffer, int colSize, int rowSize) {
map = buffer;
mapColSize = colSize;
mapRowSize = rowSize;
int memSize = mapColSize * mapRowSize;
if (memSize > 1000) memSize /= 4;
pather = new MicroPather(this, memSize);
}
int* Pather::getPath( int sx, int sy, int nx, int ny )
{
// micropather uses this
std::vector<void*> path;
// I want to change to my own format and return with pathResult and size of path, could add total cost too, possible memory leak
unsigned short int* result;
int pathResult = MicroPather::NO_SOLUTION;
unsigned int size = 0;
if ( Passable( sx, sy ) == 1 && Passable( nx, ny ) == 1 )
{
float totalCost;
pathResult = pather->Solve( XYToNode( sx, sy ), XYToNode( nx, ny ), &path, &totalCost );
}
if ( pathResult == MicroPather::SOLVED ) {
size = path.size();
result = new unsigned short int[(size*2)+2];
result[0] = pathResult;
result[1] = size;
unsigned k;
for( k=0; k<size; ++k ) {
int x, y;
NodeToXY( path[k], &x, &y );
result[(k*2)+2] = x;
result[(k*2)+3] = y;
}
}
else
{
result = new unsigned short int[2];
result[0] = pathResult;
result[1] = size;
}
return (int*)result;
}
int Pather::Passable( int nx, int ny )
{
if ( nx >= 0 && nx < mapColSize
&& ny >= 0 && ny < mapRowSize )
{
int index = ny*mapColSize+nx;
return map[ index ];
}
return 0;
}
void Pather::NodeToXY( void* node, int* x, int* y )
{
int index = (intptr_t)node;
*y = index / mapColSize;
*x = index - *y * mapColSize;
}
void* Pather::XYToNode( int x, int y )
{
return (void*) ( y*mapColSize + x );
}
float Pather::LeastCostEstimate( void* nodeStart, void* nodeEnd )
{
int xStart, yStart, xEnd, yEnd;
NodeToXY( nodeStart, &xStart, &yStart );
NodeToXY( nodeEnd, &xEnd, &yEnd );
/* Compute the minimum path cost using distance measurement. It is possible
to compute the exact minimum path using the fact that you can move only
on a straight line or on a diagonal, and this will yield a better result.
*/
int dx = xStart - xEnd;
int dy = yStart - yEnd;
return (float) sqrt( (double)(dx*dx) + (double)(dy*dy) );
}
void Pather::AdjacentCost( void* node, std::vector< StateCost > *neighbors )
{
int x, y;
const int dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 };
const int dy[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };
const float cost[8] = { 1.0f, 1.41f, 1.0f, 1.41f, 1.0f, 1.41f, 1.0f, 1.41f };
NodeToXY( node, &x, &y );
for( int i=0; i<8; ++i ) {
int nx = x + dx[i];
int ny = y + dy[i];
int pass = Passable( nx, ny );
if ( pass == 1 )
{
// Normal floor
StateCost nodeCost = { XYToNode( nx, ny ), cost[i] };
neighbors->push_back( nodeCost );
}
}
}
void Pather::PrintStateInfo( void* node )
{
}
The Passable method checks to make sure the coordinates are inside the map and that it is a passable tile. The NodeToXY and XYToNode methods are helper methods that convert between the array index and x and y locations. The actual call to the MicroPather Solve method is in the getPath method.
3. Next, you need to update the flascc interface code and the ActionScript 3 wrapper class as well. Open the samples/PathFinder/sandbox/as3api.cpp
file and replace the contents with the following code (found in the Step6 folder):
#include "pathfinder.h"
#include "AS3/AS3.h"
// First we mark the function declaration with a GCC attribute specifying the
// AS3 signature
////** flascc_createPather **////
void flascc_createPather() __attribute__((used,
annotate("as3sig:public function flascc_createPather():int"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_createPather()
{
// Create an instance of the Class
Pather *result;
result = (Pather *)new Pather();
// Create int for the pointer into memory for this object instance
AS3_DeclareVar(asresult, int);
AS3_CopyScalarToVar(asresult, result);
// return the pointer value of this object in memory
AS3_ReturnAS3Var(asresult);
}
////** flascc_deletePather **////
void flascc_deletePather() __attribute__((used,
annotate("as3sig:public function flascc_deletePather(self):void"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_deletePather()
{
Pather *arg1 = (Pather *) 0 ;
AS3_GetScalarFromVar(arg1, self);
delete arg1;
AS3_ReturnAS3Var(undefined);
}
////** flascc_setMap **////
void flascc_setMap() __attribute__((used,
annotate("as3sig:public function flascc_setMap(self, buffer:int, colSize:int, rowSize:int):void"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_setMap()
{
Pather *arg1 = (Pather *) 0 ;
unsigned char *arg2 = (unsigned char *) 0 ;
int arg3;
int arg4;
// convert arguments
AS3_GetScalarFromVar(arg1, self);
AS3_GetScalarFromVar(arg2, buffer);
AS3_GetScalarFromVar(arg3, colSize);
AS3_GetScalarFromVar(arg4, rowSize);
// apply to object
(arg1)->setMap((unsigned char *)arg2, arg3, arg4);
// return void
AS3_ReturnAS3Var(undefined);
}
////** flascc_getPath **////
void flascc_getPath() __attribute__((used,
annotate("as3sig:public function flascc_getPath(self, sx:int, sy:int, nx:int, ny:int):int"),
annotate("as3package:com.renaun.flascc_interface")));
void flascc_getPath()
{
Pather *arg1 = (Pather *) 0 ;
int arg2;
int arg3;
int arg4;
int arg5;
int* result;
// convert arguments
AS3_GetScalarFromVar(arg1, self);
AS3_GetScalarFromVar(arg2, sx);
AS3_GetScalarFromVar(arg3, sy);
AS3_GetScalarFromVar(arg4, nx);
AS3_GetScalarFromVar(arg5, ny);
// apply to object
result = (arg1)->getPath(arg2, arg3, arg4, arg5);
// return Number
AS3_DeclareVar(asresult, int);
AS3_CopyScalarToVar(asresult, result);
AS3_ReturnAS3Var(asresult);
}
- Now that you have added the getPath() and setMap() interface methods in the as3api.cpp file you need to update the ActionScript 3 wrapper class PathFinderWrapper.as. Open
samples/PathFinder/sandbox/PathFinderWrapper.as
file and replace the contents with the following code (found in the Step6 folder):
package com.renaun
{
import com.renaun.flascc_interface.*;
import com.renaun.flascc.CModule;
import com.renaun.flascc.ram;
import flash.utils.ByteArray;
public class PathFinder
{
public function PathFinder(map:ByteArray, colSize:int, rowSize:int):void
{
objectPtr = flascc_createPather();
mapColSize = colSize;
mapRowSize = rowSize;
if (map.length != colSize * rowSize)
throw new Error("Map size doesn't equal col * row size");
// Setup bytes to be written to Flascc memory
map.position = 0;
mapByteArrayPtr = CModule.malloc(map.length);
CModule.writeBytes(mapByteArrayPtr, map.length, map);
flascc_setMap(objectPtr, mapByteArrayPtr, colSize, rowSize);
}
public var mapColSize:int;
public var mapRowSize:int;
private var objectPtr:int;
public var mapByteArrayPtr:int;
public function getMap():ByteArray
{
var mapBytes:ByteArray = new ByteArray();
ram.position = mapByteArrayPtr;
ram.readBytes(mapBytes, 0, mapColSize * mapRowSize);
return mapBytes;
}
public function getPath(sx:int, sy:int, nx:int, ny:int):Array
{
var pathPtr:int = flascc_getPath(objectPtr, sx, sy, nx, ny);
ram.position = pathPtr;
var result:int = ram.readShort();
var size:int = ram.readShort();
var pathXYs:Array = [];
if (size > 0)
{
ram.readShort();
ram.readShort();
for (var j:int = 1; j < size; j++)
{
pathXYs.push(ram.readShort());
pathXYs.push(ram.readShort());
}
}
//CModule.free(pathPtr);
return pathXYs;
}
public function destroy():void
{
CModule.free(mapByteArrayPtr);
flascc_deletePather(objectPtr);
}
}
}
This implements a useful library that takes a map as input and provides a path between two points. The map is made up of 1s for passable and 0s for non-passable points.
5. Execute one of following commands to build the SWC (micropather.cpp has been included):
Mac> ~/flascc/sdk/usr/bin/g++ PathFinderWrapper.abc micropather.cpp pathfinder.cpp as3api.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
Win> /cygdrive/c/flascc/sdk/usr/bin/g++.exe PathFinderWrapper.abc micropather.cpp pathfinder.cpp as3api.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
Testing PathFinder.swc (Step 7)
To try out the newly implemented path finding code, you’ll need to create a new application that uses the SWC.
- Open PathFinderAS3Project/src/PathFinderAS3Project.as and replace its contents with the following code (found in the Step7 folder):
package
{
import com.renaun.PathFinder;
import com.renaun.flascc.CModule;
import flash.display.Sprite;
import flash.text.TextField;
import flash.utils.ByteArray;
[SWF(frameRate="60", width="640" height="640", backgroundColor="#336699")]
public class PathFinderAS3Project extends Sprite
{
public function PathFinderAS3Project()
{
CModule.startAsync(this);
var tf:TextField = new TextField();
tf.multiline = true;
tf.width = tf.height = 600;
addChild(tf);
// Create Checker Board map Map
var map:ByteArray = new ByteArray();
var mapCols:int = 10;
var mapRows:int = 10;
for (var i:int = 0; i < mapCols * mapRows; i++)
map.writeByte(int((1+i+i/mapCols) % 2));
var pf:PathFinder = new PathFinder(map, mapCols, mapRows);
tf.appendText("Path Between (0,0 , 9, 5): " + pf.getPath(0, 0, 9, 5) + "\n");
}
}
}
The code above creates a checkerboard map and then finds a path between points 0,0 and 9,5.
Now you’re ready to build a more complex game to make use of the new PathFinder.swc. The idea of the game is to have a blue box that the user controls by clicking on valid map tiles. The game will create a random number of red circles that will chase the blue circle by changing their paths every certain number of frames (see Figure 5). The blue circle will move faster than the red circles so it will be easy to move around and see the red circles’ paths change. To see a live version of the game, visit http://renaun.com/flash/pathfinder/.
Implementing the circle entity class (Step 8)
Next, you’ll make a class that knows how to render blue and red circles, move them by location, and move them along a path. It is a good practice to encapsulate specific class logic into the class to which it’s related.
1.Create a new file named PathFinderAS3Project/src/com/renaun/game/MovableEntity.as.
2.Open the file and insert the following code (found in the Step8 folder):
package com.renaun.game
{
import flash.display.Sprite;
public class MovableEntity extends Sprite
{
public function MovableEntity(size:int = 10, type:String = "enemy")
{
this.size = size;
graphics.beginFill( ((type == "enemy") ?
0xCC0000 : 0x3333AA), 0.8);
graphics.lineStyle(2, 0x222222, 0.8);
graphics.drawCircle(size/2, size/2, size/2);
graphics.endFill();
}
protected var size:int = 10;
public var tileX:int = 0;
public var tileY:int = 0;
public var currentPath:Array;
public function move(tileX:int, tileY:int):void
{
this.tileX = tileX;
this.tileY = tileY;
x = tileX * size;
y = tileY * size;
}
public function moveByPath():void
{
if (!currentPath || currentPath.length == 0)
return;
var newX:int = currentPath.shift();
var newY:int = currentPath.shift();
move( newX, newY);
}
}
}
The MovableEntity class draws a blue or red circle based on the size and type passed into the constructor. It supports moves to a specified tile and along a path specified by the currentPath value. The currentPath value will be set in our game logic loop with the result of our path finder library call between the current red circle and the blue circle. This is a fairly simple class but it will make implementing game logic easier.
Implementing the Board Renderer class (Step 8 continued)
Another good place to encapsulate logic is the code that renders the map. It’s just a grid of 1s and 0s. So for simplicity, you’ll just use two different colors and draw the grid based on a fixed size.
1.Create a new file named PathFinderAS3Project/src/com/renaun/game/BoardRenderer.as.
2.Open the file and add the following code (found in the Step8 folder):
package com.renaun.game
{
import flash.display.Sprite;
import flash.utils.ByteArray;
import com.renaun.PathFinder;
public class BoardRenderer extends Sprite
{
public function BoardRenderer(pather:PathFinder, squareSize:int = 20)
{
this.pather = pather;
this.squareSize = squareSize;
render();
}
protected var pather:PathFinder;
protected var squareSize:int;
private function render():void
{
var mapBytes:ByteArray = pather.getMap();
graphics.clear();
for (var i:int = 0; i < pather.mapRowSize; i++)
for (var j:int = 0; j < pather.mapColSize; j++)
drawTile(mapBytes.readByte(), j, i);
}
private function drawTile(type:int, x:int, y:int):void
{
var color:uint = (type == 1) ? 0xAAAAAA : 0x663333;
graphics.beginFill(color);
graphics.lineStyle(1, 0x222222, 0.5);
graphics.drawRect(x*squareSize, y*squareSize, squareSize, squareSize);
graphics.endFill();
}
public function drawPath(path:Array):void
{
graphics.lineStyle(squareSize/3, 0xff0000);
for (var i:int = 0; i < path.length/2; i++)
{
if (i == 0)
graphics.moveTo( (path[i*2]*squareSize)+(squareSize/2), (path[(i*2)+1]*squareSize)+(squareSize/2));
graphics.lineTo( (path[i*2]*squareSize)+(squareSize/2), (path[(i*2)+1]*squareSize)+(squareSize/2));
}
}
}
}
You pass this class the tile size and a reference to the Pather instance, so it can get the map values and know how to render it. The render() method draws different colored squares based upon the map values.
Implementing the game logic (Step 8 cont)
The final step in putting all the pieces together is to implement the setup and game logic. The game logic includes the following tasks:
- Listen for mouse clicks. On each mouse click, set the blue circle’s current path.
- Every tenth frame (out of 60 frames per second) move the blue circle to the next location in the path.
- Every thirtieth frame (out of 60 frames per second) move all red circles to the next location in their paths.
- On every frame, check if the blue circle is on a new tile location. If so update all red circles paths to the new blue circle tile location.
There is also some setup code to randomize the map each time and randomly pick starting points for the circles. The setup keeps track of valid tiles so the circles can move immediately at the start.
Replace the contents of PathFinderAS3Project/src/PathFinderAS3Project.as
with the following code (found in the Step8 folder):
package
{
import com.renaun.PathFinder;
import com.renaun.flascc.CModule;
import com.renaun.flascc.ram;
import com.renaun.game.BoardRenderer;
import com.renaun.game.MovableEntity;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.ByteArray;
[SWF(frameRate="60", width="640" height="640", backgroundColor="#336699")]
public class PathFinderAS3Project extends Sprite
{
public static const TILE_SIZE:int = 10;
public static const TIME_SLICE_GUY:int = 6;
public static const TIME_SLICE_ENEMY:int = 30;
private var timeSlice:int = 0;
private var validStartTiles:Array;
private var mapCols:int;
private var mapRows:int;
private var theGuy:MovableEntity;
private var enemies:Array = [];
private var lastX:int = -1;
private var lastY:int = -1;
private var pf:PathFinder;
public function PathFinderAS3Project()
{
addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
protected function addedToStageHandler(event:Event):void
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
CModule.startAsync(this);
// Generate random map and keep track of valid start tiles
var map:ByteArray = new ByteArray();
validStartTiles = [];
mapCols = 60;
mapRows = 60;
var temp:int = 0;
for (var i:int = 0; i < mapCols * mapRows; i++)
{
temp = (int(Math.random()*0xffff) % 2 == 1) ? 0 : 1;
if (temp == 1) validStartTiles.push(i);
map.writeByte(temp);
}
pf = new PathFinder(map, mapCols, mapRows);
var debugRender:BoardRenderer = new BoardRenderer(pf, PathFinderAS3Project.TILE_SIZE);
addChildAt(debugRender,0);
// Make Blue and Red Circles
for (var k:int = 0; k < 60; k++)
createEntities();
createEntities("main");
// Set up event methods for run loop and mouse down
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
private function createEntities(type:String = "enemy"):void
{
var guy:MovableEntity = new MovableEntity(TILE_SIZE, type);
var startIndex:int = validStartTiles[int(Math.random() * validStartTiles.length)];
var tileY:int = startIndex / mapCols;
guy.move(startIndex - (mapCols * tileY), tileY);
if (type == "enemy")
enemies.push(guy);
else
theGuy = guy;
addChild(guy);
}
protected function mouseDownHandler(event:MouseEvent):void
{
var tileX:int = int(event.stageX / TILE_SIZE);
var tileY:int = int(event.stageY / TILE_SIZE);
if (tileX < mapCols && tileY < mapRows)
theGuy.currentPath = pf.getPath(theGuy.tileX, theGuy.tileY, tileX, tileY);
}
protected function enterFrameHandler(event:Event):void
{
var i:int;
if (timeSlice % TIME_SLICE_GUY == 0)
theGuy.moveByPath();
// Has The guy changed locations? if so re do the paths
if (lastX != theGuy.tileX || lastY != theGuy.tileY)
{
lastX = theGuy.tileX;
lastY = theGuy.tileY;
for (i = 0; i < enemies.length; i++)
enemies[i].currentPath = pf.getPath(enemies[i].tileX, enemies[i].tileY, theGuy.tileX, theGuy.tileY);
}
if (timeSlice % TIME_SLICE_ENEMY == 0)
{
var hits:int = 0;
for (i = 0; i < enemies.length; i++)
{
enemies[i].moveByPath();
if (enemies[i].tileX == theGuy.tileX && enemies[i].tileY == theGuy.tileY)
hits++;
}
if (hits > 5) restartGame();
}
timeSlice++;
if (timeSlice > 360)
timeSlice = 0;
}
protected function restartGame():void
{
timeSlice = 0;
var startIndex:int = validStartTiles[int(Math.random() * validStartTiles.length)];
var tileY:int = startIndex / mapCols;
theGuy.move(startIndex - (mapCols * tileY), tileY);
for (var i:int = 0; i < enemies.length; i++)
{
startIndex = validStartTiles[int(Math.random() * validStartTiles.length)];
tileY = startIndex / mapCols;
enemies[i].move(startIndex - (mapCols * tileY), tileY);
}
}
}
}
That is all the code for the game. Run the game in Flash Builder to see it all working together. You can play around with the map size or the number of red circles generated by making a few changes to PathFinderAS3Project.as.
Debugging with the flascc’s GDB (Step 9)
You can debug the C++ code in flascc-generated SWFs or SWCs. This is done using the modified flascc GDB found at sdk/usr/bin/gdb
. It’s important to point out that the flascc GDB only debugs C++ code and not ActionScript 3 code in your ActionScript 3 project.
1.You first need to create a debug version of your SWC that includes all the debug symbols by adding the g++ –g flag. This is done with the following command:
Mac> ~/flascc/sdk/usr/bin/g++ -g PathFinderWrapper.abc micropather.cpp pathfinder.cpp as3api.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
Win> /cygdrive/c/flascc/sdk/usr/bin/g++.exe -g PathFinderWrapper.abc micropather.cpp pathfinder.cpp as3api.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
The next step is to tell the flascc GDB where the debug version of Flash Player or a browser with a debug version of Flash Player is located. I recommend pointing directly to the standalone player the first time you try and get GDB working.
2.You must set the FLASCC_GDB_RUNTIME environment variable. You can do this with one of the following commands (assuming the standalone Flash Player is in the folder /FlashPlayers
or C:\FlashPlayers)
:
Mac> export FLASCC_GDB_RUNTIME=/FlashPlayers/Flash\ Player\ Debugger.app
Win> export FLASCC_GDB_RUNTIME=C:\FlashPlayers\flashplayer_11_sa_debug.exe
To get the latest standalone debug Flash Player, visit http://www.adobe.com/support/flashplayer/downloads.html.
The next step is to recompile the PathFinderAS3Project SWF with the PathFinder.swc that has the debug symbols. Since a SWF could have multiple flascc SWCs with different namespaces you need to tell GDB what namespace to use. This namespace is the value set with –emit-swc=com.renaun.flascc
in the SWC compile command. The GDB command to set the correct namespace is set as3namespace com.renaun.flascc
. Instead of typing commands in the GDB command prompt each time, you can set up a GDB script file.
3.Create a file named sandbox/gdb_commands.txt and add the following text (found in the Step9 folder):
set as3namespace com.renaun.flascc
set breakpoint pending on
b flascc_setMap
r
The first command sets the namespace as discussed above. The second command sets the default value of "yes" for a warning about setting break points to code that hasn’t had its symbols loaded yet. The third line sets a break point to a function called flascc_setMap, which is in the C++ as3api.cpp file. The fourth and final line runs the debugger.
4.Now all you need to do is run GDB with the gdc_commands.txt script against the SWF application:
Mac> ~/flascc/sdk/usr/bin/gdb -x gdb_commands.txt PathFinderAS3Project/bin-debug/PathFinderAS3Project.swf
Win> /cygdrive/c/flascc/sdk/usr/bin/gdb.exe -x gdb_commands.txt PathFinderAS3Project/bin-debug/PathFinderAS3Project.swf
You should see the standalone Flash Player launch and the following output in your command-line shell:
GNU gdb (GDB) 7.3
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin10 --target=avm2-elf".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
No symbol table is loaded. Use the "file" command.
Breakpoint 1 (flascc_setMap) pending.
0xdddddddd in ?? ()
Breakpoint 1, 0xf0009822 in flascc_setMap () at as3api.cpp:57
57 Pather *arg1 = (Pather *) 0 ;
(gdb)
This indicates that GDB stopped at the flascc_setMap() breakpoint and you can now use the normal GDB commands to debug the C++ code.
5.Type continue and let the game start.
6.To exit GDB type quit.
As far as GDB knows, it is remotely debugging a real native C/C++ application, so when it prints out information about the current call stack or local variables, it prints out the current C/C++ call stack or the current C/C++ local variables. To see further up the call stack into ActionScript code, or to inspect the values of ActionScript local variables, flascc implements several AS3-specific versions of the regular GDB commands, prefixed with as3. A list of these commands and descriptions are found in the flascc documentation Reference.html page.
Note: The use of –g –O0 might be needed if you are not getting all your debug symbols. The extra –O0 flag turns off all optimization that might strip symbols.
Optimizing the SWC size (Step 10)
When creating a SWF or SWC, flascc compiles every symbol that it finds in the code. Most likely, you do not need every symbol. For example, you don’t want to include object symbols that are never used because they take up space. You might also want to only include object symbols that are used in your SWC interface so you can keep the size small.
In the process of creating and using SWC libraries with Flash Builder 4.7 (or compc), you specify in Flash Builder (or compc) which classes you want included. For example, in the ActionScript library project shown in Figure 6, I include only certain classes into my SWC. Then when a SWF compiles against the SWC it will only include the objects that are used.
Flash Builder will still pull in any classes that are referenced inside the included classes. The process of optimizing the GCC tool workflow to limit what object symbols are present in the final bits through inclusion or exclusion is a bit different than just giving it a list of class names. Also you still need to include all used object code, you just don’t want to include it as public interfaces, which in the flascc process would create additional unwanted code.
Getting the object symbols
The first step optimizing the SWC size is to identify the object symbols for the methods included as the public API for the flascc produced SWC. To create just object files with g++ (and not link them into the final output) use the -c flag. Since this is a flascc g++ it will create abc object code, which you do not want at this step. The other flag that is needed to produce LLVM bitcode is –emit-llvm or –O4 (that is capital letter O not zero). In this case, use the –O4 flag, which is similar –emit-llvm but with a few other optimizations. You have to decide what public API you want included in the optimized SWC. This is simple because all of the interface code is in the as3api.cpp file. You can generate LLVM bitcode with the following command:
Mac> ~/flascc/sdk/usr/bin/g++ -O4 –c as3api.cpp
Win> /cygdrive/c/flascc/sdk/usr/bin/g++.exe -O4 –c as3api.cpp
This will generate an as3api.o file in the same directory. This binary file contains the object code as well as the names of the symbols. You want to filter out the symbol names to use as a list of symbols for the LLVM tool chain to use. To do this you can use a few common tools: nm, grep, awk, and sed.
Listing symbols from a binary object file – Using the nm tool
The nm tool lists symbols from binary object files. Since the flascc tools generate and use specific object code you will need to use the nm version included in the flascc SDK, which is located at sdk/usr/bin/nm; for example:
Mac> ~/flascc/sdk/usr/bin/nm as3api.o
Win> /cygdrive/c/flascc/sdk/usr/bin/nm.exe as3api.o
The preceding commands generate the following output:
00000000 T __Z13flascc_setMapv
00000000 T __Z14flascc_getPathv
00000000 T __Z19flascc_createPatherv
00000000 T __Z19flascc_deletePatherv
00000000 W __ZN11micropather5GraphD0Ev
00000000 W __ZN11micropather5GraphD1Ev
U __ZN6Pather6setMapEPhii
U __ZN6Pather7getPathEiiii
00000000 W __ZTIN11micropather5GraphE
00000000 W __ZTSN11micropather5GraphE
U __ZTV6Pather
U __ZTVN10__cxxabiv117__class_type_infoE
00000000 W __ZTVN11micropather5GraphE
U __ZdlPv
U __Znwj
U ___cxa_pure_virtual
The second column with the capital letters defines what type of symbol in the list. The T type is for symbols in the code. The W and U types are weak and undefined symbols, respectively. In this case, you are only concerned with the T type symbols. You could manually copy the symbol names out of the output but that could be cumbersome for larger object files. You can use grep, awk, and sed to change a line like:
00000000 T __Z13flascc_setMapv
and make it look like:
_Z13flascc_setMapv
Here is the command:
Mac> ~/flascc/sdk/usr/bin/nm as3api.o | grep " T " | awk '{print $3}' | sed 's/__/_/'
Win> /cygdrive/c/flascc/sdk/usr/bin/nm.exe as3api.o | grep " T " | awk '{print $3}' | sed 's/__/_/'
The pipe (|) command feeds the output of one command into the input of the command after the pipe. The command above sends the output of nm to grep, then sends the output of grep to awk, and finally sends the output of awk to sed. The grep tool searches text and only return lines of text that match the given pattern, in this case " T ". After sed, the output looks like this:
00000000 T __Z13flascc_setMapv
00000000 T __Z14flascc_getPathv
00000000 T __Z19flascc_createPatherv
00000000 T __Z19flascc_deletePatherv
The awk tool by default treats spaces in text lines as delimiters for fields of data. The awk instruction ‘{print $3}’ tells awk to print the third parameter on each line. After awk, the output looks like this:
__Z13flascc_setMapv
__Z14flascc_getPathv
__Z19flascc_createPatherv
__Z19flascc_deletePatherv
The sed tool facilitates string replacement on each line of text. In this case you want to replace the double underscore characters with just one underscore character. This is done with ‘s/__/_/’. After sed, the output looks like this:
_Z13flascc_setMapv
_Z14flascc_getPathv
_Z19flascc_createPatherv
_Z19flascc_deletePatherv
Now you have the list of symbols that you need for the public API that LLVM will recognize in the optimization phase of only including the symbols you want. The last step is to take this output and put it into a file that you can use in the g++ build command. You do this by redirecting the output of the command into a file using the redirect and append command (>>). Here is the full command:
Mac> ~/flascc/sdk/usr/bin/nm as3api.o | grep " T " | awk '{print $3}' | sed 's/__/_/' >> exports.txt
Win> /cygdrive/c/flascc/sdk/usr/bin/nm.exe as3api.o | grep " T " | awk '{print $3}' | sed 's/__/_/' >> exports.txt
You will use the exports.txt file in the next step.
Using the flascc-specific GCC command line option –flto-api
The flascc-specific GCC command line option –flto-api
is used to define the public API that LLVM will preserve. This allows the LLVM’s optimizer to strip any symbols not mentioned in the exports.txt file or that aren't referenced by the symbols mentioned in this file. This is similar to how the ActionScript library compiler (or compc) will pull in only the files that you include as well as any classes that the included files reference.
You have to do one more thing before running the command. There are a few flascc-specific public API symbols that you need to include to be able to run your code. These are default symbols that you should always include. Create a file named sandbox/flascc_exports.txt
and add the following text with a blank line at the end (see the Step10 folder for an example):
# built in symbols that must always be preserved
_start1
malloc
free
memcpy
memmove
flascc_uiTickProc
_sync_synchronize
# symbols for C++ exception handling
_Unwind_SjLj_Register
_Unwind_SjLj_Resume
_Unwind_SjLj_Unregister
_Unwind_SjLj_RaiseException
# MAKE SURE TO LEAVE A EMPTY LINE AT THE BOTTOM
You want to copy these default symbols to exports.txt each time you add the symbols from your code into the file. Here are the commands for that process:
Mac> cp –f flascc_exports.txt exports.txt
Mac> ~/flascc/sdk/usr/bin/nm as3api.o | grep " T " | awk '{print $3}' | sed 's/__/_/' >> exports.txt
Win> cp –f flascc_exports.txt exports.txt
Win> /cygdrive/c/flascc/sdk/usr/bin/nm.exe as3api.o | grep " T " | awk '{print $3}' | sed 's/__/_/' >> exports.txt
Again make sure the first symbol from your code ends up on a new line in the exports.txt after the last # comment in flascc_exports.txt. The exports.txt file should end up looking like this:
# built in symbols that must always be preserved
_start1
malloc
free
memcpy
memmove
flascc_uiTickProc
_sync_synchronize
# symbols for C++ exception handling
_Unwind_SjLj_Register
_Unwind_SjLj_Resume
_Unwind_SjLj_Unregister
_Unwind_SjLj_RaiseException
# MAKE SURE TO LEAVE A EMPTY LINE AT THE BOTTOM
_Z13flascc_setMapv
_Z14flascc_getPathv
_Z19flascc_createPatherv
_Z19flascc_deletePatherv
With the list of files for the LLVM to optimize all that is needed is to add the –flto-api
to the final g++ build command. Along with the addition of the –flto-api you also need to add the –O4 optimization flag to the command. The addition of the –O4 flag is important to make sure the symbols you included, which were created with –O4 as LLVM bitcode, match the LLVM bitcode that –O4 produces in this final step. Here is the command:
Mac> ~/flascc/sdk/usr/bin/g++ -O4 -flto-api=exports.txt PathFinderWrapper.abc micropather.cpp pathfinder.cpp as3api.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
Win> /cygdrive/c/flascc/sdk/usr/bin/g++.exe -O4 -flto-api=exports.txt PathFinderWrapper.abc micropather.cpp
pathfinder.cpp as3api.cpp main.cpp -emit-swc=com.renaun.flascc -o PathFinder.swc
Looking at the resulting PathFinder.swc size compared to the non-optimized version, you’ll see quite a difference
PathFinder.swc with no -O4, size: 619kb
PathFinder.swc with -O4, size: 483kb
PathFinder.swc with -O4 and exports.txt, size: 217kb
That is the sizes for the SWC, but how does this impact the final release build of the game? Here is the size of PathFinderAS3Project.swf as a release build using the different SWCs: That is the sizes for the SWC, but how does this impact the final release build of the game? Here is the size of PathFinderAS3Project.swf as a release build using the different SWCs:
Using PathFinder.swc with no -O4, SWF size: 328.4kb
Using PathFinder.swc with -O4, SWF size: 263.3kb
Using PathFinder.swc with -O4 and exports.txt, SWF size: 88.7kb
I think the size numbers speak for themselves. It is well worth spending the time to optimize the code.
Where to go from here
You’ve now walked through the full process of creating and using flascc compiled code inside an ActionScript 3 game. There is a lot of C/C++ code that is ripe to use inside ActionScript 3 games. With the skills learned in this article, including building, using, debugging, and optimizing flascc-based SWCs, you’re ready to explore the advanced examples in the flascc SDK. You may want to learn how to use multithreading, integrate with a Stage3D game, or apply advanced SWIG features, which are all covered in the samples folder. There are also reference samples of Lua, Quake, Box2D, and the Bullet physics library running in ActionScript 3 through the use of flascc.
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.