diff -r cfcbf08528c4 -r 2b40d63a9c3d qtmobility/src/publishsubscribe/sharedmemorylayer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qtmobility/src/publishsubscribe/sharedmemorylayer.cpp Fri Apr 16 15:51:22 2010 +0300 @@ -0,0 +1,3394 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvaluespace_p.h" +#include "qmallocpool_p.h" +#include "qvaluespacepublisher.h" +#include "qsystemreadwritelock_p.h" +#include "qpacketprotocol_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +QTM_BEGIN_NAMESPACE + +#define VERSION_TABLE_ENTRIES 8191 +#define ROOT_VERSION_ENTRY 0 + +#define MAX_PATH_SIZE 16384 +#define MAX_DATA_SIZE 16384 +#define INVALID_HANDLE 0xFFFF + +#define ALIGN4b(n) ((n) + (((n) % 4)?(4 - ((n) % 4)):0)) + +// #define QVALUESPACE_UPDATE_STATS + +static inline QDataStream& operator<<(QDataStream& stream, unsigned long v) +{ + if (sizeof(unsigned long) == sizeof(uint)) { + stream << (uint)v; + return stream; + } else { + stream << (quint64)v; + return stream; + } +} + +static inline QDataStream& operator>>(QDataStream& stream, unsigned long& v) +{ + if (sizeof(unsigned long) == sizeof(uint)) { + uint v32; + stream >> v32; + v = (unsigned long)v32; + } else { + quint64 v64; + stream >> v64; + v = (unsigned long)v64; + } + return stream; +} + +static int vsmemcmp(const char * s1, int l1, const char * s2, int l2) +{ + if (l1 < l2) + return -1; + if (l1 > l2) + return 1; + return ::memcmp(s1, s2, l1); +} + +/////////////////////////////////////////////////////////////////////////////// +// declare VersionTable +/////////////////////////////////////////////////////////////////////////////// +struct VersionTable { + + struct Entry { + Entry(unsigned int v, unsigned int n) : version(v), nodePtr(n) {} + + unsigned int version; + union { + unsigned int nodePtr; + unsigned int nxtFreeBlk; // 0 means no next free block + // (as root is always 0) + }; + }; + + unsigned int nxtFreeBlk; + unsigned int nextCreationId; + + Entry entries[VERSION_TABLE_ENTRIES]; +}; + + +// declare NodeOwner +/* + Owner of data in a node. + + Overhead: 8 + */ +struct NodeOwner { + unsigned long data1; + unsigned long data2; +}; + + +// declare NodeDatum +/* + Encapsulates the data and data owner associated with a Node. + + Overhead: 12 + data size + */ +struct NodeDatum { + NodeOwner owner; + enum Type { Boolean = QVariant::Bool, + Int = QVariant::Int, + UInt = QVariant::UInt, + LongLong = QVariant::LongLong, + ULongLong = QVariant::ULongLong, + Double = QVariant::Double, + Char = QVariant::Char, + String = QVariant::String, + ByteArray = QVariant::ByteArray, + SerializedType /* Catch all for everything else */ }; + //Type type; + unsigned short type; + unsigned short len; + char data[0]; +}; + +// declare NodeWatch +/* + Node write watcher. + + Overhead: 8 +*/ +struct NodeWatch { + unsigned long data1; + unsigned long data2; +}; + +/////////////////////////////////////////////////////////////////////////////// +// declare Node +/////////////////////////////////////////////////////////////////////////////// +/* + In memory representation of a node. + + In memory, nodes are structured in one of the following three ways. + Virtual (No Data) Node: + + Single data node: + + Multiple data node: + = 2)> + With single watch: + + With multiple watch: + = 2)> + + Overhead: 16 bytes + name size + */ +struct Node { + unsigned short parent; /* Id for my parent */ + unsigned int creationId; /* Unique id set when I was constructed */ + + unsigned short subNodes; /* Sub node count */ + unsigned int subNodePtr; /* Ptr to malloc'd array of sub node ptrs */ + + unsigned short watchCount; + unsigned short dataCount; /* Amount of data associated with this node */ + unsigned short nameLen; /* Length of name in bytes */ + unsigned short dummy; /* dummy for 4 byte alignment */ + + char name[0]; /* My name - NO trailing null */ + + bool valid() { + return 0 != watchCount || 0 != dataCount || 0 != subNodes; + } + unsigned int dataBlockSize() + { + if(0 == dataCount) { + return 0; + } else if(1 == dataCount) { + NodeDatum * datum = (NodeDatum *)dataBegin(); + return sizeof(NodeDatum) + datum->len; + } else if(2 >= dataCount) { + return sizeof(unsigned int); + } + } + + unsigned int watchBlockSize() + { + if(0 == watchCount) + return 0; + else if(1 == watchCount) + return sizeof(NodeWatch); + else if(2 >= watchCount) + return sizeof(unsigned int); + return 0; + } + + unsigned int size() + { + unsigned int rv = sizeof(Node); + rv += ALIGN4b(nameLen); + if(watchCount == 1) { + rv += sizeof(NodeWatch); + } else if(watchCount >= 2) { + rv += sizeof(unsigned int); + } + + if(dataCount == 1) { + NodeDatum * datum = (NodeDatum *)dataBegin(); + rv += sizeof(NodeDatum) + ALIGN4b(datum->len); + } else if(dataCount >= 2) { + rv += sizeof(unsigned int); + } + + return rv; + } + + void * dataBegin() + { + return (void *)(name + ALIGN4b(nameLen)); + } + + void * watchBegin() + { + if(0 == dataCount) { + return dataBegin(); + } else if(1 == dataCount) { + NodeDatum * datum = (NodeDatum *)dataBegin(); + return datum->data + ALIGN4b(datum->len); + } else { + return name + ALIGN4b(nameLen) + sizeof(unsigned int); + } + } +}; + +bool operator==(const NodeOwner &lhs, const NodeOwner &rhs) +{ + return 0 == ::memcmp(&lhs, &rhs, sizeof(NodeOwner)); +} +bool operator!=(const NodeOwner &lhs, const NodeOwner &rhs) +{ + return 0 != ::memcmp(&lhs, &rhs, sizeof(NodeOwner)); +} +bool operator==(const NodeWatch &lhs, const NodeWatch &rhs) +{ + return 0 == ::memcmp(&lhs, &rhs, sizeof(NodeWatch)); +} +bool operator!=(const NodeWatch &lhs, const NodeWatch &rhs) +{ + return 0 != ::memcmp(&lhs, &rhs, sizeof(NodeWatch)); +} +static const NodeWatch INVALID_WATCH = {0,0}; +static const NodeOwner INVALID_OWNER = {0,0}; + +/////////////////////////////////////////////////////////////////////////////// +// declare FixedMemoryTree +/////////////////////////////////////////////////////////////////////////////// +class FixedMemoryTree : public QObject +{ + Q_OBJECT +public: + FixedMemoryTree(void * mem, unsigned int size, bool initMem); + + // Returns the closest node to the specified path + unsigned short findClosest(const char * path, + const char ** matched); + unsigned short findClosest(unsigned int from, + const char * subPath, + const char ** matched); + + NodeDatum * data(unsigned short node); + + bool addWatch(const char * path, NodeWatch owner); + bool remWatch(const char * path, NodeWatch owner); + + bool insert(const char * path, + NodeDatum::Type type, + const char * data, + unsigned int dataLen, + NodeOwner owner); + + unsigned short offsetOfSubNode(unsigned short node, + unsigned short subNode); + + bool remove(const char * path, NodeOwner owner); + + inline Node * getNode(unsigned int); + inline Node * subNode(unsigned int, unsigned short); + inline unsigned int version(unsigned int); + + inline void * fromPtr(unsigned int ptr); + +#ifdef QVALUESPACE_UPDATE_STATS + inline QMallocPool *mallocPool() const { return pool; } +#endif + + typedef void (*NodeChangeFunction)(unsigned short node, void *ctxt); + void setNodeChangeFunction(NodeChangeFunction, void *); + +private: + static unsigned int growListSize(unsigned int currentSize); + static unsigned int shrunkListSize(unsigned int currentSize, + unsigned int toSize); + + bool setWatch(unsigned short node, NodeWatch owner); + bool remWatch(unsigned short node, NodeWatch owner); + bool setData(unsigned short node, NodeOwner owner, + NodeDatum::Type type, const char * data, + unsigned int dataLen); + + void removeFrom(unsigned short from, unsigned short subNodeNumber); + + unsigned short findRecur(unsigned short node, const char * path, + const char ** consumed); + + bool insertRecur(unsigned short node, const char * path, + NodeDatum::Type type, const char * data, + unsigned int dataLen, NodeOwner owner); + + bool addWatchRecur(unsigned short node, const char * path, + NodeWatch owner); + + bool remWatchRecur(unsigned short node, const char * path, + NodeWatch owner); + bool removeRecur(unsigned short node, const char * path, + NodeOwner owner); + unsigned short removeRecur(unsigned short, NodeOwner owner); + + unsigned int locateInNode(Node * node, const char * name, + unsigned int len, bool * match); + + inline unsigned int ptr(void * mem); + inline VersionTable * versionTable(); + inline void bump(unsigned short node); + + unsigned short newNode(const char * name, unsigned int len, + NodeWatch owner); + unsigned short newNode(const char * name, + unsigned int len, + NodeOwner owner = INVALID_OWNER, + NodeDatum::Type type = (NodeDatum::Type)0, + const char * data = 0, + unsigned int dataLen = 0); + + inline Node * node(unsigned int); + + char * poolMem; + QMallocPool * pool; + bool changed; + NodeChangeFunction changeFunc; + void * changeFuncContext; +}; + +/////////////////////////////////////////////////////////////////////////////// +// define FixedMemoryTree +/////////////////////////////////////////////////////////////////////////////// +FixedMemoryTree::FixedMemoryTree(void * mem, unsigned int size, + bool initMemory ) +: poolMem((char *)mem), pool(0), changed(false), changeFunc(0) +{ + Q_ASSERT(size); + Q_ASSERT(poolMem); + + if(initMemory) { + /* Initialize layer version table - would be worth randomizing the + nextFreeBlk chain to distribute entries across the 128-bit change + notification. */ + ::memset(poolMem,0, sizeof(VersionTable)); + for(int ii = 0; ii < VERSION_TABLE_ENTRIES; ++ii) + versionTable()->entries[ii].nxtFreeBlk = + (ii + 1) % VERSION_TABLE_ENTRIES; + + /* Create malloc pool on the non-version table portion of the layer */ + pool = + new QMallocPool(poolMem + sizeof(VersionTable), + size - sizeof(VersionTable), + QMallocPool::Owned, QLatin1String("FixedMemoryTree")); + + /* Create root node and fixup free block pointer */ + versionTable()->nxtFreeBlk = + versionTable()->entries[ROOT_VERSION_ENTRY].nxtFreeBlk; + Node * root = (Node *)pool->malloc(sizeof(Node)); + ::memset(root,0, sizeof(Node)); + root->creationId = versionTable()->nextCreationId++; + root->parent = INVALID_HANDLE; + Q_ASSERT((char *)root > poolMem); + versionTable()->entries[ROOT_VERSION_ENTRY] = + VersionTable::Entry(1, ptr(root)); + } +} + +unsigned short FixedMemoryTree::findClosest(const char * path, + const char ** matched) +{ + Q_ASSERT(path); + Q_ASSERT(matched); + Q_ASSERT(path[0] == '/'); + + ++path; // Skip initial '/' + + if('\0' == *path) + return ROOT_VERSION_ENTRY; + else + return findRecur(ROOT_VERSION_ENTRY, path, matched); +} + +unsigned short FixedMemoryTree::findClosest(unsigned int from, + const char * subPath, + const char ** matched) +{ + Q_ASSERT(subPath); + Q_ASSERT(matched); + Q_ASSERT(subPath[0] == '/'); + + ++subPath; // Skip initial '/' + if('\0' == *subPath) + return from; + else + return findRecur(from, subPath, matched); +} + +/*! + Returns the default datum for \a node, or NULL if one doesn't exist. + */ +NodeDatum * FixedMemoryTree::data(unsigned short node) +{ + Node * n = this->node(node); + if(0 == n->dataCount) { + return 0; + } else if(1 == n->dataCount) { + return (NodeDatum *)n->dataBegin(); + } else { + unsigned int * valueList = + (unsigned int *)fromPtr(*(unsigned int *)n->dataBegin()); + return (NodeDatum *)fromPtr(*valueList); + } +} + +/*! + Inserts a watch for \a owner at the specified \a path +*/ +bool FixedMemoryTree::addWatch(const char * path, NodeWatch owner) +{ + Q_ASSERT(path); + Q_ASSERT(path[0] == '/'); + Q_ASSERT(INVALID_WATCH != owner); + + ++path; // Skip initial '/' + + if(*path == '\0') + return setWatch(ROOT_VERSION_ENTRY, owner); + else + return addWatchRecur(ROOT_VERSION_ENTRY, path, owner); +} + +/*! + Removes an existing watch for \a owner from the specified \a path +*/ +bool FixedMemoryTree::remWatch(const char * path, NodeWatch owner) +{ + Q_ASSERT(path); + Q_ASSERT(path[0] == '/'); + Q_ASSERT(INVALID_WATCH != owner); + + ++path; // Skip initial '/' + + if(*path == '\0') + return remWatch((unsigned short)ROOT_VERSION_ENTRY, owner); + else + return remWatchRecur(ROOT_VERSION_ENTRY, path, owner); +} + +/*! + Inserts \a data of length \a dataLen at \a path with the set \a owner. + Returns true if the tree has visibly changed. + */ +bool FixedMemoryTree::insert(const char * path, NodeDatum::Type type, + const char * data, unsigned int dataLen, + NodeOwner owner) +{ + Q_ASSERT(path); + Q_ASSERT(path[0] == '/'); + Q_ASSERT(data || !dataLen); + Q_ASSERT(INVALID_OWNER != owner); + + if(dataLen > MAX_DATA_SIZE) + return false; + + ++path; // Skip initial '/' + + // Special root case - no one can insert + if(*path == '\0') return false; + + return insertRecur(ROOT_VERSION_ENTRY, path, type, data, dataLen, owner); +} + +/*! + Returns the linear offset of \a subNode within \a node. Asserts if not + found. + */ +unsigned short FixedMemoryTree::offsetOfSubNode(unsigned short node, + unsigned short subNode) +{ + Node * me = getNode(node); + if(!me) + return INVALID_HANDLE; + + unsigned short * const subNodes = + (unsigned short *)fromPtr(me->subNodePtr); + for(unsigned short ii = 0; ii < me->subNodes; ++ii) { + if(subNodes[ii] == subNode) + return ii; + } + + qFatal("SharedMemoryLayer: Unable to find sub-node %x of %x.", subNode, node); + return 0; +} + +bool FixedMemoryTree::remove(const char * path, NodeOwner owner) +{ + Q_ASSERT(path); + Q_ASSERT(path[0] == '/'); + Q_ASSERT(INVALID_OWNER != owner); + + ++path; // Skip initial '/' + + if(*path == '\0') + return INVALID_HANDLE != removeRecur(ROOT_VERSION_ENTRY, owner); + else + return removeRecur(ROOT_VERSION_ENTRY, path, owner); +} + +/*! + Given a list of size \a currentSize that needs another element, returns a + new size designated by the following growth strategy: + + \list + \i Initially (currentSize == 0) allocate a list of one entry. + \i Up to 64 entries, double the size each allocation. + \i From 64 onwards, grow in chunks of 64 + \endlist + */ +unsigned int FixedMemoryTree::growListSize(unsigned int currentSize) +{ + if(0 == currentSize) + return 1; + if(currentSize <= 32) + return currentSize * 2; + return currentSize + 64; +} + +/*! + Given that we have an allocated list of size \a currentSize, that needs to + contain contentSize elements, returns an ideal list size designated by the + following shrink strategy: + + \list + \i For current size > 64, reduce in blocks of 64 + \i For currentSize < 64, reduce by half if possible + \i Otherwise, remain the same + \endlist + */ +unsigned int FixedMemoryTree::shrunkListSize(unsigned int currentSize, + unsigned int contentSize) +{ + if(currentSize > 64) { + return currentSize - 64 * ((currentSize - contentSize) / 64); + } else if(contentSize < (currentSize / 2)) { + return currentSize / 2; + } + + return currentSize; +} + +/*! + Attempts to locate the sub node \a name (of length \a len) in \a node. If + found, returns the offset and sets \a *match to true. If not found, + sets \a *match to false and returns the offset where the node would be + found, should it exist. Moving all nodes up one notch and inserting the + new node at the returned offset maintains order. + */ +unsigned int FixedMemoryTree::locateInNode(Node * node, const char * name, + unsigned int len, bool * match) +{ + Q_ASSERT(node && name && match); + + // This is a linear search. It would be possible to replace this with a + // binary search, which may or may not improve performance depending on the + // number of sub nodes. + unsigned short counter = 0; + unsigned short * subNodes = (unsigned short *)fromPtr(node->subNodePtr); + + *match = false; + for(counter = 0; counter < node->subNodes; ++counter) { + Node * currentNode = this->node(subNodes[counter]); + + int memcmprv = vsmemcmp(name, len, currentNode->name, + currentNode->nameLen); + if(memcmprv < 0) { + continue; + } else if(memcmprv == 0) { + *match = true; + break; + } else if(memcmprv > 0) { + break; + } + } + + return counter; +} + +/*! + \internal + Recursive implementation of FixedMemoryTree::findClosest + */ +unsigned short FixedMemoryTree::findRecur(unsigned short node, + const char * path, + const char ** consumed) +{ + // Locate end of section + const char * endOfSection = path; + while(*endOfSection != '/' && *endOfSection != '\0') ++endOfSection; + const unsigned int sectionLen = (endOfSection - path); + + bool match; + Node * const me = this->node(node); + const unsigned int matchId = locateInNode(me, path, sectionLen, &match); + Q_ASSERT((match && matchId < me->subNodes) || + (!match && matchId <= me->subNodes)); + + if(!match) { + *consumed = path - 1; + return node; + } + + unsigned short * const subNodes = + (unsigned short *)fromPtr(me->subNodePtr); + if('\0' == *endOfSection) { + *consumed = NULL; + return subNodes[matchId]; + } + + ++endOfSection; + return findRecur(subNodes[matchId], endOfSection, consumed); +} + +/*! Remove all nodes under node that match owner (or no-owner with no + dependents). Returns the last node removed, which will be \a node if + it was removed, or INVALID_HANDLE if no nodes were removed. This can + be used by the parent to determine whether to increment the version + number and whether to remove the sub node. + */ +unsigned short FixedMemoryTree::removeRecur(unsigned short node, + NodeOwner owner) +{ + Node * me = this->node(node); + + unsigned short * const subNodes = + (unsigned short*)fromPtr(me->subNodePtr); + + unsigned short removedAny = INVALID_HANDLE; + unsigned int removedSubNodes = 0; + + for(unsigned short ii = 0; ii < me->subNodes; ++ii) { + + unsigned short id = subNodes[ii]; + // Blank recur + unsigned short removedId = removeRecur(id, owner); + + if(removedId != INVALID_HANDLE) + removedAny = removedId; + + if(removedId == id) { + // This node was removed + ++removedSubNodes; + subNodes[ii] = INVALID_HANDLE; + } + + } + + // If removed any sub nodes we need to collapse our subnode list + if(removedSubNodes == me->subNodes) { + // All removed! + pool->free(subNodes); + me->subNodes = 0; + me->subNodePtr = 0; + } else if(removedSubNodes) { + // Some removed + unsigned int fillUpto = 0; + + for(unsigned int sourceUpto = 0; + sourceUpto < me->subNodes; + ++sourceUpto) { + + if(INVALID_HANDLE != subNodes[sourceUpto]) { + subNodes[fillUpto] = subNodes[sourceUpto]; + ++fillUpto; + } + } + + me->subNodes = fillUpto; + } + + // Now to remove data and possibly me as necessary + if(1 == me->dataCount) { + + NodeDatum * datum = (NodeDatum *)me->dataBegin(); + if(datum->owner == owner || + (owner.data2 = 0xFFFFFFFF && owner.data1 == datum->owner.data1)) + me->dataCount = 0; + + } else if(me->dataCount > 1) { + + // Data list time - sigh + unsigned int * list = + (unsigned int *)fromPtr(*((unsigned int *)me->dataBegin())); + unsigned int removed = 0; + for(int ii = 0; ii < me->dataCount; ++ii) { + NodeDatum * datum = (NodeDatum *)fromPtr(list[ii]); + if(datum->owner == owner || + (owner.data2 == 0xFFFFFFFF && + owner.data1 == datum->owner.data1)) { + // Remove this datum + ++removed; + pool->free(datum); + list[ii] = 0xFFFFFFFF; + } + } + + if(removed) { + // Three cases: Only one left in list - move into node + // All removed - remove list + // Some removed - shorten list if necessary + if((removed + 1)== me->dataCount) { + // Find remaining node + bool found = false; + for(int ii = 0; !found && ii < me->dataCount; ++ii) { + if(0xFFFFFFFF != list[ii]) { + NodeDatum * datum = (NodeDatum *)fromPtr(list[ii]); + me->dataCount = 0; + setData(node, datum->owner, + (NodeDatum::Type)datum->type, + datum->data, datum->len); + me = this->node(node); + found = true; + } + } + Q_ASSERT(found); + pool->free(list); + } else if(removed == me->dataCount) { + // Easy! + me->dataCount = 0; + pool->free(list); + } else { + // Some removed + unsigned int * newList = list; + unsigned int allocatedListSize = pool->size_of(list) / + sizeof(unsigned int); + unsigned int newListSize = shrunkListSize(allocatedListSize, + me->dataCount - removed); + if(newListSize != allocatedListSize) { + newList = (unsigned int *)pool->malloc(newListSize * + sizeof(unsigned int)); + } else { + newList = list; + } + + unsigned int newListCntr = 0; + for(int ii = 0; ii < me->dataCount; ++ii) { + if(list[ii] != 0xFFFFFFFF) + newList[newListCntr++] = list[ii]; + } + + me->dataCount = me->dataCount - removed; + if(list != newList) { + pool->free(list); + *((unsigned int *)me->dataBegin()) = ptr(newList); + } + } + } + } + + // Attempt watch cleanup + if(1 == me->watchCount) { + NodeWatch * watch = (NodeWatch *)me->watchBegin(); + if(watch->data1 == owner.data1 && + (owner.data2 == 0xFFFFFFFF || watch->data2 == owner.data2)) + me->watchCount = 0; + + } else if(me->watchCount > 1) { + // Watch list time... + NodeWatch * watch = + (NodeWatch *)fromPtr(*(unsigned int *)me->watchBegin()); + + unsigned int lastSpot = 0; + unsigned int watchers = 0; + for(unsigned int ii = 0; ii < me->watchCount; ++ii) { + if(watch[ii].data1 == owner.data1 && + (owner.data2 == 0xFFFFFFFF || watch[ii].data2 == owner.data2)) { + // Do nothing + } else { + ++watchers; + if(ii != lastSpot) + watch[lastSpot] = watch[ii]; + ++lastSpot; + } + } + + if(watchers == me->watchCount) { + // Nothing to do + } else if(0 == watchers) { + // Remove list and set watchCount appropriately + pool->free(watch); + me->watchCount = 0; + } else if(1 == watchers) { + // Set in main node + me->watchCount = 0; + setWatch(node, watch[0]); + pool->free(watch); + Q_ASSERT(1 == me->watchCount); + } else { + // Check if we need to shrink the list + unsigned int allocatedLength = pool->size_of(watch) / sizeof(NodeWatch); + unsigned int newLength = shrunkListSize(allocatedLength, watchers); + if(newLength != allocatedLength) { + NodeWatch * newList = + (NodeWatch *)pool->malloc(sizeof(NodeWatch) * newLength); + ::memcpy(newList, watch, sizeof(NodeWatch) * watchers); + *(unsigned int *)me->watchBegin() = ptr(newList); + pool->free(watch); + } + me->watchCount = watchers; + } + } + + if(!me->valid() && node != ROOT_VERSION_ENTRY) { + // Remove me :( + VersionTable::Entry * const entry = + &(versionTable()->entries[node]); + entry->nxtFreeBlk = versionTable()->nxtFreeBlk; + versionTable()->nxtFreeBlk = node; + pool->free(me); + bump(node); + return node; + } else if(removedAny != INVALID_HANDLE) { + bump(node); + } + + return removedAny; +} + +/*! + Removes sub node number \a subNodeNumber from \a from's sub node list. Does + bump \a from's version number. Does not touch removed sub node. + */ +void FixedMemoryTree::removeFrom(unsigned short from, + unsigned short subNodeNumber) +{ + Node * const me = node(from); + + Q_ASSERT(subNodeNumber < me->subNodes); + + unsigned short * const subNodes = + (unsigned short *)fromPtr(me->subNodePtr); + + unsigned int currentSize = pool->size_of(subNodes) / sizeof(unsigned short); + unsigned int scaleSize = shrunkListSize(currentSize, me->subNodes - 1); + if(scaleSize != currentSize) { + // Resize + unsigned short * newSubNodes = + (unsigned short *)pool->malloc(scaleSize * sizeof(unsigned short)); + ::memcpy(newSubNodes, subNodes, subNodeNumber * sizeof(unsigned short)); + ::memcpy(newSubNodes + subNodeNumber, subNodes + subNodeNumber + 1, + (me->subNodes - subNodeNumber - 1) * sizeof(unsigned short)); + me->subNodePtr = ptr(newSubNodes); + pool->free(subNodes); + } else { + // Do not resize + ::memmove(subNodes + subNodeNumber, subNodes + subNodeNumber + 1, + (me->subNodes - subNodeNumber - 1) * sizeof(unsigned short)); + } + --me->subNodes; + + // Bump version number + bump(from); +} + +bool FixedMemoryTree::remWatchRecur(unsigned short node, const char * path, + NodeWatch owner) +{ + // Locate end of section + const char * endOfSection = path; + while(*endOfSection != '/' && *endOfSection != '\0') ++endOfSection; + unsigned int sectionLen = (endOfSection - path); + + Node * const me = this->node(node); + bool match; + unsigned int matchId = locateInNode(me, path, sectionLen, &match); + Q_ASSERT((match && matchId < me->subNodes) || + (!match && matchId <= me->subNodes)); + + if(!match) + return false; + + unsigned short * const subNodes = + (unsigned short *)fromPtr(me->subNodePtr); + + if('\0' == *endOfSection) { + unsigned int id = subNodes[matchId]; + + // We must remove this node + if(remWatch((unsigned short)id, owner)) { + bump(node); + Node * them = this->node(id); + if(!them->valid()) { + // Remove from node list + removeFrom(node, matchId); + return true; + } + return false; + } + return false; + } else { + ++endOfSection; + bool rv = remWatchRecur((unsigned short)subNodes[matchId], endOfSection, owner); + + if(rv) { + bump(node); + Node * const subNode = this->node(subNodes[matchId]); + if(!subNode->valid()) { + // Remove this one too + if(subNode->subNodePtr) + pool->free(fromPtr(subNode->subNodePtr)); + pool->free(subNode); + removeFrom(node, matchId); + } + } + return rv; + } +} + +bool FixedMemoryTree::removeRecur(unsigned short node, const char * path, + NodeOwner owner) +{ + // Locate end of section + const char * endOfSection = path; + while(*endOfSection != '/' && *endOfSection != '\0') ++endOfSection; + unsigned int sectionLen = (endOfSection - path); + + Node * const me = this->node(node); + bool match; + unsigned int matchId = locateInNode(me, path, sectionLen, &match); + Q_ASSERT((match && matchId < me->subNodes) || + (!match && matchId <= me->subNodes)); + + if(!match) + return false; + + unsigned short * const subNodes = + (unsigned short *)fromPtr(me->subNodePtr); + + if('\0' == *endOfSection) { + + unsigned int id = subNodes[matchId]; + + // We must remove this node + unsigned short removeId = removeRecur(id, owner); + + if(removeId == id) { + // Remove from node list + removeFrom(node, matchId); + // Bump my version + bump(node); + return true; + } else if(INVALID_HANDLE != removeId) { + // Bump my version + bump(node); + return true; + } else { + return false; + } + } else { + ++endOfSection; + bool rv = removeRecur(subNodes[matchId], endOfSection, owner); + if(rv) { + bump(node); + Node * const subNode = this->node(subNodes[matchId]); + if(!subNode->valid()) { + // Remove this one too + if(subNode->subNodePtr) + pool->free(fromPtr(subNode->subNodePtr)); + pool->free(subNode); + removeFrom(node, matchId); + } + } + return rv; + } +} + +/*! + Removes \a owner as a watch on \a node. Returns true if owner was removed, + false if owner was not a watch. + */ +bool FixedMemoryTree::remWatch(unsigned short node, NodeWatch owner) +{ + Node * me = this->node(node); + + if(0 == me->watchCount) { + return false; + } else if(1 == me->watchCount) { + NodeWatch * watch = (NodeWatch *)me->watchBegin(); + if(*watch == owner) { + me->watchCount = 0; + return true; + } else { + return false; + } + } else if(me->watchCount == 2) { + NodeWatch * list = (NodeWatch *)fromPtr(*(unsigned int *)me->watchBegin()); + for(int ii = 0; ii < me->watchCount; ++ii) { + if(list[ii] == owner) { + // Found! + int remaining = (ii = 0)?1:0; + me->watchCount = 0; + setWatch(node, list[remaining]); + pool->free(list); + return true; + } + } + return false; + } else if(me->watchCount > 2) { + NodeWatch * list = (NodeWatch *)fromPtr(*(unsigned int *)me->watchBegin()); + for(unsigned short ii = 0; ii < me->watchCount; ++ii) { + if(list[ii] == owner) { + // Found! + unsigned int currentListSize = + pool->size_of(list) / sizeof(NodeWatch); + unsigned int newListSize = + shrunkListSize(currentListSize, me->watchCount - 1); + if(currentListSize != newListSize) { + // Create a new list + NodeWatch *newList = (NodeWatch *)pool->malloc(newListSize * sizeof(NodeWatch)); + ::memcpy(newList, list, ii * sizeof(NodeWatch)); + ::memcpy(newList + ii, list + ii + 1, + (me->watchCount - 1 - ii) * sizeof(NodeWatch)); + *(unsigned int *)me->watchBegin() = ptr(newList); + pool->free(list); + } else { + // Simply left shift everything + ::memmove(list + ii, list + ii + 1, + (me->watchCount - 1 - ii) * sizeof(NodeWatch)); + } + return true; + } + } + } + + return false; +} + +/*! + Inserts \a owner as a watch on \a node. + */ +bool FixedMemoryTree::setWatch(unsigned short node, NodeWatch owner) +{ + Node * me = this->node(node); + + /* + if 0 == node->watchCount + add to end of node + else if 1 == node->watchCount + move existing watch into new watch list and append to list + else + append to existing watch list + */ + + if(0 == me->watchCount) { + unsigned int spareLen = pool->size_of(me) - me->size(); + + if(spareLen < sizeof(NodeWatch)) { + // Need a new node + Node * newNode = + (Node *)pool->malloc(me->size() + sizeof(NodeWatch)); + ::memcpy(newNode, me, me->size()); + versionTable()->entries[node].nodePtr = ptr(newNode); + pool->free(me); + me = newNode; + } + me->watchCount = 1; + *(NodeWatch *)(me->watchBegin()) = owner; + } else if(1 == me->watchCount) { + // Need to create a list + NodeWatch * list = (NodeWatch *)pool->malloc(2 * sizeof(NodeWatch)); + list[0] = *(NodeWatch *)(me->watchBegin()); + list[1] = owner; + *(unsigned int *)(me->watchBegin()) = ptr(list); + ++me->watchCount; + } else { + // Append to list + NodeWatch * list = (NodeWatch *)fromPtr(*(unsigned int *)me->watchBegin()); + int availableSize = pool->size_of(list) / sizeof(NodeWatch); + if(availableSize < me->watchCount + 1) { + // Reallocate + NodeWatch * newList = + (NodeWatch *)pool->malloc(growListSize(availableSize) * + sizeof(NodeWatch)); + ::memcpy(newList, list, me->watchCount * sizeof(NodeWatch)); + pool->free(list); + list = newList; + *(unsigned int *)me->watchBegin() = ptr(newList); + } + list[me->watchCount] = owner; + ++me->watchCount; + } + return true; +} + +/*! + Inserts \a data of length \a dataLen into \a node. Returns true if the + version number of \a node has increased to handle this set. + */ +bool FixedMemoryTree::setData(unsigned short node, + NodeOwner owner, + NodeDatum::Type type, + const char * data, + unsigned int dataLen) +{ + Q_ASSERT(!dataLen || data); + + Node * me = this->node(node); + + /* + if 0 == node->dataCount + insert directly into node + else if 1 == node->dataCount && node->dataOwner == owner + insert directly into node + else if 1 == node->dataCount + move existing data into new data list and append new data to list + else + locate or append data to existing data list + */ + + if (0 == me->dataCount || + (1 == me->dataCount && ((NodeDatum *)me->dataBegin())->owner == owner)) { + unsigned int allocatedDataLen = + pool->size_of(me) - + sizeof(Node) - + me->watchBlockSize() - + ALIGN4b(me->nameLen); + + if(allocatedDataLen > sizeof(NodeDatum)) + allocatedDataLen -= sizeof(NodeDatum); + else + allocatedDataLen = 0; + + if(dataLen <= allocatedDataLen) { + // Sweet, reuse existing + NodeDatum * datum = (NodeDatum *)me->dataBegin(); + datum->owner = owner; + datum->type = type; + if(me->watchBlockSize()) + ::memcpy(datum->data + ALIGN4b(dataLen), me->watchBegin(), + me->watchBlockSize()); + datum->len = dataLen; + ::memcpy(datum->data, data, dataLen); + me->dataCount = 1; + } else { + // Need new node + Node * newNode = + (Node *)pool->malloc(sizeof(Node) + ALIGN4b(me->nameLen) + + sizeof(NodeDatum) + ALIGN4b(dataLen) + + me->watchBlockSize()); + ::memcpy(newNode, me, sizeof(Node) + ALIGN4b(me->nameLen)); + versionTable()->entries[node].nodePtr = ptr(newNode); + + NodeDatum * datum = (NodeDatum *)newNode->dataBegin(); + datum->owner = owner; + datum->type = type; + if(me->watchBlockSize()) + ::memcpy(datum->data + ALIGN4b(dataLen), me->watchBegin(), + me->watchBlockSize()); + datum->len = dataLen; + ::memcpy(datum->data, data, dataLen); + me->dataCount = 1; + pool->free(me); + me = newNode; + } + + bump(node); + + return true; + } else if(1 == me->dataCount) { + // Damn, need to create a value chain list + NodeDatum * existingDatum = (NodeDatum *)me->dataBegin(); + unsigned int * list = + (unsigned int *)pool->malloc(2 * sizeof(unsigned int)); + NodeDatum * currentDatum = + (NodeDatum *)pool->malloc(sizeof(NodeDatum) + existingDatum->len); + NodeDatum * newDatum = + (NodeDatum *)pool->malloc(sizeof(NodeDatum) + dataLen); + list[0] = ptr(currentDatum); + list[1] = ptr(newDatum); + + // Copy existing data + ::memcpy(currentDatum, existingDatum, + sizeof(NodeDatum) + existingDatum->len); + // Copy new data + newDatum->owner = owner; + newDatum->type = type; + newDatum->len = dataLen; + ::memcpy(newDatum->data, data, dataLen); + if(me->watchBlockSize()) + ::memcpy(((unsigned int *)me->dataBegin()) + 1, me->watchBegin(), + me->watchBlockSize()); + // Add value chain list to node + *(unsigned int *)me->dataBegin() = ptr(list); + me->dataCount = 2; + return false; + } else { + // Attempt to locate in existing chain + unsigned int * list = + (unsigned int *)fromPtr(*(unsigned int *)me->dataBegin()); + for(int ii = 0; ii < me->dataCount; ++ii) { + NodeDatum * ndata = (NodeDatum *)fromPtr(list[ii]); + if(ndata->owner == owner) { + // Found - but is it big enough? + unsigned int allocatedDataLen = + pool->size_of(ndata) - sizeof(NodeDatum); + if(allocatedDataLen >= dataLen) { + // Copy over the top + ::memcpy(ndata->data, data, dataLen); + ndata->len = dataLen; + ndata->type = type; + } else { + // Reallocate + NodeDatum * newData = + (NodeDatum *)pool->malloc(sizeof(NodeDatum) + + dataLen); + newData->owner = owner; + newData->type = type; + newData->len = dataLen; + ::memcpy(newData->data, data, dataLen); + list[ii] = ptr(newData); + pool->free(ndata); + } + return ii == 0; + } + } + + // Not found - append + unsigned int allocatedListLen = + pool->size_of(list) / sizeof(unsigned int); + if(allocatedListLen >= (unsigned int)me->dataCount + 1) { + // We fit! + } else { + // We don't fit + unsigned int * newList = + (unsigned int *)pool->malloc(growListSize(allocatedListLen) * + sizeof(unsigned int)); + ::memcpy(newList, list, allocatedListLen * sizeof(unsigned int)); + pool->free(list); + list = newList; + *(unsigned int *)me->dataBegin() = ptr(newList); + } + + NodeDatum * datum = (NodeDatum *)pool->malloc(sizeof(NodeDatum) + + dataLen); + datum->owner = owner; + datum->len = dataLen; + datum->type = type; + ::memcpy(datum->data, data, dataLen); + list[me->dataCount] = ptr(datum); + ++me->dataCount; + + return false; + } +} + +bool FixedMemoryTree::addWatchRecur(unsigned short node, const char * path, + NodeWatch owner) +{ + // Locate end of section + const char * endOfSection = path; + while(*endOfSection != '/' && *endOfSection != '\0') ++endOfSection; + const unsigned int sectionLen = (endOfSection - path); + + // We operate on structures, not numbers + Node * const me = this->node(node); + unsigned short * subNodes = + me->subNodePtr?(unsigned short *)fromPtr(me->subNodePtr):0; + + // Attempt to locate the sub node within the current node + bool match; + const unsigned int matchId = locateInNode(me, path, sectionLen, &match); + Q_ASSERT((match && matchId < me->subNodes) || + (!match && matchId <= me->subNodes)); + + if(match) { + + // There is a sub node of the correct name + const unsigned short id = subNodes[matchId]; + + if('\0' == *endOfSection) { + // Update the node. Bumps *their* version if necessary. + bool rv = setWatch(id, owner); + if(rv) bump(node); + return rv; + } + ++endOfSection; + + bool rv = addWatchRecur(id, endOfSection, owner); + if(rv) bump(node); + return rv; + + } else { /* !match */ + + // We *may* need to lengthen our list + unsigned short maxSubNodesLen = subNodes? + pool->size_of(subNodes) / sizeof(unsigned short):0; + unsigned int newSubNode = 0; + + if('\0' == *endOfSection) + newSubNode = newNode(path, sectionLen, owner); + else + newSubNode = newNode(path, sectionLen); + + if (newSubNode == ROOT_VERSION_ENTRY) + return false; + + Node * const newNode = this->node(newSubNode); + newNode->parent = node; + + if(maxSubNodesLen < me->subNodes + 1) { + // Grow list + unsigned short * newSubNodes = + (unsigned short *)pool->malloc(growListSize(me->subNodes) * + sizeof(unsigned short)); + ::memcpy(newSubNodes, subNodes, matchId * sizeof(unsigned short)); + ::memcpy(newSubNodes + matchId + 1, subNodes + matchId, + (me->subNodes - matchId) * sizeof(unsigned short)); + pool->free(fromPtr(me->subNodePtr)); + me->subNodePtr = ptr(newSubNodes); + subNodes = newSubNodes; + } else { + // Split list + ::memmove(subNodes + matchId + 1, subNodes + matchId, + (me->subNodes - matchId) * sizeof(unsigned short)); + } + subNodes[matchId] = newSubNode; + ++me->subNodes; + + // Bump my version + bump(node); + + if('\0' != *endOfSection) { + ++endOfSection; + addWatchRecur(newSubNode, endOfSection, owner); + } + return true; + } +} + +/*! + Returns true if the insert should force a version bump. + */ +bool FixedMemoryTree::insertRecur(unsigned short node, + const char * path, + NodeDatum::Type type, + const char * data, + unsigned int dataLen, + NodeOwner owner) +{ + // Locate end of section + const char * endOfSection = path; + while(*endOfSection != '/' && *endOfSection != '\0') ++endOfSection; + const unsigned int sectionLen = (endOfSection - path); + + // We operate on structures, not numbers + Node * const me = this->node(node); + unsigned short * subNodes = + me->subNodePtr?(unsigned short *)fromPtr(me->subNodePtr):0; + + // Attempt to locate the sub node within the current node + bool match; + const unsigned int matchId = locateInNode(me, path, sectionLen, &match); + Q_ASSERT((match && matchId < me->subNodes) || + (!match && matchId <= me->subNodes)); + + if(match) { + + // There is a sub node of the correct name + const unsigned short id = subNodes[matchId]; + + if('\0' == *endOfSection) { + // Update the node. Bumps *their* version if necessary. + bool rv = setData(id, owner, type, data, dataLen); + if(rv) bump(node); + return rv; + } + ++endOfSection; + + bool rv = insertRecur(id, endOfSection, type, data, dataLen, owner); + if(rv) bump(node); + return rv; + + } else { /* !match */ + + // We *may* need to lengthen our list + unsigned short maxSubNodesLen = subNodes? + pool->size_of(subNodes) / sizeof(unsigned short):0; + unsigned int newSubNode = 0; + + if('\0' == *endOfSection) + newSubNode = newNode(path, sectionLen, owner, type, data, dataLen); + else + newSubNode = newNode(path, sectionLen); + + if (newSubNode == ROOT_VERSION_ENTRY) + return false; + + Node * const newNode = this->node(newSubNode); + newNode->parent = node; + + if(maxSubNodesLen < me->subNodes + 1) { + // Grow list + unsigned short * newSubNodes = + (unsigned short *)pool->malloc(growListSize(me->subNodes) * + sizeof(unsigned short)); + ::memcpy(newSubNodes, subNodes, matchId * sizeof(unsigned short)); + ::memcpy(newSubNodes + matchId + 1, subNodes + matchId, + (me->subNodes - matchId) * sizeof(unsigned short)); + pool->free(fromPtr(me->subNodePtr)); + me->subNodePtr = ptr(newSubNodes); + subNodes = newSubNodes; + } else { + // Split list + ::memmove(subNodes + matchId + 1, subNodes + matchId, + (me->subNodes - matchId) * sizeof(unsigned short)); + } + subNodes[matchId] = newSubNode; + ++me->subNodes; + + // Bump my version + bump(node); + + if('\0' != *endOfSection) { + ++endOfSection; + insertRecur(newSubNode, endOfSection, type, data, dataLen, owner); + } + return true; + } +} + +VersionTable * FixedMemoryTree::versionTable() +{ + return (VersionTable *)poolMem; +} + +void * FixedMemoryTree::fromPtr(unsigned int ptr) +{ + if(!ptr) + return 0; + return (void *)(poolMem + ptr); +} + +void FixedMemoryTree::setNodeChangeFunction(NodeChangeFunction func, void *ctxt) +{ + changeFunc = func; + changeFuncContext = ctxt; +} + +unsigned int FixedMemoryTree::ptr(void * mem) +{ + Q_ASSERT(mem > poolMem); + return (unsigned int)((char *)mem - poolMem); +} + +/*! Increment \a node's version number */ +void FixedMemoryTree::bump(unsigned short node) +{ + ++(versionTable()->entries[node].version); + if(changeFunc) + changeFunc(node, changeFuncContext); +} + +Node * FixedMemoryTree::node(unsigned int entry) +{ + Q_ASSERT(entry < VERSION_TABLE_ENTRIES); + return (Node *)fromPtr(versionTable()->entries[entry].nodePtr); +} + +/*! Returns the node identified by \a entry, or null if there is no node in that + slot. This is different to FixedMemoryTree::node() in that it checks the + return value (and is thus slower). + */ +Node * FixedMemoryTree::getNode(unsigned int entry) +{ + Q_ASSERT(entry < VERSION_TABLE_ENTRIES); + void * rv = fromPtr(versionTable()->entries[entry].nodePtr); + if(rv < poolMem + sizeof(VersionTable)) { + return 0; + } else { + return (Node *)rv; + } +} + +Node * FixedMemoryTree::subNode(unsigned int node, unsigned short sub) +{ + Q_ASSERT(node < VERSION_TABLE_ENTRIES); + Node * me = this->node(node); + Q_ASSERT(sub < me->subNodes); + unsigned short * const subNodes = + (unsigned short *)fromPtr(me->subNodePtr); + return this->node(subNodes[sub]); +} + +unsigned int FixedMemoryTree::version(unsigned int entry) +{ + Q_ASSERT(entry < VERSION_TABLE_ENTRIES); + return versionTable()->entries[entry].version; +} + +/*! + Creates a new node of \a name (name length \a len) and sets a single \a watch on it. Returns + ROOT_VERSION_ENTRY if the new node could not be created. +*/ +unsigned short FixedMemoryTree::newNode(const char * name, unsigned int len, + NodeWatch owner) +{ + Q_ASSERT(owner != INVALID_WATCH); + + // Find an empty node entry + unsigned int node = versionTable()->nxtFreeBlk; + if (node == ROOT_VERSION_ENTRY) + return node; + + // Reset the next free blk + versionTable()->nxtFreeBlk = versionTable()->entries[node].nxtFreeBlk; + + int nodeSize = sizeof(Node) + ALIGN4b(len) + sizeof(NodeWatch); + + // Allocate a new node + Node * nodePtr = (Node *)pool->malloc(nodeSize); + Q_ASSERT((char *)nodePtr > poolMem); + ::memset(nodePtr,0, sizeof(Node)); + nodePtr->creationId = versionTable()->nextCreationId++; + + // Setup name + nodePtr->nameLen = len; + ::memcpy(nodePtr->name, name, len); + + // Setup watch + nodePtr->watchCount = 1; + *(NodeWatch *)(nodePtr->name + ALIGN4b(len)) = owner; + + // Enter node + versionTable()->entries[node] = VersionTable::Entry(1, ptr(nodePtr)); + // Bump version + bump(node); + + // Done + return node; + +} + +/*! + Creates a new node of \a name (name length \a len) and pre-fills it with \a data of length + \a dataLen owned by \a owner. If \a owner is an invalid owner, the node is a virtual node and + not prefilled with data. Returns ROOT_VERSION_ENTRY if the new node could not be created. +*/ +unsigned short FixedMemoryTree::newNode(const char * name, unsigned int len, + NodeOwner owner, NodeDatum::Type type, + const char * data, unsigned int dataLen) +{ + Q_ASSERT(owner != INVALID_OWNER || (!data && 0 == dataLen)); + + // Find an empty node entry + unsigned int node = versionTable()->nxtFreeBlk; + if (node == ROOT_VERSION_ENTRY) + return node; + + // Reset the next free blk + versionTable()->nxtFreeBlk = versionTable()->entries[node].nxtFreeBlk; + + int nodeSize = sizeof(Node) + ALIGN4b(len); + if(INVALID_OWNER != owner) + nodeSize += sizeof(NodeDatum) + ALIGN4b(dataLen); + + // Allocate a new node + Node * nodePtr = (Node *)pool->malloc(nodeSize); + Q_ASSERT((char *)nodePtr > poolMem); + ::memset(nodePtr, 0, sizeof(Node)); + nodePtr->creationId = versionTable()->nextCreationId++; + + // Setup name + nodePtr->nameLen = len; + ::memcpy(nodePtr->name, name, len); + + if(INVALID_OWNER != owner) { + // Setup data + nodePtr->dataCount = 1; + NodeDatum * datum = (NodeDatum *)nodePtr->dataBegin(); + datum->owner = owner; + datum->len = dataLen; + datum->type = type; + if(dataLen) + ::memcpy(datum->data, data, dataLen); + } + + // Enter node + versionTable()->entries[node] = VersionTable::Entry(1, ptr(nodePtr)); + // Bump version + bump(node); + + // Done + return node; +} + +// Use aggregation/composition - the SharedMemoryLayer "has a" server socket +// rather than "is a" server socket - also gets around multiple inheritance +// issues with QObject since the QLocalServer in Qt is a QObject +class ALServerImpl : public QLocalServer +{ +public: + ALServerImpl( QObject *p ) : QLocalServer( p ) {} + virtual ~ALServerImpl(); +protected: + virtual void incomingConnection(quintptr socketDescriptor); +}; + +ALServerImpl::~ALServerImpl() +{ +} + +// declare SharedMemoryLayer +class SharedMemoryLayer : public QAbstractValueSpaceLayer +{ + Q_OBJECT +public: + SharedMemoryLayer(); + virtual ~SharedMemoryLayer(); + + /* Common functions */ + QString name(); + + bool startup(Type); + + QUuid id(); + unsigned int order(); + + Handle item(Handle parent, const QString &); + void removeHandle(Handle); + void setProperty(Handle handle, Properties); + + bool value(Handle, QVariant *); + bool value(Handle, const QString &, QVariant *); + QSet children(Handle); + + QValueSpace::LayerOptions layerOptions() const; + + /* QValueSpaceSubscriber functions */ + bool supportsInterestNotification() const; + bool notifyInterest(Handle handle, bool interested); + + /* QValueSpacePublisher functions */ + bool setValue(QValueSpacePublisher *creator, Handle handle, const QString &, const QVariant &); + bool removeValue(QValueSpacePublisher *creator, Handle handle, const QString &); + bool removeSubTree(QValueSpacePublisher *creator, Handle handle); + void addWatch(QValueSpacePublisher *creator, Handle handle); + void removeWatches(QValueSpacePublisher *creator, Handle parent); + void sync(); + + void nodeChanged(unsigned short); + + static SharedMemoryLayer * instance(); + +private slots: + void disconnected(); + void readyRead(); + void closeConnections(); + + void doClientSync(); + +protected: + virtual void timerEvent(QTimerEvent *); + +private: +#ifdef QVALUESPACE_UPDATE_STATS + void updateStats(); +#else + inline void updateStats() {} +#endif + QList watchers(const QByteArray &); + void doClientEmit(); + void doClientTransmit(); + void doServerTransmit(); + bool doRemove(const QByteArray &path); + bool doWriteItem(const QByteArray &path, const QVariant &val); + bool setItem(NodeOwner owner, const QByteArray &path, const QVariant &val); + bool doSetItem(NodeOwner owner, const QByteArray &path, const QVariant &val); + bool remItems(NodeOwner owner, const QByteArray &path); + bool doRemItems(NodeOwner owner, const QByteArray &path); + bool setWatch(NodeWatch watch, const QByteArray &path); + bool doSetWatch(NodeWatch watch, const QByteArray &path); + bool remWatch(NodeWatch watch, const QByteArray &path); + bool doRemWatch(NodeWatch watch, const QByteArray &path); + void doNotify(const QByteArray &path, const QPacketProtocol *protocol, bool interested); + void doClientNotify(QValueSpacePublisher *publisher, const QByteArray &path, bool interested); + void doNotifyObject(unsigned long own, unsigned long protocol); + + QString socket() const; + + static QVariant fromDatum(const NodeDatum * data); + + struct ReadHandle + { + ReadHandle(const QByteArray &_path) + : refCount(1), path(_path), currentPath(0), + currentNode(INVALID_HANDLE), version(0), creationId(0), + forceChange(false) + { + } + + unsigned int refCount; + + /* Path this read handle is *intended* to point to */ + QByteArray path; + /* Offset into path indicating where we currently point to. 0xFFFFFFFF + means we point to the full path (saves path.size() call) */ + unsigned int currentPath; + /* Node we are currently pointing to. */ + unsigned short currentNode; + /* Last version of current node */ + unsigned int version; + /* Last creation Id of current node */ + unsigned int creationId; + + /* Indicates that version has been updated in a value() call, but + a change should be emitted anyway. */ + bool forceChange; + }; + ReadHandle * rh(QAbstractValueSpaceLayer::Handle handle) const + { + Q_ASSERT(handle && INVALID_HANDLE != handle); + return reinterpret_cast(handle); + } + bool refreshHandle(ReadHandle *); + void clearHandle(ReadHandle *); + + QLocalServer *sserver; + + Type type; + FixedMemoryTree * layer; + QSystemReadWriteLock * lock; + QMutex localLock; + QMap handles; + + void triggerTodo(); + bool triggeredTodo; + QPacket todo; + QSet connections; + + unsigned int newPackId() { + lastSentId = nextPackId++; + return lastSentId; + } + unsigned int nextPackId; + + unsigned int lastSentId; + unsigned int lastRecvId; + + bool valid; + friend class ALServerImpl; + + unsigned int forceChangeCount; + + uchar *clientIndex; + void incNode(unsigned short); + void decNode(unsigned short); + QHash m_nodeInterest; + + + unsigned int changedNodesCount; + unsigned short changedNodes[VERSION_TABLE_ENTRIES]; + + // Stats memory + unsigned long *m_statPoolSize; + unsigned long *m_statMaxSystemBytes; + unsigned long *m_statSystemBytes; + unsigned long *m_statInuseBytes; + unsigned long *m_statKeepCost; + + QSharedMemory* shm; + QSharedMemory* subShm; +}; + +QVALUESPACE_AUTO_INSTALL_LAYER(SharedMemoryLayer); + +/////////////////////////////////////////////////////////////////////////////// +// define SharedMemoryLayer +/////////////////////////////////////////////////////////////////////////////// +#define SHMLAYER_SIZE 1000000 +#define SHMLAYER_ADD 0 +#define SHMLAYER_REM 1 +#define SHMLAYER_DONE 2 +#define SHMLAYER_ADDWATCH 5 +#define SHMLAYER_REMWATCH 6 +#define SHMLAYER_SYNC 7 +#define SHMLAYER_SUBINDEX 8 +#define SHMLAYER_NOTIFY 9 + +struct SharedMemoryLayerClient : public QPacketProtocol +{ + SharedMemoryLayerClient(QIODevice *dev, QObject *parent = 0) + : QPacketProtocol(dev, parent), index(0) {} + + uchar *index; +}; + +SharedMemoryLayer::SharedMemoryLayer() +: type(Client), layer(0), lock(0), localLock(QMutex::Recursive), triggeredTodo(false), + nextPackId(1), lastSentId(0), lastRecvId(0), valid(false), + forceChangeCount(0), clientIndex(0), + changedNodesCount(0), + m_statPoolSize(0), m_statMaxSystemBytes(0), m_statSystemBytes(0), + m_statInuseBytes(0), m_statKeepCost(0), shm(0), subShm(0) +{ + sserver = new ALServerImpl( this ); + + connect(qApp, SIGNAL(destroyed()), this, SLOT(closeConnections())); +} + +SharedMemoryLayer::~SharedMemoryLayer() +{ + if(Server == type) { + delete lock; + lock = 0; + } +} + +QString SharedMemoryLayer::name() +{ + return QLatin1String("Shared Memory Layer"); +} + +static void ShmLayerNodeChanged(unsigned short, void *); +bool SharedMemoryLayer::startup(Type type) +{ + QMutexLocker locker(&localLock); + + valid = false; + + Q_ASSERT(!layer); + if(Server == type) { + if(!sserver->listen(socket())) { + qWarning() << "SharedMemoryLayer: Unable to create server socket. Only " + "local connections will be allowed." << sserver->errorString(); + } + } else { + QLocalSocket * sock = new QLocalSocket; + sock->connectToServer(socket()); + if(!sock->waitForConnected(1000)) { + qWarning("SharedMemoryLayer: Unable to connect to server, " + "shared memory layer will be disabled."); + delete sock; + return false; + } + + QPacketProtocol * protocol; + protocol = new QPacketProtocol(sock, this); + sock->setParent(protocol); + + QObject::connect(sock, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)), + protocol, SIGNAL(invalidPacket())); + QObject::connect(protocol, SIGNAL(invalidPacket()), + this, SLOT(disconnected())); + QObject::connect(protocol, SIGNAL(readyRead()), + this, SLOT(readyRead())); + + connections.insert(protocol); + } + + if(Server == type) { + shm = new QSharedMemory(socket(), this); + bool created = shm->create(SHMLAYER_SIZE); + if (!created) { + //qDebug() << "Reattaching to existing memory"; + shm->attach(); + } + lock = new QSystemReadWriteLock(socket() + "_lock", QSystemReadWriteLock::Create); + } else { + shm = new QSharedMemory(socket(), this); + shm->attach(QSharedMemory::ReadOnly); + qsrand(QTime(0,0,0).secsTo(QTime::currentTime())+QCoreApplication::applicationPid()); + subShm = new QSharedMemory(socket()+QString::number(qrand()), this); + if (!subShm->create((VERSION_TABLE_ENTRIES + 7) / 8, QSharedMemory::ReadWrite)) { + qWarning() << "SharedMemoryLayer client cannot create clientIndex:" + << subShm->errorString() << subShm->key(); + } + + lock = new QSystemReadWriteLock(socket() + "_lock", QSystemReadWriteLock::Open); + } + + if (shm->error() != QSharedMemory::NoError || + ((!subShm || subShm->error()!= QSharedMemory::NoError) && Server != type)) { + qFatal("SharedMemoryLayer: Unable to create or access shared resources. (%s - %s)", + shm->errorString().toLatin1().constData(), + subShm->errorString().toLatin1().constData()); + return false; + } + + if (subShm) + clientIndex = (uchar *)subShm->data(); + if(Client == type) { + QPacket mem; + mem << (unsigned int)0 + << (quint8)SHMLAYER_SUBINDEX << subShm->key() + << (quint8)SHMLAYER_DONE; + (*connections.begin())->send(mem); + } + + layer = new FixedMemoryTree((char*)shm->data(), + SHMLAYER_SIZE, + (Server == type)); + if(Server == type) + layer->setNodeChangeFunction(&ShmLayerNodeChanged, (void *)this); + + this->type = type; + + valid = (lock != 0) && (layer != 0); + if (!valid) + qWarning("SharedMemoryLayer::startup failed"); + return valid; +} + +void ALServerImpl::incomingConnection(quintptr socketDescriptor) +{ + QLocalSocket * sock = new QLocalSocket; + sock->setSocketDescriptor(socketDescriptor); + + QPacketProtocol * protocol; + protocol = new SharedMemoryLayerClient(sock, this); + sock->setParent(protocol); + + SharedMemoryLayer *al = static_cast( QObject::parent() ); + + QObject::connect(sock, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)), + protocol, SIGNAL(invalidPacket())); + QObject::connect(protocol, SIGNAL(invalidPacket()), + al, SLOT(disconnected())); + QObject::connect(protocol, SIGNAL(readyRead()), + al, SLOT(readyRead())); + + qDebug() << "New client" << protocol << "connected"; + + al->connections.insert(protocol); +} + +void SharedMemoryLayer::timerEvent(QTimerEvent *) +{ + QMutexLocker locker(&localLock); + + if(Client == type) + doClientTransmit(); + else + doServerTransmit(); +} + +void SharedMemoryLayer::doServerTransmit() +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(Server == type); + + triggeredTodo = false; + + QPacket others; + others << (quint8)SHMLAYER_SYNC << (unsigned int)0; + + for(QSet::ConstIterator iter = connections.begin(); + iter != connections.end(); + ++iter) { + + SharedMemoryLayerClient *client = + (SharedMemoryLayerClient *)(*iter); + if(!client->index) { + (*iter)->send(others); + } else { + bool found = false; + for(unsigned int ii = 0;!found && ii < changedNodesCount; ++ii) { + if(client->index[changedNodes[ii] >> 3] & (1 << (changedNodes[ii] & 0x7))) + found = true; + } + if(found) { + //qDebug() << "GGGGGGGGGGGGGGGG"; + (*iter)->send(others); + } + } + + } + + changedNodesCount = 0; + doClientEmit(); +} + +void SharedMemoryLayer::doClientTransmit() +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(Client == type); + + triggeredTodo = false; + + if(!todo.isEmpty()) { + todo << (quint8)SHMLAYER_DONE; + (*connections.begin())->send(todo); + todo.clear(); + } +} + +typedef QHash > PathsOfInterest; +Q_GLOBAL_STATIC(PathsOfInterest, pathsOfInterest); + +void SharedMemoryLayer::disconnected() +{ + Q_ASSERT(sender()); + + QMutexLocker locker(&localLock); + + QPacketProtocol * protocol = (QPacketProtocol *)sender(); + protocol->disconnect(); + protocol->deleteLater(); + + if(Client == type) { + qFatal("SharedMemoryLayer: Shared memory layer server unexpectedly terminated."); + } else { + qDebug() << "Removing" << protocol << "from space"; + + NodeOwner owner; + owner.data1 = (unsigned long)protocol; + owner.data2 = 0xFFFFFFFF; + + connections.remove(protocol); + if(layer->remove("/", owner)) { + QPacket others; + others << (quint8)SHMLAYER_SYNC << (unsigned int)0; + for(QSet::ConstIterator iter = connections.begin(); + iter != connections.end(); + ++iter) + (*iter)->send(others); + doClientEmit(); + } + + QList paths = pathsOfInterest()->keys(); + + for (int i = 0; i < paths.count(); ++i) { + const QByteArray interestPath = paths.at(i); + + if (!(*pathsOfInterest())[interestPath].remove(protocol)) + continue; + + if (!(*pathsOfInterest())[interestPath].isEmpty()) + continue; + + pathsOfInterest()->remove(interestPath); + + QList owners = watchers(interestPath); + + QSet written; + QSet notified; + + for (int ii = 0; ii < owners.count(); ++ii) { + const NodeWatch &watch = owners.at(ii); + + if (watch.data1 != (unsigned long)protocol) + continue; + + if (watch.data1 == 0) { + QValueSpacePublisher *publisher = + reinterpret_cast(watch.data2); + if (!notified.contains(publisher)) { + doClientNotify(publisher, interestPath, false); + notified.insert(publisher); + } + } else { + if (!written.contains(watch.data1)) { + ((QPacketProtocol *)watch.data1)->send() << quint8(SHMLAYER_NOTIFY) + << watch.data2 << interestPath + << false; + written.insert(watch.data1); + } + } + } + + } + } +} + +static void ShmLayerNodeChanged(unsigned short node, void *ctxt) +{ + SharedMemoryLayer *layer = (SharedMemoryLayer *)ctxt; + layer->nodeChanged(node); +} + +void SharedMemoryLayer::nodeChanged(unsigned short node) +{ + QMutexLocker locker(&localLock); + + if(changedNodesCount != VERSION_TABLE_ENTRIES) + changedNodes[changedNodesCount++] = node; +} + +void SharedMemoryLayer::readyRead() +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(sender()); + QPacketProtocol * protocol = (QPacketProtocol *)sender(); + + QPacket pack = protocol->read(); + if (Client == type) { + //qDebug() << "Client" << protocol << "woken"; + if (pack.isEmpty()) + return; + + quint8 op = 0; + pack >> op; + switch(op) { + case SHMLAYER_SYNC: { + unsigned int recvId = 0; + pack >> recvId; + if(0 != recvId) + lastRecvId = recvId; + doClientEmit(); + break; + } + case SHMLAYER_NOTIFY: { + unsigned long owner; + QByteArray path; + bool interested; + pack >> owner >> path >> interested; + doClientNotify(reinterpret_cast(owner), path, interested); + break; + } + default: + qFatal("SharedMemoryLayer: Invalid message type %d " + "received from shared memory layer server.", op); + break; + } + } else { + unsigned int packId = 0; + pack >> packId; + + bool changed = false; + bool done = false; + + changedNodesCount = 0; + while(!done && !pack.isEmpty()) { + quint8 op; + pack >> op; + + switch(op) { + case SHMLAYER_ADD: + { + unsigned long own; + QByteArray path; + QVariant value; + pack >> own >> path >> value; + NodeOwner owner; + owner.data1 = (unsigned long)protocol; + owner.data2 = own; + changed |= doSetItem(owner, path, value); + } + break; + + case SHMLAYER_REM: + { + unsigned long own; + QByteArray path; + pack >> own >> path; + NodeOwner owner; + owner.data1 = (unsigned long)protocol; + owner.data2 = own; + changed |= doRemItems(owner, path); + } + break; + + case SHMLAYER_ADDWATCH: + { + unsigned long own; + QByteArray path; + pack >> own >> path; + NodeWatch owner; + owner.data1 = (unsigned long)protocol; + owner.data2 = own; + changed |= doSetWatch(owner, path); + } + break; + + case SHMLAYER_REMWATCH: + { + unsigned long own; + QByteArray path; + pack >> own >> path; + NodeWatch owner; + owner.data1 = (unsigned long)protocol; + owner.data2 = own; + changed |= doRemWatch(owner, path); + } + break; + + case SHMLAYER_DONE: + done = true; + break; + + case SHMLAYER_SUBINDEX: + { + QString shmkey; + pack >> shmkey; + + QSharedMemory* mem = new QSharedMemory(shmkey, protocol); + if (!mem->attach(QSharedMemory::ReadOnly)) { + qWarning() << "SharedMemoryLayer: Unable to shmat with id:" + << shmkey << "error:" << mem->errorString(); + delete mem; + } else { + static_cast(protocol)->index = + reinterpret_cast(mem->data()); + } + } + break; + + case SHMLAYER_NOTIFY: + { + QByteArray path; + bool interested; + pack >> path >> interested; + doNotify(path, protocol, interested); + } + break; + default: + qWarning("SharedMemoryLayer: Invalid client request %x received.", op); + disconnected(); + return; + } + } + + // Send change notification + QPacket causal; + causal << (quint8)SHMLAYER_SYNC << packId; + protocol->send(causal); + + if(changed) + doServerTransmit(); + } +} + +void SharedMemoryLayer::closeConnections() +{ + QMutexLocker locker(&localLock); + + QMutableSetIterator i(connections); + while (i.hasNext()) { + delete i.next(); + i.remove(); + } +} + +void SharedMemoryLayer::doClientEmit() +{ + QMutexLocker locker(&localLock); + + QMap cpy = handles; + for(QMap::ConstIterator iter = cpy.begin(); + iter != cpy.end(); + ++iter) + ++(*iter)->refCount; + + for(QMap::ConstIterator iter = cpy.begin(); + iter != cpy.end(); + ++iter) { + if(refreshHandle(*iter)) { + if((*iter)->forceChange) { + Q_ASSERT(forceChangeCount); + --forceChangeCount; + (*iter)->forceChange = false; + } + + emit handleChanged((Handle)*iter); + } else { + + if((*iter)->forceChange) { + Q_ASSERT(forceChangeCount); + --forceChangeCount; + (*iter)->forceChange = false; + emit handleChanged((Handle)*iter); + } + } + } + + while(forceChangeCount) { + for(QMap::ConstIterator iter = cpy.begin(); + forceChangeCount && iter != cpy.end(); + ++iter) { + + if((*iter)->forceChange) { + Q_ASSERT(forceChangeCount); + --forceChangeCount; + (*iter)->forceChange = false; + emit handleChanged((Handle)*iter); + } + + } + } + + for(QMap::ConstIterator iter = cpy.begin(); + iter != cpy.end(); + ++iter) + removeHandle((Handle)*iter); +} + +QUuid SharedMemoryLayer::id() +{ + return QVALUESPACE_SHAREDMEMORY_LAYER; +} + +unsigned int SharedMemoryLayer::order() +{ + return 0x10000000; +} + +bool SharedMemoryLayer::value(Handle handle, QVariant *data) +{ + QMutexLocker locker(&localLock); + + if (!valid) + return false; + Q_ASSERT(layer); + Q_ASSERT(data); + + ReadHandle * rhandle = rh(handle); + + lock->lockForRead(); + + if(0xFFFFFFFF != rhandle->currentPath) + if(refreshHandle(rhandle)) { + if(!rhandle->forceChange) { + ++forceChangeCount; + rhandle->forceChange = true; + } + } + + Node * node = 0; + if(0xFFFFFFFF == rhandle->currentPath) { + node = layer->getNode(rhandle->currentNode); + if(!node || node->creationId != rhandle->creationId) { + refreshHandle(rhandle); + node = layer->getNode(rhandle->currentNode); + if(!rhandle->forceChange) { + ++forceChangeCount; + rhandle->forceChange = true; + } + } + } + + bool rv = false; + if(0xFFFFFFFF == rhandle->currentPath) { + Q_ASSERT(node); + NodeDatum const * const datum = layer->data(rhandle->currentNode); + if(datum) { + *data = fromDatum(datum); + rv = true; + } + } + + lock->unlock(); + + return rv; +} + +bool SharedMemoryLayer::value(Handle handle, const QString &subPath, + QVariant *data) +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(layer); + Q_ASSERT(data); + Q_ASSERT(!subPath.isEmpty()); + Q_ASSERT(*subPath.constData() == QLatin1Char('/')); + + ReadHandle * rhandle = rh(handle); + + lock->lockForRead(); + + if(0xFFFFFFFF != rhandle->currentPath) + if(refreshHandle(rhandle)) { + if(!rhandle->forceChange) { + ++forceChangeCount; + rhandle->forceChange = true; + } + } + + bool rv = false; + if(0xFFFFFFFF == rhandle->currentPath) { + QByteArray abs_path = rhandle->path + subPath.toUtf8(); + if (rhandle->path.length() == 1) + abs_path = subPath.toUtf8(); + ReadHandle vhandle(abs_path); + clearHandle(&vhandle); + refreshHandle(&vhandle); + if(0xFFFFFFFF == vhandle.currentPath) { + // Valid! + NodeDatum const * const datum = layer->data(vhandle.currentNode); + if(datum) { + *data = fromDatum(datum); + rv = true; + } + } + } + + lock->unlock(); + + return rv; +} + +QSet SharedMemoryLayer::children(Handle handle) +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(layer); + ReadHandle * rhandle = rh(handle); + + QSet rv; + lock->lockForRead(); + + if(0xFFFFFFFF != rhandle->currentPath) + if(refreshHandle(rhandle)) { + if(!rhandle->forceChange) { + ++forceChangeCount; + rhandle->forceChange = true; + } + } + + Node * node = 0; + if(0xFFFFFFFF == rhandle->currentPath) { + node = layer->getNode(rhandle->currentNode); + if(!node || node->creationId != rhandle->creationId) { + if(refreshHandle(rhandle)) { + if(0xFFFFFFFF == rhandle->currentPath) + node = layer->getNode(rhandle->currentNode); + else + node = 0; + if(!rhandle->forceChange) { + ++forceChangeCount; + rhandle->forceChange = true; + } + } + } + } + + if(0xFFFFFFFF == rhandle->currentPath) { + Q_ASSERT(node); + for(unsigned short ii = 0; ii < node->subNodes; ++ii) { + Node * n = layer->subNode(rhandle->currentNode, ii); + rv.insert(QString::fromUtf8(n->name, n->nameLen)); + } + } + + lock->unlock(); + return rv; +} + +QValueSpace::LayerOptions SharedMemoryLayer::layerOptions() const +{ + return QValueSpace::TransientLayer | QValueSpace::WritableLayer; +} + +SharedMemoryLayer::Handle SharedMemoryLayer::item(Handle parent, const QString &key) +{ + QMutexLocker locker(&localLock); + + Q_UNUSED(parent); + Q_ASSERT(layer); + Q_ASSERT(*key.constData() == QLatin1Char('/')); + Q_ASSERT(InvalidHandle == parent); + QMap::Iterator iter = handles.find(key); + if(iter != handles.end()) { + ++(*iter)->refCount; + return (QAbstractValueSpaceLayer::Handle)*iter; + } else { + ReadHandle * handle = new ReadHandle(key.toUtf8()); + clearHandle(handle); + lock->lockForRead(); + refreshHandle(handle); + lock->unlock(); + handles.insert(key, handle); + return (QAbstractValueSpaceLayer::Handle)(handle); + } +} + +/*! + Attempt to advance \a handle to point to the true path or update it if it + does. Returns true if the handle has either: + + \list 1 + \i Changed value + \i Moved from being a partial reference to a true reference + \i Moved from being a true reference to a partial reference + \endlist + + That is, returns true if a handleChanged() signal is needed :) + + The layer MUST be locked for reading. + */ + +bool SharedMemoryLayer::refreshHandle(ReadHandle * handle) +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(handle); + + ReadHandle old = *handle; + unsigned short oldNode = handle->currentNode; + + // Refresh handle + if(0xFFFFFFFF == handle->currentPath) { + // True path! + Node * node = layer->getNode(handle->currentNode); + if(!node || node->creationId != handle->creationId) { + // Bad news! + clearHandle(handle); + oldNode = INVALID_HANDLE; // prevent double dec + } else { + // Super - still a true path + handle->version = layer->version(handle->currentNode); + } + } + + if(0xFFFFFFFF != handle->currentPath && + INVALID_HANDLE != handle->currentNode) { + // Partial + Node * node = layer->getNode(handle->currentNode); + if(!node || node->creationId != handle->creationId) { + // Bad news! + clearHandle(handle); + oldNode = INVALID_HANDLE; // prevent double dec + } else if(handle->version != layer->version(handle->currentNode)) { + // Something changed - try and advance! + const char * out = 0; + handle->currentNode = layer->findClosest(handle->currentNode, + handle->path.constData() + handle->currentPath, + &out); + + Node * newNode = layer->getNode(handle->currentNode); + Q_ASSERT(newNode); + handle->version = layer->version(handle->currentNode); + handle->creationId = newNode->creationId; + if(out) + handle->currentPath = (out - handle->path.constData()); + else + handle->currentPath = 0xFFFFFFFF; + } + } + + if(INVALID_HANDLE == handle->currentNode) { + // Start from scratch + const char * out = 0; + handle->currentNode = layer->findClosest(handle->path.constData(), + &out); + + Node * newNode = layer->getNode(handle->currentNode); + Q_ASSERT(newNode); + handle->version = layer->version(handle->currentNode); + handle->creationId = newNode->creationId; + if(out) + handle->currentPath = (out - handle->path.constData()); + else + handle->currentPath = 0xFFFFFFFF; + } + + if(handle->currentNode != oldNode) { + decNode(oldNode); + incNode(handle->currentNode); + } + + bool rv = false; + if(0xFFFFFFFF == handle->currentPath) { + // Valid! + rv = (handle->currentNode != old.currentNode || + handle->currentPath != old.currentPath || + handle->creationId != old.creationId || + handle->version != old.version); + } else { + // Returns true if old was valid + rv = (0xFFFFFFFF == old.currentPath); + } + + return rv; +} + +void SharedMemoryLayer::clearHandle(ReadHandle *handle) +{ + QMutexLocker locker(&localLock); + + handle->currentPath = 0; + decNode(handle->currentNode); + handle->currentNode = INVALID_HANDLE; + handle->version = 0; + handle->creationId = 0; +} + +void SharedMemoryLayer::triggerTodo() +{ + QMutexLocker locker(&localLock); + + if (triggeredTodo || !valid) + return; + + QCoreApplication::postEvent(this, new QTimerEvent(0), Qt::LowEventPriority); +} + +void SharedMemoryLayer::setProperty(Handle, Properties) +{ + // Properties aren't used by shared memory layer (always emits changed) +} + +void SharedMemoryLayer::removeHandle(Handle h) +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(layer); + Q_ASSERT(h && INVALID_HANDLE != h); + + ReadHandle * rhandle = rh(h); + Q_ASSERT(rhandle->refCount); + --rhandle->refCount; + if(!rhandle->refCount) { + handles.remove(QString::fromUtf8(rhandle->path.constData(), rhandle->path.length())); + clearHandle(rhandle); + delete rhandle; + } +} + +#ifdef QVALUESPACE_UPDATE_STATS + +void SharedMemoryLayer::updateStats() +{ + QMutexLocker locker(&localLock); + + if(!m_statPoolSize) { + // Hasn't initialized stats! + Q_ASSERT(layer); + unsigned int v = 0; + NodeOwner owner = { 1, 1 }; + + layer->insert("/.ValueSpace/SharedMemoryLayer/Memory/PoolSize", + NodeDatum::UInt, (const char *)&v, sizeof(unsigned int), + owner); + layer->insert("/.ValueSpace/SharedMemoryLayer/Memory/MaxSystemBytes", + NodeDatum::UInt, (const char *)&v, sizeof(unsigned int), + owner); + layer->insert("/.ValueSpace/SharedMemoryLayer/Memory/SystemBytes", + NodeDatum::UInt, (const char *)&v, sizeof(unsigned int), + owner); + layer->insert("/.ValueSpace/SharedMemoryLayer/Memory/InuseBytes", + NodeDatum::UInt, (const char *)&v, sizeof(unsigned int), + owner); + layer->insert("/.ValueSpace/SharedMemoryLayer/Memory/KeepCost", + NodeDatum::UInt, (const char *)&v, sizeof(unsigned int), + owner); + + const char * out; + unsigned short node; + unsigned short baseNode = + layer->findClosest("/.ValueSpace/SharedMemoryLayer/Memory", &out); + + node = layer->findClosest(baseNode, "/PoolSize", &out); + m_statPoolSize = (unsigned long *)layer->data(node)->data; + node = layer->findClosest(baseNode, "/MaxSystemBytes", &out); + m_statMaxSystemBytes = (unsigned long *)layer->data(node)->data; + node = layer->findClosest(baseNode, "/SystemBytes", &out); + m_statSystemBytes = (unsigned long *)layer->data(node)->data; + node = layer->findClosest(baseNode, "/InuseBytes", &out); + m_statInuseBytes = (unsigned long *)layer->data(node)->data; + node = layer->findClosest(baseNode, "/KeepCost", &out); + m_statKeepCost = (unsigned long *)layer->data(node)->data; + } + + QMallocPool::MemoryStats mstats = layer->mallocPool()->memoryStatistics(); + *m_statPoolSize = mstats.poolSize; + *m_statMaxSystemBytes = mstats.maxSystemBytes; + *m_statSystemBytes = mstats.systemBytes; + *m_statInuseBytes = mstats.inuseBytes; + *m_statKeepCost = mstats.keepCost; +} + +#endif + +QList SharedMemoryLayer::watchers(const QByteArray &path) +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(layer); + + lock->lockForRead(); + + const char * matched = 0; + unsigned short node = layer->findClosest(path.constData(), &matched); + QList owners; + while(INVALID_HANDLE != node) { + Node * n = layer->getNode(node); + if(1 == n->watchCount) { + owners.append(*(NodeWatch *)n->watchBegin()); + } else if(2 <= n->watchCount) { + NodeWatch *watches = + (NodeWatch *)layer->fromPtr(*(unsigned int*)n->watchBegin()); + for(unsigned int ii = 0; ii < n->watchCount; ++ii) { + owners.append(watches[ii]); + } + } + + node = n->parent; + } + + lock->unlock(); + + return owners; +} + +void SharedMemoryLayer::doNotify(const QByteArray &path, const QPacketProtocol *protocol, + bool interested) +{ + QMutexLocker locker(&localLock); + + bool sendNotification = false; + if (interested) { + if ((++(*pathsOfInterest())[path][protocol]) == 1) + sendNotification = true; + } else { + if ((--(*pathsOfInterest())[path][protocol]) == 0) { + (*pathsOfInterest())[path].remove(protocol); + + if ((*pathsOfInterest())[path].isEmpty()) + pathsOfInterest()->remove(path); + + sendNotification = true; + } + } + + if (!sendNotification) + return; + + QList owners = watchers(path); + + QSet written; + QSet notified; + + for (int ii = 0; ii < owners.count(); ++ii) { + const NodeWatch &watch = owners.at(ii); + + if (watch.data1 == 0) { + QValueSpacePublisher *publisher = + reinterpret_cast(watch.data2); + if (!notified.contains(publisher)) { + doClientNotify(publisher, path, interested); + notified.insert(publisher); + } + } else { + if (!written.contains(watch.data1)) { + ((QPacketProtocol *)watch.data1)->send() << quint8(SHMLAYER_NOTIFY) + << watch.data2 << path << interested; + written.insert(watch.data1); + } + } + } +} + +void SharedMemoryLayer::doNotifyObject(unsigned long own, unsigned long protocol) +{ + QMutexLocker locker(&localLock); + + QList paths = pathsOfInterest()->keys(); + + for (int i = 0; i < paths.count(); ++i) { + const QByteArray &interestPath = paths.at(i); + + QList owners = watchers(interestPath); + + QSet written; + QSet notified; + + for (int ii = 0; ii < owners.count(); ++ii) { + const NodeWatch &watch = owners.at(ii); + + if (watch.data1 != protocol || watch.data2 != own) + continue; + + if (watch.data1 == 0) { + QValueSpacePublisher *publisher = + reinterpret_cast(watch.data2); + if (!notified.contains(publisher)) { + doClientNotify(publisher, interestPath, true); + notified.insert(publisher); + } + } else { + if (!written.contains(watch.data1)) { + ((QPacketProtocol *)watch.data1)->send() << quint8(SHMLAYER_NOTIFY) + << watch.data2 << interestPath + << true; + written.insert(watch.data1); + } + } + } + } +} + +bool SharedMemoryLayer::setWatch(NodeWatch watch, const QByteArray &path) +{ + QMutexLocker locker(&localLock); + + if(path.count() > MAX_PATH_SIZE || path.startsWith("/.ValueSpace") || !valid) + return false; + Q_ASSERT(layer); + + if(Client == type) { + if(todo.isEmpty()) + todo << newPackId(); + + todo << (quint8)SHMLAYER_ADDWATCH << watch.data2 << path; + triggerTodo(); + return true; + } else { + bool changed = doSetWatch(watch, path); + if(changed) triggerTodo(); + return changed; + } +} + +bool SharedMemoryLayer::doSetWatch(NodeWatch watch, const QByteArray &path) +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(layer); + + lock->lockForWrite(); + bool rv = layer->addWatch(path.constData(), watch); + updateStats(); + lock->unlock(); + + doNotifyObject(watch.data2, watch.data1); + + return rv; +} + +bool SharedMemoryLayer::remWatch(NodeWatch watch, const QByteArray &path) +{ + QMutexLocker locker(&localLock); + + if(path.count() > MAX_PATH_SIZE || !valid) + return false; + Q_ASSERT(layer); + + if(Client == type) { + if(todo.isEmpty()) + todo << newPackId(); + + todo << (quint8)SHMLAYER_REMWATCH << watch.data2 << path; + triggerTodo(); + return true; + } else { + bool changed = doRemWatch(watch, path); + if(changed) triggerTodo(); + return changed; + } +} + +bool SharedMemoryLayer::doRemWatch(NodeWatch watch, const QByteArray &path) +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(layer); + + lock->lockForWrite(); + bool rv = layer->remWatch(path.constData(), watch); + updateStats(); + lock->unlock(); + + return rv; +} + +bool SharedMemoryLayer::setItem(NodeOwner owner, const QByteArray &path, + const QVariant &val) +{ + QMutexLocker locker(&localLock); + + if(path.count() > MAX_PATH_SIZE || path.startsWith("/.ValueSpace") || !valid) + return false; + Q_ASSERT(layer); + bool changed = false; + + if(Client == type) { + if(todo.isEmpty()) + todo << newPackId(); + + todo << (quint8)SHMLAYER_ADD << owner.data2 << path << val; + triggerTodo(); + return true; + } else { + changed = doSetItem(owner, path, val); + if(changed) + triggerTodo(); + } + return changed; +} + +bool SharedMemoryLayer::doSetItem(NodeOwner owner, const QByteArray &path, + const QVariant &val) +{ + QMutexLocker locker(&localLock); + + bool rv = false; + + lock->lockForWrite(); + + switch(val.type()) { + case QVariant::Bool: + { + unsigned int v = val.toBool()?1:0; + rv = layer->insert(path.constData(), NodeDatum::Boolean, (const char *)&v, + sizeof(unsigned int), owner); + } + break; + case QVariant::Int: + { + int v = val.toInt(); + rv = layer->insert(path.constData(), NodeDatum::Int, (const char *)&v, + sizeof(int), owner); + } + break; + case QVariant::UInt: + { + unsigned int v = val.toUInt(); + rv = layer->insert(path.constData(), NodeDatum::UInt, (const char *)&v, + sizeof(unsigned int), owner); + } + break; + case QVariant::LongLong: + { + long long v = val.toLongLong(); + rv = layer->insert(path.constData(), NodeDatum::LongLong, (const char *)&v, + sizeof(long long), owner); + } + break; + case QVariant::ULongLong: + { + unsigned long long v = val.toULongLong(); + rv = layer->insert(path.constData(), NodeDatum::ULongLong, (const char *)&v, + sizeof(unsigned long long), owner); + } + break; + case QVariant::Double: + { + double v = val.toDouble(); + rv = layer->insert(path.constData(), NodeDatum::Double, (const char *)&v, + sizeof(double), owner); + } + break; + case QVariant::Char: + { + QChar v = val.toChar(); + rv = layer->insert(path.constData(), NodeDatum::Char, (const char *)&v, + sizeof(QChar), owner); + } + break; + case QVariant::String: + { + QString v = val.toString(); + rv = layer->insert(path.constData(), NodeDatum::String, + (const char *)v.constData(), v.length() * sizeof(QChar), owner); + } + break; + case QVariant::ByteArray: + { + QByteArray v = val.toByteArray(); + rv = layer->insert(path.constData(), NodeDatum::ByteArray, + v.constData(), v.length(), owner); + } + break; + default: + { + QByteArray v; + QDataStream ds(&v, QIODevice::WriteOnly); + ds << val; + rv = layer->insert(path.constData(), NodeDatum::SerializedType, + v.constData(), v.length(), owner); + } + break; + } + + updateStats(); + lock->unlock(); + + return rv; +} + +bool SharedMemoryLayer::remItems(NodeOwner owner, const QByteArray &path) +{ + QMutexLocker locker(&localLock); + + if (!valid) + return false; + bool rv = false; + + if(Client == type) { + if(todo.isEmpty()) + todo << newPackId(); + + todo << (quint8)SHMLAYER_REM << owner.data2 << path; + triggerTodo(); + } else { + rv = doRemItems(owner, path); + if(rv) triggerTodo(); + } + + return rv; +} + +bool SharedMemoryLayer::doRemItems(NodeOwner owner, const QByteArray &path) +{ + QMutexLocker locker(&localLock); + + if (!valid) + return false; + bool rv = false; + + if(Client == type) { + if(todo.isEmpty()) + todo << newPackId(); + + todo << (quint8)SHMLAYER_REM << owner.data2 << path; + triggerTodo(); + + } else { + Q_ASSERT(layer); + lock->lockForWrite(); + rv = layer->remove(path.constData(), owner); + updateStats(); + lock->unlock(); + } + + return rv; +} + +QString SharedMemoryLayer::socket() const +{ + QString socketPath(QLatin1String("qt/")); + +#ifdef Q_OS_UNIX + static bool pipePathExists = false; + if (!pipePathExists) { + if (QDir::temp().mkpath(socketPath)) + pipePathExists = true; + } + socketPath = QDir::temp().absoluteFilePath(socketPath); +#endif + + QByteArray key = qgetenv("QT_VALUESPACE_SHMLAYER"); + if (!key.isEmpty()) + socketPath.append(QString::fromLocal8Bit(key.constData(), key.length())); + else + socketPath.append(QLatin1String("valuespace_shmlayer")); + + return socketPath; +} + +void SharedMemoryLayer::sync() +{ + if (type == Client) { + if (QThread::currentThread() != thread()) + QMetaObject::invokeMethod(this, "doClientSync", Qt::BlockingQueuedConnection); + else + doClientSync(); + } else { + doServerTransmit(); + } +} + +void SharedMemoryLayer::doClientSync() +{ + QMutexLocker locker(&localLock); + + Q_ASSERT(1 == connections.count()); + + unsigned int waitId = lastSentId; + + if (lastRecvId >= waitId) + return; // Done + + // Send any outstanding messages + doClientTransmit(); + + // Get transmission socket + QLocalSocket *socket = static_cast((*connections.begin())->device()); + socket->flush(); + + // Wait + while (QLocalSocket::UnconnectedState != socket->state() && waitId > lastRecvId) + socket->waitForReadyRead(-1); +} + +QVariant SharedMemoryLayer::fromDatum(const NodeDatum * data) +{ + switch(data->type) { + case NodeDatum::Boolean: + Q_ASSERT(4 == data->len); + return QVariant(0 != (*(unsigned int *)data->data)); + case NodeDatum::Int: + Q_ASSERT(4 == data->len); + return QVariant((*(int *)data->data)); + case NodeDatum::UInt: + Q_ASSERT(4 == data->len); + return QVariant((*(unsigned int *)data->data)); + case NodeDatum::LongLong: + Q_ASSERT(8 == data->len); + return QVariant((*(long long*)data->data)); + case NodeDatum::ULongLong: + Q_ASSERT(8 == data->len); + return QVariant((*(unsigned long long*)data->data)); + case NodeDatum::Double: + Q_ASSERT(8 == data->len); + return QVariant((*(double*)data->data)); + case NodeDatum::Char: + Q_ASSERT(2 == data->len); + return QVariant(QChar((unsigned short)(0xFFFF & (*(unsigned int*)data->data)))); + case NodeDatum::String: + return QVariant(QString((QChar *)data->data, + data->len / sizeof(QChar))); + case NodeDatum::ByteArray: + return QVariant(QByteArray(data->data, data->len)); + case NodeDatum::SerializedType: + { + QByteArray ba(data->data, data->len); + QDataStream ds(ba); + QVariant rv; + ds >> rv; + return rv; + } + default: + qFatal("SharedMemoryLayer: Unknown datum type."); + return QVariant(); + } +} + +void SharedMemoryLayer::incNode(unsigned short node) +{ + QMutexLocker locker(&localLock); + + if(type == Server || node == INVALID_HANDLE) + return; + + QHash::Iterator iter = + m_nodeInterest.find(node); + if(iter == m_nodeInterest.end()) { + clientIndex[node >> 3] |= (1 << (node & 0x7)); + m_nodeInterest.insert(node, 1); + } else { + (*iter)++; + } +} + +void SharedMemoryLayer::decNode(unsigned short node) +{ + QMutexLocker locker(&localLock); + + if(type == Server || node == INVALID_HANDLE) + return; + + QHash::Iterator iter = + m_nodeInterest.find(node); + Q_ASSERT(iter != m_nodeInterest.end()); + (*iter)--; + if(!*iter) { + clientIndex[node >> 3] &= ~(1 << (node & 0x7)); + m_nodeInterest.erase(iter); + } +} + +Q_GLOBAL_STATIC(SharedMemoryLayer, sharedMemoryLayer); +SharedMemoryLayer * SharedMemoryLayer::instance() +{ + return sharedMemoryLayer(); +} + +typedef QSet WatchObjects; +Q_GLOBAL_STATIC(WatchObjects, watchObjects); + +void SharedMemoryLayer::doClientNotify(QValueSpacePublisher *publisher, const QByteArray &path, + bool interested) +{ + QMutexLocker locker(&localLock); + + // Invalid publisher. + if (!watchObjects()->contains(publisher)) + return; + + QByteArray emitPath; + + const QByteArray publisherPath = publisher->path().toUtf8(); + if (path.startsWith(publisherPath)) { + // path is under publisherPath + emitPath = path.mid(publisherPath.length()); + } else if (publisherPath.startsWith(path)) { + // path is a parent of publisherPath + } else { + // path is not for this publisher. + return; + } + + emitInterestChanged(publisher, QString::fromUtf8(emitPath.constData()), interested); + } + +bool SharedMemoryLayer::supportsInterestNotification() const +{ + return true; +} + +bool SharedMemoryLayer::notifyInterest(Handle handle, bool interested) +{ + QMutexLocker locker(&localLock); + + if (!valid) + return false; + Q_ASSERT(layer); + + ReadHandle *rhandle = rh(handle); + if (type == Client) { + if (todo.isEmpty()) + todo << newPackId(); + + todo << quint8(SHMLAYER_NOTIFY) << rhandle->path << interested; + + triggerTodo(); + } else { + doNotify(rhandle->path, 0, interested); + } + + return true; +} + +bool SharedMemoryLayer::setValue(QValueSpacePublisher *creator, Handle handle, const QString &path, + const QVariant &data) +{ + QMutexLocker locker(&localLock); + + ReadHandle *readHandle = reinterpret_cast(handle); + + if (!handles.values().contains(readHandle)) + return false; + + NodeOwner owner; + owner.data1 = 0; + owner.data2 = reinterpret_cast(creator); + + QByteArray fullPath(readHandle->path); + if (!fullPath.endsWith('/') && path != QLatin1String("/")) + fullPath.append('/'); + + fullPath.append(path.mid(1)); + + return setItem(owner, fullPath, data); +} + +bool SharedMemoryLayer::removeValue(QValueSpacePublisher *creator, + Handle handle, + const QString &path) +{ + QMutexLocker locker(&localLock); + + ReadHandle *readHandle = reinterpret_cast(handle); + + if (!handles.values().contains(readHandle)) + return false; + + NodeOwner owner; + owner.data1 = 0; + owner.data2 = reinterpret_cast(creator); + + QByteArray fullPath(readHandle->path); + if (!fullPath.endsWith('/')) + fullPath.append('/'); + + int index = 0; + while (index < path.length() && path[index] == QLatin1Char('/')) + ++index; + + fullPath.append(path.mid(index)); + + return remItems(owner, fullPath); +} + +bool SharedMemoryLayer::removeSubTree(QValueSpacePublisher *creator, Handle handle) +{ + QMutexLocker locker(&localLock); + + ReadHandle *readHandle = reinterpret_cast(handle); + + if (!handles.values().contains(readHandle)) + return false; + + NodeOwner owner; + owner.data1 = 0; + owner.data2 = reinterpret_cast(creator); + + return remItems(owner, readHandle->path); +} + +void SharedMemoryLayer::addWatch(QValueSpacePublisher *creator, Handle handle) +{ + QMutexLocker locker(&localLock); + + watchObjects()->insert(creator); + + ReadHandle *readHandle = reinterpret_cast(handle); + + if (!handles.values().contains(readHandle)) + return; + + NodeWatch owner; + owner.data1 = 0; + owner.data2 = reinterpret_cast(creator); + + setWatch(owner, readHandle->path); +} + +void SharedMemoryLayer::removeWatches(QValueSpacePublisher *creator, Handle parent) +{ + QMutexLocker locker(&localLock); + + watchObjects()->remove(creator); + + ReadHandle *readHandle = reinterpret_cast(parent); + + if (!handles.values().contains(readHandle)) + return; + + NodeWatch owner; + owner.data1 = 0; + owner.data2 = reinterpret_cast(creator); + + remWatch(owner, readHandle->path); +} + +#include "sharedmemorylayer.moc" +QTM_END_NAMESPACE +