views:

110

answers:

1

I am drawing a cellular automaton simulation on a Flex Canvas. I alternate computing the state of the automaton with updating the graphics on the Panel. However, the updating of the Panel does not seem to be "keeping up" with the update of the CA state.

I'm wondering whether I am doing something wrong with my handling of the graphics code. The code is below. Sorry it's so lengthy, but I don't think the problem will happen with anything simpler (well, it probably will, but I'm not certain I want to take the time to find it).

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init();">
    <mx:Script>
        <![CDATA[
        private static const universeRadius:uint = 8;

        private var universes:Array = new Array();
        private var currentUniverse:uint = 0;
        private var squareHeight:uint;
        private var squareWidth:uint;

        private function init():void {
            squareHeight = myCanvas.height / universeRadius;
            squareWidth = myCanvas.width / universeRadius;
            initUniverses();

            while (true) {
                trace("Calling draw()");
                draw();
                trace("Calling updateUniverse()");
                updateUniverse();
            }
        }

        private function initUniverses():void {
            var universe0:Array = new Array();
            var universe1:Array = new Array();

            var i:int;
            for (i = 0; i < universeRadius; i++) {
                var universe0Row:Array = new Array();
                var universe1Row:Array = new Array();
                var j:int;
                for (j = 0; j < universeRadius; j++) {
                    if (Math.random() < 0.5) {
                        universe0Row[j] = 0;
                    } else {
                        universe0Row[j] = 1;
                    }
                    universe1Row[j] = 0;
                }
                universe0[i] = universe0Row;
                universe1[i] = universe1Row;
            }

            universes[0] = universe0;
            universes[1] = universe1;
        }

        private function normalize(pos:int):int {
            return (pos + universeRadius) % universeRadius;
        }

        private function updateUniverse():void {
            var newUniverse:int = 1 - currentUniverse;
            var i:int;
            for (i = 0; i < universeRadius; i++) {
                var j:int;
                for (j = 0; j < universeRadius; j++) {
                    var dx:int;
                    var dy:int;
                    var count:int = 0;
                    for (dx = -1; dx <= 1; dx++) {
                        var neighborX:int = normalize(i + dx);
                        for (dy = -1; dy <= 1; dy++) {
                            var neighborY:int = normalize(j + dy);
                            if ((dx != 0 || dy != 0) && 
                                universes[currentUniverse][neighborX][neighborY] == 1) {
                                    count++;
                                }
                        }
                    }
                    var currentCell:int = universes[currentUniverse][i][j];
                    if (currentCell == 1) {
                        // 1. Any live cell with fewer than two live neighbours 
                        //    dies, as if caused by underpopulation.
                        if (count < 2) {
                            universes[newUniverse][i][j] = 0;
                        }
                        // 2. Any live cell with more than three live neighbours
                        //    dies, as if by overcrowding.
                        else if (count > 3) {
                            universes[newUniverse][i][j] = 0;
                        }
                        // 3. Any live cell with two or three live neighbours 
                        //    lives on to the next generation.
                        else {
                            universes[newUniverse][i][j] = 1;
                        }
                    } else {
                        // 4. Any dead cell with exactly three live neighbours 
                        //    becomes a live cell.
                        if (count == 3) {
                            universes[newUniverse][i][j] = 1;
                        } else {
                            universes[newUniverse][i][j] = 0;
                        }
                    }
                }
            }

            currentUniverse = newUniverse;
        }

        private function draw():void {
            myCanvas.graphics.clear();
            myCanvas.graphics.beginFill(0xFFFFFF, 1.0);
            myCanvas.graphics.drawRect(0, 0, myCanvas.width, myCanvas.height);
            var i:int;
            for (i = 0; i < universeRadius; i++) {
                var j:int;
                for (j = 0; j < universeRadius; j++) {
                    if (universes[currentUniverse][i][j] == "1") {
                        myCanvas.graphics.beginFill(0x000000, 1.0);
                        myCanvas.graphics.drawRect(
                            j * squareWidth, i * squareHeight, squareWidth, squareHeight)
                    }
                }
            }
            myCanvas.graphics.endFill();
        }
        ]]>
    </mx:Script>
    <mx:Panel title="Life" height="95%" width="95%" 
        paddingTop="5" paddingLeft="5" paddingRight="5" paddingBottom="5">

        <mx:Canvas id="myCanvas" borderStyle="solid" height="100%" width="100%">
        </mx:Canvas>
    </mx:Panel>
</mx:Application>
+1  A: 

Scary:

while (true) {

Flex is single-threaded, so I think you're never giving the UIComponents a chance to fully update.

Alternatives would be to run the draw and update methods on a Timer, or run them on the ENTER_FRAME event. Either of these would let the display list updates occur in between your

private function cycleUniverse(event:Event):void {
    updateUniverse();
    draw();
}

and then either of

private var t:Timer;

private function init():void {
    t = new Timer(500); // every 500 ms
    t.addEventListener(TimerEvent.Timer, cycleUniverse);
    t.start();
}

or

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" enterFrame="cycleUniverse(event)">
Michael Brewer-Davis
I ended up putting the updates on a Timer event and that works fine.I know "while (true)" is scary, but it's an early version of the code and I planned on changing that later.Thanks!
Paul Reiners
Only scary because of Flex's single-threadedness. Elsewhere, while (true) away.
Michael Brewer-Davis