How to properly delete a box2d body in version: Box2dWeb-2.1.a.3, Box2D_v2.3.1r3? Box2D bug? - memory-leaks

Update
Since the problem has been found I've also find out that Box2D for web is leaking on every side :/
To show this I made a simple circle moving in a static polygon and here is the result after some time.
Notice how the following items are leaking as I'm not creating any body or changing the world in any way:
b2Vec2
Features
b2ManifoldPoint
b2ContactID
b2Manifold
b2ContactEdge
b2PolyAndCircleContact
Array
...
Original post
I have a problem because I'm profiling my game and the garbage collector doesnt' delete my bodies, contacts and other stuff. Then I've looked at what are they keeping from the GC and was the Box2D itself. This might lead to 2 options: I'm doing it bad or Box2D is leaking. I consider is my cause.
What exactly is keeping it?
contact.m_nodeA.other was appearing to be the most used to keep it from GC.
other times: m_fixtureB in a contact... see image
You can see that the body has a __destroyed property. That is set manually before deleting it with world.DestroyBody(body)
When I destroy a body I call it after I call the step method on the world.
As you can see from the box2d method it doesn't get rid of the other variable nor it changes it to another body and my body is not GC.
Any idea of what I'm missing here?
Now I can fix the problem only if the world.Step is not ran:
var gravity = new Box2D.Vec2(0, 0);
var doSleep = true;
var world = new Box2D.World(gravity, doSleep);
var step = false;
var fixtureDef = new Box2D.FixtureDef();
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.2;
fixtureDef.shape = new Box2D.PolygonShape();
fixtureDef.shape.SetAsBox(1, 1);
var bodyDef = new Box2D.BodyDef;
bodyDef.type = Box2D.Body.b2_dynamicBody;
bodyDef.position.x = 0.4;
bodyDef.position.y = 0.4;
var bodies = []
var fix = [];
window.c = function(){
for(var i = 0; i < 100; i++){
var body = world.CreateBody(bodyDef);
body._id = i;
fix.push(body.CreateFixture(fixtureDef));
bodies.push(body);
}
if(step){world.Step(1/60, 3, 3); world.ClearForces();}
console.log('Created', bodies)
fixtureDef = null;
bodyDef = null;
}
window.d = function(){
_.each(bodies, function(body, i){
body.DestroyFixture(fix[i]);
world.DestroyBody(body);
fix[i] = null;
bodies[i] = null;
})
if(step){world.Step(1/60, 3, 3); world.ClearForces();}
bodies = null;
fix = null;
}
Change the step to true and the memory leak problem appears again.
Reproduce the memory leak problem:
Code in your file:
var gravity = new Box2D.Vec2(0, 0);
var doSleep = true;
var world = new Box2D.World(gravity, doSleep);
var bodies = []
window.c = function(){
for(var i = 0; i < 100; i++){
var bodyDef = new Box2D.BodyDef();
bodyDef.type = 2;
var shape = new Box2D.PolygonShape();
shape.SetAsBox(1, 1);
var fixtureDef = new Box2D.FixtureDef();
fixtureDef.shape = shape;
var body = world.CreateBody(bodyDef);
body._id = i;
body.CreateFixture(fixtureDef);
bodies.push(body);
}
world.Step(0.3, 3, 3);
console.log('Created', bodies)
}
window.d = function(){
_.each(bodies, function(body, i){
world.DestroyBody(body);
bodies[i] = null;
})
world.Step(0.3, 3, 3);
bodies = null;
}
Open google chrome:
Then open your profile and make a snapshot.
Now run the c() method in your console to create 100 bodies
Now snapshot 2
Search in snapshot for b2Body and you'll find 100 Object count
Now run d() to delete all your bodies;
Force Garbage collection by clicking on the garbage can
Make a snapshot 3
Search for b2Body and you'll also find 100 Object count
At the last step should only be 0 objects as they have been destroyed. Instead of this you'll find this:
Now you can see there are a lot of references from b2ContactEdge. Now if you remove the world.Step part of the code you will only see 2 references to the body.
If you remove this line
body.CreateFixture(fixtureDef);
or making the body static is not leaking anymore.
My game loop
...gameLoop = function(o){
// used a lot here
var world = o.world;
// calculate the new positions
var worldStepSeconds = o.worldStepMs / 1000;
// step world
world.Step(worldStepSeconds, o.velocityIterations, o.positionIterations)
// render debug
if(o.renderDebug){
world.DrawDebugData();
}
// always to not accumulate forces, maybe some bug occurs
world.ClearForces();
// tick all ticking entities
_.each(o.getTickEntitiesFn(), function(actor){
if(!actor) return;
actor.tick(o.worldStepMs, o.lastFrameMs);
})
// update PIXI entities
var body = world.GetBodyList();
var worldScale = world.SCALE;
var destroyBody = world.DestroyBody.bind(world);
while(body){
var actor = null;
var visualEntity = null;
var box2DEntity = o.getBox2DEntityByIdFn(body.GetUserData());
if(box2DEntity){
visualEntity = o.getVisualEntityByIdFn(box2DEntity.getVisualEntityId());
if(box2DEntity.isDestroying()){
// optimization
body.__destroyed = true;
world.DestroyBody(body);
box2DEntity.completeDestroy();
}
}
if(visualEntity){
if(visualEntity.isDestroying()){
visualEntity.completeDestroy();
}else{
var inverseY = true;
var bodyDetails = Utils.getScreenPositionAndRotationOfBody(world, body, inverseY);
visualEntity.updateSprite(bodyDetails.x, bodyDetails.y, bodyDetails.rotation);
}
}
// this delegates out functionality for each body processed
if(o.triggersFn.eachBody) o.triggersFn.eachBody(world, body, visualEntity);
body = body.GetNext();
}
// when a joint is created is then also created it's visual counterpart and then set to userData.
var joint = world.GetJointList();
while(joint){
var pixiGraphics = joint.GetUserData();
if(pixiGraphics){
// In order to draw a distance joint we need to know the start and end positions.
// The joint saves the global (yes) anchor positions for each body.
// After that we need to scale to our screen and invert y axis.
var anchorA = joint.GetAnchorA();
var anchorB = joint.GetAnchorB();
var screenPositionA = anchorA.Copy();
var screenPositionB = anchorB.Copy();
// scale
screenPositionA.Multiply(world.SCALE);
screenPositionB.Multiply(world.SCALE);
// invert y
screenPositionA.y = world.CANVAS_HEIGHT - screenPositionA.y
screenPositionB.y = world.CANVAS_HEIGHT - screenPositionB.y
// draw a black line
pixiGraphics.clear();
pixiGraphics.lineStyle(1, 0x000000, 0.7);
pixiGraphics.moveTo(screenPositionA.x, screenPositionA.y);
pixiGraphics.lineTo(screenPositionB.x, screenPositionB.y);
}
joint = joint.GetNext();
}
// render the PIXI scene
if(o.renderPixi){
o.renderer.render(o.stage)
}
// render next frame
requestAnimFrame(o.requestAnimFrameFn);
}
Code from Box2d:
b2ContactManager.prototype.Destroy = function (c) {
var fixtureA = c.GetFixtureA();
var fixtureB = c.GetFixtureB();
var bodyA = fixtureA.GetBody();
var bodyB = fixtureB.GetBody();
if (c.IsTouching()) {
this.m_contactListener.EndContact(c);
}
if (c.m_prev) {
c.m_prev.m_next = c.m_next;
}
if (c.m_next) {
c.m_next.m_prev = c.m_prev;
}
if (c == this.m_world.m_contactList) {
this.m_world.m_contactList = c.m_next;
}
if (c.m_nodeA.prev) {
c.m_nodeA.prev.next = c.m_nodeA.next;
}
if (c.m_nodeA.next) {
c.m_nodeA.next.prev = c.m_nodeA.prev;
}
if (c.m_nodeA == bodyA.m_contactList) {
bodyA.m_contactList = c.m_nodeA.next;
}
if (c.m_nodeB.prev) {
c.m_nodeB.prev.next = c.m_nodeB.next;
}
if (c.m_nodeB.next) {
c.m_nodeB.next.prev = c.m_nodeB.prev;
}
if (c.m_nodeB == bodyB.m_contactList) {
bodyB.m_contactList = c.m_nodeB.next;
}
this.m_contactFactory.Destroy(c);
--this.m_contactCount;
}
b2ContactFactory.prototype.Destroy = function (contact) {
if (contact.m_manifold.m_pointCount > 0) {
contact.m_fixtureA.m_body.SetAwake(true);
contact.m_fixtureB.m_body.SetAwake(true);
}
var type1 = parseInt(contact.m_fixtureA.GetType());
var type2 = parseInt(contact.m_fixtureB.GetType());
var reg = this.m_registers[type1][type2];
if (true) {
reg.poolCount++;
contact.m_next = reg.pool;
reg.pool = contact;
}
var destroyFcn = reg.destroyFcn;
destroyFcn(contact, this.m_allocator);
}

I have the same problem, but I think I find out from where it comes.
Instead of m_* try functions, like GetFixtureA() instead of m_fixtureA.

Totti did you ever figure this out? It looks like box2dweb requires manual destruction and memory management.
I think I have found your leaks, un-implemented ( static class ) destruction functions:
b2Joint.Destroy = function (joint, allocator) {}
b2CircleContact.Destroy = function (contact, allocator) {}<
b2PolygonContact.Destroy = function (contact, allocator) {}
b2EdgeAndCircleContact.Destroy = function (contact, allocator) {}<
b2PolyAndCircleContact.Destroy = function (contact, allocator) {}
b2PolyAndEdgeContact.Destroy = function (contact, allocator) {}
[UPDATE...]
b2DestructionListener.b2DestructionListener = function () {};
b2DestructionListener.prototype.SayGoodbyeJoint = function (joint) {}
b2DestructionListener.prototype.SayGoodbyeFixture = function (fixture) {}
b2Contact.prototype.Reset(fixtureA, fixtureB)
called with with one/both fixture arguments resets passed in fixture/s BUT ALSO pass in NO arguments and it 'nulls' all the the b2Contact properties! (UNTESTED:) but I suggest set your YOURcontactListener class up to handle all contact callbacks EVERY call with Reset(??) dynamically configureable as logic requies EVERY call (there are more than you'd imagine each and every world step).
Also take Colt McAnlis clever advice and strategically pre allocate all the memory the life of your game will need (by creating game and box2d object pools now you know objects can be reset) so the garbage collector NEVER runs until, you destroy object pools at times of your own convenience.... i.e when you close the tab, or your device needs recharging! ;D [...UPDATE]
// you can define and assign your own contact listener ...via...
YOUR.b2world.b2ContactManager.m_world.m_contactList = new YOURcontactlistener();<br>[edit]...if you dont it actually does have Box2D.Dynamics.b2ContactListener.b2_defaultListener.
// box2d in the worldStep calls YOURcontactlistener.update() via:
this.b2world.b2ContactManager.m_world.m_contactList.Update(this.m_contactListener) // this.m_contactListener being YOURS || b2_defaultListener;
// which instantiates ALL your listed leaking object like so:
{b2Contact which instantiates {b2ContactEdge} and {b2Manifold which instantiates {b2ManifoldPoint{which instantiates m_id.key == ContactID{which instantiates Features}}}} along with {B2Vec2} are instantiated in b2ContactResult ...which I can not actually find but assume it must be instantiated in the Solver.
// There is a Contacts.destroyFcn callback is CREATED in....
b2ContactFactory.prototype.Destroy = function (contact) {...}
// then Contacts.destroyFcn callback(s) are privately REGISTERED in....
b2ContactFactory.prototype.InitializeRegisters() {...}
...via...
this.AddType = function (createFcn, destroyFcn, type1, type2) {...}
...BUT... THOSE privately registered ARE four of the un-implimented static class function from above...
b2PolygonContact.Destroy = function (contact, allocator) {}
b2EdgeAndCircleContact.Destroy = function (contact, allocator) {}
b2PolyAndCircleContact.Destroy = function (contact, allocator) {}
b2PolyAndEdgeContact.Destroy = function (contact, allocator) {}
So I havn't tested it yet but it looks like box2dweb just gives you the Destroy callback/handler functions and you have to read the source to find all the properties you need to null. [Edit] In combination with b2Contact.prototype.Reset(fixtureA, fixtureB)
But either way pretty confident the functions above(possibly incomplete) are callback/handlers, and can be used to null your way back to performance for anyone else who stumbles across this problem. Pretty sure Totti's moved on(dont forget to handle your 'this' scope in callbacks).

Related

How to export (save) data in OpenFL (Haxe) via XML(?)

In AS3 I could write the following:
fileReference = new FileReference();
var xmlStage:XML = new XML(<STAGE/>);
var xmlObjects:XML = new XML(<OBJECTS/>);
var j:uint;
var scene:SomeScene = ((origin_ as SecurityButton).origin as SomeScene);
var object:SomeObject;
for (j = 0; j < scene.objectArray.length; ++j) {
object = scene.objectArray[j];
if (1 == object.saveToXML){
var item:String = "obj";
var o:XML = new XML(<{item}/>);
o.#x = scene.objectArray[j].x;
o.#y = scene.objectArray[j].y;
o.#n = scene.objectArray[j].name;
o.#g = scene.objectArray[j].band;
o.#f = scene.objectArray[j].frame;
o.#w = scene.objectArray[j].width;
o.#h = scene.objectArray[j].height;
o.#s = scene.objectArray[j].sprite;
o.#b = scene.objectArray[j].bodyType;
xmlObjects.appendChild(o);
//System.disposeXML(o);
}
}
xmlStage.appendChild(xmlObjects);
fileReference.save(xmlStage, "XML.xml");
//System.disposeXML(xmlObjects);
//System.disposeXML(xmlStage);
//fileReference = null;
Is there an equivalent way to do this in Haxe? (Target of interest: HTML5)
If not, what are my options?
(The exported results of this code in AS3 are shown in this link below)
https://pastebin.com/raw/5twiJ01B
You can use the Xml class to create xml (see example: https://try.haxe.org/#68cfF )
class Test {
static function main() {
var root = Xml.createElement('root');
var child = Xml.createElement('my-element');
child.set('attribute1', 'value1'); //add your own object's values
child.set('attribute2', 'value2'); //may be add a few more children
root.addChild(child);
//this could be a file write, or POST'ed to http, or socket
trace(root.toString()); // <root><my-element attribute1="value1" attribute2="value2"/></root>
}
}
The root.toString() in that example could be instead serialized to a file File, or indeed any other kind of output (like POSTing via http to somewhere).
You could use FileReference for flash target, and sys.io and File for supported targets:
var output = sys.io.File.write(path, true);
output.writeString(data);
output.flush();
output.close();

Object Based EventListener/EventEmitter in NodeJS?

I suddenly realized that event emitter in NodeJS is usually like a static method in Java.. Example:
// This illustrated that event listener is universal
function A(a){
var that = this;
this.a = a;
this.cnt = 0;
this.done = function(){
this.emit("done");
};
this.say = function(){
console.log(a + " = " + that.cnt);
};
this.inc = function(){
that.cnt++;
};
}
A.prototype = new events.EventEmitter;
var a = new A("a"),
b = new A("b"),
c = new A("c");
a.on("done",function(){a.inc()});
b.on("done",function(){b.inc()});
c.on("done",function(){c.inc()});
c.done();
c.done();
a.say();
b.say();
This code would give output:
a = 2
b = 2
While I'm actually expecting:
a = 0
b = 0
I believe this is because of the line:
A.prototype = new events.EventEmitter;
and I think the "prototype" kind of definition would be used like "static" in Java.
In order to have per-object based event listener, I changed the above code to be:
function B(a){
var that = this;
this.evt = new events.EventEmitter;
this.a = a;
this.cnt = 0;
this.done = function(){
this.evt.emit("done");
};
this.say = function(){
console.log(a + " = " + that.cnt);
};
this.inc = function(){
that.cnt++;
};
}
var a = new B("a"),
b = new B("b"),
c = new B("c");
a.evt.on("done",function(){a.inc()});
b.evt.on("done",function(){b.inc()});
c.evt.on("done",function(){c.inc()});
c.done();
c.done();
a.say();
b.say();
This would be per-object event listener, but I don't really think that is a good design/implementation because it breaks the chaining of EventEmitter. I.e., like code bellow:
// can chain another method of A after the on() method
a.on("event",functionCallback).anotherMethodOfA();
I'd like to ask, what's a proper implementation of the per-object event listener in NodeJS?
You can use addListener or on to attach listeners to your custom events. You won't need to chain calls on these methods. Of course you can inherit any object from EventEmitter and add emitting functionality to your object. You can inherit your object from an instance of EventEmitter. There's is function called inherit in the util library which does that for you.
var util = require('util');
var eventEmitter = require('events').EventEmitter;
// Now create your constructor/object.
function MyObj(a, b) {
this.a = a;
this.b = b;
.
.
.
}
util.inherits(MyObj,eventEmitter);
// Implement your methods and add the functionality you need.
MyObj.prototype.aMethod = function(arg) {
.
.
.
// Define how to emit events
if (arg == 'A')
this.emit('eventA', this.a);
else if (arg == 'B')
this.emit('eventB');
// Return this for chaining method calls
return this;
}
MyObj.prototype.anotherMethod = function() {
// Add more functionality...
.
.
.
return this;
}
// Now instantiate the constructor and add listenters
var instanceOfMyObj = new MyObj('a parameter', 'another parameter');
instanceOfMyObj.on('eventA', function(a){
// Handle the event
});
// Now chain calls..
instanceOfMyObj.aMethod('A').anotherMethod(); // This will trigger eventA...

Generators in Node.js - Fiber or pure JavaScript?

I am trying to implement generators in Node.js. I came across node-fiber and node-lazy. Node-lazy deals with arrays and streams, but does not generate lazy things inherently (except numbers).
While using fiber looks cleaner, it has its cons, and as such, I prefer pure Javascript with closures as it's more explicit. My question is: are there memory or perf problems using closure to generate an iterator?
As an example, I'm trying to iterate through a tree depth-first, for as long as the caller asks for it. I want to find 'waldo' and stop at the first instance.
Fiber:
var depthFirst = Fiber(function iterate(tree) {
tree.children.forEach(iterate);
Fiber.yield(tree.value);
});
var tree = ...;
depthFirst.run(tree);
while (true) {
if (depthFirst.run() === 'waldo')
console.log('Found waldo');
}
Pure JavaScript with closures:
function iterate(tree) {
var childIndex = 0;
var childIter = null;
var returned = false;
return function() {
if (!childIter && childIndex < tree.children.length)
childIter = iterate(tree.children[childIndex++]);
var result = null;
if (childIter && (result = childIter()))
return result;
if (!returned) {
returned = true;
return tree.value;
}
};
}
var tree = ...;
var iter = iterate(tree);
while (true) {
if (iter() === 'waldo')
console.log('found waldo');
}

Drag/Move Multiple Selected Features - OpenLayers

I know that I can easily allow a user to select multiple Features/Geometries in OpenLayers but I then want enable the user to easily drag/move all of the selected features at the same time.
With the ModifyFeature control it only moves one feature at a time ... is there a way to easily extend this control (or whatever works) to move all of the selected features on that layer?
Okay, skip the ModifyFeature control and just hook into the SelectFeature control to keep track of the selected features and then use the DragControl to manipulate the selected points at the same time.
Example of the control instantiation:
var drag = new OpenLayers.Control.DragFeature(vectors, {
onStart: startDrag,
onDrag: doDrag,
onComplete: endDrag
});
var select = new OpenLayers.Control.SelectFeature(vectors, {
box: true,
multiple: true,
onSelect: addSelected,
onUnselect: clearSelected
});
Example of the event handling functions:
/* Keep track of the selected features */
function addSelected(feature) {
selectedFeatures.push(feature);
}
/* Clear the list of selected features */
function clearSelected(feature) {
selectedFeatures = [];
}
/* Feature starting to move */
function startDrag(feature, pixel) {
lastPixel = pixel;
}
/* Feature moving */
function doDrag(feature, pixel) {
for (f in selectedFeatures) {
if (feature != selectedFeatures[f]) {
var res = map.getResolution();
selectedFeatures[f].geometry.move(res * (pixel.x - lastPixel.x), res * (lastPixel.y - pixel.y));
vectors.drawFeature(selectedFeatures[f]);
}
}
lastPixel = pixel;
}
/* Featrue stopped moving */
function endDrag(feature, pixel) {
for (f in selectedFeatures) {
f.state = OpenLayers.State.UPDATE;
}
}
Hmm...
I tried the code above, and couldn't make it work. Two issues:
1) To move each feature, you need to use the original position of that feature, and add the "drag vector" from whatever feature the DragControl is moving around by itself (i.e. the feature-parameter to doDrag).
2) Since DragFeatures own code sets lastPixel=pixel before calling onDrag, the line calling move() will move the feature to (0,0).
My code looks something like this:
var lastPixels;
function startDrag(feature, pixel) {
// save hash with selected features start position
lastPixels = [];
for( var f=0; f<wfs.selectedFeatures.length; f++){
lastPixels.push({ fid: layer.selectedFeatures[f].fid,
lastPixel: map.getPixelFromLonLat( layer.selectedFeatures[f].geometry.getBounds().getCenterLonLat() )
});
}
}
function doDrag(feature, pixel) {
/* because DragFeatures own handler overwrites dragSelected.lastPixel with pixel before this is called, calculate drag vector from movement of "feature" */
var g = 0;
while( lastPixels[g].fid != feature.fid ){ g++; }
var lastPixel = lastPixels[g].lastPixel;
var currentCenter = map.getPixelFromLonLat( feature.geometry.getBounds().getCenterLonLat() );
var dragVector = { dx: currentCenter.x - lastPixel.x, dy: lastPixel.y - currentCenter.y };
for( var f=0; f<layer.selectedFeatures.length; f++){
if (feature != layer.selectedFeatures[f]) {
// get lastpixel of this feature
lastPixel = null;
var h = 0;
while( lastPixels[h].fid != layer.selectedFeatures[f].fid ){ h++; }
lastPixel = lastPixels[h].lastPixel;
var newPixel = new OpenLayers.Pixel( lastPixel.x + dragVector.dx, lastPixel.y - dragVector.dy );
// move() moves polygon feature so that centre is at location given as parameter
layer.selectedFeatures[f].move(newPixel);
}
}
}
I had a similar problem and solved it by overriding DragFeature's moveFeature function and putting this.lastPixel = pixel inside the for loop that applies the move to all features within my layer vector. Until I moved this.lastPixel = pixel inside the loop, all features except the one being dragged got crazily distorted.
`OpenLayers.Control.DragFeature.prototype.moveFeature = function (pixel) {
var res = this.map.getResolution();
for (var i = 0; i < vector.features.length; i++) {
var feature = vector.features[i];
feature .geometry.move(res * (pixel.x - this.lastPixel.x),
res * (this.lastPixel.y - pixel.y));
this.layer.drawFeature(feature );
this.lastPixel = pixel;
}
this.onDrag(this.feature, pixel);
};
`

Help on Removal of Dynamically Created sprites

import flash.display.Sprite;
import flash.net.URLLoader;
var index:int = 0;
var constY = 291;
var constW = 2;
var constH = 40;
hydrogenBtn.label = "Hydrogen";
heliumBtn.label = "Helium";
lithiumBtn.label = "Lithium";
hydrogenBtn.addEventListener (MouseEvent.CLICK, loadHydrogen);
heliumBtn.addEventListener (MouseEvent.CLICK, loadHelium);
lithiumBtn.addEventListener (MouseEvent.CLICK, loadLithium);
var myTextLoader:URLLoader = new URLLoader();
myTextLoader.addEventListener(Event.COMPLETE, onLoaded);
function loadHydrogen (event:Event):void {
myTextLoader.load(new URLRequest("hydrogen.txt"));
}
function loadHelium (event:Event):void {
myTextLoader.load(new URLRequest("helium.txt"));
}
function loadLithium (event:Event):void {
myTextLoader.load(new URLRequest("lithium.txt"));
}
var DataSet:Array = new Array();
var valueRead1:String;
var valueRead2:String;
function onLoaded(event:Event):void {
var rawData:String = event.target.data;
for(var i:int = 0; i<rawData.length; i++){
var commaIndex = rawData.search(",");
valueRead1 = rawData.substr(0,commaIndex);
rawData = rawData.substr(commaIndex+1, rawData.length+1);
DataSet.push(valueRead1);
commaIndex = rawData.search(",");
if(commaIndex == -1) {commaIndex = rawData.length+1;}
valueRead2 = rawData.substr(0,commaIndex);
rawData = rawData.substr(commaIndex+1, rawData.length+1);
DataSet.push(valueRead2);
}
generateMask_Emission(DataSet);
}
function generateMask_Emission(dataArray:Array):void{
var spriteName:String = "Mask"+index;
trace(spriteName);
this[spriteName] = new Sprite();
for (var i:int=0; i<dataArray.length; i+=2){
this[spriteName].graphics.beginFill(0x000000, dataArray[i+1]);
this[spriteName].graphics.drawRect(dataArray[i],constY,constW, constH);
this[spriteName].graphics.endFill();
}
addChild(this[spriteName]);
index++;
}
Hi, I am relatively new to flash and action script as well and I am having a problem getting the sprite to be removed after another is called. I am making emission spectrum's of 3 elements by dynamically generating the mask over a picture on the stage. Everything works perfectly fine with the code I have right now except the sprites stack on top of each other and I end up with bold lines all over my picture instead of a new set of lines each time i press a button.
I have tried using try/catch to remove the sprites and I have also rearranged the entire code from what is seen here to make 3 seperate entities (hoping I could remove them if they were seperate variables) instead of 2 functions that handle the whole process. I have tried everything to the extent of my knowledge (which is pretty minimal # this point) any suggestions?
Thanks ahead of time!
My AS3 knowledge is rather rudimentary right now but I think two things may help you.
You could use removeChild before recreating the Sprite. Alternatively, just reuse the Sprite.
Try to add this[spriteName].graphics.clear(); to reset the sprite and start redrawing.
function generateMask_Emission (dataArray : Array) : void {
var spriteName:String = "Mask"+index;
trace(spriteName);
// Don't recreate if sprite object already created
if (this[spriteName] == null)
{
this[spriteName] = new Sprite();
// Only need to add sprite to display object once
addChild(this[spriteName]);
}
for (var i:int= 0; i < dataArray.length; i+=2)
{
this[spriteName].graphics.clear();
this[spriteName].graphics.beginFill(0x000000, dataArray[i+1]);
this[spriteName].graphics.drawRect(dataArray[i],constY,constW, constH);
this[spriteName].graphics.endFill();
}
index++;
}
Just in case anyone was curious or having a similar problem. Extremely simple fix but here is what I did.
Also should mention that I don't think that the graphics.clear function actually fixed the problem (though I didn't have the sprite being cleared properly before), but I believe the problem lies in the beginning of the onloaded function where 3 of those variables used to be outside of the function.
import flash.display.Sprite;
import flash.net.URLLoader;
import flash.events.Event;
var constY = 291; //this value represets the Y value of the bottom of the background spectrum image
var constW = 2; //this value represents the width of every emission line
var constH = 40; //this value represents the height of every emission line
//Create Button Labels
hydrogenBtn.label = "Hydrogen";
heliumBtn.label = "Helium";
lithiumBtn.label = "Lithium";
//These listen for the buttons to be clicked to begin loading in the data
hydrogenBtn.addEventListener (MouseEvent.CLICK, loadHydrogen);
heliumBtn.addEventListener (MouseEvent.CLICK, loadHelium);
lithiumBtn.addEventListener (MouseEvent.CLICK, loadLithium);
var myTextLoader:URLLoader = new URLLoader();//the object to load in data from external files
myTextLoader.addEventListener(Event.COMPLETE, onLoaded);//triggers the function when the file is loaded
var Mask:Sprite = new Sprite(); //This sprite will hold the information for the spectrum to be put on stage
function loadHydrogen (event:Event):void {
myTextLoader.load(new URLRequest("hydrogen.txt"));//starts loading Hydrogen emisson data
}
function loadHelium (event:Event):void {
myTextLoader.load(new URLRequest("helium.txt"));//starts loading Helium emission data
}
function loadLithium (event:Event):void {
myTextLoader.load(new URLRequest("lithium.txt"));//starts loading Lithium emission data
}
function onLoaded(event:Event):void {//the function that handles the data from the external file
var rawData:String = event.target.data; //create a new string and load in the data from the file
var DataSet:Array = new Array();//the array to load values in to
var valueRead1:String; //subset of array elements (n)
var valueRead2:String; //subset of array elements (n+1)
for(var i:int = 0; i<rawData.length; i++){ //loop through the string and cut up the data # commas
var commaIndex = rawData.search(",");
valueRead1 = rawData.substr(0,commaIndex);
rawData = rawData.substr(commaIndex+1, rawData.length+1);
DataSet.push(valueRead1);
commaIndex = rawData.search(",");
if(commaIndex == -1) {commaIndex = rawData.length+1;}
valueRead2 = rawData.substr(0,commaIndex);
rawData = rawData.substr(commaIndex+1, rawData.length+1);
DataSet.push(valueRead2);
}
generateMask_Emission(DataSet);//call the generateMaskEmission function on new data to fill emission lines
}
//This function loops through an array, setting alternating values as locations and alphas
function generateMask_Emission(dataArray:Array):void{
Mask.graphics.clear(); //Clears the Mask sprite for the next set of values
addChild(Mask); //Adds the blank sprite in order to clear the stage of old sprites
//This loop actually draws out how the sprite should look before it is added
for (var i:int=0; i<dataArray.length; i+=2){
Mask.graphics.beginFill(0x000000, dataArray[i+1]);
Mask.graphics.drawRect(dataArray[i],constY,constW, constH);
Mask.graphics.endFill();
}
addChild(Mask);// actually adds the mask we have created to the stage
}

Resources