Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-08 10:11:15

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright Celeritas contributors: see top-level COPYRIGHT file for details
0003 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0004 //---------------------------------------------------------------------------//
0005 //! \file celeritas/ext/detail/LocalProcessorSlots.hh
0006 //! \sa test/celeritas/ext/GeantSd.test.cc
0007 //---------------------------------------------------------------------------//
0008 #pragma once
0009 
0010 #include <memory>
0011 #include <utility>
0012 #include <vector>
0013 
0014 #include "corecel/Assert.hh"
0015 #include "corecel/cont/Range.hh"
0016 #include "corecel/sys/ThreadId.hh"
0017 
0018 namespace celeritas
0019 {
0020 namespace detail
0021 {
0022 //---------------------------------------------------------------------------//
0023 /*!
0024  * Per-stream cache of thread-local processors owned by local transporters.
0025  *
0026  * Classes that interface Celeritas with thread-local Geant4 objects (e.g.,
0027  * sensitive detectors) are shared across threads but must dispatch to a
0028  * processor that is created, used, and destroyed on a single worker thread.
0029  * This class holds one weakly referenced processor per stream.
0030  *
0031  * The references between a slot and its processor are cyclic and
0032  * deliberately non-owning in both directions:
0033  * \verbatim
0034    Shared --(shared)--> Slot --(weak + raw cache)--> Processor
0035                          ^                              |
0036                          +--(weak, via custom deleter)--+
0037 
0038    LocalTransporter --(shared, with custom deleter)--> Processor
0039    \endverbatim
0040  *
0041  * The thread-local transporter shares ownership of the processor, and the
0042  * shared object owns the slot. Since the cross references are weak, either
0043  * side may be destroyed first:
0044  * - when the last local reference to the processor is released (on the
0045  *   worker thread that created it), the deleter resets the slot's cached
0046  *   pointers so a later \c make call recreates the processor instead of
0047  *   returning a dangling pointer;
0048  * - when the slots are destroyed first, locking the weak pointer fails and
0049  *   only the processor is deleted.
0050  *
0051  * The weak pointer is used only when creating or recreating a local
0052  * processor. Step processing uses the cached raw pointer to avoid locking a
0053  * weak pointer on every step iteration.
0054  */
0055 template<class T>
0056 class LocalProcessorSlots
0057 {
0058   public:
0059     //!@{
0060     //! \name Type aliases
0061     using SPProcessor = std::shared_ptr<T>;
0062     //!@}
0063 
0064   public:
0065     //! Construct without any streams
0066     LocalProcessorSlots() = default;
0067 
0068     // Construct with the number of streams
0069     explicit inline LocalProcessorSlots(StreamId::size_type num_streams);
0070 
0071     // Get or create the processor for a stream
0072     template<class F>
0073     inline SPProcessor make(StreamId sid, F&& construct);
0074 
0075     // Access the processor for a stream
0076     inline T& get(StreamId sid) const;
0077 
0078     //! Number of streams
0079     StreamId::size_type size() const { return slots_.size(); }
0080 
0081   private:
0082     struct Slot
0083     {
0084         std::weak_ptr<T> weak_processor;
0085         T* processor{nullptr};
0086     };
0087 
0088     struct SlotDeleter
0089     {
0090         std::weak_ptr<Slot> weak_slot;
0091 
0092         void operator()(T* processor) const
0093         {
0094             if (auto slot = weak_slot.lock())
0095             {
0096                 if (slot->processor == processor)
0097                 {
0098                     slot->processor = nullptr;
0099                     slot->weak_processor.reset();
0100                 }
0101             }
0102             delete processor;
0103         }
0104     };
0105 
0106     std::vector<std::shared_ptr<Slot>> slots_;
0107 };
0108 
0109 //---------------------------------------------------------------------------//
0110 // INLINE DEFINITIONS
0111 //---------------------------------------------------------------------------//
0112 /*!
0113  * Construct with the number of streams.
0114  */
0115 template<class T>
0116 LocalProcessorSlots<T>::LocalProcessorSlots(StreamId::size_type num_streams)
0117 {
0118     CELER_EXPECT(num_streams > 0);
0119     slots_.reserve(num_streams);
0120     for ([[maybe_unused]] auto i : range(num_streams))
0121     {
0122         slots_.push_back(std::make_shared<Slot>());
0123     }
0124 }
0125 
0126 //---------------------------------------------------------------------------//
0127 /*!
0128  * Get or create the processor for a stream.
0129  *
0130  * The \c construct function must return a newly allocated (raw) pointer to
0131  * the processor. Due to Geant4 multithread semantics, this \b must be called
0132  * on the same CPU thread on which the resulting processor is used.
0133  */
0134 template<class T>
0135 template<class F>
0136 auto LocalProcessorSlots<T>::make(StreamId sid, F&& construct) -> SPProcessor
0137 {
0138     CELER_EXPECT(sid < slots_.size());
0139 
0140     auto const& slot = slots_[sid.get()];
0141     CELER_ASSERT(slot);
0142     if (auto result = slot->weak_processor.lock())
0143     {
0144         CELER_ASSERT(result.get() == slot->processor);
0145         return result;
0146     }
0147 
0148     slot->processor = nullptr;
0149 
0150     SPProcessor result{std::forward<F>(construct)(), SlotDeleter{slot}};
0151     CELER_ASSERT(result);
0152     slot->weak_processor = result;
0153     slot->processor = result.get();
0154     return result;
0155 }
0156 
0157 //---------------------------------------------------------------------------//
0158 /*!
0159  * Access the processor for a stream.
0160  *
0161  * The processor must have been created with \c make and still be alive.
0162  */
0163 template<class T>
0164 T& LocalProcessorSlots<T>::get(StreamId sid) const
0165 {
0166     CELER_EXPECT(sid < slots_.size());
0167     auto* result = slots_[sid.get()]->processor;
0168     CELER_EXPECT(result);
0169     return *result;
0170 }
0171 
0172 //---------------------------------------------------------------------------//
0173 }  // namespace detail
0174 }  // namespace celeritas