From 034ce097c566c299a77ada34fc2779e123ec1b70 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 23 Sep 2019 19:52:25 +0200 Subject: [PATCH] - added more utilities from GZDoom - TArray/TMap and FileReader. --- source/CMakeLists.txt | 3 + source/common/utility/files.cpp | 494 +++++++ source/common/utility/files.h | 331 +++++ source/common/utility/files_decompress.cpp | 672 +++++++++ source/common/utility/m_argv.cpp | 11 + source/common/utility/m_argv.h | 1 + source/common/utility/m_swap.h | 255 ++++ source/common/utility/tarray.h | 1563 ++++++++++++++++++++ source/common/utility/templates.h | 18 + 9 files changed, 3348 insertions(+) create mode 100644 source/common/utility/files.cpp create mode 100644 source/common/utility/files.h create mode 100644 source/common/utility/files_decompress.cpp create mode 100644 source/common/utility/m_swap.h create mode 100644 source/common/utility/tarray.h create mode 100644 source/common/utility/templates.h diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 95d73a355..f4a295a41 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -963,6 +963,9 @@ set (PCH_SOURCES blood/src/weapon.cpp common/utility/m_argv.cpp + common/utility/files.cpp + common/utility/files_decompress.cpp + #common/utility/configfile.cpp ) if( MSVC ) diff --git a/source/common/utility/files.cpp b/source/common/utility/files.cpp new file mode 100644 index 000000000..8b50bd486 --- /dev/null +++ b/source/common/utility/files.cpp @@ -0,0 +1,494 @@ +/* +** files.cpp +** Implements classes for reading from files or memory blocks +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** Copyright 2005-2008 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "files.h" +#include "templates.h" // just for 'clamp' + + +FILE *myfopen(const char *filename, const char *flags) +{ + // fix this later +#if 1//ndef _WIN32 + return fopen(filename, flags); +#else + auto widename = WideString(filename); + auto wideflags = WideString(flags); + return _wfopen(widename.c_str(), wideflags.c_str()); +#endif +} + + +//========================================================================== +// +// StdFileReader +// +// reads data from an stdio FILE* or part of it. +// +//========================================================================== + +class StdFileReader : public FileReaderInterface +{ + FILE *File = nullptr; + long StartPos = 0; + long FilePos = 0; + +public: + StdFileReader() + {} + + ~StdFileReader() + { + if (File != nullptr) + { + fclose(File); + } + File = nullptr; + } + + bool Open(const char *filename, long startpos = 0, long len = -1) + { + File = myfopen(filename, "rb"); + if (File == nullptr) return false; + FilePos = startpos; + StartPos = startpos; + Length = CalcFileLen(); + if (len >= 0 && len < Length) Length = len; + if (startpos > 0) Seek(0, SEEK_SET); + return true; + } + + long Tell() const override + { + return FilePos - StartPos; + } + + long Seek(long offset, int origin) override + { + if (origin == SEEK_SET) + { + offset += StartPos; + } + else if (origin == SEEK_CUR) + { + offset += FilePos; + } + else if (origin == SEEK_END) + { + offset += StartPos + Length; + } + if (offset < StartPos || offset > StartPos + Length) return -1; // out of scope + + if (0 == fseek(File, offset, SEEK_SET)) + { + FilePos = offset; + return 0; + } + return -1; + } + + long Read(void *buffer, long len) override + { + assert(len >= 0); + if (len <= 0) return 0; + if (FilePos + len > StartPos + Length) + { + len = Length - FilePos + StartPos; + } + len = (long)fread(buffer, 1, len, File); + FilePos += len; + return len; + } + + char *Gets(char *strbuf, int len) override + { + if (len <= 0 || FilePos >= StartPos + Length) return NULL; + char *p = fgets(strbuf, len, File); + if (p != NULL) + { + int old = FilePos; + FilePos = ftell(File); + if (FilePos - StartPos > Length) + { + strbuf[Length - old + StartPos] = 0; + } + } + return p; + } + +private: + long CalcFileLen() const + { + long endpos; + + fseek(File, 0, SEEK_END); + endpos = ftell(File); + fseek(File, 0, SEEK_SET); + return endpos; + } +}; + +//========================================================================== +// +// FileReaderRedirect +// +// like the above, but uses another File reader as its backing data +// +//========================================================================== + +class FileReaderRedirect : public FileReaderInterface +{ + FileReader *mReader = nullptr; + long StartPos = 0; + long FilePos = 0; + +public: + FileReaderRedirect(FileReader &parent, long start, long length) + { + mReader = &parent; + FilePos = start; + StartPos = start; + Length = length; + Seek(0, SEEK_SET); + } + + virtual long Tell() const override + { + return FilePos - StartPos; + } + + virtual long Seek(long offset, int origin) + { + switch (origin) + { + case SEEK_SET: + offset += StartPos; + break; + + case SEEK_END: + offset += StartPos + Length; + break; + + case SEEK_CUR: + offset += (long)mReader->Tell(); + break; + } + if (offset < StartPos || offset > StartPos + Length) return -1; // out of scope + if (mReader->Seek(offset, FileReader::SeekSet) == 0) + { + FilePos = offset; + return 0; + } + return -1; + } + + virtual long Read(void *buffer, long len) + { + assert(len >= 0); + if (len <= 0) return 0; + if (FilePos + len > StartPos + Length) + { + len = Length - FilePos + StartPos; + } + len = (long)mReader->Read(buffer, len); + FilePos += len; + return len; + } + + virtual char *Gets(char *strbuf, int len) + { + if (len <= 0 || FilePos >= StartPos + Length) return NULL; + char *p = mReader->Gets(strbuf, len); + if (p != NULL) + { + int old = FilePos; + FilePos = (long)mReader->Tell(); + if (FilePos - StartPos > Length) + { + strbuf[Length - old + StartPos] = 0; + } + } + return p; + } + +}; + +//========================================================================== +// +// MemoryReader +// +// reads data from a block of memory +// +//========================================================================== + +long MemoryReader::Tell() const +{ + return FilePos; +} + +long MemoryReader::Seek(long offset, int origin) +{ + switch (origin) + { + case SEEK_CUR: + offset += FilePos; + break; + + case SEEK_END: + offset += Length; + break; + + } + if (offset < 0 || offset > Length) return -1; + FilePos = clamp(offset, 0, Length); + return 0; +} + +long MemoryReader::Read(void *buffer, long len) +{ + if (len>Length - FilePos) len = Length - FilePos; + if (len<0) len = 0; + memcpy(buffer, bufptr + FilePos, len); + FilePos += len; + return len; +} + +char *MemoryReader::Gets(char *strbuf, int len) +{ + if (len>Length - FilePos) len = Length - FilePos; + if (len <= 0) return NULL; + + char *p = strbuf; + while (len > 1) + { + if (bufptr[FilePos] == 0) + { + FilePos++; + break; + } + if (bufptr[FilePos] != '\r') + { + *p++ = bufptr[FilePos]; + len--; + if (bufptr[FilePos] == '\n') + { + FilePos++; + break; + } + } + FilePos++; + } + if (p == strbuf) return NULL; + *p++ = 0; + return strbuf; +} + +//========================================================================== +// +// MemoryArrayReader +// +// reads data from an array of memory +// +//========================================================================== + +class MemoryArrayReader : public MemoryReader +{ + TArray buf; + +public: + MemoryArrayReader(const char *buffer, long length) + { + if (length > 0) + { + buf.Resize(length); + memcpy(&buf[0], buffer, length); + } + UpdateBuffer(); + } + + TArray &GetArray() { return buf; } + + void UpdateBuffer() + { + bufptr = (const char*)&buf[0]; + FilePos = 0; + Length = buf.Size(); + } +}; + + + +//========================================================================== +// +// FileReader +// +// this wraps the different reader types in an object with value semantics. +// +//========================================================================== + +bool FileReader::OpenFile(const char *filename, FileReader::Size start, FileReader::Size length) +{ + auto reader = new StdFileReader; + if (!reader->Open(filename, (long)start, (long)length)) + { + delete reader; + return false; + } + Close(); + mReader = reader; + return true; +} + +bool FileReader::OpenFilePart(FileReader &parent, FileReader::Size start, FileReader::Size length) +{ + auto reader = new FileReaderRedirect(parent, (long)start, (long)length); + Close(); + mReader = reader; + return true; +} + +bool FileReader::OpenMemory(const void *mem, FileReader::Size length) +{ + Close(); + mReader = new MemoryReader((const char *)mem, (long)length); + return true; +} + +bool FileReader::OpenMemoryArray(const void *mem, FileReader::Size length) +{ + Close(); + mReader = new MemoryArrayReader((const char *)mem, (long)length); + return true; +} + +bool FileReader::OpenMemoryArray(std::function&)> getter) +{ + auto reader = new MemoryArrayReader(nullptr, 0); + if (getter(reader->GetArray())) + { + Close(); + reader->UpdateBuffer(); + mReader = reader; + return true; + } + else + { + // This will keep the old buffer, if one existed + delete reader; + return false; + } +} + + +//========================================================================== +// +// FileWriter (the motivation here is to have a buffer writing subclass) +// +//========================================================================== + +bool FileWriter::OpenDirect(const char *filename) +{ + File = myfopen(filename, "wb"); + return (File != NULL); +} + +FileWriter *FileWriter::Open(const char *filename) +{ + FileWriter *fwrit = new FileWriter(); + if (fwrit->OpenDirect(filename)) + { + return fwrit; + } + delete fwrit; + return NULL; +} + +size_t FileWriter::Write(const void *buffer, size_t len) +{ + if (File != NULL) + { + return fwrite(buffer, 1, len, File); + } + else + { + return 0; + } +} + +long FileWriter::Tell() +{ + if (File != NULL) + { + return ftell(File); + } + else + { + return 0; + } +} + +long FileWriter::Seek(long offset, int mode) +{ + if (File != NULL) + { + return fseek(File, offset, mode); + } + else + { + return 0; + } +} + +size_t FileWriter::Printf(const char *fmt, ...) +{ + // fix me later +#if 0 + va_list ap; + FString out; + + va_start(ap, fmt); + out.VFormat(fmt, ap); + va_end(ap); + return Write(out.GetChars(), out.Len()); +#else + return 0; +#endif +} + +size_t BufferWriter::Write(const void *buffer, size_t len) +{ + unsigned int ofs = mBuffer.Reserve((unsigned)len); + memcpy(&mBuffer[ofs], buffer, len); + return len; +} diff --git a/source/common/utility/files.h b/source/common/utility/files.h new file mode 100644 index 000000000..ccb4a1ba9 --- /dev/null +++ b/source/common/utility/files.h @@ -0,0 +1,331 @@ +/* +** files.h +** Implements classes for reading from files or memory blocks +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** Copyright 2005-2008 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef FILES_H +#define FILES_H + +#include +#include +#include +#include +#include "basics.h" +#include "m_swap.h" +#include "tarray.h" + +// Zip compression methods, extended by some internal types to be passed to OpenDecompressor +enum +{ + METHOD_STORED = 0, + METHOD_SHRINK = 1, + METHOD_IMPLODE = 6, + METHOD_DEFLATE = 8, + METHOD_BZIP2 = 12, + METHOD_LZMA = 14, + METHOD_PPMD = 98, + METHOD_LZSS = 1337, // not used in Zips - this is for Console Doom compression + METHOD_ZLIB = 1338, // Zlib stream with header, used by compressed nodes. +}; + +class FileReaderInterface +{ +public: + long Length = -1; + virtual ~FileReaderInterface() {} + virtual long Tell () const = 0; + virtual long Seek (long offset, int origin) = 0; + virtual long Read (void *buffer, long len) = 0; + virtual char *Gets(char *strbuf, int len) = 0; + virtual const char *GetBuffer() const { return nullptr; } + long GetLength () const { return Length; } +}; + +class DecompressorBase : public FileReaderInterface +{ + std::function ErrorCallback = nullptr; +public: + // These do not work but need to be defined to satisfy the FileReaderInterface. + // They will just error out when called. + long Tell() const override; + long Seek(long offset, int origin) override; + char *Gets(char *strbuf, int len) override; + void DecompressionError(const char* error, ...) const; + void SetErrorCallback(const std::function& cb) + { + ErrorCallback = cb; + } +}; + +class MemoryReader : public FileReaderInterface +{ +protected: + const char * bufptr = nullptr; + long FilePos = 0; + + MemoryReader() + {} + +public: + MemoryReader(const char *buffer, long length) + { + bufptr = buffer; + Length = length; + FilePos = 0; + } + + long Tell() const override; + long Seek(long offset, int origin) override; + long Read(void *buffer, long len) override; + char *Gets(char *strbuf, int len) override; + virtual const char *GetBuffer() const override { return bufptr; } +}; + + +struct FResourceLump; + +class FileReader +{ + friend struct FResourceLump; // needs access to the private constructor. + + FileReaderInterface *mReader = nullptr; + + FileReader(const FileReader &r) = delete; + FileReader &operator=(const FileReader &r) = delete; + + explicit FileReader(FileReaderInterface *r) + { + mReader = r; + } + +public: + enum ESeek + { + SeekSet = SEEK_SET, + SeekCur = SEEK_CUR, + SeekEnd = SEEK_END + }; + + typedef ptrdiff_t Size; // let's not use 'long' here. + + FileReader() {} + + FileReader(FileReader &&r) + { + mReader = r.mReader; + r.mReader = nullptr; + } + + FileReader& operator =(FileReader &&r) + { + Close(); + mReader = r.mReader; + r.mReader = nullptr; + return *this; + } + + + ~FileReader() + { + Close(); + } + + bool isOpen() const + { + return mReader != nullptr; + } + + void Close() + { + if (mReader != nullptr) delete mReader; + mReader = nullptr; + } + + bool OpenFile(const char *filename, Size start = 0, Size length = -1); + bool OpenFilePart(FileReader &parent, Size start, Size length); + bool OpenMemory(const void *mem, Size length); // read directly from the buffer + bool OpenMemoryArray(const void *mem, Size length); // read from a copy of the buffer. + bool OpenMemoryArray(std::function&)> getter); // read contents to a buffer and return a reader to it + bool OpenDecompressor(FileReader &parent, Size length, int method, bool seekable, const std::function& cb); // creates a decompressor stream. 'seekable' uses a buffered version so that the Seek and Tell methods can be used. + + Size Tell() const + { + return mReader->Tell(); + } + + Size Seek(Size offset, ESeek origin) + { + return mReader->Seek((long)offset, origin); + } + + Size Read(void *buffer, Size len) + { + return mReader->Read(buffer, (long)len); + } + + TArray Read(size_t len) + { + TArray buffer((int)len, true); + Size length = mReader->Read(&buffer[0], (long)len); + buffer.Clamp((int)length); + return buffer; + } + + TArray Read() + { + TArray buffer(mReader->Length, true); + Size length = mReader->Read(&buffer[0], mReader->Length); + if (length < mReader->Length) buffer.Clear(); + return buffer; + } + + char *Gets(char *strbuf, Size len) + { + return mReader->Gets(strbuf, (int)len); + } + + const char *GetBuffer() + { + return mReader->GetBuffer(); + } + + Size GetLength() const + { + return mReader->GetLength(); + } + + uint8_t ReadUInt8() + { + uint8_t v = 0; + Read(&v, 1); + return v; + } + + int8_t ReadInt8() + { + int8_t v = 0; + Read(&v, 1); + return v; + } + + uint16_t ReadUInt16() + { + uint16_t v = 0; + Read(&v, 2); + return LittleShort(v); + } + + int16_t ReadInt16() + { + uint16_t v = 0; + Read(&v, 2); + return LittleShort(v); + } + + uint32_t ReadUInt32() + { + uint32_t v = 0; + Read(&v, 4); + return LittleLong(v); + } + + int32_t ReadInt32() + { + uint32_t v = 0; + Read(&v, 4); + return LittleLong(v); + } + + uint32_t ReadUInt32BE() + { + uint32_t v = 0; + Read(&v, 4); + return BigLong(v); + } + + int32_t ReadInt32BE() + { + uint32_t v = 0; + Read(&v, 4); + return BigLong(v); + } + + + friend class FWadCollection; +}; + + + + +class FileWriter +{ +protected: + bool OpenDirect(const char *filename); + + FileWriter() + { + File = NULL; + } +public: + virtual ~FileWriter() + { + if (File != NULL) fclose(File); + } + + static FileWriter *Open(const char *filename); + + virtual size_t Write(const void *buffer, size_t len); + virtual long Tell(); + virtual long Seek(long offset, int mode); + size_t Printf(const char *fmt, ...) GCCPRINTF(2,3); + +protected: + + FILE *File; + +protected: + bool CloseOnDestruct; +}; + +class BufferWriter : public FileWriter +{ +protected: + TArray mBuffer; +public: + + BufferWriter() {} + virtual size_t Write(const void *buffer, size_t len) override; + TArray *GetBuffer() { return &mBuffer; } +}; + +#endif diff --git a/source/common/utility/files_decompress.cpp b/source/common/utility/files_decompress.cpp new file mode 100644 index 000000000..132d5d4ff --- /dev/null +++ b/source/common/utility/files_decompress.cpp @@ -0,0 +1,672 @@ +/* +** files.cpp +** Implements classes for reading from files or memory blocks +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 Randy Heit +** Copyright 2005-2008 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// Caution: LzmaDec also pulls in windows.h! +#include "LzmaDec.h" +#include +#include +#include + +#include "files.h" +#include "templates.h" + +//========================================================================== +// +// I_Error +// +// Throw an error that will send us to the console if we are far enough +// along in the startup process. +// +//========================================================================== + +void DecompressorBase::DecompressionError(const char *error, ...) const +{ + const int MAX_ERRORTEXT = 300; + va_list argptr; + char errortext[MAX_ERRORTEXT]; + + va_start(argptr, error); + vsnprintf(errortext, MAX_ERRORTEXT, error, argptr); + va_end(argptr); + + if (ErrorCallback != nullptr) ErrorCallback(errortext); + else std::terminate(); +} + +long DecompressorBase::Tell () const +{ + DecompressionError("Cannot get position of decompressor stream"); + return 0; +} +long DecompressorBase::Seek (long offset, int origin) +{ + DecompressionError("Cannot seek in decompressor stream"); + return 0; +} +char *DecompressorBase::Gets(char *strbuf, int len) +{ + DecompressionError("Cannot use Gets on decompressor stream"); + return nullptr; +} + +// +// M_ZlibError +// +#if 0 +FString M_ZLibError(int zerr) +{ + if (zerr >= 0) + { + return "OK"; + } + else if (zerr < -6) + { + FString out; + out.Format("%d", zerr); + return out; + } + else + { + static const char* errs[6] = + { + "Errno", + "Stream Error", + "Data Error", + "Memory Error", + "Buffer Error", + "Version Error" + }; + return errs[-zerr - 1]; + } +} +#endif + +//========================================================================== +// +// DecompressorZ +// +// The zlib wrapper +// reads data from a ZLib compressed stream +// +//========================================================================== + +class DecompressorZ : public DecompressorBase +{ + enum { BUFF_SIZE = 4096 }; + + FileReader &File; + bool SawEOF; + z_stream Stream; + uint8_t InBuff[BUFF_SIZE]; + +public: + DecompressorZ (FileReader &file, bool zip, const std::function& cb) + : File(file), SawEOF(false) + { + int err; + + SetErrorCallback(cb); + FillBuffer (); + + Stream.zalloc = Z_NULL; + Stream.zfree = Z_NULL; + + if (!zip) err = inflateInit (&Stream); + else err = inflateInit2 (&Stream, -MAX_WBITS); + + if (err != Z_OK) + { + // Later, when FString is available. +#if 0 + DecompressionError ("DecompressorZ: inflateInit failed: %s\n", M_ZLibError(err).GetChars()); +#endif + } + } + + ~DecompressorZ () + { + inflateEnd (&Stream); + } + + long Read (void *buffer, long len) override + { + int err; + + Stream.next_out = (Bytef *)buffer; + Stream.avail_out = len; + + do + { + err = inflate (&Stream, Z_SYNC_FLUSH); + if (Stream.avail_in == 0 && !SawEOF) + { + FillBuffer (); + } + } while (err == Z_OK && Stream.avail_out != 0); + + if (err != Z_OK && err != Z_STREAM_END) + { + DecompressionError ("Corrupt zlib stream"); + } + + if (Stream.avail_out != 0) + { + DecompressionError ("Ran out of data in zlib stream"); + } + + return len - Stream.avail_out; + } + + void FillBuffer () + { + auto numread = File.Read (InBuff, BUFF_SIZE); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + Stream.next_in = InBuff; + Stream.avail_in = (uInt)numread; + } +}; + + +//========================================================================== +// +// DecompressorZ +// +// The bzip2 wrapper +// reads data from a libbzip2 compressed stream +// +//========================================================================== + +class DecompressorBZ2; +static DecompressorBZ2 * stupidGlobal; // Why does that dumb global error callback not pass the decompressor state? + // Thanks to that brain-dead interface we have to use a global variable to get the error to the proper handler. + +class DecompressorBZ2 : public DecompressorBase +{ + enum { BUFF_SIZE = 4096 }; + + FileReader &File; + bool SawEOF; + bz_stream Stream; + uint8_t InBuff[BUFF_SIZE]; + +public: + DecompressorBZ2 (FileReader &file, const std::function& cb) + : File(file), SawEOF(false) + { + int err; + + SetErrorCallback(cb); + stupidGlobal = this; + FillBuffer (); + + Stream.bzalloc = NULL; + Stream.bzfree = NULL; + Stream.opaque = NULL; + + err = BZ2_bzDecompressInit(&Stream, 0, 0); + + if (err != BZ_OK) + { + DecompressionError ("DecompressorBZ2: bzDecompressInit failed: %d\n", err); + } + } + + ~DecompressorBZ2 () + { + stupidGlobal = this; + BZ2_bzDecompressEnd (&Stream); + } + + long Read (void *buffer, long len) override + { + int err; + + stupidGlobal = this; + Stream.next_out = (char *)buffer; + Stream.avail_out = len; + + do + { + err = BZ2_bzDecompress(&Stream); + if (Stream.avail_in == 0 && !SawEOF) + { + FillBuffer (); + } + } while (err == BZ_OK && Stream.avail_out != 0); + + if (err != BZ_OK && err != BZ_STREAM_END) + { + DecompressionError ("Corrupt bzip2 stream"); + } + + if (Stream.avail_out != 0) + { + DecompressionError ("Ran out of data in bzip2 stream"); + } + + return len - Stream.avail_out; + } + + void FillBuffer () + { + auto numread = File.Read(InBuff, BUFF_SIZE); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + Stream.next_in = (char *)InBuff; + Stream.avail_in = (unsigned)numread; + } + +}; + +//========================================================================== +// +// bz_internal_error +// +// libbzip2 wants this, since we build it with BZ_NO_STDIO set. +// +//========================================================================== + +extern "C" void bz_internal_error (int errcode) +{ + if (stupidGlobal) stupidGlobal->DecompressionError("libbzip2: internal error number %d\n", errcode); + else std::terminate(); +} + +//========================================================================== +// +// DecompressorLZMA +// +// The lzma wrapper +// reads data from a LZMA compressed stream +// +//========================================================================== + +static void *SzAlloc(ISzAllocPtr, size_t size) { return malloc(size); } +static void SzFree(ISzAllocPtr, void *address) { free(address); } +ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +// Wraps around a Decompressor to decompress a lzma stream +class DecompressorLZMA : public DecompressorBase +{ + enum { BUFF_SIZE = 4096 }; + + FileReader &File; + bool SawEOF; + CLzmaDec Stream; + size_t Size; + size_t InPos, InSize; + size_t OutProcessed; + uint8_t InBuff[BUFF_SIZE]; + +public: + + DecompressorLZMA (FileReader &file, size_t uncompressed_size, const std::function& cb) + : File(file), SawEOF(false) + { + uint8_t header[4 + LZMA_PROPS_SIZE]; + int err; + SetErrorCallback(cb); + + Size = uncompressed_size; + OutProcessed = 0; + + // Read zip LZMA properties header + if (File.Read(header, sizeof(header)) < (long)sizeof(header)) + { + DecompressionError("DecompressorLZMA: File too short\n"); + } + if (header[2] + header[3] * 256 != LZMA_PROPS_SIZE) + { + DecompressionError("DecompressorLZMA: LZMA props size is %d (expected %d)\n", + header[2] + header[3] * 256, LZMA_PROPS_SIZE); + } + + FillBuffer(); + + LzmaDec_Construct(&Stream); + err = LzmaDec_Allocate(&Stream, header + 4, LZMA_PROPS_SIZE, &g_Alloc); + + if (err != SZ_OK) + { + DecompressionError("DecompressorLZMA: LzmaDec_Allocate failed: %d\n", err); + } + + LzmaDec_Init(&Stream); + } + + ~DecompressorLZMA () + { + LzmaDec_Free(&Stream, &g_Alloc); + } + + long Read (void *buffer, long len) override + { + int err; + Byte *next_out = (Byte *)buffer; + + do + { + ELzmaFinishMode finish_mode = LZMA_FINISH_ANY; + ELzmaStatus status; + size_t out_processed = len; + size_t in_processed = InSize; + + err = LzmaDec_DecodeToBuf(&Stream, next_out, &out_processed, InBuff + InPos, &in_processed, finish_mode, &status); + InPos += in_processed; + InSize -= in_processed; + next_out += out_processed; + len = (long)(len - out_processed); + if (err != SZ_OK) + { + DecompressionError ("Corrupt LZMA stream"); + } + if (in_processed == 0 && out_processed == 0) + { + if (status != LZMA_STATUS_FINISHED_WITH_MARK) + { + DecompressionError ("Corrupt LZMA stream"); + } + } + if (InSize == 0 && !SawEOF) + { + FillBuffer (); + } + } while (err == SZ_OK && len != 0); + + if (err != Z_OK && err != Z_STREAM_END) + { + DecompressionError ("Corrupt LZMA stream"); + } + + if (len != 0) + { + DecompressionError ("Ran out of data in LZMA stream"); + } + + return (long)(next_out - (Byte *)buffer); + } + + void FillBuffer () + { + auto numread = File.Read(InBuff, BUFF_SIZE); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + InPos = 0; + InSize = numread; + } + +}; + +//========================================================================== +// +// Console Doom LZSS wrapper. +// +//========================================================================== + +class DecompressorLZSS : public DecompressorBase +{ + enum { BUFF_SIZE = 4096, WINDOW_SIZE = 4096, INTERNAL_BUFFER_SIZE = 128 }; + + FileReader &File; + bool SawEOF; + uint8_t InBuff[BUFF_SIZE]; + + enum StreamState + { + STREAM_EMPTY, + STREAM_BITS, + STREAM_FLUSH, + STREAM_FINAL + }; + struct + { + StreamState State; + + uint8_t *In; + unsigned int AvailIn; + unsigned int InternalOut; + + uint8_t CFlags, Bits; + + uint8_t Window[WINDOW_SIZE+INTERNAL_BUFFER_SIZE]; + const uint8_t *WindowData; + uint8_t *InternalBuffer; + } Stream; + + void FillBuffer() + { + if(Stream.AvailIn) + memmove(InBuff, Stream.In, Stream.AvailIn); + + auto numread = File.Read(InBuff+Stream.AvailIn, BUFF_SIZE-Stream.AvailIn); + + if (numread < BUFF_SIZE) + { + SawEOF = true; + } + Stream.In = InBuff; + Stream.AvailIn = (unsigned)numread+Stream.AvailIn; + } + + // Reads a flag byte. + void PrepareBlocks() + { + assert(Stream.InternalBuffer == Stream.WindowData); + Stream.CFlags = *Stream.In++; + --Stream.AvailIn; + Stream.Bits = 0xFF; + Stream.State = STREAM_BITS; + } + + // Reads the next chunk in the block. Returns true if successful and + // returns false if it ran out of input data. + bool UncompressBlock() + { + if(Stream.CFlags & 1) + { + // Check to see if we have enough input + if(Stream.AvailIn < 2) + return false; + Stream.AvailIn -= 2; + + uint16_t pos = BigShort(*(uint16_t*)Stream.In); + uint8_t len = (pos & 0xF)+1; + pos >>= 4; + Stream.In += 2; + if(len == 1) + { + // We've reached the end of the stream. + Stream.State = STREAM_FINAL; + return true; + } + + const uint8_t* copyStart = Stream.InternalBuffer-pos-1; + + // Complete overlap: Single byte repeated + if(pos == 0) + memset(Stream.InternalBuffer, *copyStart, len); + // No overlap: One copy + else if(pos >= len) + memcpy(Stream.InternalBuffer, copyStart, len); + else + { + // Partial overlap: Copy in 2 or 3 chunks. + do + { + unsigned int copy = std::min(len, pos+1); + memcpy(Stream.InternalBuffer, copyStart, copy); + Stream.InternalBuffer += copy; + Stream.InternalOut += copy; + len -= copy; + pos += copy; // Increase our position since we can copy twice as much the next round. + } + while(len); + } + + Stream.InternalOut += len; + Stream.InternalBuffer += len; + } + else + { + // Uncompressed byte. + *Stream.InternalBuffer++ = *Stream.In++; + --Stream.AvailIn; + ++Stream.InternalOut; + } + + Stream.CFlags >>= 1; + Stream.Bits >>= 1; + + // If we're done with this block, flush the output + if(Stream.Bits == 0) + Stream.State = STREAM_FLUSH; + + return true; + } + +public: + DecompressorLZSS(FileReader &file, const std::function& cb) : File(file), SawEOF(false) + { + SetErrorCallback(cb); + Stream.State = STREAM_EMPTY; + Stream.WindowData = Stream.InternalBuffer = Stream.Window+WINDOW_SIZE; + Stream.InternalOut = 0; + Stream.AvailIn = 0; + + FillBuffer(); + } + + ~DecompressorLZSS() + { + } + + long Read(void *buffer, long len) override + { + + uint8_t *Out = (uint8_t*)buffer; + long AvailOut = len; + + do + { + while(Stream.AvailIn) + { + if(Stream.State == STREAM_EMPTY) + PrepareBlocks(); + else if(Stream.State == STREAM_BITS && !UncompressBlock()) + break; + else + break; + } + + unsigned int copy = std::min(Stream.InternalOut, AvailOut); + if(copy > 0) + { + memcpy(Out, Stream.WindowData, copy); + Out += copy; + AvailOut -= copy; + + // Slide our window + memmove(Stream.Window, Stream.Window+copy, WINDOW_SIZE+INTERNAL_BUFFER_SIZE-copy); + Stream.InternalBuffer -= copy; + Stream.InternalOut -= copy; + } + + if(Stream.State == STREAM_FINAL) + break; + + if(Stream.InternalOut == 0 && Stream.State == STREAM_FLUSH) + Stream.State = STREAM_EMPTY; + + if(Stream.AvailIn < 2) + FillBuffer(); + } + while(AvailOut && Stream.State != STREAM_FINAL); + + assert(AvailOut == 0); + return (long)(Out - (uint8_t*)buffer); + } +}; + + +bool FileReader::OpenDecompressor(FileReader &parent, Size length, int method, bool seekable, const std::function& cb) +{ + DecompressorBase *dec = nullptr; + switch (method) + { + case METHOD_DEFLATE: + case METHOD_ZLIB: + dec = new DecompressorZ(parent, method == METHOD_DEFLATE, cb); + break; + + case METHOD_BZIP2: + dec = new DecompressorBZ2(parent, cb); + break; + + case METHOD_LZMA: + dec = new DecompressorLZMA(parent, length, cb); + break; + + case METHOD_LZSS: + dec = new DecompressorLZSS(parent, cb); + break; + + // todo: METHOD_IMPLODE, METHOD_SHRINK + default: + return false; + } + dec->Length = (long)length; + if (!seekable) + { + Close(); + mReader = dec; + return true; + } + else + { + // todo: create a wrapper. for now this fails + delete dec; + return false; + } +} diff --git a/source/common/utility/m_argv.cpp b/source/common/utility/m_argv.cpp index 76cc73e43..4c7290de0 100644 --- a/source/common/utility/m_argv.cpp +++ b/source/common/utility/m_argv.cpp @@ -67,6 +67,17 @@ FArgs::FArgs(int argc, char **argv) SetArgs(argc, argv); } +//=========================================================================== +// +// FArgs Argv Constructor +// +//=========================================================================== + +FArgs::FArgs(int argc, const char** argv) +{ + SetArgs(argc, const_cast(argv)); // Thanks, C++, for the inflexible const casting rules... +} + //=========================================================================== // // FArgs String Argv Constructor diff --git a/source/common/utility/m_argv.h b/source/common/utility/m_argv.h index fe0875818..1a723bb34 100644 --- a/source/common/utility/m_argv.h +++ b/source/common/utility/m_argv.h @@ -46,6 +46,7 @@ public: FArgs(); FArgs(const FArgs &args); FArgs(int argc, char **argv); + FArgs(int argc, const char** argv); FArgs(int argc, const std::string *argv); FArgs &operator=(const FArgs &other); diff --git a/source/common/utility/m_swap.h b/source/common/utility/m_swap.h new file mode 100644 index 000000000..de9b7780a --- /dev/null +++ b/source/common/utility/m_swap.h @@ -0,0 +1,255 @@ +// +// DESCRIPTION: +// Endianess handling, swapping 16bit and 32bit. +// +//----------------------------------------------------------------------------- + + +#ifndef __M_SWAP_H__ +#define __M_SWAP_H__ + +#include + +// Endianess handling. +// WAD files are stored little endian. + +#ifdef __APPLE__ +#include + +inline short LittleShort(short x) +{ + return (short)OSSwapLittleToHostInt16((uint16_t)x); +} + +inline unsigned short LittleShort(unsigned short x) +{ + return OSSwapLittleToHostInt16(x); +} + +inline short LittleShort(int x) +{ + return OSSwapLittleToHostInt16((uint16_t)x); +} + +inline unsigned short LittleShort(unsigned int x) +{ + return OSSwapLittleToHostInt16((uint16_t)x); +} + +inline int LittleLong(int x) +{ + return OSSwapLittleToHostInt32((uint32_t)x); +} + +inline unsigned int LittleLong(unsigned int x) +{ + return OSSwapLittleToHostInt32(x); +} + +inline short BigShort(short x) +{ + return (short)OSSwapBigToHostInt16((uint16_t)x); +} + +inline unsigned short BigShort(unsigned short x) +{ + return OSSwapBigToHostInt16(x); +} + +inline int BigLong(int x) +{ + return OSSwapBigToHostInt32((uint32_t)x); +} + +inline unsigned int BigLong(unsigned int x) +{ + return OSSwapBigToHostInt32(x); +} + +#elif defined __BIG_ENDIAN__ + +// Swap 16bit, that is, MSB and LSB byte. +// No masking with 0xFF should be necessary. +inline short LittleShort (short x) +{ + return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8)); +} + +inline unsigned short LittleShort (unsigned short x) +{ + return (unsigned short)((x>>8) | (x<<8)); +} + +inline short LittleShort (int x) +{ + return LittleShort((short)x); +} + +inline unsigned short LittleShort (unsigned int x) +{ + return LittleShort((unsigned short)x); +} + +// Swapping 32bit. +inline unsigned int LittleLong (unsigned int x) +{ + return (unsigned int)( + (x>>24) + | ((x>>8) & 0xff00) + | ((x<<8) & 0xff0000) + | (x<<24)); +} + +inline int LittleLong (int x) +{ + return (int)( + (((unsigned int)x)>>24) + | ((((unsigned int)x)>>8) & 0xff00) + | ((((unsigned int)x)<<8) & 0xff0000) + | (((unsigned int)x)<<24)); +} + +inline short BigShort(short x) +{ + return x; +} + +inline unsigned short BigShort(unsigned short x) +{ + return x; +} + +inline unsigned int BigLong(unsigned int x) +{ + return x; +} + +inline int BigLong(int x) +{ + return x; +} + +#else + +inline short LittleShort(short x) +{ + return x; +} + +inline unsigned short LittleShort(unsigned short x) +{ + return x; +} + +inline unsigned int LittleLong(unsigned int x) +{ + return x; +} + +inline int LittleLong(int x) +{ + return x; +} + +#ifdef _MSC_VER + +inline short BigShort(short x) +{ + return (short)_byteswap_ushort((unsigned short)x); +} + +inline unsigned short BigShort(unsigned short x) +{ + return _byteswap_ushort(x); +} + +inline int BigLong(int x) +{ + return (int)_byteswap_ulong((unsigned long)x); +} + +inline unsigned int BigLong(unsigned int x) +{ + return (unsigned int)_byteswap_ulong((unsigned long)x); +} +#pragma warning (default: 4035) + +#else + +inline short BigShort (short x) +{ + return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8)); +} + +inline unsigned short BigShort (unsigned short x) +{ + return (unsigned short)((x>>8) | (x<<8)); +} + +inline unsigned int BigLong (unsigned int x) +{ + return (unsigned int)( + (x>>24) + | ((x>>8) & 0xff00) + | ((x<<8) & 0xff0000) + | (x<<24)); +} + +inline int BigLong (int x) +{ + return (int)( + (((unsigned int)x)>>24) + | ((((unsigned int)x)>>8) & 0xff00) + | ((((unsigned int)x)<<8) & 0xff0000) + | (((unsigned int)x)<<24)); +} + +#endif + +#endif // __BIG_ENDIAN__ + +// These may be destructive so they should create errors +unsigned long BigLong(unsigned long) = delete; +long BigLong(long) = delete; +unsigned long LittleLong(unsigned long) = delete; +long LittleLong(long) = delete; + + +// Data accessors, since some data is highly likely to be unaligned. +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) +inline int GetShort(const unsigned char *foo) +{ + return *(const short *)foo; +} +inline int GetInt(const unsigned char *foo) +{ + return *(const int *)foo; +} +#else +inline int GetShort(const unsigned char *foo) +{ + return short(foo[0] | (foo[1] << 8)); +} +inline int GetInt(const unsigned char *foo) +{ + return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24)); +} +#endif +inline int GetBigInt(const unsigned char *foo) +{ + return int((foo[0] << 24) | (foo[1] << 16) | (foo[2] << 8) | foo[3]); +} + +#ifdef __BIG_ENDIAN__ +inline int GetNativeInt(const unsigned char *foo) +{ + return GetBigInt(foo); +} +#else +inline int GetNativeInt(const unsigned char *foo) +{ + return GetInt(foo); +} +#endif + +#endif // __M_SWAP_H__ diff --git a/source/common/utility/tarray.h b/source/common/utility/tarray.h new file mode 100644 index 000000000..d8b3defc6 --- /dev/null +++ b/source/common/utility/tarray.h @@ -0,0 +1,1563 @@ +#pragma once +/* +** tarray.h +** Templated, automatically resizing array +** +**--------------------------------------------------------------------------- +** Copyright 1998-2007 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + + +#include +#include +#include +#include +#include +#include + +#if !defined(_WIN32) +#include // for intptr_t +#else +#include // for mingw +#endif + +template class TIterator : public std::iterator +{ +public: + typedef typename TIterator::value_type value_type; + typedef typename TIterator::difference_type difference_type; + typedef typename TIterator::pointer pointer; + typedef typename TIterator::reference reference; + typedef typename TIterator::iterator_category iterator_category; + + TIterator(T* ptr = nullptr) { m_ptr = ptr; } + + // Comparison operators + bool operator==(const TIterator &other) const { return m_ptr == other.m_ptr; } + bool operator!=(const TIterator &other) const { return m_ptr != other.m_ptr; } + bool operator< (const TIterator &other) const { return m_ptr < other.m_ptr; } + bool operator<=(const TIterator &other) const { return m_ptr <= other.m_ptr; } + bool operator> (const TIterator &other) const { return m_ptr > other.m_ptr; } + bool operator>=(const TIterator &other) const { return m_ptr >= other.m_ptr; } + + // Arithmetic operators + TIterator &operator++() { ++m_ptr; return *this; } + TIterator operator++(int) { pointer tmp = m_ptr; ++*this; return TIterator(tmp); } + TIterator &operator--() { --m_ptr; return *this; } + TIterator operator--(int) { pointer tmp = m_ptr; --*this; return TIterator(tmp); } + TIterator &operator+=(difference_type offset) { m_ptr += offset; return *this; } + TIterator operator+(difference_type offset) const { return TIterator(m_ptr + offset); } + friend TIterator operator+(difference_type offset, const TIterator &other) { return TIterator(offset + other.m_ptr); } + TIterator &operator-=(difference_type offset) { m_ptr -= offset; return *this; } + TIterator operator-(difference_type offset) const { return TIterator(m_ptr - offset); } + difference_type operator-(const TIterator &other) const { return m_ptr - other.m_ptr; } + + T &operator*() { return *m_ptr; } + const T &operator*() const { return *m_ptr; } + T* operator->() { return m_ptr; } + +protected: + T* m_ptr; +}; + + +// TArray ------------------------------------------------------------------- + +// Must match TArray's layout. +struct FArray +{ + void *Array; + unsigned int Count; + unsigned int Most; +}; + +// T is the type stored in the array. +// TT is the type returned by operator(). +template +class TArray +{ +public: + + typedef TIterator iterator; + typedef TIterator const_iterator; + typedef T value_type; + + iterator begin() + { + return &Array[0]; + } + const_iterator begin() const + { + return &Array[0]; + } + const_iterator cbegin() const + { + return &Array[0]; + } + + iterator end() + { + return &Array[Count]; + } + const_iterator end() const + { + return &Array[Count]; + } + const_iterator cend() const + { + return &Array[Count]; + } + + + + //////// + // This is a dummy constructor that does nothing. The purpose of this + // is so you can create a global TArray in the data segment that gets + // used by code before startup without worrying about the constructor + // resetting it after it's already been used. You MUST NOT use it for + // heap- or stack-allocated TArrays. + enum ENoInit + { + NoInit + }; + TArray (ENoInit dummy) + { + } + //////// + TArray () + { + Most = 0; + Count = 0; + Array = NULL; + } + explicit TArray (size_t max, bool reserve = false) + { + Most = (unsigned)max; + Count = (unsigned)(reserve? max : 0); + Array = (T *)malloc (sizeof(T)*max); + if (reserve && Count > 0) + { + ConstructEmpty(0, Count - 1); + } + } + TArray (const TArray &other) + { + DoCopy (other); + } + TArray (TArray &&other) + { + Array = other.Array; other.Array = NULL; + Most = other.Most; other.Most = 0; + Count = other.Count; other.Count = 0; + } + TArray &operator= (const TArray &other) + { + if (&other != this) + { + if (Array != NULL) + { + if (Count > 0) + { + DoDelete (0, Count-1); + } + free (Array); + } + DoCopy (other); + } + return *this; + } + TArray &operator= (TArray &&other) + { + if (Array) + { + if (Count > 0) + { + DoDelete (0, Count-1); + } + free (Array); + } + Array = other.Array; other.Array = NULL; + Most = other.Most; other.Most = 0; + Count = other.Count; other.Count = 0; + return *this; + } + ~TArray () + { + if (Array) + { + if (Count > 0) + { + DoDelete (0, Count-1); + } + free (Array); + Array = NULL; + Count = 0; + Most = 0; + } + } + // Check equality of two arrays + bool operator==(const TArray &other) const + { + if (Count != other.Count) + { + return false; + } + for (unsigned int i = 0; i < Count; ++i) + { + if (Array[i] != other.Array[i]) + { + return false; + } + } + return true; + } + // Return a reference to an element + T &operator[] (size_t index) const + { + return Array[index]; + } + // Returns the value of an element + TT operator() (size_t index) const + { + return Array[index]; + } + // Returns a reference to the last element + T &Last() const + { + return Array[Count-1]; + } + + // returns address of first element + T *Data() const + { + return &Array[0]; + } + + unsigned int Find(const T& item) const + { + unsigned int i; + for(i = 0;i < Count;++i) + { + if(Array[i] == item) + break; + } + return i; + } + + template + unsigned int FindEx(Func compare) const + { + unsigned int i; + for (i = 0; i < Count; ++i) + { + if (compare(Array[i])) + break; + } + return i; + } + + unsigned int Push (const T &item) + { + Grow (1); + ::new((void*)&Array[Count]) T(item); + return Count++; + } + + unsigned int Push(T &&item) + { + Grow(1); + ::new((void*)&Array[Count]) T(std::move(item)); + return Count++; + } + + unsigned Append(const TArray &item) + { + unsigned start = Count; + + Grow(item.Size()); + Count += item.Size(); + + for (unsigned i = 0; i < item.Size(); i++) + { + new(&Array[start + i]) T(item[i]); + } + return start; + } + + unsigned Append(TArray &&item) + { + unsigned start = Count; + + Grow(item.Size()); + Count += item.Size(); + + for (unsigned i = 0; i < item.Size(); i++) + { + new(&Array[start + i]) T(std::move(item[i])); + } + return start; + } + + bool Pop () + { + if (Count > 0) + { + Array[--Count].~T(); + return true; + } + return false; + } + bool Pop (T &item) + { + if (Count > 0) + { + item = Array[--Count]; + Array[Count].~T(); + return true; + } + return false; + } + void Delete (unsigned int index) + { + if (index < Count) + { + Array[index].~T(); + if (index < --Count) + { + memmove (&Array[index], &Array[index+1], sizeof(T)*(Count - index)); + } + } + } + + void Delete (unsigned int index, int deletecount) + { + if (index + deletecount > Count) + { + deletecount = Count - index; + } + if (deletecount > 0) + { + for (int i = 0; i < deletecount; i++) + { + Array[index + i].~T(); + } + Count -= deletecount; + if (index < Count) + { + memmove (&Array[index], &Array[index+deletecount], sizeof(T)*(Count - index)); + } + } + } + + // Inserts an item into the array, shifting elements as needed + void Insert (unsigned int index, const T &item) + { + if (index >= Count) + { + // Inserting somewhere past the end of the array, so we can + // just add it without moving things. + Resize (index + 1); + ::new ((void *)&Array[index]) T(item); + } + else + { + // Inserting somewhere in the middle of the array, + // so make room for it + Resize (Count + 1); + + // Now move items from the index and onward out of the way + memmove (&Array[index+1], &Array[index], sizeof(T)*(Count - index - 1)); + + // And put the new element in + ::new ((void *)&Array[index]) T(item); + } + } + + void ShrinkToFit () + { + if (Most > Count) + { + Most = Count; + if (Most == 0) + { + if (Array != NULL) + { + free (Array); + Array = NULL; + } + } + else + { + DoResize (); + } + } + } + // Grow Array to be large enough to hold amount more entries without + // further growing. + void Grow (unsigned int amount) + { + if (Count + amount > Most) + { + const unsigned int choicea = Count + amount; + const unsigned int choiceb = Most = (Most >= 16) ? Most + Most / 2 : 16; + Most = (choicea > choiceb ? choicea : choiceb); + DoResize (); + } + } + // Resize Array so that it has exactly amount entries in use. + void Resize (unsigned int amount) + { + if (Count < amount) + { + // Adding new entries + Grow (amount - Count); + ConstructEmpty(Count, amount - 1); + } + else if (Count != amount) + { + // Deleting old entries + DoDelete (amount, Count - 1); + } + Count = amount; + } + // Ensures that the array has at most amount entries. + // Useful in cases where the initial allocation may be larger than the final result. + // Resize would create a lot of unneeded code in those cases. + void Clamp(unsigned int amount) + { + if (Count > amount) + { + // Deleting old entries + DoDelete(amount, Count - 1); + Count = amount; + } + } + void Alloc(unsigned int amount) + { + // first destroys all content and then rebuilds the array. + if (Count > 0) DoDelete(0, Count - 1); + Count = 0; + Resize(amount); + ShrinkToFit(); + } + // Reserves amount entries at the end of the array, but does nothing + // with them. + unsigned int Reserve (size_t amount) + { + Grow ((unsigned)amount); + unsigned int place = Count; + Count += (unsigned)amount; + if (Count > 0) ConstructEmpty(place, Count - 1); + return place; + } + unsigned int Size () const + { + return Count; + } + unsigned int Max () const + { + return Most; + } + void Clear () + { + if (Count > 0) + { + DoDelete (0, Count-1); + Count = 0; + } + } + void Reset() + { + Clear(); + Most = 0; + if (Array != nullptr) + { + free(Array); + Array = nullptr; + } + } + + void Swap(TArray &other) + { + std::swap(Array, other.Array); + std::swap(Count, other.Count); + std::swap(Most, other.Most); + } + +private: + T *Array; + unsigned int Count; + unsigned int Most; + + void DoCopy (const TArray &other) + { + Most = Count = other.Count; + if (Count != 0) + { + Array = (T *)malloc (sizeof(T)*Most); + for (unsigned int i = 0; i < Count; ++i) + { + ::new(&Array[i]) T(other.Array[i]); + } + } + else + { + Array = NULL; + } + } + + void DoResize () + { + size_t allocsize = sizeof(T)*Most; + Array = (T *)realloc (Array, allocsize); + } + + void DoDelete (unsigned int first, unsigned int last) + { + assert (last != ~0u); + for (unsigned int i = first; i <= last; ++i) + { + Array[i].~T(); + } + } + + void ConstructEmpty(unsigned int first, unsigned int last) + { + assert(last != ~0u); + for (unsigned int i = first; i <= last; ++i) + { + ::new(&Array[i]) T; + } + } +}; + +// TDeletingArray ----------------------------------------------------------- +// An array that deletes its elements when it gets deleted. +template +class TDeletingArray : public TArray +{ +public: + TDeletingArray() : TArray() {} + TDeletingArray(TDeletingArray &&other) : TArray(std::move(other)) {} + TDeletingArray &operator=(TDeletingArray &&other) + { + TArray::operator=(std::move(other)); + return *this; + } + + ~TDeletingArray () + { + for (unsigned int i = 0; i < TArray::Size(); ++i) + { + if ((*this)[i] != NULL) + delete (*this)[i]; + } + } + void DeleteAndClear() + { + for (unsigned int i = 0; i < TArray::Size(); ++i) + { + if ((*this)[i] != NULL) + delete (*this)[i]; + } + this->Clear(); + } +}; + +// This is only used for exposing the sector's Lines array to ZScript. +// Unlike TArrayView, its members are public as needed by the map loader. + +template +class TStaticPointedArray +{ +public: + + typedef TIterator iterator; + typedef TIterator const_iterator; + + iterator begin() + { + return &Array[0]; + } + const_iterator begin() const + { + return &Array[0]; + } + const_iterator cbegin() const + { + return &Array[0]; + } + + iterator end() + { + return &Array[Count]; + } + const_iterator end() const + { + return &Array[Count]; + } + const_iterator cend() const + { + return &Array[Count]; + } + + void Init(T *ptr, unsigned cnt) + { + Array = ptr; + Count = cnt; + } + // Return a reference to an element + T &operator[] (size_t index) const + { + return Array[index]; + } + T &At(size_t index) const + { + return Array[index]; + } + unsigned int Size() const + { + return Count; + } + // Some code needs to access these directly so they cannot be private. + T *Array; + unsigned int Count; +}; + +// TAutoGrowArray ----------------------------------------------------------- +// An array with accessors that automatically grow the array as needed. +// It can still be used as a normal TArray if needed. ACS uses this for +// world and global arrays. + +template +class TAutoGrowArray : public TArray +{ +public: + T GetVal (unsigned int index) + { + if (index >= this->Size()) + { + return 0; + } + return (*this)[index]; + } + void SetVal (unsigned int index, T val) + { + if ((int)index < 0) return; // These always result in an out of memory condition. + + if (index >= this->Size()) + { + this->Resize (index + 1); + } + (*this)[index] = val; + } +}; + +// TMap --------------------------------------------------------------------- +// An associative array, similar in concept to the STL extension +// class hash_map. It is implemented using Lua's table algorithm: +/* +** Hash uses a mix of chained scatter table with Brent's variation. +** A main invariant of these tables is that, if an element is not +** in its main position (i.e. the `original' position that its hash gives +** to it), then the colliding element is in its own main position. +** Hence even when the load factor reaches 100%, performance remains good. +*/ +/****************************************************************************** +* Copyright (C) 1994-2006 Lua.org, PUC-Rio. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ + +typedef unsigned int hash_t; + +template struct THashTraits +{ + // Returns the hash value for a key. + hash_t Hash(const KT key) { return (hash_t)(intptr_t)key; } + hash_t Hash(double key) + { + hash_t keyhash[2]; + memcpy(&keyhash, &key, sizeof(keyhash)); + return keyhash[0] ^ keyhash[1]; + } + + // Compares two keys, returning zero if they are the same. + int Compare(const KT left, const KT right) { return left != right; } +}; + +template<> struct THashTraits +{ + // Use all bits when hashing singles instead of converting them to ints. + hash_t Hash(float key) + { + hash_t keyhash; + memcpy(&keyhash, &key, sizeof(keyhash)); + return keyhash; + } + int Compare(float left, float right) { return left != right; } +}; + +template<> struct THashTraits +{ + // Use all bits when hashing doubles instead of converting them to ints. + hash_t Hash(double key) + { + hash_t keyhash[2]; + memcpy(&keyhash, &key, sizeof(keyhash)); + return keyhash[0] ^ keyhash[1]; + } + int Compare(double left, double right) { return left != right; } +}; + +template struct TValueTraits +{ + // Initializes a value for TMap. If a regular constructor isn't + // good enough, you can override it. + void Init(VT &value) + { + ::new(&value) VT; + } +}; + +// Must match layout of TMap +struct FMap +{ + void *Nodes; + void *LastFree; + hash_t Size; + hash_t NumUsed; +}; + + +template class TMapIterator; +template class TMapConstIterator; + +template, class ValueTraits=TValueTraits > +class TMap +{ + template friend class TMapIterator; + template friend class TMapConstIterator; + +public: + typedef class TMap MyType; + typedef class TMapIterator Iterator; + typedef class TMapConstIterator ConstIterator; + typedef struct { const KT Key; VT Value; } Pair; + typedef const Pair ConstPair; + + TMap() { NumUsed = 0; SetNodeVector(1); } + TMap(hash_t size) { NumUsed = 0; SetNodeVector(size); } + ~TMap() { ClearNodeVector(); } + + TMap(const TMap &o) + { + NumUsed = 0; + SetNodeVector(o.CountUsed()); + CopyNodes(o.Nodes, o.Size); + } + + TMap &operator= (const TMap &o) + { + NumUsed = 0; + ClearNodeVector(); + SetNodeVector(o.CountUsed()); + CopyNodes(o.Nodes, o.Size); + return *this; + } + + //======================================================================= + // + // TransferFrom + // + // Moves the contents from one TMap to another, leaving the TMap moved + // from empty. + // + //======================================================================= + + void TransferFrom(TMap &o) + { + // Clear all our nodes. + NumUsed = 0; + ClearNodeVector(); + + // Copy all of o's nodes. + Nodes = o.Nodes; + LastFree = o.LastFree; + Size = o.Size; + NumUsed = o.NumUsed; + + // Tell o it doesn't have any nodes. + o.Nodes = NULL; + o.Size = 0; + o.LastFree = NULL; + o.NumUsed = 0; + + // Leave o functional with one empty node. + o.SetNodeVector(1); + } + + //======================================================================= + // + // Clear + // + // Empties out the table and resizes it with room for count entries. + // + //======================================================================= + + void Clear(hash_t count=1) + { + ClearNodeVector(); + SetNodeVector(count); + } + + //======================================================================= + // + // CountUsed + // + // Returns the number of entries in use in the table. + // + //======================================================================= + + hash_t CountUsed() const + { +#ifdef _DEBUG + hash_t used = 0; + hash_t ct = Size; + for (Node *n = Nodes; ct-- > 0; ++n) + { + if (!n->IsNil()) + { + ++used; + } + } + assert (used == NumUsed); +#endif + return NumUsed; + } + + //======================================================================= + // + // operator[] + // + // Returns a reference to the value associated with a particular key, + // creating the pair if the key isn't already in the table. + // + //======================================================================= + + VT &operator[] (const KT key) + { + return GetNode(key)->Pair.Value; + } + + const VT &operator[] (const KT key) const + { + return GetNode(key)->Pair.Value; + } + + //======================================================================= + // + // CheckKey + // + // Returns a pointer to the value associated with a particular key, or + // NULL if the key isn't in the table. + // + //======================================================================= + + VT *CheckKey (const KT key) + { + Node *n = FindKey(key); + return n != NULL ? &n->Pair.Value : NULL; + } + + const VT *CheckKey (const KT key) const + { + const Node *n = FindKey(key); + return n != NULL ? &n->Pair.Value : NULL; + } + + //======================================================================= + // + // Insert + // + // Adds a key/value pair to the table if key isn't in the table, or + // replaces the value for the existing pair if the key is in the table. + // + // This is functionally equivalent to (*this)[key] = value; but can be + // slightly faster if the pair needs to be created because it doesn't run + // the constructor on the value part twice. + // + //======================================================================= + + VT &Insert(const KT key, const VT &value) + { + Node *n = FindKey(key); + if (n != NULL) + { + n->Pair.Value = value; + } + else + { + n = NewKey(key); + ::new(&n->Pair.Value) VT(value); + } + return n->Pair.Value; + } + + VT &Insert(const KT key, VT &&value) + { + Node *n = FindKey(key); + if (n != NULL) + { + n->Pair.Value = value; + } + else + { + n = NewKey(key); + ::new(&n->Pair.Value) VT(value); + } + return n->Pair.Value; + } + + VT &InsertNew(const KT key) + { + Node *n = FindKey(key); + if (n != NULL) + { + n->Pair.Value.~VT(); + } + else + { + n = NewKey(key); + } + ::new(&n->Pair.Value) VT; + return n->Pair.Value; + } + + //======================================================================= + // + // Remove + // + // Removes the key/value pair for a particular key if it is in the table. + // + //======================================================================= + + void Remove(const KT key) + { + DelKey(key); + } + + void Swap(MyType &other) + { + std::swap(Nodes, other.Nodes); + std::swap(LastFree, other.LastFree); + std::swap(Size, other.Size); + std::swap(NumUsed, other.NumUsed); + } + +protected: + struct IPair // This must be the same as Pair above, but with a + { // non-const Key. + KT Key; + VT Value; + }; + struct Node + { + Node *Next; + IPair Pair; + void SetNil() + { + Next = (Node *)1; + } + bool IsNil() const + { + return Next == (Node *)1; + } + }; + + /* This is used instead of memcpy, because Node is likely to be small, + * such that the time spent calling a function would eclipse the time + * spent copying. */ + struct NodeSizedStruct { unsigned char Pads[sizeof(Node)]; }; + + Node *Nodes; + Node *LastFree; /* any free position is before this position */ + hash_t Size; /* must be a power of 2 */ + hash_t NumUsed; + + const Node *MainPosition(const KT k) const + { + HashTraits Traits; + return &Nodes[Traits.Hash(k) & (Size - 1)]; + } + + Node *MainPosition(const KT k) + { + HashTraits Traits; + return &Nodes[Traits.Hash(k) & (Size - 1)]; + } + + void SetNodeVector(hash_t size) + { + // Round size up to nearest power of 2 + for (Size = 1; Size < size; Size <<= 1) + { } + Nodes = (Node *)malloc(Size * sizeof(Node)); + LastFree = &Nodes[Size]; /* all positions are free */ + for (hash_t i = 0; i < Size; ++i) + { + Nodes[i].SetNil(); + } + } + + void ClearNodeVector() + { + for (hash_t i = 0; i < Size; ++i) + { + if (!Nodes[i].IsNil()) + { + Nodes[i].~Node(); + } + } + free(Nodes); + Nodes = NULL; + Size = 0; + LastFree = NULL; + NumUsed = 0; + } + + void Resize(hash_t nhsize) + { + hash_t i, oldhsize = Size; + Node *nold = Nodes; + /* create new hash part with appropriate size */ + SetNodeVector(nhsize); + /* re-insert elements from hash part */ + NumUsed = 0; + for (i = 0; i < oldhsize; ++i) + { + if (!nold[i].IsNil()) + { + Node *n = NewKey(nold[i].Pair.Key); + ::new(&n->Pair.Value) VT(std::move(nold[i].Pair.Value)); + nold[i].~Node(); + } + } + free(nold); + } + + void Rehash() + { + Resize (Size << 1); + } + + Node *GetFreePos() + { + while (LastFree-- > Nodes) + { + if (LastFree->IsNil()) + { + return LastFree; + } + } + return NULL; /* could not find a free place */ + } + + /* + ** Inserts a new key into a hash table; first, check whether key's main + ** position is free. If not, check whether colliding node is in its main + ** position or not: if it is not, move colliding node to an empty place and + ** put new key in its main position; otherwise (colliding node is in its main + ** position), new key goes to an empty position. + ** + ** The Value field is left unconstructed. + */ + Node *NewKey(const KT key) + { + Node *mp = MainPosition(key); + if (!mp->IsNil()) + { + Node *othern; + Node *n = GetFreePos(); /* get a free place */ + if (n == NULL) /* cannot find a free place? */ + { + Rehash(); /* grow table */ + return NewKey(key); /* re-insert key into grown table */ + } + othern = MainPosition(mp->Pair.Key); + if (othern != mp) /* is colliding node out of its main position? */ + { /* yes; move colliding node into free position */ + while (othern->Next != mp) /* find previous */ + { + othern = othern->Next; + } + othern->Next = n; /* redo the chain with 'n' in place of 'mp' */ + CopyNode(n, mp); /* copy colliding node into free pos. (mp->Next also goes) */ + mp->Next = NULL; /* now 'mp' is free */ + } + else /* colliding node is in its own main position */ + { /* new node will go into free position */ + n->Next = mp->Next; /* chain new position */ + mp->Next = n; + mp = n; + } + } + else + { + mp->Next = NULL; + } + ++NumUsed; + ::new(&mp->Pair.Key) KT(key); + return mp; + } + + void DelKey(const KT key) + { + Node *mp = MainPosition(key), **mpp; + HashTraits Traits; + + if (mp->IsNil()) + { + /* the key is definitely not present, because there is nothing at its main position */ + } + else if (!Traits.Compare(mp->Pair.Key, key)) /* the key is in its main position */ + { + if (mp->Next != NULL) /* move next node to its main position */ + { + Node *n = mp->Next; + mp->~Node(); /* deconstruct old node */ + CopyNode(mp, n); /* copy next node */ + n->SetNil(); /* next node is now nil */ + } + else + { + mp->~Node(); + mp->SetNil(); /* there is no chain, so main position is nil */ + } + --NumUsed; + } + else /* the key is either not present or not in its main position */ + { + for (mpp = &mp->Next, mp = *mpp; mp != NULL && Traits.Compare(mp->Pair.Key, key); mpp = &mp->Next, mp = *mpp) + { } /* look for the key */ + if (mp != NULL) /* found it */ + { + *mpp = mp->Next; /* rechain so this node is skipped */ + mp->~Node(); + mp->SetNil(); /* because this node is now nil */ + --NumUsed; + } + } + } + + Node *FindKey(const KT key) + { + HashTraits Traits; + Node *n = MainPosition(key); + while (n != NULL && !n->IsNil() && Traits.Compare(n->Pair.Key, key)) + { + n = n->Next; + } + return n == NULL || n->IsNil() ? NULL : n; + } + + const Node *FindKey(const KT key) const + { + HashTraits Traits; + const Node *n = MainPosition(key); + while (n != NULL && !n->IsNil() && Traits.Compare(n->Pair.Key, key)) + { + n = n->Next; + } + return n == NULL || n->IsNil() ? NULL : n; + } + + Node *GetNode(const KT key) + { + Node *n = FindKey(key); + if (n != NULL) + { + return n; + } + n = NewKey(key); + ValueTraits traits; + traits.Init(n->Pair.Value); + return n; + } + + /* Perform a bit-wise copy of the node. Used when relocating a node in the table. */ + void CopyNode(Node *dst, const Node *src) + { + *(NodeSizedStruct *)dst = *(const NodeSizedStruct *)src; + } + + /* Copy all nodes in the node vector to this table. */ + void CopyNodes(const Node *nodes, hash_t numnodes) + { + for (; numnodes-- > 0; ++nodes) + { + if (!nodes->IsNil()) + { + Node *n = NewKey(nodes->Pair.Key); + ::new(&n->Pair.Value) VT(nodes->Pair.Value); + } + } + } +}; + +// TMapIterator ------------------------------------------------------------- +// A class to iterate over all the pairs in a TMap. + +template > +class TMapIterator +{ +public: + TMapIterator(MapType &map) + : Map(map), Position(0) + { + } + + //======================================================================= + // + // NextPair + // + // Returns false if there are no more entries in the table. Otherwise, it + // returns true, and pair is filled with a pointer to the pair in the + // table. + // + //======================================================================= + + bool NextPair(typename MapType::Pair *&pair) + { + if (Position >= Map.Size) + { + return false; + } + do + { + if (!Map.Nodes[Position].IsNil()) + { + pair = reinterpret_cast(&Map.Nodes[Position].Pair); + Position += 1; + return true; + } + } while (++Position < Map.Size); + return false; + } + + //======================================================================= + // + // Reset + // + // Restarts the iteration so you can do it all over again. + // + //======================================================================= + + void Reset() + { + Position = 0; + } + +protected: + MapType ⤅ + hash_t Position; +}; + +// TMapConstIterator -------------------------------------------------------- +// Exactly the same as TMapIterator, but it works with a const TMap. + +template > +class TMapConstIterator +{ +public: + TMapConstIterator(const MapType &map) + : Map(map), Position(0) + { + } + + bool NextPair(typename MapType::ConstPair *&pair) + { + if (Position >= Map.Size) + { + return false; + } + do + { + if (!Map.Nodes[Position].IsNil()) + { + pair = reinterpret_cast(&Map.Nodes[Position].Pair); + Position += 1; + return true; + } + } while (++Position < Map.Size); + return false; + } + +protected: + const MapType ⤅ + hash_t Position; +}; + + + +//========================================================================== +// +// an array to hold a small number of unique entries +// +//========================================================================== + +template class UniqueList +{ + TArray Array; + +public: + + T * Get(T * t) + { + for (unsigned i = 0; i bytes; + unsigned size; + +public: + void Resize(unsigned elem) + { + bytes.Resize((elem + 7) / 8); + size = elem; + } + + BitArray() : size(0) + { + } + + BitArray(const BitArray & arr) + { + bytes = arr.bytes; + size = arr.size; + } + + BitArray &operator=(const BitArray & arr) + { + bytes = arr.bytes; + size = arr.size; + return *this; + } + + BitArray(BitArray && arr) + { + bytes = std::move(arr.bytes); + size = arr.size; + arr.size = 0; + } + + BitArray &operator=(BitArray && arr) + { + bytes = std::move(arr.bytes); + size = arr.size; + arr.size = 0; + return *this; + } + + bool operator[](size_t index) const + { + return !!(bytes[index >> 3] & (1 << (index & 7))); + } + + void Set(size_t index) + { + bytes[index >> 3] |= (1 << (index & 7)); + } + + void Clear(size_t index) + { + bytes[index >> 3] &= ~(1 << (index & 7)); + } + + unsigned Size() const + { + return size; + } + + void Zero() + { + memset(&bytes[0], 0, bytes.Size()); + } +}; + + +// A wrapper to externally stored data. +// I would have expected something for this in the stl, but std::span is only in C++20. +template +class TArrayView +{ +public: + + typedef TIterator iterator; + typedef TIterator const_iterator; + + iterator begin() + { + return &Array[0]; + } + const_iterator begin() const + { + return &Array[0]; + } + const_iterator cbegin() const + { + return &Array[0]; + } + + iterator end() + { + return &Array[Count]; + } + const_iterator end() const + { + return &Array[Count]; + } + const_iterator cend() const + { + return &Array[Count]; + } + + + //////// + TArrayView() = default; // intended to keep this type trivial. + TArrayView(T *data, unsigned count = 0) + { + Count = count; + Array = data; + } + TArrayView(const TArrayView &other) = default; + TArrayView &operator= (const TArrayView &other) = default; + + // Check equality of two arrays + bool operator==(const TArrayView &other) const + { + if (Count != other.Count) + { + return false; + } + for (unsigned int i = 0; i < Count; ++i) + { + if (Array[i] != other.Array[i]) + { + return false; + } + } + return true; + } + // Return a reference to an element + T &operator[] (size_t index) const + { + return Array[index]; + } + // Returns a reference to the last element + T &Last() const + { + return Array[Count - 1]; + } + + // returns address of first element + T *Data() const + { + return &Array[0]; + } + + unsigned Size() const + { + return Count; + } + + unsigned int Find(const T& item) const + { + unsigned int i; + for (i = 0; i < Count; ++i) + { + if (Array[i] == item) + break; + } + return i; + } + + void Set(T *data, unsigned count) + { + Array = data; + Count = count; + } + + void Clear() + { + Count = 0; + Array = nullptr; + } +private: + T *Array; + unsigned int Count; +}; diff --git a/source/common/utility/templates.h b/source/common/utility/templates.h new file mode 100644 index 000000000..294b3ebc5 --- /dev/null +++ b/source/common/utility/templates.h @@ -0,0 +1,18 @@ + +#pragma once + +// we do not want C++17 just for this one function... + +//========================================================================== +// +// clamp +// +// Clamps in to the range [min,max]. +//========================================================================== + +template +inline +T clamp (const T in, const T min, const T max) +{ + return in <= min ? min : in >= max ? max : in; +}