| 
									
										
										
										
											2022-09-13 08:44:26 +02:00
										 |  |  | /* SPDX-License-Identifier: GPL-2.0-or-later */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #pragma once
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** \file
 | 
					
						
							|  |  |  |  * \ingroup bli | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * When logging computed values, we generally want to know where the value was computed. For | 
					
						
							|  |  |  |  * example, geometry nodes logs socket values so that they can be displayed in the ui. For that we | 
					
						
							|  |  |  |  * can combine the logged value with a `ComputeContext`, which identifies the place where the value | 
					
						
							|  |  |  |  * was computed. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2022-09-17 14:46:50 +10:00
										 |  |  |  * This is not a trivial problem because e.g. just storing a pointer to the socket a value | 
					
						
							| 
									
										
										
										
											2022-09-13 08:44:26 +02:00
										 |  |  |  * belongs to is not enough. That's because the same socket may correspond to many different values | 
					
						
							|  |  |  |  * when the socket is used in a node group that is used multiple times. In this case, not only does | 
					
						
							|  |  |  |  * the socket have to be stored but also the entire nested node group path that led to the | 
					
						
							|  |  |  |  * evaluation of the socket. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Storing the entire "context path" for every logged value is not feasible, because that path can | 
					
						
							|  |  |  |  * become quite long. So that would need much more memory, more compute overhead and makes it | 
					
						
							|  |  |  |  * complicated to compare if two contexts are the same. If the identifier for a compute context | 
					
						
							|  |  |  |  * would have a variable size, it would also be much harder to create a map from context to values. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * The solution implemented below uses the following key ideas: | 
					
						
							|  |  |  |  * - Every compute context can be hashed to a unique fixed size value (`ComputeContextHash`). While | 
					
						
							|  |  |  |  *   technically there could be hash collisions, the hashing algorithm has to be chosen to make | 
					
						
							|  |  |  |  *   that practically impossible. This way an entire context path, possibly consisting of many | 
					
						
							|  |  |  |  *   nested contexts, is represented by a single value that can be stored easily. | 
					
						
							|  |  |  |  * - A nested compute context is build as singly linked list, where every compute context has a | 
					
						
							|  |  |  |  *   pointer to the parent compute context. Note that a link in the other direction is not possible | 
					
						
							|  |  |  |  *   because the same parent compute context may be used by many different children which possibly | 
					
						
							|  |  |  |  *   run on different threads. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "BLI_array.hh"
 | 
					
						
							|  |  |  | #include "BLI_linear_allocator.hh"
 | 
					
						
							|  |  |  | #include "BLI_stack.hh"
 | 
					
						
							|  |  |  | #include "BLI_string_ref.hh"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace blender { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  * A hash that uniquely identifies a specific (non-fixed-size) compute context. The hash has to | 
					
						
							|  |  |  |  * have enough bits to make collisions practically impossible. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | struct ComputeContextHash { | 
					
						
							|  |  |  |   static constexpr int64_t HashSizeInBytes = 16; | 
					
						
							|  |  |  |   uint64_t v1 = 0; | 
					
						
							|  |  |  |   uint64_t v2 = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   uint64_t hash() const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     return v1; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   friend bool operator==(const ComputeContextHash &a, const ComputeContextHash &b) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     return a.v1 == b.v1 && a.v2 == b.v2; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void mix_in(const void *data, int64_t len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   friend std::ostream &operator<<(std::ostream &stream, const ComputeContextHash &hash); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static_assert(sizeof(ComputeContextHash) == ComputeContextHash::HashSizeInBytes); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  * Identifies the context in which a computation happens. This context can be used to identify | 
					
						
							|  |  |  |  * values logged during the computation. For more details, see the comment at the top of the file. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This class should be subclassed to implement specific contexts. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class ComputeContext { | 
					
						
							|  |  |  |  private: | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Only used for debugging currently. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   const char *static_type_; | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Pointer to the context that this context is child of. That allows nesting compute contexts. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   const ComputeContext *parent_ = nullptr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  protected: | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * The hash that uniquely identifies this context. It's a combined hash of this context as well | 
					
						
							|  |  |  |    * as all the parent contexts. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   ComputeContextHash hash_; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  public: | 
					
						
							|  |  |  |   ComputeContext(const char *static_type, const ComputeContext *parent) | 
					
						
							|  |  |  |       : static_type_(static_type), parent_(parent) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (parent != nullptr) { | 
					
						
							|  |  |  |       hash_ = parent_->hash_; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   virtual ~ComputeContext() = default; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const ComputeContextHash &hash() const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     return hash_; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const char *static_type() const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     return static_type_; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const ComputeContext *parent() const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     return parent_; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Print the entire nested context stack. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   void print_stack(std::ostream &stream, StringRef name) const; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /**
 | 
					
						
							|  |  |  |    * Print information about this specific context. This has to be implemented by each subclass. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   virtual void print_current_in_line(std::ostream &stream) const = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   friend std::ostream &operator<<(std::ostream &stream, const ComputeContext &compute_context); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  * Utility class to build a context stack in one place. This is typically used to get the hash that | 
					
						
							|  |  |  |  * corresponds to a specific nested compute context, in order to look up corresponding logged | 
					
						
							|  |  |  |  * values. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class ComputeContextBuilder { | 
					
						
							|  |  |  |  private: | 
					
						
							|  |  |  |   LinearAllocator<> allocator_; | 
					
						
							|  |  |  |   Stack<destruct_ptr<ComputeContext>> contexts_; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  public: | 
					
						
							|  |  |  |   bool is_empty() const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     return contexts_.is_empty(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const ComputeContext *current() const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (contexts_.is_empty()) { | 
					
						
							|  |  |  |       return nullptr; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return contexts_.peek().get(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const ComputeContextHash hash() const | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     BLI_assert(!contexts_.is_empty()); | 
					
						
							|  |  |  |     return this->current()->hash(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   template<typename T, typename... Args> void push(Args &&...args) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     const ComputeContext *current = this->current(); | 
					
						
							|  |  |  |     destruct_ptr<T> context = allocator_.construct<T>(current, std::forward<Args>(args)...); | 
					
						
							|  |  |  |     contexts_.push(std::move(context)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void pop() | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     contexts_.pop(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }  // namespace blender
 |