added everything
This commit is contained in:
165
engine/core/bitMatrix.h
Executable file
165
engine/core/bitMatrix.h
Executable 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
986
engine/core/bitRender.cc
Executable 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
42
engine/core/bitRender.h
Executable 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
71
engine/core/bitSet.h
Executable 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
1058
engine/core/bitStream.cc
Executable file
File diff suppressed because it is too large
Load Diff
277
engine/core/bitStream.h
Executable file
277
engine/core/bitStream.h
Executable 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
25
engine/core/bitTables.cc
Executable 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
42
engine/core/bitTables.h
Executable 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
169
engine/core/bitVector.h
Executable 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
91
engine/core/bitVectorW.h
Executable 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
421
engine/core/chunkFile.cc
Executable 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
128
engine/core/chunkFile.h
Executable 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
495
engine/core/color.h
Executable 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
64
engine/core/coreRes.h
Executable 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
72
engine/core/crc.cc
Executable 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
17
engine/core/crc.h
Executable 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
59
engine/core/dataChunker.cc
Executable 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
135
engine/core/dataChunker.h
Executable 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
263
engine/core/dnet.cc
Executable 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
64
engine/core/dnet.h
Executable 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
177
engine/core/fileObject.cc
Executable 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
42
engine/core/fileObject.h
Executable 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
496
engine/core/fileStream.cc
Executable 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
75
engine/core/fileStream.h
Executable 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
121
engine/core/fileio.h
Executable 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
61
engine/core/filterStream.cc
Executable 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
39
engine/core/filterStream.h
Executable 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
142
engine/core/findMatch.cc
Executable 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
35
engine/core/findMatch.h
Executable 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
21
engine/core/frameAllocator.cc
Executable 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
242
engine/core/frameAllocator.h
Executable 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
87
engine/core/iTickable.cc
Executable 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
141
engine/core/iTickable.h
Executable 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
19
engine/core/idGenerator.cc
Executable 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
76
engine/core/idGenerator.h
Executable 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
306
engine/core/llist.h
Executable 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
157
engine/core/memStream.cc
Executable 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
45
engine/core/memstream.h
Executable 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
169
engine/core/nStream.cc
Executable 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
40
engine/core/nTypes.cc
Executable 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
54
engine/core/polyList.h
Executable 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
27
engine/core/realComp.h
Executable 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
128
engine/core/resDictionary.cc
Executable 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
1250
engine/core/resManager.cc
Executable file
File diff suppressed because it is too large
Load Diff
489
engine/core/resManager.h
Executable file
489
engine/core/resManager.h
Executable 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
139
engine/core/resizeStream.cc
Executable 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
47
engine/core/resizeStream.h
Executable 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
182
engine/core/stream.h
Executable 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
442
engine/core/stringBuffer.cc
Executable 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 ©) : mBuffer(), mBuffer8()
|
||||
{
|
||||
SBMAddThisStringBuffer();
|
||||
set(©);
|
||||
};
|
||||
|
||||
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
117
engine/core/stringBuffer.h
Executable 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 ©);
|
||||
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
204
engine/core/stringTable.cc
Executable 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
146
engine/core/stringTable.h
Executable 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
19
engine/core/tAlgorithm.h
Executable 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
133
engine/core/tSparseArray.h
Executable 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
74
engine/core/tVector.cc
Executable 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
662
engine/core/tVector.h
Executable 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
303
engine/core/tagDictionary.cc
Executable 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
66
engine/core/tagDictionary.h
Executable 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
968
engine/core/theoraPlayer.cc
Executable 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
180
engine/core/theoraPlayer.h
Executable 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
291
engine/core/tokenizer.cc
Executable 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
66
engine/core/tokenizer.h
Executable 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
112
engine/core/torqueConfig.h
Executable 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
574
engine/core/unicode.cc
Executable 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
103
engine/core/unicode.h
Executable 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
242
engine/core/zipAggregate.cc
Executable 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
67
engine/core/zipAggregate.h
Executable 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
188
engine/core/zipHeaders.cc
Executable 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
200
engine/core/zipHeaders.h
Executable 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
452
engine/core/zipSubStream.cc
Executable 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
97
engine/core/zipSubStream.h
Executable 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_
|
||||
Reference in New Issue
Block a user