[Issue 1190] Reference becoming null

d-bugmail at puremagic.com d-bugmail at puremagic.com
Tue Sep 4 01:10:07 PDT 2007


http://d.puremagic.com/issues/show_bug.cgi?id=1190


davidl at 126.com changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |RESOLVED
         Resolution|                            |INVALID




------- Comment #6 from davidl at 126.com  2007-09-04 03:10 -------
I've noticed that your code create something like :
myNode.rNeigh.bNeigh == myNode
So I assume it's your code fault, if you can't provide a smaller test case.

import std.string;
import std.stream;
import std.math;

alias std.string.toString strfy;
alias std.stdio.writefln Log;
int staticc;

private static const ubyte PatchSize=64; //must be power of 2
//const ubyte MAX_DEPTH=cast(ubyte)(2*sqrt(PatchSize)+1);
private static const MaxDepth=4;//17

private struct CamInfo
{
    uint x, y, z;
}

private CamInfo camInfo;

/**
The Terrain engine class.
*/
public final class Terrain
{
    ///The TreeNode class is used to store information about the patch tree
    private final class TreeNode
    {
        private TreeNode lChild, rChild, bNeigh, lNeigh, rNeigh;

        public this()
        {
            reset();
        }

        public void reset()
        {
            lChild=rChild=bNeigh=lNeigh=rNeigh=null;
        }
    }

    ///Patch Class. A terrain is composed of several patches
    private final class Patch
    {
        this (ushort x, ushort y)
        {
            this.x=x; this.y=y;
            visible=true;
            rNode=new TreeNode(); //these never get reassigned
            lNode=new TreeNode(); //they're const, remmember?
            assert(rNode !is null);
            assert(lNode !is null);
            //nodes come already clean
            rNode.bNeigh=lNode;
            lNode.bNeigh=rNode;
            calcError();
        }

        ///Resets the patch. It becomes undivided and ready for the next
tesselation.
        void reset()
        {
            rNode.reset();
            lNode.reset();
            rNode.bNeigh=lNode;
            lNode.bNeigh=rNode;
            //TODO: check visibility here
            visible=true;
        }

        ///Tesselates this patch and it becomes ready for rendering
        void update()
        {
            std.gc.disable();
            cError[]=lError;
            tesselate(lNode, x, cast(ushort)(y+PatchSize),
cast(ushort)(x+PatchSize), y, x, y, 1);
            cError[]=rError;
            tesselate(rNode, cast(ushort)(x+PatchSize), y, x,
cast(ushort)(y+PatchSize),
                cast(ushort)(x+PatchSize), cast(ushort)(y+PatchSize), 1);
            std.gc.enable();
        }

        ///This patch's error measure is updated from the data on hData.
        void calcError()
        {
            Calculate(x, cast(ushort)(y+PatchSize), cast(ushort)(x+PatchSize),
y, x, y, 1);
            lError[]=cError; //copy the error data
            Calculate(cast(ushort)(x+PatchSize), y, x,
cast(ushort)(y+PatchSize),
                cast(ushort)(x+PatchSize), cast(ushort)(y+PatchSize), 1);
            rError[]=cError;
            return;
        }

        ~this()
        {
            delete rNode;
            delete lNode;
        }

        //no need to protect this vars because this class won't be accessed
from the exterior
        const ushort x, y; //coordinates aren't expected to change
        bool visible; ///Result of lastest cull check
        ushort[MaxDepth] lError, rError;
        const TreeNode rNode, lNode;
    }

    public:
    this(in File file, float detail)
    in
    {
        assert(file !is null);
        assert(detail>=0); //negative error values make no sense. 0 values are
purely debugging technices
    }
    body
    {
        debug Log("Creating terrain object");
        this.detail=detail;
        file.read(sizeX);
        file.read(sizeY);
        assert((sizeX>0)&&(sizeY>0));
        HMult=1; //TODO: Replace with resolution from file
        hData=new ushort[][](sizeX, sizeY); //initialize data
        debug Log(" * Loading terrain data...");
        foreach (stripe; hData)
            foreach (inout vertex; stripe)
            {
                file.read(vertex);
                vertex*=HMult;
            }
        maxH=0; //Maximum height present on the map. Used somewhere in debug
code
        foreach (stripe; hData)
            foreach (vertex; stripe)
                if (vertex>maxH) maxH=vertex;
        xPatch=cast(ubyte)(sizeX/PatchSize);
        yPatch=cast(ubyte)(sizeY/PatchSize);
        nodes.length=(sizeX*sizeY/PatchSize);
        debug Log(" * Creating terrain nodes ("~strfy(nodes.length)~")...");
        foreach(inout node; nodes) node=new TreeNode;

        debug Log(" * Creating patches...");
        patches.length=xPatch;//how many columns will we have? Patch[col][row]
        foreach (size_t row, inout Patch[] strip; patches)
        {
            assert(strip.length==0, "Oups! Redefining a length of a strip");
            strip.length=yPatch; //How many rows?
            foreach (size_t col, inout Patch patch; strip)
                patch=new Patch(cast(ushort)(col*PatchSize),
cast(ushort)(row*PatchSize));
        }
        debug Log("Terrain creation complete");
    }

    public float heightAt(in float x, in float y)
    out(result)
    {
        assert((result>=0.0f)||(result==float.nan));
    }
    body
    {
        if ((x<0.0)||(y<0.0f)) return float.nan;
        //triangulate height at the given point
        return 0.0f;
    }

    ///Makes the terrain ready for rendering. Tesselates all the patches.
    void update()
    body
    {
        //reset the engine first
        currNode=0; //reset current node
        foreach (size_t row, inout Patch[] strip; patches)
            foreach (size_t col, inout Patch patch; strip)
            {
                patch.reset(); //Recalculate the visibility flag
                if (patch.visible) //bail on non-visible patches
                {
                    if (col>0) patch.lNode.lNeigh=patches[col-1][row].rNode;
                    else patch.lNode.lNeigh=null;
                    if (col<(xPatch-1))
patch.rNode.lNeigh=patches[col+1][row].lNode;
                    else patch.rNode.lNeigh=null;
                    if (row>0) patch.lNode.rNeigh=patches[col][row-1].rNode;
                    else patch.lNode.rNeigh=null;
                    if (row<(yPatch-1))
patch.rNode.rNeigh=patches[col][row+1].lNode;
                    else patch.rNode.rNeigh=null;
                }
            }
        //Unfloatize camera information. Remember the case in which the coords
are <0
        foreach (Patch[] strip; patches)
            foreach (Patch patch; strip)
                if (patch.visible) patch.update();
        return;
    }

    ~this()
    {
        /*Since the terrain is the last thing to be deleted when a game is
over, a gc.fullCollect()
        will be preformed sometime soon, so it is not necessary to clean up
manually*/
        foreach (inout stripe; hData)
        {
            stripe.length=0; delete stripe;
        }
        hData.length=0;
        foreach (inout Patch[] strip; patches)
            foreach (inout Patch patch; strip)
                delete patch;
        foreach(inout node; nodes) delete node;
        nodes.length=0;
    }

    private:
    ///Returns the next available TreeNode. Creates a new one if there are no
more available.
    TreeNode newNode()
    out(result)
    {
        assert(result !is null, "newNode() attempted to return a null
TreeNode.");
    }
    body
    {
        assert(currNode<=nodes.length, "currNode advanced too much");
        if (currNode==nodes.length)
        {
            nodes~=new TreeNode; //no more nodes available, create another one
            debug Log("Insufficient TreeNodes for current tesselation
(detail="~strfy(detail)~"). Increasing TreeNode pool
(currNode="~strfy(currNode)~").");
        }
        TreeNode ret=nodes[currNode]; //get a new node
        ret.reset(); //clear new node's affiliations
        currNode++;
        return ret;
    }

    ///To be used by functions of the class, returns raw height value.
    ushort heightAt(ushort x, ushort y)
    {
        if ((x==sizeX)||(y==sizeY)) return 0; //Out of bounds values are not
drawn (taken has void in game)
        assert(x<sizeX, "Out of bounds value requested");
        assert(y<sizeY, "Out of bounds value requested");
        return hData[x][y];
    }

    ushort Calculate(ushort lX, ushort lY, ushort rX, ushort rY, ushort aX,
ushort aY, ushort node)
    {
        //Calculate hipotenuse
        ushort cX=cast(ushort)((rX+lX)/2);
        ushort cY=cast(ushort)((rY+lY)/2);
        ushort cZ=heightAt(cX, cY);
        ushort rZ=heightAt(rX, rY);
        ushort lZ=heightAt(lX, lY);
        ushort errRatio=cast(ushort)abs(cZ-((lZ+rZ)/2)); //this iteraction's
primary error ratio
        if (errRatio==0) return 0; //reached perfectness
        if ((abs((rX-lX))>=2)||(abs(rY-lY))>=2) //smaller than 2x2
        {
            //calculate the error of one of the child triangles
            ushort tempErrRatio=Calculate(aX, aY, lX, lY, cX, cY,
cast(ushort)(node*2));
            if (errRatio<tempErrRatio) errRatio=tempErrRatio; //error is
greater, adopt it
            //and then try the other child
            tempErrRatio=Calculate(rX, rY, aX, aY, cX, cY,
cast(ushort)(node*2+1));
            if (errRatio<tempErrRatio) errRatio=tempErrRatio; //error is
greater, adopt it
        }
        //TODO: this "if" may not need to be here: required for range check,
nothing else
        if (node<MaxDepth) cError[node]=errRatio; //store error ratio as we
descend on the tree
        return errRatio; //return this levels error ratio
    }

    ///Splits a TreeNode, used by Patch sub-class
    void split(inout TreeNode myNode)
    in
    {
                if (myNode.rNeigh !is null)
                assert(myNode.rNeigh.bNeigh !is myNode);
        assert(myNode !is null, "Attempted to split a null node");
    }
    body
    {
        if (myNode.lChild !is null) return; //already split
        //Check to see if current triangle is diamond with bNeigh
        if ((myNode.bNeigh !is null)&&(myNode.bNeigh.bNeigh !is myNode))
        {
            split(myNode.bNeigh); //it has to be split, so that current
triangle can be a diamond with it's bNeigh
            assert(myNode.bNeigh.lChild !is null, "Split failed!");
        }

        //asserts that I am a diamond with my bNeigh, or I'm a border node
        assert ((myNode.bNeigh is null)||(myNode.bNeigh.bNeigh is myNode),
"Attempted to split a node that is not part of a diamond");

        myNode.lChild=newNode();
        myNode.rChild=newNode();
        //with (myNode) //in this block, all the relations of myNode are
updated
        {
            myNode.lChild.bNeigh=myNode.lNeigh;
myNode.lChild.lNeigh=myNode.rChild;
            myNode.rChild.bNeigh=myNode.rNeigh;
myNode.rChild.rNeigh=myNode.lChild;

            if (myNode.lNeigh !is null) //I have a left neigh
            {
                if (myNode.lNeigh.bNeigh is myNode)
myNode.lNeigh.bNeigh=myNode.lChild;
                else
                {
                    if (myNode.lNeigh.lNeigh is myNode)
myNode.lNeigh.lNeigh=myNode.lChild;
                    else if (myNode.lNeigh.rNeigh is myNode)
myNode.lNeigh.rNeigh=myNode.lChild;
                }
            }

            assert((myNode.lChild !is null)&&(myNode.rChild !is null),
"Children died at lNeigh update");

            //The following if block causes the children to become null
            if (myNode.rNeigh !is null) //I have a right neigh
            {
                if (myNode.rNeigh.bNeigh is myNode)
                {
                    assert(myNode.lChild !is null);
                    assert(myNode.rChild !is null);
                    assert(myNode.rNeigh.bNeigh !is null);
                                        assert(myNode.rNeigh.bNeigh !is
myNode);
                        myNode.rNeigh.bNeigh=myNode.rChild; //This causes the
children (both) to die
                                        assert(myNode.lChild !is
myNode.rNeigh.bNeigh);
                    assert(myNode.lChild !is null); //crash here
                    assert(myNode.rChild !is null);
                }
                else
                {
                    if (myNode.rNeigh.rNeigh is myNode)
myNode.rNeigh.rNeigh=myNode.rChild;
                    else if (myNode.rNeigh.lNeigh is myNode)
myNode.rNeigh.lNeigh=myNode.rChild;
                }
            }
            assert((myNode.lChild !is null)&&(myNode.rChild !is null),
"Children died at rNeigh update");

            if (myNode.bNeigh !is null) //I have a base neigh
            {
                if (myNode.bNeigh.lChild !is null) //My bNeigh is already split
                {
                    assert(myNode.bNeigh.rChild !is null, "bNeigh is only
partially split, cannot update relations.");
                    myNode.bNeigh.lChild.rNeigh=myNode.rChild;
                    myNode.bNeigh.rChild.lNeigh=myNode.lChild;
                    myNode.lChild.rNeigh=myNode.bNeigh.rChild;
                    myNode.rChild.lNeigh=myNode.bNeigh.lChild;
                }
                else split(myNode.bNeigh); //Split my base. He'll take care of
Neigh relations
            }
            else //I don't have bNeigh
            {
                myNode.lChild.rNeigh=null;
                myNode.rChild.lNeigh=null;
            }
            assert((myNode.lChild !is null)&&(myNode.rChild !is null),
"Children died at bNeigh update");
        } //updates to myNode are over
        assert((myNode.lChild !is null)&&(myNode.rChild !is null), "Node
children have been lost!");
        return;
    }

    void tesselate (TreeNode myNode, ushort lX, ushort lY, ushort rX, ushort
rY, ushort aX, ushort aY, ushort node)
    in
    {
                if (myNode.rNeigh !is null)
                assert(myNode.rNeigh.bNeigh !is myNode);
        assert(myNode !is null, "Attempted to tesselate a null node");
    }
    body
    {
        //Calculate hipotenuse
        ushort cX=cast(ushort)((rX+lX)/2);
        ushort cY=cast(ushort)((rY+lY)/2);
        ushort cZ=heightAt(cX, cY);
        ulong
distance=(cX-camInfo.x)*(cX-camInfo.x)+(cY-camInfo.y)*(cY-camInfo.y)+(cZ-camInfo.z)*(cZ-camInfo.z);
        ulong threshold=cError[node]*cError[node];

        if ((threshold*detail)>distance) //should this triangle be split?
        {
            split(myNode); //won't fail because TreeNodes will not go out
            //I have children and they aren't too small,
            //TODO: Unnecessary check: split never fails: (myNode.lChild !is
null)&&(myNode.rChild !is null)&&
            if
((node<cast(ushort)(MaxDepth/2))&&((abs(lX-rX)>=2)||(abs(lY-rY)>=2)))
            {
                tesselate(myNode.lChild, aX, aY, lX, lY, cX, cY,
cast(ushort)(node*2));
                tesselate(myNode.rChild, rX, rY, aX, aY, cX, cY,
cast(ushort)(node*2+1));
            }
        }
    }

    const ushort maxH;
    float detail; ///Detail Threshold
    TreeNode nodes[]; ///Node pool
    size_t currNode; ///Current node being assigned
    const ushort sizeX, sizeY;
    const ubyte xPatch, yPatch;
    ushort hData[][]; ///The Height Data
    static ushort[MaxDepth] cError; //error buffer
    const ubyte HMult; ///Height multiplier, determines the resolution of the
terrain
    const Patch patches[][]; ///The pathes that compose the terrain.
}

void main()
{
        File este=new File("novomapa.raw", FileMode.In);
        Terrain terrain=new Terrain(este, 2500);
        delete este;
        std.stdio.writefln("Preparing for rendering...");
        terrain.update();

        delete terrain;
}


-- 



More information about the Digitalmars-d-bugs mailing list