diff --git a/include/ao/sim/turbAtmosphere.hpp b/include/ao/sim/turbAtmosphere.hpp index 728931f77..a34e9bc7a 100644 --- a/include/ao/sim/turbAtmosphere.hpp +++ b/include/ao/sim/turbAtmosphere.hpp @@ -736,7 +736,7 @@ int turbAtmosphere::shift( improc::milkImage &milkPh { } - // Don't use OMP if no multiple layers b/c it seems to make it use multiple threads in some awful way + // Don't use OMP if not multiple layers b/c it seems to make it use multiple threads in some awful way if( m_layers.size() > 1 ) { // clang-format off @@ -748,10 +748,7 @@ int turbAtmosphere::shift( improc::milkImage &milkPh } else { - for( size_t j = 0; j < m_layers.size(); ++j ) - { - m_layers[j].shift( dt ); - } + m_layers[0].shift( dt ); } milkPhase.setWrite(); diff --git a/include/ao/sim/turbLayer.hpp b/include/ao/sim/turbLayer.hpp index 89baa69d2..19b318d95 100644 --- a/include/ao/sim/turbLayer.hpp +++ b/include/ao/sim/turbLayer.hpp @@ -293,23 +293,32 @@ void turbLayer::shift( realT dt ) ddx = m_x0 + m_dx * dt; ddy = m_y0 + m_dy * dt; - wdx = (int)trunc( ddx ); + wdx = static_cast( std::floor( ddx ) ); ddx -= wdx; - wdy = (int)trunc( ddy ); + wdy = static_cast( std::floor( ddy ) ); ddy -= wdy; - wdx %= m_scrnSz; - wdy %= m_scrnSz; + wdx %= static_cast( m_scrnSz ); + wdy %= static_cast( m_scrnSz ); + + if( wdx < 0 ) + wdx += m_scrnSz; + if( wdy < 0 ) + wdy += m_scrnSz; if( dt == 0 ) { m_shiftPhase = m_phase; + // Prime both the whole-pixel and shifted buffers so the first + // subsequent sub-pixel shift has valid input data. + improc::imageShiftWP( m_shiftPhaseWP, m_phase, wdx, wdy ); + m_shiftPhase = m_shiftPhaseWP; } else { // Check for a new whole-pixel shift - if( wdx != m_last_wdx || wdy != m_last_wdy ) + if( wdx != m_last_wdx || wdy != m_last_wdy || m_last_wdx == m_scrnSz + 1 || m_last_wdy == m_scrnSz + 1 ) { // Need a whole pixel shift (this also extracts the m_wfSz + m_buffSz subarray) improc::imageShiftWP( m_shiftPhaseWP, m_phase, wdx, wdy ); diff --git a/include/sigproc/circularBuffer.hpp b/include/sigproc/circularBuffer.hpp index b1ddac2f9..4ebca16a4 100644 --- a/include/sigproc/circularBuffer.hpp +++ b/include/sigproc/circularBuffer.hpp @@ -27,6 +27,7 @@ #ifndef sigproc_circularBuffer #define sigproc_circularBuffer +#include #include namespace mx @@ -80,20 +81,20 @@ class circularBufferBase public: typedef _derivedT derivedT; ///< The child class - typedef _storedT storedT; ///< The type stored in the circular buffer - typedef _indexT indexT; ///< The index type, also used for sizes + typedef _storedT storedT; ///< The type stored in the circular buffer + typedef _indexT indexT; ///< The index type, also used for sizes - static_assert(std::is_signed_v<_indexT> == true, "circularBuffer indexT must be signed"); + static_assert( std::is_signed_v<_indexT> == true, "circularBuffer indexT must be signed" ); protected: std::vector m_buffer; ///< The circular buffer storage - indexT m_maxEntries{ 0 }; ///< The maximum number of entries to allow in the buffer before wrapping - indexT m_nextEntry{ 0 }; ///< Index into m_buffer of the next entry. This is the oldest entry in the buffer. - indexT m_latest{ 0 }; ///< Index into m_buff of the latest entry. This is the newest entry in the buffer. + indexT m_maxEntries{ 0 }; ///< The maximum number of entries to allow in the buffer before wrapping + indexT m_nextEntry{ 0 }; ///< Index into m_buffer of the next entry. This is the oldest entry in the buffer. + indexT m_latest{ 0 }; ///< Index into m_buff of the latest entry. This is the newest entry in the buffer. - uint64_t m_mono{ 0 }; /**< A monotonic counter, which is incremented for each new entry, - and reset on call to maxEntries.*/ + uint64_t m_mono{ 0 }; /**< A monotonic counter, which is incremented for each new entry, + and reset on call to maxEntries.*/ public: /// Default c'tor @@ -363,11 +364,11 @@ class circularBufferBranch : public circularBufferBasem_buffer[_idx + this->m_buffer.size()]; } - else if( _idx > this->m_buffer.size()-1 ) + else if( _idx > this->m_buffer.size() - 1 ) { return this->m_buffer[_idx - this->m_buffer.size()]; } @@ -389,11 +390,11 @@ class circularBufferBranch : public circularBufferBasem_buffer[_idx + this->m_buffer.size()]; } - else if( _idx > this->m_buffer.size()-1 ) + else if( _idx > this->m_buffer.size() - 1 ) { return this->m_buffer[_idx - this->m_buffer.size()]; } @@ -404,7 +405,7 @@ class circularBufferBranch : public circularBufferBase +class circularBufferIndex; + template -class circularBufferIndex : public circularBufferBase, _storedT, _indexT> +class circularBufferIndex<_storedT, _indexT, false> + : public circularBufferBase, _storedT, _indexT> { public: typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping @@ -502,7 +507,7 @@ class circularBufferIndex : public circularBufferBasem_maxEntries + i] = i; - m_indices[2*this->m_maxEntries + i] = i; + m_indices[2 * this->m_maxEntries + i] = i; } } @@ -515,7 +520,7 @@ class circularBufferIndex : public circularBufferBasem_buffer[m_indices[this->m_maxEntries+refEntry + idx]]; + return this->m_buffer[m_indices[this->m_maxEntries + refEntry + idx]]; } /// Interface implementation for entry access, const version @@ -529,7 +534,331 @@ class circularBufferIndex : public circularBufferBasem_buffer[this->m_maxEntries+m_indices[refEntry + idx]]; + return this->m_buffer[this->m_maxEntries + m_indices[refEntry + idx]]; + } +}; + +/// Fixed-size circular buffer specialization for single-producer/single-consumer use. +/** + * \ingroup circular_buffer + */ +template +class circularBufferIndex<_storedT, _indexT, true> +{ + public: + typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping + typedef _indexT indexT; ///< The index type, also used for sizes + + static_assert( std::is_signed_v<_indexT> == true, "circularBuffer indexT must be signed" ); + static_assert( std::is_trivially_copyable_v<_storedT> == true, + "fixed_size circularBufferIndex requires trivially copyable storedT" ); + + /// Snapshot of the readable state of the buffer. + struct snapshotT + { + indexT earliest{ 0 }; ///< Earliest readable slot in the current snapshot. + indexT latest{ 0 }; ///< Latest published slot in the current snapshot. + indexT validEntries{ 0 }; ///< Number of currently valid entries. + indexT maxEntries{ 0 }; ///< Fixed capacity. + uint64_t mono{ 0 }; ///< Publication generation for retry validation. + bool full{ false }; ///< Whether the buffer has reached fixed capacity. + }; + + protected: + std::vector m_buffer; ///< The circular buffer storage. + std::vector m_indices; ///< Vector of indices for fast indexing into m_buffer. + + std::atomic m_nextEntry{ 0 }; ///< The slot that will be written next by the producer. + std::atomic m_latest{ 0 }; ///< The latest slot published by the producer. + std::atomic m_validEntries{ 0 }; ///< The number of valid published entries. + std::atomic m_mono{ 0 }; ///< A monotonic publication counter. + + indexT m_maxEntries{ 0 }; ///< Fixed capacity of the buffer. + + public: + /// Default c'tor. + circularBufferIndex() = default; + + /// Sizing constructor. + explicit circularBufferIndex( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ ) + { + maxEntries( maxEnt ); + } + + /// Set the maximum size of the buffer. + void maxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ ) + { + m_buffer.clear(); + m_indices.clear(); + + m_maxEntries = maxEnt; + + if( m_maxEntries > 0 ) + { + m_buffer.resize( m_maxEntries ); + m_indices.resize( 3 * m_maxEntries ); + + for( size_t i = 0; i < static_cast( m_maxEntries ); ++i ) + { + m_indices[i] = i; + m_indices[m_maxEntries + i] = i; + m_indices[2 * m_maxEntries + i] = i; + } + } + + m_nextEntry.store( 0, std::memory_order_release ); + m_latest.store( 0, std::memory_order_release ); + m_validEntries.store( 0, std::memory_order_release ); + m_mono.store( 0, std::memory_order_release ); + } + + /// Get the fixed capacity of the buffer. + indexT maxEntries() + { + return m_maxEntries; + } + + /// Get the number of valid entries. + indexT size() const + { + return m_validEntries.load( std::memory_order_acquire ); + } + + /// Add the next entry to the circular buffer. + void nextEntry( const storedT &newEnt /**< [in] the new entry to add to the buffer*/ ) + { + if( m_maxEntries <= 0 ) + { + return; + } + + indexT next = m_nextEntry.load( std::memory_order_relaxed ); + + m_buffer[next] = newEnt; + + indexT latest = next; + ++next; + if( next >= m_maxEntries ) + { + next = 0; + } + + indexT validEntries = m_validEntries.load( std::memory_order_relaxed ); + if( validEntries < m_maxEntries ) + { + ++validEntries; + } + + m_latest.store( latest, std::memory_order_relaxed ); + m_validEntries.store( validEntries, std::memory_order_relaxed ); + m_nextEntry.store( next, std::memory_order_relaxed ); + m_mono.fetch_add( 1, std::memory_order_release ); + } + + /// Move to the next entry using a default constructed value. + void nextEntry() + { + nextEntry( storedT{} ); + } + + /// Returns the index of the earliest entry. + indexT earliest() const + { + snapshotT sn = snapshot(); + return sn.earliest; + } + + /// Returns the index of the latest entry. + indexT latest() const + { + return m_latest.load( std::memory_order_acquire ); + } + + /// Returns the publication counter. + uint64_t mono() const + { + return m_mono.load( std::memory_order_acquire ); + } + + /// Get the entry at a given index. + storedT operator[]( indexT idx /**< [in] the index of the entry to access*/ ) const + { + snapshotT sn = snapshot(); + + if( sn.validEntries <= 0 ) + { + return storedT{}; + } + + return at( sn.earliest, idx ); + } + + /// Get the entry at a given index relative a fixed reference entry. + storedT at( indexT refEntry, ///< [in] the entry to start counting from + indexT idx ///< [in] the index of the entry to access + ) const + { + return m_buffer[physicalIndex( refEntry, idx )]; + } + + /// Return a snapshot of the current readable region. + snapshotT snapshot() const + { + snapshotT sn; + + sn.mono = m_mono.load( std::memory_order_acquire ); + sn.latest = m_latest.load( std::memory_order_relaxed ); + sn.validEntries = m_validEntries.load( std::memory_order_relaxed ); + sn.maxEntries = m_maxEntries; + sn.full = ( sn.validEntries == sn.maxEntries && sn.maxEntries > 0 ); + + if( sn.full ) + { + sn.earliest = m_nextEntry.load( std::memory_order_relaxed ); + } + else + { + sn.earliest = 0; + } + + return sn; + } + + /// Determine whether a logical window is readable from the provided snapshot. + bool windowReadable( const snapshotT &sn, ///< [in] the snapshot to validate against + indexT refEntry, ///< [in] the entry to start counting from + indexT count ///< [in] the number of entries requested + ) const + { + if( count <= 0 || refEntry < 0 || count > sn.validEntries ) + { + return false; + } + + if( !sn.full ) + { + return refEntry + count <= sn.validEntries; + } + + for( indexT n = 0; n < count; ++n ) + { + if( physicalIndex( refEntry, n ) == sn.earliest ) + { + return false; + } + } + + return true; + } + + /// Copy a logical sequence into caller-owned storage, retrying if the producer advances. + bool loadSequence( indexT refEntry, ///< [in] the entry to start counting from + indexT count, ///< [in] the number of entries requested + storedT *dest, ///< [out] destination storage of size count + snapshotT &sn, ///< [out] the validated snapshot used for the copy + int maxRetries = 3 /**< [in] the maximum number of retries*/ + ) const + { + if( dest == nullptr ) + { + return false; + } + + for( int retry = 0; retry < maxRetries; ++retry ) + { + sn = snapshot(); + + if( !windowReadable( sn, refEntry, count ) ) + { + return false; + } + + for( indexT n = 0; n < count; ++n ) + { + dest[n] = m_buffer[physicalIndex( refEntry, n )]; + } + + if( m_mono.load( std::memory_order_acquire ) == sn.mono ) + { + return true; + } + } + + return false; + } + + /// Copy a logical sequence from a caller-supplied snapshot, validating that the producer has not advanced. + bool loadSequence( const snapshotT &sn, ///< [in] the snapshot to validate against + indexT refEntry, ///< [in] the entry to start counting from + indexT count, ///< [in] the number of entries requested + storedT *dest ///< [out] destination storage of size count + ) const + { + if( dest == nullptr || !windowReadable( sn, refEntry, count ) ) + { + return false; + } + + for( indexT n = 0; n < count; ++n ) + { + dest[n] = m_buffer[physicalIndex( refEntry, n )]; + } + + return m_mono.load( std::memory_order_acquire ) == sn.mono; + } + + /// Copy the latest logical sequence into caller-owned storage, retrying if the producer advances. + bool loadLatestSequence( indexT count, ///< [in] the number of entries requested + storedT *dest, ///< [out] destination storage of size count + snapshotT &sn, ///< [out] the validated snapshot used for the copy + int maxRetries = 3 /**< [in] the maximum number of retries*/ + ) const + { + for( int retry = 0; retry < maxRetries; ++retry ) + { + sn = snapshot(); + + if( count <= 0 || count > sn.validEntries ) + { + return false; + } + + indexT refEntry; + if( sn.latest >= count - 1 ) + { + refEntry = sn.latest - ( count - 1 ); + } + else + { + refEntry = sn.maxEntries + sn.latest - ( count - 1 ); + } + + if( !windowReadable( sn, refEntry, count ) ) + { + return false; + } + + for( indexT n = 0; n < count; ++n ) + { + dest[n] = m_buffer[physicalIndex( refEntry, n )]; + } + + if( m_mono.load( std::memory_order_acquire ) == sn.mono ) + { + return true; + } + } + + return false; + } + + protected: + /// Convert a logical index into a physical slot index. + indexT physicalIndex( indexT refEntry, ///< [in] the entry to start counting from + indexT idx ///< [in] the index of the entry to access + ) const + { + return static_cast( m_indices[m_maxEntries + refEntry + idx] ); } };