[Issue 1190] New: Reference becoming null
d-bugmail at puremagic.com
d-bugmail at puremagic.com
Wed Apr 25 06:38:50 PDT 2007
http://d.puremagic.com/issues/show_bug.cgi?id=1190
Summary: Reference becoming null
Product: D
Version: 1.013
Platform: PC
OS/Version: All
Status: NEW
Keywords: wrong-code
Severity: blocker
Priority: P2
Component: DMD
AssignedTo: bugzilla at digitalmars.com
ReportedBy: arkangath at gmail.com
The following code is part of a LOD terrain engine I'm coding and the code
fails on a split operation, where references to Nodes are updated (copied) to
generate child nodes. The code has been ported from C++ (I can post it as well)
and all lines has been checked and re-checked for errors on the implementation.
The code has been striped of all unnecessary methods to improve readability and
reduce dependencies. When compiled with "dmd -g -debug teste.d" the program
fails to generate a viable mesh, crashing on an assert. This assert can be
removed and the code would generate a mesh, but with null Nodes, if could not
be rendered (Access Violation if attempted).
The assert occurs in both Windows and Linux, the only platforms available for
testing.
The line in which the assert fails is 307, when the current node's right
neighbor's base neighbor is set to current node's right child node. Before that
line, both are asserted as NOT being null. I tried to analyze the disassembled
code, but I couldn't find the source of the error.
------------------ teste.d -------------------------
///Contains all declarations concerning the terrain engine
import std.string;
import std.stream;
import std.math;
alias std.string.toString strfy;
alias std.stdio.writefln Log;
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
{
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);
myNode.rNeigh.bNeigh=myNode.rChild; //This causes the
children (both) to die
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
{
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