BELFEM 0.9.0
Berkeley Lab Finite Element Framework
Loading...
Searching...
No Matches
cl_ShiftRegister.hpp
Go to the documentation of this file.
1/*
2 * BELFEM -- The Berkeley Lab Finite Element Framework
3 * Copyright (c) 2026, The Regents of the University of California,
4 * through Lawrence Berkeley National Laboratory (subject to receipt of any required
5 * approvals from the U.S. Dept. of Energy). All rights reserved.
6 *
7 * Developers: Christian Messe, Gregory Giard
8 *
9 * See the top-level LICENSE file for the complete license and disclaimer.
10 */
11
12
13#ifndef BELFEM_CL_SHIFTREGISTER_HPP
14#define BELFEM_CL_SHIFTREGISTER_HPP
15
16#include <algorithm>
17#include <initializer_list>
18#include <cstdlib>
19#include <new>
20#include <type_traits>
21#include "typedefs.hpp"
22#include "assert.hpp"
23
24namespace belfem
25{
26 // Forward declarations for shift-register-safe specializations
27 template< typename T > class Vector;
28 template< typename T > class Matrix;
29
30//------------------------------------------------------------------------------
31
55 template< typename T >
56 struct is_shift_register_safe : std::is_trivially_copyable< T > {};
57
58 template< typename T >
59 struct is_shift_register_safe< Vector< T > > : std::true_type {};
60
61 template< typename T >
62 struct is_shift_register_safe< Matrix< T > > : std::true_type {};
63
64//------------------------------------------------------------------------------
65
72 template < typename T >
74 {
76 "ShiftRegister<T> uses malloc/free — T must be trivially copyable, "
77 "or specialize belfem::is_shift_register_safe<T> for an owning type "
78 "that is default-constructible and has a well-behaved operator= "
79 "(see cl_ShiftRegister.hpp for details)" );
80
81 // owning (non-trivial) types are lifetime-managed via placement-new /
82 // explicit destruction, which requires a default constructor
83 static_assert( std::is_trivially_copyable< T >::value
84 || std::is_default_constructible< T >::value,
85 "ShiftRegister<T>: an owning T must be default-constructible so its "
86 "slots can be default-constructed on reserve()" );
87
88 // fixed size data container
89 // we keep one more so that we can do a revert
90 // the backup value is not passed during copy ( a copy cannot revert );
91 // a move steals the whole buffer, backup included
92 T * mData = nullptr ;
93
94 // size with actually filled values
95 size_t mSize = 0;
96
97 size_t mCapacity = 0 ;
98
99 enum class RevertState : uint8_t { None, CanRevert, CanRevertFull };
100 RevertState mCanRevert = RevertState::None;
101
102//------------------------------------------------------------------------------
103 private:
104//------------------------------------------------------------------------------
105
106 // Default-construct all (mCapacity+1) slots of a freshly malloc'd
107 // buffer, so every slot is a valid object before any assignment.
108 // Compiles to nothing for trivially copyable T (slots used raw).
109 void
110 construct_slots()
111 {
112 if constexpr ( ! std::is_trivially_copyable< T >::value )
113 {
114 for ( size_t k = 0; k < mCapacity + 1; ++k )
115 {
116 ::new ( static_cast< void * >( mData + k ) ) T();
117 }
118 }
119 }
120
121//------------------------------------------------------------------------------
122
123 // Destroy all (mCapacity+1) slots before the buffer is freed, so
124 // owning elements release their storage. Must be called while mData
125 // and mCapacity still describe the live buffer.
126 // Compiles to nothing for trivially copyable T.
127 void
128 destroy_slots()
129 {
130 if constexpr ( ! std::is_trivially_copyable< T >::value )
131 {
132 for ( size_t k = 0; k < mCapacity + 1; ++k )
133 {
134 ( mData + k )->~T();
135 }
136 }
137 }
138
139//------------------------------------------------------------------------------
140 public:
141//------------------------------------------------------------------------------
142
143 // Constructor with capacity only ( register starts empty )
144 ShiftRegister( const size_t aCapacity )
145 {
146 this->reserve( aCapacity );
147 }
148
149 // Constructor with fill value
150 ShiftRegister( const size_t aCapacity, const T & aInitValue )
151 {
152 this->reserve( aCapacity );
153 std::fill( mData, mData + aCapacity, aInitValue );
154 mSize = aCapacity ;
155 }
156
157
158 // Constructor with initializer list (fills from newest to oldest)
159 ShiftRegister( const std::initializer_list< T > & aInitList )
160 {
161 this->reserve( aInitList.size() );
162
163 uint k = 0;
164 for( const T & value : aInitList )
165 {
166 mData[ k++ ] = value;
167 }
168
169 // remember size
170 mSize = aInitList.size();
171 }
172
173//------------------------------------------------------------------------------
174
175 // Copy constructor
176 ShiftRegister( const ShiftRegister & aOther )
177 {
178 // reserve memory
179 this->reserve( aOther.capacity() );
180
181 // Copy only the valid elements
182 std::copy( aOther.begin(), aOther.end(), mData );
183
184 mCanRevert = RevertState::None ;
185
186 // remember size
187 mSize = aOther.size();
188 }
189
190//------------------------------------------------------------------------------
191
192 // Move constructor
193 ShiftRegister( ShiftRegister && aOther ) noexcept
194 {
195 // a freshly constructed object owns no buffer, so just steal
196 // Move the pointer
197 mData = aOther.data();
198 mSize = aOther.size();
199 mCapacity = aOther.capacity();
200 mCanRevert = aOther.mCanRevert;
201
202 // reset the source object
203 aOther.mData = nullptr;
204 aOther.mCapacity = 0;
205 aOther.mSize = 0;
206 aOther.mCanRevert = RevertState::None;
207 }
208
209 // Copy assignment operator
211 operator=( const ShiftRegister & aOther )
212 {
213 if( this != &aOther )
214 {
215 this->reserve( aOther.capacity() );
216
217 mSize = aOther.size();
218
219 std::copy( aOther.begin(), aOther.end(), mData );
220 mCanRevert = RevertState::None ;
221
222 }
223 return *this;
224 }
225
226//------------------------------------------------------------------------------
227
228 // Move assignment operator
230 operator=( ShiftRegister && aOther ) noexcept
231 {
232 if( this != &aOther )
233 {
234 if ( mData != nullptr )
235 {
236 this->destroy_slots();
237 std::free( mData );
238 }
239
240 // Move the pointer
241 mData = aOther.data();
242 mSize = aOther.size();
243 mCapacity = aOther.capacity();
244 mCanRevert = aOther.mCanRevert;
245
246 // reset the source object
247 aOther.mData = nullptr;
248 aOther.mCapacity = 0;
249 aOther.mSize = 0;
250 aOther.mCanRevert = RevertState::None;
251 }
252 return *this;
253 }
254
255//------------------------------------------------------------------------------
256
258 {
259 if ( mData != nullptr )
260 {
261 this->destroy_slots();
262 std::free( mData ) ;
263 }
264 }
265
266//------------------------------------------------------------------------------
267
268 void
269 push( T & aValue )
270 {
271 if( mSize > 0 )
272 {
273 // Determine how many elements to move
274 index_t tElementsToMove = std::min( mSize, mCapacity-1 );
275
276 // if we have a backup, we remember this value too
277 if ( mSize == mCapacity ) ++tElementsToMove;
278
279 // Move elements to the right using std::move_backward
280 // This moves from [0, tElementsToMove) to [1, tElementsToMove+1)
281 // including the backup value
282 std::move_backward( mData, mData + tElementsToMove, mData + tElementsToMove + 1 );
283 }
284
285 // Insert new value at position 0
286 mData[ 0 ] = aValue;
287
288 mCanRevert = mSize == mCapacity ? RevertState::CanRevertFull : RevertState::CanRevert ;
289
290 // Update size (cap at N)
291 mSize = std::min( mSize+1, mCapacity );
292 }
293
294//------------------------------------------------------------------------------
295
296 void
297 push( const T & aValue )
298 {
299 if( mSize > 0 )
300 {
301 // Determine how many elements to move
302 index_t tElementsToMove = std::min( mSize, mCapacity-1 );
303
304 // if we have a backup, we remember this value too
305 if ( mSize == mCapacity ) ++tElementsToMove;
306
307 // Move elements to the right using std::move_backward
308 // This moves from [0, tElementsToMove) to [1, tElementsToMove+1)
309 std::move_backward( mData, mData + tElementsToMove, mData + tElementsToMove + 1 );
310 }
311
312 // Insert new value at position 0
313 mData[ 0 ] = aValue;
314
315 mCanRevert = mSize == mCapacity ? RevertState::CanRevertFull : RevertState::CanRevert ;
316
317 // Update size (cap at N)
318 mSize = std::min( mSize+1, mCapacity );
319 }
320
321//------------------------------------------------------------------------------
322
323 void
325 {
326 BELFEM_ERROR( mCanRevert != RevertState::None, "Cannot revert" );
327
328 std::move( mData + 1, mData + mSize + ( mCanRevert == RevertState::CanRevertFull ? 1 : 0 ), mData );
329
330 if ( mCanRevert == RevertState::CanRevert ) --mSize;
331
332 mCanRevert = RevertState::None ;
333 }
334//------------------------------------------------------------------------------
335
339 T &
340 operator()( const uint aIndex )
341 {
342 BELFEM_ASSERT( aIndex < mSize,
343 "Index %u out of range (expect < %u)",
344 (unsigned int) aIndex, (unsigned int) mSize );
345
346 return mData[ aIndex ];
347 }
348
349//------------------------------------------------------------------------------
350
354 const T &
355 operator()( const uint aIndex ) const
356 {
357 BELFEM_ASSERT( aIndex < mSize,
358 "Index %u out of range (expect < %u)",
359 (unsigned int) aIndex, (unsigned int) mSize );
360
361 return mData[ aIndex ];
362 }
363
364//------------------------------------------------------------------------------
365
369 auto
370 size() const -> decltype( mSize )
371 {
372 return mSize;
373 }
374
375//------------------------------------------------------------------------------
376
380 auto
381 capacity() const -> decltype( mCapacity )
382 {
383 return mCapacity ;
384 }
385
386//------------------------------------------------------------------------------
387
391 bool
392 empty() const
393 {
394 return mSize == 0;
395 }
396
397//------------------------------------------------------------------------------
398
402 bool
403 full() const
404 {
405 return mSize == mCapacity ;
406 }
407
408//------------------------------------------------------------------------------
409
413 void
415 {
416 mSize = 0;
417 mCanRevert = RevertState::None ;
418 }
419
420//------------------------------------------------------------------------------
421
425 T *
427 {
428 return mData;
429 }
430
431//------------------------------------------------------------------------------
432
436 const T *
437 data() const
438 {
439 return mData;
440 }
441
442//------------------------------------------------------------------------------
443
444 T *
446 {
447 return mData;
448 }
449
450//------------------------------------------------------------------------------
451
452 T *
454 {
455 return mData + mSize;
456 }
457
458//------------------------------------------------------------------------------
459
460 const T *
461 begin() const
462 {
463 return mData;
464 }
465
466//------------------------------------------------------------------------------
467
468 const T*
469 end() const
470 {
471 return mData + mSize;
472 }
473
474 void
475 fill( const T aValue )
476 {
477 std::fill( mData, mData + mSize, aValue );
478 mCanRevert = RevertState::None ;
479 }
480
481 void
482 reserve( const index_t aCapacity )
483 {
484 // a zero-capacity register never allocates the backup slot, so a
485 // later push() would write through nullptr — disallow it outright
486 BELFEM_ERROR( aCapacity > 0,
487 "ShiftRegister: capacity must be greater than zero" );
488
489 if ( mCapacity == aCapacity ) return ;
490
491 // tear down the existing buffer (destroy owning slots first)
492 if ( mData != nullptr )
493 {
494 this->destroy_slots();
495 std::free( mData );
496 }
497
498 // reserve one extra for reversion backup
499 mCapacity = aCapacity ;
500 mData = ( T * ) std::malloc( ( aCapacity + 1 ) * sizeof( T ) );
501
502 // construct_slots() dereferences mData immediately, so a failed
503 // allocation must be caught here rather than becoming UB
504 BELFEM_ERROR( mData != nullptr,
505 "ShiftRegister: failed to allocate %lu slots",
506 ( unsigned long ) ( aCapacity + 1 ) );
507
508 // bring every slot to a valid, constructed state
509 this->construct_slots();
510
511 mSize = 0 ;
512 mCanRevert = RevertState::None ;
513 }
514
515 void
517 {
518 if ( mData != nullptr )
519 {
520 this->destroy_slots();
521 std::free( mData );
522 mData = nullptr ;
523 mSize = 0 ;
524 mCapacity = 0 ;
525 mCanRevert = RevertState::None ;
526 }
527 }
528
529//------------------------------------------------------------------------------
530 };
531
532}
533#endif // BELFEM_CL_SHIFTREGISTER_HPP
#define BELFEM_ERROR(aCheck,...)
Definition assert.hpp:264
#define BELFEM_ASSERT(aCheck,...)
Definition assert.hpp:244
Dense column-major matrix.
Definition cl_BZ_Matrix.hpp:28
Fixed-capacity FIFO with history, for time-stepping.
Definition cl_ShiftRegister.hpp:74
void free()
Definition cl_ShiftRegister.hpp:516
bool full() const
Check if the register is full.
Definition cl_ShiftRegister.hpp:403
const T * end() const
Definition cl_ShiftRegister.hpp:469
void reserve(const index_t aCapacity)
Definition cl_ShiftRegister.hpp:482
ShiftRegister(const size_t aCapacity, const T &aInitValue)
Definition cl_ShiftRegister.hpp:150
void revert()
Definition cl_ShiftRegister.hpp:324
const T & operator()(const uint aIndex) const
Access element by index (const version).
Definition cl_ShiftRegister.hpp:355
void fill(const T aValue)
Definition cl_ShiftRegister.hpp:475
T * begin()
Definition cl_ShiftRegister.hpp:445
bool empty() const
Check if the register is empty.
Definition cl_ShiftRegister.hpp:392
auto size() const -> decltype(mSize)
Get current number of elements.
Definition cl_ShiftRegister.hpp:370
~ShiftRegister()
Definition cl_ShiftRegister.hpp:257
ShiftRegister(const size_t aCapacity)
Definition cl_ShiftRegister.hpp:144
ShiftRegister & operator=(const ShiftRegister &aOther)
Definition cl_ShiftRegister.hpp:211
T * end()
Definition cl_ShiftRegister.hpp:453
void push(T &aValue)
Definition cl_ShiftRegister.hpp:269
const T * data() const
Get raw pointer to data array (const version).
Definition cl_ShiftRegister.hpp:437
ShiftRegister(const std::initializer_list< T > &aInitList)
Definition cl_ShiftRegister.hpp:159
const T * begin() const
Definition cl_ShiftRegister.hpp:461
ShiftRegister(ShiftRegister &&aOther) noexcept
Definition cl_ShiftRegister.hpp:193
ShiftRegister(const ShiftRegister &aOther)
Definition cl_ShiftRegister.hpp:176
T * data()
Get raw pointer to data array.
Definition cl_ShiftRegister.hpp:426
auto capacity() const -> decltype(mCapacity)
Get maximum capacity.
Definition cl_ShiftRegister.hpp:381
void clear()
Clear all elements.
Definition cl_ShiftRegister.hpp:414
T & operator()(const uint aIndex)
Access element by index (0 = newest, N-1 = oldest).
Definition cl_ShiftRegister.hpp:340
void push(const T &aValue)
Definition cl_ShiftRegister.hpp:297
ShiftRegister & operator=(ShiftRegister &&aOther) noexcept
Definition cl_ShiftRegister.hpp:230
Column vector.
Definition cl_BZ_Vector.hpp:41
USER GUIDES:
Definition cl_Capacitor.cpp:16
std::pair< real, unit > value
Definition typedefs.hpp:74
unsigned int uint
Definition typedefs.hpp:30
uint32_t index_t
Definition typedefs.hpp:52
Type trait: may T be stored in a ShiftRegister?
Definition cl_ShiftRegister.hpp:56