From ea9dee1286f5fcd361296ecccd02f705f33be145 Mon Sep 17 00:00:00 2001 From: samcake Date: Mon, 12 Mar 2018 17:49:45 -0700 Subject: [PATCH] FIrst step introducing the Transaction model to access the Proxy of the space --- libraries/workload/src/workload/Proxy.h | 38 +++ libraries/workload/src/workload/Space.h | 49 ++-- .../workload/src/workload/Transaction.cpp | 235 ++++++++++++++++++ libraries/workload/src/workload/Transaction.h | 166 +++++++++++++ 4 files changed, 468 insertions(+), 20 deletions(-) create mode 100644 libraries/workload/src/workload/Proxy.h create mode 100644 libraries/workload/src/workload/Transaction.cpp create mode 100644 libraries/workload/src/workload/Transaction.h diff --git a/libraries/workload/src/workload/Proxy.h b/libraries/workload/src/workload/Proxy.h new file mode 100644 index 0000000000..bf3de6e4c9 --- /dev/null +++ b/libraries/workload/src/workload/Proxy.h @@ -0,0 +1,38 @@ +// +// Proxy.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.01.30 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_workload_Proxy_h +#define hifi_workload_Proxy_h + +#include "View.h" + +namespace workload { + +using Index = int32_t; +using ProxyID = Index; + +using IndexVector = std::vector; + +class Proxy { +public: + Proxy() : sphere(0.0f) {} + Proxy(const Sphere& s) : sphere(s) {} + + Sphere sphere; + uint8_t region{ Region::UNKNOWN }; + uint8_t prevRegion{ Region::UNKNOWN }; + uint16_t _padding; + uint32_t _paddings[3]; +}; + + +} // namespace workload + +#endif // hifi_workload_Proxy_h diff --git a/libraries/workload/src/workload/Space.h b/libraries/workload/src/workload/Space.h index 0acd69194b..699d44f7a1 100644 --- a/libraries/workload/src/workload/Space.h +++ b/libraries/workload/src/workload/Space.h @@ -19,29 +19,14 @@ #include #include -#include "View.h" - +#include "Transaction.h" namespace workload { -using IndexVector = std::vector; - class Space { public: - using Sphere = glm::vec4; // = center, w = radius using ProxyUpdate = std::pair; - class Proxy { - public: - Proxy() : sphere(0.0f) {} - Proxy(const Sphere& s) : sphere(s) {} - Sphere sphere; - uint8_t region { Region::UNKNOWN }; - uint8_t prevRegion { Region::UNKNOWN }; - uint16_t _padding; - uint32_t _paddings[3]; - }; - class Change { public: Change(int32_t i, uint32_t c, uint32_t p) : proxyId(i), region(c), prevRegion(p) {} @@ -52,10 +37,27 @@ public: Space() {} - void clear(); - int32_t createProxy(const Sphere& sphere); - void deleteProxies(const IndexVector& deadIndices); - void updateProxies(const std::vector& changedProxies); + // This call is thread safe, can be called from anywhere to allocate a new ID + ProxyID allocateID(); + + // Check that the ID is valid and allocated for this space, this a threadsafe call + bool isAllocatedID(const ProxyID& id) const; + + // THis is the total number of allocated proxies, this a threadsafe call + Index getNumAllocatedProxies() const { return _numAllocatedProxies.load(); } + + // Enqueue transaction to the space + void enqueueTransaction(const Transaction& transaction); + + // Enqueue transaction to the space + void enqueueTransaction(Transaction&& transaction); + + // Enqueue end of frame transactions boundary + uint32_t enqueueFrame(); + + // Process the pending transactions queued + void processTransactionQueue(); + void setViews(const std::vector& views); uint32_t getNumViews() const { return (uint32_t)(_views.size()); } @@ -69,6 +71,13 @@ public: uint32_t copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const; private: + + + void clear(); + int32_t createProxy(const Sphere& sphere); + void deleteProxies(const IndexVector& deadIndices); + void updateProxies(const std::vector& changedProxies); + void deleteProxy(int32_t proxyId); void updateProxy(int32_t proxyId, const Sphere& sphere); diff --git a/libraries/workload/src/workload/Transaction.cpp b/libraries/workload/src/workload/Transaction.cpp new file mode 100644 index 0000000000..3a5e0b31d8 --- /dev/null +++ b/libraries/workload/src/workload/Transaction.cpp @@ -0,0 +1,235 @@ +// +// Transaction.cpp +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.12 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Transaction.h" + +using namespace workload; + + +void Transaction::resetItem(ItemID id, PayloadPointer& payload) { + if (payload) { + _resetItems.emplace_back(Reset{ id, payload }); + } else { + qCDebug(renderlogging) << "WARNING: Transaction::resetItem with a null payload!"; + removeItem(id); + } +} + +void Transaction::removeItem(ItemID id) { + _removedItems.emplace_back(id); +} + +void Transaction::updateItem(ItemID id, const UpdateFunctorPointer& functor) { + _updatedItems.emplace_back(id, functor); +} + +void Transaction::reserve(const std::vector& transactionContainer) { + size_t resetItemsCount = 0; + size_t removedItemsCount = 0; + size_t updatedItemsCount = 0; + + for (const auto& transaction : transactionContainer) { + resetItemsCount += transaction._resetItems.size(); + removedItemsCount += transaction._removedItems.size(); + updatedItemsCount += transaction._updatedItems.size(); + } + + _resetItems.reserve(resetItemsCount); + _removedItems.reserve(removedItemsCount); + _updatedItems.reserve(updatedItemsCount); +} + +void Transaction::merge(const std::vector& transactionContainer) { + reserve(transactionContainer); + for (const auto& transaction : transactionContainer) { + merge(transaction); + } +} + + +void Transaction::merge(std::vector&& transactionContainer) { + reserve(transactionContainer); + auto begin = std::make_move_iterator(transactionContainer.begin()); + auto end = std::make_move_iterator(transactionContainer.end()); + for (auto itr = begin; itr != end; ++itr) { + merge(*itr); + } + transactionContainer.clear(); +} + + +template +void moveElements(T& target, T& source) { + target.insert(target.end(), std::make_move_iterator(source.begin()), std::make_move_iterator(source.end())); + source.clear(); +} + +template +void copyElements(T& target, const T& source) { + target.insert(target.end(), source.begin(), source.end()); +} + + +void Transaction::merge(Transaction&& transaction) { + moveElements(_resetItems, transaction._resetItems); + moveElements(_removedItems, transaction._removedItems); + moveElements(_updatedItems, transaction._updatedItems); +} + +void Transaction::merge(const Transaction& transaction) { + copyElements(_resetItems, transaction._resetItems); + copyElements(_removedItems, transaction._removedItems); + copyElements(_updatedItems, transaction._updatedItems); +} + +void Transaction::clear() { + _resetItems.clear(); + _removedItems.clear(); + _updatedItems.clear(); +} + + + + +Collection::Collection() { + _items.push_back(Item()); // add the itemID #0 to nothing +} + +Collection::~Collection() { + qCDebug(renderlogging) << "Scene::~Scene()"; +} + +ItemID Collection::allocateID() { + // Just increment and return the proevious value initialized at 0 + return _IDAllocator.fetch_add(1); +} + +bool Collection::isAllocatedID(const ItemID& id) const { + return Item::isValidID(id) && (id < _numAllocatedItems.load()); +} + +/// Enqueue change batch to the scene +void Collection::enqueueTransaction(const Transaction& transaction) { + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.emplace_back(transaction); +} + +void Collection::enqueueTransaction(Transaction&& transaction) { + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.emplace_back(std::move(transaction)); +} + +uint32_t Collection::enqueueFrame() { + PROFILE_RANGE(render, __FUNCTION__); + TransactionQueue localTransactionQueue; + { + std::unique_lock lock(_transactionQueueMutex); + localTransactionQueue.swap(_transactionQueue); + } + + Transaction consolidatedTransaction; + consolidatedTransaction.merge(std::move(localTransactionQueue)); + { + std::unique_lock lock(_transactionFramesMutex); + _transactionFrames.push_back(consolidatedTransaction); + } + + return ++_transactionFrameNumber; +} + + +void Collection::processTransactionQueue() { + PROFILE_RANGE(render, __FUNCTION__); + + static TransactionFrames queuedFrames; + { + // capture the queued frames and clear the queue + std::unique_lock lock(_transactionFramesMutex); + queuedFrames.swap(_transactionFrames); + } + + // go through the queue of frames and process them + for (auto& frame : queuedFrames) { + processTransactionFrame(frame); + } + + queuedFrames.clear(); +} + +void Collection::processTransactionFrame(const Transaction& transaction) { + PROFILE_RANGE(render, __FUNCTION__); + { + std::unique_lock lock(_itemsMutex); + // Here we should be able to check the value of last ItemID allocated + // and allocate new items accordingly + ItemID maxID = _IDAllocator.load(); + if (maxID > _items.size()) { + _items.resize(maxID + 100); // allocate the maxId and more + } + // Now we know for sure that we have enough items in the array to + // capture anything coming from the transaction + + // resets and potential NEW items + resetItems(transaction._resetItems); + + // Update the numItemsAtomic counter AFTER the reset changes went through + _numAllocatedItems.exchange(maxID); + + // updates + updateItems(transaction._updatedItems); + + // removes + removeItems(transaction._removedItems); + + // add transitions + transitionItems(transaction._addedTransitions); + reApplyTransitions(transaction._reAppliedTransitions); + queryTransitionItems(transaction._queriedTransitions); + + // Update the numItemsAtomic counter AFTER the pending changes went through + _numAllocatedItems.exchange(maxID); + } +} + +void Collection::resetItems(const Transaction::Resets& transactions) { + for (auto& reset : transactions) { + // Access the true item + auto itemId = std::get<0>(reset); + auto& item = _items[itemId]; + + // Reset the item with a new payload + item.resetPayload(std::get<1>(reset)); + } +} + +void Collection::removeItems(const Transaction::Removes& transactions) { + for (auto removedID : transactions) { + // Access the true item + auto& item = _items[removedID]; + + // Kill it + item.kill(); + } +} + +void Collection::updateItems(const Transaction::Updates& transactions) { + for (auto& update : transactions) { + auto updateID = std::get<0>(update); + if (updateID == Item::INVALID_ITEM_ID) { + continue; + } + + // Access the true item + auto& item = _items[updateID]; + + // Update the item + item.update(std::get<1>(update)); + } +} \ No newline at end of file diff --git a/libraries/workload/src/workload/Transaction.h b/libraries/workload/src/workload/Transaction.h new file mode 100644 index 0000000000..87bf0e4209 --- /dev/null +++ b/libraries/workload/src/workload/Transaction.h @@ -0,0 +1,166 @@ +// +// Transaction.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.12 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_workload_Transaction_h +#define hifi_workload_Transaction_h + +#include +#include +#include + +#include "Proxy.h" + + +namespace workload { + +// Transaction is the mechanism to make any change to the Space. +// Whenever a new proxy need to be reset, +// or when an proxy changes its position or its size +// or when an proxy is destroyed +// These changes must be expressed through the corresponding command from the Transaction +// The Transaction is then queued on the Space so all the pending transactions can be consolidated and processed at the time +// of updating the space at the Frame boundary. +// +class Transaction { + friend class Space; +public: + using ProxyPayload = Sphere; + + Transaction() {} + ~Transaction() {} + + // Proxy transactions + void reset(ProxyID id, const ProxyPayload& sphere); + void remove(ProxyID id); + bool hasRemovals() const { return !_removedItems.empty(); } + + void update(ProxyID id, const ProxyPayload& sphere); + + void reserve(const std::vector& transactionContainer); + void merge(const std::vector& transactionContainer); + void merge(std::vector&& transactionContainer); + void merge(const Transaction& transaction); + void merge(Transaction&& transaction); + void clear(); + +protected: + + using Reset = std::tuple; + using Remove = ProxyID; + using Update = std::tuple; + + using Resets = std::vector; + using Removes = std::vector; + using Updates = std::vector; + + Resets _resetItems; + Removes _removedItems; + Updates _updatedItems; +}; +typedef std::vector TransactionQueue; + +namespace indexed_container { + + using Index = int32_t; + const Index MAXIMUM_INDEX{ 1 << 30 }; + const Index INVALID_INDEX{ -1 }; + using Indices = std::vector< Index >; + + template + class Allocator { + public: + Allocator() {} + Indices _freeIndices; + std::atomic _nextNewIndex{ 0 }; + std::atomic _numFreeIndices{ 0 }; + + bool checkIndex(Index index) const { return ((index >= 0) && (index < _nextNewIndex.load())); } + Index getNumIndices() const { return _nextNewIndex - (Index)_freeIndices.size(); } + Index getNumFreeIndices() const { return (Index)_freeIndices.size(); } + Index getNumAllocatedIndices() const { return _nextNewIndex.load(); } + + Index allocateIndex() { + if (_freeIndices.empty()) { + Index index = _nextNewIndex; + if (index >= MaxNumElements) { + // abort! we are trying to go overboard with the total number of allocated elements + assert(false); + // This should never happen because Bricks are allocated along with the cells and there + // is already a cap on the cells allocation + return INVALID_INDEX; + } + _nextNewIndex++; + return index; + } else { + Index index = _freeIndices.back(); + _freeIndices.pop_back(); + return index; + } + } + + void freeIndex(Index index) { + if (checkIndex(index)) { + _freeIndices.push_back(index); + } + } + + void clear() { + _freeIndices.clear(); + _nextNewIndex = 0; + } + }; +} + +class Collection { +public: + + // This call is thread safe, can be called from anywhere to allocate a new ID + ProxyID allocateID(); + + // Check that the ID is valid and allocated for this space, this a threadsafe call + bool isAllocatedID(const ProxyID& id) const; + + // THis is the total number of allocated proxies, this a threadsafe call + Index getNumAllocatedProxies() const { return _numAllocatedProxies.load(); } + + // Enqueue transaction to the space + void enqueueTransaction(const Transaction& transaction); + + // Enqueue transaction to the space + void enqueueTransaction(Transaction&& transaction); + + // Enqueue end of frame transactions boundary + uint32_t enqueueFrame(); + + // Process the pending transactions queued + void processTransactionQueue(); + +protected: + + // Thread safe elements that can be accessed from anywhere + std::atomic _IDAllocator{ 1 }; // first valid itemID will be One + std::atomic _numAllocatedItems{ 1 }; // num of allocated items, matching the _items.size() + std::mutex _transactionQueueMutex; + TransactionQueue _transactionQueue; + + + std::mutex _transactionFramesMutex; + using TransactionFrames = std::vector; + TransactionFrames _transactionFrames; + uint32_t _transactionFrameNumber{ 0 }; + + // Process one transaction frame + void processTransactionFrame(const Transaction& transaction); +}; + +} // namespace workload + +#endif // hifi_workload_Transaction_h \ No newline at end of file