added everything

This commit is contained in:
Metario
2017-04-17 06:17:10 -06:00
commit 9c6ff74f19
6121 changed files with 1625704 additions and 0 deletions

165
engine/core/bitMatrix.h Executable file
View File

@ -0,0 +1,165 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BITMATRIX_H_
#define _BITMATRIX_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _BITVECTOR_H_
#include "core/bitVector.h"
#endif
/// A matrix of bits.
///
/// This class manages an array of bits. There are no limitations on the
/// size of the bit matrix (beyond available memory).
///
/// @note This class is currently unused.
class BitMatrix
{
U32 mWidth;
U32 mHeight;
U32 mRowByteWidth;
U8* mBits;
U32 mSize;
BitVector mColFlags;
BitVector mRowFlags;
public:
/// Create a new bit matrix.
///
/// @param width Width of matrix in bits.
/// @param height Height of matrix in bits.
BitMatrix(const U32 width, const U32 height);
~BitMatrix();
/// @name Setters
/// @{
/// Set all the bits in the matrix to false.
void clearAllBits();
/// Set all the bits in the matrix to true.
void setAllBits();
/// Set a bit at a given location in the matrix.
void setBit(const U32 x, const U32 y);
/// Clear a bit at a given location in the matrix.
void clearBit(const U32 x, const U32 y);
/// @}
/// @name Queries
/// @{
/// Is the specified bit set?
bool isSet(const U32 x, const U32 y) const;
/// Is any bit in the given column set?
bool isAnySetCol(const U32 x);
/// Is any bit in the given row set?
bool isAnySetRow(const U32 y);
/// @}
};
inline BitMatrix::BitMatrix(const U32 width, const U32 height)
: mColFlags(width),
mRowFlags(height)
{
AssertFatal(width != 0 && height != 0, "Error, w/h must be non-zero");
mWidth = width;
mHeight = height;
mRowByteWidth = (width + 7) >> 3;
mSize = mRowByteWidth * mHeight;
mBits = new U8[mSize];
}
inline BitMatrix::~BitMatrix()
{
mWidth = 0;
mHeight = 0;
mRowByteWidth = 0;
mSize = 0;
delete [] mBits;
mBits = NULL;
}
inline void BitMatrix::clearAllBits()
{
AssertFatal(mBits != NULL, "Error, clearing after deletion");
dMemset(mBits, 0x00, mSize);
mColFlags.clear();
mRowFlags.clear();
}
inline void BitMatrix::setAllBits()
{
AssertFatal(mBits != NULL, "Error, setting after deletion");
dMemset(mBits, 0xFF, mSize);
mColFlags.set();
mRowFlags.set();
}
inline void BitMatrix::setBit(const U32 x, const U32 y)
{
AssertFatal(x < mWidth && y < mHeight, "Error, out of bounds bit!");
U8* pRow = &mBits[y * mRowByteWidth];
U8* pByte = &pRow[x >> 3];
*pByte |= 1 << (x & 0x7);
mColFlags.set(x);
mRowFlags.set(y);
}
inline void BitMatrix::clearBit(const U32 x, const U32 y)
{
AssertFatal(x < mWidth && y < mHeight, "Error, out of bounds bit!");
U8* pRow = &mBits[y * mRowByteWidth];
U8* pByte = &pRow[x >> 3];
*pByte &= ~(1 << (x & 0x7));
}
inline bool BitMatrix::isSet(const U32 x, const U32 y) const
{
AssertFatal(x < mWidth && y < mHeight, "Error, out of bounds bit!");
U8* pRow = &mBits[y * mRowByteWidth];
U8* pByte = &pRow[x >> 3];
return (*pByte & (1 << (x & 0x7))) != 0;
}
inline bool BitMatrix::isAnySetCol(const U32 x)
{
AssertFatal(x < mWidth, "Error, out of bounds column!");
return mColFlags.test(x);
}
inline bool BitMatrix::isAnySetRow(const U32 y)
{
AssertFatal(y < mHeight, "Error, out of bounds row!");
return mRowFlags.test(y);
}
#endif // _H_BITMATRIX_

986
engine/core/bitRender.cc Executable file
View File

@ -0,0 +1,986 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/bitRender.h"
U32 openTable[32] = { 0xFFFFFFFF,0xFFFFFFFE,0xFFFFFFFC,0xFFFFFFF8,
0xFFFFFFF0,0xFFFFFFE0,0xFFFFFFC0,0xFFFFFF80,
0xFFFFFF00,0xFFFFFE00,0xFFFFFC00,0xFFFFF800,
0xFFFFF000,0xFFFFE000,0xFFFFC000,0xFFFF8000,
0xFFFF0000,0xFFFE0000,0xFFFC0000,0xFFF80000,
0xFFF00000,0xFFE00000,0xFFC00000,0xFF800000,
0xFF000000,0xFE000000,0xFC000000,0xF8000000,
0xF0000000,0xE0000000,0xC0000000,0x80000000 };
U32 closeTable[32] = { 0x00000001,0x00000003,0x00000007,0x0000000F,
0x0000001F,0x0000003F,0x0000007F,0x000000FF,
0x000001FF,0x000003FF,0x000007FF,0x00000FFF,
0x00001FFF,0x00003FFF,0x00007FFF,0x0000FFFF,
0x0001FFFF,0x0003FFFF,0x0007FFFF,0x000FFFFF,
0x001FFFFF,0x003FFFFF,0x007FFFFF,0x00FFFFFF,
0x01FFFFFF,0x03FFFFFF,0x07FFFFFF,0x0FFFFFFF,
0x1FFFFFFF,0x3FFFFFFF,0x7FFFFFFF,0xFFFFFFFF };
struct DrawStruct
{
S16 start;
S16 num;
};
void BitRender::render_strips(const U8 * draw, S32 numDraw, S32 szDraw, const U16 * indices, const Point2I * points, S32 dim, U32 * bits)
{
const U8 * drawEnd = draw + numDraw*szDraw;
// loop through strips...
for (; draw<drawEnd; draw += szDraw)
{
const DrawStruct * drawStruct = (DrawStruct*)draw;
const U16 * icurrent = indices + drawStruct->start;
const U16 * iend = icurrent + drawStruct->num;
const Point2I * vv0 = points + *(icurrent++);
const Point2I * vv1;
const Point2I * vv2 = points + *(icurrent++);
const Point2I **nextPt = &vv1;
while (icurrent<iend)
{
*nextPt = vv2;
nextPt = (const Point2I**)( (dsize_t)nextPt ^ (dsize_t)&vv0 ^ (dsize_t)&vv1 );
vv2 = points + *(icurrent++);
// skip degenerate triangles...
if (vv0==vv1 || vv1==vv2 || vv2==vv0)
continue;
// following copied from BitRender::render...indented to highlight this fact, no function to indentation
{
const Point2I * v0 = vv0;
const Point2I * v1 = vv1;
const Point2I * v2 = vv2;
AssertFatal( v0->x>=0 && v0->x<dim && v0->y>=0 && v0->y<dim,"BitRender::render: v0 out of range");
AssertFatal( v1->x>=0 && v1->x<dim && v1->y>=0 && v1->y<dim,"BitRender::render: v1 out of range");
AssertFatal( v2->x>=0 && v2->x<dim && v2->y>=0 && v2->y<dim,"BitRender::render: v2 out of range");
// back-face cull
if ((v0->x-v1->x)*(v2->y-v1->y) > (v0->y-v1->y)*(v2->x-v1->x))
continue;
// rotate so that v0 holds topmost y coord
// Note: The particular inequalities used ( <=, <, then <=) are important.
// They guarantee that if there are two verts on the top row, that
// they will be vert v0 and v1 (also could be three).
if (v1->y <= v2->y)
{
if (v1->y < v0->y)
{
const Point2I * tmp = v0;
v0 = v1;
v1 = v2;
v2 = tmp;
}
}
else
{
if (v2->y <= v0->y)
{
const Point2I * tmp = v0;
v0 = v2;
v2 = v1;
v1 = tmp;
}
}
// all the control variables we have to set up...
S32 leftDeltaY, xLeftInc, xLeftErrInc, xLeftDir, xLeft, xLeftErr = 0;
S32 rightDeltaY, xRightInc, xRightErrInc, xRightDir, xRight, xRightErr = 0;
S32 * changeThisDelta;
S32 * changeThisInc;
S32 * changeThisErrInc;
S32 * changeThisDir;
S32 toThisDelta;
S32 toThisInc;
S32 toThisErrInc;
S32 toThisDir;
S32 ySwitch;
S32 yBottom;
// check for special case where top row holds two verts
if (v0->y==v1->y)
{
leftDeltaY = v2->y-v0->y;
rightDeltaY = v2->y-v1->y;
ySwitch = v2->y;
yBottom = v2->y;
if (v1->y==v2->y)
{
// special-special case where top row holds all three verts
xLeft = getMin(getMin(v0->x,v1->x),v2->x);
xRight = getMax(getMax(v0->x,v1->x),v2->x);
xLeftInc = xRightInc = 0;
xLeftErrInc = xRightErrInc = 0;
xLeftDir = xRightDir = 1;
}
else
{
// standard-special case...v2 on different row from v0 and v1
xLeft = v0->x;
xLeftInc = (v2->x-v0->x) / leftDeltaY;
xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc;
xLeftDir = v2->x-v0->x > 0 ? 1 : -1;
xRight = v1->x;
xRightInc = (v2->x-v1->x) / rightDeltaY;
xRightErrInc = (v2->x-v1->x) - rightDeltaY*xRightInc;
xRightDir = v2->x-v1->x > 0 ? 1 : -1;
}
// set these variables to avoid a crash...
changeThisDelta = &toThisDelta;
changeThisInc = &toThisInc;
changeThisErrInc = &toThisErrInc;
changeThisDir = &toThisDir;
}
else
{
leftDeltaY = v2->y-v0->y;
xLeftInc = (v2->x-v0->x) / leftDeltaY;
xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc;
xLeftDir = v2->x-v0->x > 0 ? 1 : -1;
rightDeltaY = v1->y-v0->y;
xRightInc = (v1->x-v0->x) / rightDeltaY;
xRightErrInc = (v1->x-v0->x) - rightDeltaY*xRightInc;
xRightDir = v1->x-v0->x > 0 ? 1 : -1;
xLeft = xRight = v0->x;
if (v1->y<v2->y)
{
// right edge bends
changeThisDelta = &rightDeltaY;
changeThisInc = &xRightInc;
changeThisErrInc = &xRightErrInc;
changeThisDir = &xRightDir;
toThisDelta = v2->y-v1->y;
toThisInc = (v2->x-v1->x) / toThisDelta;
toThisErrInc = (v2->x-v1->x) - toThisDelta * toThisInc;
toThisDir = v2->x-v1->x > 0 ? 1 : -1;
ySwitch = v1->y-1;
yBottom = v2->y;
}
else
{
// left edge bends
changeThisDelta = &leftDeltaY;
changeThisInc = &xLeftInc;
changeThisErrInc = &xLeftErrInc;
changeThisDir = &xLeftDir;
toThisDelta = v1->y-v2->y;
toThisInc = toThisDelta ? (v1->x-v2->x) / toThisDelta : 0;
toThisErrInc = toThisDelta ? (v1->x-v2->x) - toThisDelta * toThisInc : 0;
toThisDir = v1->x-v2->x > 0 ? 1 : -1;
ySwitch = v2->y-1;
yBottom = v1->y;
}
}
xLeftErrInc *= xLeftDir;
xRightErrInc *= xRightDir;
toThisErrInc *= toThisDir;
U32 * rowStart = bits + v0->y * (dim>>5);
for (S32 y=v0->y; y<=yBottom;)
{
do
{
AssertFatal(xLeft<=xRight,"BitRender::render");
U32 open = openTable[xLeft&31];
U32 close = closeTable[xRight&31];
if ( (xLeft^xRight) & ~31)
{
U32 * x = rowStart+(xLeft>>5);
*x |= open;
U32 * xEnd = rowStart+(xRight>>5);
while (++x<xEnd)
*x |= 0xFFFFFFFF;
*x |= close;
}
else
rowStart[xLeft>>5] |= open & close;
xLeft += xLeftInc;
xLeftErr += xLeftErrInc;
if (xLeftErr >= leftDeltaY)
{
xLeft += xLeftDir;
xLeftErr -= leftDeltaY;
}
xRight += xRightInc;
xRightErr += xRightErrInc;
if (xRightErr >= rightDeltaY)
{
xRight += xRightDir;
xRightErr -= rightDeltaY;
}
rowStart += dim>>5;
} while (y++<ySwitch);
ySwitch=yBottom; // get here at most twice
*changeThisDelta = toThisDelta;
*changeThisInc = toThisInc;
*changeThisErrInc = toThisErrInc;
*changeThisDir = toThisDir;
}
}
}
}
}
void BitRender::render_tris(const U8 * draw, S32 numDraw, S32 szDraw, const U16 * indices, const Point2I * points, S32 dim, U32 * bits)
{
const U8 * drawEnd = draw + numDraw*szDraw;
// loop through strips...
for (; draw<drawEnd; draw += szDraw)
{
const DrawStruct * drawStruct = (DrawStruct*)draw;
const U16 * icurrent = indices + drawStruct->start;
const U16 * iend = icurrent + drawStruct->num;
while (icurrent<iend)
{
const Point2I * v0 = points + *(icurrent++);
const Point2I * v1 = points + *(icurrent++);
const Point2I * v2 = points + *(icurrent++);
// following copied from BitRender::render...indented to highlight this fact, no function to indentation
{
AssertFatal( v0->x>=0 && v0->x<dim && v0->y>=0 && v0->y<dim,"BitRender::render: v0 out of range");
AssertFatal( v1->x>=0 && v1->x<dim && v1->y>=0 && v1->y<dim,"BitRender::render: v1 out of range");
AssertFatal( v2->x>=0 && v2->x<dim && v2->y>=0 && v2->y<dim,"BitRender::render: v2 out of range");
// back-face cull
if ((v0->x-v1->x)*(v2->y-v1->y) > (v0->y-v1->y)*(v2->x-v1->x))
continue;
// rotate so that v0 holds topmost y coord
// Note: The particular inequalities used ( <=, <, then <=) are important.
// They guarantee that if there are two verts on the top row, that
// they will be vert v0 and v1 (also could be three).
if (v1->y <= v2->y)
{
if (v1->y < v0->y)
{
const Point2I * tmp = v0;
v0 = v1;
v1 = v2;
v2 = tmp;
}
}
else
{
if (v2->y <= v0->y)
{
const Point2I * tmp = v0;
v0 = v2;
v2 = v1;
v1 = tmp;
}
}
// all the control variables we have to set up...
S32 leftDeltaY, xLeftInc, xLeftErrInc, xLeftDir, xLeft, xLeftErr = 0;
S32 rightDeltaY, xRightInc, xRightErrInc, xRightDir, xRight, xRightErr = 0;
S32 * changeThisDelta;
S32 * changeThisInc;
S32 * changeThisErrInc;
S32 * changeThisDir;
S32 toThisDelta;
S32 toThisInc;
S32 toThisErrInc;
S32 toThisDir;
S32 ySwitch;
S32 yBottom;
// check for special case where top row holds two verts
if (v0->y==v1->y)
{
leftDeltaY = v2->y-v0->y;
rightDeltaY = v2->y-v1->y;
ySwitch = v2->y;
yBottom = v2->y;
if (v1->y==v2->y)
{
// special-special case where top row holds all three verts
xLeft = getMin(getMin(v0->x,v1->x),v2->x);
xRight = getMax(getMax(v0->x,v1->x),v2->x);
xLeftInc = xRightInc = 0;
xLeftErrInc = xRightErrInc = 0;
xLeftDir = xRightDir = 1;
}
else
{
// standard-special case...v2 on different row from v0 and v1
xLeft = v0->x;
xLeftInc = (v2->x-v0->x) / leftDeltaY;
xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc;
xLeftDir = v2->x-v0->x > 0 ? 1 : -1;
xRight = v1->x;
xRightInc = (v2->x-v1->x) / rightDeltaY;
xRightErrInc = (v2->x-v1->x) - rightDeltaY*xRightInc;
xRightDir = v2->x-v1->x > 0 ? 1 : -1;
}
// set these variables to avoid a crash...
changeThisDelta = &toThisDelta;
changeThisInc = &toThisInc;
changeThisErrInc = &toThisErrInc;
changeThisDir = &toThisDir;
}
else
{
leftDeltaY = v2->y-v0->y;
xLeftInc = (v2->x-v0->x) / leftDeltaY;
xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc;
xLeftDir = v2->x-v0->x > 0 ? 1 : -1;
rightDeltaY = v1->y-v0->y;
xRightInc = (v1->x-v0->x) / rightDeltaY;
xRightErrInc = (v1->x-v0->x) - rightDeltaY*xRightInc;
xRightDir = v1->x-v0->x > 0 ? 1 : -1;
xLeft = xRight = v0->x;
if (v1->y<v2->y)
{
// right edge bends
changeThisDelta = &rightDeltaY;
changeThisInc = &xRightInc;
changeThisErrInc = &xRightErrInc;
changeThisDir = &xRightDir;
toThisDelta = v2->y-v1->y;
toThisInc = (v2->x-v1->x) / toThisDelta;
toThisErrInc = (v2->x-v1->x) - toThisDelta * toThisInc;
toThisDir = v2->x-v1->x > 0 ? 1 : -1;
ySwitch = v1->y-1;
yBottom = v2->y;
}
else
{
// left edge bends
changeThisDelta = &leftDeltaY;
changeThisInc = &xLeftInc;
changeThisErrInc = &xLeftErrInc;
changeThisDir = &xLeftDir;
toThisDelta = v1->y-v2->y;
toThisInc = toThisDelta ? (v1->x-v2->x) / toThisDelta : 0;
toThisErrInc = toThisDelta ? (v1->x-v2->x) - toThisDelta * toThisInc : 0;
toThisDir = v1->x-v2->x > 0 ? 1 : -1;
ySwitch = v2->y-1;
yBottom = v1->y;
}
}
xLeftErrInc *= xLeftDir;
xRightErrInc *= xRightDir;
toThisErrInc *= toThisDir;
U32 * rowStart = bits + v0->y * (dim>>5);
for (S32 y=v0->y; y<=yBottom;)
{
do
{
AssertFatal(xLeft<=xRight,"BitRender::render");
U32 open = openTable[xLeft&31];
U32 close = closeTable[xRight&31];
if ( (xLeft^xRight) & ~31)
{
U32 * x = rowStart+(xLeft>>5);
*x |= open;
U32 * xEnd = rowStart+(xRight>>5);
while (++x<xEnd)
*x |= 0xFFFFFFFF;
*x |= close;
}
else
rowStart[xLeft>>5] |= open & close;
xLeft += xLeftInc;
xLeftErr += xLeftErrInc;
if (xLeftErr >= leftDeltaY)
{
xLeft += xLeftDir;
xLeftErr -= leftDeltaY;
}
xRight += xRightInc;
xRightErr += xRightErrInc;
if (xRightErr >= rightDeltaY)
{
xRight += xRightDir;
xRightErr -= rightDeltaY;
}
rowStart += dim>>5;
} while (y++<ySwitch);
ySwitch=yBottom; // get here at most twice
*changeThisDelta = toThisDelta;
*changeThisInc = toThisInc;
*changeThisErrInc = toThisErrInc;
*changeThisDir = toThisDir;
}
}
}
}
}
// assumes coords are in range
void BitRender::render(const Point2I * v0, const Point2I * v1, const Point2I * v2, S32 dim, U32 * bits)
{
AssertFatal( v0->x>=0 && v0->x<dim && v0->y>=0 && v0->y<dim,"BitRender::render: v0 out of range");
AssertFatal( v1->x>=0 && v1->x<dim && v1->y>=0 && v1->y<dim,"BitRender::render: v1 out of range");
AssertFatal( v2->x>=0 && v2->x<dim && v2->y>=0 && v2->y<dim,"BitRender::render: v2 out of range");
// back-face cull
if ((v0->x-v1->x)*(v2->y-v1->y) > (v0->y-v1->y)*(v2->x-v1->x))
return;
// rotate so that v0 holds topmost y coord
// Note: The particular inequalities used ( <=, <, then <=) are important.
// They guarantee that if there are two verts on the top row, that
// they will be vert v0 and v1 (also could be three).
if (v1->y <= v2->y)
{
if (v1->y < v0->y)
{
const Point2I * tmp = v0;
v0 = v1;
v1 = v2;
v2 = tmp;
}
}
else
{
if (v2->y <= v0->y)
{
const Point2I * tmp = v0;
v0 = v2;
v2 = v1;
v1 = tmp;
}
}
// all the control variables we have to set up...
S32 leftDeltaY, xLeftInc, xLeftErrInc, xLeftDir, xLeft, xLeftErr = 0;
S32 rightDeltaY, xRightInc, xRightErrInc, xRightDir, xRight, xRightErr = 0;
S32 * changeThisDelta;
S32 * changeThisInc;
S32 * changeThisErrInc;
S32 * changeThisDir;
S32 toThisDelta;
S32 toThisInc;
S32 toThisErrInc;
S32 toThisDir;
S32 ySwitch;
S32 yBottom;
// check for special case where top row holds two verts
if (v0->y==v1->y)
{
leftDeltaY = v2->y-v0->y;
rightDeltaY = v2->y-v1->y;
ySwitch = v2->y;
yBottom = v2->y;
if (v1->y==v2->y)
{
// special-special case where top row holds all three verts
xLeft = getMin(getMin(v0->x,v1->x),v2->x);
xRight = getMax(getMax(v0->x,v1->x),v2->x);
xLeftErrInc = xRightErrInc = 0;
}
else
{
// standard-special case...v2 on different row from v0 and v1
xLeft = v0->x;
xLeftInc = (v2->x-v0->x) / leftDeltaY;
xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc;
xLeftDir = v2->x-v0->x > 0 ? 1 : -1;
xRight = v1->x;
xRightInc = (v2->x-v1->x) / rightDeltaY;
xRightErrInc = (v2->x-v1->x) - rightDeltaY*xRightInc;
xRightDir = v2->x-v1->x > 0 ? 1 : -1;
}
// set these variables to avoid a crash...
changeThisDelta = &toThisDelta;
changeThisInc = &toThisInc;
changeThisErrInc = &toThisErrInc;
changeThisDir = &toThisDir;
}
else
{
leftDeltaY = v2->y-v0->y;
xLeftInc = (v2->x-v0->x) / leftDeltaY;
xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc;
xLeftDir = v2->x-v0->x > 0 ? 1 : -1;
rightDeltaY = v1->y-v0->y;
xRightInc = (v1->x-v0->x) / rightDeltaY;
xRightErrInc = (v1->x-v0->x) - rightDeltaY*xRightInc;
xRightDir = v1->x-v0->x > 0 ? 1 : -1;
xLeft = xRight = v0->x;
if (v1->y<v2->y)
{
// right edge bends
changeThisDelta = &rightDeltaY;
changeThisInc = &xRightInc;
changeThisErrInc = &xRightErrInc;
changeThisDir = &xRightDir;
toThisDelta = v2->y-v1->y;
toThisInc = (v2->x-v1->x) / toThisDelta;
toThisErrInc = (v2->x-v1->x) - toThisDelta * toThisInc;
toThisDir = v2->x-v1->x > 0 ? 1 : -1;
ySwitch = v1->y-1;
yBottom = v2->y;
}
else
{
// left edge bends
changeThisDelta = &leftDeltaY;
changeThisInc = &xLeftInc;
changeThisErrInc = &xLeftErrInc;
changeThisDir = &xLeftDir;
toThisDelta = v1->y-v2->y;
toThisInc = toThisDelta ? (v1->x-v2->x) / toThisDelta : 0;
toThisErrInc = toThisDelta ? (v1->x-v2->x) - toThisDelta * toThisInc : 0;
toThisDir = v1->x-v2->x > 0 ? 1 : -1;
ySwitch = v2->y-1;
yBottom = v1->y;
}
}
xLeftErrInc *= xLeftDir;
xRightErrInc *= xRightDir;
toThisErrInc *= toThisDir;
U32 * rowStart = bits + v0->y * (dim>>5);
for (S32 y=v0->y; y<=yBottom;)
{
do
{
AssertFatal(xLeft<=xRight,"BitRender::render");
U32 open = openTable[xLeft&31];
U32 close = closeTable[xRight&31];
if ( (xLeft^xRight) & ~31)
{
U32 * x = rowStart+(xLeft>>5);
*x |= open;
U32 * xEnd = rowStart+(xRight>>5);
while (++x<xEnd)
*x |= 0xFFFFFFFF;
*x |= close;
}
else
rowStart[xLeft>>5] |= open & close;
xLeft += xLeftInc;
xLeftErr += xLeftErrInc;
if (xLeftErr >= leftDeltaY)
{
xLeft += xLeftDir;
xLeftErr -= leftDeltaY;
}
xRight += xRightInc;
xRightErr += xRightErrInc;
if (xRightErr >= rightDeltaY)
{
xRight += xRightDir;
xRightErr -= rightDeltaY;
}
rowStart += dim>>5;
} while (y++<ySwitch);
ySwitch=yBottom; // get here at most twice
*changeThisDelta = toThisDelta;
*changeThisInc = toThisInc;
*changeThisErrInc = toThisErrInc;
*changeThisDir = toThisDir;
}
}
// These macros currently define how black the shadows get
// Set the shift factor to zero results in totally black
// shadows. Be nice to have this dynamic...
#define SF(x) (x)
#define SF32(a,b,c,d) {SF(a),SF(b),SF(c),SF(d)}
/*
// endian-ordering version of the SF macro, for the blur methods.
#if PLATFORM_LITTLE_ENDIAN
#define SF32E(a,b,c,d) SF32(a,b,c,d)
#else
#define SF32E(a,b,c,d) SF32(d,c,b,a)
#endif
*/
U8 bitTable[16][4] =
{
SF32( 0, 0, 0, 0), // 0
SF32(255, 0, 0, 0), // 1
SF32( 0,255, 0, 0), // 2
SF32(255,255, 0, 0), // 3
SF32( 0, 0,255, 0), // 4
SF32(255, 0,255, 0), // 5
SF32( 0,255,255, 0), // 6
SF32(255,255,255, 0), // 7
SF32( 0, 0, 0,255), // 8
SF32(255, 0, 0,255), // 9
SF32( 0,255, 0,255), // 10
SF32(255,255, 0,255), // 11
SF32( 0, 0,255,255), // 12
SF32(255, 0,255,255), // 13
SF32( 0,255,255,255), // 14
SF32(255,255,255,255), // 15
};
void BitRender::bitTo8Bit(U32 * bits, U32 * eightBits, S32 dim)
{
dim *= dim>>5;
for (S32 i=0; i<dim; i++)
{
U32 val = *bits++;
*eightBits++ = *(U32*)&bitTable[val&0xF];
val >>= 4;
*eightBits++ = *(U32*)&bitTable[val&0xF];
val >>= 4;
*eightBits++ = *(U32*)&bitTable[val&0xF];
val >>= 4;
*eightBits++ = *(U32*)&bitTable[val&0xF];
val >>= 4;
*eightBits++ = *(U32*)&bitTable[val&0xF];
val >>= 4;
*eightBits++ = *(U32*)&bitTable[val&0xF];
val >>= 4;
*eightBits++ = *(U32*)&bitTable[val&0xF];
val >>= 4;
*eightBits++ = *(U32*)&bitTable[val&0xF];
val >>= 4;
}
}
U8 bitTableA[16][4] =
{
SF32( 0, 0, 0, 0), // 0
SF32( 0, 0, 0, 0), // 1
SF32( 0, 0, 0, 0), // 2
SF32( 0, 0, 0, 0), // 3
SF32( 0, 0, 0, 0), // 4
SF32( 0, 0, 0, 0), // 5
SF32( 0, 0, 0, 0), // 6
SF32( 0, 0, 0, 0), // 7
SF32( 17, 0, 0, 0), // 8
SF32( 17, 0, 0, 0), // 9
SF32( 17, 0, 0, 0), // 10
SF32( 17, 0, 0, 0), // 11
SF32( 17, 0, 0, 0), // 12
SF32( 17, 0, 0, 0), // 13
SF32( 17, 0, 0, 0), // 14
SF32( 17, 0, 0, 0), // 15
};
U8 bitTableB[16][4] =
{
SF32( 0, 0, 0, 0), // 0
SF32( 34, 17, 0, 0), // 1
SF32( 17, 34, 17, 0), // 2
SF32( 51, 51, 17, 0), // 3
SF32( 0, 17, 34, 17), // 4
SF32( 34, 34, 34, 17), // 5
SF32( 17, 51, 51, 17), // 6
SF32( 51, 68, 51, 17), // 7
SF32( 0, 0, 17, 34), // 8
SF32( 34, 17, 17, 34), // 9
SF32( 17, 34, 34, 34), // 10
SF32( 51, 51, 34, 34), // 11
SF32( 0, 17, 51, 51), // 12
SF32( 34, 34, 51, 51), // 13
SF32( 17, 51, 68, 51), // 14
SF32( 51, 68, 68, 51), // 15
};
U8 bitTableC[16][4] =
{
SF32( 0, 0, 0, 0), // 0
SF32( 0, 0, 0, 17), // 1
SF32( 0, 0, 0, 0), // 2
SF32( 0, 0, 0, 17), // 3
SF32( 0, 0, 0, 0), // 4
SF32( 0, 0, 0, 17), // 5
SF32( 0, 0, 0, 0), // 6
SF32( 0, 0, 0, 17), // 7
SF32( 0, 0, 0, 0), // 8
SF32( 0, 0, 0, 17), // 9
SF32( 0, 0, 0, 0), // 10
SF32( 0, 0, 0, 17), // 11
SF32( 0, 0, 0, 0), // 12
SF32( 0, 0, 0, 17), // 13
SF32( 0, 0, 0, 0), // 14
SF32( 0, 0, 0, 17), // 15
};
U8 bitTableE[16][4] =
{
SF32( 0, 0, 0, 0), // 0
SF32( 51, 34, 0, 0), // 1
SF32( 34, 51, 34, 0), // 2
SF32( 85, 85, 34, 0), // 3
SF32( 0, 34, 51, 34), // 4
SF32( 51, 68, 51, 34), // 5
SF32( 34, 85, 85, 34), // 6
SF32( 85,119, 85, 34), // 7
SF32( 0, 0, 34, 51), // 8
SF32( 51, 34, 34, 51), // 9
SF32( 34, 51, 68, 51), // 10
SF32( 85, 85, 68, 51), // 11
SF32( 0, 34, 85, 85), // 12
SF32( 51, 68, 85, 85), // 13
SF32( 34, 85,119, 85), // 14
SF32( 85,119,119, 85), // 15
};
void BitRender::bitTo8Bit_3(U32 * bits, U32 * eightBits, S32 dim)
{
#if defined(TORQUE_BIG_ENDIAN)
#define MAX_SHADOW_TEXELS (256 + 4) //256 seems big enough, +4 so we can run off end of buffer.
// slow fake gaussian
int i, j, c;
U8 tmpLine[3][MAX_SHADOW_TEXELS];
U8 *src0, *src1, *src2, *srcTmp;
U8 *s0, *s1, *s2;
U32 dimS2 = dim>>2;
U32 *src32;
U32 *currLine = bits;
U32 currVal;
U32 sampleVal;
U8 c00, c01, c02, c10, c11, c12, c20, c21, c22;
int openBuf;
src0 = tmpLine[0];
src1 = tmpLine[1];
src2 = tmpLine[2];
openBuf = 2; // the one src2 is using right now.
// pre-process two rows into our tmp buffers.
src32 = (U32*)(src0);
for(i=0; i<(dimS2>>3); i++) // walk 4 bytes at a time, 4 bits at a time.
{
currVal = *currLine++;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[currVal];
}
src32 = (U32*)(src1);
for(i=0; i<(dimS2>>3); i++)
{
currVal = *currLine++;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[currVal];
}
// pre-clear first row of depth buffer.
for (i=0; i<dimS2; i++)
*eightBits++ = 0L;
// start.
j = dim - 2; // we pre-process two lines, and only really do the internal -2 rows
while (j>0)
{
j--;
// process new line (currLine) into new tmp buffer (src2)
src32 = (U32*)(src2);
for(i=0; i<(dimS2>>3); i++) // 8 at a time.
{
currVal = *currLine++;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4;
*src32++ = *(U32*)&bitTable[currVal];
}
// blur & copy current src1 to current dest
#if NO_BLUR
// test for basic functionality.
srcTmp = src1;
src32 = (U32*)srcTmp;
for (i=0; i<dimS2; i++)
*eightBits++ = *src32++;
#else
s0 = src0;
s1 = src1;
s2 = src2;
// c#0 will be copied in at end of loop...
c00 = 0; c01 = *s0++;
c10 = 0; c11 = *s1++;
c20 = 0; c21 = *s2++;
for (i=0; i<dimS2; i++) // process samples in 32bit inc.
{
currVal = 0L;
for (c=0; c<4; c++)
{
// update right-edge samples -- for very last pixel this reads off end of real data.
c02 = *s0++;
c12 = *s1++;
c22 = *s2++;
// process sample i*4+c
if (!i && !c) // very first pixel
sampleVal = 0; //c11; // take mid as it's the right thing.
else
if (i==(dimS2-1) && c==3) // very last pixel
sampleVal = 0; //c12; // take end.
else // inner pixel
{
const int wXP = 3; // corners
const int wPP = 4; // plus/NSEW
const int wCP = 4; // center
sampleVal = (c00+c02+c20+c22)*wXP + (c01+c10+c12+c21)*wPP + c11*wCP;
sampleVal >>= 5; // div by 32 subsamples
}
// mix into val store to hold.
currVal |= sampleVal << (8*(3-c));
c00 = c01; c01 = c02;
c10 = c11; c11 = c12;
c20 = c21; c21 = c22;
//c#2 defd next time round
}
// put samples into dest buffer.
*eightBits++ = currVal;
}
#endif
// flip around ptrs for next row processing.
openBuf++;
if (openBuf>2)
openBuf = 0;
src0 = src1;
src1 = src2;
src2 = tmpLine[openBuf];
}
// clear last dest buffer row
for (i=0; i<dimS2; i++)
*eightBits++ = 0L;
#else // the old windows implementation, which isn't working on Mac right now.
// clear out first row of dest
U32 * end32 = eightBits + (dim>>2);
do { *eightBits++=0; } while (eightBits<end32);
end32 += (dim>>2)*(dim-1);
U8 * p0 = (U8*)bits;
U8 bitLo10 = 0x0F & *p0;
U8 bitHi10 = (*p0) >> 4;
p0++;
U8 * p1 = (U8*)bits + (dim>>3);
U8 bitLo11 = 0x0F & *p1;
U8 bitHi11 = (*p1) >> 4;
p1++;
U8 * p2 = (U8*)bits + (dim>>2);
U8 bitLo12 = 0x0F & *p2;
U8 bitHi12 = (*p2) >> 4;
p2++;
U8 bitLo20, bitHi20;
U8 bitLo21, bitHi21;
U8 bitLo22, bitHi22;
U8 bitHi00 = 0;
U8 bitHi01 = 0;
U8 bitHi02 = 0;
// go thru penultimate row (but stop before last entry in that row)
U8 * end = (U8*)bits + dim*(dim>>3) - 1;
do
{
bitLo20 = 0x0F & *p0;
bitHi20 = (*p0) >> 4;
bitLo21 = 0x0F & *p1;
bitHi21 = (*p1) >> 4;
bitLo22 = 0x0F & *p2;
bitHi22 = (*p2) >> 4;
*eightBits++ = *(U32*)&bitTableA[bitHi00] + *(U32*)&bitTableB[bitLo10] + *(U32*)&bitTableC[bitHi10] +
*(U32*)&bitTableA[bitHi01]*2 + *(U32*)&bitTableE[bitLo11] + *(U32*)&bitTableC[bitHi11]*2 +
*(U32*)&bitTableA[bitHi02] + *(U32*)&bitTableB[bitLo12] + *(U32*)&bitTableC[bitHi12];
*eightBits++ = *(U32*)&bitTableA[bitLo10] + *(U32*)&bitTableB[bitHi10] + *(U32*)&bitTableC[bitLo20] +
*(U32*)&bitTableA[bitLo11]*2 + *(U32*)&bitTableE[bitHi11] + *(U32*)&bitTableC[bitLo21]*2 +
*(U32*)&bitTableA[bitLo12] + *(U32*)&bitTableB[bitHi12] + *(U32*)&bitTableC[bitLo22];
bitHi00 = bitHi10;
bitLo10 = bitLo20;
bitHi10 = bitHi20;
bitHi01 = bitHi11;
bitLo11 = bitLo21;
bitHi11 = bitHi21;
bitHi02 = bitHi12;
bitLo12 = bitLo22;
bitHi12 = bitHi22;
p0++;
p1++;
p2++;
}
while (p2<end);
// clear out last row of dest
do { *eightBits++=0; } while (eightBits<end32);
#endif
}

42
engine/core/bitRender.h Executable file
View File

@ -0,0 +1,42 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BITRENDER_H_
#define _BITRENDER_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
/// Functions for rendering to 1bpp bitmaps.
///
/// Used primarily for fast shadow rendering.
struct BitRender
{
/// Render a triangle to a bitmap of 1-bit per pixel and size dim X dim.
static void render(const Point2I *, const Point2I *, const Point2I *, S32 dim, U32 * bits);
/// Render a number of triangle strips to 1-bit per pixel bmp of size dim by dim.
static void render_strips(const U8 * draw, S32 numDraw, S32 szDraw, const U16 * indices, const Point2I * points, S32 dim, U32 * bits);
/// Render a number of triangles to 1-bit per pixel bmp of size dim by dim.
static void render_tris(const U8 * draw, S32 numDraw, S32 szDraw, const U16 * indices, const Point2I * points, S32 dim, U32 * bits);
/// @name Render Bits
/// These are used to convert a 1bpp bitmap to an 8bpp bitmap.
///
/// @see Shadow::endRenderToBitmap
/// @{
/// Render bits to the bitmap.
static void bitTo8Bit(U32 * bits, U32 * eightBits, S32 dim);
/// Render bits to the bitmap, with gaussian pass.
static void bitTo8Bit_3(U32 * bits, U32 * eightBits, S32 dim);
/// @}
};
#endif // _BIT_RENDER_H_

71
engine/core/bitSet.h Executable file
View File

@ -0,0 +1,71 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BITSET_H_
#define _BITSET_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
/// A convenience class to manipulate a set of bits.
///
/// Notice that bits are accessed directly, ie, by passing
/// a variable with the relevant bit set or not, instead of
/// passing the index of the relevant bit.
class BitSet32
{
private:
/// Internal representation of bitset.
U32 mbits;
public:
BitSet32() { mbits = 0; }
BitSet32(const BitSet32& in_rCopy) { mbits = in_rCopy.mbits; }
BitSet32(const U32 in_mask) { mbits = in_mask; }
operator U32() const { return mbits; }
U32 getMask() const { return mbits; }
/// Set all bits to true.
void set() { mbits = 0xFFFFFFFFUL; }
/// Set the specified bit(s) to true.
void set(const U32 m) { mbits |= m; }
/// Masked-set the bitset; ie, using s as the mask and then setting the masked bits
/// to b.
void set(BitSet32 s, bool b) { mbits = (mbits&~(s.mbits))|(b?s.mbits:0); }
/// Clear all bits.
void clear() { mbits = 0; }
/// Clear the specified bit(s).
void clear(const U32 m) { mbits &= ~m; }
/// Toggle the specified bit(s).
void toggle(const U32 m) { mbits ^= m; }
/// Are any of the specified bit(s) set?
bool test(const U32 m) const { return (mbits & m) != 0; }
/// Are ALL the specified bit(s) set?
bool testStrict(const U32 m) const { return (mbits & m) == m; }
/// @name Operator Overloads
/// @{
BitSet32& operator =(const U32 m) { mbits = m; return *this; }
BitSet32& operator|=(const U32 m) { mbits |= m; return *this; }
BitSet32& operator&=(const U32 m) { mbits &= m; return *this; }
BitSet32& operator^=(const U32 m) { mbits ^= m; return *this; }
BitSet32 operator|(const U32 m) const { return BitSet32(mbits | m); }
BitSet32 operator&(const U32 m) const { return BitSet32(mbits & m); }
BitSet32 operator^(const U32 m) const { return BitSet32(mbits ^ m); }
/// @}
};
#endif //_NBITSET_H_

1058
engine/core/bitStream.cc Executable file

File diff suppressed because it is too large Load Diff

277
engine/core/bitStream.h Executable file
View File

@ -0,0 +1,277 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BITSTREAM_H_
#define _BITSTREAM_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _STREAM_H_
#include "core/stream.h"
#endif
#ifndef _MPOINT_H_
#include "math/mPoint.h"
#endif
#include "core/crc.h"
//-------------------------------------- Some caveats when using this class:
// - Get/setPosition semantics are changed
// to indicate bit position rather than
// byte position.
//
class Point3F;
class MatrixF;
class HuffmanProcessor;
class BitStream : public Stream
{
protected:
U8 *dataPtr;
S32 bitNum;
S32 bufSize;
bool error;
S32 maxReadBitNum;
S32 maxWriteBitNum;
char *stringBuffer;
bool mCompressRelative;
Point3F mCompressPoint;
friend class HuffmanProcessor;
public:
static BitStream *getPacketStream(U32 writeSize = 0);
static void sendPacketStream(const NetAddress *addr);
void setBuffer(void *bufPtr, S32 bufSize, S32 maxSize = 0);
U8* getBuffer() { return dataPtr; }
U8* getBytePtr();
U32 getReadByteSize();
S32 getCurPos() const;
void setCurPos(const U32);
BitStream(void *bufPtr, S32 bufSize, S32 maxWriteSize = -1) { setBuffer(bufPtr, bufSize,maxWriteSize); stringBuffer = NULL; }
void clear();
void setStringBuffer(char buffer[256]);
void writeInt(S32 value, S32 bitCount);
S32 readInt(S32 bitCount);
/// Use this method to write out values in a concise but ass backwards way...
/// Good for values you expect to be frequently zero, often small. Worst case
/// this will bloat values by nearly 20% (5 extra bits!) Best case you'll get
/// one bit (if it's zero).
///
/// This is not so much for efficiency's sake, as to make life painful for
/// people that want to reverse engineer our network or file formats.
void writeCussedU32(U32 val)
{
// Is it zero?
if(writeFlag(val == 0))
return;
if(writeFlag(val <= 0xF)) // 4 bit
writeRangedU32(val, 0, 0xF);
else if(writeFlag(val <= 0xFF)) // 8 bit
writeRangedU32(val, 0, 0xFF);
else if(writeFlag(val <= 0xFFFF)) // 16 bit
writeRangedU32(val, 0, 0xFFFF);
else if(writeFlag(val <= 0xFFFFFF)) // 24 bit
writeRangedU32(val, 0, 0xFFFFFF);
else
writeRangedU32(val, 0, 0xFFFFFFFF);
}
U32 readCussedU32()
{
if(readFlag())
return 0;
if(readFlag())
return readRangedU32(0, 0xF);
else if(readFlag())
return readRangedU32(0, 0xFF);
else if(readFlag())
return readRangedU32(0, 0xFFFF);
else if(readFlag())
return readRangedU32(0, 0xFFFFFF);
else
return readRangedU32(0, 0xFFFFFFFF);
}
void writeSignedInt(S32 value, S32 bitCount);
S32 readSignedInt(S32 bitCount);
void writeRangedU32(U32 value, U32 rangeStart, U32 rangeEnd);
U32 readRangedU32(U32 rangeStart, U32 rangeEnd);
// read and write floats... floats are 0 to 1 inclusive, signed floats are -1 to 1 inclusive
F32 readFloat(S32 bitCount);
F32 readSignedFloat(S32 bitCount);
void writeFloat(F32 f, S32 bitCount);
void writeSignedFloat(F32 f, S32 bitCount);
void writeClassId(U32 classId, U32 classType, U32 classGroup);
S32 readClassId(U32 classType, U32 classGroup); // returns -1 if the class type is out of range
// writes a normalized vector
void writeNormalVector(const Point3F& vec, S32 bitCount);
void readNormalVector(Point3F *vec, S32 bitCount);
void clearCompressionPoint();
void setCompressionPoint(const Point3F& p);
// Matching calls to these compression methods must, of course,
// have matching scale values.
void writeCompressedPoint(const Point3F& p,F32 scale = 0.01f);
void readCompressedPoint(Point3F* p,F32 scale = 0.01f);
// Uses the above method to reduce the precision of a normal vector so the server can
// determine exactly what is on the client. (Pre-dumbing the vector before sending
// to the client can result in precision errors...)
static Point3F dumbDownNormal(const Point3F& vec, S32 bitCount);
// writes a normalized vector using alternate method
void writeNormalVector(const Point3F& vec, S32 angleBitCount, S32 zBitCount);
void readNormalVector(Point3F *vec, S32 angleBitCount, S32 zBitCount);
// writes an affine transform (full precision version)
void writeAffineTransform(const MatrixF&);
void readAffineTransform(MatrixF*);
virtual void writeBits(S32 bitCount, const void *bitPtr);
virtual void readBits(S32 bitCount, void *bitPtr);
virtual bool writeFlag(bool val);
virtual bool readFlag();
void setBit(S32 bitCount, bool set);
bool testBit(S32 bitCount);
bool isFull() { return bitNum > (bufSize << 3); }
bool isValid() { return !error; }
bool _read (const U32 size,void* d);
bool _write(const U32 size,const void* d);
void readString(char stringBuf[256]);
void writeString(const char *stringBuf, S32 maxLen=255);
bool hasCapability(const Capability) const { return true; }
U32 getPosition() const;
bool setPosition(const U32 in_newPosition);
U32 getStreamSize();
};
class ResizeBitStream : public BitStream
{
protected:
U32 mMinSpace;
public:
ResizeBitStream(U32 minSpace = 1500, U32 initialSize = 0);
void validate();
~ResizeBitStream();
};
/// This class acts to provide an "infinitely extending" stream.
///
/// Basically, it does what ResizeBitStream does, but it validates
/// on every write op, so that you never have to worry about overwriting
/// the buffer.
class InfiniteBitStream : public ResizeBitStream
{
public:
InfiniteBitStream();
~InfiniteBitStream();
/// Ensure we have space for at least upcomingBytes more bytes in the stream.
void validate(U32 upcomingBytes);
/// Reset the stream to zero length (but don't clean memory).
void reset();
/// Shrink the buffer down to match the actual size of the data.
void compact();
/// Write us out to a stream... Results in last byte getting padded!
void writeToStream(Stream &s);
virtual void writeBits(S32 bitCount, const void *bitPtr)
{
validate((bitCount >> 3) + 1); // Add a little safety.
BitStream::writeBits(bitCount, bitPtr);
}
virtual bool writeFlag(bool val)
{
validate(1); // One bit will at most grow our buffer by a byte.
return BitStream::writeFlag(val);
}
const U32 getCRC()
{
// This could be kinda inefficient - BJG
return calculateCRC(getBuffer(), getStreamSize());
}
};
//------------------------------------------------------------------------------
//-------------------------------------- INLINES
//
inline S32 BitStream::getCurPos() const
{
return bitNum;
}
inline void BitStream::setCurPos(const U32 in_position)
{
AssertFatal(in_position < (U32)(bufSize << 3), "Out of range bitposition");
bitNum = S32(in_position);
}
inline bool BitStream::readFlag()
{
if(bitNum > maxReadBitNum)
{
error = true;
AssertFatal(false, "Out of range read");
return false;
}
S32 mask = 1 << (bitNum & 0x7);
bool ret = (*(dataPtr + (bitNum >> 3)) & mask) != 0;
bitNum++;
return ret;
}
inline void BitStream::writeRangedU32(U32 value, U32 rangeStart, U32 rangeEnd)
{
AssertFatal(value >= rangeStart && value <= rangeEnd, "Out of bounds value!");
AssertFatal(rangeEnd >= rangeStart, "error, end of range less than start");
U32 rangeSize = rangeEnd - rangeStart + 1;
U32 rangeBits = getBinLog2(getNextPow2(rangeSize));
writeInt(S32(value - rangeStart), S32(rangeBits));
}
inline U32 BitStream::readRangedU32(U32 rangeStart, U32 rangeEnd)
{
AssertFatal(rangeEnd >= rangeStart, "error, end of range less than start");
U32 rangeSize = rangeEnd - rangeStart + 1;
U32 rangeBits = getBinLog2(getNextPow2(rangeSize));
U32 val = U32(readInt(S32(rangeBits)));
return val + rangeStart;
}
#endif //_BITSTREAM_H_

25
engine/core/bitTables.cc Executable file
View File

@ -0,0 +1,25 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/bitTables.h"
bool BitTables::mTablesBuilt = false;
S8 BitTables::mHighBit[256];
S8 BitTables::mWhichOn[256][8];
S8 BitTables::mNumOn[256];
static BitTables sBuildTheTables; // invoke ctor first-time work
BitTables::BitTables()
{
if(! mTablesBuilt){
// This code only happens once - it relies on the tables being clear.
for( U32 byte = 0; byte < 256; byte++ )
for( U32 bit = 0; bit < 8; bit++ )
if( byte & (1 << bit) )
mHighBit[byte] = (mWhichOn[byte][mNumOn[byte]++] = bit) + 1;
mTablesBuilt = true;
}
}

42
engine/core/bitTables.h Executable file
View File

@ -0,0 +1,42 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BITTABLES_H_
#define _BITTABLES_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
class BitTables
{
private:
static bool mTablesBuilt; ///< For first time build
static S8 mHighBit[256]; ///< I.e. crude logarithm
static S8 mWhichOn[256][8]; ///< Unroll a bitset collection (note
static S8 mNumOn[256]; // ptr table wastes same amt.)
public:
BitTables();
/// How many bits are on?
static const S32 numOn(U8 b) { return mNumOn[b]; }
/// Return a pointer to 8 S8's, which are set according to which bits are set in the passed value.
static const S8 * whichOn(U8 b) { return mWhichOn[b]; }
static const S32 highBit(U8 b) { return mHighBit[b]; }
static S32 getPower16(U16 x) { return x<256 ? mHighBit[x] : mHighBit[x>>8]+8; }
static S32 getPower32(U32 x){
if( x < (1<<16) )
return( x < (1<<8) ? mHighBit[x] : mHighBit[x>>8]+8 );
else
return( x < (1<<24) ? mHighBit[x>>16]+16 : mHighBit[x>>24]+24 );
}
};
#endif

169
engine/core/bitVector.h Executable file
View File

@ -0,0 +1,169 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BITVECTOR_H_
#define _BITVECTOR_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
/// Manage a vector of bits of arbitrary size.
class BitVector
{
U8* mBits;
U32 mByteSize;
U32 mSize;
static U32 calcByteSize(const U32 numBits);
public:
BitVector();
BitVector(const U32 _size);
~BitVector();
/// @name Size Management
/// @{
void setSize(const U32 _size);
U32 getSize() const;
U32 getByteSize() const;
U32 getAllocatedByteSize() const { return mByteSize; }
const U8* getBits() const { return mBits; }
U8* getNCBits() { return mBits; }
/// @}
bool copy(const BitVector& from);
/// @name Mutators
/// Note that bits are specified by index, unlike BitSet32.
/// @{
/// Clear all the bits.
void clear();
/// Set all the bits.
void set();
/// Set the specified bit.
void set(U32 bit);
/// Clear the specified bit.
void clear(U32 bit);
/// Test that the specified bit is set.
bool test(U32 bit) const;
/// @}
};
inline BitVector::BitVector()
{
mBits = NULL;
mByteSize = 0;
mSize = 0;
}
inline BitVector::BitVector(const U32 _size)
{
mBits = NULL;
mByteSize = 0;
mSize = 0;
setSize(_size);
}
inline BitVector::~BitVector()
{
delete [] mBits;
mBits = NULL;
mByteSize = 0;
mSize = 0;
}
inline U32 BitVector::calcByteSize(const U32 numBits)
{
// Make sure that we are 32 bit aligned
//
return (((numBits + 0x7) >> 3) + 0x3) & ~0x3;
}
inline void BitVector::setSize(const U32 _size)
{
if (_size != 0) {
U32 newSize = calcByteSize(_size);
if (mByteSize < newSize) {
delete [] mBits;
mBits = new U8[newSize];
mByteSize = newSize;
}
} else {
delete [] mBits;
mBits = NULL;
mByteSize = 0;
}
mSize = _size;
}
inline U32 BitVector::getSize() const
{
return mSize;
}
inline U32 BitVector::getByteSize() const
{
return calcByteSize(mSize);
}
inline void BitVector::clear()
{
if (mSize != 0)
dMemset(mBits, 0x00, calcByteSize(mSize));
}
inline bool BitVector::copy(const BitVector& from)
{
U32 sourceSize = from.getSize();
if (sourceSize) {
setSize(sourceSize);
dMemcpy(mBits, from.getBits(), getByteSize());
return true;
}
return false;
}
inline void BitVector::set()
{
if (mSize != 0)
dMemset(mBits, 0xFF, calcByteSize(mSize));
}
inline void BitVector::set(U32 bit)
{
AssertFatal(bit < mSize, "Error, out of range bit");
mBits[bit >> 3] |= U8(1 << (bit & 0x7));
}
inline void BitVector::clear(U32 bit)
{
AssertFatal(bit < mSize, "Error, out of range bit");
mBits[bit >> 3] &= U8(~(1 << (bit & 0x7)));
}
inline bool BitVector::test(U32 bit) const
{
AssertFatal(bit < mSize, "Error, out of range bit");
return (mBits[bit >> 3] & U8(1 << (bit & 0x7))) != 0;
}
#endif //_BITVECTOR_H_

91
engine/core/bitVectorW.h Executable file
View File

@ -0,0 +1,91 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BITVECTORW_H_
#define _BITVECTORW_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
/// @see BitVector
class BitVectorW
{
U32 mNumEntries;
U32 mBitWidth;
U32 mBitMask;
U8 * mDataPtr;
public:
BitVectorW() {mDataPtr=NULL; setDims(0,0);}
~BitVectorW() {if(mDataPtr) delete [] mDataPtr;}
U32 bitWidth() const {return mBitWidth;}
U32 numEntries() const {return mNumEntries;}
U8 * dataPtr() const {return mDataPtr;}
U32 getU17(U32 idx) const; // get and set for bit widths
void setU17(U32 idx, U32 val); // of 17 or less
U32 numBytes() const;
void setDims(U32 sz, U32 w);
};
//-------------------------------------------------------------------------------------
inline U32 BitVectorW::numBytes() const
{
if (mNumEntries > 0)
return (mBitWidth * mNumEntries) + 32 >> 3;
else
return 0;
}
// Alloc the data - note it does work for a bit width of zero (lookups return zero)
inline void BitVectorW::setDims(U32 size, U32 width)
{
if (mDataPtr)
delete [] mDataPtr;
if (size > 0 && width <= 17)
{
mBitWidth = width;
mNumEntries = size;
mBitMask = (1 << width) - 1;
U32 dataSize = numBytes();
mDataPtr = new U8 [dataSize];
dMemset(mDataPtr, 0, dataSize);
}
else
{
mDataPtr = NULL;
mBitWidth = mBitMask = mNumEntries = 0;
}
}
//-------------------------------------------------------------------------------------
// For coding ease, the get and set methods might read or write an extra byte or two.
// If more or less max bit width is ever needed, add or remove the x[] expressions.
inline U32 BitVectorW::getU17(U32 i) const
{
if (mDataPtr) {
register U8 * x = &mDataPtr[(i *= mBitWidth) >> 3];
return (U32(*x) + (U32(x[1])<<8) + (U32(x[2])<<16) >> (i&7)) & mBitMask;
}
return 0;
}
inline void BitVectorW::setU17(U32 i, U32 value)
{
if (mDataPtr) {
register U8 * x = &mDataPtr[(i *= mBitWidth) >> 3];
register U32 mask = mBitMask << (i &= 7);
x[0] = (x[0] & (~mask >> 0)) | ((value <<= i) & (mask >> 0));
x[1] = (x[1] & (~mask >> 8)) | ((value >> 8) & (mask >> 8));
x[2] = (x[2] & (~mask >> 16)) | ((value >> 16) & (mask >> 16));
}
}
#endif //_BITVECTORW_H_

421
engine/core/chunkFile.cc Executable file
View File

@ -0,0 +1,421 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/consoleTypes.h"
#include "core/chunkFile.h"
InfiniteBitStream gChunkBuffer;
InfiniteBitStream gHeaderBuffer;
const U32 ChunkFile::csmFileFourCC = makeFourCCTag('T', 'C', 'H', 'K');
const U32 ChunkFile::csmFileVersion = 2;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool ChunkFile::save(const char * filename)
{
// Just to be sure, wipe stuff.
gChunkBuffer.reset();
// Get a stream...
FileStream s;
if(!ResourceManager->openFileForWrite(s, filename))
{
Con::errorf("ChunkFile::save - cannot open '%s' for write.", filename);
return false;
}
// write preamble!
s.write(csmFileFourCC);
s.write(csmFileVersion);
// Now save out the chunks!
saveInner(s, getRoot());
// Free any memory we had to use for buffering.
gChunkBuffer.reset();
gChunkBuffer.compact();
// All done!
return true;
}
bool ChunkFile::saveInner(Stream &s, SimChunk *c)
{
// First, write the chunk to a buffer so we can meditate on it
gChunkBuffer.reset();
c->writeChunk(gChunkBuffer);
// Buffer the header...
gHeaderBuffer.reset();
// Prepare the header
gHeaderBuffer.writeCussedU32(c->getChunkVersion());
gHeaderBuffer.writeCussedU32(gChunkBuffer.getPosition());
gHeaderBuffer.writeString((const char *)c->getFourCCString(), 4);
gHeaderBuffer.writeCussedU32(c->size()); // Child count.
gHeaderBuffer.write(gChunkBuffer.getCRC());
// Write the header (first size, then data)!
AssertFatal(gHeaderBuffer.getPosition() < 256,
"ChunkFile::saveInner - got too big header!");
s.write(U8(gHeaderBuffer.getPosition()));
gHeaderBuffer.writeToStream(s);
gHeaderBuffer.reset();
// Write the chunk!
gChunkBuffer.writeToStream(s);
gChunkBuffer.reset();
// Recurse!
for(U32 i=0; i<c->size(); i++)
{
// We have to assume people will stuff all sorts of crap into this thing.
SimChunk *sc = dynamic_cast<SimChunk*>((*c)[i]);
if(sc)
saveInner(s, sc);
else
{
Con::errorf("ChunkFile::saveInner - got unexpected class '%s' as child of object %d!",
(c ? (*c)[i]->getClassName() : "NULL OBJECT"), c->getId());
// Write a null header, eww gross.
gHeaderBuffer.writeCussedU32(0);
gHeaderBuffer.writeCussedU32(0);
gHeaderBuffer.writeString("", 4);
gHeaderBuffer.writeCussedU32(0); // Child count.
gHeaderBuffer.write(0);
// That should allow us to recover gracefully when we load...
}
}
return true;
}
//------------------------------------------------------------------------------
bool ChunkFile::load(Stream &s)
{
// check preamble!
U32 fourcc, ver;
s.read(&fourcc);
s.read(&ver);
if(fourcc != csmFileFourCC)
{
Con::errorf("ChunkFile::load - unexpected header value!");
return false;
}
if(ver != csmFileVersion)
{
// Be clever
if(ver > csmFileVersion)
{
Con::errorf("ChunkFile::load - encountered file header version from the future!");
}
else
{
Con::errorf("ChunkFile::load - encountered file header version from the past!");
}
// Error out.
return false;
}
// Now recursively load all our children!
mRoot = loadInner(s);
// Add us to the ChunkFileGroup
Sim::getChunkFileGroup()->addObject(mRoot);
return true;
}
SimChunk *ChunkFile::loadInner(Stream &s, U32 childCount)
{
U8 headerSize;
U32 fourCC, ver, crc, size;
S32 children;
// Read the header size and prepare to read the header bitstream...
s.read(&headerSize);
U8 *headerBuff = new U8[headerSize];
s.read(headerSize, headerBuff);
BitStream hData(headerBuff, headerSize);
// Read actual header values.
ver = hData.readCussedU32();
size = hData.readCussedU32();
// Get the fourcc...
char fcctmp[256]; // For safety we make it 256, longest size string we can read.
hData.readString(fcctmp);
fourCC = makeFourCCTag(fcctmp[0], fcctmp[1], fcctmp[2], fcctmp[3]);
children = hData.readCussedU32();
hData.read(&crc);
delete[] headerBuff;
// Create the chunk...
SimChunk *sc = SimChunk::createChunkFromFourCC(fourCC);
// Do some version sanity checking.
if(ver > sc->getChunkVersion())
{
// uh oh, it's a chunk from the future!
Con::warnf("ChunkFile::loadInner - found a '%s' chunk with version %d; highest supported version is %d. Treating as unknown chunk...",
fcctmp, ver, sc->getChunkVersion());
// Let's use an unknown chunk instead.
delete sc;
sc = new UnknownChunk();
}
// Read the chunk data into a buffer...
U8 *buff = new U8[size];
s.read(size, buff);
BitStream cData(buff, size);
sc->readChunk(cData, size, ver, crc, fourCC);
delete[] buff;
// Register it!
sc->registerObject();
// Recurse on children, adding them to our SimChunk.
for(S32 i=0; i<children; i++)
sc->addObject(loadInner(s, childCount));
// All done!
return sc;
}
//------------------------------------------------------------------------------
ResourceInstance *ChunkFile::constructChunkFile(Stream &stream)
{
ChunkFile *cf = new ChunkFile();
if(cf->load(stream))
{
return cf;
}
else
{
delete cf;
return NULL;
}
}
//------------------------------------------------------------------------------
Vector<SimChunk::FourCCToAcr*> SimChunk::smFourCCList;
void SimChunk::initChunkMappings()
{
Con::printf("Initializing chunk mappings...");
for(AbstractClassRep *rep = AbstractClassRep::getClassList(); rep; rep = rep->getNextClass())
{
ConsoleObject *obj = rep->create();
SimChunk *chunk;
if(obj && (chunk = dynamic_cast<SimChunk *>(obj)))
{
Con::printf(" o '%s' maps to %s", chunk->getFourCCString(), chunk->getClassName());
FourCCToAcr * fcta = new FourCCToAcr();
fcta->fourCC = chunk->getFourCC();
fcta->acr = rep;
smFourCCList.push_back(fcta);
}
delete obj;
}
// Also register the .chunk file format.
ResourceManager->registerExtension(".chunk", ChunkFile::constructChunkFile);
}
SimChunk *SimChunk::createChunkFromFourCC(U32 fourCC)
{
// Try to find a match.
for(U32 i=0; i<smFourCCList.size(); i++)
{
if(smFourCCList[i]->fourCC == fourCC)
return (SimChunk*)smFourCCList[i]->acr->create();
}
// Unknown 4cc, let's use the UnknownChunk
U8 c[4];
c[0] = fourCC >> 24;
c[1] = fourCC >> 16;
c[2] = fourCC >> 8;
c[3] = fourCC >> 0;
Con::warnf("SimChunk::createChunkFromFourCC - encountered unknown fourCC '%c%c%c%c'", c[0], c[1], c[2], c[3]);
return new UnknownChunk();
}
//------------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(SimChunk);
void SimChunk::writeChunk(BitStream &b)
{
}
void SimChunk::readChunk(BitStream &s, const U32 length, const U32 version, const U32 crc, const U32 fourCC)
{
}
//------------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(UnknownChunk);
UnknownChunk::UnknownChunk()
{
mChunkFourCC = mChunkVersion = mChunkCRC = mDataLength = 0;
mDataBuffer = NULL;
}
UnknownChunk::~UnknownChunk()
{
if(mDataBuffer)
dFree(mDataBuffer);
mDataBuffer = NULL;
}
void UnknownChunk::writeChunk(BitStream &b)
{
b.write(mDataLength, mDataBuffer);
}
void UnknownChunk::readChunk(BitStream &s, const U32 length, const U32 version, const U32 crc, const U32 fourCC)
{
mDataBuffer = (U8*)dMalloc(length);
s.read(length, mDataBuffer);
}
//------------------------------------------------------------------------------
class TextChunk : public SimChunk
{
typedef SimChunk Parent;
protected:
const char * mText;
public:
DECLARE_CHUNK(TextChunk, ('T','E','X','T'), 2);
TextChunk()
{
mText = StringTable->insert("");
}
~TextChunk()
{
// No deletion, it's a string table entry.
}
static void initPersistFields()
{
Parent::initPersistFields();
addField("textData", TypeString, Offset(mText, TextChunk));
}
void readChunk(BitStream &s, const U32 length, const U32 version, const U32 crc, const U32 fourCC)
{
switch(version)
{
// Deal with older stuff.
case 1:
mText = s.readSTString();
break;
// Shiny new format!
case 2:
// Resize the buffer..
U32 newSize;
s.read(&newSize);
char *tmpBuff = new char[newSize+1];
s.readLongString(newSize, (char*)tmpBuff);
mText = StringTable->insert(tmpBuff);
delete[] tmpBuff;
break;
}
}
void writeChunk(BitStream &s)
{
s.write((U32)dStrlen(mText));
s.writeLongString(dStrlen(mText), mText);
}
};
IMPLEMENT_CONOBJECT(TextChunk);
//------------------------------------------------------------------------------
ConsoleFunction(saveChunkFile, bool, 3, 3, "(SimChunk chunk, Filename file)"
"Write a chunk hierarchy to a file.")
{
SimChunk *rootChunk = NULL;
const char *file = argv[2];
if(!Sim::findObject(argv[1], rootChunk))
{
Con::errorf("writeChunkFile - Unable to locate root chunk '%s'", argv[1]);
return false;
}
ChunkFile *cf = new ChunkFile();
cf->setRoot(rootChunk);
bool res = true;
if(!cf->save(file))
{
Con::errorf("writeChunkFile - Failed to save '%s' to '%s'", argv[1], file);
res = false;
}
delete cf;
return res;
}
ConsoleFunction(loadChunkFile, S32, 2, 2, "(Filename file)"
"Read a chunk hierarchy from a file.")
{
Resource<ChunkFile> ri = ResourceManager->load(argv[1]);
if(bool(ri) == false)
{
Con::errorf("loadChunkFile - failed to open '%s'", argv[1]);
return 0;
}
// Otherwise we're ok.
return ri->getRoot()->getId();
}

128
engine/core/chunkFile.h Executable file
View File

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CORE_CHUNKFILE_H_
#define _CORE_CHUNKFILE_H_
#include "core/bitStream.h"
#include "core/resManager.h"
#include "console/simBase.h"
// class, fourcc, rev
#define DECLARE_CHUNK(className, fourcc, rev) \
static ConcreteClassRep<className> dynClassRep; \
static AbstractClassRep* getParentStaticClassRep(); \
static AbstractClassRep* getStaticClassRep(); \
virtual AbstractClassRep* getClassRep() const; \
virtual const U32 getFourCC() { return makeFourCCTag fourcc; } \
virtual const U32 getChunkVersion() { return rev; }
class SimChunk : public SimGroup
{
typedef SimGroup Parent;
public:
DECLARE_CHUNK(SimChunk, ('S','C','H','K'), 1);
/// Called to read the chunk's data. We are provided with all the
/// information that we got from the file, just in case it's relevant.
virtual void readChunk(BitStream &s, const U32 length, const U32 version, const U32 crc, const U32 fourCC);
/// Write a chunk. Length is defined by the size of the data we write out,
/// version by the DECLARE_CHUNK macro, crc by the data we write, and
/// FourCC by the macro. So all we have to do is write data!
virtual void writeChunk(BitStream &s);
/// Called post console init to set up the 4cc->class mappings.
struct FourCCToAcr
{
U32 fourCC;
AbstractClassRep *acr;
};
virtual const char *getFourCCString()
{
U8 tmp[5];
U32 cc = getFourCC();
tmp[0] = (cc >> 0) & 0xFF;
tmp[1] = (cc >> 8) & 0xFF;
tmp[2] = (cc >> 16) & 0xFF;
tmp[3] = (cc >> 24) & 0xFF;
tmp[4] = 0;
return StringTable->insert((const char*)tmp, true);
}
static Vector<FourCCToAcr*> smFourCCList;
static void initChunkMappings();
static SimChunk *createChunkFromFourCC(U32 fourCC);
};
/// This is a special purpose chunk we use to deal with situations where
/// we have an unknown FourCC. Basically, it loads everything into a buffer,
/// then writes it back out again, masquerading as the unknown chunk.
///
/// This means we have to manually do some of what DECLARE_CHUNK does, ew.
class UnknownChunk : public SimChunk
{
typedef SimChunk Parent;
U32 mChunkFourCC;
U8 mChunkFourCCString[4];
U32 mChunkVersion;
U32 mChunkCRC;
U32 mDataLength;
U8 *mDataBuffer;
public:
DECLARE_CONOBJECT(UnknownChunk);
UnknownChunk();
~UnknownChunk();
virtual const U32 getFourCC() const { return mChunkFourCC; }
virtual const U8 *getFourCCString() const { return &mChunkFourCCString[0]; }
virtual const U32 getChunkVersion() const { return mChunkVersion; }
virtual void readChunk(BitStream &s, const U32 length, const U32 version, const U32 crc, const U32 fourCC);
virtual void writeChunk(BitStream &s);
};
// Allow only SimChunk and derivatives to be added to a SimChunk (override addObject)
// write/readChunk must call Parent::'s versions
class ChunkFile : ResourceInstance
{
private:
/// Root of the chunk hierarchy for this file.
SimObjectPtr<SimChunk> mRoot;
/// Helper function, saves out a chunk and its children...
bool saveInner(Stream &s, SimChunk *c);
/// Helper function, loads up a chunk and its children...
SimChunk *loadInner(Stream &s, U32 childCount=1);
static const U32 csmFileFourCC;
static const U32 csmFileVersion;
public:
/// Generic chunk file loader.
static ResourceInstance *constructChunkFile(Stream &stream);
/// Return a pointer to the root chunk.
SimChunk * getRoot() const { return mRoot; };
void setRoot( SimChunk * c) { mRoot = c; };
/// Serialize!
bool save(const char * filename);
/// Deserialize!
bool load(Stream &s);
};
#endif

495
engine/core/color.h Executable file
View File

@ -0,0 +1,495 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _COLOR_H_
#define _COLOR_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
// Forward Declarations...
class ColorI;
class ColorF
{
public:
F32 red;
F32 green;
F32 blue;
F32 alpha;
public:
ColorF() { }
ColorF(const ColorF& in_rCopy);
ColorF(const F32 in_r,
const F32 in_g,
const F32 in_b,
const F32 in_a = 1.0f);
void set(const F32 in_r,
const F32 in_g,
const F32 in_b,
const F32 in_a = 1.0f);
ColorF& operator*=(const ColorF& in_mul); // Can be useful for lighting
ColorF operator*(const ColorF& in_mul) const;
ColorF& operator+=(const ColorF& in_rAdd);
ColorF operator+(const ColorF& in_rAdd) const;
ColorF& operator-=(const ColorF& in_rSub);
ColorF operator-(const ColorF& in_rSub) const;
ColorF& operator*=(const F32 in_mul);
ColorF operator*(const F32 in_mul) const;
ColorF& operator/=(const F32 in_div);
ColorF operator/(const F32 in_div) const;
ColorF operator-() const;
bool operator==(const ColorF&) const;
bool operator!=(const ColorF&) const;
operator const F32*() const { return &red; }
U32 getARGBPack() const;
U32 getRGBAPack() const;
U32 getBGRAPack() const;
operator ColorI() const;
void interpolate(const ColorF& in_rC1,
const ColorF& in_rC2,
const F32 in_factor);
bool isValidColor() const { return (red >= 0.0f && red <= 1.0f) &&
(green >= 0.0f && green <= 1.0f) &&
(blue >= 0.0f && blue <= 1.0f) &&
(alpha >= 0.0f && alpha <= 1.0f); }
void clamp();
};
//-------------------------------------- ColorI's are missing some of the operations
// present in ColorF since they cannot recover
// properly from over/underflow. Also, structure
// designed to be castable to PALETTEENTRY's in
// Win32 environments...
class ColorI
{
public:
U8 red;
U8 green;
U8 blue;
U8 alpha;
public:
ColorI() { }
ColorI(const ColorI& in_rCopy);
ColorI(const U8 in_r,
const U8 in_g,
const U8 in_b,
const U8 in_a = U8(255));
void set(const U8 in_r,
const U8 in_g,
const U8 in_b,
const U8 in_a = U8(255));
ColorI& operator*=(const F32 in_mul);
ColorI operator*(const F32 in_mul) const;
bool operator==(const ColorI&) const;
bool operator!=(const ColorI&) const;
void interpolate(const ColorI& in_rC1,
const ColorI& in_rC2,
const F32 in_factor);
U32 getARGBPack() const;
U32 getRGBAPack() const;
U32 getABGRPack() const;
U32 getBGRPack() const;
U32 getRGBPack() const;
U32 getRGBEndian() const;
U32 getARGBEndian() const;
U16 get565() const;
U16 get4444() const;
operator ColorF() const;
operator const U8*() const { return &red; }
};
//------------------------------------------------------------------------------
//-------------------------------------- INLINES (ColorF)
//
inline void ColorF::set(const F32 in_r,
const F32 in_g,
const F32 in_b,
const F32 in_a)
{
red = in_r;
green = in_g;
blue = in_b;
alpha = in_a;
}
void a()
{
ColorI* test = new ColorI(255, 255, 255);
test->set(0, 0, 0);
}
inline ColorF::ColorF(const ColorF& in_rCopy)
{
red = in_rCopy.red;
green = in_rCopy.green;
blue = in_rCopy.blue;
alpha = in_rCopy.alpha;
}
inline ColorF::ColorF(const F32 in_r,
const F32 in_g,
const F32 in_b,
const F32 in_a)
{
set(in_r, in_g, in_b, in_a);
}
inline ColorF& ColorF::operator*=(const ColorF& in_mul)
{
red *= in_mul.red;
green *= in_mul.green;
blue *= in_mul.blue;
alpha *= in_mul.alpha;
return *this;
}
inline ColorF ColorF::operator*(const ColorF& in_mul) const
{
return ColorF(red * in_mul.red,
green * in_mul.green,
blue * in_mul.blue,
alpha * in_mul.alpha);
}
inline ColorF& ColorF::operator+=(const ColorF& in_rAdd)
{
red += in_rAdd.red;
green += in_rAdd.green;
blue += in_rAdd.blue;
alpha += in_rAdd.alpha;
return *this;
}
inline ColorF ColorF::operator+(const ColorF& in_rAdd) const
{
return ColorF(red + in_rAdd.red,
green + in_rAdd.green,
blue + in_rAdd.blue,
alpha + in_rAdd.alpha);
}
inline ColorF& ColorF::operator-=(const ColorF& in_rSub)
{
red -= in_rSub.red;
green -= in_rSub.green;
blue -= in_rSub.blue;
alpha -= in_rSub.alpha;
return *this;
}
inline ColorF ColorF::operator-(const ColorF& in_rSub) const
{
return ColorF(red - in_rSub.red,
green - in_rSub.green,
blue - in_rSub.blue,
alpha - in_rSub.alpha);
}
inline ColorF& ColorF::operator*=(const F32 in_mul)
{
red *= in_mul;
green *= in_mul;
blue *= in_mul;
alpha *= in_mul;
return *this;
}
inline ColorF ColorF::operator*(const F32 in_mul) const
{
return ColorF(red * in_mul,
green * in_mul,
blue * in_mul,
alpha * in_mul);
}
inline ColorF& ColorF::operator/=(const F32 in_div)
{
AssertFatal(in_div != 0.0f, "Error, div by zero...");
F32 inv = 1.0f / in_div;
red *= inv;
green *= inv;
blue *= inv;
alpha *= inv;
return *this;
}
inline ColorF ColorF::operator/(const F32 in_div) const
{
AssertFatal(in_div != 0.0f, "Error, div by zero...");
F32 inv = 1.0f / in_div;
return ColorF(red * inv,
green * inv,
blue * inv,
alpha * inv);
}
inline ColorF ColorF::operator-() const
{
return ColorF(-red, -green, -blue, -alpha);
}
inline bool ColorF::operator==(const ColorF& in_Cmp) const
{
return (red == in_Cmp.red && green == in_Cmp.green && blue == in_Cmp.blue && alpha == in_Cmp.alpha);
}
inline bool ColorF::operator!=(const ColorF& in_Cmp) const
{
return (red != in_Cmp.red || green != in_Cmp.green || blue != in_Cmp.blue || alpha != in_Cmp.alpha);
}
inline U32 ColorF::getARGBPack() const
{
return (U32(alpha * 255.0f + 0.5) << 24) |
(U32(red * 255.0f + 0.5) << 16) |
(U32(green * 255.0f + 0.5) << 8) |
(U32(blue * 255.0f + 0.5) << 0);
}
inline U32 ColorF::getRGBAPack() const
{
return (U32(alpha * 255.0f + 0.5) << 0) |
(U32(red * 255.0f + 0.5) << 24) |
(U32(green * 255.0f + 0.5) << 16) |
(U32(blue * 255.0f + 0.5) << 8);
}
inline U32 ColorF::getBGRAPack() const
{
return (U32(alpha * 255.0f + 0.5) << 0) |
(U32(red * 255.0f + 0.5) << 8) |
(U32(green * 255.0f + 0.5) << 16) |
(U32(blue * 255.0f + 0.5) << 24);
}
inline void ColorF::interpolate(const ColorF& in_rC1,
const ColorF& in_rC2,
const F32 in_factor)
{
F32 f2 = 1.0f - in_factor;
red = (in_rC1.red * f2) + (in_rC2.red * in_factor);
green = (in_rC1.green * f2) + (in_rC2.green * in_factor);
blue = (in_rC1.blue * f2) + (in_rC2.blue * in_factor);
alpha = (in_rC1.alpha * f2) + (in_rC2.alpha * in_factor);
}
inline void ColorF::clamp()
{
if (red > 1.0f)
red = 1.0f;
else if (red < 0.0f)
red = 0.0f;
if (green > 1.0f)
green = 1.0f;
else if (green < 0.0f)
green = 0.0f;
if (blue > 1.0f)
blue = 1.0f;
else if (blue < 0.0f)
blue = 0.0f;
if (alpha > 1.0f)
alpha = 1.0f;
else if (alpha < 0.0f)
alpha = 0.0f;
}
//------------------------------------------------------------------------------
//-------------------------------------- INLINES (ColorI)
//
inline void ColorI::set(const U8 in_r,
const U8 in_g,
const U8 in_b,
const U8 in_a)
{
red = in_r;
green = in_g;
blue = in_b;
alpha = in_a;
}
inline ColorI::ColorI(const ColorI& in_rCopy)
{
red = in_rCopy.red;
green = in_rCopy.green;
blue = in_rCopy.blue;
alpha = in_rCopy.alpha;
}
inline ColorI::ColorI(const U8 in_r,
const U8 in_g,
const U8 in_b,
const U8 in_a)
{
set(in_r, in_g, in_b, in_a);
}
inline ColorI& ColorI::operator*=(const F32 in_mul)
{
red = U8((F32(red) * in_mul) + 0.5f);
green = U8((F32(green) * in_mul) + 0.5f);
blue = U8((F32(blue) * in_mul) + 0.5f);
alpha = U8((F32(alpha) * in_mul) + 0.5f);
return *this;
}
inline ColorI ColorI::operator*(const F32 in_mul) const
{
ColorI temp(*this);
temp *= in_mul;
return temp;
}
inline bool ColorI::operator==(const ColorI& in_Cmp) const
{
return (red == in_Cmp.red && green == in_Cmp.green && blue == in_Cmp.blue && alpha == in_Cmp.alpha);
}
inline bool ColorI::operator!=(const ColorI& in_Cmp) const
{
return (red != in_Cmp.red || green != in_Cmp.green || blue != in_Cmp.blue || alpha != in_Cmp.alpha);
}
inline void ColorI::interpolate(const ColorI& in_rC1,
const ColorI& in_rC2,
const F32 in_factor)
{
F32 f2= 1.0f - in_factor;
red = U8(((F32(in_rC1.red) * f2) + (F32(in_rC2.red) * in_factor)) + 0.5f);
green = U8(((F32(in_rC1.green) * f2) + (F32(in_rC2.green) * in_factor)) + 0.5f);
blue = U8(((F32(in_rC1.blue) * f2) + (F32(in_rC2.blue) * in_factor)) + 0.5f);
alpha = U8(((F32(in_rC1.alpha) * f2) + (F32(in_rC2.alpha) * in_factor)) + 0.5f);
}
inline U32 ColorI::getARGBPack() const
{
return (U32(alpha) << 24) |
(U32(red) << 16) |
(U32(green) << 8) |
(U32(blue) << 0);
}
inline U32 ColorI::getRGBAPack() const
{
return (U32(alpha) << 0) |
(U32(red) << 24) |
(U32(green) << 16) |
(U32(blue) << 8);
}
inline U32 ColorI::getABGRPack() const
{
return (U32(alpha) << 24) |
(U32(red) << 16) |
(U32(green) << 8) |
(U32(blue) << 0);
}
inline U32 ColorI::getBGRPack() const
{
return (U32(blue) << 16) |
(U32(green) << 8) |
(U32(red) << 0);
}
inline U32 ColorI::getRGBPack() const
{
return (U32(red) << 16) |
(U32(green) << 8) |
(U32(blue) << 0);
}
inline U32 ColorI::getRGBEndian() const
{
#if defined(TORQUE_BIG_ENDIAN)
return(getRGBPack());
#else
return(getBGRPack());
#endif
}
inline U32 ColorI::getARGBEndian() const
{
#if defined(TORQUE_BIG_ENDIAN)
return(getABGRPack());
#else
return(getARGBPack());
#endif
}
inline U16 ColorI::get565() const
{
return U16((U16(red >> 3) << 11) |
(U16(green >> 2) << 5) |
(U16(blue >> 3) << 0));
}
inline U16 ColorI::get4444() const
{
return U16(U16(U16(alpha >> 4) << 12) |
U16(U16(red >> 4) << 8) |
U16(U16(green >> 4) << 4) |
U16(U16(blue >> 4) << 0));
}
//-------------------------------------- INLINE CONVERSION OPERATORS
inline ColorF::operator ColorI() const
{
return ColorI(U8(red * 255.0f + 0.5),
U8(green * 255.0f + 0.5),
U8(blue * 255.0f + 0.5),
U8(alpha * 255.0f + 0.5));
}
inline ColorI::operator ColorF() const
{
const F32 inv255 = 1.0f / 255.0f;
return ColorF(F32(red) * inv255,
F32(green) * inv255,
F32(blue) * inv255,
F32(alpha) * inv255);
}
#endif //_COLOR_H_

64
engine/core/coreRes.h Executable file
View File

@ -0,0 +1,64 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CORERES_H_
#define _CORERES_H_
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
class RawData
{
private:
bool ownMemory;
public:
char *data;
S32 size;
RawData() { ownMemory = false; }
RawData(Stream &s, S32 sz) {
ownMemory = true;
size = sz;
data = new char[size];
if (data)
s.read(size, data);
}
~RawData() {
if (ownMemory)
delete [] data;
data = NULL;
ownMemory = false;
size = 0;
}
};
//-------------------------------------- RawData type
class ResourceTypeRawData : public ResourceType
{
public:
ResourceTypeRawData(const char *ext = ".dat"):
ResourceType( ResourceType::typeof(ext) ) { }
void* construct(Stream *stream, S32 size)
{ return (void*)new RawData(*stream, size); }
void destruct(void *p)
{ delete (RawData*)p; }
};
class ResourceTypeStaticRawData : public ResourceType
{
public:
ResourceTypeStaticRawData(const char *ext = ".sdt"):
ResourceType( ResourceType::typeof(ext) ) { }
void* construct(Stream *stream, S32 size)
{ return (void*)new RawData(*stream, size); }
void destruct(void *p)
{ }
};
#endif //_CORERES_H_

72
engine/core/crc.cc Executable file
View File

@ -0,0 +1,72 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/stream.h"
//-----------------------------------------------------------------------------
// simple crc function - generates lookup table on first call
static U32 crcTable[256];
static bool crcTableValid;
static void calculateCRCTable()
{
U32 val;
for(S32 i = 0; i < 256; i++)
{
val = i;
for(S32 j = 0; j < 8; j++)
{
if(val & 0x01)
val = 0xedb88320 ^ (val >> 1);
else
val = val >> 1;
}
crcTable[i] = val;
}
crcTableValid = true;
}
//-----------------------------------------------------------------------------
U32 calculateCRC(const void * buffer, S32 len, U32 crcVal )
{
// check if need to generate the crc table
if(!crcTableValid)
calculateCRCTable();
// now calculate the crc
char * buf = (char*)buffer;
for(S32 i = 0; i < len; i++)
crcVal = crcTable[(crcVal ^ buf[i]) & 0xff] ^ (crcVal >> 8);
return(crcVal);
}
U32 calculateCRCStream(Stream *stream, U32 crcVal )
{
// check if need to generate the crc table
if(!crcTableValid)
calculateCRCTable();
// now calculate the crc
stream->setPosition(0);
S32 len = stream->getStreamSize();
U8 buf[4096];
S32 segCount = (len + 4095) / 4096;
for(S32 j = 0; j < segCount; j++)
{
S32 slen = getMin(4096, len - (j * 4096));
stream->read(slen, buf);
crcVal = calculateCRC(buf, slen, crcVal);
}
stream->setPosition(0);
return(crcVal);
}

17
engine/core/crc.h Executable file
View File

@ -0,0 +1,17 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CRC_H_
#define _CRC_H_
#define INITIAL_CRC_VALUE 0xffffffff
class Stream;
U32 calculateCRC(const void * buffer, S32 len, U32 crcVal = INITIAL_CRC_VALUE);
U32 calculateCRCStream(Stream *stream, U32 crcVal = INITIAL_CRC_VALUE);
#endif

59
engine/core/dataChunker.cc Executable file
View File

@ -0,0 +1,59 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/dataChunker.h"
//----------------------------------------------------------------------------
DataChunker::DataChunker(S32 size)
{
chunkSize = size;
curBlock = new DataBlock(size);
curBlock->next = NULL;
curBlock->curIndex = 0;
}
DataChunker::~DataChunker()
{
freeBlocks();
}
void *DataChunker::alloc(S32 size)
{
AssertFatal(size <= chunkSize, "Data chunk too large.");
if(!curBlock || size + curBlock->curIndex > chunkSize)
{
DataBlock *temp = new DataBlock(chunkSize);
temp->next = curBlock;
temp->curIndex = 0;
curBlock = temp;
}
void *ret = curBlock->data + curBlock->curIndex;
curBlock->curIndex += (size + 3) & ~3; // dword align
return ret;
}
DataChunker::DataBlock::DataBlock(S32 size)
{
data = new U8[size];
}
DataChunker::DataBlock::~DataBlock()
{
delete[] data;
}
void DataChunker::freeBlocks()
{
while(curBlock)
{
DataBlock *temp = curBlock->next;
delete curBlock;
curBlock = temp;
}
}

135
engine/core/dataChunker.h Executable file
View File

@ -0,0 +1,135 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _DATACHUNKER_H_
#define _DATACHUNKER_H_
//----------------------------------------------------------------------------
/// Implements a chunked data allocater.
///
/// Calling new/malloc all the time is a time consuming operation. Therefore,
/// we provide the DataChunker, which allocates memory in blockss of
/// chunkSize (by default 16k, see ChunkSize, though it can be set in
/// the constructor), then doles it out as requested, in chunks of up to
/// chunkSize in size.
///
/// It will assert if you try to get more than ChunkSize bytes at a time,
/// and it deals with the logic of allocating new blocks and giving out
/// word-aligned chunks.
///
/// Note that new/free/realloc WILL NOT WORK on memory gotten from the
/// DataChunker. This also only grows (you can call freeBlocks to deallocate
/// and reset things).
class DataChunker
{
public:
enum {
ChunkSize = 16376 ///< Default size for chunks.
};
private:
/// Block of allocated memory.
///
/// <b>This has nothing to do with datablocks as used in the rest of Torque.</b>
struct DataBlock
{
DataBlock *next;
U8 *data;
S32 curIndex;
DataBlock(S32 size);
~DataBlock();
};
DataBlock *curBlock;
S32 chunkSize;
public:
/// Return a pointer to a chunk of memory from a pre-allocated block.
///
/// This memory goes away when you call freeBlocks.
///
/// This memory is word-aligned.
/// @param size Size of chunk to return. This must be less than chunkSize or else
/// an assertion will occur.
void *alloc(S32 size);
/// Free all allocated memory blocks.
///
/// This invalidates all pointers returned from alloc().
void freeBlocks();
/// Initialize using blocks of a given size.
///
/// One new block is allocated at constructor-time.
///
/// @param size Size in bytes of the space to allocate for each block.
DataChunker(S32 size=ChunkSize);
~DataChunker();
};
//----------------------------------------------------------------------------
template<class T>
class Chunker: private DataChunker
{
public:
Chunker(S32 size = DataChunker::ChunkSize) : DataChunker(size) {};
T* alloc() { return reinterpret_cast<T*>(DataChunker::alloc(S32(sizeof(T)))); }
void clear() { freeBlocks(); };
};
template<class T>
class FreeListChunker: private DataChunker
{
S32 numAllocated;
S32 elementSize;
T *freeListHead;
public:
FreeListChunker(S32 size = DataChunker::ChunkSize) : DataChunker(size)
{
numAllocated = 0;
elementSize = getMax(U32(sizeof(T)), U32(sizeof(T *)));
freeListHead = NULL;
}
T *alloc()
{
numAllocated++;
if(freeListHead == NULL)
return reinterpret_cast<T*>(DataChunker::alloc(elementSize));
T* ret = freeListHead;
freeListHead = *(reinterpret_cast<T**>(freeListHead));
return ret;
}
void free(T* elem)
{
numAllocated--;
*(reinterpret_cast<T**>(elem)) = freeListHead;
freeListHead = elem;
// If nothing's allocated, clean up!
if(!numAllocated)
{
freeBlocks();
freeListHead = NULL;
}
}
// Allow people to free all their memory if they want.
void freeBlocks()
{
DataChunker::freeBlocks();
// We have to terminate the freelist as well or else we'll run
// into crazy unused memory.
freeListHead = NULL;
}
};
#endif

263
engine/core/dnet.cc Executable file
View File

@ -0,0 +1,263 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/dnet.h"
#include "console/console.h"
#include "core/bitStream.h"
#include "console/consoleTypes.h"
bool gLogToConsole = false;
S32 gNetBitsReceived = 0;
enum NetPacketType
{
DataPacket,
PingPacket,
AckPacket,
InvalidPacketType,
};
static const char *packetTypeNames[] =
{
"DataPacket",
"PingPacket",
"AckPacket",
};
//-----------------------------------------------------------------
//-----------------------------------------------------------------
//-----------------------------------------------------------------
ConsoleFunction(DNetSetLogging, void, 2, 2, "(bool enabled)")
{
argc;
gLogToConsole = dAtob(argv[1]);
}
ConnectionProtocol::ConnectionProtocol()
{
mLastSeqRecvd = 0;
mHighestAckedSeq = 0;
mLastSendSeq = 0; // start sending at 1
mAckMask = 0;
mLastRecvAckAck = 0;
}
void ConnectionProtocol::buildSendPacketHeader(BitStream *stream, S32 packetType)
{
S32 ackByteCount = ((mLastSeqRecvd - mLastRecvAckAck + 7) >> 3);
AssertFatal(ackByteCount <= 4, "Too few ack bytes!");
S32 headerSize = 3 + ackByteCount;
S32 datalen = 0;
if(packetType == DataPacket)
mLastSendSeq++;
stream->writeFlag(true);
stream->writeInt(mConnectSequence & 1, 1);
stream->writeInt(mLastSendSeq, 9);
stream->writeInt(mLastSeqRecvd, 9);
stream->writeInt(packetType, 2);
stream->writeInt(ackByteCount, 3);
stream->writeInt(mAckMask, ackByteCount * 8);
// if we're resending this header, we can't advance the
// sequence recieved (in case this packet drops and the prev one
// goes through)
if(gLogToConsole)
Con::printf("build hdr %d %d", mLastSendSeq, packetType);
if(packetType == DataPacket)
mLastSeqRecvdAtSend[mLastSendSeq & 0x1F] = mLastSeqRecvd;
}
void ConnectionProtocol::sendPingPacket()
{
U8 buffer[16];
BitStream bs(buffer, 16);
buildSendPacketHeader(&bs, PingPacket);
if(gLogToConsole)
Con::printf("send ping %d", mLastSendSeq);
sendPacket(&bs);
}
void ConnectionProtocol::sendAckPacket()
{
U8 buffer[16];
BitStream bs(buffer, 16);
buildSendPacketHeader(&bs, AckPacket);
if(gLogToConsole)
Con::printf("send ack %d", mLastSendSeq);
sendPacket(&bs);
}
// packets are read directly into the data portion of
// connection notify packets... makes the events easier to post into
// the system.
void ConnectionProtocol::processRawPacket(BitStream *pstream)
{
// read in the packet header:
// Fixed packet header: 3 bytes
//
// 1 bit game packet flag
// 1 bit connect sequence
// 9 bits packet seq number
// 9 bits ackstart seq number
// 2 bits packet type
// 2 bits ack byte count
//
// type is:
// 00 data packet
// 01 ping packet
// 02 ack packet
// next 1-4 bytes are ack flags
//
// header len is 4-9 bytes
// average case 4 byte header
gNetBitsReceived = pstream->getStreamSize();
pstream->readFlag(); // get rid of the game info packet bit
U32 pkConnectSeqBit = pstream->readInt(1);
U32 pkSequenceNumber = pstream->readInt(9);
U32 pkHighestAck = pstream->readInt(9);
U32 pkPacketType = pstream->readInt(2);
S32 pkAckByteCount = pstream->readInt(3);
// check connection sequence bit
if(pkConnectSeqBit != (mConnectSequence & 1))
return;
if(pkAckByteCount > 4 || pkPacketType >= InvalidPacketType)
return;
S32 pkAckMask = pstream->readInt(8 * pkAckByteCount);
// verify packet ordering and acking and stuff
// check if the 9-bit sequence is within the packet window
// (within 31 packets of the last received sequence number).
pkSequenceNumber |= (mLastSeqRecvd & 0xFFFFFE00);
// account for wrap around
if(pkSequenceNumber < mLastSeqRecvd)
pkSequenceNumber += 0x200;
if(pkSequenceNumber > mLastSeqRecvd + 31)
{
// the sequence number is outside the window... must be out of order
// discard.
return;
}
pkHighestAck |= (mHighestAckedSeq & 0xFFFFFE00);
// account for wrap around
if(pkHighestAck < mHighestAckedSeq)
pkHighestAck += 0x200;
if(pkHighestAck > mLastSendSeq)
{
// the ack number is outside the window... must be an out of order
// packet, discard.
return;
}
if(gLogToConsole)
{
for(U32 i = mLastSeqRecvd+1; i < pkSequenceNumber; i++)
Con::printf("Not recv %d", i);
Con::printf("Recv %d %s", pkSequenceNumber, packetTypeNames[pkPacketType]);
}
// shift up the ack mask by the packet difference
// this essentially nacks all the packets dropped
mAckMask <<= pkSequenceNumber - mLastSeqRecvd;
// if this packet is a data packet (i.e. not a ping packet or an ack packet), ack it
if(pkPacketType == DataPacket)
mAckMask |= 1;
// do all the notifies...
for(U32 i = mHighestAckedSeq+1; i <= pkHighestAck; i++)
{
bool packetTransmitSuccess = pkAckMask & (1 << (pkHighestAck - i));
handleNotify(packetTransmitSuccess);
if(gLogToConsole)
Con::printf("Ack %d %d", i, packetTransmitSuccess);
if(packetTransmitSuccess)
{
mLastRecvAckAck = mLastSeqRecvdAtSend[i & 0x1F];
if(!mConnectionEstablished)
{
mConnectionEstablished = true;
handleConnectionEstablished();
}
}
}
// the other side knows more about its window than we do.
if(pkSequenceNumber - mLastRecvAckAck > 32)
mLastRecvAckAck = pkSequenceNumber - 32;
mHighestAckedSeq = pkHighestAck;
// first things first...
// ackback any pings or accept connects
if(pkPacketType == PingPacket)
{
// send an ack to the other side
// the ack will have the same packet sequence as our last sent packet
// if the last packet we sent was the connection accepted packet
// we must resend that packet
sendAckPacket();
}
keepAlive(); // notification that the connection is ok
if(mLastSeqRecvd != pkSequenceNumber && pkPacketType == DataPacket)
handlePacket(pstream);
mLastSeqRecvd = pkSequenceNumber;
}
bool ConnectionProtocol::windowFull()
{
return mLastSendSeq - mHighestAckedSeq >= 30;
}
void ConnectionProtocol::writeDemoStartBlock(ResizeBitStream *stream)
{
for(U32 i = 0; i < 32; i++)
stream->write(mLastSeqRecvdAtSend[i]);
stream->write(mLastSeqRecvd);
stream->write(mHighestAckedSeq);
stream->write(mLastSendSeq);
stream->write(mAckMask);
stream->write(mConnectSequence);
stream->write(mLastRecvAckAck);
stream->write(mConnectionEstablished);
}
bool ConnectionProtocol::readDemoStartBlock(BitStream *stream)
{
for(U32 i = 0; i < 32; i++)
stream->read(&mLastSeqRecvdAtSend[i]);
stream->read(&mLastSeqRecvd);
stream->read(&mHighestAckedSeq);
stream->read(&mLastSendSeq);
stream->read(&mAckMask);
stream->read(&mConnectSequence);
stream->read(&mLastRecvAckAck);
stream->read(&mConnectionEstablished);
return true;
}

64
engine/core/dnet.h Executable file
View File

@ -0,0 +1,64 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _DNET_H_
#define _DNET_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _EVENT_H_
#include "platform/event.h"
#endif
class BitStream;
class ResizeBitStream;
/// The base class for Torque's networking protocol.
///
/// This implements a sliding window connected message stream over an unreliable transport (UDP). It
/// provides a simple notify protocol to allow subclasses to be aware of what packets were sent
/// succesfully and which failed.
///
/// Basically, a window size of 32 is provided, and each packet contains in the header a bitmask,
/// acknowledging the receipt (or failure to receive) of the last 32 packets.
///
/// @see NetConnection, @ref NetProtocol
class ConnectionProtocol
{
protected:
U32 mLastSeqRecvdAtSend[32];
U32 mLastSeqRecvd;
U32 mHighestAckedSeq;
U32 mLastSendSeq;
U32 mAckMask;
U32 mConnectSequence;
U32 mLastRecvAckAck;
bool mConnectionEstablished;
public:
ConnectionProtocol();
void buildSendPacketHeader(BitStream *bstream, S32 packetType = 0);
void sendPingPacket();
void sendAckPacket();
void setConnectionEstablished() { mConnectionEstablished = true; }
bool windowFull();
bool connectionEstablished();
void setConnectSequence(U32 connectSeq) { mConnectSequence = connectSeq; }
virtual void writeDemoStartBlock(ResizeBitStream *stream);
virtual bool readDemoStartBlock(BitStream *stream);
virtual void processRawPacket(BitStream *bstream);
virtual Net::Error sendPacket(BitStream *bstream) = 0;
virtual void keepAlive() = 0;
virtual void handleConnectionEstablished() = 0;
virtual void handleNotify(bool recvd) = 0;
virtual void handlePacket(BitStream *bstream) = 0;
};
#endif

177
engine/core/fileObject.cc Executable file
View File

@ -0,0 +1,177 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/fileObject.h"
IMPLEMENT_CONOBJECT(FileObject);
bool FileObject::isEOF()
{
return mCurPos == mBufferSize;
}
FileObject::FileObject()
{
mFileBuffer = NULL;
mBufferSize = 0;
mCurPos = 0;
}
FileObject::~FileObject()
{
dFree(mFileBuffer);
}
void FileObject::close()
{
stream.close();
dFree(mFileBuffer);
mFileBuffer = NULL;
mBufferSize = mCurPos = 0;
}
bool FileObject::openForWrite(const char *fileName, const bool append)
{
char buffer[1024];
Con::expandScriptFilename( buffer, sizeof( buffer ), fileName );
close();
if ( !append )
return( ResourceManager->openFileForWrite(stream, buffer) );
// Use the WriteAppend flag so it doesn't clobber the existing file:
if ( !ResourceManager->openFileForWrite(stream, buffer, File::WriteAppend) )
return( false );
stream.setPosition( stream.getStreamSize() );
return( true );
}
bool FileObject::openForRead(const char* /*fileName*/)
{
AssertFatal(false, "Error, not yet implemented!");
return false;
}
bool FileObject::readMemory(const char *fileName)
{
char buffer[1024];
Con::expandScriptFilename( buffer, sizeof( buffer ), fileName );
close();
Stream *s = ResourceManager->openStream(buffer);
if(!s)
return false;
mBufferSize = ResourceManager->getSize(buffer);
mFileBuffer = (U8 *) dMalloc(mBufferSize + 1);
mFileBuffer[mBufferSize] = 0;
s->read(mBufferSize, mFileBuffer);
ResourceManager->closeStream(s);
mCurPos = 0;
return true;
}
const U8 *FileObject::readLine()
{
if(!mFileBuffer)
return (U8 *) "";
U32 tokPos = mCurPos;
for(;;)
{
if(mCurPos == mBufferSize)
break;
if(mFileBuffer[mCurPos] == '\r')
{
mFileBuffer[mCurPos++] = 0;
if(mFileBuffer[mCurPos] == '\n')
mCurPos++;
break;
}
if(mFileBuffer[mCurPos] == '\n')
{
mFileBuffer[mCurPos++] = 0;
break;
}
mCurPos++;
}
return mFileBuffer + tokPos;
}
void FileObject::writeLine(const U8 *line)
{
stream.write(dStrlen((const char *) line), line);
stream.write(2, "\r\n");
}
void FileObject::writeObject( SimObject* object, const U8* objectPrepend )
{
if( objectPrepend == NULL )
stream.write(2, "\r\n");
else
stream.write(dStrlen((const char *) objectPrepend), objectPrepend );
object->write( stream, 0 );
stream.write(2, "\r\n");
}
ConsoleMethod( FileObject, openForRead, bool, 3, 3, "(string filename)")
{
return object->readMemory(argv[2]);
}
ConsoleMethod( FileObject, openForWrite, bool, 3, 3, "(string filename)")
{
return object->openForWrite(argv[2]);
}
ConsoleMethod( FileObject, openForAppend, bool, 3, 3, "(string filename)")
{
return object->openForWrite(argv[2], true);
}
ConsoleMethod( FileObject, isEOF, bool, 2, 2, "Are we at the end of the file?")
{
return object->isEOF();
}
ConsoleMethod( FileObject, readLine, const char*, 2, 2, "Read a line from the file.")
{
return (const char *) object->readLine();
}
ConsoleMethod( FileObject, writeLine, void, 3, 3, "(string text)"
"Write a line to the file, if it was opened for writing.")
{
object->writeLine((const U8 *) argv[2]);
}
ConsoleMethod( FileObject, close, void, 2, 2, "Close the file.")
{
object->close();
}
ConsoleMethod( FileObject, writeObject, void, 3, 4, "FileObject.writeObject(SimObject, object prepend)" )
{
SimObject* obj = Sim::findObject( argv[2] );
if( !obj )
{
Con::printf("FileObject::writeObject - Invalid Object!");
return;
}
char *objName = NULL;
if( argc == 4 )
objName = (char*)argv[3];
object->writeObject( obj, (const U8*)objName );
}

42
engine/core/fileObject.h Executable file
View File

@ -0,0 +1,42 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _FILEOBJECT_H_
#define _FILEOBJECT_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
#ifndef _FILESTREAM_H_
#include "core/fileStream.h"
#endif
class FileObject : public SimObject
{
typedef SimObject Parent;
U8 *mFileBuffer;
U32 mBufferSize;
U32 mCurPos;
FileStream stream;
public:
FileObject();
~FileObject();
bool openForWrite(const char *fileName, const bool append = false);
bool openForRead(const char *fileName);
bool readMemory(const char *fileName);
const U8 *readLine();
bool isEOF();
void writeLine(const U8 *line);
void close();
void writeObject( SimObject* object, const U8* objectPrepend = NULL );
DECLARE_CONOBJECT(FileObject);
};
#endif

496
engine/core/fileStream.cc Executable file
View File

@ -0,0 +1,496 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/fileStream.h"
#include "platform/platform.h"
//-----------------------------------------------------------------------------
// FileStream methods...
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
FileStream::FileStream()
{
// initialize the file stream
init();
}
//-----------------------------------------------------------------------------
FileStream::~FileStream()
{
// make sure the file stream is closed
close();
}
//-----------------------------------------------------------------------------
bool FileStream::hasCapability(const Capability i_cap) const
{
return(0 != (U32(i_cap) & mStreamCaps));
}
//-----------------------------------------------------------------------------
U32 FileStream::getPosition() const
{
AssertFatal(0 != mStreamCaps, "FileStream::getPosition: the stream isn't open");
AssertFatal(true == hasCapability(StreamPosition), "FileStream::getPosition(): lacks positioning capability");
// return the position inside the buffer if its valid, otherwise return the underlying file position
return((BUFFER_INVALID != mBuffHead) ? mBuffPos : mFile.getPosition());
}
//-----------------------------------------------------------------------------
bool FileStream::setPosition(const U32 i_newPosition)
{
AssertFatal(0 != mStreamCaps, "FileStream::setPosition: the stream isn't open");
AssertFatal(true == hasCapability(StreamPosition), "FileStream::setPosition: lacks positioning capability");
// if the buffer is valid, test the new position against the bounds of the buffer
if ((BUFFER_INVALID != mBuffHead) && (i_newPosition >= mBuffHead) && (i_newPosition <= mBuffTail))
{
// set the position and return
mBuffPos = i_newPosition;
if(mBuffPos < mBuffTail)
Stream::setStatus(Ok);
return(true);
}
// otherwise the new position lies in some block not in memory
else
{
// flush the buffer if its dirty
if (true == mDirty)
flush();
// and clear out the state of the file stream
clearBuffer();
// and set the new position
mFile.setPosition((S32)i_newPosition);
// update the stream to reflect the file's state
setStatus();
// taking end-of-file into consideration
if ((S32)EOS == (S32)(mFile.getStatus()))
mEOF = true;
// and return good states
return(Ok == getStatus() || EOS == getStatus());
}
}
//-----------------------------------------------------------------------------
U32 FileStream::getStreamSize()
{
AssertWarn(0 != mStreamCaps, "FileStream::getStreamSize: the stream isn't open");
AssertFatal((BUFFER_INVALID != mBuffHead && true == mDirty) || false == mDirty, "FileStream::getStreamSize: buffer must be valid if its dirty");
// the stream size may not match the size on-disk if its been written to...
if (true == mDirty)
return(getMax(mFile.getSize(), mBuffTail + 1));
// otherwise just get the size on disk...
else
return(mFile.getSize());
}
//-----------------------------------------------------------------------------
bool FileStream::open(const char *i_pFilename, AccessMode i_openMode)
{
AssertWarn(0 == mStreamCaps, "FileStream::setPosition: the stream is already open");
AssertFatal(NULL != i_pFilename, "FileStream::open: NULL filename");
// make sure the file stream's state is clean
clearBuffer();
if (File::Ok == mFile.open(i_pFilename, (File::AccessMode)i_openMode))
{
setStatus();
switch (i_openMode)
{
case Read:
mStreamCaps = U32(StreamRead) |
U32(StreamPosition);
break;
case Write:
case WriteAppend:
mStreamCaps = U32(StreamWrite) |
U32(StreamPosition);
break;
case ReadWrite:
mStreamCaps = U32(StreamRead) |
U32(StreamWrite) |
U32(StreamPosition);
break;
default:
AssertFatal(false, "FileStream::open: bad access mode");
}
}
else
{
setStatus();
return(false);
}
return(true);
}
//-----------------------------------------------------------------------------
void FileStream::close()
{
if (Closed == getStatus())
return;
// make sure nothing in the buffer differs from what is on disk
if (true == mDirty)
flush();
// and close the file
File::Status closeResult = mFile.close();
AssertFatal(File::Closed == closeResult, "FileStream::close: close failed");
// clear the file stream's state
init();
}
//-----------------------------------------------------------------------------
bool FileStream::flush()
{
AssertWarn(0 != mStreamCaps, "FileStream::flush: the stream isn't open");
AssertFatal(false == mDirty || BUFFER_INVALID != mBuffHead, "FileStream::flush: buffer must be valid if its dirty");
// if the buffer is dirty
if (true == mDirty)
{
AssertFatal(true == hasCapability(StreamWrite), "FileStream::flush: a buffer without write-capability should never be dirty");
// align the file pointer to the buffer head
if (mBuffHead != mFile.getPosition())
{
mFile.setPosition(mBuffHead);
if (File::Ok != mFile.getStatus() && File::EOS != mFile.getStatus())
return(false);
}
// write contents of the buffer to disk
U32 blockHead;
calcBlockHead(mBuffHead, &blockHead);
mFile.write(mBuffTail - mBuffHead + 1, (char *)mBuffer + (mBuffHead - blockHead));
// and update the file stream's state
setStatus();
if (EOS == getStatus())
mEOF = true;
if (Ok == getStatus() || EOS == getStatus())
// and update the status of the buffer
mDirty = false;
else
return(false);
}
return(true);
}
//-----------------------------------------------------------------------------
bool FileStream::_read(const U32 i_numBytes, void *o_pBuffer)
{
AssertFatal(0 != mStreamCaps, "FileStream::_read: the stream isn't open");
AssertFatal(NULL != o_pBuffer || i_numBytes == 0, "FileStream::_read: NULL destination pointer with non-zero read request");
if (false == hasCapability(Stream::StreamRead))
{
AssertFatal(false, "FileStream::_read: file stream lacks capability");
Stream::setStatus(IllegalCall);
return(false);
}
// exit on pre-existing errors
if (Ok != getStatus())
return(false);
// if a request of non-zero length was made
if (0 != i_numBytes)
{
U8 *pDst = (U8 *)o_pBuffer;
U32 readSize;
U32 remaining = i_numBytes;
U32 bytesRead;
U32 blockHead;
U32 blockTail;
// check if the buffer has some data in it
if (BUFFER_INVALID != mBuffHead)
{
// copy as much as possible from the buffer into the destination
readSize = ((mBuffTail + 1) >= mBuffPos) ? (mBuffTail + 1 - mBuffPos) : 0;
readSize = getMin(readSize, remaining);
calcBlockHead(mBuffPos, &blockHead);
dMemcpy(pDst, mBuffer + (mBuffPos - blockHead), readSize);
// reduce the remaining amount to read
remaining -= readSize;
// advance the buffer pointers
mBuffPos += readSize;
pDst += readSize;
if (mBuffPos > mBuffTail && remaining != 0)
{
flush();
mBuffHead = BUFFER_INVALID;
if (mEOF == true)
Stream::setStatus(EOS);
}
}
// if the request wasn't satisfied by the buffer and the file has more data
if (false == mEOF && 0 < remaining)
{
// flush the buffer if its dirty, since we now need to go to disk
if (true == mDirty)
flush();
// make sure we know the current read location in the underlying file
mBuffPos = mFile.getPosition();
calcBlockBounds(mBuffPos, &blockHead, &blockTail);
// check if the data to be read falls within a single block
if ((mBuffPos + remaining) <= blockTail)
{
// fill the buffer from disk
if (true == fillBuffer(mBuffPos))
{
// copy as much as possible from the buffer to the destination
remaining = getMin(remaining, mBuffTail - mBuffPos + 1);
dMemcpy(pDst, mBuffer + (mBuffPos - blockHead), remaining);
// advance the buffer pointer
mBuffPos += remaining;
}
else
return(false);
}
// otherwise the remaining spans multiple blocks
else
{
clearBuffer();
// read from disk directly into the destination
mFile.read(remaining, (char *)pDst, &bytesRead);
setStatus();
// check to make sure we read as much as expected
if (Ok == getStatus() || EOS == getStatus())
{
// if not, update the end-of-file status
if (0 != bytesRead && EOS == getStatus())
{
Stream::setStatus(Ok);
mEOF = true;
}
}
else
return(false);
}
}
}
return(true);
}
//-----------------------------------------------------------------------------
bool FileStream::_write(const U32 i_numBytes, const void *i_pBuffer)
{
AssertFatal(0 != mStreamCaps, "FileStream::_write: the stream isn't open");
AssertFatal(NULL != i_pBuffer || i_numBytes == 0, "FileStream::_write: NULL source buffer pointer on non-zero write request");
if (false == hasCapability(Stream::StreamWrite))
{
AssertFatal(false, "FileStream::_write: file stream lacks capability");
Stream::setStatus(IllegalCall);
return(false);
}
// exit on pre-existing errors
if (Ok != getStatus() && EOS != getStatus())
return(false);
// if a request of non-zero length was made
if (0 != i_numBytes)
{
U8 *pSrc = (U8 *)i_pBuffer;
U32 writeSize;
U32 remaining = i_numBytes;
U32 bytesWrit;
U32 blockHead;
U32 blockTail;
// check if the buffer is valid
if (BUFFER_INVALID != mBuffHead)
{
// copy as much as possible from the source to the buffer
calcBlockBounds(mBuffHead, &blockHead, &blockTail);
writeSize = (mBuffPos > blockTail) ? 0 : blockTail - mBuffPos + 1;
writeSize = getMin(writeSize, remaining);
AssertFatal(0 == writeSize || (mBuffPos - blockHead) < BUFFER_SIZE, "FileStream::_write: out of bounds buffer position");
dMemcpy(mBuffer + (mBuffPos - blockHead), pSrc, writeSize);
// reduce the remaining amount to be written
remaining -= writeSize;
// advance the buffer pointers
mBuffPos += writeSize;
mBuffTail = getMax(mBuffTail, mBuffPos - 1);
pSrc += writeSize;
// mark the buffer dirty
if (0 < writeSize)
mDirty = true;
}
// if the request wasn't satisfied by the buffer
if (0 < remaining)
{
// flush the buffer if its dirty, since we now need to go to disk
if (true == mDirty)
flush();
// make sure we know the current write location in the underlying file
mBuffPos = mFile.getPosition();
calcBlockBounds(mBuffPos, &blockHead, &blockTail);
// check if the data to be written falls within a single block
if ((mBuffPos + remaining) <= blockTail)
{
// write the data to the buffer
dMemcpy(mBuffer + (mBuffPos - blockHead), pSrc, remaining);
// update the buffer pointers
mBuffHead = mBuffPos;
mBuffPos += remaining;
mBuffTail = mBuffPos - 1;
// mark the buffer dirty
mDirty = true;
}
// otherwise the remaining spans multiple blocks
else
{
clearBuffer();
// write to disk directly from the source
mFile.write(remaining, (char *)pSrc, &bytesWrit);
setStatus();
return(Ok == getStatus() || EOS == getStatus());
}
}
}
return(true);
}
//-----------------------------------------------------------------------------
void FileStream::init()
{
mStreamCaps = 0;
Stream::setStatus(Closed);
clearBuffer();
}
//-----------------------------------------------------------------------------
bool FileStream::fillBuffer(const U32 i_startPosition)
{
AssertFatal(0 != mStreamCaps, "FileStream::fillBuffer: the stream isn't open");
AssertFatal(false == mDirty, "FileStream::fillBuffer: buffer must be clean to fill");
// make sure start position and file pointer jive
if (i_startPosition != mFile.getPosition())
{
mFile.setPosition(i_startPosition);
if (File::Ok != mFile.getStatus() && File::EOS != mFile.getStatus())
{
setStatus();
return(false);
}
else
// update buffer pointer
mBuffPos = i_startPosition;
}
// check if file pointer is at end-of-file
if (EOS == getStatus())
{
// invalidate the buffer
mBuffHead = BUFFER_INVALID;
// set the status to end-of-stream
mEOF = true;
}
// otherwise
else
{
U32 bytesRead = 0;
U32 blockHead;
// locate bounds of buffer containing current position
calcBlockHead(mBuffPos, &blockHead);
// read as much as possible from input file
mFile.read(BUFFER_SIZE - (i_startPosition - blockHead), (char *)mBuffer + (i_startPosition - blockHead), &bytesRead);
setStatus();
if (Ok == getStatus() || EOS == getStatus())
{
// update buffer pointers
mBuffHead = i_startPosition;
mBuffPos = i_startPosition;
mBuffTail = i_startPosition + bytesRead - 1;
// update end-of-file status
if (0 != bytesRead && EOS == getStatus())
{
Stream::setStatus(Ok);
mEOF = true;
}
}
else
{
mBuffHead = BUFFER_INVALID;
return(false);
}
}
return(true);
}
//-----------------------------------------------------------------------------
void FileStream::clearBuffer()
{
mBuffHead = BUFFER_INVALID;
mBuffPos = 0;
mBuffTail = 0;
mDirty = false;
mEOF = false;
}
//-----------------------------------------------------------------------------
void FileStream::calcBlockHead(const U32 i_position, U32 *o_blockHead)
{
AssertFatal(NULL != o_blockHead, "FileStream::calcBlockHead: NULL pointer passed for block head");
*o_blockHead = i_position/BUFFER_SIZE * BUFFER_SIZE;
}
//-----------------------------------------------------------------------------
void FileStream::calcBlockBounds(const U32 i_position, U32 *o_blockHead, U32 *o_blockTail)
{
AssertFatal(NULL != o_blockHead, "FileStream::calcBlockBounds: NULL pointer passed for block head");
AssertFatal(NULL != o_blockTail, "FileStream::calcBlockBounds: NULL pointer passed for block tail");
*o_blockHead = i_position/BUFFER_SIZE * BUFFER_SIZE;
*o_blockTail = *o_blockHead + BUFFER_SIZE - 1;
}
//-----------------------------------------------------------------------------
void FileStream::setStatus()
{
switch (mFile.getStatus())
{
case File::Ok:
Stream::setStatus(Ok);
break;
case File::IOError:
Stream::setStatus(IOError);
break;
case File::EOS:
Stream::setStatus(EOS);
break;
case File::IllegalCall:
Stream::setStatus(IllegalCall);
break;
case File::Closed:
Stream::setStatus(Closed);
break;
case File::UnknownError:
Stream::setStatus(UnknownError);
break;
default:
AssertFatal(false, "FileStream::setStatus: invalid error mode");
}
}

75
engine/core/fileStream.h Executable file
View File

@ -0,0 +1,75 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _FILESTREAM_H_
#define _FILESTREAM_H_
#ifndef _FILEIO_H_
#include "core/fileio.h"
#endif
#ifndef _STREAM_H_
#include "core/stream.h"
#endif
class FileStream : public Stream
{
public:
enum AccessMode
{
Read = File::Read,
Write = File::Write,
ReadWrite = File::ReadWrite,
WriteAppend = File::WriteAppend
};
enum
{
BUFFER_SIZE = 8 * 1024, // this can be changed to anything appropriate
BUFFER_INVALID = 0xffffffff // file offsets must all be less than this
};
private:
File mFile; // file being streamed
U32 mStreamCaps; // dependent on access mode
U8 mBuffer[BUFFER_SIZE];
U32 mBuffHead; // first valid position of buffer (from start-of-file)
U32 mBuffPos; // next read or write will occur here
U32 mBuffTail; // last valid position in buffer (inclusive)
bool mDirty; // whether buffer has been written to
bool mEOF; // whether disk reads have reached the end-of-file
FileStream(const FileStream &i_fileStrm); // disable copy constructor
FileStream& operator=(const FileStream &i_fileStrm); // disable assignment operator
public:
FileStream(); // default constructor
virtual ~FileStream(); // destructor
// mandatory methods from Stream base class...
virtual bool hasCapability(const Capability i_cap) const;
virtual U32 getPosition() const;
virtual bool setPosition(const U32 i_newPosition);
virtual U32 getStreamSize();
// additional methods needed for a file stream...
bool open(const char *i_pFilename, AccessMode i_openMode);
void close();
bool flush();
protected:
// more mandatory methods from Stream base class...
virtual bool _read(const U32 i_numBytes, void *o_pBuffer);
virtual bool _write(const U32 i_numBytes, const void* i_pBuffer);
void init();
bool fillBuffer(const U32 i_startPosition);
void clearBuffer();
static void calcBlockHead(const U32 i_position, U32 *o_blockHead);
static void calcBlockBounds(const U32 i_position, U32 *o_blockHead, U32 *o_blockTail);
void setStatus();
};
#endif // _FILE_STREAM_H

121
engine/core/fileio.h Executable file
View File

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _FILEIO_H_
#define _FILEIO_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
class File
{
public:
/// What is the status of our file handle?
enum Status
{
Ok = 0, ///< Ok!
IOError, ///< Read or Write error
EOS, ///< End of Stream reached (mostly for reads)
IllegalCall, ///< An unsupported operation used. Always accompanied by AssertWarn
Closed, ///< Tried to operate on a closed stream (or detached filter)
UnknownError ///< Catchall
};
/// How are we accessing the file?
enum AccessMode
{
Read = 0, ///< Open for read only, starting at beginning of file.
Write = 1, ///< Open for write only, starting at beginning of file; will blast old contents of file.
ReadWrite = 2, ///< Open for read-write.
WriteAppend = 3 ///< Write-only, starting at end of file.
};
/// Flags used to indicate what we can do to the file.
enum Capability
{
FileRead = BIT(0),
FileWrite = BIT(1)
};
private:
void *handle; ///< Pointer to the file handle.
Status currentStatus; ///< Current status of the file (Ok, IOError, etc.).
U32 capability; ///< Keeps track of file capabilities.
File(const File&); ///< This is here to disable the copy constructor.
File& operator=(const File&); ///< This is here to disable assignment.
public:
File(); ///< Default constructor
virtual ~File(); ///< Destructor
/// Opens a file for access using the specified AccessMode
///
/// @returns The status of the file
Status open(const char *filename, const AccessMode openMode);
/// Gets the current position in the file
///
/// This is in bytes from the beginning of the file.
U32 getPosition() const;
/// Sets the current position in the file.
///
/// You can set either a relative or absolute position to go to in the file.
///
/// @code
/// File *foo;
///
/// ... set up file ...
///
/// // Go to byte 32 in the file...
/// foo->setPosition(32);
///
/// // Now skip back 20 bytes...
/// foo->setPosition(-20, false);
///
/// // And forward 17...
/// foo->setPosition(17, false);
/// @endcode
///
/// @returns The status of the file
Status setPosition(S32 position, bool absolutePos = true);
/// Returns the size of the file
U32 getSize() const;
/// Make sure everything that's supposed to be written to the file gets written.
///
/// @returns The status of the file.
Status flush();
/// Closes the file
///
/// @returns The status of the file.
Status close();
/// Gets the status of the file
Status getStatus() const;
/// Reads "size" bytes from the file, and dumps data into "dst".
/// The number of actual bytes read is returned in bytesRead
/// @returns The status of the file
Status read(U32 size, char *dst, U32 *bytesRead = NULL);
/// Writes "size" bytes into the file from the pointer "src".
/// The number of actual bytes written is returned in bytesWritten
/// @returns The status of the file
Status write(U32 size, const char *src, U32 *bytesWritten = NULL);
/// Returns whether or not this file is capable of the given function.
bool hasCapability(Capability cap) const;
protected:
Status setStatus(); ///< Called after error encountered.
Status setStatus(Status status); ///< Setter for the current status.
};
#endif // _FILE_IO_H_

61
engine/core/filterStream.cc Executable file
View File

@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/filterStream.h"
FilterStream::~FilterStream()
{
//
}
bool FilterStream::_read(const U32 in_numBytes, void* out_pBuffer)
{
AssertFatal(getStream() != NULL, "Error no stream to pass to");
bool success = getStream()->read(in_numBytes, out_pBuffer);
setStatus(getStream()->getStatus());
return success;
}
bool FilterStream::_write(const U32, const void*)
{
AssertFatal(false, "No writing allowed to filter");
return false;
}
bool FilterStream::hasCapability(const Capability in_streamCap) const
{
// Fool the compiler. We know better...
FilterStream* ncThis = const_cast<FilterStream*>(this);
AssertFatal(ncThis->getStream() != NULL, "Error no stream to pass to");
return ncThis->getStream()->hasCapability(in_streamCap);
}
U32 FilterStream::getPosition() const
{
// Fool the compiler. We know better...
FilterStream* ncThis = const_cast<FilterStream*>(this);
AssertFatal(ncThis->getStream() != NULL, "Error no stream to pass to");
return ncThis->getStream()->getPosition();
}
bool FilterStream::setPosition(const U32 in_newPosition)
{
AssertFatal(getStream() != NULL, "Error no stream to pass to");
return getStream()->setPosition(in_newPosition);
}
U32 FilterStream::getStreamSize()
{
AssertFatal(getStream() != NULL, "Error no stream to pass to");
return getStream()->getStreamSize();
}

39
engine/core/filterStream.h Executable file
View File

@ -0,0 +1,39 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _FILTERSTREAM_H_
#define _FILTERSTREAM_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _STREAM_H_
#include "core/stream.h"
#endif
class FilterStream : public Stream
{
public:
virtual ~FilterStream();
virtual bool attachStream(Stream* io_pSlaveStream) = 0;
virtual void detachStream() = 0;
virtual Stream* getStream() = 0;
// Mandatory overrides. By default, these are simply passed to
// whatever is returned from getStream();
protected:
bool _read(const U32 in_numBytes, void* out_pBuffer);
bool _write(const U32 in_numBytes, const void* in_pBuffer);
public:
bool hasCapability(const Capability) const;
U32 getPosition() const;
bool setPosition(const U32 in_newPosition);
U32 getStreamSize();
};
#endif //_FILTERSTREAM_H_

142
engine/core/findMatch.cc Executable file
View File

@ -0,0 +1,142 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/findMatch.h"
//--------------------------------------------------------------------------------
// NAME
// FindMatch::FindMatch( const char *_expression, S32 maxNumMatches )
//
// DESCRIPTION
// Class to match regular expressions (file names)
// only works with '*','?', and 'chars'
//
// ARGUMENTS
// _expression - The regular expression you intend to match (*.??abc.bmp)
// _maxMatches - The maximum number of strings you wish to match.
//
// RETURNS
//
// NOTES
//
//--------------------------------------------------------------------------------
FindMatch::FindMatch( U32 _maxMatches )
{
VECTOR_SET_ASSOCIATION(matchList);
expression = NULL;
maxMatches = _maxMatches;
matchList.reserve( maxMatches );
}
FindMatch::FindMatch( char *_expression, U32 _maxMatches )
{
VECTOR_SET_ASSOCIATION(matchList);
expression = NULL;
setExpression( _expression );
maxMatches = _maxMatches;
matchList.reserve( maxMatches );
}
FindMatch::~FindMatch()
{
delete [] expression;
matchList.clear();
}
void FindMatch::setExpression( const char *_expression )
{
delete [] expression;
expression = new char[dStrlen(_expression) + 1];
dStrcpy(expression, _expression);
dStrupr(expression);
}
bool FindMatch::findMatch( const char *str, bool caseSensitive )
{
if ( isFull() )
return false;
char nstr[512];
dStrcpy( nstr,str );
dStrupr(nstr);
if ( isMatch( expression, nstr, caseSensitive ) )
{
matchList.push_back( (char*)str );
return true;
}
return false;
}
bool FindMatch::isMatch( const char *exp, const char *str, bool caseSensitive )
{
const char *e=exp;
const char *s=str;
bool match=true;
while ( match && *e && *s )
{
switch( *e )
{
case '*':
e++;
match = false;
while( ((s=dStrchr(s,*e)) !=NULL) && !match )
{
match = isMatch( e, s, caseSensitive );
s++;
}
return( match );
case '?':
e++;
s++;
break;
default:
if (caseSensitive) match = ( *e++ == *s++ );
else match = ( dToupper(*e++) == dToupper(*s++) );
break;
}
}
if (*e != *s) // both exp and str should be at '\0' if match was successfull
match = false;
return ( match );
}
bool FindMatch::isMatchMultipleExprs( const char *exps, const char *str, bool caseSensitive )
{
char *tok = 0;
int len = dStrlen(exps);
char *e = new char[len+1];
dStrcpy(e,exps);
// allow for either tab or space seperated.
for(int i=0;i<len;i++)
{
if(e[i]=='\t')
e[i]=' ';
}
// search for each expression. return true soon as we see one.
for( tok = dStrtok(e," "); tok != NULL; tok = dStrtok(NULL," "))
{
if( isMatch( tok, str, caseSensitive) )
{
delete []e;
return true;
}
}
delete []e;
return false;
}

35
engine/core/findMatch.h Executable file
View File

@ -0,0 +1,35 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _FINDMATCH_H_
#define _FINDMATCH_H_
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
class FindMatch
{
char* expression;
U32 maxMatches;
public:
static bool isMatch( const char *exp, const char *string, bool caseSensitive = false );
static bool isMatchMultipleExprs( const char *exps, const char *str, bool caseSensitive );
Vector<char *> matchList;
FindMatch( U32 _maxMatches = 256 );
FindMatch( char *_expression, U32 _maxMatches = 256 );
~FindMatch();
bool findMatch(const char *string, bool caseSensitive = false);
void setExpression( const char *_expression );
S32 numMatches() const { return(matchList.size()); }
bool isFull() const { return (matchList.size() >= maxMatches); }
void clear() { matchList.clear(); }
};
#endif // _FINDMATCH_H_

21
engine/core/frameAllocator.cc Executable file
View File

@ -0,0 +1,21 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/frameAllocator.h"
#include "console/console.h"
U8* FrameAllocator::smBuffer = NULL;
U32 FrameAllocator::smWaterMark = 0;
U32 FrameAllocator::smHighWaterMark = 0;
#if defined(TORQUE_DEBUG)
ConsoleFunction(getMaxFrameAllocation, S32, 1,1, "getMaxFrameAllocation();")
{
argc, argv;
return sgMaxFrameAllocation;
}
#endif

242
engine/core/frameAllocator.h Executable file
View File

@ -0,0 +1,242 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _FRAMEALLOCATOR_H_
#define _FRAMEALLOCATOR_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
/// Temporary memory pool for per-frame allocations.
///
/// In the course of rendering a frame, it is often necessary to allocate
/// many small chunks of memory, then free them all in a batch. For instance,
/// say we're allocating storage for some vertex calculations:
///
/// @code
/// // Get FrameAllocator memory...
/// U32 waterMark = FrameAllocator::getWaterMark();
/// F32 * ptr = (F32*)FrameAllocator::alloc(sizeof(F32)*2*targetMesh->vertsPerFrame);
///
/// ... calculations ...
///
/// // Free frameAllocator memory
/// FrameAllocator::setWaterMark(waterMark);
/// @endcode
class FrameAllocator
{
static U8* smBuffer;
static U32 smHighWaterMark;
static U32 smWaterMark;
public:
inline static void init(const U32 frameSize);
inline static void destroy();
inline static void* alloc(const U32 allocSize);
inline static void setWaterMark(const U32);
inline static U32 getWaterMark();
inline static U32 getHighWaterMark();
};
#if defined(TORQUE_DEBUG)
static S32 sgMaxFrameAllocation = 0;
#endif
void FrameAllocator::init(const U32 frameSize)
{
AssertFatal(smBuffer == NULL, "Error, already initialized");
smBuffer = new U8[frameSize];
smWaterMark = 0;
smHighWaterMark = frameSize;
}
void FrameAllocator::destroy()
{
AssertFatal(smBuffer != NULL, "Error, not initialized");
delete [] smBuffer;
smBuffer = NULL;
smWaterMark = 0;
smHighWaterMark = 0;
}
void* FrameAllocator::alloc(const U32 allocSize)
{
U32 _allocSize = allocSize;
#if defined(FRAMEALLOCATOR_DEBUG_GUARD)
_allocSize+=4;
#endif
AssertFatal(smBuffer != NULL, "Error, no buffer!");
AssertFatal(smWaterMark + _allocSize <= smHighWaterMark, "Error alloc too large, increase frame size!");
U8* p = &smBuffer[smWaterMark];
smWaterMark += _allocSize;
#if defined(TORQUE_DEBUG)
if (smWaterMark > sgMaxFrameAllocation)
sgMaxFrameAllocation = smWaterMark;
#endif
#if defined(FRAMEALLOCATOR_DEBUG_GUARD)
U32 *flag = (U32*) &smBuffer[smWaterMark-4];
*flag = 0xdeadbeef ^ smWaterMark;
#endif
return p;
}
void FrameAllocator::setWaterMark(const U32 waterMark)
{
AssertFatal(waterMark < smHighWaterMark, "Error, invalid waterMark");
#if defined(FRAMEALLOCATOR_DEBUG_GUARD)
if(smWaterMark >= 4 )
{
U32 *flag = (U32*) &smBuffer[smWaterMark-4];
AssertFatal( *flag == 0xdeadbeef ^ smWaterMark, "FrameAllocator guard overwritten!");
}
#endif
smWaterMark = waterMark;
}
U32 FrameAllocator::getWaterMark()
{
return smWaterMark;
}
U32 FrameAllocator::getHighWaterMark()
{
return smHighWaterMark;
}
/// Helper class to deal with FrameAllocator usage.
///
/// The purpose of this class is to make it simpler and more reliable to use the
/// FrameAllocator. Simply use it like this:
///
/// @code
/// FrameAllocatorMarker mem;
///
/// char *buff = (char*)mem.alloc(100);
/// @endcode
///
/// When you leave the scope you defined the FrameAllocatorMarker in, it will
/// automatically restore the watermark on the FrameAllocator. In situations
/// with complex branches, this can be a significant headache remover, as you
/// don't have to remember to reset the FrameAllocator on every posssible branch.
class FrameAllocatorMarker
{
U32 mMarker;
public:
FrameAllocatorMarker()
{
mMarker = FrameAllocator::getWaterMark();
}
~FrameAllocatorMarker()
{
FrameAllocator::setWaterMark(mMarker);
}
void* alloc(const U32 allocSize) const
{
return FrameAllocator::alloc(allocSize);
}
};
/// Class for temporary variables that you want to allocate easily using
/// the FrameAllocator. For example:
/// @code
/// FrameTemp<char> tempStr(32); // NOTE! This parameter is NOT THE SIZE IN BYTES. See constructor docs.
/// dStrcat( tempStr, SomeOtherString );
/// tempStr[2] = 'l';
/// Con::printf( tempStr );
/// Con::printf( "Foo: %s", ~tempStr );
/// @endcode
///
/// This will automatically handle getting and restoring the watermark of the
/// FrameAllocator when it goes out of scope. You should notice the strange
/// operator infront of tempStr on the printf call. This is normally a unary
/// operator for ones-complement, but in this class it will simply return the
/// memory of the allocation. It's the same as doing (const char *)tempStr
/// in the above case. The reason why it is necessary for the second printf
/// and not the first is because the second one is taking a variable arg
/// list and so it isn't getting the cast so that it's cast operator can
/// properly return the memory instead of the FrameTemp object itself.
///
/// @note It is important to note that this object is designed to just be a
/// temporary array of a dynamic size. Some wierdness may occur if you try
/// do perform crazy pointer stuff with it using regular operators on it.
/// I implemented what I thought were the most common operators that it
/// would be used for. If strange things happen, you will need to debug
/// them yourself.
template<class T>
class FrameTemp
{
protected:
U32 mWaterMark;
T *mMemory;
public:
/// Constructor will store the FrameAllocator watermark and allocate the memory off
/// of the FrameAllocator.
///
/// @note It is important to note that, unlike the FrameAllocatorMarker and the
/// FrameAllocator itself, the argument to allocate is NOT the size in bytes,
/// doing:
/// @code
/// FrameTemp<F64> f64s(5);
/// @endcode
/// Is the same as
/// @code
/// F64 *f64s = new F64[5];
/// @endcode
///
/// @param count The number of objects to allocate
FrameTemp( const U32 count = 1 )
{
AssertFatal( count > 0, "Allocating a FrameTemp with less than one instance" );
mWaterMark = FrameAllocator::getWaterMark();
mMemory = static_cast<T *>( FrameAllocator::alloc( sizeof( T ) * count ) );
}
/// Destructor restores the watermark
~FrameTemp()
{
FrameAllocator::setWaterMark( mWaterMark );
}
/// NOTE: This will return the memory, NOT perform a ones-complement
T* operator ~() { return mMemory; };
/// NOTE: This will return the memory, NOT perform a ones-complement
const T* operator ~() const { return mMemory; };
/// NOTE: This will dereference the memory, NOT do standard unary plus behavior
T& operator +() { return *mMemory; };
/// NOTE: This will dereference the memory, NOT do standard unary plus behavior
const T& operator +() const { return *mMemory; };
T& operator *() { return *mMemory; };
const T& operator *() const { return *mMemory; };
T** operator &() { return &mMemory; };
const T** operator &() const { return &mMemory; };
operator T*() { return mMemory; }
operator const T*() const { return mMemory; }
operator T&() { return *mMemory; }
operator const T&() const { return *mMemory; }
operator T() { return *mMemory; }
operator const T() const { return *mMemory; }
};
#endif // _H_FRAMEALLOCATOR_

87
engine/core/iTickable.cc Executable file
View File

@ -0,0 +1,87 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/iTickable.h"
// The statics
U32 ITickable::smLastTick = 0;
U32 ITickable::smLastTime = 0;
U32 ITickable::smLastDelta = 0;
const F32 ITickable::smTickSec = ( F32( ITickable::smTickMs ) / 1000.f );
const U32 ITickable::smTickShift = 5;
const U32 ITickable::smTickMs = ( 1 << smTickShift );
const U32 ITickable::smTickMask = ( smTickMs - 1 );
//------------------------------------------------------------------------------
ITickable::ITickable()
{
getProcessList().push_back( this );
}
//------------------------------------------------------------------------------
ITickable::~ITickable()
{
for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ )
{
if( (*i) == this )
{
getProcessList().erase( i );
return;
}
}
}
//------------------------------------------------------------------------------
Vector<ITickable *>& ITickable::getProcessList()
{
// This helps to avoid the static initialization order fiasco
static Vector<ITickable *> smProcessList; ///< List of tick controls
return smProcessList;
}
//------------------------------------------------------------------------------
bool ITickable::advanceTime( U32 timeDelta )
{
U32 targetTime = smLastTime + timeDelta;
U32 targetTick = ( targetTime + smTickMask ) & ~smTickMask;
U32 tickCount = ( targetTick - smLastTick ) >> smTickShift;
// If we are going to send a tick, call interpolateTick(0) so that the objects
// will reset back to their position at the last full tick
if( smLastDelta && tickCount )
for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ )
if( (*i)->isProcessingTicks() )
(*i)->interpolateTick( 0.f );
// Advance objects
if( tickCount )
for( ; smLastTick != targetTick; smLastTick += smTickMs )
for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ )
if( (*i)->isProcessingTicks() )
(*i)->processTick();
smLastDelta = ( smTickMs - ( targetTime & smTickMask ) ) & smTickMask;
F32 dt = smLastDelta / F32( smTickMs );
// Now interpolate objects that want ticks
for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ )
if( (*i)->isProcessingTicks() )
(*i)->interpolateTick( dt );
// Inform ALL objects that time was advanced
dt = F32( timeDelta ) / 1000.f;
for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ )
(*i)->advanceTime( dt );
smLastTime = targetTime;
return tickCount != 0;
}

141
engine/core/iTickable.h Executable file
View File

@ -0,0 +1,141 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _ITICKABLE_H_
#define _ITICKABLE_H_
#include "core/tVector.h"
/// This interface allows you to let any object be ticked. You use it like so:
/// @code
/// class FooClass : public SimObject, public virtual ITickable
/// {
/// // You still mark SimObject as Parent
/// typdef SimObject Parent;
/// private:
/// ...
///
/// protected:
/// // These three methods are the interface for ITickable
/// virtual void interpolateTick( F32 delta );
/// virtual void processTick();
/// virtual void advanceTime( F32 timeDelta );
///
/// public:
/// ...
/// };
/// @endcode
/// Please note the three methods you must implement to use ITickable, but don't
/// worry. If you forget, the compiler will tell you so. Also note that the
/// typedef for Parent should NOT BE SET to ITickable, the compiler will <i>probably</i>
/// also tell you if you forget that. Last, but assuridly not least is that you note
/// the way that the inheretance is done: public <b>virtual</b> ITickable
/// It is very important that you keep the virtual keyword in there, otherwise
/// proper behavior is not guarenteed. You have been warned.
///
/// The point of a tickable object is that the object gets ticks at a fixed rate
/// which is one tick every 32ms. This means, also, that if an object doesn't get
/// updated for 64ms, that the next update it will get two-ticks. Basically it
/// comes down to this. You are assured to get one tick per 32ms of time passing
/// provided that isProcessingTicks returns true when ITickable calls it.
///
/// isProcessingTicks is a virtual method and you can (should you want to)
/// override it and put some extended functionality to decide if you want to
/// recieve tick-notification or not.
///
/// The other half of this is that you get time-notification from advanceTime.
/// advanceTime lets you know when time passes regardless of the return value
/// of isProcessingTicks. The object WILL get the advanceTime call every single
/// update. The argument passed to advanceTime is the time since the last call
/// to advanceTime. Updates are not based on the 32ms tick time. Updates are
/// dependant on framerate. So you may get 200 advanceTime calls in a second, or you
/// may only get 20. There is no way of assuring consistant calls of advanceTime
/// like there is with processTick. Both are useful for different things, and
/// it is important to understand the differences between them.
///
/// Interpolation is the last part of the ITickable interface. It is called
/// every update, as long as isProcessingTicks evaluates to true on the object.
/// This is used to interpolate between 32ms ticks. The argument passed to
/// interpolateTick is the time since the last call to processTick. You can see
/// in the code for ITickable::advanceTime that before a tick occurs it calls
/// interpolateTick(0) on every object. This is to tell objects which do interpolate
/// between ticks to reset their interpolation because they are about to get a
/// new tick.
///
/// This is an extremely powerful interface when used properly. An example of a class
/// that properly uses this interface is GuiTickCtrl. The documentation for that
/// class describes why it was created and why it was important that it use
/// a consistant update frequency for its effects.
/// @see GuiTickCtrl
///
/// @todo Support processBefore/After and move the GameBase processing over to use ITickable
class ITickable
{
private:
static U32 smLastTick; ///< Time of the last tick that occurred
static U32 smLastTime; ///< Last time value at which advanceTime was called
static U32 smLastDelta; ///< Last delta value for advanceTime
// This just makes life easy
typedef Vector<ITickable *>::iterator ProcessListIterator;
/// Returns a reference to the list of all ITickable objects.
static Vector<ITickable *>& getProcessList();
protected:
bool mProcessTick; ///< Set to true if this object wants tick processing
/// This method is called every frame and lets the control interpolate between
/// ticks so you can smooth things as long as isProcessingTicks returns true
/// when it is called on the object
virtual void interpolateTick( F32 delta ) = 0;
/// This method is called once every 32ms if isProcessingTicks returns true
/// when called on the object
virtual void processTick() = 0;
/// This method is called once every frame regardless of the return value of
/// isProcessingTicks and informs the object of the passage of time
virtual void advanceTime( F32 timeDelta ) = 0;
public:
// Can let everyone look at these if they want to
static const U32 smTickShift; ///< Shift value to control how often Ticks occur
static const U32 smTickMs; ///< Number of milliseconds per tick, 32 in this case
static const F32 smTickSec; ///< Fraction of a second per tick
static const U32 smTickMask;
/// Constructor
/// This will add the object to the process list
ITickable();
/// Destructor
/// Remove this object from the process list
virtual ~ITickable();
/// Is this object wanting to receive tick notifications
/// @returns True if object wants tick notifications
virtual bool isProcessingTicks() const { return mProcessTick; };
/// Sets this object as either tick processing or not
/// @parm tick True if this object should process ticks
virtual void setProcessTicks( bool tick = true );
//------------------------------------------------------------------------------
/// This is called in clientProcess to advance the time for all ITickable
/// objects
/// @returns True if any ticks were sent
/// @see clientProcess
static bool advanceTime( U32 timeDelta );
};
//------------------------------------------------------------------------------
inline void ITickable::setProcessTicks( bool tick /* = true */ )
{
mProcessTick = tick;
}
#endif

19
engine/core/idGenerator.cc Executable file
View File

@ -0,0 +1,19 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/idGenerator.h"
void IdGenerator::reclaim()
{
// attempt to keep the pool vector as small as possible by reclaiming
// pool entries back into the nextIdBlock variable
while (!mPool.empty() && (mPool.last() == (mNextId-1)) )
{
mNextId--;
mPool.pop_back();
}
}

76
engine/core/idGenerator.h Executable file
View File

@ -0,0 +1,76 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _IDGENERATOR_H_
#define _IDGENERATOR_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
class IdGenerator
{
private:
U32 mIdBlockBase;
U32 mIdRangeSize;
Vector<U32> mPool;
U32 mNextId;
void reclaim();
public:
IdGenerator(U32 base, U32 numIds)
{
VECTOR_SET_ASSOCIATION(mPool);
mIdBlockBase = base;
mIdRangeSize = numIds;
mNextId = mIdBlockBase;
}
void reset()
{
mPool.clear();
mNextId = mIdBlockBase;
}
U32 alloc()
{
// fist check the pool:
if(!mPool.empty())
{
U32 id = mPool.last();
mPool.pop_back();
reclaim();
return id;
}
if(mIdRangeSize && mNextId >= mIdBlockBase + mIdRangeSize)
return 0;
return mNextId++;
}
void free(U32 id)
{
AssertFatal(id >= mIdBlockBase, "IdGenerator::alloc: invalid id, id does not belong to this IdGenerator.")
if(id == mNextId - 1)
{
mNextId--;
reclaim();
}
else
mPool.push_back(id);
}
U32 numIdsUsed()
{
return mNextId - mIdBlockBase - mPool.size();
}
};
#endif

306
engine/core/llist.h Executable file
View File

@ -0,0 +1,306 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef LLIST_H
#define LLIST_H
//***************************************************************************************
// Linked List
//***************************************************************************************
// This template has an overhead of size LListNode for each entry in the linked list -
// be aware of this for very large linked lists.
//
// This template has no destructor! You must explicitly call free() if you want the
// contents of the list to be destroyed.
//
//
// WARNING - this template has not been thoroughly tested so there may be bugs in it!
//
// -Bramage
//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------
// LListNode template data node
//---------------------------------------------------------------------------------------
template <class T> class LListNode
{
public:
LListNode<T> * Next;
LListNode<T> * Prev;
T * Data;
LListNode()
{
Next = NULL;
Prev = NULL;
Data = NULL;
}
};
//---------------------------------------------------------------------------------------
// LList template
//---------------------------------------------------------------------------------------
template <class T> class LList
{
protected:
LListNode< T > *first_entry;
LListNode< T > *last_entry;
int cnt;
public:
//---------------------------------------------------------------------------------------
// Constructor initializes empty list
//---------------------------------------------------------------------------------------
LList()
{
reset();
}
//---------------------------------------------------------------------------------------
// Reset list to empty state by abandoning contents
//---------------------------------------------------------------------------------------
void reset(void)
{
first_entry = NULL;
last_entry = NULL;
cnt = 0;
}
//---------------------------------------------------------------------------------------
// Return entry count
//---------------------------------------------------------------------------------------
int size(void) const
{
return cnt;
}
//---------------------------------------------------------------------------------------
// Return first list entry (NULL if list empty)
//---------------------------------------------------------------------------------------
T *first(void) const
{
if( first_entry ){
return first_entry->Data;
}
else
{
return NULL;
}
}
//---------------------------------------------------------------------------------------
// Return last list entry (NULL if list empty)
//---------------------------------------------------------------------------------------
T *last(void) const
{
if( last_entry )
{
return last_entry->Data;
}
else
{
return NULL;
}
}
//---------------------------------------------------------------------------------------
// Returns next entry - returns first entry if current == NULL
//---------------------------------------------------------------------------------------
T *next( T* current )
{
if( current == NULL )
{
return first();
}
LListNode<T> *next = findNode( current )->Next;
if( next )
{
return next->Data;
}
else
{
return NULL;
}
}
//---------------------------------------------------------------------------------------
// Returns prev entry - returns last entry if current == NULL
//---------------------------------------------------------------------------------------
T *prev( T *current )
{
if( current == NULL )
{
return last();
}
LListNode<T> *prev = findNode( current )->Prev;
if( prev )
{
return prev->Data;
}
else
{
return NULL;
}
}
//---------------------------------------------------------------------------------------
// Link new item into list before specified entry
// If specified entry==NULL, insert at end of list
//---------------------------------------------------------------------------------------
T *link(T *entry, T *next = NULL)
{
LListNode<T> *prevNode = NULL;
LListNode<T> *nextNode = findNode( next );
LListNode<T> *newNode = new LListNode<T>;
newNode->Data = entry;
if( nextNode == NULL)
{
prevNode = last_entry;
last_entry = newNode;
}
else
{
prevNode = nextNode->Prev;
nextNode->Prev = newNode;
}
if( prevNode == NULL )
{
first_entry = newNode;
}
else
{
prevNode->Next = newNode;
}
newNode->Next = nextNode;
newNode->Prev = prevNode;
++cnt;
return entry;
}
//---------------------------------------------------------------------------------------
// Link new item into list before specified entry
// If specified entry==NULL, insert at end of list
//---------------------------------------------------------------------------------------
T *link(T &entry, T *next = NULL)
{
T *newEntry = new T;
*newEntry = entry;
return link( newEntry, next );
}
//---------------------------------------------------------------------------------------
// Unlink item from list (without destroying it)
//---------------------------------------------------------------------------------------
void unlink(T *entry)
{
LListNode<T> *entryNode = findNode( entry );
if( !entryNode ) return;
if( entryNode->Prev == NULL )
{
first_entry = entryNode->Next;
}
else
{
entryNode->Prev->Next = entryNode->Next;
}
if( entryNode->Next == NULL )
{
last_entry = entryNode->Prev;
}
else
{
entryNode->Next->Prev = entryNode->Prev;
}
delete entryNode;
--cnt;
}
//---------------------------------------------------------------------------------------
// Allocate entry and insert before specified entry
// If specified entry==NULL, insert at end of list
//---------------------------------------------------------------------------------------
T * alloc( T *next = NULL )
{
T *entry = new T;
if( entry == NULL )
{
return NULL;
}
return link( entry, next );
}
//---------------------------------------------------------------------------------------
// Unlink item from list and destroy it
//---------------------------------------------------------------------------------------
void free(T *entry)
{
unlink(entry);
delete entry;
}
//---------------------------------------------------------------------------------------
// Unlink and destroy all list items
//---------------------------------------------------------------------------------------
void free(void)
{
LListNode<T> *node = NULL;
while( (node = iterate( node ) )!=NULL )
{
LListNode<T> *nodeToKill = node;
node = node->Prev;
free( nodeToKill->Data );
}
}
//---------------------------------------------------------------------------------------
// Find node
//---------------------------------------------------------------------------------------
LListNode<T> *findNode( T *entry )
{
LListNode<T> *it = NULL;
while( (it = iterate( it ) )!=NULL )
{
if( it->Data == entry )
{
return it;
}
}
return NULL;
}
//---------------------------------------------------------------------------------------
// Returns the next node in list
//---------------------------------------------------------------------------------------
LListNode<T> *iterate( LListNode<T> *entry = NULL )
{
if( entry == NULL ) return first_entry;
return entry->Next;
}
};
#endif

157
engine/core/memStream.cc Executable file
View File

@ -0,0 +1,157 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/memstream.h"
MemStream::MemStream(const U32 in_bufferSize,
void* io_pBuffer,
const bool in_allowRead,
const bool in_allowWrite)
: cm_bufferSize(in_bufferSize),
m_pBufferBase(io_pBuffer),
m_instCaps(0),
m_currentPosition(0)
{
AssertFatal(io_pBuffer != NULL, "Invalid buffer pointer");
AssertFatal(in_bufferSize > 0, "Invalid buffer size");
AssertFatal(in_allowRead || in_allowWrite, "Either write or read must be allowed");
if (in_allowRead)
m_instCaps |= Stream::StreamRead;
if (in_allowWrite)
m_instCaps |= Stream::StreamWrite;
setStatus(Ok);
}
MemStream::~MemStream()
{
m_pBufferBase = NULL;
m_currentPosition = 0;
setStatus(Closed);
}
U32 MemStream::getStreamSize()
{
AssertFatal(getStatus() != Closed, "Stream not open, size undefined");
return cm_bufferSize;
}
bool MemStream::hasCapability(const Capability in_cap) const
{
// Closed streams can't do anything
//
if (getStatus() == Closed)
return false;
U32 totalCaps = U32(Stream::StreamPosition) | m_instCaps;
return (U32(in_cap) & totalCaps) != 0;
}
U32 MemStream::getPosition() const
{
AssertFatal(getStatus() != Closed, "Position of a closed stream is undefined");
return m_currentPosition;
}
bool MemStream::setPosition(const U32 in_newPosition)
{
AssertFatal(getStatus() != Closed, "SetPosition of a closed stream is not allowed");
AssertFatal(in_newPosition <= cm_bufferSize, "Invalid position");
m_currentPosition = in_newPosition;
if (m_currentPosition > cm_bufferSize) {
// Never gets here in debug version, this is for the release builds...
//
setStatus(UnknownError);
return false;
} else if (m_currentPosition == cm_bufferSize) {
setStatus(EOS);
} else {
setStatus(Ok);
}
return true;
}
bool MemStream::_read(const U32 in_numBytes, void *out_pBuffer)
{
AssertFatal(getStatus() != Closed, "Attempted read from a closed stream");
if (in_numBytes == 0)
return true;
AssertFatal(out_pBuffer != NULL, "Invalid output buffer");
if (hasCapability(StreamRead) == false) {
AssertWarn(false, "Reading is disallowed on this stream");
setStatus(IllegalCall);
return false;
}
bool success = true;
U32 actualBytes = in_numBytes;
if ((m_currentPosition + in_numBytes) > cm_bufferSize) {
success = false;
actualBytes = cm_bufferSize - m_currentPosition;
}
// Obtain a current pointer, and do the copy
const void* pCurrent = (const void*)((const U8*)m_pBufferBase + m_currentPosition);
dMemcpy(out_pBuffer, pCurrent, actualBytes);
// Advance the stream position
m_currentPosition += actualBytes;
if (!success)
setStatus(EOS);
else
setStatus(Ok);
return success;
}
bool MemStream::_write(const U32 in_numBytes, const void *in_pBuffer)
{
AssertFatal(getStatus() != Closed, "Attempted write to a closed stream");
if (in_numBytes == 0)
return true;
AssertFatal(in_pBuffer != NULL, "Invalid input buffer");
if (hasCapability(StreamWrite) == false) {
AssertWarn(0, "Writing is disallowed on this stream");
setStatus(IllegalCall);
return false;
}
bool success = true;
U32 actualBytes = in_numBytes;
if ((m_currentPosition + in_numBytes) > cm_bufferSize) {
success = false;
actualBytes = cm_bufferSize - m_currentPosition;
}
// Obtain a current pointer, and do the copy
void* pCurrent = (void*)((U8*)m_pBufferBase + m_currentPosition);
dMemcpy(pCurrent, in_pBuffer, actualBytes);
// Advance the stream position
m_currentPosition += actualBytes;
if (m_currentPosition == cm_bufferSize)
setStatus(EOS);
else
setStatus(Ok);
return success;
}

45
engine/core/memstream.h Executable file
View File

@ -0,0 +1,45 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _MEMSTREAM_H_
#define _MEMSTREAM_H_
//Includes
#ifndef _STREAM_H_
#include "core/stream.h"
#endif
class MemStream : public Stream {
typedef Stream Parent;
protected:
U32 const cm_bufferSize;
void* m_pBufferBase;
U32 m_instCaps;
U32 m_currentPosition;
public:
MemStream(const U32 in_bufferSize,
void* io_pBuffer,
const bool in_allowRead = true,
const bool in_allowWrite = true);
~MemStream();
// Mandatory overrides from Stream
protected:
bool _read(const U32 in_numBytes, void* out_pBuffer);
bool _write(const U32 in_numBytes, const void* in_pBuffer);
public:
bool hasCapability(const Capability) const;
U32 getPosition() const;
bool setPosition(const U32 in_newPosition);
// Mandatory overrides from Stream
public:
U32 getStreamSize();
};
#endif //_MEMSTREAM_H_

169
engine/core/nStream.cc Executable file
View File

@ -0,0 +1,169 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/stream.h"
#include "core/stringTable.h"
#include "core/color.h"
Stream::Stream()
: m_streamStatus(Closed)
{
//
}
Stream::~Stream()
{
//
}
const char* Stream::getStatusString(const Status in_status)
{
switch (in_status) {
case Ok:
return "StreamOk";
case IOError:
return "StreamIOError";
case EOS:
return "StreamEOS";
case IllegalCall:
return "StreamIllegalCall";
case Closed:
return "StreamClosed";
case UnknownError:
return "StreamUnknownError";
default:
return "Invalid Stream::Status";
}
}
void Stream::writeString(const char *string, S32 maxLen)
{
S32 len = string ? dStrlen(string) : 0;
if(len > maxLen)
len = maxLen;
write(U8(len));
if(len)
write(len, string);
}
void Stream::readString(char buf[256])
{
U8 len;
read(&len);
read(S32(len), buf);
buf[len] = 0;
}
const char *Stream::readSTString(bool casesens)
{
char buf[256];
readString(buf);
return StringTable->insert(buf, casesens);
}
void Stream::readLongString(U32 maxStringLen, char *stringBuf)
{
U32 len;
read(&len);
if(len > maxStringLen)
{
m_streamStatus = IOError;
return;
}
read(len, stringBuf);
stringBuf[len] = 0;
}
void Stream::writeLongString(U32 maxStringLen, const char *string)
{
U32 len = dStrlen(string);
if(len > maxStringLen)
len = maxStringLen;
write(len);
write(len, string);
}
void Stream::readLine(U8 *buffer, U32 bufferSize)
{
bufferSize--; // account for NULL terminator
U8 *buff = buffer;
U8 *buffEnd = buff + bufferSize;
*buff = '\r';
// strip off preceding white space
while ( *buff == '\r' )
{
if ( !read(buff) || *buff == '\n' )
{
*buff = 0;
return;
}
}
// read line
while ( buff != buffEnd && read(++buff) && *buff != '\n' )
{
if ( *buff == '\r' )
{
#if defined(TORQUE_OS_MAC)
U32 pushPos = getPosition(); // in case we need to back up.
if (read(buff)) // feeling free to overwrite the \r as the NULL below will overwrite again...
if (*buff != '\n') // then push our position back.
setPosition(pushPos);
break; // we're always done after seeing the CR...
#else
buff--; // 'erases' the CR of a CRLF
#endif
}
}
*buff = 0;
}
void Stream::writeLine(U8 *buffer)
{
write(dStrlen((StringTableEntry)buffer), buffer);
write(2, "\r\n");
}
bool Stream::write(const ColorI& rColor)
{
bool success = write(rColor.red);
success |= write(rColor.green);
success |= write(rColor.blue);
success |= write(rColor.alpha);
return success;
}
bool Stream::write(const ColorF& rColor)
{
ColorI temp = rColor;
return write(temp);
}
bool Stream::read(ColorI* pColor)
{
bool success = read(&pColor->red);
success |= read(&pColor->green);
success |= read(&pColor->blue);
success |= read(&pColor->alpha);
return success;
}
bool Stream::read(ColorF* pColor)
{
ColorI temp;
bool success = read(&temp);
*pColor = temp;
return success;
}

40
engine/core/nTypes.cc Executable file
View File

@ -0,0 +1,40 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
//--------------------------------------
U32 getNextPow2(U32 io_num)
{
S32 oneCount = 0;
S32 shiftCount = -1;
while (io_num) {
if(io_num & 1)
oneCount++;
shiftCount++;
io_num >>= 1;
}
if(oneCount > 1)
shiftCount++;
return U32(1 << shiftCount);
}
//--------------------------------------
U32 getBinLog2(U32 io_num)
{
AssertFatal(io_num != 0 && isPow2(io_num) == true,
"Error, this only works on powers of 2 > 0");
S32 shiftCount = 0;
while (io_num) {
shiftCount++;
io_num >>= 1;
}
return U32(shiftCount - 1);
}

54
engine/core/polyList.h Executable file
View File

@ -0,0 +1,54 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _POLYLIST_H_
#define _POLYLIST_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _MPOINT_H_
#include "math/mPoint.h"
#endif
#ifndef _MPLANE_H_
#include "math/mPlane.h"
#endif
class PolyList
{
public:
struct Poly {
public:
PlaneF plane;
U32 material;
U16 vStart;
U16 vCount;
// NOTE: USE THESE FUNCTIONS! The above exposed implementation is likely
// to change soon.
public:
U16 getVIndex(U16 n) const { return U16(vStart + n); }
U16 getVCount() const { return vCount; }
const PlaneF& getPlane() const { return plane; }
U32 getMaterial() const { return material; }
};
public:
PolyList() {
VECTOR_SET_ASSOCIATION(mPolys);
VECTOR_SET_ASSOCIATION(mVertices);
}
Vector<Poly> mPolys;
Vector<Point3F> mVertices;
};
#endif //_POLYLIST_H_

27
engine/core/realComp.h Executable file
View File

@ -0,0 +1,27 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _REALCOMP_H_
#define _REALCOMP_H_
//Includes
#ifndef _MMATHFN_H_
#include "math/mMathFn.h"
#endif
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
inline bool isEqual(F32 a, F32 b)
{
return mFabs(a - b) < __EQUAL_CONST_F;
}
inline bool isZero(F32 a)
{
return mFabs(a) < __EQUAL_CONST_F;
}
#endif //_REALCOMP_H_

128
engine/core/resDictionary.cc Executable file
View File

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/resManager.h"
#include "core/tAlgorithm.h"
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
ResDictionary::ResDictionary()
{
entryCount = 0;
hashTableSize = 1023; //DefaultTableSize;
hashTable = new ResourceObject *[hashTableSize];
S32 i;
for(i = 0; i < hashTableSize; i++)
hashTable[i] = NULL;
}
ResDictionary::~ResDictionary()
{
// we assume the resources are purged before we destroy
// the dictionary
delete[] hashTable;
}
S32 ResDictionary::hash(StringTableEntry path, StringTableEntry file)
{
return ((S32)((((dsize_t)path) >> 2) + (((dsize_t)file) >> 2) )) % hashTableSize;
}
void ResDictionary::insert(ResourceObject *obj, StringTableEntry path, StringTableEntry file)
{
obj->name = file;
obj->path = path;
S32 idx = hash(path, file);
obj->nextEntry = hashTable[idx];
hashTable[idx] = obj;
entryCount++;
if(entryCount > hashTableSize) {
ResourceObject *head = NULL, *temp, *walk;
for(idx = 0; idx < hashTableSize;idx++) {
walk = hashTable[idx];
while(walk)
{
temp = walk->nextEntry;
walk->nextEntry = head;
head = walk;
walk = temp;
}
}
delete[] hashTable;
hashTableSize = 2 * hashTableSize - 1;
hashTable = new ResourceObject *[hashTableSize];
for(idx = 0; idx < hashTableSize; idx++)
hashTable[idx] = NULL;
walk = head;
while(walk)
{
temp = walk->nextEntry;
idx = hash(walk);
walk->nextEntry = hashTable[idx];
hashTable[idx] = walk;
walk = temp;
}
}
}
ResourceObject* ResDictionary::find(StringTableEntry path, StringTableEntry name)
{
for(ResourceObject *walk = hashTable[hash(path, name)]; walk; walk = walk->nextEntry)
if(walk->name == name && walk->path == path)
return walk;
return NULL;
}
ResourceObject* ResDictionary::find(StringTableEntry path, StringTableEntry name, StringTableEntry zipPath, StringTableEntry zipName)
{
for(ResourceObject *walk = hashTable[hash(path, name)]; walk; walk = walk->nextEntry)
if(walk->name == name && walk->path == path && walk->zipName == zipName && walk->zipPath == zipPath)
return walk;
return NULL;
}
ResourceObject* ResDictionary::find(StringTableEntry path, StringTableEntry name, U32 flags)
{
for(ResourceObject *walk = hashTable[hash(path, name)]; walk; walk = walk->nextEntry)
if(walk->name == name && walk->path == path && U32(walk->flags) == flags)
return walk;
return NULL;
}
void ResDictionary::pushBehind(ResourceObject *resObj, S32 flagMask)
{
remove(resObj);
entryCount++;
ResourceObject **walk = &hashTable[hash(resObj)];
for(; *walk; walk = &(*walk)->nextEntry)
{
if(!((*walk)->flags & flagMask))
{
resObj->nextEntry = *walk;
*walk = resObj;
return;
}
}
resObj->nextEntry = NULL;
*walk = resObj;
}
void ResDictionary::remove(ResourceObject *resObj)
{
for(ResourceObject **walk = &hashTable[hash(resObj)]; *walk; walk = &(*walk)->nextEntry)
{
if(*walk == resObj)
{
entryCount--;
*walk = resObj->nextEntry;
return;
}
}
}

1250
engine/core/resManager.cc Executable file

File diff suppressed because it is too large Load Diff

489
engine/core/resManager.h Executable file
View File

@ -0,0 +1,489 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _RESMANAGER_H_
#define _RESMANAGER_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif
#ifndef _FILESTREAM_H_
#include "core/fileStream.h"
#endif
#ifndef _ZIPSUBSTREAM_H_
#include "core/zipSubStream.h"
#endif
#ifndef _ZIPAGGREGATE_H_
#include "core/zipAggregate.h"
#endif
#ifndef _ZIPHEADERS_H_
#include "core/zipHeaders.h"
#endif
#ifndef _CRC_H_
#include "core/crc.h"
#endif
class Stream;
class FileStream;
class ZipSubRStream;
class ResManager;
class FindMatch;
extern ResManager *ResourceManager;
//------------------------------------------------------------------------------
class ResourceObject;
/// The base class for all resources.
///
/// This must be subclassed to implement a resource that can be managed.
///
/// Creating a new resource type is very simple. First, you need a function
/// which can create a new instance of the resource given a stream:
///
/// @code
/// ResourceInstance* constructBitmapBMP(Stream &stream)
/// {
/// GBitmap *bmp = new GBitmap;
/// if(bmp->readMSBmp(stream))
/// return bmp;
/// else
/// {
/// delete bmp;
/// return NULL;
/// }
/// }
/// @endcode
///
/// Then you need to register the extension of your resource type with the resource manager:
///
/// @code
/// ResourceManager->registerExtension(".bmp", constructBitmapBMP);
/// @endcode
///
/// And, of course, you need to provide a subclass of ResourceInstance:
///
/// @code
/// class GBitmap : ResourceInstance {
/// ... whatever you need for your resource goes in here ...
/// }
/// @endcode
///
/// ResourceInstance imposes very few requirements on you as the resource type
/// implementor. All you "have" to provide is a destructor to deallocate whatever
/// storage your resource might allocate. The ResourceManager will ensure that
/// there is only one instance of the ResourceInstance for each file, and that it
/// is only destroyed when no more references to it remain.
///
/// @see TerrainFile, GBitmap, AudioBuffer for more examples.
class ResourceInstance
{
private:
public:
/// Pointer to the ResourceObject that stores all our book-keeping data.
ResourceObject *mSourceResource;
ResourceInstance() { mSourceResource = NULL; }
virtual ~ResourceInstance() {}
};
typedef ResourceInstance* (*RESOURCE_CREATE_FN)(Stream &stream);
//------------------------------------------------------------------------------
#define InvalidCRC 0xFFFFFFFF
/// Wrapper around a ResourceInstance.
///
/// This contains all the book-keeping data used by ResDictionary and ResManager.
///
/// @see ResManager
class ResourceObject
{
friend class ResDictionary;
friend class ResManager;
/// @name List Book-keeping
/// @{
///
ResourceObject *prev, *next;
ResourceObject *nextEntry; ///< This is used by ResDictionary for its hash table.
ResourceObject *nextResource;
ResourceObject *prevResource;
/// @}
public:
enum Flags
{
VolumeBlock = BIT(0),
File = BIT(1),
Added = BIT(2),
};
S32 flags; ///< Set from Flags.
StringTableEntry path; ///< Resource path.
StringTableEntry name; ///< Resource name.
/// @name ZIP Archive
/// If the resource is stored in a zip file, these members are populated.
/// @{
///
StringTableEntry zipPath; ///< Path of zip file.
StringTableEntry zipName; ///< Name of zip file.
S32 fileOffset; ///< Offset of data in zip file.
S32 fileSize; ///< Size on disk of resource block.
S32 compressedFileSize; ///< Actual size of resource data.
/// @}
ResourceInstance *mInstance; ///< Pointer to actual object instance. If the object is not loaded,
/// this may be NULL or garbage.
S32 lockCount; ///< Lock count; used to control load/unload of resource from memory.
U32 crc; ///< CRC of resource.
ResourceObject();
~ResourceObject() { unlink(); }
void destruct();
/// @name List Management
/// @{
///
ResourceObject* getNext() const { return next; }
void unlink();
void linkAfter(ResourceObject* res);
/// @}
/// Get some information about file times.
///
/// @param createTime Pointer to a FileTime structure to fill with information about when this object was
/// created.
/// @param modifyTime Pointer to a FileTime structure to fill with information about when this object was
/// modified.
void getFileTimes(FileTime *createTime, FileTime *modifyTime);
};
inline void ResourceObject::unlink()
{
if (next)
next->prev = prev;
if (prev)
prev->next = next;
next = prev = 0;
}
inline void ResourceObject::linkAfter(ResourceObject* res)
{
unlink();
prev = res;
if ((next = res->next) != 0)
next->prev = this;
res->next = this;
}
//------------------------------------------------------------------------------
/// Wrapper class around a ResourceInstance subclass.
///
/// @code
/// // Loading a resource...
/// Resource<TerrainFile> terrRes;
///
/// terrRes = ResourceManager->load(fileName);
/// if(!bool(terrRes))
/// Con::errorf(ConsoleLogEntry::General, "Terraformer::terrainFile - invalid terrain file '%s'.", fileName);
/// @endcode
///
/// When the Resource<> is destroyed, it frees the lock on the resource.
///
/// @see ResManager
template <class T> class Resource
{
private:
ResourceObject *obj; ///< Actual resource object
// ***WARNING***
// Using a faster lock that bypasses the resource manger.
// void _lock() { if (obj) obj->rm->lockResource( obj ); }
void _lock(); ///< Increments the lock count on this object
void _unlock(); ///< Decrements the lock count on this object
public:
/// If assigned a ResourceObject, it's assumed to already have
/// been locked, lock count is incremented only for copies or
/// assignment from another Resource.
Resource() : obj(NULL) { ; }
Resource(ResourceObject *p) : obj(p) { ; }
Resource(const Resource &res) : obj(res.obj) { _lock(); }
~Resource() { unlock(); } ///< Decrements the lock count on this object, and if the lock count is 0 afterwards,
///< adds the object to the timeoutList for deletion on execution of purge().
const char *getFilePath() const { return (obj ? obj->path : NULL); } ///< Returns the path of the file (without the actual name)
const char *getFileName() const { return (obj ? obj->name : NULL); } ///< Returns the actual file name (without the path)
Resource& operator= (ResourceObject *p) { _unlock(); obj = p; return *this; }
Resource& operator= (const Resource &r) { _unlock(); obj = r.obj; _lock(); return *this; }
U32 getCRC() { return (obj ? obj->crc : 0); }
bool isNull() const { return ((obj == NULL) || (obj->mInstance == NULL)); }
operator bool() const { return ((obj != NULL) && (obj->mInstance != NULL)); }
T* operator->() { return (T*)obj->mInstance; }
T& operator*() { return *((T*)obj->mInstance); }
operator T*() const { return (obj) ? (T*)obj->mInstance : (T*)NULL; }
const T* operator->() const { return (const T*)obj->mInstance; }
const T& operator*() const { return *((const T*)obj->mInstance); }
operator const T*() const { return (obj) ? (const T*)obj->mInstance : (const T*)NULL; }
void unlock();
void purge();
};
#define INVALID_ID ((U32)(~0))
//----------------------------------------------------------------------------
/// Resource Dictionary.
///
/// Maps of names and object IDs to objects.
///
/// Provides fast lookup for name->object, id->object and
/// for fast removal of an object given a pointer to it.
///
/// @see ResManager
class ResDictionary
{
/// @name Hash Table
/// @{
enum { DefaultTableSize = 1029 };
ResourceObject **hashTable;
S32 entryCount;
S32 hashTableSize;
DataChunker memPool;
S32 hash(StringTableEntry path, StringTableEntry name);
S32 hash(ResourceObject *obj) { return hash(obj->path, obj->name); }
/// @}
public:
ResDictionary();
~ResDictionary();
/// Add a ResourceObject to the dictionary.
void insert(ResourceObject *obj, StringTableEntry path, StringTableEntry file);
/// @name Find
/// These functions search the hash table for an individual resource. If the resource has
/// already been loaded, it will find the resource and return its object. If not,
/// it will return NULL.
/// @{
ResourceObject* find(StringTableEntry path, StringTableEntry file);
ResourceObject* find(StringTableEntry path, StringTableEntry file, StringTableEntry filePath, StringTableEntry fileName);
ResourceObject* find(StringTableEntry path, StringTableEntry file, U32 flags);
/// @}
/// Move a previously added resource object to be in the list after everything that matches the specified mask.
void pushBehind(ResourceObject *obj, S32 mask);
/// Remove a resource object from the dictionary.
void remove(ResourceObject *obj);
};
//------------------------------------------------------------------------------
/// A virtual file system for the storage and retrieval of ResourceObjects.
///
/// Basic resource manager behavior:
/// - Set the mod path.
/// - ResManager scans directory tree under listed base directories
/// - Any volume (.zip) file in the root directory of a mod is scanned
/// for resources.
/// - Any files currently in the resource manager become memory resources.
/// - They can be "reattached" to the first file that matches the file name.
///
/// All classes which wish to be handled by the resource manager need:
/// -# To be derived from ResourceInstance.
/// -# To register a creation function and file extension with the manager.
///
/// @nosubgrouping
class ResManager
{
private:
/// Path to which we will write data.
///
/// This is used when, for instance, we download files from a server.
char writeablePath[1024];
/// Primary path from which we load data.
char primaryPath[1024];
/// List of secondary paths to search.
char* pathList;
ResourceObject timeoutList;
ResourceObject resourceList;
ResDictionary dictionary;
bool echoFileNames;
bool isIgnoredSubdirectoryName(const char *name) const;
/// Scan a zip file for resources.
bool scanZip(ResourceObject *zipObject);
/// Create a ResourceObject from the given file.
ResourceObject* createResource(StringTableEntry path, StringTableEntry file);
/// Create a ResourceObject from the given file in a zip file.
ResourceObject* createZipResource(StringTableEntry path, StringTableEntry file, StringTableEntry zipPath, StringTableEntry zipFle);
void searchPath(const char *pathStart);
bool setModZip(const char* path);
struct RegisteredExtension
{
StringTableEntry mExtension;
RESOURCE_CREATE_FN mCreateFn;
RegisteredExtension *next;
};
Vector<char *> mMissingFileList; ///< List of missing files.
bool mLoggingMissingFiles; ///< Are there any missing files?
void fileIsMissing(const char *fileName); ///< Called when a file is missing.
RegisteredExtension *registeredList;
static char *smExcludedDirectories;
ResManager();
public:
RESOURCE_CREATE_FN getCreateFunction( const char *name );
~ResManager();
/// @name Global Control
/// These are called to initialize/destroy the resource manager at runtime.
/// @{
static void create();
static void destroy();
/// Load the excluded directories from the resource manager pref and
/// stuff it into the platform layer.
static void initExcludedDirectories();
///@}
void setFileNameEcho(bool on); ///< Sets whether or not to echo filenames that have been accessed by means of openStream
void setModPaths(U32 numPaths, const char **dirs); ///< Sets the path for the current game mod
const char* getModPaths(); ///< Gets the path for the current game mod
void setMissingFileLogging(bool log); ///< Should we log missing files?
bool getMissingFileList(Vector<char *> &list); ///< Gets which files are missing
void clearMissingFileList(); ///< Clears the missing file list
/// Tells the resource manager what to do with a resource that it loads
void registerExtension(const char *extension, RESOURCE_CREATE_FN create_fn);
S32 getSize(const char* filename); ///< Gets the size of the file
const char* getFullPath(const char * filename, char * path, U32 pathLen); ///< Gets the full path of the file
const char* getModPathOf(const char* fileName); ///< Gets the path of the file local to the mod
const char* getPathOf(const char * filename); ///< Gets the path of the file from the base directory
const char* getBasePath(); ///< Gets the base path
ResourceObject* load(const char * fileName, bool computeCRC = false); ///< loads an instance of an object
Stream* openStream(const char * fileName); ///< Opens a stream for an object
Stream* openStream(ResourceObject *object); ///< Opens a stream for an object
void closeStream(Stream *stream); ///< Closes the stream
/// Decrements the lock count of an object. If the lock count is zero post-decrement,
/// the object is added to the timeoutList for deletion upon call of flush.
void unlock( ResourceObject* );
/// Add a new resource instance
bool add(const char* name, ResourceInstance *addInstance, bool extraLock = false);
/// Searches the hash list for the filename and returns it's object if found, otherwise NULL
ResourceObject* find(const char * fileName);
/// Loads a new instance of an object by means of a filename
ResourceInstance* loadInstance(const char *fileName, bool computeCRC = false);
/// Loads a new instance of an object by means of a resource object
ResourceInstance* loadInstance(ResourceObject *object, bool computeCRC = false);
/// Searches the hash list for the filename and returns it's object if found, otherwise NULL
ResourceObject* find(const char * fileName, U32 flags);
/// Finds a resource object with given expression
ResourceObject* findMatch(const char *expression, const char **fn, ResourceObject *start = NULL);
/// Finds a resource object with given expressions, seperated by " "
ResourceObject* findMatchMultiExprs(const char *multiExpression, const char **fn, ResourceObject *start = NULL);
void purge(); ///< Goes through the timeoutList and deletes it all. BURN!!!
void purge( ResourceObject *obj ); ///< Deletes one resource object.
void freeResource(ResourceObject *resObject); ///< Frees a resource!
void serialize(VectorPtr<const char *> &filenames);///< Sorts the resource objects
S32 findMatches( FindMatch *pFM ); ///< Finds multiple matches to an expression.
bool findFile( const char *name ); ///< Checks to see if a file exists.
/// Computes the CRC of a file.
///
/// By passing a different crcInitialVal, you can take the CRC of multiple files.
bool getCrc(const char * fileName, U32 & crcVal, const U32 crcInitialVal = INITIAL_CRC_VALUE );
void setWriteablePath(const char *path); ///< Sets the writable path for a file to the one given.
bool isValidWriteFileName(const char *fn); ///< Checks to see if the given path is valid for writing.
/// Opens a file for writing!
bool openFileForWrite(FileStream &fs, const char *fileName, U32 accessMode = 1);
#ifdef TORQUE_DEBUG
void dumpLoadedResources(); ///< Dumps all loaded resources to the console.
#endif
};
template<class T> inline void Resource<T>::unlock()
{
if (obj) {
ResourceManager->unlock( obj );
obj=NULL;
}
}
template<class T> inline void Resource<T>::purge()
{
if (obj) {
ResourceManager->unlock( obj );
if (obj->lockCount == 0)
ResourceManager->purge(obj);
obj = NULL;
}
}
template <class T> inline void Resource<T>::_lock()
{
if (obj)
obj->lockCount++;
}
template <class T> inline void Resource<T>::_unlock()
{
if (obj)
ResourceManager->unlock( obj );
}
#endif //_RESMANAGER_H_

139
engine/core/resizeStream.cc Executable file
View File

@ -0,0 +1,139 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/resizeStream.h"
ResizeFilterStream::ResizeFilterStream()
: m_pStream(NULL),
m_startOffset(0),
m_streamLen(0),
m_currOffset(0)
{
//
}
ResizeFilterStream::~ResizeFilterStream()
{
detachStream();
}
bool ResizeFilterStream::attachStream(Stream* io_pSlaveStream)
{
AssertFatal(io_pSlaveStream != NULL, "NULL Slave stream?");
m_pStream = io_pSlaveStream;
m_startOffset = 0;
m_streamLen = m_pStream->getStreamSize();
m_currOffset = 0;
setStatus(EOS);
return true;
}
void ResizeFilterStream::detachStream()
{
m_pStream = NULL;
m_startOffset = 0;
m_streamLen = 0;
m_currOffset = 0;
setStatus(Closed);
}
Stream* ResizeFilterStream::getStream()
{
return m_pStream;
}
bool ResizeFilterStream::setStreamOffset(const U32 in_startOffset, const U32 in_streamLen)
{
AssertFatal(m_pStream != NULL, "stream not attached!");
if (m_pStream == NULL)
return false;
U32 start = in_startOffset;
U32 end = in_startOffset + in_streamLen;
U32 actual = m_pStream->getStreamSize();
if (start >= actual || end > actual)
return false;
m_startOffset = start;
m_streamLen = in_streamLen;
m_currOffset = 0;
if (m_streamLen != 0)
setStatus(Ok);
else
setStatus(EOS);
return true;
}
U32 ResizeFilterStream::getPosition() const
{
AssertFatal(m_pStream != NULL, "Error, stream not attached");
if (m_pStream == NULL)
return 0;
return m_currOffset;
}
bool ResizeFilterStream::setPosition(const U32 in_newPosition)
{
AssertFatal(m_pStream != NULL, "Error, stream not attached");
if (m_pStream == NULL)
return false;
if (in_newPosition < m_streamLen) {
m_currOffset = in_newPosition;
return true;
} else {
m_currOffset = m_streamLen;
return false;
}
}
U32 ResizeFilterStream::getStreamSize()
{
AssertFatal(m_pStream != NULL, "Error, stream not attached");
return m_streamLen;
}
bool ResizeFilterStream::_read(const U32 in_numBytes, void* out_pBuffer)
{
AssertFatal(m_pStream != NULL, "Error, stream not attached");
if (in_numBytes == 0)
return true;
AssertFatal(out_pBuffer != NULL, "Invalid output buffer");
if (getStatus() == Closed) {
AssertFatal(false, "Attempted read from closed stream");
return false;
}
U32 savePosition = m_pStream->getPosition();
if (m_pStream->setPosition(m_startOffset + m_currOffset) == false)
return false;
U32 actualSize = in_numBytes;
U32 position = m_startOffset + m_currOffset;
if (in_numBytes + position > m_startOffset + m_streamLen)
actualSize = m_streamLen - (position - m_startOffset);
if (actualSize == 0) {
setStatus(EOS);
return false;
}
bool success = m_pStream->read(actualSize, out_pBuffer);
m_currOffset += actualSize;
setStatus(m_pStream->getStatus());
m_pStream->setPosition(savePosition);
return success;
}

47
engine/core/resizeStream.h Executable file
View File

@ -0,0 +1,47 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _RESIZESTREAM_H_
#define _RESIZESTREAM_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _FILTERSTREAM_H_
#include "core/filterStream.h"
#endif
class ResizeFilterStream : public FilterStream
{
typedef FilterStream Parent;
Stream* m_pStream;
U32 m_startOffset;
U32 m_streamLen;
U32 m_currOffset;
public:
ResizeFilterStream();
~ResizeFilterStream();
bool attachStream(Stream* io_pSlaveStream);
void detachStream();
Stream* getStream();
bool setStreamOffset(const U32 in_startOffset,
const U32 in_streamLen);
// Mandatory overrides.
protected:
bool _read(const U32 in_numBytes, void* out_pBuffer);
public:
U32 getPosition() const;
bool setPosition(const U32 in_newPosition);
U32 getStreamSize();
};
#endif //_RESIZESTREAM_H_

182
engine/core/stream.h Executable file
View File

@ -0,0 +1,182 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _STREAM_H_
#define _STREAM_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
// This should ideally be done with templates...
//
/// @defgroup stream_overload Primitive Type Stream Operation Overloads
/// These macros define the read and write functions for all primitive types.
/// @{
#define DECLARE_OVERLOADED_READ(type) \
bool read(type* out_read) { \
return read(sizeof(type), out_read); \
}
#define DECLARE_OVERLOADED_WRITE(type) \
bool write(type in_write) { \
return write(sizeof(type), &in_write); \
}
#define DECLARE_ENDIAN_OVERLOADED_READ(type) \
bool read(type* out_read) { \
type temp; \
bool success = read(sizeof(type), &temp); \
*out_read = convertLEndianToHost(temp); \
return success; \
}
#define DECLARE_ENDIAN_OVERLOADED_WRITE(type) \
bool write(type in_write) { \
type temp = convertHostToLEndian(in_write); \
return write(sizeof(type), &temp); \
}
/// @}
class ColorI;
class ColorF;
//------------------------------------------------------------------------------
//-------------------------------------- Base Stream class
//
/// Base stream class for streaming data across a specific media
class Stream {
// Public structs and enumerations...
public:
/// Status constantants for the stream
enum Status {
Ok = 0, ///< Ok!
IOError, ///< Read or Write error
EOS, ///< End of Stream reached (mostly for reads)
IllegalCall, ///< An unsupported operation used. Always w/ accompanied by AssertWarn
Closed, ///< Tried to operate on a closed stream (or detached filter)
UnknownError ///< Catchall
};
enum Capability {
StreamWrite = BIT(0), ///< Can this stream write?
StreamRead = BIT(1), ///< Can this stream read?
StreamPosition = BIT(2) ///< Can this stream position?
};
// Accessible only through inline accessors
private:
Status m_streamStatus;
// Derived accessible data modifiers...
protected:
void setStatus(const Status in_newStatus) { m_streamStatus = in_newStatus; }
public:
Stream();
virtual ~Stream();
/// Gets the status of the stream
Stream::Status getStatus() const { return m_streamStatus; }
/// Gets a printable string form of the status
static const char* getStatusString(const Status in_status);
// Derived classes must override these...
protected:
virtual bool _read(const U32 in_numBytes, void* out_pBuffer) = 0;
virtual bool _write(const U32 in_numBytes, const void* in_pBuffer) = 0;
public:
/// Checks to see if this stream has the capability of a given function
virtual bool hasCapability(const Capability) const = 0;
/// Gets the position in the stream
virtual U32 getPosition() const = 0;
/// Sets the position of the stream. Returns if the new position is valid or not
virtual bool setPosition(const U32 in_newPosition) = 0;
/// Gets the size of the stream
virtual U32 getStreamSize() = 0;
/// Reads a line from the stream.
/// @param buffer buffer to be read into
/// @param bufferSize max size of the buffer. Will not read more than the "bufferSize"
void readLine(U8 *buffer, U32 bufferSize);
/// writes a line to the stream
void writeLine(U8 *buffer);
/// Reads a string and inserts it into the StringTable
/// @see StringTable
const char *readSTString(bool casesens = false);
/// Reads a string of maximum 255 characters long
virtual void readString(char stringBuf[256]);
/// Reads a string that could potentially be more than 255 characters long.
/// @param maxStringLen Maximum length to read. If the string is longer than maxStringLen, only maxStringLen bytes will be read.
/// @param stringBuf buffer where data is read into
void readLongString(U32 maxStringLen, char *stringBuf);
/// Writes a string to the stream. This function is slightly unstable.
/// Only use this if you have a valid string that is not empty.
/// writeString is safer.
void writeLongString(U32 maxStringLen, const char *string);
/// Writes a string to the stream.
virtual void writeString(const char *stringBuf, S32 maxLen=255);
/// Write an integral color to the stream.
bool write(const ColorI&);
/// Write a floating point color to the stream.
bool write(const ColorF&);
/// Read an integral color from the stream.
bool read(ColorI*);
/// Read a floating point color from the stream.
bool read(ColorF*);
// Overloaded write and read ops..
public:
bool read(const U32 in_numBytes, void* out_pBuffer) {
return _read(in_numBytes, out_pBuffer);
}
bool write(const U32 in_numBytes, const void* in_pBuffer) {
return _write(in_numBytes, in_pBuffer);
}
DECLARE_OVERLOADED_WRITE(S8)
DECLARE_OVERLOADED_WRITE(U8)
DECLARE_ENDIAN_OVERLOADED_WRITE(S16)
DECLARE_ENDIAN_OVERLOADED_WRITE(S32)
DECLARE_ENDIAN_OVERLOADED_WRITE(U16)
DECLARE_ENDIAN_OVERLOADED_WRITE(U32)
DECLARE_ENDIAN_OVERLOADED_WRITE(F32)
DECLARE_ENDIAN_OVERLOADED_WRITE(F64)
DECLARE_OVERLOADED_READ(S8)
DECLARE_OVERLOADED_READ(U8)
DECLARE_ENDIAN_OVERLOADED_READ(S16)
DECLARE_ENDIAN_OVERLOADED_READ(S32)
DECLARE_ENDIAN_OVERLOADED_READ(U16)
DECLARE_ENDIAN_OVERLOADED_READ(U32)
DECLARE_ENDIAN_OVERLOADED_READ(F32)
DECLARE_ENDIAN_OVERLOADED_READ(F64)
// We have to do the bool's by hand, since they are different sizes
// on different compilers...
//
bool read(bool* out_pRead) {
U8 translate;
bool success = read(&translate);
if (success == false)
return false;
*out_pRead = translate != 0;
return true;
}
bool write(const bool& in_rWrite) {
U8 translate = in_rWrite ? U8(1) : U8(0);
return write(translate);
}
};
#endif //_STREAM_H_

442
engine/core/stringBuffer.cc Executable file
View File

@ -0,0 +1,442 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/stringBuffer.h"
#include "core/frameAllocator.h"
#include "core/unicode.h"
#if defined(TORQUE_DEBUG)
class StringBufferManager
{
public:
static StringBufferManager& getManager();
Vector<StringBuffer*> strings;
U64 request8;
U64 request16;
void add(StringBuffer* s);
void remove(StringBuffer* s);
void updateStats();
void dumpStats();
void dumpAllStrings();
};
ConsoleFunction(sbmDumpStats, void, 1, 1, "")
{
StringBufferManager::getManager().dumpStats();
}
ConsoleFunction(sbmDumpStrings, void, 1, 1, "")
{
StringBufferManager::getManager().dumpAllStrings();
}
#endif // TORQUE_DEBUG
#if defined(TORQUE_DEBUG)
#define SBMAddThisStringBuffer() \
StringBufferManager::getManager().add(this); \
rc = new RequestCounts; \
clearRequestCounts()
#define incRequestCount8() rc->requestCount8++
#define incRequestCount16() rc->requestCount16++
#define decRequestCount16() rc->requestCount16--
#else
#define SBMAddThisStringBuffer()
#define incRequestCount8()
#define incRequestCount16()
#define decRequestCount16()
#endif
StringBuffer::StringBuffer() : mBuffer(), mBuffer8(), mDirty8(true)
{
SBMAddThisStringBuffer();
mBuffer.push_back(0);
};
/// Copy constructor. Very important.
StringBuffer::StringBuffer(const StringBuffer &copy) : mBuffer(), mBuffer8()
{
SBMAddThisStringBuffer();
set(&copy);
};
StringBuffer::StringBuffer(const StringBuffer *in)
: mBuffer(), mBuffer8()
{
SBMAddThisStringBuffer();
set(in);
}
StringBuffer::StringBuffer(const UTF8 *in)
: mBuffer(), mBuffer8()
{
SBMAddThisStringBuffer();
set(in);
}
StringBuffer::StringBuffer(const UTF16 *in)
: mBuffer(), mBuffer8()
{
SBMAddThisStringBuffer();
set(in);
}
//-------------------------------------------------------------------------
StringBuffer::~StringBuffer()
{
// Everything will get cleared up nicely cuz it's a vector. Sweet.
#if defined(TORQUE_DEBUG)
StringBufferManager::getManager().remove(this);
delete rc;
#endif
}
//-------------------------------------------------------------------------
void StringBuffer::set(const StringBuffer *in)
{
// Copy the vector.
mBuffer.setSize(in->length()+1);
dMemcpy(mBuffer.address(), in->mBuffer.address(), sizeof(UTF16)*in->mBuffer.size());
mDirty8 = true;
}
void StringBuffer::set(const UTF8 *in)
{
incRequestCount8();
// Convert and store. Note that a UTF16 version of the string cannot be longer.
FrameTemp<UTF16> tmpBuff(dStrlen(in)+1);
if(!in || in[0] == 0 || !convertUTF8toUTF16(in, tmpBuff, dStrlen(in)+1))
{
// Easy out, it's a blank string, or a bad string.
mBuffer.clear();
mBuffer.push_back(0);
AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF8 - not a null terminated string!");
return;
}
// Otherwise, we've a copy to do. (This might not be strictly necessary.)
mBuffer.setSize(dStrlen(tmpBuff)+1);
dMemcpy(mBuffer.address(), tmpBuff, sizeof(UTF16) * mBuffer.size());
mBuffer.compact();
AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF8 - not a null terminated string!");
mDirty8 = true;
}
void StringBuffer::set(const UTF16 *in)
{
incRequestCount16();
// Just copy, it's already UTF16.
mBuffer.setSize(dStrlen(in)+1);
dMemcpy(mBuffer.address(), in, sizeof(UTF16) * mBuffer.size());
mBuffer.compact();
AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF16 - not a null terminated string!");
mDirty8 = true;
}
//-------------------------------------------------------------------------
void StringBuffer::append(const StringBuffer &in)
{
append(in.mBuffer.address(), in.length());
}
void StringBuffer::append(const UTF8* in)
{
incRequestCount8();
decRequestCount16(); // because we're about to inc it when we go through append(utf16)
// convert to UTF16, because that's our internal format.
// if the conversion fails, exit.
UTF16* tmp = convertUTF8toUTF16(in);
AssertFatal(tmp, "StringBuffer::append(UTF8) - could not convert UTF8 string!");
if(!tmp)
return;
append(tmp);
delete [] tmp;
}
void StringBuffer::append(const UTF16* in)
{
AssertFatal(in, "StringBuffer::append(UTF16) - null UTF16 string!");
append(in, dStrlen(in));
}
void StringBuffer::append(const UTF16* in, const U32 len)
{
incRequestCount16();
// Stick in onto the end of us - first make space.
U32 oldSize = length();
mBuffer.increment(len);
// Copy it in, ignoring both our terminator and theirs.
dMemcpy(&mBuffer[oldSize], in, sizeof(UTF16) * len);
// Terminate the string.
mBuffer.last() = 0;
// mark utf8 buffer dirty
mDirty8 = true;
}
void StringBuffer::insert(const U32 charOffset, const StringBuffer &in)
{
insert(charOffset, in.mBuffer.address(), in.length());
}
void StringBuffer::insert(const U32 charOffset, const UTF8* in)
{
incRequestCount8();
decRequestCount16();
// convert to UTF16, because that's our internal format.
// if the conversion fails, exit.
UTF16* tmp = convertUTF8toUTF16(in);
AssertFatal(tmp, "StringBuffer::insert(UTF8) - could not convert UTF8 string!");
if(!tmp)
return;
insert(charOffset, tmp);
delete [] tmp;
}
void StringBuffer::insert(const U32 charOffset, const UTF16* in)
{
AssertFatal(in, "StringBuffer::insert(UTF16) - null UTF16 string!");
insert(charOffset, in, dStrlen(in));
}
void StringBuffer::insert(const U32 charOffset, const UTF16* in, const U32 len)
{
incRequestCount16();
// Deal with append case.
if(charOffset >= length())
{
append(in, len);
return;
}
// Append was easy, now we have to do some work.
// Copy everything we have that comes after charOffset past where the new
// string data will be.
// Figure the address to start copying at. We know this is ok as otherwise
// we'd be in the append case.
const UTF16 *copyStart = &mBuffer[charOffset];
// Figure the number of UTF16's to copy, taking into account the possibility
// that we may be inserting a long string into a short string.
// How many chars come after the insert point? Add 1 to deal with terminator.
const U32 copyCharLength = (S32)(length() - charOffset) + 1;
// Make some space in our buffer. We only need in.length() more as we
// will be dropping one of the two terminators present in this operation.
mBuffer.increment(len);
for(S32 i=copyCharLength-1; i>=0; i--)
mBuffer[charOffset+i+len] = mBuffer[charOffset+i];
// Can't copy directly: memcpy behavior is undefined if src and dst overlap.
//dMemcpy(&mBuffer[charOffset+len], &mBuffer[charOffset], sizeof(UTF16) * copyCharLength);
// And finally copy the new data in, not including its terminator.
dMemcpy(&mBuffer[charOffset], in, sizeof(UTF16) * len);
// All done!
AssertFatal(mBuffer.last() == 0, "StringBuffer::insert - not a null terminated string!");
// mark utf8 buffer dirty
mDirty8 = true;
}
StringBuffer StringBuffer::substring(const U32 start, const U32 len) const
{
// Deal with bonehead user input.
if(start >= length() || len == 0)
{
// Either they asked beyond the end of the string or we're null. Either
// way, let's give them a null string.
return StringBuffer("");
}
AssertFatal(start < length(), "StringBuffer::substring - invalid start!");
AssertFatal(start+len <= length(), "StringBuffer::substring - invalid len!");
AssertFatal(len > 0, "StringBuffer::substring - len must be >= 1.");
StringBuffer tmp;
tmp.mBuffer.clear();
for(S32 i=0; i<len; i++)
tmp.mBuffer.push_back(mBuffer[start+i]);
if(tmp.mBuffer.last() != 0) tmp.mBuffer.push_back(0);
// Make sure this shit is terminated; we might get a totally empty string.
if(!tmp.mBuffer.size())
tmp.mBuffer.push_back(0);
return tmp;
}
UTF8* StringBuffer::createSubstring8(const U32 start, const U32 len) const
{
incRequestCount8();
StringBuffer sub = this->substring(start, len);
return sub.createCopy8();
}
void StringBuffer::cut(const U32 start, const U32 len)
{
AssertFatal(start < length(), "StringBuffer::cut - invalid start!");
AssertFatal(start+len <= length(), "StringBuffer::cut - invalid len!");
AssertFatal(len > 0, "StringBuffer::cut - len >= 1.");
AssertFatal(mBuffer.last() == 0, "StringBuffer::cut - not a null terminated string! (pre)");
// Now snip things.
for(S32 i=start; i<mBuffer.size()-len; i++)
mBuffer[i] = mBuffer[i+len];
mBuffer.decrement(len);
mBuffer.compact();
AssertFatal(mBuffer.last() == 0, "StringBuffer::cut - not a null terminated string! (post)");
// mark utf8 buffer dirty
mDirty8 = true;
}
const UTF16 StringBuffer::getChar(const U32 offset) const
{
incRequestCount16();
// Allow them to grab the null terminator if they want.
AssertFatal(offset<mBuffer.size(), "StringBuffer::getChar - outside of range.");
return mBuffer[offset];
}
//-------------------------------------------------------------------------
void StringBuffer::getCopy8(UTF8 *buff, const U32 buffSize) const
{
incRequestCount8();
AssertFatal(mBuffer.last() == 0, "StringBuffer::get UTF8 - not a null terminated string!");
convertUTF16toUTF8(mBuffer.address(), buff, buffSize);
}
void StringBuffer::getCopy(UTF16 *buff, const U32 buffSize) const
{
incRequestCount16();
// Just copy it out.
AssertFatal(mBuffer.last() == 0, "StringBuffer::get UTF8 - not a null terminated string!");
dMemcpy(buff, mBuffer.address(), buffSize*sizeof(UTF16));
// ensure null termination.
buff[buffSize-1] = NULL;
}
UTF8* StringBuffer::createCopy8() const
{
incRequestCount8();
// convert will create a buffer of the appropriate size for a null terminated
// input string.
return convertUTF16toUTF8(mBuffer.address());
}
const UTF16* StringBuffer::getPtr() const
{
incRequestCount16();
// get a pointer to the StringBuffer's data store.
// this pointer is volatile, and not thread safe.
// use this in situations where you can be sure that the StringBuffer will
// not be modified out from under you.
// the key here is, you avoid yet another data copy. data copy is slow on
// most modern hardware.
return mBuffer.address();
}
const UTF8* StringBuffer::getPtr8() const
{
incRequestCount8();
// get a pointer to the utf8 version of the StringBuffer's data store.
// if the utf8 version is dirty, update it first.
if(mDirty8)
const_cast<StringBuffer *>(this)->updateBuffer8();
return mBuffer8.address();
}
void StringBuffer::updateBuffer8()
{
U32 slackLen = getUTF8BufferSizeEstimate();
mBuffer8.setSize(slackLen);
U32 len = convertUTF16toUTF8(mBuffer.address(), mBuffer8.address(), slackLen);
mBuffer8.setSize(len+1);
mBuffer8.compact();
mDirty8 = false;
}
#if defined(TORQUE_DEBUG)
StringBufferManager& StringBufferManager::getManager()
{
static StringBufferManager _sbm; return _sbm;
}
void StringBufferManager::add(StringBuffer* s)
{
strings.push_back(s);
}
void StringBufferManager::remove(StringBuffer* s)
{
U32 nstrings = strings.size();
for(int i=0; i < nstrings; i++)
if(strings[i] == s)
strings.erase_fast(i);
}
void StringBufferManager::updateStats()
{
request8 = 0;
request16 = 0;
U32 nstrings = strings.size();
for(int i=0; i < nstrings; i++)
{
request8 += strings[i]->rc->requestCount8;
request16 += strings[i]->rc->requestCount16;
}
}
void StringBufferManager::dumpStats()
{
updateStats();
Con::printf("===== String Manager Stats =====");
Con::printf(" strings: %i", strings.size());
Con::printf(" utf8 requests: %Lu", request8);
Con::printf(" utf16 requests: %Lu", request16);
}
void StringBufferManager::dumpAllStrings()
{
U32 nstrings = strings.size();
Con::printf("===== String Manager: All Strings =====");
Con::printf(" utf8 | utf16 | string");
for(int i=0; i < nstrings; i++)
{
UTF8* tmp = strings[i]->createCopy8();
strings[i]->rc->requestCount8--;
Con::printf("%5llu %5llu \"%s\"", strings[i]->rc->requestCount8, strings[i]->rc->requestCount16, tmp);
delete[] tmp;
}
}
#endif

117
engine/core/stringBuffer.h Executable file
View File

@ -0,0 +1,117 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _STRINGBUFFER_H_
#define _STRINGBUFFER_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#include "console/console.h"
/// Utility class to wrap string manipulation in a representation
/// independent way.
///
/// Length does NOT include the null terminator.
class StringBuffer
{
Vector<UTF16> mBuffer;
Vector<UTF8> mBuffer8;
bool mDirty8;
public:
#if defined(TORQUE_DEBUG)
typedef struct RequestCounts
{
U64 requestCount8;
U64 requestCount16;
};
RequestCounts *rc;
#endif
StringBuffer();
StringBuffer(const StringBuffer &copy);
StringBuffer(const StringBuffer *in);
StringBuffer(const UTF8 *in);
StringBuffer(const UTF16 *in);
~StringBuffer();
void append(const StringBuffer &in);
void append(const UTF8* in);
void append(const UTF16* in);
void append(const UTF16* in, U32 len);
void insert(const U32 charOffset, const StringBuffer &in);
void insert(const U32 charOffset, const UTF8* in);
void insert(const U32 charOffset, const UTF16* in);
void insert(const U32 charOffset, const UTF16* in, const U32 len);
/// Get a StringBuffer substring of length 'len' starting from 'start'.
/// Returns a new StringBuffer by value;
StringBuffer substring(const U32 start, const U32 len) const;
/// Get a pointer to a substring of length 'len' starting from 'start'.
/// Returns a raw pointer to a unicode string.
/// You must delete[] the returned string when you are done with it.
/// This follows the "create rule".
UTF8* createSubstring8(const U32 start, const U32 len) const;
UTF16* createSubstring16(const U32 start, const U32 len) const;
void cut(const U32 start, const U32 len);
// UTF8* cut8(const U32 start, const U32 len);
// UTF16* cut16(const U32 start, const U32 len);
const UTF16 getChar(const U32 offset) const;
void setChar(const U32 offset, UTF16 c);
void set(const StringBuffer *in);
void set(const UTF8 *in);
void set(const UTF16 *in);
inline const U32 length() const
{
return mBuffer.size() - 1; // Don't count the NULL of course.
}
/// Get an upper bound size estimate for a UTF8 buffer to hold this
/// string.
const U32 getUTF8BufferSizeEstimate() const
{
return length() * 3 + 1;
}
void getCopy8(UTF8 *buff, const U32 buffSize) const;
void getCopy(UTF16 *buff, const U32 buffSize) const;
/// Get a copy of the contents of the string buffer.
/// You must delete[] the returned copy when you are done with it.
/// This follows the "create rule".
UTF8* createCopy8() const;
UTF16* createCopy() const;
/// Get a pointer to the StringBuffer's data store.
/// Use this in situations where you can be sure that the StringBuffer will
/// not be modified out from under you.
/// The win here is, you avoid yet another data copy. Data copy is slow on
/// most modern hardware.
const UTF16* getPtr() const;
const UTF8* getPtr8() const;
private:
void updateBuffer8();
#if defined(TORQUE_DEBUG)
void clearRequestCounts() { rc->requestCount16 = 0; rc->requestCount8 = 0; }
#endif
};
#endif

204
engine/core/stringTable.cc Executable file
View File

@ -0,0 +1,204 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/stringTable.h"
_StringTable *StringTable = NULL;
const U32 _StringTable::csm_stInitSize = 29;
//---------------------------------------------------------------
//
// StringTable functions
//
//---------------------------------------------------------------
namespace {
bool sgInitTable = true;
U8 sgHashTable[256];
void initTolowerTable()
{
for (U32 i = 0; i < 256; i++) {
U8 c = dTolower(i);
sgHashTable[i] = c * c;
}
sgInitTable = false;
}
} // namespace {}
U32 _StringTable::hashString(const char* str)
{
if (sgInitTable)
initTolowerTable();
U32 ret = 0;
char c;
while((c = *str++) != 0) {
ret <<= 1;
ret ^= sgHashTable[c];
}
return ret;
}
U32 _StringTable::hashStringn(const char* str, S32 len)
{
if (sgInitTable)
initTolowerTable();
U32 ret = 0;
char c;
while((c = *str++) != 0 && len--) {
ret <<= 1;
ret ^= sgHashTable[c];
}
return ret;
}
//--------------------------------------
_StringTable::_StringTable()
{
buckets = (Node **) dMalloc(csm_stInitSize * sizeof(Node *));
for(U32 i = 0; i < csm_stInitSize; i++) {
buckets[i] = 0;
}
numBuckets = csm_stInitSize;
itemCount = 0;
}
//--------------------------------------
_StringTable::~_StringTable()
{
dFree(buckets);
}
//--------------------------------------
void _StringTable::create()
{
AssertFatal(StringTable == NULL, "StringTable::create: StringTable already exists.");
StringTable = new _StringTable;
}
//--------------------------------------
void _StringTable::destroy()
{
AssertFatal(StringTable != NULL, "StringTable::destroy: StringTable does not exist.");
delete StringTable;
StringTable = NULL;
}
//--------------------------------------
StringTableEntry _StringTable::insert(const char* val, const bool caseSens)
{
Node **walk, *temp;
U32 key = hashString(val);
walk = &buckets[key % numBuckets];
while((temp = *walk) != NULL) {
if(caseSens && !dStrcmp(temp->val, val))
return temp->val;
else if(!caseSens && !dStricmp(temp->val, val))
return temp->val;
walk = &(temp->next);
}
char *ret = 0;
if(!*walk) {
*walk = (Node *) mempool.alloc(sizeof(Node));
(*walk)->next = 0;
(*walk)->val = (char *) mempool.alloc(dStrlen(val) + 1);
dStrcpy((*walk)->val, val);
ret = (*walk)->val;
itemCount ++;
}
if(itemCount > 2 * numBuckets) {
resize(4 * numBuckets - 1);
}
return ret;
}
//--------------------------------------
StringTableEntry _StringTable::insertn(const char* src, S32 len, const bool caseSens)
{
char val[256];
AssertFatal(len < 255, "Invalid string to insertn");
dStrncpy(val, src, len);
val[len] = 0;
return insert(val, caseSens);
}
//--------------------------------------
StringTableEntry _StringTable::lookup(const char* val, const bool caseSens)
{
Node **walk, *temp;
U32 key = hashString(val);
walk = &buckets[key % numBuckets];
while((temp = *walk) != NULL) {
if(caseSens && !dStrcmp(temp->val, val))
return temp->val;
else if(!caseSens && !dStricmp(temp->val, val))
return temp->val;
walk = &(temp->next);
}
return NULL;
}
//--------------------------------------
StringTableEntry _StringTable::lookupn(const char* val, S32 len, const bool caseSens)
{
Node **walk, *temp;
U32 key = hashStringn(val, len);
walk = &buckets[key % numBuckets];
while((temp = *walk) != NULL) {
if(caseSens && !dStrncmp(temp->val, val, len) && temp->val[len] == 0)
return temp->val;
else if(!caseSens && !dStrnicmp(temp->val, val, len) && temp->val[len] == 0)
return temp->val;
walk = &(temp->next);
}
return NULL;
}
//--------------------------------------
void _StringTable::resize(const U32 newSize)
{
Node *head = NULL, *walk, *temp;
U32 i;
// reverse individual bucket lists
// we do this because new strings are added at the end of bucket
// lists so that case sens strings are always after their
// corresponding case insens strings
for(i = 0; i < numBuckets; i++) {
walk = buckets[i];
while(walk)
{
temp = walk->next;
walk->next = head;
head = walk;
walk = temp;
}
}
buckets = (Node **) dRealloc(buckets, newSize * sizeof(Node));
for(i = 0; i < newSize; i++) {
buckets[i] = 0;
}
numBuckets = newSize;
walk = head;
while(walk) {
U32 key;
Node *temp = walk;
walk = walk->next;
key = hashString(temp->val);
temp->next = buckets[key % newSize];
buckets[key % newSize] = temp;
}
}

146
engine/core/stringTable.h Executable file
View File

@ -0,0 +1,146 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _STRINGTABLE_H_
#define _STRINGTABLE_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _DATACHUNKER_H_
#include "core/dataChunker.h"
#endif
//--------------------------------------
/// A global table for the hashing and tracking of strings.
///
/// Only one _StringTable is ever instantiated in Torque. It is accessible via the
/// global variable StringTable.
///
/// StringTable is used to manage strings in Torque. It performs the following tasks:
/// - Ensures that only one pointer is ever used for a given string (through
/// insert()).
/// - Allows the lookup of a string in the table.
///
/// @code
/// // Adding a string to the StringTable.
/// StringTableEntry mRoot;
/// mRoot = StringTable->insert(root);
///
/// // Looking up a string in the StringTable.
/// StringTableEntry stName = StringTable->lookupn(name, len);
///
/// // Comparing two strings in the StringTable (see below).
/// if(mRoot == stName) Con::printf("These strings are equal!");
/// @endcode
///
/// <b>But why is this useful, you ask?</b> Because every string that's run through the
/// StringTable is stored once and only once, every string has one and only one
/// pointer mapped to it. As a pointer is an integer value (usually an unsigned int),
/// so we can do several neat things:
/// - StringTableEntrys can be compared directly for equality, instead of using
/// the time-consuming dStrcmp() or dStricmp() function.
/// - For things like object names, we can avoid storing multiple copies of the
/// string containing the name. The StringTable ensures that we only ever store
/// one copy.
/// - When we're doing lookups by name (for instances, of resources), we can determine
/// if the object is even registered in the system by looking up its name in the
/// StringTable. Then, we can use the pointer as a hash key.
///
/// The scripting engine and the resource manager are the primary users of the
/// StringTable.
///
/// @note Be aware that the StringTable NEVER DEALLOCATES memory, so be careful when you
/// add strings to it. If you carelessly add many strings, you will end up wasting
/// space.
class _StringTable
{
private:
/// @name Implementation details
/// @{
/// This is internal to the _StringTable class.
struct Node
{
char *val;
Node *next;
};
Node** buckets;
U32 numBuckets;
U32 itemCount;
DataChunker mempool;
protected:
static const U32 csm_stInitSize;
_StringTable();
~_StringTable();
/// @}
public:
/// Initialize StringTable.
///
/// This is called at program start to initialize the StringTable global.
static void create();
/// Destroy the StringTable
///
/// This is called at program end to destroy the StringTable global.
static void destroy();
/// Get a pointer from the string table, adding the string to the table
/// if it was not already present.
///
/// @param string String to check in the table (and add).
/// @param caseSens Determines whether case matters.
StringTableEntry insert(const char *string, bool caseSens = false);
/// Get a pointer from the string table, adding the string to the table
/// if it was not already present.
///
/// @param string String to check in the table (and add).
/// @param len Length of the string in bytes.
/// @param caseSens Determines whether case matters.
StringTableEntry insertn(const char *string, S32 len, bool caseSens = false);
/// Get a pointer from the string table, NOT adding the string to the table
/// if it was not already present.
///
/// @param string String to check in the table (but not add).
/// @param caseSens Determines whether case matters.
StringTableEntry lookup(const char *string, bool caseSens = false);
/// Get a pointer from the string table, NOT adding the string to the table
/// if it was not already present.
///
/// @param string String to check in the table (but not add).
/// @param len Length of string in bytes.
/// @param caseSens Determines whether case matters.
StringTableEntry lookupn(const char *string, S32 len, bool caseSens = false);
/// Resize the StringTable to be able to hold newSize items. This
/// is called automatically by the StringTable when the table is
/// full past a certain threshhold.
///
/// @param newSize Number of new items to allocate space for.
void resize(const U32 newSize);
/// Hash a string into a U32.
static U32 hashString(const char* in_pString);
/// Hash a string of given length into a U32.
static U32 hashStringn(const char* in_pString, S32 len);
};
extern _StringTable *StringTable;
#endif //_STRINGTABLE_H_

19
engine/core/tAlgorithm.h Executable file
View File

@ -0,0 +1,19 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TALGORITHM_H_
#define _TALGORITHM_H_
//Includes
template <class Iterator, class Value>
Iterator find(Iterator first, Iterator last, Value value)
{
while (first != last && *first != value)
++first;
return first;
}
#endif //_TALGORITHM_H_

133
engine/core/tSparseArray.h Executable file
View File

@ -0,0 +1,133 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TSPARSEARRAY_H_
#define _TSPARSEARRAY_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _PLATFORMASSERT_H_
#include "platform/platformAssert.h"
#endif
template <class T>
class SparseArray
{
protected:
struct Node {
T* pObject;
U32 key;
Node* next;
};
protected:
U32 mModulus;
Node* mSentryTables;
void clearTables(); // Note: _deletes_ the objects!
public:
SparseArray(const U32 modulusSize = 64);
~SparseArray();
void insert(T* pObject, U32 key);
T* remove(U32 key);
T* retreive(U32 key);
};
template <class T>
inline SparseArray<T>::SparseArray(const U32 modulusSize)
{
AssertFatal(modulusSize > 0, "Error, modulus must be > 0");
mModulus = modulusSize;
mSentryTables = new Node[mModulus];
for (U32 i = 0; i < mModulus; i++)
mSentryTables[i].next = NULL;
}
template <class T>
inline SparseArray<T>::~SparseArray()
{
clearTables();
}
template <class T>
inline void SparseArray<T>::clearTables()
{
for (U32 i = 0; i < mModulus; i++) {
Node* pProbe = mSentryTables[i].next;
while (pProbe != NULL) {
Node* pNext = pProbe->next;
delete pProbe->pObject;
delete pProbe;
pProbe = pNext;
}
}
delete [] mSentryTables;
mSentryTables = NULL;
mModulus = 0;
}
template <class T>
inline void SparseArray<T>::insert(T* pObject, U32 key)
{
U32 insert = key % mModulus;
Node* pNew = new Node;
pNew->pObject = pObject;
pNew->key = key;
pNew->next = mSentryTables[insert].next;
mSentryTables[insert].next = pNew;
#ifdef TORQUE_DEBUG
Node* probe = pNew->next;
while (probe != NULL) {
AssertFatal(probe->key != key, "error, duplicate keys in sparse array!");
probe = probe->next;
}
#endif
}
template <class T>
inline T* SparseArray<T>::remove(U32 key)
{
U32 remove = key % mModulus;
Node* probe = mSentryTables[remove];
while (probe->next != NULL) {
if (probe->next->key == key) {
Node* remove = probe->next;
T* pReturn = remove->pObject;
probe->next = remove->next;
delete remove;
return pReturn;
}
probe = probe->next;
}
AssertFatal(false, "Key didn't exist in the array!");
return NULL;
}
template <class T>
inline T* SparseArray<T>::retreive(U32 key)
{
U32 retrieve = key % mModulus;
Node* probe = mSentryTables[retrieve];
while (probe->next != NULL) {
if (probe->next->key == key) {
return probe->next->pObject;
}
probe = probe->next;
}
AssertFatal(false, "Key didn't exist in the array!");
return NULL;
}
#endif //_TSPARSEARRAY_H_

74
engine/core/tVector.cc Executable file
View File

@ -0,0 +1,74 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/tVector.h"
#ifdef TORQUE_DEBUG_GUARD
bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize,
const char* fileName,
const U32 lineNum)
{
if (newCount > 0) {
U32 blocks = newCount / VectorBlockSize;
if (newCount % VectorBlockSize)
blocks++;
S32 mem_size = blocks * VectorBlockSize * elemSize;
if (*arrayPtr != NULL)
{
*arrayPtr = dRealloc(*arrayPtr,mem_size);
}
else
{
const char* pUseFileName = fileName != NULL ? fileName : __FILE__;
U32 useLineNum = fileName != NULL ? lineNum : __LINE__;
*arrayPtr = dMalloc_r(mem_size, pUseFileName, useLineNum);
}
*aCount = newCount;
*aSize = blocks * VectorBlockSize;
return true;
}
if (*arrayPtr) {
dFree(*arrayPtr);
*arrayPtr = 0;
}
*aSize = 0;
*aCount = 0;
return true;
}
#else
bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize)
{
if (newCount > 0)
{
U32 blocks = newCount / VectorBlockSize;
if (newCount % VectorBlockSize)
blocks++;
S32 mem_size = blocks * VectorBlockSize * elemSize;
*arrayPtr = *arrayPtr ? dRealloc(*arrayPtr,mem_size) :
dMalloc(mem_size);
*aCount = newCount;
*aSize = blocks * VectorBlockSize;
return true;
}
if (*arrayPtr)
{
dFree(*arrayPtr);
*arrayPtr = 0;
}
*aSize = 0;
*aCount = 0;
return true;
}
#endif

662
engine/core/tVector.h Executable file
View File

@ -0,0 +1,662 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TVECTOR_H_
#define _TVECTOR_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
//-----------------------------------------------------------------------------
// Helper definitions for the vector class.
/// Size of memory blocks to allocate at a time for vectors.
#define VectorBlockSize 16
#ifdef TORQUE_DEBUG_GUARD
extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize,
const char* fileName,
const U32 lineNum);
#else
extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize);
#endif
/// Use the following macro to bind a vector to a particular line
/// of the owning class for memory tracking purposes
#ifdef TORQUE_DEBUG_GUARD
#define VECTOR_SET_ASSOCIATION(x) x.setFileAssociation(__FILE__, __LINE__)
#else
#define VECTOR_SET_ASSOCIATION(x)
#endif
// =============================================================================
/// A dynamic array class.
///
/// The vector grows as you insert or append
/// elements. Insertion is fastest at the end of the array. Resizing
/// of the array can be avoided by pre-allocating space using the
/// reserve() method.
///
/// <b>***WARNING***</b>
///
/// This template does not initialize, construct or destruct any of
/// it's elements. This means don't use this template for elements
/// (classes) that need these operations. This template is intended
/// to be used for simple structures that have no constructors or
/// destructors.
///
/// @nosubgrouping
template<class T>
class Vector
{
protected:
U32 mElementCount;
U32 mArraySize;
T* mArray;
#ifdef TORQUE_DEBUG_GUARD
const char* mFileAssociation;
U32 mLineAssociation;
#endif
bool resize(U32);
public:
Vector(const U32 initialSize = 0);
Vector(const U32 initialSize, const char* fileName, const U32 lineNum);
Vector(const char* fileName, const U32 lineNum);
Vector(const Vector&);
~Vector();
#ifdef TORQUE_DEBUG_GUARD
void setFileAssociation(const char* file, const U32 line);
#endif
/// @name STL interface
/// @{
typedef T value_type;
typedef T& reference;
typedef const T& const_reference;
typedef T* iterator;
typedef const T* const_iterator;
typedef S32 difference_type;
typedef U32 size_type;
Vector<T>& operator=(const Vector<T>& p);
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
S32 size() const;
bool empty() const;
void insert(iterator, const T&);
void erase(iterator);
T& front();
const T& front() const;
T& back();
const T& back() const;
void push_front(const T&);
void push_back(const T&);
void pop_front();
void pop_back();
T& operator[](U32);
const T& operator[](U32) const;
T& operator[](S32 i) { return operator[](U32(i)); }
const T& operator[](S32 i ) const { return operator[](U32(i)); }
void reserve(U32);
U32 capacity() const;
/// @}
/// @name Extended interface
/// @{
U32 memSize() const;
T* address() const;
U32 setSize(U32);
void increment(U32 = 1);
void decrement(U32 = 1);
void insert(U32);
void erase(U32);
void erase_fast(U32);
void erase_fast(iterator);
void clear();
void compact();
T& first();
T& last();
const T& first() const;
const T& last() const;
void set(void * addr, U32 sz);
/// Merge another vector into this one.
///
/// @author BJW 8/20/97
void merge(const Vector& p);
/// @}
};
template<class T> inline Vector<T>::~Vector()
{
dFree(mArray);
}
template<class T> inline Vector<T>::Vector(const U32 initialSize)
{
#ifdef TORQUE_DEBUG_GUARD
mFileAssociation = NULL;
mLineAssociation = 0;
#endif
mArray = 0;
mElementCount = 0;
mArraySize = 0;
if(initialSize)
reserve(initialSize);
}
template<class T> inline Vector<T>::Vector(const U32 initialSize,
const char* fileName,
const U32 lineNum)
{
#ifdef TORQUE_DEBUG_GUARD
mFileAssociation = fileName;
mLineAssociation = lineNum;
#else
fileName;
lineNum;
#endif
mArray = 0;
mElementCount = 0;
mArraySize = 0;
if(initialSize)
reserve(initialSize);
}
template<class T> inline Vector<T>::Vector(const char* fileName,
const U32 lineNum)
{
#ifdef TORQUE_DEBUG_GUARD
mFileAssociation = fileName;
mLineAssociation = lineNum;
#else
fileName;
lineNum;
#endif
mArray = 0;
mElementCount = 0;
mArraySize = 0;
}
template<class T> inline Vector<T>::Vector(const Vector& p)
{
#ifdef TORQUE_DEBUG_GUARD
mFileAssociation = p.mFileAssociation;
mLineAssociation = p.mLineAssociation;
#endif
mArray = 0;
resize(p.mElementCount);
if (p.mElementCount)
dMemcpy(mArray,p.mArray,mElementCount * sizeof(value_type));
}
#ifdef TORQUE_DEBUG_GUARD
template<class T> inline void Vector<T>::setFileAssociation(const char* file,
const U32 line)
{
mFileAssociation = file;
mLineAssociation = line;
}
#endif
template<class T> inline U32 Vector<T>::memSize() const
{
return capacity() * sizeof(T);
}
template<class T> inline T* Vector<T>::address() const
{
return mArray;
}
template<class T> inline U32 Vector<T>::setSize(U32 size)
{
if (size > mArraySize)
resize(size);
else
mElementCount = size;
return mElementCount;
}
template<class T> inline void Vector<T>::increment(U32 delta)
{
if ((mElementCount += delta) > mArraySize)
resize(mElementCount);
}
template<class T> inline void Vector<T>::decrement(U32 delta)
{
if (mElementCount > delta)
mElementCount -= delta;
else
mElementCount = 0;
}
template<class T> inline void Vector<T>::insert(U32 index)
{
// Assert: index >= 0 && index < mElementCount
increment();
dMemmove(&mArray[index + 1],
&mArray[index],
(mElementCount - index - 1) * sizeof(value_type));
}
template<class T> inline void Vector<T>::erase(U32 index)
{
// Assert: index >= 0 && index < mElementCount
dMemmove(&mArray[index],
&mArray[index + 1],
(mElementCount - index - 1) * sizeof(value_type));
decrement();
}
template<class T> inline void Vector<T>::erase_fast(U32 index)
{
// CAUTION: this operator does NOT maintain list order
// Copy the last element into the deleted 'hole' and decrement the
// size of the vector.
// Assert: index >= 0 && index < mElementCount
if (index < (mElementCount - 1))
dMemmove(&mArray[index], &mArray[mElementCount - 1], sizeof(value_type));
decrement();
}
template<class T> inline T& Vector<T>::first()
{
return mArray[0];
}
template<class T> inline const T& Vector<T>::first() const
{
return mArray[0];
}
template<class T> inline T& Vector<T>::last()
{
AssertFatal(mElementCount != 0, "Error, no last element of a zero sized array!");
return mArray[mElementCount - 1];
}
template<class T> inline const T& Vector<T>::last() const
{
return mArray[mElementCount - 1];
}
template<class T> inline void Vector<T>::clear()
{
mElementCount = 0;
}
template<class T> inline void Vector<T>::compact()
{
resize(mElementCount);
}
//-----------------------------------------------------------------------------
template<class T> inline Vector<T>& Vector<T>::operator=(const Vector<T>& p)
{
resize(p.mElementCount);
if (p.mElementCount)
dMemcpy(mArray,p.mArray,mElementCount * sizeof(value_type));
return *this;
}
template<class T> inline typename Vector<T>::iterator Vector<T>::begin()
{
return mArray;
}
template<class T> inline typename Vector<T>::const_iterator Vector<T>::begin() const
{
return mArray;
}
template<class T> inline typename Vector<T>::iterator Vector<T>::end()
{
return mArray + mElementCount;
}
template<class T> inline typename Vector<T>::const_iterator Vector<T>::end() const
{
return mArray + mElementCount;
}
template<class T> inline S32 Vector<T>::size() const
{
return (S32)mElementCount;
}
template<class T> inline bool Vector<T>::empty() const
{
return (mElementCount == 0);
}
template<class T> inline void Vector<T>::insert(iterator p,const T& x)
{
U32 index = (U32) (p - mArray);
insert(index);
mArray[index] = x;
}
template<class T> inline void Vector<T>::erase(iterator q)
{
erase(U32(q - mArray));
}
template<class T> inline void Vector<T>::erase_fast(iterator q)
{
erase_fast(U32(q - mArray));
}
template<class T> inline T& Vector<T>::front()
{
return *begin();
}
template<class T> inline const T& Vector<T>::front() const
{
return *begin();
}
template<class T> inline T& Vector<T>::back()
{
return *end();
}
template<class T> inline const T& Vector<T>::back() const
{
return *end();
}
template<class T> inline void Vector<T>::push_front(const T& x)
{
insert(0);
mArray[0] = x;
}
template<class T> inline void Vector<T>::push_back(const T& x)
{
increment();
mArray[mElementCount - 1] = x;
}
template<class T> inline void Vector<T>::pop_front()
{
erase(U32(0));
}
template<class T> inline void Vector<T>::pop_back()
{
decrement();
}
template<class T> inline T& Vector<T>::operator[](U32 index)
{
//AssertFatal((index < mElementCount), "Index too large.");
return mArray[index];
}
template<class T> inline const T& Vector<T>::operator[](U32 index) const
{
//AssertFatal((index < mElementCount), "Index too large.");
return mArray[index];
}
template<class T> inline void Vector<T>::reserve(U32 size)
{
if (size > mArraySize) {
U32 ec = mElementCount;
if (resize(size))
mElementCount = ec;
}
}
template<class T> inline U32 Vector<T>::capacity() const
{
return mArraySize;
}
template<class T> inline void Vector<T>::set(void * addr, U32 sz)
{
setSize(sz);
if (addr)
dMemcpy(address(),addr,sz*sizeof(T));
}
//-----------------------------------------------------------------------------
template<class T> inline bool Vector<T>::resize(U32 ecount)
{
#ifdef TORQUE_DEBUG_GUARD
return VectorResize(&mArraySize, &mElementCount, (void**) &mArray, ecount, sizeof(T),
mFileAssociation, mLineAssociation);
#else
return VectorResize(&mArraySize, &mElementCount, (void**) &mArray, ecount, sizeof(T));
#endif
}
// BJW 8/20/97
// code to merge a vector into this one
template<class T> inline void Vector<T>::merge(const Vector& p)
{
if (p.size()) {
S32 oldsize = size();
resize(oldsize + p.size());
dMemcpy( &mArray[oldsize], p.address(), p.size() * sizeof(T) );
}
}
//-----------------------------------------------------------------------------
/// Template for vectors of pointers.
template <class T>
class VectorPtr : public Vector<void*>
{
/// @deprecated Disallowed.
VectorPtr(const VectorPtr&); // Disallowed
public:
VectorPtr();
VectorPtr(const char* fileName, const U32 lineNum);
/// @name STL interface
/// @{
typedef T value_type;
typedef T& reference;
typedef const T& const_reference;
typedef T* iterator;
typedef const T* const_iterator;
typedef U32 difference_type;
typedef U32 size_type;
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
void insert(iterator,const T&);
void erase(iterator);
T& front();
const T& front() const;
T& back();
const T& back() const;
void push_front(const T&);
void push_back(const T&);
T& operator[](U32);
const T& operator[](U32) const;
/// @}
/// @name Extended interface
/// @{
typedef Vector<void*> Parent;
T& first();
T& last();
const T& first() const;
const T& last() const;
void erase_fast(U32);
void erase_fast(iterator);
/// @}
};
//-----------------------------------------------------------------------------
template<class T> inline VectorPtr<T>::VectorPtr()
{
//
}
template<class T> inline VectorPtr<T>::VectorPtr(const char* fileName,
const U32 lineNum)
: Vector<void*>(fileName, lineNum)
{
//
}
template<class T> inline T& VectorPtr<T>::first()
{
return (T&)Parent::first();
}
template<class T> inline const T& VectorPtr<T>::first() const
{
return (const T)Parent::first();
}
template<class T> inline T& VectorPtr<T>::last()
{
return (T&)Parent::last();
}
template<class T> inline const T& VectorPtr<T>::last() const
{
return (const T&)Parent::last();
}
template<class T> inline typename VectorPtr<T>::iterator VectorPtr<T>::begin()
{
return (iterator)Parent::begin();
}
template<class T> inline typename VectorPtr<T>::const_iterator VectorPtr<T>::begin() const
{
return (const_iterator)Parent::begin();
}
template<class T> inline typename VectorPtr<T>::iterator VectorPtr<T>::end()
{
return (iterator)Parent::end();
}
template<class T> inline typename VectorPtr<T>::const_iterator VectorPtr<T>::end() const
{
return (const_iterator)Parent::end();
}
template<class T> inline void VectorPtr<T>::insert(iterator i,const T& x)
{
Parent::insert( (Parent::iterator)i, (Parent::reference)x );
}
template<class T> inline void VectorPtr<T>::erase(iterator i)
{
Parent::erase( (Parent::iterator)i );
}
template<class T> inline void VectorPtr<T>::erase_fast(U32 index)
{
// CAUTION: this operator does maintain list order
// Copy the last element into the deleted 'hole' and decrement the
// size of the vector.
// Assert: index >= 0 && index < mElementCount
if (index < (mElementCount - 1))
mArray[index] = mArray[mElementCount - 1];
decrement();
}
template<class T> inline void VectorPtr<T>::erase_fast(iterator i)
{
erase_fast(U32(i - iterator(mArray)));
}
template<class T> inline T& VectorPtr<T>::front()
{
return *begin();
}
template<class T> inline const T& VectorPtr<T>::front() const
{
return *begin();
}
template<class T> inline T& VectorPtr<T>::back()
{
return *end();
}
template<class T> inline const T& VectorPtr<T>::back() const
{
return *end();
}
template<class T> inline void VectorPtr<T>::push_front(const T& x)
{
Parent::push_front((Parent::const_reference)x);
}
template<class T> inline void VectorPtr<T>::push_back(const T& x)
{
Parent::push_back((Parent::const_reference)x);
}
template<class T> inline T& VectorPtr<T>::operator[](U32 index)
{
return (T&)Parent::operator[](index);
}
template<class T> inline const T& VectorPtr<T>::operator[](U32 index) const
{
return (const T&)Parent::operator[](index);
}
#endif //_TVECTOR_H_

303
engine/core/tagDictionary.cc Executable file
View File

@ -0,0 +1,303 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/tagDictionary.h"
#include "core/stream.h"
namespace {
const char TAG_ASCII_ID[] = "[TAG]";
const char TAG_ASCII_END[] = "[END]";
const char TAG_ASCII_HEADER[] = "// Auto-Generated by TagDictionary class";
const S32 sg_tagDictAsciiUser = 1;
} // namespace
TagDictionary tagDictionary;
TagDictionary::TagDictionary()
{
numBuckets = 29;
defineHashBuckets = (TagEntry **) dMalloc(numBuckets * sizeof(TagEntry *));
idHashBuckets = (TagEntry **) dMalloc(numBuckets * sizeof(TagEntry *));
S32 i;
for(i = 0; i < numBuckets; i++)
{
defineHashBuckets[i] = NULL;
idHashBuckets[i] = NULL;
}
numEntries = 0;
entryChain = NULL;
}
TagDictionary::~TagDictionary()
{
dFree(defineHashBuckets);
dFree(idHashBuckets);
}
//------------------------------------------------------------------------------
static inline S32 hashId(S32 id, S32 tsize)
{
return id % tsize;
}
static inline S32 hashDefine(StringTableEntry define, S32 tsize)
{
return ((S32)((dsize_t)define) >> 2) % tsize;
}
//------------------------------------------------------------------------------
bool TagDictionary::addEntry(S32 value, StringTableEntry define, StringTableEntry string)
{
if(!value)
return false;
//#pragma message "put console prints back"
if(idToDefine(value))
{
AssertWarn(false, avar("Error: id %d already defined to a tag.", value));
//Con::printf("Error: id %d already defined to a tag.", value);
return false;
}
S32 tempTag;
if((tempTag = defineToId(define)) != 0)
{
AssertWarn(false, avar("Error: define %s already defined to tag %d.", define, tempTag));
//Con::printf("Error: define %s already defined to tag %d.", define, tempTag);
return false;
}
TagEntry *newEntry = (TagEntry *) mempool.alloc(sizeof(TagEntry));
newEntry->id = value;
newEntry->define = define;
newEntry->string = string;
numEntries++;
if(numEntries > numBuckets)
{
numBuckets = numBuckets * 2 + 1;
defineHashBuckets = (TagEntry **) dRealloc(defineHashBuckets, numBuckets * sizeof(TagEntry *));
idHashBuckets = (TagEntry **) dRealloc(idHashBuckets, numBuckets * sizeof(TagEntry *));
S32 i;
for(i = 0; i < numBuckets; i++)
{
defineHashBuckets[i] = NULL;
idHashBuckets[i] = NULL;
}
TagEntry *walk = entryChain;
while(walk)
{
S32 index = hashId(walk->id, numBuckets);
walk->idHashLink = idHashBuckets[index];
idHashBuckets[index] = walk;
index = hashDefine(walk->define, numBuckets);
walk->defineHashLink = defineHashBuckets[index];
defineHashBuckets[index] = walk;
walk = walk->chain;
}
}
newEntry->chain = entryChain;
entryChain = newEntry;
S32 index = hashId(newEntry->id, numBuckets);
newEntry->idHashLink = idHashBuckets[index];
idHashBuckets[index] = newEntry;
index = hashDefine(newEntry->define, numBuckets);
newEntry->defineHashLink = defineHashBuckets[index];
defineHashBuckets[index] = newEntry;
return true;
}
//------------------------------------------------------------------------------
bool TagDictionary::writeHeader(Stream& io_sio)
{
char buff[15000];
Vector<S32> v;
TagEntry *walk = entryChain;
while(walk)
{
v.push_back(walk->id);
walk = walk->chain;
}
sortIdVector(v);
io_sio.write( sizeof(TAG_ASCII_HEADER)-1, TAG_ASCII_HEADER);
io_sio.write( 4, "\r\n\r\n");
char exclude[256];
char tempBuf[256];
dSprintf(exclude, sizeof(exclude), "_TD%10.10u_H_", Platform::getVirtualMilliseconds() / 4);
dSprintf(tempBuf, sizeof(tempBuf), "#ifndef %s\r\n", exclude);
io_sio.write(dStrlen(tempBuf), tempBuf);
dSprintf(tempBuf, sizeof(tempBuf), "#define %s\r\n\r\n", exclude);
io_sio.write(dStrlen(tempBuf), tempBuf);
for (U32 i = 0; i < v.size(); i++)
{
dSprintf(buff, sizeof(buff), "#define %s (%d)\r\n", idToDefine(v[i]), v[i]);
io_sio.write(dStrlen(buff), buff);
}
dSprintf(tempBuf, sizeof(tempBuf), "\r\n#endif // %s\r\n", exclude);
io_sio.write(dStrlen(tempBuf), tempBuf);
return (io_sio.getStatus() == Stream::Ok);
}
//------------------------------------------------------------------------------
StringTableEntry TagDictionary::defineToString(StringTableEntry tag)
{
S32 index = hashDefine(tag, numBuckets);
if (index < 0) return NULL;
TagEntry *walk = defineHashBuckets[index];
while(walk)
{
if(walk->define == tag)
return walk->string;
walk = walk->defineHashLink;
}
return NULL;
}
S32 TagDictionary::defineToId(StringTableEntry tag)
{
S32 index = hashDefine(tag, numBuckets);
if (index < 0) return 0;
TagEntry *walk = defineHashBuckets[index];
while(walk)
{
if(walk->define == tag)
return walk->id;
walk = walk->defineHashLink;
}
return 0;
}
StringTableEntry TagDictionary::idToString(S32 id)
{
S32 index = hashId(id, numBuckets);
if (index < 0) return NULL;
TagEntry *walk = idHashBuckets[index];
while(walk)
{
if(walk->id == id)
return walk->string;
walk = walk->idHashLink;
}
return NULL;
}
StringTableEntry TagDictionary::idToDefine(S32 id)
{
S32 index = hashId(id, numBuckets);
if (index < 0) return NULL;
TagEntry *walk = idHashBuckets[index];
while(walk)
{
if(walk->id == id)
return walk->define;
walk = walk->idHashLink;
}
return NULL;
}
//------------------------------------------------------------------------------
void TagDictionary::findIDs(Vector<S32>& out_v,
const S32 in_minID,
const S32 in_maxID )
{
//locate all IDs that lie in between minID and maxID
TagEntry *walk = entryChain;
while(walk)
{
if(walk->id > in_minID && walk->id < in_maxID)
out_v.push_back(walk->id);
walk = walk->chain;
}
sortIdVector(out_v);
}
//------------------------------------------------------------------------------
void TagDictionary::findStrings(Vector<S32>& out_v, const char* in_pPattern)
{
//locate all strings that match the pattern
//
TagEntry *walk = entryChain;
while(walk)
{
if (match(in_pPattern, walk->string))
out_v.push_back(walk->id);
walk = walk->chain;
}
sortIdVector(out_v);
}
//------------------------------------------------------------------------------
void TagDictionary::findDefines(Vector<S32>& out_v, const char* in_pPattern)
{
//locate all define strings that match the pattern and add their ID
//to the given vector
//
TagEntry *walk = entryChain;
while(walk)
{
if (match(in_pPattern, walk->define))
out_v.push_back(walk->id);
walk = walk->chain;
}
sortIdVector(out_v);
}
//------------------------------------------------------------------------------
bool TagDictionary::match(const char* pattern, const char* str)
{
//quick and dirty recursive DOS-style wild-card string matcher
//
switch (*pattern) {
case '\0':
return !*str;
case '*':
return match(pattern+1, str) || *str && match(pattern, str+1);
case '?':
return *str && match(pattern+1, str+1);
default:
return (*pattern == *str) && match(pattern+1, str+1);
}
}
//------------------------------------------------------------------------------
static int QSORT_CALLBACK idCompare(const void *in_p1, const void *in_p2)
{
return *((S32 *) in_p1) - *((S32 *) in_p2);
}
void TagDictionary::sortIdVector(Vector<S32>& out_v)
{
dQsort(out_v.address(), out_v.size(), sizeof(S32), idCompare);
}

66
engine/core/tagDictionary.h Executable file
View File

@ -0,0 +1,66 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TAGDICTIONARY_H_
#define _TAGDICTIONARY_H_
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
class Stream;
class TagDictionary
{
struct TagEntry
{
S32 id;
StringTableEntry define;
StringTableEntry string;
TagEntry *chain; // for linear traversal
TagEntry *defineHashLink;
TagEntry *idHashLink;
};
TagEntry **defineHashBuckets;
TagEntry **idHashBuckets;
TagEntry *entryChain;
DataChunker mempool;
S32 numBuckets;
S32 numEntries;
bool match(const char* pattern, const char* str);
void sortIdVector(Vector<S32>& out_v);
public:
TagDictionary();
~TagDictionary();
//IO functions
//
bool writeHeader(Stream &);
// String/Define retrieval and search functions...
//
bool addEntry(S32 value, StringTableEntry define, StringTableEntry string);
StringTableEntry defineToString(StringTableEntry tag);
StringTableEntry idToString(S32 tag);
StringTableEntry idToDefine(S32 tag);
S32 defineToId(StringTableEntry tag);
// get IDs such that minID < IDs < maxID
void findIDs( Vector<S32> &v, const S32 minID, const S32 maxID );
void findStrings( Vector<S32> &v, const char *pattern);
void findDefines( Vector<S32> &v, const char *pattern);
};
extern TagDictionary tagDictionary;
#endif //_TAGDICTIONARY_H_

968
engine/core/theoraPlayer.cc Executable file
View File

@ -0,0 +1,968 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "theoraPlayer.h"
#include "math/mMath.h"
#include "util/safeDelete.h"
#include "platform/profiler.h"
//-----------------------------------------------------------------------------
TheoraTexture::MagicalTrevor::BufInf::BufInf()
{
id=0;
time=0;
next=NULL;
}
TheoraTexture::MagicalTrevor::MagicalTrevor()
{
mBufListHead = NULL;
mLastBufferID = -1;
mMutex = Mutex::createMutex();
}
TheoraTexture::MagicalTrevor::~MagicalTrevor()
{
// Cheat on freeing, since BufInf has no destructor.
mBuffPool.freeBlocks();
Mutex::destroyMutex(mMutex);
}
const F64 TheoraTexture::MagicalTrevor::advanceTime(const ALuint buffId)
{
MutexHandle handle;
handle.lock(mMutex);
// We basically find the last entry on the list that references
// this buffId, then count how much time it + all its followers
// contains, and return that amount. Then the list is truncated.
// Skip if we just saw this one... we'd better not go
// through all the buffers in one advanceTime call, that would
// confuse the hell out of this code.
if(mLastBufferID == buffId)
return 0.f;
mLastBufferID = buffId;
// Ok, find last occurence of buffId.
BufInf **walk = &mBufListHead;
BufInf **lastOccurence = NULL;
while(*walk)
{
if((*walk)->id == buffId)
lastOccurence = walk;
walk = &(*walk)->next;
}
if(lastOccurence == NULL)
{
Con::warnf("MagicalTrevor::advancetime - no last occurrence for buffer %d found!", buffId);
return 0.f;
}
// We've got the last occurrence, sum the time and truncate the list.
F64 timeSum = 0.f;
walk = lastOccurence;
while(*walk)
{
timeSum += (*walk)->time;
// Blast it and advance.
BufInf *del = *walk;
*walk = (*walk)->next;
mBuffPool.free(del);
}
return timeSum;
}
void TheoraTexture::MagicalTrevor::postBuffer(const ALuint buffId, const F64 duration)
{
MutexHandle handle;
handle.lock(mMutex);
// Stick the buffer at the front of the queue...
BufInf *walk = mBuffPool.alloc();
walk->id = buffId;
walk->time = duration;
// Link it in.
walk->next = mBufListHead;
mBufListHead = walk;
}
const U32 TheoraTexture::MagicalTrevor::getListSize() const
{
MutexHandle handle;
handle.lock(mMutex);
U32 size=0;
// Ok, find last occurence of buffId.
const BufInf *walk = mBufListHead;
while(walk)
{
size++;
walk = walk->next;
}
return size;
}
void TheoraTexture::MagicalTrevor::reset()
{
MutexHandle handle;
handle.lock(mMutex);
// Since we're mostly touched by the thread, let's make this a mutex
// operation.
mBuffPool.freeBlocks();
mBufListHead = NULL;
mLastBufferID = -1;
}
//-----------------------------------------------------------------------------
TheoraTexture::TheoraTexture()
{
init();
}
TheoraTexture::TheoraTexture(const char* szFilename, bool fPlay, Audio::Description* desc)
{
init();
setFile(szFilename, fPlay, false, desc);
}
void TheoraTexture::init()
{
mReady = false;
mPlaying = false;
mHasVorbis = false;
mVorbisHandle = NULL;
mVorbisBuffer = NULL;
mPlayThread = NULL;
mTheoraFile = NULL;
mTextureHandle = NULL;
mMagicalTrevor.reset();
}
TheoraTexture::~TheoraTexture()
{
destroyTexture();
}
// tears down anything the texture has
void TheoraTexture::destroyTexture(bool restartOgg)
{
mPlaying = false;
// kill the thread if its playing
SAFE_DELETE(mPlayThread);
// kill the sound if its playing
if(mVorbisHandle)
{
alxStop(mVorbisHandle);
mVorbisHandle = NULL;
mVorbisBuffer = NULL; // this is already deleted in alxStop
mMagicalTrevor.reset();
}
if(mHasVorbis)
{
ogg_stream_clear(&mOggVorbisStream);
dMemset(&mOggVorbisStream, 0, sizeof(ogg_stream_state));
vorbis_dsp_clear(&mVorbisDspState);
dMemset(&mVorbisDspState, 0, sizeof(vorbis_dsp_state));
vorbis_block_clear(&mVorbisBlock);
dMemset(&mVorbisBlock, 0, sizeof(vorbis_block));
vorbis_comment_clear(&mVorbisComment);
vorbis_info_clear(&mVorbisInfo);
mHasVorbis = false;
mMagicalTrevor.reset();
}
if(mReady)
{
ogg_stream_clear(&mOggTheoraStream);
theora_clear(&mTheoraState);
theora_comment_clear(&mTheoraComment);
theora_info_clear(&mTheoraInfo);
ogg_sync_clear(&mOggSyncState);
}
// close the file if it's open
if(mTheoraFile)
{
ResourceManager->closeStream(mTheoraFile);
mTheoraFile = NULL;
}
if(restartOgg)
return;
// Set us to a null state.
mReady = false;
//SAFE_DELETE(mTextureHandle);
}
// Takes file name to open, and whether it should autoplay when loaded
bool TheoraTexture::setFile(const char* filename, bool doPlay, bool doRestart, Audio::Description* desc)
{
if(mPlaying)
stop();
if(mReady)
destroyTexture(doRestart);
// open the theora file
mTheoraFile = ResourceManager->openStream(filename);
mMagicalTrevor.reset();
if(!mTheoraFile)
{
Con::errorf("TheoraTexture::setFile - Theora file '%s' not found.", filename);
return false;
}
Con::printf("TheoraTexture - Loading file '%s'", filename);
// start up Ogg stream synchronization layer
ogg_sync_init(&mOggSyncState);
// init supporting Theora structures needed in header parsing
theora_comment_init(&mTheoraComment);
theora_info_init(&mTheoraInfo);
// init supporting Vorbis structures needed in header parsing
vorbis_comment_init(&mVorbisComment);
vorbis_info_init(&mVorbisInfo);
if(!parseHeaders())
{
// No theora stream found (must be a vorbis only file?)
// Clean up all the structs
theora_comment_clear(&mTheoraComment);
theora_info_clear(&mTheoraInfo);
// trash vorbis too, this class isn't for playing lone vorbis streams
vorbis_info_clear(&mVorbisInfo);
vorbis_comment_clear(&mVorbisComment);
Con::errorf("TheoraTexture::setFile - Failed to parse Ogg headers");
return false;
}
// If theora stream found, initialize decoders...
theora_decode_init(&mTheoraState, &mTheoraInfo);
// This is a work around for a bug in theora when you're using only the
// decoder (think its fixed in newest theora lib).
mTheoraState.internal_encode = NULL;
// Note our state.
Con::printf(" Ogg logical stream %x is Theora %dx%d %.02f fps video",
mOggTheoraStream.serialno,
mTheoraInfo.width,
mTheoraInfo.height,
(F64)mTheoraInfo.fps_numerator / (F64)mTheoraInfo.fps_denominator);
Con::printf(" - Frame content is %dx%d with offset (%d,%d).",
mTheoraInfo.frame_width,
mTheoraInfo.frame_height,
mTheoraInfo.offset_x,
mTheoraInfo.offset_y);
if(mHasVorbis)
{
vorbis_synthesis_init(&mVorbisDspState, &mVorbisInfo);
vorbis_block_init(&mVorbisDspState, &mVorbisBlock);
Con::printf(" Ogg logical stream %x is Vorbis %d channel %d Hz audio.",
mOggVorbisStream.serialno,
mVorbisInfo.channels,
mVorbisInfo.rate);
if(!(mHasVorbis = createAudioBuffers(desc)))
{
ogg_stream_clear(&mOggVorbisStream);
vorbis_block_clear(&mVorbisBlock);
vorbis_dsp_clear(&mVorbisDspState);
}
}
// Check again because the buffers might fail.
if(!mHasVorbis)
{
// no vorbis stream was found, throw out the vorbis structs
vorbis_info_clear(&mVorbisInfo);
vorbis_comment_clear(&mVorbisComment);
}
if(!mReady)
{
if(!createVideoBuffers())
{
// failed to create buffers, blow everything else up..
ogg_stream_clear(&mOggTheoraStream);
theora_clear(&mTheoraState);
theora_comment_clear(&mTheoraComment);
theora_info_clear(&mTheoraInfo);
ogg_sync_clear(&mOggSyncState);
// And destroy our texture.
destroyTexture();
return false;
}
mReady = true;
}
if(doPlay)
play();
return true;
}
bool TheoraTexture::parseHeaders()
{
ogg_packet sOggPacket;
S32 nTheora = 0;
S32 nVorbis = 0;
S32 ret;
mHasVorbis = false;
// Parse the headers
// find theora and vorbis streams
// search pages till you find the headers
mTheoraFile->setPosition(0);
while(1)
{
ret = bufferOggPage();
if(ret == 0)
break;
if(!ogg_sync_pageout(&mOggSyncState, &mOggPage))
break;
ogg_stream_state testStream;
if(!ogg_page_bos(&mOggPage))
{
// this is not an initial header, queue it up
// exit stream header finding loop (headers always come before non header stuff)
queueOggPage(&mOggPage);
break;
}
// create a stream
ogg_stream_init(&testStream, ogg_page_serialno(&mOggPage));
ogg_stream_pagein(&testStream, &mOggPage);
ogg_stream_packetout(&testStream, &sOggPacket);
// test if its a theora header
if(theora_decode_header(&mTheoraInfo, &mTheoraComment, &sOggPacket) >= 0)
{
// it is theora, copy testStream over to the theora stream
dMemcpy(&mOggTheoraStream, &testStream, sizeof(testStream));
nTheora = 1;
}
// test if its vorbis
else if(vorbis_synthesis_headerin(&mVorbisInfo, &mVorbisComment, &sOggPacket) >= 0)
{
// it is vorbis, copy testStream over to the vorbis stream
dMemcpy(&mOggVorbisStream, &testStream, sizeof(testStream));
mHasVorbis = true;
nVorbis = 1;
}
else
{
// some other stream header? unsupported, toss it
ogg_stream_clear(&testStream);
}
// if both vorbis and theora have been found, exit loop
if(nVorbis && nTheora)
break;
}
if(!nTheora)
{
// no theora stream header found
Con::errorf("TheoraTexture::parseHeaders - No theora stream headers found.");
// HAVE to have theora, thats what this class is for. return failure
return false;
}
// we've now identified all the streams. parse (toss) the secondary header packets.
// it looks like we just have to throw out a few packets from the header page
// so that they arent mistaken as theora movie data? nothing is done with these things..
while((nTheora < 3) ||
(nVorbis && nVorbis < 3))
{
// look for further theora headers
while((nTheora < 3) &&
(ret = ogg_stream_packetout(&mOggTheoraStream, &sOggPacket)))
{
if(ret < 0)
{
Con::errorf("TheoraPlayer::parseHeaders - Error parsing Theora stream headers; corrupt stream? (nothing read?)");
return false;
}
if(theora_decode_header(&mTheoraInfo, &mTheoraComment, &sOggPacket))
{
Con::errorf("TheoraPlayer::parseHeaders - Error parsing Theora stream headers; corrupt stream? (failed to decode)");
return false;
}
// Sanity around corrupt headers.
nTheora++;
if(nTheora == 3)
break;
}
// look for more vorbis headers
while((nVorbis) &&
(nVorbis < 3) &&
(ret = ogg_stream_packetout(&mOggVorbisStream, &sOggPacket)))
{
if(ret < 0)
{
Con::errorf("Error parsing vorbis stream headers; corrupt stream? (nothing read?)");
return false;
}
if(vorbis_synthesis_headerin(&mVorbisInfo, &mVorbisComment, &sOggPacket))
{
Con::errorf("Error parsing Vorbis stream headers; corrupt stream? (bad synthesis_headerin)");
return false;
}
nVorbis++;
if(nVorbis == 3)
break;
}
// The header pages/packets will arrive before anything else we
// care about, or the stream is not obeying spec
// continue searching the next page for the headers
// put the next page into the theora stream
if(ogg_sync_pageout(&mOggSyncState, &mOggPage) > 0)
{
queueOggPage(&mOggPage);
}
else
{
// if there are no more pages, buffer another one
const S32 ret = bufferOggPage();
// if there is nothing left to buffer..
if(ret == 0)
{
Con::errorf("TheoraTexture::parseHeaders - End of file while searching for codec headers.");
return false;
}
}
}
return true;
}
bool TheoraTexture::createVideoBuffers()
{
// Set up our texture.
const GBitmap *bmp = new GBitmap(
getMax((U32)mTheoraInfo.frame_width, (U32)mTheoraInfo.width),
getMax((U32)mTheoraInfo.frame_height, (U32)mTheoraInfo.height),
false, GBitmap::RGB);
mTextureHandle = new TextureHandle(NULL, bmp, true);
// generate yuv conversion lookup tables
generateLookupTables();
return true;
}
bool TheoraTexture::createAudioBuffers(Audio::Description* desc)
{
// Just to be sure, clear out Trevor.
mMagicalTrevor.reset();
// if they didnt pass a description...
if(!desc)
{
// ...fill a default
static Audio::Description sDesc;
desc = &sDesc;
sDesc.mReferenceDistance = 1.0f;
sDesc.mMaxDistance = 100.0f;
sDesc.mConeInsideAngle = 360;
sDesc.mConeOutsideAngle = 360;
sDesc.mConeOutsideVolume = 1.0f;
sDesc.mConeVector.set(0, 0, 1);
sDesc.mEnvironmentLevel = 0.f;
sDesc.mLoopCount = -1;
sDesc.mMinLoopGap = 0;
sDesc.mMaxLoopGap = 0;
sDesc.mIs3D = false;
sDesc.mVolume = 1.0f;
sDesc.mIsLooping = false;
sDesc.mType = 1;
sDesc.mIsStreaming = true;
}
// create an audio handle to use
mVorbisHandle = alxCreateSource(desc, "oggMixedStream");
if(!mVorbisHandle)
{
Con::errorf("Could not alxCreateSource for oggMixedStream.\n");
return false;
}
// get a pointer for it
mVorbisBuffer = dynamic_cast<OggMixedStreamSource*>(alxFindAudioStreamSource(mVorbisHandle));
if(!mVorbisBuffer)
{
alxStop(mVorbisHandle); // not sure how alxStop would find it if i couldnt..
Con::errorf("Could not find oggMixedStreamSource ptr.");
return false;
}
return true;
}
// 6K!! memory needed to speed up theora player. Small price to pay!
static S32 sAdjCrr[256];
static S32 sAdjCrg[256];
static S32 sAdjCbg[256];
static S32 sAdjCbb[256];
static S32 sAdjY[256];
static U8 sClampBuff[1024];
static U8* sClamp = sClampBuff + 384;
// precalculate adjusted YUV values for faster RGB conversion
void TheoraTexture::generateLookupTables()
{
static bool fGenerated = false;
S32 i;
for(i = 0; i < 256; i++)
{
sAdjCrr[i] = (409 * (i - 128) + 128) >> 8;
sAdjCrg[i] = (208 * (i - 128) + 128) >> 8;
sAdjCbg[i] = (100 * (i - 128) + 128) >> 8;
sAdjCbb[i] = (516 * (i - 128) + 128) >> 8;
sAdjY[i] = (298 * (i - 16)) >> 8;
}
// and setup LUT clamp range
for(i = -384; i < 640; i++)
{
sClamp[i] = mClamp(i, 0, 0xFF);
}
}
void TheoraTexture::drawFrame()
{
yuv_buffer yuv;
// decode a frame! (into yuv)
theora_decode_YUVout(&mTheoraState, &yuv);
// get destination buffer (and 1 row offset)
GBitmap *bmp = mTextureHandle->getBitmap();
U8* dst0 = bmp->getAddress(0, 0);
U8 *dst1 = dst0 + bmp->getWidth() * bmp->bytesPerPixel;
// find picture offset
const S32 pictOffset = yuv.y_stride * mTheoraInfo.offset_y + mTheoraInfo.offset_x;
const U8 *pY0, *pY1, *pU, *pV;
for(S32 y = 0; y < yuv.y_height; y += 2)
{
// set pointers into yuv buffers (2 lines for y)
pY0 = yuv.y + pictOffset + y * (yuv.y_stride);
pY1 = yuv.y + pictOffset + (y | 1) * (yuv.y_stride);
pU = yuv.u + ((pictOffset + y * (yuv.uv_stride)) >> 1);
pV = yuv.v + ((pictOffset + y * (yuv.uv_stride)) >> 1);
for(S32 x = 0; x < yuv.y_width; x += 2)
{
// convert a 2x2 block over
// speed up G conversion a very very small amount ;)
const S32 G = sAdjCrg[*pV] + sAdjCbg[*pU];
// pixel 0x0
*dst0++ = sClamp[sAdjY[*pY0] + sAdjCrr[*pV]];
*dst0++ = sClamp[sAdjY[*pY0] - G];
*dst0++ = sClamp[sAdjY[*pY0++] + sAdjCbb[*pU]];
// pixel 1x0
*dst0++ = sClamp[sAdjY[*pY0] + sAdjCrr[*pV]];
*dst0++ = sClamp[sAdjY[*pY0] - G];
*dst0++ = sClamp[sAdjY[*pY0++] + sAdjCbb[*pU]];
// pixel 0x1
*dst1++ = sClamp[sAdjY[*pY1] + sAdjCrr[*pV]];
*dst1++ = sClamp[sAdjY[*pY1] - G];
*dst1++ = sClamp[sAdjY[*pY1++] + sAdjCbb[*pU]];
// pixel 1x1
*dst1++ = sClamp[sAdjY[*pY1] + sAdjCrr[*pV++]];
*dst1++ = sClamp[sAdjY[*pY1] - G];
*dst1++ = sClamp[sAdjY[*pY1++] + sAdjCbb[*pU++]];
}
// shift the destination pointers a row (loop incs 2 at a time)
dst0 = dst1;
dst1 += bmp->getWidth() * bmp->bytesPerPixel;
}
}
bool TheoraTexture::play()
{
if(mReady && !mPlaying)
mPlayThread = new Thread((ThreadRunFunction)playThread, (S32) this, 1);
return mPlayThread;
}
void TheoraTexture::stop()
{
mPlaying = false;
if(mPlayThread)
{
SAFE_DELETE(mPlayThread);
}
}
void TheoraTexture::playThread( void *udata )
{
TheoraTexture* pThis = (TheoraTexture *)udata;
pThis->playLoop();
}
bool TheoraTexture::playLoop()
{
bool fMoreVideo = true;
bool fMoreAudio = mHasVorbis;
// timing variables
F64 dVBuffTime = 0;
F64 dLastFrame = 0;
mPlaying = true;
mCurrentTime = 0.f;
mStartTick = Platform::getRealMilliseconds();
bool isAudioActive = false;
while(mPlaying)
{
if(fMoreAudio)
fMoreAudio = readyAudio();
if(fMoreVideo)
fMoreVideo = readyVideo(dLastFrame, dVBuffTime);
if(!fMoreVideo && !fMoreAudio)
break; // if we have no more audio to buffer, and no more video frames to display, we are done
// if we haven't started yet, start it! :)
if(!isAudioActive && mHasVorbis)
{
alxPlay(mVorbisHandle);
isAudioActive = true;
}
// if we're set for the next frame, sleep
/*S32 t = (int)((double) 1000 * (dVBuffTime - getTheoraTime()));
if(t>0)
Platform::sleep(t); */
U32 safety = 0;
while((dVBuffTime - getTheoraTime()) >= 0.f && safety < 500)
{
safety++;
Platform::sleep(2);
}
// time to draw the frame!
drawFrame();
// keep track of the last frame time
dLastFrame = getTheoraTime();
}
if(mHasVorbis)
{
alxStop(mVorbisHandle);
mMagicalTrevor.reset();
}
mPlaying = false;
mVorbisHandle = NULL;
mVorbisBuffer = NULL;
return false;
}
// ready a single frame (not a late one either)
bool TheoraTexture::readyVideo(F64 dLastFrame, F64& dVBuffTime)
{
ogg_packet sOggPacket;
while(1)
{
PROFILE_START(TheoraTexture_readyVideo);
// get a video packet
if(ogg_stream_packetout(&mOggTheoraStream, &sOggPacket) > 0)
{
theora_decode_packetin(&mTheoraState, &sOggPacket);
dVBuffTime = theora_granule_time(&mTheoraState, mTheoraState.granulepos);
// check if this frame time has not passed yet.
// If the frame is late we need to decode additional
// ones and keep looping, since theora at this stage
// needs to decode all frames
const F64 dNow = getTheoraTime();
if((dVBuffTime - dNow) >= 0.0)
{
PROFILE_END();
return true; // got a good frame, not late, ready to break out
}
else if((dNow - dLastFrame) >= 1.0)
{
PROFILE_END();
return true; // display at least one frame per second, regardless
}
// else frame is dropped (its behind), look again
}
else // get another page
{
if(!demandOggPage()) // end of file
{
PROFILE_END();
return false;
}
}
//else we got a page, try and get a frame out of it
PROFILE_END();
}
}
// buffers up as much audio as it can fit into OggMixedStream audiostream thing
bool TheoraTexture::readyAudio()
{
#define BUFFERSIZE 8192
ogg_packet sOggPacket;
ALuint bufferId = 0;
S32 ret, i, count;
float **pcm;
// i was making buffers to fit the exact size
// but the memory manager doesn't seem to be working
// with multiple threads..
S16 samples[BUFFERSIZE]; // this should be large enough
// If i don't have a buffer to put samples into..
while(1)
{
PROFILE_START(TheoraTexture_readyAudio);
bufferId = mVorbisBuffer->GetAvailableBuffer();
if(!bufferId) // buffered all that it cant fit
{
PROFILE_END();
return true;
}
// Skip if we're ahead of the video...
// if the buffer is ready now
// fill it!
while(!(ret = vorbis_synthesis_pcmout(&mVorbisDspState, &pcm)))
{
// no pending audio; is there a pending packet to decode?
if(ogg_stream_packetout(&mOggVorbisStream, &sOggPacket) > 0)
{
if(vorbis_synthesis(&mVorbisBlock, &sOggPacket) == 0) // test for success!
vorbis_synthesis_blockin(&mVorbisDspState, &mVorbisBlock);
}
else // we need more data; break out to suck in another page
{
if(!demandOggPage())
{
PROFILE_END();
return false; // end of file
}
}
}
// found samples to buffer!
for(count = i = 0; i < ret && i < (BUFFERSIZE * mVorbisInfo.channels); i++)
{
for(int j = 0; j < mVorbisInfo.channels; j++)
{
int val = (int)(pcm[j][i] * 32767.f);
if(val > 32767)
val = 32767;
if(val < -32768)
val = -32768;
#if defined(TORQUE_OS_MAC) && !defined(TORQUE_BIG_ENDIAN)
samples[count++] = ((val << 8) & 0xFF00) | ((val >> 8) & 0x00FF);
#else
samples[count++] = val;
#endif
}
}
// bump up my buffering position
vorbis_synthesis_read(&mVorbisDspState, i);
// by this point the buffer should be filled (or as close as its gonna get)
// ... Queue buffer
alBufferData( bufferId,
(mVorbisInfo.channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
samples,
count * 2,
mVorbisInfo.rate);
// Note the time for synchronization by magical trevor.
const F64 newTimeSlice = F64(ret) / F64(mVorbisInfo.rate);
mMagicalTrevor.postBuffer(bufferId, newTimeSlice);
mVorbisBuffer->QueueBuffer(bufferId);
PROFILE_END();
}
}
F64 TheoraTexture::getTheoraTime()
{
if(mHasVorbis)
{
// We have audio, so synch to audio track.
ALint buf=-1;
alGetSourcei(mVorbisBuffer->mSource, AL_BUFFER, &buf);
mCurrentTime += mMagicalTrevor.advanceTime(buf);
return mCurrentTime;
}
else
{
// We have no audio, just synch to start time.
return (double)0.001 * (double)(Platform::getRealMilliseconds() - mStartTick);
}
}
// helpers
// function does whatever it can get pages into streams
bool TheoraTexture::demandOggPage()
{
while(1)
{
if(ogg_sync_pageout(&mOggSyncState,&mOggPage) > 0)
{
// found a page, queue it to its stream
queueOggPage(&mOggPage);
return true;
}
if(bufferOggPage() == 0)
{
// Ogg buffering stopped, end of file reached
return false;
}
}
}
// grabs some more compressed bitstream and syncs it for page extraction
S32 TheoraTexture::bufferOggPage()
{
char *buffer = ogg_sync_buffer(&mOggSyncState, 4096);
// this is a bit of a hack, i guess, i dont know how else to do it
// you dont want to send extra data to ogg_sync or it will try and
// pull it out like its real data. thats no good
// grab current position
U32 bytes = mTheoraFile->getPosition();
mTheoraFile->read(4096, buffer);
// find out how much was read
bytes = mTheoraFile->getPosition() - bytes;
// give it to ogg and tell it how many bytes
ogg_sync_wrote(&mOggSyncState, bytes);
return bytes;
}
// try and put the page into the theora and vorbis streams,
// they wont accept pages that arent for them
S32 TheoraTexture::queueOggPage(ogg_page *page)
{
ogg_stream_pagein(&mOggTheoraStream, page);
if(mHasVorbis)
ogg_stream_pagein(&mOggVorbisStream, page);
return 0;
}
// returns a handle for OpenGL calls
U32 TheoraTexture::getGLName()
{
if(mTextureHandle)
{
mTextureHandle->refresh();
return mTextureHandle->getGLName();
}
return 0;
}
// copies the newest texture data to openGL video memory
void TheoraTexture::refresh()
{
if(mTextureHandle)
mTextureHandle->refresh();
}

180
engine/core/theoraPlayer.h Executable file
View File

@ -0,0 +1,180 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _THEORATEXTURE_H_
#define _THEORATEXTURE_H_
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
#ifndef _GTEXMANAGER_H_
#include "dgl/gTexManager.h"
#endif
#ifndef _GBITMAP_H_
#include "dgl/gBitmap.h"
#endif
#ifndef _O_THEORA_H_
#include "theora/theora.h"
#endif
#ifndef _vorbis_codec_h_
#include "vorbis/codec.h"
#endif
#ifndef _PLATFORMTHREAD_H_
#include "platform/platformThread.h"
#endif
#ifndef _OGGMIXEDSTREAMSOURCE_H_
#include "audio/oggMixedStreamSource.h"
#endif
#ifndef _VORBISSTREAMSOURCE_H_
#include "audio/vorbisStreamSource.h"
#endif
/// TheoraTexture decodes Ogg Theora files, and their audio.
///
/// TheoraTexture objects can be used similarly to TextureObjects. Just
/// set the video, call play(), and then refresh every frame to get the
/// latest video. Audio happens automagically.
///
/// @note Uses Theora and ogg libraries which are Copyright (C) Xiph.org Foundation
class TheoraTexture
{
private:
Thread* mPlayThread;
/// Ogg and codec state for demux/decode.
ogg_sync_state mOggSyncState;
ogg_page mOggPage;
ogg_stream_state mOggTheoraStream;
ogg_stream_state mOggVorbisStream;
theora_info mTheoraInfo;
theora_comment mTheoraComment;
theora_state mTheoraState;
vorbis_info mVorbisInfo;
vorbis_comment mVorbisComment;
vorbis_dsp_state mVorbisDspState;
vorbis_block mVorbisBlock;
/// File handle for the theora file.
Stream* mTheoraFile;
volatile bool mReady;
volatile bool mPlaying;
volatile bool mHasVorbis;
OggMixedStreamSource* mVorbisBuffer;
AUDIOHANDLE mVorbisHandle;
volatile F32 mCurrentTime;
volatile U32 mStartTick;
void init();
bool parseHeaders();
bool createVideoBuffers();
bool createAudioBuffers(Audio::Description* desc);
/// We precalculate adjusted YUV values for faster RGB conversion. This
/// function is responsible for making sure they're present and valid.
void generateLookupTables();
void destroyTexture(bool restartOgg = false);
void drawFrame();
bool readyVideo(const F64 lastFrame, F64 &vBuffTime);
bool readyAudio();
bool demandOggPage();
S32 bufferOggPage();
S32 queueOggPage(ogg_page *page);
F64 getTheoraTime();
/// Background playback thread.
static void playThread( void *udata );
bool playLoop();
/// Magical Trevor is responsible for tracking elapsed time based on
/// the currently playing buffer.
///
/// @note He's ever so clear.
///
/// Basically it takes periodic updates of the currently playing buffer
/// and figures the time between that buffer and the last tracked
/// buffer, and tells you how much time passed between those two events.
/// (time == duration of audio)
struct MagicalTrevor
{
struct BufInf
{
BufInf();
ALuint id;
F64 time;
BufInf *next;
};
FreeListChunker<BufInf> mBuffPool;
BufInf *mBufListHead;
ALint mLastBufferID;
void *mMutex;
MagicalTrevor();
~MagicalTrevor();
/// Given current buffer, infer elapsed time since last call.
const F64 advanceTime(const ALuint buffId);
/// Note a buffer, and how much time it contains.
void postBuffer(const ALuint buffId, const F64 duration);
/// How many items in our list? Used for debugging.
const U32 getListSize() const;
/// Clear everything out in preparation for the next playback.
void reset();
};
MagicalTrevor mMagicalTrevor;
public:
TheoraTexture();
TheoraTexture(const char *filename, bool play = false, Audio::Description* desc = NULL);
~TheoraTexture();
operator TextureObject*()
{
return (TextureObject*)*mTextureHandle;
}
bool setFile(const char*, bool play = false, bool restart = false, Audio::Description* desc = NULL);
const bool isPlaying() const { return mPlaying; }
const bool isReady() const { return mReady; }
bool play();
void stop();
bool pause();
/// Update the GL texture from the bitmap the background thread writes to.
void refresh();
U32 getGLName();
const F64 getCurrentTime()
{
return getTheoraTime();
}
TextureHandle* mTextureHandle;
};
#endif

291
engine/core/tokenizer.cc Executable file
View File

@ -0,0 +1,291 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/tokenizer.h"
#include "platform/platform.h"
#include "core/fileStream.h"
Tokenizer::Tokenizer()
{
dMemset(mFileName, 0, sizeof(mFileName));
mpBuffer = NULL;
mBufferSize = 0;
mCurrPos = 0;
mCurrLine = 0;
mPrevPos = 0;
mStartPos = 0;
dMemset(mCurrTokenBuffer, 0, sizeof(mCurrTokenBuffer));
mTokenIsCurrent = false;
}
Tokenizer::~Tokenizer()
{
dMemset(mFileName, 0, sizeof(mFileName));
delete [] mpBuffer;
mpBuffer = NULL;
mBufferSize = 0;
mCurrPos = 0;
mCurrLine = 0;
mPrevPos = 0;
mStartPos = 0;
dMemset(mCurrTokenBuffer, 0, sizeof(mCurrTokenBuffer));
mTokenIsCurrent = false;
}
bool Tokenizer::openFile(const char* pFileName)
{
AssertFatal(mFileName[0] == '\0', "Reuse of Tokenizers not allowed!");
FileStream* pStream = new FileStream;
if (pStream->open(pFileName, FileStream::Read) == false)
{
delete pStream;
return false;
}
dStrcpy(mFileName, pFileName);
mBufferSize = pStream->getStreamSize();
mpBuffer = new char[mBufferSize];
pStream->read(mBufferSize, mpBuffer);
pStream->close();
delete pStream;
// Not really necessary, but couldn't hurt...
mCurrPos = 0;
mCurrLine = 0;
dMemset(mCurrTokenBuffer, 0, sizeof(mCurrTokenBuffer));
mTokenIsCurrent = false;
return true;
}
bool Tokenizer::openFile(Stream* pStream)
{
mBufferSize = pStream->getStreamSize();
mpBuffer = new char[mBufferSize];
pStream->read(mBufferSize, mpBuffer);
//pStream->close();
//delete pStream;
// Not really necessary, but couldn't hurt...
mCurrPos = 0;
mCurrLine = 0;
dMemset(mCurrTokenBuffer, 0, sizeof(mCurrTokenBuffer));
mTokenIsCurrent = false;
return true;
}
bool Tokenizer::reset()
{
mCurrPos = 0;
mCurrLine = 0;
mPrevPos = 0;
mStartPos = 0;
dMemset(mCurrTokenBuffer, 0, sizeof(mCurrTokenBuffer));
mTokenIsCurrent = false;
return true;
}
bool Tokenizer::setCurrentPos(U32 pos)
{
mCurrPos = pos;
mTokenIsCurrent = false;
return advanceToken(true);
}
bool Tokenizer::advanceToken(const bool crossLine, const bool assertAvail)
{
if (mTokenIsCurrent == true)
{
AssertFatal(mCurrTokenBuffer[0] != '\0', "No token, but marked as current?");
mTokenIsCurrent = false;
return true;
}
U32 currPosition = 0;
mCurrTokenBuffer[0] = '\0';
// Store the beginning of the previous advance
// and the beginning of the current advance
mPrevPos = mStartPos;
mStartPos = mCurrPos;
while (mCurrPos < mBufferSize)
{
char c = mpBuffer[mCurrPos];
bool cont = true;
switch (c)
{
case ' ':
case '\t':
if (currPosition == 0)
{
// Token hasn't started yet...
mCurrPos++;
}
else
{
// End of token
mCurrPos++;
cont = false;
}
break;
case '\r':
case '\n':
if (crossLine == true)
{
if (currPosition == 0)
{
// Haven't started getting token, but we're crossing lines...
while (mpBuffer[mCurrPos] == '\r' || mpBuffer[mCurrPos] == '\n')
mCurrPos++;
mCurrLine++;
}
else
{
// Getting token, stop here, leave pointer at newline...
cont = false;
}
}
else
{
cont = false;
break;
}
break;
default:
if (c == '\"')
{
// Quoted token
//AssertISV(currPosition == 0,
// avar("Error, quotes MUST be at start of token. Error: (%s: %d)",
// getFileName(), getCurrentLine()));
U32 startLine = getCurrentLine();
mCurrPos++;
while (mpBuffer[mCurrPos] != '\"') {
AssertISV(mCurrPos < mBufferSize,
avar("End of file before quote closed. Quote started: (%s: %d)",
getFileName(), startLine));
AssertISV((mpBuffer[mCurrPos] != '\n' && mpBuffer[mCurrPos] != '\r'),
avar("End of line reached before end of quote. Quote started: (%s: %d)",
getFileName(), startLine));
mCurrTokenBuffer[currPosition++] = mpBuffer[mCurrPos++];
}
mCurrPos++;
cont = false;
}
else if (c == '/' && mpBuffer[mCurrPos+1] == '/')
{
// Line quote...
if (currPosition == 0)
{
// continue to end of line, then let crossLine determine on the next pass
while (mCurrPos < mBufferSize && (mpBuffer[mCurrPos] != '\n' && mpBuffer[mCurrPos] != '\r'))
mCurrPos++;
}
else
{
// This is the end of the token. Continue to EOL
while (mCurrPos < mBufferSize && (mpBuffer[mCurrPos] != '\n' && mpBuffer[mCurrPos] != '\r'))
mCurrPos++;
cont = false;
}
}
else
{
mCurrTokenBuffer[currPosition++] = c;
mCurrPos++;
}
break;
}
if (cont == false)
break;
}
mCurrTokenBuffer[currPosition] = '\0';
if (assertAvail == true)
AssertISV(currPosition != 0, avar("Error parsing: %s at or around line: %d", getFileName(), getCurrentLine()));
if (mCurrPos == mBufferSize)
return false;
return currPosition != 0;
}
bool Tokenizer::regressToken(const bool crossLine)
{
AssertFatal(mTokenIsCurrent == false && mCurrPos != 0,
"Error, token already regressed, or no token has been taken yet...");
return setCurrentPos(mPrevPos);
}
bool Tokenizer::tokenAvailable()
{
// Note: this implies that when advanceToken(false) fails, it must cap the
// token buffer.
//
return mCurrTokenBuffer[0] != '\0';
}
const char* Tokenizer::getToken() const
{
return mCurrTokenBuffer;
}
bool Tokenizer::tokenICmp(const char* pCmp) const
{
return dStricmp(mCurrTokenBuffer, pCmp) == 0;
}
bool Tokenizer::findToken(U32 start, const char* pCmp)
{
// Move to the start
setCurrentPos(start);
// Loop through the file and see if the token exists
while (advanceToken(true))
{
if (tokenICmp(pCmp))
return true;
}
return false;
}
bool Tokenizer::findToken(const char* pCmp)
{
return findToken(0, pCmp);
}
bool Tokenizer::endOfFile()
{
if (mCurrPos < mBufferSize)
return false;
else
return true;
}

66
engine/core/tokenizer.h Executable file
View File

@ -0,0 +1,66 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TOKENIZER_H_
#define _TOKENIZER_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#include "core/stream.h"
class SizedStream;
class Tokenizer
{
public:
enum {
MaxTokenSize = 1023
};
private:
char mFileName[1024];
char* mpBuffer;
U32 mBufferSize;
U32 mCurrPos;
U32 mCurrLine;
U32 mPrevPos;
U32 mStartPos;
char mCurrTokenBuffer[MaxTokenSize + 1];
bool mTokenIsCurrent;
public:
Tokenizer();
~Tokenizer();
bool openFile(const char* pFileName);
bool openFile(Stream* pStream);
bool advanceToken(const bool crossLine, const bool assertAvailable = false);
bool regressToken(const bool crossLine);
bool tokenAvailable();
const char* getToken() const;
bool tokenICmp(const char* pCmp) const;
bool findToken(const char* pCmp);
bool findToken(U32 start, const char* pCmp);
const char* getFileName() const { return mFileName; }
U32 getCurrentLine() const { return mCurrLine; }
U32 getCurrentPos() const { return mCurrPos; }
bool setCurrentPos(U32 pos);
bool reset();
bool endOfFile();
};
#endif //_TOKENIZER_H_

112
engine/core/torqueConfig.h Executable file
View File

@ -0,0 +1,112 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TORQUECONFIG_H_
#define _TORQUECONFIG_H_
//Hi, and welcome to the Torque Config file.
//
//This file is a central reference for the various configuration flags that
//you'll be using when controlling what sort of a Torque build you have. In
//general, the information here is global for your entire codebase, applying
//not only to your game proper, but also to all of your tools.
//
//This file also contains information which is used for various other needs,
//for instance, defines indicating what engine we're building, or what version
//we're at.
/// What engine are we running? The presence and value of this define are
/// used to determine what engine (TGE, T2D, etc.) and version thereof we're
/// running - useful if you're a content pack or other 3rd party code
/// snippet!
///
/// Version number is major * 1000 + minor * 100 + revision * 10.
#define TORQUE_GAME_ENGINE 1510
/// What's the name of your game? Used in a variety of places.
#define TORQUE_GAME_NAME "Torque Demo"
/// Human readable version string.
#define TORQUE_GAME_VERSION_STRING "Torque Demo 1.5.1 (TGE 1.5.1)"
/// Define me if you want to enable multithreading support.
#define TORQUE_MULTITHREAD
/// Define me if you want to enable the profiler.
/// See also the TORQUE_SHIPPING block below
//#define TORQUE_ENABLE_PROFILER
/// Define me to enable debug mode; enables a great number of additional
/// sanity checks, as well as making AssertFatal and AssertWarn do something.
/// This is usually defined by the build target.
//#define TORQUE_DEBUG
/// Define me if this is a shipping build; if defined I will instruct Torque
/// to batten down some hatches and generally be more "final game" oriented.
/// Notably this disables a liberal resource manager file searching, and
/// console help strings.
//#define TORQUE_SHIPPING
/// Define me to enable a variety of network debugging aids.
//#define TORQUE_DEBUG_NET
/// Modify me to enable metric gathering code in the renderers.
///
/// 0 does nothing; higher numbers enable higher levels of metric gathering.
//#define TORQUE_GATHER_METRICS 0
/// Define me if you want to enable debug guards in the memory manager.
///
/// Debug guards are known values placed before and after every block of
/// allocated memory. They are checked periodically by Memory::validate(),
/// and if they are modified (indicating an access to memory the app doesn't
/// "own"), an error is flagged (ie, you'll see a crash in the memory
/// manager's validate code). Using this and a debugger, you can track down
/// memory corruption issues quickly.
//#define TORQUE_DEBUG_GUARD
/// Define me if you want to enable debug guards on the FrameAllocator.
///
/// This is similar to the above memory manager guards, but applies only to the
/// fast FrameAllocator temporary pool memory allocations. The guards are only
/// checked when the FrameAllocator frees memory (when it's water mark changes).
/// This is most useful for detecting buffer overruns when using FrameTemp<> .
/// A buffer overrun in the FrameAllocator is unlikely to cause a crash, but may
/// still result in unexpected behavior, if other FrameTemp's are stomped.
///#define FRAMEALLOCATOR_DEBUG_GUARD
/// Define to disable Ogg Vorbis audio support. Libs are compiled without this by
/// default.
//#define TORQUE_NO_OGGVORBIS
// Finally, we define some dependent #defines. This enables some subsidiary
// functionality to get automatically turned on in certain configurations.
#ifdef TORQUE_DEBUG
# define TORQUE_GATHER_METRICS 0
#endif
#ifdef TORQUE_RELEASE
// If it's not DEBUG, it's a RELEASE build, put appropriate things here.
#endif
#ifdef TORQUE_SHIPPING
// TORQUE_SHIPPING flags here.
#else
// enable the profiler by default, if we're not doing a shipping build
# define TORQUE_ENABLE_PROFILER
#endif
#ifdef TORQUE_LIB
#ifndef TORQUE_NO_OGGVORBIS
#define TORQUE_NO_OGGVORBIS
#endif
#endif
// Someday, it might make sense to do some pragma magic here so we error
// on inconsistent flags.
#endif

574
engine/core/unicode.cc Executable file
View File

@ -0,0 +1,574 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "core/unicode.h"
#include "core/frameAllocator.h"
#include "platform/profiler.h"
#include <stdio.h>
//-----------------------------------------------------------------------------
/// replacement character. Standard correct value is 0xFFFD.
#define kReplacementChar 0xFFFD
/// Look up table. Shift a byte >> 1, then look up how many bytes to expect after it.
/// Contains -1's for illegal values.
U8 firstByteLUT[128] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x0F // single byte ascii
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x1F // single byte ascii
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x2F // single byte ascii
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x3F // single byte ascii
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4F // trailing utf8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x5F // trailing utf8
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x6F // first of 2
3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 0, // 0x7F // first of 3,4,5,illegal in utf-8
};
/// Look up table. Shift a 16-bit word >> 10, then look up whether it is a surrogate,
/// and which part. 0 means non-surrogate, 1 means 1st in pair, 2 means 2nd in pair.
U8 surrogateLUT[64] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x1F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x2F
0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, // 0x3F
};
/// Look up table. Feed value from firstByteLUT in, gives you
/// the mask for the data bits of that UTF-8 code unit.
U8 byteMask8LUT[] = { 0x3f, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; // last 0=6, 1=7, 2=5, 4, 3, 2, 1 bits
/// Mask for the data bits of a UTF-16 surrogate.
U16 byteMaskLow10 = 0x03ff;
//-----------------------------------------------------------------------------
inline bool isSurrogateRange(U32 codepoint)
{
return ( 0xd800 < codepoint && codepoint < 0xdfff );
}
inline bool isAboveBMP(U32 codepoint)
{
return ( codepoint > 0xFFFF );
}
//-----------------------------------------------------------------------------
const U32 convertUTF8toUTF16(const UTF8 *unistring, UTF16 *outbuffer, U32 len)
{
AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator.");
PROFILE_START(convertUTF8toUTF16);
U32 walked, nCodepoints;
UTF32 middleman;
nCodepoints=0;
while(*unistring != NULL && nCodepoints < len)
{
walked = 1;
middleman = oneUTF8toUTF32(unistring,&walked);
outbuffer[nCodepoints] = oneUTF32toUTF16(middleman);
unistring+=walked;
nCodepoints++;
}
nCodepoints = getMin(nCodepoints,len);
outbuffer[nCodepoints] = NULL;
PROFILE_END();
return nCodepoints;
}
//-----------------------------------------------------------------------------
const U32 convertUTF8toUTF32(const UTF8 *unistring, UTF32 *outbuffer, U32 len)
{
AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator.");
PROFILE_START(convertUTF8toUTF32);
U32 walked, nCodepoints;
nCodepoints=0;
while(*unistring != NULL && nCodepoints < len)
{
walked = 1;
outbuffer[nCodepoints] = oneUTF8toUTF32(unistring,&walked);
unistring+=walked;
nCodepoints++;
}
nCodepoints = getMin(nCodepoints,len);
outbuffer[nCodepoints] = NULL;
PROFILE_END();
return nCodepoints;
}
//-----------------------------------------------------------------------------
const U32 convertUTF16toUTF8( const UTF16 *unistring, UTF8 *outbuffer, U32 len)
{
AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator.");
PROFILE_START(convertUTF16toUTF8);
U32 walked, nCodeunits, codeunitLen;
UTF32 middleman;
nCodeunits=0;
while( *unistring != NULL && nCodeunits + 3 < len )
{
walked = 1;
middleman = oneUTF16toUTF32(unistring,&walked);
codeunitLen = oneUTF32toUTF8(middleman, &outbuffer[nCodeunits]);
unistring += walked;
nCodeunits += codeunitLen;
}
nCodeunits = getMin(nCodeunits,len - 1);
outbuffer[nCodeunits] = NULL;
PROFILE_END();
return nCodeunits;
}
//-----------------------------------------------------------------------------
const U32 convertUTF16toUTF32(const UTF16 *unistring, UTF32 *outbuffer, U32 len)
{
AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator.");
PROFILE_START(convertUTF16toUTF32);
U32 walked, nCodepoints;
nCodepoints=0;
while( *unistring != NULL && nCodepoints < len )
{
walked=1;
outbuffer[nCodepoints] = oneUTF16toUTF32(unistring,&walked);
unistring += walked;
nCodepoints++;
}
nCodepoints = getMin(nCodepoints,len);
outbuffer[nCodepoints] = NULL;
PROFILE_END();
return nCodepoints;
}
//-----------------------------------------------------------------------------
const U32 convertUTF32toUTF8( const UTF32 *unistring, UTF8 *outbuffer, U32 len)
{
AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator.");
PROFILE_START(convertUTF32toUTF8);
U32 nCodeunits, codeunitLen;
nCodeunits=0;
while( *unistring != NULL && nCodeunits + 3 < len )
{
codeunitLen = oneUTF32toUTF8(*unistring, &outbuffer[nCodeunits]);
unistring++;
nCodeunits += codeunitLen;
}
nCodeunits = getMin(nCodeunits,len);
outbuffer[nCodeunits] = NULL;
PROFILE_END();
return nCodeunits;
}
//-----------------------------------------------------------------------------
const U32 convertUTF32toUTF16(const UTF32 *unistring, UTF16 *outbuffer, U32 len)
{
AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator.");
PROFILE_START(convertUTF32toUTF16);
U32 walked, nCodepoints;
nCodepoints=0;
while(*unistring != NULL && nCodepoints < len)
{
outbuffer[nCodepoints] = oneUTF32toUTF16(*unistring);
unistring++;
nCodepoints++;
}
nCodepoints = getMin(nCodepoints,len);
outbuffer[nCodepoints] = NULL;
PROFILE_END();
return nCodepoints;
}
//-----------------------------------------------------------------------------
// Functions that convert buffers of unicode code points
//-----------------------------------------------------------------------------
UTF16* convertUTF8toUTF16( const UTF8* unistring)
{
PROFILE_START(convertUTF8toUTF16_create);
// allocate plenty of memory.
U32 nCodepoints, len = dStrlen(unistring) + 1;
FrameTemp<UTF16> buf(len);
// perform conversion
nCodepoints = convertUTF8toUTF16( unistring, buf, len);
// add 1 for the NULL terminator the converter promises it included.
nCodepoints++;
// allocate the return buffer, copy over, and return it.
UTF16 *ret = new UTF16[nCodepoints];
dMemcpy(ret, buf, nCodepoints * sizeof(UTF16));
PROFILE_END();
return ret;
}
//-----------------------------------------------------------------------------
UTF32* convertUTF8toUTF32( const UTF8* unistring)
{
PROFILE_START(convertUTF8toUTF32_create);
// allocate plenty of memory.
U32 nCodepoints, len = dStrlen(unistring) + 1;
FrameTemp<UTF32> buf(len);
// perform conversion
nCodepoints = convertUTF8toUTF32( unistring, buf, len);
// add 1 for the NULL terminator the converter promises it included.
nCodepoints++;
// allocate the return buffer, copy over, and return it.
UTF32 *ret = new UTF32[nCodepoints];
dMemcpy(ret, buf, nCodepoints * sizeof(UTF32));
PROFILE_END();
return ret;
}
//-----------------------------------------------------------------------------
UTF8* convertUTF16toUTF8( const UTF16* unistring)
{
PROFILE_START(convertUTF16toUTF8_create);
// allocate plenty of memory.
U32 nCodeunits, len = dStrlen(unistring) * 3 + 1;
FrameTemp<UTF8> buf(len);
// perform conversion
nCodeunits = convertUTF16toUTF8( unistring, buf, len);
// add 1 for the NULL terminator the converter promises it included.
nCodeunits++;
// allocate the return buffer, copy over, and return it.
UTF8 *ret = new UTF8[nCodeunits];
dMemcpy(ret, buf, nCodeunits * sizeof(UTF8));
PROFILE_END();
return ret;
}
//-----------------------------------------------------------------------------
UTF32* convertUTF16toUTF32(const UTF16* unistring)
{
PROFILE_START(convertUTF16toUTF32_create);
// allocate plenty of memory.
U32 nCodepoints, len = dStrlen(unistring) + 1;
FrameTemp<UTF32> buf(len);
// perform conversion
nCodepoints = convertUTF16toUTF32( unistring, buf, len);
// add 1 for the NULL terminator the converter promises it included.
nCodepoints++;
// allocate the return buffer, copy over, and return it.
UTF32 *ret = new UTF32[nCodepoints];
dMemcpy(ret, buf, nCodepoints * sizeof(UTF32));
PROFILE_END();
return ret;
}
//-----------------------------------------------------------------------------
UTF8* convertUTF32toUTF8( const UTF32* unistring)
{
PROFILE_START(convertUTF32toUTF8_create);
// allocate plenty of memory.
U32 nCodeunits, len = dStrlen(unistring) * 3 + 1;
FrameTemp<UTF8> buf(len);
// perform conversion
nCodeunits = convertUTF32toUTF8( unistring, buf, len);
// add 1 for the NULL terminator the converter promises it included.
nCodeunits++;
// allocate the return buffer, copy over, and return it.
UTF8 *ret = new UTF8[nCodeunits];
dMemcpy(ret, buf, nCodeunits * sizeof(UTF8));
PROFILE_END();
return ret;
}
//-----------------------------------------------------------------------------
UTF16* convertUTF32toUTF16(const UTF32* unistring)
{
PROFILE_START(convertUTF32toUTF16_create);
// allocate plenty of memory.
U32 nCodepoints, len = dStrlen(unistring) + 1;
FrameTemp<UTF16> buf(len);
// perform conversion
nCodepoints = convertUTF32toUTF16( unistring, buf, len);
// add 1 for the NULL terminator the converter promises it included.
nCodepoints++;
// allocate the return buffer, copy over, and return it.
UTF16 *ret = new UTF16[nCodepoints];
dMemcpy(ret, buf, nCodepoints * sizeof(UTF16));
PROFILE_END();
return ret;
}
//-----------------------------------------------------------------------------
// Functions that converts one unicode codepoint at a time
//-----------------------------------------------------------------------------
const UTF32 oneUTF8toUTF32( const UTF8* codepoint, U32 *unitsWalked)
{
PROFILE_START(oneUTF8toUTF32);
// codepoints 6 codeunits long are read, but do not convert correctly,
// and are filtered out anyway.
// early out for ascii
if(!(*codepoint & 0x0080))
{
*unitsWalked = 1;
PROFILE_END();
return (UTF32)*codepoint;
}
U32 expectedByteCount;
UTF32 ret = 0;
U8 codeunit;
// check the first byte ( a.k.a. codeunit ) .
unsigned char c = codepoint[0];
c = c >> 1;
expectedByteCount = firstByteLUT[c];
if(expectedByteCount > 0) // 0 or negative is illegal to start with
{
// process 1st codeunit
ret |= byteMask8LUT[expectedByteCount] & codepoint[0]; // bug?
// process trailing codeunits
for(U32 i=1;i<expectedByteCount; i++)
{
codeunit = codepoint[i];
if( firstByteLUT[codeunit>>1] == 0 )
{
ret <<= 6; // shift up 6
ret |= (codeunit & 0x3f); // mask in the low 6 bits of this codeunit byte.
}
else
{
// found a bad codepoint - did not get a medial where we wanted one.
// Dump the replacement, and claim to have parsed only 1 char,
// so that we'll dump a slew of replacements, instead of eating the next char.
ret = kReplacementChar;
expectedByteCount = 1;
break;
}
}
}
else
{
// found a bad codepoint - got a medial or an illegal codeunit.
// Dump the replacement, and claim to have parsed only 1 char,
// so that we'll dump a slew of replacements, instead of eating the next char.
ret = kReplacementChar;
expectedByteCount = 1;
}
if(unitsWalked != NULL)
*unitsWalked = expectedByteCount;
// codepoints in the surrogate range are illegal, and should be replaced.
if(isSurrogateRange(ret))
ret = kReplacementChar;
// codepoints outside the Basic Multilingual Plane add complexity to our UTF16 string classes,
// we've read them correctly so they wont foul the byte stream,
// but we kill them here to make sure they wont foul anything else
if(isAboveBMP(ret))
ret = kReplacementChar;
PROFILE_END();
return ret;
}
//-----------------------------------------------------------------------------
const UTF32 oneUTF16toUTF32(const UTF16* codepoint, U32 *unitsWalked)
{
PROFILE_START(oneUTF16toUTF32);
U8 expectedType;
U32 unitCount;
UTF32 ret = 0;
UTF16 codeunit1,codeunit2;
codeunit1 = codepoint[0];
expectedType = surrogateLUT[codeunit1 >> 10];
switch(expectedType)
{
case 0: // simple
ret = codeunit1;
unitCount = 1;
break;
case 1: // 2 surrogates
codeunit2 = codepoint[1];
if( surrogateLUT[codeunit2 >> 10] == 2)
{
ret = ((codeunit1 & byteMaskLow10 ) << 10) | (codeunit2 & byteMaskLow10);
unitCount = 2;
break;
}
// else, did not find a trailing surrogate where we expected one,
// so fall through to the error
case 2: // error
// found a trailing surrogate where we expected a codepoint or leading surrogate.
// Dump the replacement.
ret = kReplacementChar;
unitCount = 1;
break;
}
if(unitsWalked != NULL)
*unitsWalked = unitCount;
// codepoints in the surrogate range are illegal, and should be replaced.
if(isSurrogateRange(ret))
ret = kReplacementChar;
// codepoints outside the Basic Multilingual Plane add complexity to our UTF16 string classes,
// we've read them correctly so they wont foul the byte stream,
// but we kill them here to make sure they wont foul anything else
// NOTE: these are perfectly legal codepoints, we just dont want to deal with them.
if(isAboveBMP(ret))
ret = kReplacementChar;
PROFILE_END();
return ret;
}
//-----------------------------------------------------------------------------
const UTF16 oneUTF32toUTF16(const UTF32 codepoint)
{
// found a codepoint outside the codeable UTF-16 range!
// or, found an illegal codepoint!
if(codepoint >= 0x10FFFF || isSurrogateRange(codepoint))
return kReplacementChar;
// these are legal, we just dont want to deal with them.
if(isAboveBMP(codepoint))
return kReplacementChar;
return (UTF16)codepoint;
}
//-----------------------------------------------------------------------------
const U32 oneUTF32toUTF8(const UTF32 codepoint, UTF8 *threeByteCodeunitBuf)
{
PROFILE_START(oneUTF32toUTF8);
U32 bytecount = 0;
UTF8 *buf;
U32 working = codepoint;
buf = threeByteCodeunitBuf;
//-----------------
if(isSurrogateRange(working)) // found an illegal codepoint!
working = kReplacementChar;
//return oneUTF32toUTF8(kReplacementChar, threeByteCodeunitBuf);
if(isAboveBMP(working)) // these are legal, we just dont want to deal with them.
working = kReplacementChar;
//return oneUTF32toUTF8(kReplacementChar, threeByteCodeunitBuf);
//-----------------
if( working < (1 << 7)) // codeable in 7 bits
bytecount = 1;
else if( working < (1 << 11)) // codeable in 11 bits
bytecount = 2;
else if( working < (1 << 16)) // codeable in 16 bits
bytecount = 3;
AssertISV( bytecount > 0, "Error converting to UTF-8 in oneUTF32toUTF8(). isAboveBMP() should have caught this!");
//-----------------
U8 mask = byteMask8LUT[0]; // 0011 1111
U8 marker = ( ~mask << 1); // 1000 0000
// Process the low order bytes, shifting the codepoint down 6 each pass.
for( int i = bytecount-1; i > 0; i--)
{
threeByteCodeunitBuf[i] = marker | (working & mask);
working >>= 6;
}
// Process the 1st byte. filter based on the # of expected bytes.
mask = byteMask8LUT[bytecount];
marker = ( ~mask << 1 );
threeByteCodeunitBuf[0] = marker | working & mask;
PROFILE_END();
return bytecount;
}
//-----------------------------------------------------------------------------
const U32 dStrlen(const UTF16 *unistring)
{
U32 i = 0;
while(unistring[i] != NULL)
i++;
return i;
}
//-----------------------------------------------------------------------------
const U32 dStrlen(const UTF32 *unistring)
{
U32 i = 0;
while(unistring[i] != NULL)
i++;
return i;
}
//-----------------------------------------------------------------------------
const UTF8* getNthCodepoint(const UTF8 *unistring, const U32 n)
{
const UTF8* ret = unistring;
U32 charsseen = 0;
while( *ret && charsseen < n)
{
ret++;
if((*ret & 0xC0) != 0x80)
charsseen++;
}
return ret;
}
/* alternate utf-8 decode impl for speed, no error checking,
left here for your amusement:
U32 codeunit = codepoint + expectedByteCount - 1;
U32 i = 0;
switch(expectedByteCount)
{
case 6: ret |= ( *(codeunit--) & 0x3f ); i++;
case 5: ret |= ( *(codeunit--) & 0x3f ) << (6 * i++);
case 4: ret |= ( *(codeunit--) & 0x3f ) << (6 * i++);
case 3: ret |= ( *(codeunit--) & 0x3f ) << (6 * i++);
case 2: ret |= ( *(codeunit--) & 0x3f ) << (6 * i++);
case 1: ret |= *(codeunit) & byteMask8LUT[expectedByteCount] << (6 * i);
}
*/

103
engine/core/unicode.h Executable file
View File

@ -0,0 +1,103 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _UNICODE_H_
#define _UNICODE_H_
#include "platform/types.h"
/// Unicode conversion utility functions
///
/// Some definitions first:
/// - <b>Code Point</b>: a single character of Unicode text. Used to disabmiguate from C char type.
/// - <b>UTF-32</b>: a Unicode encoding format where one code point is always 32 bits wide.
/// This format can in theory contain any Unicode code point that will ever be needed, now or in the future. 4billion+ code points should be enough, right?
/// - <b>UTF-16</b>: a variable length Unicode encoding format where one code point can be
/// either one or two 16-bit code units long.
/// - <b>UTF-8</b>: a variable length Unicode endocing format where one code point can be
/// up to four 8-bit code units long. The first bit of a single byte UTF-8 code point is 0.
/// The first few bits of a multi-byte code point determine the length of the code point.
/// @see http://en.wikipedia.org/wiki/UTF-8
/// - <b>Surrogate Pair</b>: a pair of special UTF-16 code units, that encode a code point
/// that is too large to fit into 16 bits. The surrogate values sit in a special reserved range of Unicode.
/// - <b>Code Unit</b>: a single unit of a variable length Unicode encoded code point.
/// UTF-8 has 8 bit wide code units. UTF-16 has 16 bit wide code units.
/// - <b>BMP</b>: "Basic Multilingual Plane". Unicode values U+0000 - U+FFFF. This range
/// of Unicode contains all the characters for all the languages of the world, that one would
/// usually be interested in. All code points in the BMP are 16 bits wide or less.
/// The current implementation of these conversion functions deals only with the BMP.
/// Any code points above 0xFFFF, the top of the BMP, are replaced with the
/// standard unicode replacement character: 0xFFFD.
/// Any UTF16 surrogates are read correctly, but replaced.
/// UTF-8 code points up to 6 code units wide will be read, but 5+ is illegal,
/// and 4+ is above the BMP, and will be replaced.
/// This means that UTF-8 output is clamped to 3 code units ( bytes ) per code point.
//-----------------------------------------------------------------------------
/// Functions that convert buffers of unicode code points, allocating a buffer.
/// - These functions allocate their own return buffers. You are responsible for
/// calling delete[] on these buffers.
/// - Because they allocate memory, do not use these functions in a tight loop.
/// - These are usefull when you need a new long term copy of a string.
UTF16* convertUTF8toUTF16( const UTF8 *unistring);
UTF32* convertUTF8toUTF32( const UTF8 *unistring);
UTF8* convertUTF16toUTF8( const UTF16 *unistring);
UTF32* convertUTF16toUTF32(const UTF16 *unistring);
UTF8* convertUTF32toUTF8( const UTF32 *unistring);
UTF16* convertUTF32toUTF16(const UTF32 *unistring);
//-----------------------------------------------------------------------------
/// Functions that convert buffers of unicode code points, into a provided buffer.
/// - These functions are useful for working on existing buffers.
/// - These cannot convert a buffer in place. If unistring is the same memory as
/// outbuffer, the behavior is undefined.
/// - The converter clamps output to the BMP (Basic Multilingual Plane) .
/// - Conversion to UTF-8 requires a buffer of 3 bytes (U8's) per character, + 1.
/// - Conversion to UTF-16 requires a buffer of 1 U16 (2 bytes) per character, + 1.
/// - Conversion to UTF-32 requires a buffer of 1 U32 (4 bytes) per character, + 1.
/// - UTF-8 only requires 3 bytes per character in the worst case.
/// - Output is null terminated. Be sure to provide 1 extra byte, U16 or U32 for
/// the null terminator, or you will see truncated output.
/// - If the provided buffer is too small, the output will be truncated.
const U32 convertUTF8toUTF16(const UTF8 *unistring, UTF16 *outbuffer, U32 len);
const U32 convertUTF8toUTF32(const UTF8 *unistring, UTF32 *outbuffer, U32 len);
const U32 convertUTF16toUTF8( const UTF16 *unistring, UTF8 *outbuffer, U32 len);
const U32 convertUTF16toUTF32(const UTF16 *unistring, UTF32 *outbuffer, U32 len);
const U32 convertUTF32toUTF8( const UTF32 *unistring, UTF8 *outbuffer, U32 len);
const U32 convertUTF32toUTF16(const UTF32 *unistring, UTF16 *outbuffer, U32 len);
//-----------------------------------------------------------------------------
/// Functions that converts one unicode codepoint at a time
/// - Since these functions are designed to be used in tight loops, they do not
/// allocate buffers.
/// - oneUTF8toUTF32() and oneUTF16toUTF32() return the converted Unicode code point
/// in *codepoint, and set *unitsWalked to the \# of code units *codepoint took up.
/// The next Unicode code point should start at *(codepoint + *unitsWalked).
/// - oneUTF32toUTF8() requires a 3 byte buffer, and returns the \# of bytes used.
const UTF32 oneUTF8toUTF32( const UTF8 *codepoint, U32 *unitsWalked = NULL);
const UTF32 oneUTF16toUTF32(const UTF16 *codepoint, U32 *unitsWalked = NULL);
const UTF16 oneUTF32toUTF16(const UTF32 codepoint);
const U32 oneUTF32toUTF8( const UTF32 codepoint, UTF8 *threeByteCodeunitBuf);
//-----------------------------------------------------------------------------
/// Functions that calculate the length of unicode strings.
/// - Since calculating the length of a UTF8 string is nearly as expensive as
/// converting it to another format, a dStrlen for UTF8 is not provided here.
/// - If *unistring does not point to a null terminated string of the correct type,
/// the behavior is undefined.
const U32 dStrlen(const UTF16 *unistring);
const U32 dStrlen(const UTF32 *unistring);
//-----------------------------------------------------------------------------
/// Functions that scan for characters in a utf8 string.
/// - this is useful for getting a character-wise offset into a UTF8 string,
/// as opposed to a byte-wise offset into a UTF8 string: foo[i]
const UTF8* getNthCodepoint(const UTF8 *unistring, const U32 n);
#endif // _UNICODE_H_

242
engine/core/zipAggregate.cc Executable file
View File

@ -0,0 +1,242 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/stringTable.h"
#include "core/fileStream.h" // Streams
#include "core/zipAggregate.h" // Own header, and private includes
#include "core/zipHeaders.h"
ZipAggregate::ZipAggregate()
: m_pZipFileName(NULL)
{
VECTOR_SET_ASSOCIATION(m_fileList);
}
ZipAggregate::~ZipAggregate()
{
closeAggregate();
}
bool
ZipAggregate::refreshAggregate()
{
AssertFatal(m_pZipFileName != NULL, "No filename? Must not be open. Disallowed");
char tmpBuff[512];
dStrcpy(tmpBuff, m_pZipFileName);
return openAggregate(tmpBuff);
}
bool
ZipAggregate::openAggregate(const char* in_pFileName)
{
closeAggregate();
AssertFatal(in_pFileName != NULL, "No filename to open!");
m_pZipFileName = new char[dStrlen(in_pFileName) + 1];
dStrcpy(m_pZipFileName, in_pFileName);
FileStream* pStream = new FileStream;
if (pStream->open(m_pZipFileName, FileStream::Read) == false ||
createZipDirectory(pStream) == false) {
// Failure, abort the open...
//
delete pStream;
delete [] m_pZipFileName;
m_pZipFileName = NULL;
return false;
}
// Finished! Open for business
delete pStream;
return true;
}
void
ZipAggregate::closeAggregate()
{
destroyZipDirectory();
delete [] m_pZipFileName;
m_pZipFileName = NULL;
}
void
ZipAggregate::destroyZipDirectory()
{
m_fileList.clear();
}
bool
ZipAggregate::createZipDirectory(Stream* io_pStream)
{
AssertFatal(io_pStream != NULL, "Error, stream not open.");
U32 streamSize = io_pStream->getStreamSize();
U32 initialPosition = io_pStream->getPosition();
// We assume that the CD is 22 bytes from the end. This will be invalid
// in the case that the zip file has comments. Perhaps test the quick
// way, then degrade to seaching the final 64k+22b (!) of the stream?
//
bool posSuccess = io_pStream->setPosition(streamSize - sizeof(ZipEOCDRecord::EOCDRecord));
if (posSuccess == false) {
AssertWarn(false, "Unable to position stream to start of EOCDRecord");
return false;
}
ZipEOCDRecord* pEOCDRecord = new ZipEOCDRecord;
if (pEOCDRecord->readFromStream(*io_pStream) == false) {
// This is where we would try to degrade to general case...
//
AssertWarn(false, "Unable to locate central directory. "
"Zip File might have comments");
delete pEOCDRecord;
return false;
}
// Check the consistency of the zipFile.
//
if ((pEOCDRecord->m_record.diskNumber != pEOCDRecord->m_record.eocdDiskNumber) ||
(pEOCDRecord->m_record.numCDEntriesDisk != pEOCDRecord->m_record.numCDEntriesTotal)) {
AssertWarn(false, "Zipfile appears to be part of a "
"multi-zip disk span set, unsupported");
delete pEOCDRecord;
return false;
}
// If we're here, we're good! Scan to the start of the CDirectory, and
// start scanning the entries into our directory structure...
//
U32 startCDPosition = pEOCDRecord->m_record.cdOffset;
U32 endCDPosition = pEOCDRecord->m_record.cdOffset +
pEOCDRecord->m_record.cdSize;
posSuccess = io_pStream->setPosition(startCDPosition);
if (posSuccess == false) {
AssertWarn(false, "Unable to position to CD entries.");
delete pEOCDRecord;
return false;
}
bool dirReadSuccess = true;
for (U16 i = 0; i < pEOCDRecord->m_record.numCDEntriesTotal; i++) {
ZipDirFileHeader zdfHeader;
bool hrSuccess = zdfHeader.readFromStream(*io_pStream);
if (hrSuccess == false) {
AssertWarn(false, "Error reading a CD Entry in zip aggregate");
dirReadSuccess = false;
break;
}
enterZipDirRecord(zdfHeader);
}
delete pEOCDRecord;
if (dirReadSuccess == true) {
// Every thing went well, we're done, position the stream to the end of the
// CD...
//
io_pStream->setPosition(endCDPosition);
return true;
} else {
// Oh, crap.
io_pStream->setPosition(initialPosition);
destroyZipDirectory();
return false;
}
}
void
ZipAggregate::enterZipDirRecord(const ZipDirFileHeader& in_rHeader)
{
// First figure out whether we are looking at a directory
// or a file. Directories have a trailing / in the file name
// and a filelength of 0
if (in_rHeader.m_pFileName[dStrlen(in_rHeader.m_pFileName) - 1] == '/' &&
(in_rHeader.m_header.compressedSize == 0 &&
in_rHeader.m_header.uncompressedSize == 0))
return;
// We have a file if we are here, so enter it
// into the directory
m_fileList.increment();
FileEntry& rEntry = m_fileList.last();
// Copy the path to a file within a zip to tempString
char tempString[1024];
dStrcpy(tempString, in_rHeader.m_pFileName);
// Iterate through the string and change any
// characters with \\ to /
char* scan = tempString;
while (*scan != '\0')
{
if (*scan == '\\')
*scan = '/';
scan++;
}
// Allocate memory for zipPath and then copy the
// full path of the zip, so thats
// <location>/<name>.zip
char* zipPath = new char[dStrlen(m_pZipFileName) + dStrlen(tempString) + 2];
dStrcpy(zipPath, m_pZipFileName);
// Get all the text from the . until the end of
// the string
char* dot = dStrrchr(zipPath, '.') - 2;
// Kill the extension so the zipPath string
// becomes <location>/<name>
dot[2] = '\0';
// Create the FULL path to the contents within a zip now
// So zipPath becomes
// <harddrive path>/<zip name>/<zip path>/<zip files>.*
dStrcat(zipPath, "/");
dStrcat(zipPath, tempString);
// Create file base name
char* pPathEnd = dStrrchr(zipPath, '/');
if(pPathEnd != NULL)
{
// Put our path and file into the string table and store
// them for the ResourceManager
pPathEnd[0] = '\0';
rEntry.pPath = StringTable->insert(zipPath);
rEntry.pFileName = StringTable->insert(pPathEnd + 1);
}
// Tell ResourceManger the appropriate file attributes
rEntry.fileSize = in_rHeader.m_header.uncompressedSize;
rEntry.compressedFileSize = in_rHeader.m_header.compressedSize;
rEntry.fileOffset = in_rHeader.m_header.relativeOffsetOfLocalHeader;
// Tell ResourceManager the appropriate file compressions used on the file
if(in_rHeader.m_header.compressionMethod == ZipDirFileHeader::Deflated)
rEntry.flags = FileEntry::Compressed;
else if(in_rHeader.m_header.compressionMethod == ZipDirFileHeader::Stored)
{
rEntry.flags = FileEntry::Uncompressed;
}
else
{
// We can't have anything other than Stored or Deflated zips
AssertWarn(0, avar("Warning, non-stored or deflated resource in %s", m_pZipFileName));
m_fileList.decrement();
}
// important!! or we have memory leaks :)
delete [] zipPath;
}

67
engine/core/zipAggregate.h Executable file
View File

@ -0,0 +1,67 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _ZIPAGGREGATE_H_
#define _ZIPAGGREGATE_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
class Stream;
class ZipDirFileHeader;
class ZipAggregate
{
public:
struct FileEntry {
enum {
Uncompressed = 0,
Compressed = 1 << 0
};
const char* pPath;
const char* pFileName;
U32 fileOffset;
U32 fileSize;
U32 compressedFileSize;
U32 flags;
};
//-------------------------------------- Instance scope members and decls.
private:
char* m_pZipFileName;
Vector<FileEntry> m_fileList;
void enterZipDirRecord(const ZipDirFileHeader& in_rHeader);
bool createZipDirectory(Stream*);
void destroyZipDirectory();
ZipAggregate(const ZipAggregate&); // disallowed
public:
ZipAggregate();
~ZipAggregate();
// Opening/Manipulation interface...
public:
bool openAggregate(const char* in_pFileName);
void closeAggregate();
bool refreshAggregate();
// Entry iteration interface...
public:
typedef Vector<FileEntry>::const_iterator iterator;
U32 numEntries() const { return m_fileList.size(); }
const FileEntry& operator[](const U32 idx) const { return m_fileList[idx]; }
iterator begin() const { return m_fileList.begin(); }
iterator end() const { return m_fileList.end(); }
};
#endif //_ZIPAGGREGATE_H_

188
engine/core/zipHeaders.cc Executable file
View File

@ -0,0 +1,188 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/stream.h"
#include "core/zipHeaders.h"
const U32 ZipLocalFileHeader::csm_localFileHeaderSig = 0x04034b50;
const U32 ZipDirFileHeader::csm_dirFileHeaderSig = 0x02014b50;
const U32 ZipEOCDRecord::csm_eocdRecordSig = 0x06054b50;
bool
ZipLocalFileHeader::readFromStream(Stream& io_rStream)
{
AssertFatal(io_rStream.getStatus() == Stream::Ok,
"Error, stream is closed or has an uncleared error.");
AssertFatal(io_rStream.hasCapability(Stream::StreamPosition),
"Must be positionable stream to read zip headers...");
// Read the initial header fields, marking the initial position...
//
U32 initialPosition = io_rStream.getPosition();
// this is NOT endian-safe code!!!
//bool success = io_rStream.read(sizeof(m_header), &m_header);
// this IS.
bool success = io_rStream.read(&m_header.headerSig);
if (success == false || m_header.headerSig != csm_localFileHeaderSig) {
AssertWarn(0, "Unable to retrieve local file header from stream position...");
io_rStream.setPosition(initialPosition);
return false;
}
// NOW, read in the rest of the fields. ENDIAN-SAFE.
// not checking errors to start with...
success &= io_rStream.read(&m_header.versionToDecompress);
success &= io_rStream.read(&m_header.bitFlags);
success &= io_rStream.read(&m_header.compressionMethod);
success &= io_rStream.read(&m_header.lastModTime);
success &= io_rStream.read(&m_header.lastModDate);
success &= io_rStream.read(&m_header.crc32);
success &= io_rStream.read(&m_header.compressedSize);
success &= io_rStream.read(&m_header.uncompressedSize);
success &= io_rStream.read(&m_header.fileNameLength);
success &= io_rStream.read(&m_header.extraFieldLength);
if (success == false)
{
AssertWarn(0, "Failed retrieving local file header fields from stream...");
io_rStream.setPosition(initialPosition);
return false;
}
// Read the variable length file name from the stream...
//
AssertFatal(m_header.fileNameLength < (MaxFileNameLength - 1),
"Filename too long, increase structure size");
success = io_rStream.read(m_header.fileNameLength, m_pFileName);
m_pFileName[m_header.fileNameLength] = '\0';
if (success == false) {
AssertWarn(0, "Unable to read file name from stream position...");
io_rStream.setPosition(initialPosition);
return false;
}
// And seek to the end of the header, ignoring the extra field.
io_rStream.setPosition(initialPosition +
(sizeof(m_header) +
m_header.fileNameLength +
m_header.extraFieldLength));
return true;
}
bool
ZipDirFileHeader::readFromStream(Stream& io_rStream)
{
AssertFatal(io_rStream.getStatus() == Stream::Ok,
"Error, stream is closed or has an uncleared error.");
AssertFatal(io_rStream.hasCapability(Stream::StreamPosition),
"Must be positionable stream to read zip headers...");
// Read the initial header fields, marking the initial position...
//
U32 initialPosition = io_rStream.getPosition();
// this is NOT endian-safe code!!!
//bool success = io_rStream.read(sizeof(m_header), &m_header);
// this IS.
bool success = io_rStream.read(&m_header.headerSig);
if (success == false || m_header.headerSig != csm_dirFileHeaderSig) {
AssertWarn(0, "Unable to retrieve dir file header from stream position...");
io_rStream.setPosition(initialPosition);
return false;
}
// NOW, read in the rest of the fields. ENDIAN-SAFE.
// not checking errors to start with...
success &= io_rStream.read(&m_header.versionMadeBy);
success &= io_rStream.read(&m_header.versionToDecompress);
success &= io_rStream.read(&m_header.bitFlags);
success &= io_rStream.read(&m_header.compressionMethod);
success &= io_rStream.read(&m_header.lastModTime);
success &= io_rStream.read(&m_header.lastModDate);
success &= io_rStream.read(&m_header.crc32);
success &= io_rStream.read(&m_header.compressedSize);
success &= io_rStream.read(&m_header.uncompressedSize);
success &= io_rStream.read(&m_header.fileNameLength);
success &= io_rStream.read(&m_header.extraFieldLength);
success &= io_rStream.read(&m_header.fileCommentLength);
success &= io_rStream.read(&m_header.diskNumberStart);
success &= io_rStream.read(&m_header.internalFileAttributes);
success &= io_rStream.read(&m_header.externalFileAttributes);
success &= io_rStream.read(&m_header.relativeOffsetOfLocalHeader);
if (success == false)
{
AssertWarn(0, "Failed retrieving dir file header fields from stream...");
io_rStream.setPosition(initialPosition);
return false;
}
// Read the variable length file name from the stream...
//
AssertFatal(m_header.fileNameLength < (MaxFileNameLength - 1),
"Filename too long, increase structure size");
success = io_rStream.read(m_header.fileNameLength, m_pFileName);
m_pFileName[m_header.fileNameLength] = '\0';
if (success == false) {
AssertWarn(0, "Unable to read dir file name from stream position...");
io_rStream.setPosition(initialPosition);
return false;
}
// And seek to the end of the header, ignoring the extra field.
io_rStream.setPosition(initialPosition +
(sizeof(m_header) +
m_header.fileNameLength +
m_header.extraFieldLength));
return true;
}
bool
ZipEOCDRecord::readFromStream(Stream& io_rStream)
{
AssertFatal(io_rStream.getStatus() == Stream::Ok,
"Error, stream is closed or has an uncleared error.");
AssertFatal(io_rStream.hasCapability(Stream::StreamPosition),
"Must be positionable stream to read zip headers...");
// Read the initial header fields, marking the initial position...
//
U32 initialPosition = io_rStream.getPosition();
// this is NOT endian-safe code!!!
//bool success = io_rStream.read(sizeof(m_record), &m_record);
// this IS.
bool success = io_rStream.read(&m_record.eocdSig);
if (success == false || m_record.eocdSig != csm_eocdRecordSig)
{
AssertWarn(0, "Unable to retrieve EOCD header from stream position...");
io_rStream.setPosition(initialPosition);
return false;
}
// NOW, read in the rest of the fields. ENDIAN-SAFE.
// not checking errors to start with...
success &= io_rStream.read(&m_record.diskNumber);
success &= io_rStream.read(&m_record.eocdDiskNumber);
success &= io_rStream.read(&m_record.numCDEntriesDisk);
success &= io_rStream.read(&m_record.numCDEntriesTotal);
success &= io_rStream.read(&m_record.cdSize);
success &= io_rStream.read(&m_record.cdOffset);
success &= io_rStream.read(&m_record.zipFileCommentLength);
if (success == false)
{
AssertWarn(0, "Failed retrieving EOCD header fields from stream...");
io_rStream.setPosition(initialPosition);
return false;
}
// And seek to the end of the header, ignoring the extra field.
io_rStream.setPosition(initialPosition +
(sizeof(m_record) + m_record.zipFileCommentLength));
return true;
}

200
engine/core/zipHeaders.h Executable file
View File

@ -0,0 +1,200 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _ZIPHEADERS_H_
#define _ZIPHEADERS_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
//-------------------------------------- NB: Structures in this header are BYTE
// aligned!
#ifdef TORQUE_COMPILER_VISUALC
# pragma pack(push,1)
#else
#ifdef TORQUE_COMPILER_CODEWARRIOR
# pragma options align=packed
#endif
#endif
class Stream;
//-------------------------------------- Structure designed to fit exactly 256 bytes.
class ZipLocalFileHeader
{
// NB: Extra field in the header is ignored, but the stream read seeks
// past it...
//
private:
static const U32 csm_localFileHeaderSig;
public:
enum {
MaxFileNameLength = 211
};
enum CompressionMethod {
Stored = 0,
Shrunk = 1,
ReducedL1 = 2,
ReducedL2 = 3,
ReducedL3 = 4,
ReducedL4 = 5,
Imploded = 6,
ReservedTokenized = 7,
Deflated = 8,
EnhDefalted = 9,
DateCompression = 10
};
struct LocalFileHeader {
U32 headerSig;
U16 versionToDecompress;
U16 bitFlags;
U16 compressionMethod;
U16 lastModTime;
U16 lastModDate;
U32 crc32;
U32 compressedSize;
U32 uncompressedSize;
U16 fileNameLength;
U16 extraFieldLength;
#ifndef TORQUE_COMPILER_GCC
};
#else
} __attribute__ ((packed));
#endif
LocalFileHeader m_header; // Fixed size header
char m_pFileName[226]; // Variable size: FileName. Note that the
// number of chars here is more than the
// max allowed filename for alignment
// purposes
// Stream read routines
public:
bool readFromStream(Stream& io_rStream);
#ifndef TORQUE_COMPILER_GCC
};
#else
} __attribute__ ((packed));
#endif
//-------------------------------------- Also designed to fit into 256 bytes, note
// that we ignore the extra and file comment
// fields.
class ZipDirFileHeader
{
private:
static const U32 csm_dirFileHeaderSig;
public:
enum {
MaxFileNameLength = 211
};
enum CompressionMethod {
Stored = 0,
Shrunk = 1,
ReducedL1 = 2,
ReducedL2 = 3,
ReducedL3 = 4,
ReducedL4 = 5,
Imploded = 6,
ReservedTokenized = 7,
Deflated = 8,
EnhDefalted = 9,
DateCompression = 10
};
struct DirFileHeader {
U32 headerSig;
U16 versionMadeBy;
U16 versionToDecompress;
U16 bitFlags;
U16 compressionMethod;
U16 lastModTime;
U16 lastModDate;
U32 crc32;
U32 compressedSize;
U32 uncompressedSize;
U16 fileNameLength;
U16 extraFieldLength;
U16 fileCommentLength;
U16 diskNumberStart;
U16 internalFileAttributes;
U32 externalFileAttributes;
U32 relativeOffsetOfLocalHeader;
#ifndef TORQUE_COMPILER_GCC
};
#else
} __attribute__ ((packed));
#endif
DirFileHeader m_header;
char m_pFileName[212];
// Stream read routines
public:
bool readFromStream(Stream& io_rStream);
#ifndef TORQUE_COMPILER_GCC
};
#else
} __attribute__ ((packed));
#endif
//-------------------------------------- Padded to 32 bytes. Note that we completely
// ignore any zip file comments.
class ZipEOCDRecord
{
private:
static const U32 csm_eocdRecordSig;
public:
enum {
ProperRecordSize = 22
};
struct EOCDRecord {
U32 eocdSig;
U16 diskNumber;
U16 eocdDiskNumber;
U16 numCDEntriesDisk;
U16 numCDEntriesTotal;
U32 cdSize;
U32 cdOffset;
U16 zipFileCommentLength;
#ifndef TORQUE_COMPILER_GCC
};
#else
} __attribute__ ((packed));
#endif
EOCDRecord m_record;
char __padding[10];
// Stream read routines
public:
bool readFromStream(Stream& io_rStream);
#ifndef TORQUE_COMPILER_GCC
};
#else
} __attribute__ ((packed));
#endif
#ifdef TORQUE_COMPILER_VISUALC
# pragma pack(pop)
#else
#ifdef TORQUE_COMPILER_CODEWARRIOR
# pragma options align=reset
#endif
#endif
#endif //_NZIPHEADERS_H_

452
engine/core/zipSubStream.cc Executable file
View File

@ -0,0 +1,452 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "zlib.h"
#include "core/zipSubStream.h"
const U32 ZipSubRStream::csm_streamCaps = U32(Stream::StreamRead) | U32(Stream::StreamPosition);
const U32 ZipSubRStream::csm_inputBufferSize = 4096;
const U32 ZipSubWStream::csm_streamCaps = U32(Stream::StreamWrite);
const U32 ZipSubWStream::csm_bufferSize = (2048 * 1024);
//--------------------------------------------------------------------------
//--------------------------------------
//
ZipSubRStream::ZipSubRStream()
: m_pStream(NULL),
m_uncompressedSize(0),
m_currentPosition(0),
m_EOS(false),
m_pZipStream(NULL),
m_originalSlavePosition(0)
{
//
}
//--------------------------------------
ZipSubRStream::~ZipSubRStream()
{
detachStream();
}
//--------------------------------------
bool ZipSubRStream::attachStream(Stream* io_pSlaveStream)
{
AssertFatal(io_pSlaveStream != NULL, "NULL Slave stream?");
AssertFatal(m_pStream == NULL, "Already attached!");
m_pStream = io_pSlaveStream;
m_originalSlavePosition = io_pSlaveStream->getPosition();
m_uncompressedSize = 0;
m_currentPosition = 0;
m_EOS = false;
// Initialize zipStream state...
m_pZipStream = new z_stream_s;
m_pInputBuffer = new U8[csm_inputBufferSize];
m_pZipStream->zalloc = Z_NULL;
m_pZipStream->zfree = Z_NULL;
m_pZipStream->opaque = Z_NULL;
U32 buffSize = fillBuffer(csm_inputBufferSize);
m_pZipStream->next_in = m_pInputBuffer;
m_pZipStream->avail_in = buffSize;
m_pZipStream->total_in = 0;
inflateInit2(m_pZipStream, -MAX_WBITS);
setStatus(Ok);
return true;
}
//--------------------------------------
void ZipSubRStream::detachStream()
{
if (m_pZipStream != NULL)
{
// close out zip stream...
inflateEnd(m_pZipStream);
delete [] m_pInputBuffer;
m_pInputBuffer = NULL;
delete m_pZipStream;
m_pZipStream = NULL;
}
m_pStream = NULL;
m_originalSlavePosition = 0;
m_uncompressedSize = 0;
m_currentPosition = 0;
m_EOS = false;
setStatus(Closed);
}
//--------------------------------------
Stream* ZipSubRStream::getStream()
{
return m_pStream;
}
//--------------------------------------
void ZipSubRStream::setUncompressedSize(const U32 in_uncSize)
{
AssertFatal(m_pStream != NULL, "error, no stream to set unc size for");
m_uncompressedSize = in_uncSize;
}
//--------------------------------------
bool ZipSubRStream::_read(const U32 in_numBytes, void *out_pBuffer)
{
if (in_numBytes == 0)
return true;
AssertFatal(out_pBuffer != NULL, "NULL output buffer");
if (getStatus() == Closed) {
AssertFatal(false, "Attempted read from closed stream");
return false;
}
if (Ok != getStatus())
return false;
if (m_EOS)
{
setStatus(EOS);
return true;
};
// Ok, we need to call inflate() until the output buffer is full.
// first, set up the output portion of the z_stream
//
m_pZipStream->next_out = (Bytef*)out_pBuffer;
m_pZipStream->avail_out = in_numBytes;
m_pZipStream->total_out = 0;
while (m_pZipStream->avail_out != 0)
{
S32 retVal = Z_OK;
if(m_pZipStream->avail_in == 0)
{
// check if there is more output pending
inflate(m_pZipStream, Z_SYNC_FLUSH);
if(m_pZipStream->total_out != in_numBytes)
{
// Need to provide more input bytes for the stream to read...
U32 buffSize = fillBuffer(csm_inputBufferSize);
AssertFatal(buffSize != 0, "Must find a more graceful way to handle this");
m_pZipStream->next_in = m_pInputBuffer;
m_pZipStream->avail_in = buffSize;
m_pZipStream->total_in = 0;
}
}
// need to get more?
if(m_pZipStream->total_out != in_numBytes)
retVal = inflate(m_pZipStream, Z_SYNC_FLUSH);
AssertFatal(retVal != Z_BUF_ERROR, "Should never run into a buffer error");
AssertFatal(retVal == Z_OK || retVal == Z_STREAM_END, "error in the stream");
if (retVal == Z_STREAM_END)
{
if (m_pZipStream->avail_out != 0)
m_EOS = true;
setStatus(Ok);
m_currentPosition += m_pZipStream->total_out;
return true;
}
}
AssertFatal(m_pZipStream->total_out == in_numBytes,
"Error, didn't finish the decompression!");
// If we're here, everything went peachy...
setStatus(Ok);
m_currentPosition += m_pZipStream->total_out;
return true;
}
//--------------------------------------
bool ZipSubRStream::hasCapability(const Capability in_cap) const
{
return (csm_streamCaps & U32(in_cap)) != 0;
}
//--------------------------------------
U32 ZipSubRStream::getPosition() const
{
AssertFatal(m_pStream != NULL, "Error, not attached");
return m_currentPosition;
}
//--------------------------------------
bool ZipSubRStream::setPosition(const U32 in_newPosition)
{
AssertFatal(m_pStream != NULL, "Error, not attached");
if (in_newPosition == 0)
{
Stream* pStream = getStream();
U32 resetPosition = m_originalSlavePosition;
U32 uncompressedSize = m_uncompressedSize;
detachStream();
pStream->setPosition(resetPosition);
attachStream(pStream);
setUncompressedSize(uncompressedSize);
return true;
}
else
{
if (in_newPosition > m_uncompressedSize)
return false;
U32 newPosition = in_newPosition;
if (newPosition < m_currentPosition)
{
Stream* pStream = getStream();
U32 resetPosition = m_originalSlavePosition;
U32 uncompressedSize = m_uncompressedSize;
detachStream();
pStream->setPosition(resetPosition);
attachStream(pStream);
setUncompressedSize(uncompressedSize);
}
else
{
newPosition -= m_currentPosition;
}
bool bRet = true;
char *buffer = new char[2048];
while (newPosition >= 2048)
{
newPosition -= 2048;
if (!_read(2048,buffer))
{
bRet = false;
break;
}
};
if (bRet && newPosition > 0)
{
if (!_read(newPosition,buffer))
{
bRet = false;
};
};
delete [] buffer;
return bRet;
}
}
//--------------------------------------
U32 ZipSubRStream::getStreamSize()
{
AssertFatal(m_pStream != NULL, "No stream to size()");
AssertFatal(m_uncompressedSize != 0, "No data? Properties probably not set...");
return m_uncompressedSize;
}
//--------------------------------------
U32 ZipSubRStream::fillBuffer(const U32 in_attemptSize)
{
AssertFatal(m_pStream != NULL, "No stream to fill from?");
AssertFatal(m_pStream->getStatus() != Stream::Closed,
"Fill from a closed stream?");
U32 streamSize = m_pStream->getStreamSize();
U32 currPos = m_pStream->getPosition();
U32 actualReadSize;
if (in_attemptSize + currPos > streamSize) {
actualReadSize = streamSize - currPos;
} else {
actualReadSize = in_attemptSize;
}
if (m_pStream->read(actualReadSize, m_pInputBuffer) == true) {
return actualReadSize;
} else {
AssertWarn(false, "Read failed while trying to fill buffer");
return 0;
}
}
//--------------------------------------------------------------------------
ZipSubWStream::ZipSubWStream()
: m_pStream(NULL),
m_currPosition(0),
m_pZipStream(NULL)
{
//
}
//--------------------------------------
ZipSubWStream::~ZipSubWStream()
{
detachStream();
}
//--------------------------------------
bool ZipSubWStream::attachStream(Stream* io_pSlaveStream)
{
AssertFatal(io_pSlaveStream != NULL, "NULL Slave stream?");
AssertFatal(m_pStream == NULL, "Already attached!");
m_pStream = io_pSlaveStream;
m_currPosition = 0;
m_pOutputBuffer = new U8[csm_bufferSize];
m_pInputBuffer = new U8[csm_bufferSize];
// Initialize zipStream state...
m_pZipStream = new z_stream_s;
m_pZipStream->zalloc = Z_NULL;
m_pZipStream->zfree = Z_NULL;
m_pZipStream->opaque = Z_NULL;
m_pZipStream->next_in = m_pInputBuffer;
m_pZipStream->avail_in = csm_bufferSize;
m_pZipStream->total_in = 0;
m_pZipStream->next_out = m_pOutputBuffer;
m_pZipStream->avail_out = csm_bufferSize;
m_pZipStream->total_out = 0;
deflateInit2(m_pZipStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
setStatus(Ok);
return true;
}
//--------------------------------------
void ZipSubWStream::detachStream()
{
// Must finish...
if (m_pZipStream != NULL)
{
m_pZipStream->avail_in = 0;
deflate(m_pZipStream, Z_FINISH);
// write the remainder
m_pStream->write(csm_bufferSize - m_pZipStream->avail_out, m_pOutputBuffer);
// close out zip stream...
deflateEnd(m_pZipStream);
delete m_pZipStream;
m_pZipStream = NULL;
delete [] m_pInputBuffer;
delete [] m_pOutputBuffer;
m_pInputBuffer = NULL;
m_pOutputBuffer = NULL;
}
m_pStream = NULL;
m_currPosition = 0;
setStatus(Closed);
}
//--------------------------------------
Stream* ZipSubWStream::getStream()
{
return m_pStream;
}
//--------------------------------------
bool ZipSubWStream::_read(const U32, void*)
{
AssertFatal(false, "Cannot read from a ZipSubWStream");
setStatus(IllegalCall);
return false;
}
//--------------------------------------
bool ZipSubWStream::_write(const U32 numBytes, const void *pBuffer)
{
if (numBytes == 0)
return true;
AssertFatal(pBuffer != NULL, "NULL input buffer");
if (getStatus() == Closed)
{
AssertFatal(false, "Attempted write to a closed stream");
return false;
}
m_pZipStream->next_in = (U8*)pBuffer;
m_pZipStream->avail_in = numBytes;
// write as many bufferSize chunks as possible
while(m_pZipStream->avail_in != 0)
{
if(m_pZipStream->avail_out == 0)
{
if(!m_pStream->write(csm_bufferSize, m_pOutputBuffer))
return(false);
m_pZipStream->next_out = m_pOutputBuffer;
m_pZipStream->avail_out = csm_bufferSize;
}
S32 retVal = deflate(m_pZipStream, Z_NO_FLUSH);
AssertFatal(retVal != Z_BUF_ERROR, "ZipSubWStream::_write: invalid buffer");
}
setStatus(Ok);
m_currPosition += m_pZipStream->total_out;
return true;
}
//--------------------------------------
bool ZipSubWStream::hasCapability(const Capability in_cap) const
{
return (csm_streamCaps & U32(in_cap)) != 0;
}
//--------------------------------------
U32 ZipSubWStream::getPosition() const
{
AssertFatal(m_pStream != NULL, "Error, not attached");
return m_currPosition;
}
//--------------------------------------
bool ZipSubWStream::setPosition(const U32 /*in_newPosition*/)
{
AssertFatal(m_pStream != NULL, "Error, not attached");
AssertFatal(false, "Not implemented!");
// Erk. How do we do this.
return false;
}
U32 ZipSubWStream::getStreamSize()
{
AssertFatal(false, "Undecided how to implement this!");
return 0;
}

97
engine/core/zipSubStream.h Executable file
View File

@ -0,0 +1,97 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _ZIPSUBSTREAM_H_
#define _ZIPSUBSTREAM_H_
//Includes
#ifndef _FILTERSTREAM_H_
#include "core/filterStream.h"
#endif
struct z_stream_s;
class ZipSubRStream : public FilterStream
{
typedef FilterStream Parent;
static const U32 csm_streamCaps;
static const U32 csm_inputBufferSize;
Stream* m_pStream;
U32 m_uncompressedSize;
U32 m_currentPosition;
bool m_EOS;
z_stream_s* m_pZipStream;
U8* m_pInputBuffer;
U32 m_originalSlavePosition;
U32 fillBuffer(const U32 in_attemptSize);
public:
ZipSubRStream();
virtual ~ZipSubRStream();
// Overrides of NFilterStream
public:
bool attachStream(Stream* io_pSlaveStream);
void detachStream();
Stream* getStream();
void setUncompressedSize(const U32);
// Mandatory overrides. By default, these are simply passed to
// whatever is returned from getStream();
protected:
bool _read(const U32 in_numBytes, void* out_pBuffer);
public:
bool hasCapability(const Capability) const;
U32 getPosition() const;
bool setPosition(const U32 in_newPosition);
U32 getStreamSize();
};
class ZipSubWStream : public FilterStream
{
typedef FilterStream Parent;
static const U32 csm_streamCaps;
static const U32 csm_bufferSize;
Stream* m_pStream;
z_stream_s* m_pZipStream;
U32 m_currPosition; // Indicates number of _uncompressed_ bytes written
U8* m_pOutputBuffer;
U8* m_pInputBuffer;
public:
ZipSubWStream();
virtual ~ZipSubWStream();
// Overrides of NFilterStream
public:
bool attachStream(Stream* io_pSlaveStream);
void detachStream();
Stream* getStream();
// Mandatory overrides. By default, these are simply passed to
// whatever is returned from getStream();
protected:
bool _read(const U32 in_numBytes, void* out_pBuffer);
bool _write(const U32 in_numBytes, const void* in_pBuffer);
public:
bool hasCapability(const Capability) const;
U32 getPosition() const;
bool setPosition(const U32 in_newPosition);
U32 getStreamSize();
};
#endif //_ZIPSUBSTREAM_H_