From a2993c7cdb7bda77b60bf9cc7adc39452b241a51 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Tue, 13 Mar 2018 01:45:21 -0700 Subject: [PATCH] First version wof space working with Transaction & Collection --- .../src/workload/GameWorkloadRenderer.cpp | 6 +- interface/src/workload/GameWorkloadRenderer.h | 4 +- .../src/EntityTreeRenderer.cpp | 15 ++- .../src/EntityTreeRenderer.h | 2 +- libraries/workload/src/workload/Engine.cpp | 11 ++ libraries/workload/src/workload/Engine.h | 18 +++ libraries/workload/src/workload/Proxy.h | 6 +- libraries/workload/src/workload/Space.cpp | 116 +++++++++++++++--- libraries/workload/src/workload/Space.h | 55 +++------ .../workload/src/workload/Transaction.cpp | 86 ++++++------- libraries/workload/src/workload/Transaction.h | 31 +++-- 11 files changed, 229 insertions(+), 121 deletions(-) diff --git a/interface/src/workload/GameWorkloadRenderer.cpp b/interface/src/workload/GameWorkloadRenderer.cpp index 8509405eb0..c21f93d764 100644 --- a/interface/src/workload/GameWorkloadRenderer.cpp +++ b/interface/src/workload/GameWorkloadRenderer.cpp @@ -56,7 +56,7 @@ void GameSpaceToRender::run(const workload::WorkloadContextPointer& runContext, return; } - std::vector proxies(space->getNumAllocatedProxies()); + workload::Proxy::Vector proxies(space->getNumAllocatedProxies()); space->copyProxyValues(proxies.data(), (uint32_t)proxies.size()); workload::Views views(space->getNumViews()); @@ -130,9 +130,9 @@ void GameWorkloadRenderItem::showViews(bool show) { } -void GameWorkloadRenderItem::setAllProxies(const std::vector& proxies) { +void GameWorkloadRenderItem::setAllProxies(const workload::Proxy::Vector& proxies) { _myOwnProxies = proxies; - static const uint32_t sizeOfProxy = sizeof(workload::Space::Proxy); + static const uint32_t sizeOfProxy = sizeof(workload::Proxy); if (!_allProxiesBuffer) { _allProxiesBuffer = std::make_shared(sizeOfProxy); } diff --git a/interface/src/workload/GameWorkloadRenderer.h b/interface/src/workload/GameWorkloadRenderer.h index 7a0800d40c..36cb32f152 100644 --- a/interface/src/workload/GameWorkloadRenderer.h +++ b/interface/src/workload/GameWorkloadRenderer.h @@ -63,7 +63,7 @@ public: void showProxies(bool show); void showViews(bool show); - void setAllProxies(const std::vector& proxies); + void setAllProxies(const workload::Proxy::Vector& proxies); void setAllViews(const workload::Views& views); render::ItemKey getKey() const; @@ -71,7 +71,7 @@ public: protected: render::Item::Bound _bound; - std::vector _myOwnProxies; + workload::Proxy::Vector _myOwnProxies; gpu::BufferPointer _allProxiesBuffer; uint32_t _numAllProxies{ 0 }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 04e8c9a414..dfb888f9cb 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -281,8 +281,11 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } if (entity->getSpaceIndex() == -1) { std::unique_lock lock(_spaceLock); - workload::Space::Sphere sphere(entity->getWorldPosition(), entity->getBoundingRadius()); - int32_t spaceIndex = _space->createProxy(sphere); + auto spaceIndex = _space->allocateID(); + workload::Sphere sphere(entity->getWorldPosition(), entity->getBoundingRadius()); + workload::Transaction transaction; + transaction.reset(spaceIndex, sphere); + _space->enqueueTransaction(transaction); entity->setSpaceIndex(spaceIndex); connect(entity.get(), &EntityItem::spaceUpdate, this, &EntityTreeRenderer::handleSpaceUpdate, Qt::QueuedConnection); } @@ -427,17 +430,19 @@ void EntityTreeRenderer::update(bool simulate) { scene->enqueueTransaction(transaction); } } + workload::Transaction spaceTransaction; { // update proxies in the workload::Space std::unique_lock lock(_spaceLock); - _space->updateProxies(_spaceUpdates); + spaceTransaction.update(_spaceUpdates); _spaceUpdates.clear(); } { std::vector staleProxies; tree->swapStaleProxies(staleProxies); + spaceTransaction.remove(staleProxies); { std::unique_lock lock(_spaceLock); - _space->deleteProxies(staleProxies); + _space->enqueueTransaction(spaceTransaction); } } @@ -458,7 +463,7 @@ void EntityTreeRenderer::update(bool simulate) { void EntityTreeRenderer::handleSpaceUpdate(std::pair proxyUpdate) { std::unique_lock lock(_spaceLock); - _spaceUpdates.push_back(proxyUpdate); + _spaceUpdates.emplace_back(proxyUpdate.first, proxyUpdate.second); } bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 5840dc6832..fa9c2ac6d2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -270,7 +270,7 @@ private: mutable std::mutex _spaceLock; workload::SpacePointer _space{ new workload::Space() }; - std::vector _spaceUpdates; + workload::Transaction::Updates _spaceUpdates; }; diff --git a/libraries/workload/src/workload/Engine.cpp b/libraries/workload/src/workload/Engine.cpp index 525872f190..0bb9c73fa0 100644 --- a/libraries/workload/src/workload/Engine.cpp +++ b/libraries/workload/src/workload/Engine.cpp @@ -29,6 +29,7 @@ namespace workload { using JobModel = Task::ModelI; void build(JobModel& model, const Varying& in, Varying& out) { model.addJob("setupViews", in); + model.addJob("updateSpace"); const auto regionChanges = model.addJob("regionTracker"); model.addJob("regionState", regionChanges); @@ -38,5 +39,15 @@ namespace workload { Engine::Engine(const WorkloadContextPointer& context) : Task("Engine", EngineBuilder::JobModel::create()), _context(context) { } + + + void PerformSpaceTransaction::configure(const Config& config) { + + } + void PerformSpaceTransaction::run(const WorkloadContextPointer& context) { + context->_space->enqueueFrame(); + context->_space->processTransactionQueue(); + } + } // namespace workload diff --git a/libraries/workload/src/workload/Engine.h b/libraries/workload/src/workload/Engine.h index aa45b03a20..773cfcb752 100644 --- a/libraries/workload/src/workload/Engine.h +++ b/libraries/workload/src/workload/Engine.h @@ -50,6 +50,24 @@ namespace workload { }; using EnginePointer = std::shared_ptr; + class PerformSpaceTransactionConfig : public Job::Config { + Q_OBJECT + public: + signals : + void dirty(); + + protected: + }; + + class PerformSpaceTransaction { + public: + using Config = PerformSpaceTransactionConfig; + using JobModel = Job::Model; + + void configure(const Config& config); + void run(const WorkloadContextPointer& context); + protected: + }; } // namespace workload #endif // hifi_workload_Space_h diff --git a/libraries/workload/src/workload/Proxy.h b/libraries/workload/src/workload/Proxy.h index b7f8094b00..9248c2bb31 100644 --- a/libraries/workload/src/workload/Proxy.h +++ b/libraries/workload/src/workload/Proxy.h @@ -21,10 +21,12 @@ public: Proxy(const Sphere& s) : sphere(s) {} Sphere sphere; - uint8_t region{ Region::UNKNOWN }; - uint8_t prevRegion{ Region::UNKNOWN }; + uint8_t region{ Region::INVALID }; + uint8_t prevRegion{ Region::INVALID }; uint16_t _padding; uint32_t _paddings[3]; + + using Vector = std::vector; }; diff --git a/libraries/workload/src/workload/Space.cpp b/libraries/workload/src/workload/Space.cpp index c2323fc0a1..45192b2a10 100644 --- a/libraries/workload/src/workload/Space.cpp +++ b/libraries/workload/src/workload/Space.cpp @@ -20,11 +20,80 @@ using namespace workload; -void Space::clear() { - _proxies.clear(); - _freeIndices.clear(); +Space::Space() : Collection() { + } + +void Space::processTransactionFrame(const Transaction& transaction) { + std::unique_lock lock(_proxiesMutex); + // Here we should be able to check the value of last ProxyID allocated + // and allocate new proxies accordingly + ProxyID maxID = _IDAllocator.getNumAllocatedIndices(); + if (maxID > _proxies.size()) { + _proxies.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 + processResets(transaction._resetItems); + + // Update the numItemsAtomic counter AFTER the reset changes went through + // _numAllocatedItems.exchange(maxID); + + // updates + processUpdates(transaction._updatedItems); + + // removes + processRemoves(transaction._removedItems); + + + // Update the numItemsAtomic counter AFTER the pending changes went through + // _numAllocatedItems.exchange(maxID); +} + +void Space::processResets(const Transaction::Resets& transactions) { + for (auto& reset : transactions) { + // Access the true item + auto ProxyID = std::get<0>(reset); + auto& item = _proxies[ProxyID]; + + // Reset the item with a new payload + item.sphere = (std::get<1>(reset)); + item.prevRegion = item.region = Region::UNKNOWN; + } +} + +void Space::processRemoves(const Transaction::Removes& transactions) { + for (auto removedID : transactions) { + _IDAllocator.freeIndex(removedID); + + // Access the true item + auto& item = _proxies[removedID]; + + // Kill it + item.prevRegion = item.region = Region::INVALID; + } +} + +void Space::processUpdates(const Transaction::Updates& transactions) { + for (auto& update : transactions) { + auto updateID = std::get<0>(update); + if (updateID == INVALID_PROXY_ID) { + continue; + } + + // Access the true item + auto& item = _proxies[updateID]; + + // Update the item + // item.update(std::get<1>(update)); + item.sphere = (std::get<1>(update)); + item.prevRegion = item.region = Region::UNKNOWN; + } +} +/* int32_t Space::createProxy(const Space::Sphere& newSphere) { if (_freeIndices.empty()) { _proxies.emplace_back(Space::Proxy(newSphere)); @@ -37,32 +106,25 @@ int32_t Space::createProxy(const Space::Sphere& newSphere) { _proxies[index].prevRegion = Region::UNKNOWN; return index; } -} - +}*/ +/* void Space::deleteProxies(const std::vector& deadIndices) { for (uint32_t i = 0; i < deadIndices.size(); ++i) { deleteProxy(deadIndices[i]); } } - -void Space::updateProxies(const std::vector& changedProxies) { +*/ +/*void Space::updateProxies(const std::vector& changedProxies) { for (uint32_t i = 0; i < changedProxies.size(); ++i) { int32_t proxyId = changedProxies[i].first; if (proxyId > -1 && proxyId < (int32_t)_proxies.size()) { updateProxy(changedProxies[i].first, changedProxies[i].second); } } -} - -void Space::setViews(const Views& views) { - _views = views; -} - -void Space::copyViews(std::vector& copy) const { - copy = _views; -} +}*/ void Space::categorizeAndGetChanges(std::vector& changes) { + std::unique_lock lock(_proxiesMutex); uint32_t numProxies = (uint32_t)_proxies.size(); uint32_t numViews = (uint32_t)_views.size(); for (uint32_t i = 0; i < numProxies; ++i) { @@ -92,7 +154,7 @@ void Space::categorizeAndGetChanges(std::vector& changes) { } // private -void Space::deleteProxy(int32_t proxyId) { +/*void Space::deleteProxy(int32_t proxyId) { if (proxyId > -1 && proxyId < (int32_t)_proxies.size()) { if (proxyId == (int32_t)_proxies.size() - 1) { // remove proxy on back @@ -110,9 +172,10 @@ void Space::deleteProxy(int32_t proxyId) { _freeIndices.push_back(proxyId); } } -} +}*/ uint32_t Space::copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const { + std::unique_lock lock(_proxiesMutex); auto numCopied = std::min(numDestProxies, (uint32_t)_proxies.size()); memcpy(proxies, _proxies.data(), numCopied * sizeof(Proxy)); return numCopied; @@ -120,9 +183,24 @@ uint32_t Space::copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const { // private -void Space::updateProxy(int32_t proxyId, const Space::Sphere& newSphere) { +/*void Space::updateProxy(int32_t proxyId, const Space::Sphere& newSphere) { if (proxyId > -1 && proxyId < (int32_t)_proxies.size()) { _proxies[proxyId].sphere = newSphere; } +}*/ + +void Space::clear() { + std::unique_lock lock(_proxiesMutex); + _IDAllocator.clear(); + _proxies.clear(); + _views.clear(); } + +void Space::setViews(const Views& views) { + _views = views; +} + +void Space::copyViews(std::vector& copy) const { + copy = _views; +} diff --git a/libraries/workload/src/workload/Space.h b/libraries/workload/src/workload/Space.h index 699d44f7a1..200d8d9ac3 100644 --- a/libraries/workload/src/workload/Space.h +++ b/libraries/workload/src/workload/Space.h @@ -23,7 +23,7 @@ namespace workload { -class Space { +class Space : public Collection { public: using ProxyUpdate = std::pair; @@ -35,55 +35,40 @@ public: uint8_t prevRegion { 0 }; }; - Space() {} + Space(); - // 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); + void setViews(const Views& views); uint32_t getNumViews() const { return (uint32_t)(_views.size()); } void copyViews(std::vector& copy) const; - - uint32_t getNumObjects() const { return (uint32_t)(_proxies.size() - _freeIndices.size()); } - uint32_t getNumAllocatedProxies() const { return (uint32_t)(_proxies.size()); } + uint32_t getNumObjects() const { return _IDAllocator.getNumLiveIndices(); } // (uint32_t)(_proxies.size() - _freeIndices.size()); } + uint32_t getNumAllocatedProxies() const { return (uint32_t)(_IDAllocator.getNumAllocatedIndices()); } void categorizeAndGetChanges(std::vector& changes); uint32_t copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const; + void clear(); private: + virtual void processTransactionFrame(const Transaction& transaction); + void processResets(const Transaction::Resets& transactions); + void processRemoves(const Transaction::Removes& transactions); + void processUpdates(const Transaction::Updates& transactions); - void clear(); - int32_t createProxy(const Sphere& sphere); - void deleteProxies(const IndexVector& deadIndices); - void updateProxies(const std::vector& changedProxies); + // 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); + // void deleteProxy(int32_t proxyId); + // void updateProxy(int32_t proxyId, const Sphere& sphere); + + + // The database of proxies is protected for editing by a mutex + mutable std::mutex _proxiesMutex; + Proxy::Vector _proxies; - std::vector _proxies; Views _views; - IndexVector _freeIndices; }; using SpacePointer = std::shared_ptr; diff --git a/libraries/workload/src/workload/Transaction.cpp b/libraries/workload/src/workload/Transaction.cpp index 3a5e0b31d8..063551763a 100644 --- a/libraries/workload/src/workload/Transaction.cpp +++ b/libraries/workload/src/workload/Transaction.cpp @@ -13,21 +13,16 @@ 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::reset(ProxyID id, const ProxyPayload& payload) { + _resetItems.emplace_back(Reset{ id, payload }); } -void Transaction::removeItem(ItemID id) { +void Transaction::remove(ProxyID id) { _removedItems.emplace_back(id); } -void Transaction::updateItem(ItemID id, const UpdateFunctorPointer& functor) { - _updatedItems.emplace_back(id, functor); +void Transaction::update(ProxyID id, const ProxyPayload& payload) { + _updatedItems.emplace_back(id, payload); } void Transaction::reserve(const std::vector& transactionContainer) { @@ -77,6 +72,18 @@ void copyElements(T& target, const T& source) { } +void Transaction::reset(const Resets& resets) { + copyElements(_resetItems, resets); +} + +void Transaction::remove(const Removes& removes) { + copyElements(_removedItems, removes); +} + +void Transaction::update(const Updates& updates) { + copyElements(_updatedItems, updates); +} + void Transaction::merge(Transaction&& transaction) { moveElements(_resetItems, transaction._resetItems); moveElements(_removedItems, transaction._removedItems); @@ -99,23 +106,22 @@ void Transaction::clear() { Collection::Collection() { - _items.push_back(Item()); // add the itemID #0 to nothing + //_items.push_back(Item()); // add the ProxyID #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); +ProxyID Collection::allocateID() { + // Just increment and return the previous value initialized at 0 + return _IDAllocator.allocateIndex(); } -bool Collection::isAllocatedID(const ItemID& id) const { - return Item::isValidID(id) && (id < _numAllocatedItems.load()); +bool Collection::isAllocatedID(const ProxyID& id) const { + return _IDAllocator.checkIndex(id); } -/// Enqueue change batch to the scene +/// Enqueue change batch to the Collection void Collection::enqueueTransaction(const Transaction& transaction) { std::unique_lock lock(_transactionQueueMutex); _transactionQueue.emplace_back(transaction); @@ -127,7 +133,6 @@ void Collection::enqueueTransaction(Transaction&& transaction) { } uint32_t Collection::enqueueFrame() { - PROFILE_RANGE(render, __FUNCTION__); TransactionQueue localTransactionQueue; { std::unique_lock lock(_transactionQueueMutex); @@ -146,8 +151,6 @@ uint32_t Collection::enqueueFrame() { void Collection::processTransactionQueue() { - PROFILE_RANGE(render, __FUNCTION__); - static TransactionFrames queuedFrames; { // capture the queued frames and clear the queue @@ -163,13 +166,12 @@ void Collection::processTransactionQueue() { queuedFrames.clear(); } -void Collection::processTransactionFrame(const Transaction& transaction) { - PROFILE_RANGE(render, __FUNCTION__); - { +//void Collection::processTransactionFrame(const Transaction& transaction) { + /** std::unique_lock lock(_itemsMutex); - // Here we should be able to check the value of last ItemID allocated + // Here we should be able to check the value of last ProxyID allocated // and allocate new items accordingly - ItemID maxID = _IDAllocator.load(); + ProxyID maxID = _IDAllocator.getNumAllocatedIndices(); if (maxID > _items.size()) { _items.resize(maxID + 100); // allocate the maxId and more } @@ -195,32 +197,32 @@ void Collection::processTransactionFrame(const Transaction& transaction) { // Update the numItemsAtomic counter AFTER the pending changes went through _numAllocatedItems.exchange(maxID); - } -} + }*/ +//} -void Collection::resetItems(const Transaction::Resets& transactions) { - for (auto& reset : transactions) { +//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]; + auto ProxyID = std::get<0>(reset); + auto& item = _items[ProxyID]; // Reset the item with a new payload item.resetPayload(std::get<1>(reset)); - } -} + }*/ +//} -void Collection::removeItems(const Transaction::Removes& transactions) { - for (auto removedID : transactions) { +//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) { +//void Collection::updateItems(const Transaction::Updates& transactions) { + /* for (auto& update : transactions) { auto updateID = std::get<0>(update); if (updateID == Item::INVALID_ITEM_ID) { continue; @@ -231,5 +233,5 @@ void Collection::updateItems(const Transaction::Updates& transactions) { // Update the item item.update(std::get<1>(update)); - } -} \ No newline at end of file + }*/ +//} \ No newline at end of file diff --git a/libraries/workload/src/workload/Transaction.h b/libraries/workload/src/workload/Transaction.h index 098e953f36..d078989f48 100644 --- a/libraries/workload/src/workload/Transaction.h +++ b/libraries/workload/src/workload/Transaction.h @@ -38,7 +38,7 @@ namespace workload { Index _nextNewIndex{ 0 }; bool checkIndex(Index index) const { return ((index >= 0) && (index < _nextNewIndex)); } - Index getNumIndices() const { return _nextNewIndex - (Index)_freeIndices.size(); } + Index getNumLiveIndices() const { return _nextNewIndex - (Index)_freeIndices.size(); } Index getNumFreeIndices() const { return (Index)_freeIndices.size(); } Index getNumAllocatedIndices() const { return _nextNewIndex; } @@ -64,6 +64,7 @@ namespace workload { void freeIndex(Index index) { if (checkIndex(index)) { _freeIndices.push_back(index); + //std::sort(_freeIndices.begin(), _freeIndices.end()); } } @@ -79,7 +80,7 @@ namespace workload { using IndexVector = indexed_container::Indices; using ProxyID = Index; - + const ProxyID INVALID_PROXY_ID{ indexed_container ::INVALID_INDEX }; // Transaction is the mechanism to make any change to the Space. // Whenever a new proxy need to be reset, @@ -94,16 +95,27 @@ class Transaction { public: using ProxyPayload = Sphere; + using Reset = std::tuple; + using Remove = ProxyID; + using Update = std::tuple; + + using Resets = std::vector; + using Removes = std::vector; + using Updates = std::vector; + Transaction() {} ~Transaction() {} // Proxy transactions void reset(ProxyID id, const ProxyPayload& sphere); + void reset(const Resets& resets); void remove(ProxyID id); + void remove(const Removes& removes); bool hasRemovals() const { return !_removedItems.empty(); } void update(ProxyID id, const ProxyPayload& sphere); - + void update(const Updates& updates); + void reserve(const std::vector& transactionContainer); void merge(const std::vector& transactionContainer); void merge(std::vector&& transactionContainer); @@ -113,13 +125,6 @@ public: 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; @@ -129,6 +134,8 @@ typedef std::vector TransactionQueue; class Collection { public: + Collection(); + ~Collection(); // This call is thread safe, can be called from anywhere to allocate a new ID ProxyID allocateID(); @@ -149,7 +156,7 @@ public: uint32_t enqueueFrame(); // Process the pending transactions queued - void processTransactionQueue(); + virtual void processTransactionQueue(); protected: @@ -168,7 +175,7 @@ protected: uint32_t _transactionFrameNumber{ 0 }; // Process one transaction frame - void processTransactionFrame(const Transaction& transaction); + virtual void processTransactionFrame(const Transaction& transaction) = 0; }; } // namespace workload