diff --git a/doomsday/libdeng2/data.pri b/doomsday/libdeng2/data.pri index df32082ca4..59389549e1 100644 --- a/doomsday/libdeng2/data.pri +++ b/doomsday/libdeng2/data.pri @@ -5,6 +5,7 @@ HEADERS += \ include/de/Audience \ include/de/BigEndianByteOrder \ include/de/BinaryTree \ + include/de/BitField \ include/de/Block \ include/de/BlockValue \ include/de/ByteOrder \ @@ -52,6 +53,7 @@ HEADERS += \ include/de/data/archive.h \ include/de/data/arrayvalue.h \ include/de/data/binarytree.h \ + include/de/data/bitfield.h \ include/de/data/block.h \ include/de/data/blockvalue.h \ include/de/data/byteorder.h \ @@ -101,6 +103,7 @@ SOURCES += \ src/data/archive.cpp \ src/data/arrayvalue.cpp \ src/data/binarytree_wrapper.cpp \ + src/data/bitfield.cpp \ src/data/block.cpp \ src/data/blockvalue.cpp \ src/data/byteorder.cpp \ diff --git a/doomsday/libdeng2/include/de/BitField b/doomsday/libdeng2/include/de/BitField new file mode 100644 index 0000000000..6395cbe7f7 --- /dev/null +++ b/doomsday/libdeng2/include/de/BitField @@ -0,0 +1 @@ +#include "data/bitfield.h" diff --git a/doomsday/libdeng2/include/de/data/bitfield.h b/doomsday/libdeng2/include/de/data/bitfield.h new file mode 100644 index 0000000000..b02f364de7 --- /dev/null +++ b/doomsday/libdeng2/include/de/data/bitfield.h @@ -0,0 +1,131 @@ +/** @file bitfield.h Array of integers packed together. + * + * @authors Copyright (c) 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef LIBDENG2_BITFIELD_H +#define LIBDENG2_BITFIELD_H + +#include +#include +#include + +#include + +namespace de { + +/** + * Array of integer values packed tightly together. + */ +class DENG2_PUBLIC BitField +{ +public: + typedef dint Id; + struct Spec { + Id id; ///< User-provided identifier for the element. + int numBits; ///< 32 bits at most. + }; + typedef QSet Ids; + + /// Failure to compare two fields with each other. @ingroup errors + DENG2_ERROR(ComparisonError); + +public: + BitField(); + BitField(BitField const &other); + BitField(Block const &data); + + /** + * Removes all the elements and the data contained in the bit field. + */ + void clear(); + + /** + * Adds a new element into the field. + * + * @param id Identifier of the element. + * @param numBits Number of bits for the element. + * + * @return Reference to this pack. + */ + BitField &addElement(Id id, dsize numBits); + + void addElements(Spec const *elements, dsize count); + + void addElements(QList const &elements); + + /** + * Returns the number of elements in the bit field. + */ + int size() const; + + Spec element(int index) const; + + /** + * Total number of bits in the packed elements. + */ + int bitCount() const; + + /** + * Returns the packed data as an array of bytes. Only bitCount() bits are + * valid; the highest bits of the last byte may be unused (and zero). + */ + Block data() const; + + bool operator == (BitField const &other) const; + bool operator != (BitField const &other) const; + + /** + * Determines which elements in this pack are different when compared to @a + * other. The fields must use the same number of elements with the same + * specs. + * + * @param other Other bit field to compare against. + * + * @return List of element Ids whose values are different than the ones + * in @a other. + */ + Ids delta(BitField const &other) const; + + /** + * Returns the plain (unsigned integer) value of an element. + * + * @param id Element id. + * + * @return Value of the element as unsigned integer. + */ + duint operator [] (Id id) const { return asUInt(id); } + + void set(Id id, bool value); + void set(Id id, duint value); + + bool asBool(Id id) const; + duint asUInt(Id id) const; + + template + Type valueAs(Id id) const { + return Type(asUInt(id)); + } + + String asText() const; + +private: + DENG2_PRIVATE(d) +}; + +} // namespace de + +#endif // LIBDENG2_BITFIELD_H diff --git a/doomsday/libdeng2/src/data/bitfield.cpp b/doomsday/libdeng2/src/data/bitfield.cpp new file mode 100644 index 0000000000..fad7265a21 --- /dev/null +++ b/doomsday/libdeng2/src/data/bitfield.cpp @@ -0,0 +1,281 @@ +/** @file bitfield.cpp Array of integers packed together. + * + * @authors Copyright (c) 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/BitField" + +#include +#include + +namespace de { + +DENG2_PIMPL(BitField) +{ + struct Element + { + int numBits; + int firstBit; + }; + typedef QMap Elements; + + Elements elements; + dsize totalBits; + + /** + * Lookup table for quickly finding out which elements are on which bytes of + * the packed data. Indexed using the packed data byte index; size == + * packed size. + */ + QList lookup; + + Block packed; + + Instance(Public *i) : Base(i), totalBits(0) + {} + + Instance(Public *i, Instance const &other) + : Base (i), + elements (other.elements), + totalBits(other.totalBits), + lookup (other.lookup), + packed (other.packed) + {} + + Element &element(Id id) + { + DENG2_ASSERT(elements.contains(id)); + return elements[id]; + } + + void set(Id id, duint value) + { + Element &f = element(id); + int packedIdx = f.firstBit >> 3; + int shift = f.firstBit & 7; + int written = 0; + + while(written < f.numBits) + { + dbyte mask = 0xff; + if(f.numBits - written < 8) mask >>= 8 - (f.numBits - written); + mask <<= shift; + + dbyte pv = packed[packedIdx] & ~mask; + pv |= mask & ((value >> written) << shift); + packed[packedIdx] = pv; + + written += 8 - shift; + shift = 0; + packedIdx++; + } + } + + duint get(Id id) const + { + duint value = 0; + + DENG2_ASSERT(elements.contains(id)); + Element const &f = elements.constFind(id).value(); + + int packedIdx = f.firstBit >> 3; + int shift = f.firstBit & 7; + int read = 0; + + while(read < f.numBits) + { + dbyte mask = 0xff; + if(f.numBits - read < 8) mask >>= 8 - (f.numBits - read); + mask <<= shift; + + value |= ((packed[packedIdx] & mask) >> shift) << read; + + read += 8 - shift; + shift = 0; + packedIdx++; + } + + return value; + } +}; + +BitField::BitField() : d(new Instance(this)) +{} + +BitField::BitField(BitField const &other) : d(new Instance(this, *other.d)) +{} + +BitField::BitField(Block const &data) : d(new Instance(this)) +{ + d->packed = data; +} + +void BitField::clear() +{ + d->packed.clear(); + d->elements.clear(); + d->lookup.clear(); + d->totalBits = 0; +} + +BitField &BitField::addElement(Id id, dsize numBits) +{ + DENG2_ASSERT(numBits >= 1); + + Instance::Element elem; + elem.numBits = numBits; + elem.firstBit = d->totalBits; + d->elements.insert(id, elem); + d->totalBits += numBits; + + // Update the lookup table. + int pos = elem.firstBit / 8; + int endPos = (elem.firstBit + (numBits - 1)) / 8; + while(d->lookup.size() <= endPos) + { + d->lookup.append(Ids()); + } + for(int i = pos; i <= endPos; ++i) + { + d->lookup[i].insert(id); + } + + return *this; +} + +void BitField::addElements(Spec const *elements, dsize count) +{ + while(count-- > 0) + { + addElement(elements->id, elements->numBits); + elements++; + } +} + +void BitField::addElements(QList const &elements) +{ + foreach(Spec spec, elements) + { + addElement(spec.id, spec.numBits); + } +} + +int BitField::size() const +{ + return d->elements.size(); +} + +BitField::Spec BitField::element(int index) const +{ + DENG2_ASSERT(index >= 0); + DENG2_ASSERT(index < size()); + + Instance::Element elem = d->elements.values()[index]; + Spec spec; + spec.id = d->elements.keys()[index]; + spec.numBits = elem.numBits; + return spec; +} + +int BitField::bitCount() const +{ + return d->totalBits; +} + +Block BitField::data() const +{ + return d->packed; +} + +bool BitField::operator == (BitField const &other) const +{ + return d->packed == other.d->packed; +} + +bool BitField::operator != (BitField const &other) const +{ + return d->packed != other.d->packed; +} + +BitField::Ids BitField::delta(BitField const &other) const +{ + if(d->elements.size() != other.d->elements.size()) + { + throw ComparisonError("BitField::delta", + "The compared fields have a different number of elements"); + } + if(d->packed.size() != other.d->packed.size()) + { + throw ComparisonError("BitField::delta", + "The compared fields have incompatible element sizes"); + } + + Ids diffs; + for(duint pos = 0; pos < d->packed.size(); ++pos) + { + if(d->packed[pos] == other.d->packed[pos]) + continue; + + // The elements on this byte are different; which are they? + foreach(Id id, d->lookup[pos]) + { + if(diffs.contains(id)) + continue; // Already in the delta. + + if(asUInt(id) != other.asUInt(id)) + { + diffs.insert(id); + } + } + } + return diffs; +} + +void BitField::set(Id id, bool value) +{ + set(id, duint(value? 1 : 0)); +} + +void BitField::set(Id id, duint value) +{ + d->set(id, value); +} + +bool BitField::asBool(Id id) const +{ + return d->get(id)? true : false; +} + +duint BitField::asUInt(Id id) const +{ + return d->get(id); +} + +String BitField::asText() const +{ + QString str; + QTextStream os(&str); + os << "BitField (" << d->totalBits << " bits, " << d->elements.size() << " elements):"; + os.setIntegerBase(2); + for(int i = d->packed.size() - 1; i >= 0; --i) + { + os << " " << qSetPadChar('0') << qSetFieldWidth(8) << dbyte(d->packed[i]) + << qSetFieldWidth(0); + } + return str; +} + +} // namespace de diff --git a/doomsday/tests/bitfield/bitfield.pro b/doomsday/tests/bitfield/bitfield.pro new file mode 100644 index 0000000000..0c825efa01 --- /dev/null +++ b/doomsday/tests/bitfield/bitfield.pro @@ -0,0 +1,8 @@ +include(../config_test.pri) + +TEMPLATE = app +TARGET = test_bitfield + +SOURCES += main.cpp + +deployTest($$TARGET) diff --git a/doomsday/tests/bitfield/main.cpp b/doomsday/tests/bitfield/main.cpp new file mode 100644 index 0000000000..bc55914926 --- /dev/null +++ b/doomsday/tests/bitfield/main.cpp @@ -0,0 +1,92 @@ +/* + * The Doomsday Engine Project + * + * Copyright (c) 2013 Jaakko Keränen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include + +using namespace de; + +int main(int, char **) +{ + try + { + BitField pack; + pack.addElement(1, 1); + pack.set(1, duint(1)); + qDebug() << pack.asText().toLatin1().constData(); + + pack.clear(); + + pack.addElement(1, 1); + pack.addElement(2, 1); + pack.set(2, true); + qDebug() << pack.asText().toLatin1().constData(); + pack.set(1, true); + qDebug() << pack.asText().toLatin1().constData(); + + pack.addElement(3, 3); + pack.set(1, false); + qDebug() << pack.asText().toLatin1().constData(); + pack.set(3, 6u); + qDebug() << pack.asText().toLatin1().constData(); + + pack.addElement(10, 8); + pack.set(10, 149u); + qDebug() << pack.asText().toLatin1().constData(); + + qDebug() << "Field 1:" << pack[1]; + qDebug() << "Field 2:" << pack[2]; + qDebug() << "Field 3:" << pack[3]; + qDebug() << "Field 10:" << pack[10]; + + DENG2_ASSERT(pack[10] == 149); + + BitField pack2 = pack; + qDebug() << "Copied:" << pack2.asText().toLatin1().constData(); + + qDebug() << "Equal:" << (pack2 == pack? "true" : "false"); + qDebug() << "Delta:" << pack.delta(pack2); + + pack2.set(3, 3u); + qDebug() << "Modified:" << pack2.asText().toLatin1().constData(); + qDebug() << "Equal:" << (pack2 == pack? "true" : "false"); + qDebug() << "Delta:" << pack.delta(pack2); + + pack2.set(3, 6u); + pack2.set(10, 128u); + qDebug() << "Modified:" << pack2.asText().toLatin1().constData(); + qDebug() << "Field 10:" << pack2[10]; + qDebug() << "Equal:" << (pack2 == pack? "true" : "false"); + qDebug() << "Delta:" << pack.delta(pack2); + + pack2.set(1, true); + qDebug() << "Modified:" << pack2.asText().toLatin1().constData(); + qDebug() << "Equal:" << (pack2 == pack? "true" : "false"); + qDebug() << "Delta:" << pack.delta(pack2); + qDebug() << "Delta (reverse):" << pack2.delta(pack); + } + catch(Error const &err) + { + qWarning() << err.asText() << "\n"; + } + + qDebug() << "Exiting main()...\n"; + return 0; +} diff --git a/doomsday/tests/tests.pro b/doomsday/tests/tests.pro index 00c6839b5e..f7a49fcd47 100644 --- a/doomsday/tests/tests.pro +++ b/doomsday/tests/tests.pro @@ -7,6 +7,7 @@ TEMPLATE = subdirs deng_tests: SUBDIRS += \ archive \ + bitfield \ log \ record \ script \