Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
uuid_identifiable_object.h
1// SPDX-FileCopyrightText: © 2024-2026 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <utility>
7
8#include "utils/icloneable.h"
9#include "utils/logger.h"
10#include "utils/optional_ref.h"
11#include "utils/rt_thread_id.h"
12#include "utils/serialization.h"
13
14#include <QUuid>
15
16#include <boost/describe/class.hpp>
17#include <boost/stl_interfaces/iterator_interface.hpp>
18#include <boost/unordered/unordered_flat_map.hpp>
19#include <fmt/format.h>
20#include <nlohmann/json_fwd.hpp>
21#include <type_safe/strong_typedef.hpp>
22
23namespace zrythm::utils
24{
28template <typename Derived> class UuidIdentifiableObject
29{
30public:
31 struct Uuid final
32 : type_safe::strong_typedef<Uuid, QUuid>,
33 type_safe::strong_typedef_op::equality_comparison<Uuid>,
34 type_safe::strong_typedef_op::relational_comparison<Uuid>
35 {
36 using UuidTag = void; // used by the fmt formatter below
37 using type_safe::strong_typedef<Uuid, QUuid>::strong_typedef;
38
39 explicit Uuid () = default;
40
41 static_assert (StrongTypedef<Uuid>);
42 // static_assert (type_safe::is_strong_typedef<Uuid>::value);
43 // static_assert (std::is_same_v<type_safe::underlying_type<Uuid>, QUuid>);
44
48 bool is_null () const { return type_safe::get (*this).isNull (); }
49
50 void set_null () { type_safe::get (*this) = QUuid (); }
51
52 std::size_t hash () const { return qHash (type_safe::get (*this)); }
53 };
54 static_assert (std::regular<Uuid>);
55 static_assert (sizeof (Uuid) == sizeof (QUuid));
56
57 UuidIdentifiableObject () : uuid_ (Uuid (QUuid::createUuid ())) { }
58 UuidIdentifiableObject (const Uuid &id) : uuid_ (id) { }
59 UuidIdentifiableObject (const UuidIdentifiableObject &other) = default;
60 UuidIdentifiableObject &
61 operator= (const UuidIdentifiableObject &other) = default;
62 UuidIdentifiableObject (UuidIdentifiableObject &&other) = default;
63 UuidIdentifiableObject &operator= (UuidIdentifiableObject &&other) = default;
64 virtual ~UuidIdentifiableObject () = default;
65
66 auto get_uuid () const { return uuid_; }
67
68 friend void init_from (
69 UuidIdentifiableObject &obj,
70 const UuidIdentifiableObject &other,
71 utils::ObjectCloneType clone_type)
72 {
74 {
75 obj.uuid_ = Uuid (QUuid::createUuid ());
76 }
77 else
78 {
79 obj.uuid_ = other.uuid_;
80 }
81 }
82
83 friend void to_json (nlohmann::json &j, const UuidIdentifiableObject &obj)
84 {
85 j[kUuidKey] = obj.uuid_;
86 }
87 friend void from_json (const nlohmann::json &j, UuidIdentifiableObject &obj)
88 {
89 j.at (kUuidKey).get_to (obj.uuid_);
90 }
91
92 friend bool operator== (
93 const UuidIdentifiableObject &lhs,
94 const UuidIdentifiableObject &rhs)
95 {
96 return lhs.uuid_ == rhs.uuid_;
97 }
98
99private:
100 static constexpr std::string_view kUuidKey = "id";
101
102 Uuid uuid_;
103
104 BOOST_DESCRIBE_CLASS (UuidIdentifiableObject, (), (), (), (uuid_))
105};
106
107template <typename T>
108concept UuidIdentifiable = std::derived_from<T, UuidIdentifiableObject<T>>;
109
110template <typename T>
112 UuidIdentifiable<T> && std::derived_from<T, QObject>;
113
114// specialization for std::hash and boost::hash
115#define DEFINE_UUID_HASH_SPECIALIZATION(UuidType) \
116 namespace std \
117 { \
118 template <> struct hash<UuidType> \
119 { \
120 size_t operator() (const UuidType &uuid) const { return uuid.hash (); } \
121 }; \
122 } \
123 namespace boost \
124 { \
125 template <> struct hash<UuidType> \
126 { \
127 size_t operator() (const UuidType &uuid) const { return uuid.hash (); } \
128 }; \
129 }
130
138template <typename RegistryT> class UuidReference
139{
140public:
141 using UuidType = typename RegistryT::UuidType;
142 using VariantType = typename RegistryT::VariantType;
143
144 // unengaged - need to call set_id to acquire a reference
145 UuidReference (RegistryT &registry) : registry_ (registry) { }
146
147 UuidReference (const UuidType &id, RegistryT &registry)
148 : id_ (id), registry_ (registry)
149 {
150 acquire_ref ();
151 }
152
153 UuidReference (const UuidReference &other)
154 : UuidReference (other.get_registry ())
155 {
156 if (other.id_.has_value ())
157 {
158 set_id (other.id_.value ());
159 }
160 }
161 UuidReference &operator= (const UuidReference &other)
162 {
163 if (this != &other)
164 {
165 id_ = other.id_;
166 registry_ = other.registry_;
167 acquire_ref ();
168 }
169 return *this;
170 }
171
172 UuidReference (UuidReference &&other)
173 : id_ (std::exchange (other.id_, std::nullopt)),
174 registry_ (std::exchange (other.registry_, std::nullopt))
175 {
176 // No need to acquire_ref() here because we're taking over the reference
177 // from 'other' which already holds it
178 }
179
180 UuidReference &operator= (UuidReference &&other)
181 {
182 if (this != &other)
183 {
184 // Release our current reference (if any)
185 release_ref ();
186
187 // Take ownership from other
188 id_ = std::exchange (other.id_, std::nullopt);
189 registry_ = std::exchange (other.registry_, std::nullopt);
190
191 // No need to acquire_ref() - we're taking over the reference
192 }
193 return *this;
194 }
195
196 ~UuidReference () { release_ref (); }
197
198 auto id () const -> UuidType { return *id_; }
199
207 void set_id (const UuidType &id)
208 {
209 if (id_.has_value ())
210 {
211 throw std::runtime_error (
212 "Cannot set id of UuidReference that already has an id");
213 }
214 id_ = id;
215 acquire_ref ();
216 }
217
218 VariantType get_object () const
219 {
220 return get_registry ().find_by_id_or_throw (id ());
221 }
222
223 // Convenience getter
224 template <typename ObjectT>
225 ObjectT * get_object_as () const
226 requires IsInVariant<ObjectT *, VariantType>
227 {
228 return std::get<ObjectT *> (get_object ());
229 }
230
231 RegistryT::BaseType * get_object_base () const
232 {
233 return get_registry ().find_by_id_as_base_or_throw (id ());
234 }
235
236 template <typename... Args>
237 UuidReference clone_new_identity (Args &&... args) const
238 {
239 return std::visit (
240 [&] (auto &&obj) {
241 return get_registry ().clone_object (*obj, std::forward<Args> (args)...);
242 },
243 get_object ());
244 }
245
246 auto get_iterator_in_registry () const
247 {
248 return get_registry ().get_iterator_for_id (id ());
249 }
250
251 auto get_iterator_in_registry ()
252 {
253 return get_registry ().get_iterator_for_id (id ());
254 }
255
256private:
257 void acquire_ref ()
258 {
259 if (id_.has_value ())
260 {
261 get_registry ().acquire_reference (*id_);
262 }
263 }
264 void release_ref ()
265 {
266 if (id_.has_value ())
267 {
268 get_registry ().release_reference (*id_);
269 }
270 }
271 RegistryT &get_registry () const
272 {
273 return const_cast<RegistryT &> (*registry_);
274 }
275 RegistryT &get_registry () { return *registry_; }
276
277 friend void to_json (nlohmann::json &j, const UuidReference &ref)
278 {
279 assert (ref.id_.has_value ());
280 j = ref.id_;
281 }
282 friend void from_json (const nlohmann::json &j, UuidReference &ref)
283 {
284 auto tmp = ref;
285 j.get_to (ref.id_);
286 ref.acquire_ref ();
287 tmp.release_ref ();
288 }
289
290 friend bool operator== (const UuidReference &lhs, const UuidReference &rhs)
291 {
292 return lhs.id () == rhs.id ();
293 }
294
295private:
296 std::optional<UuidType> id_;
297 utils::OptionalRef<RegistryT> registry_;
298};
299
300template <typename ReturnType, typename UuidType>
301using UuidIdentifiablObjectResolver =
302 std::function<ReturnType (const UuidType &)>;
303
321template <typename VariantT, UuidIdentifiable BaseT>
322class OwningObjectRegistry : public QObject
323{
324public:
326 using VariantType = VariantT;
327 using BaseType = BaseT;
328
329 OwningObjectRegistry (QObject * parent = nullptr)
330 : QObject (parent), main_thread_id_ (current_thread_id)
331 {
332 }
333
334 ~OwningObjectRegistry ()
335 {
336 destroying_ = true;
337 // ~QObject() will delete all children, which may trigger UuidReference
338 // destructors that call release_reference(). The destroying_ flag tells
339 // release_reference() to skip reference counting during destruction.
340 }
341
342 // ========================================================================
343 // QML/QObject Interface
344 // ========================================================================
345
346 // TODO signals...
347
348 // ========================================================================
349
350 // Factory method that forwards constructor arguments
351 template <typename CreateType, typename... Args>
352 auto create_object (Args &&... args) -> UuidReference<OwningObjectRegistry>
353 requires std::derived_from<CreateType, BaseT>
354 {
355 assert (is_main_thread ());
356
357 auto * obj = new CreateType (std::forward<Args> (args)...);
358 z_trace (
359 "created object of type {} with ID {}", typeid (CreateType).name (),
360 obj->get_uuid ());
361 register_object (obj);
362 return { obj->get_uuid (), *this };
363 }
364
365 // Creates a clone of the given object and registers it to the registry.
366 template <typename CreateType, typename... Args>
367 auto clone_object (const CreateType &other, Args &&... args)
369 requires std::derived_from<CreateType, BaseT>
370 {
371 assert (is_main_thread ());
372
373 CreateType * obj = clone_raw_ptr (
374 other, ObjectCloneType::NewIdentity, std::forward<Args> (args)...);
375 register_object (obj);
376 return { obj->get_uuid (), *this };
377 }
378
379 [[gnu::hot]] auto get_iterator_for_id (const UuidType &id) const
380 {
381 // TODO: some failures left to fix
382 // assert (is_main_thread ());
383
384 return objects_by_id_.find (id);
385 }
386
387 [[gnu::hot]] auto get_iterator_for_id (const UuidType &id)
388 {
389 // assert (is_main_thread ());
390
391 return objects_by_id_.find (id);
392 }
393
399 [[gnu::hot]] std::optional<VariantT>
400 find_by_id (const UuidType &id) const noexcept [[clang::blocking]]
401 {
402 const auto it = get_iterator_for_id (id);
403 if (it == objects_by_id_.end ())
404 {
405 return std::nullopt;
406 }
407 return it->second;
408 }
409
410 [[gnu::hot]] auto find_by_id_or_throw (const UuidType &id) const
411 {
412 auto val = find_by_id (id);
413 if (!val.has_value ())
414 {
415 throw std::runtime_error (
416 fmt::format ("Object with id {} not found", id));
417 }
418 return val.value ();
419 }
420
421 BaseT * find_by_id_as_base_or_throw (const UuidType &id) const
422 {
423 auto var = find_by_id_or_throw (id);
424 return std::visit ([] (auto &&val) -> BaseT * { return val; }, var);
425 }
426
427 bool contains (const UuidType &id) const
428 {
429 // assert (is_main_thread ());
430
431 return objects_by_id_.contains (id);
432 }
433
439 void register_object (VariantT obj_ptr)
440 {
441 assert (is_main_thread ());
442
443 std::visit (
444 [&] (auto &&obj) {
445 if (contains (obj->get_uuid ()))
446 {
447 throw std::runtime_error (
448 fmt::format ("Object with id {} already exists", obj->get_uuid ()));
449 }
450 z_trace ("Registering (inserting) object {}", obj->get_uuid ());
451 obj->setParent (this);
452 objects_by_id_.emplace (obj->get_uuid (), obj);
453 },
454 obj_ptr);
455 }
456
457 template <typename ObjectT>
458 void register_object (ObjectT &obj)
459 requires std::derived_from<ObjectT, BaseT>
460 {
461 register_object (&obj);
462 }
463
469 auto reference_count (const UuidType &id) const
470 {
471 assert (is_main_thread ());
472
473 return ref_counts_.at (id);
474 }
475
476 void acquire_reference (const UuidType &id)
477 {
478 assert (is_main_thread ());
479
480 ref_counts_[id]++;
481 }
482
483 void release_reference (const UuidType &id)
484 {
485 assert (is_main_thread ());
486
487 if (destroying_)
488 return;
489
490 if (--ref_counts_[id] <= 0)
491 {
492 delete_object_by_id (id);
493 }
494 }
495
496 auto &get_hash_map () const { return objects_by_id_; }
497
501 auto get_uuids () const
502 {
503 assert (is_main_thread ());
504
505 return objects_by_id_ | std::views::keys | std::ranges::to<std::vector> ();
506 }
507
508 size_t size () const
509 {
510 assert (is_main_thread ());
511
512 return objects_by_id_.size ();
513 }
514
515 friend void to_json (nlohmann::json &j, const OwningObjectRegistry &obj)
516 {
517 j = nlohmann::json::array ();
518 for (const auto &var : obj.objects_by_id_ | std::views::values)
519 {
520 j.push_back (var);
521 }
522 }
523 template <ObjectBuilder BuilderT>
524 friend void from_json_with_builder (
525 const nlohmann::json &j,
526 OwningObjectRegistry &obj,
527 const BuilderT &builder)
528 {
529 // Phase 1: Create and register all objects (no data deserialization yet)
530 // This ensures all objects exist in the registry before any references
531 // are resolved during data deserialization.
532 std::vector<std::pair<VariantT, nlohmann::json>> deferred;
533 deferred.reserve (j.size ());
534 for (const auto &object_var_json : j)
535 {
536 VariantT object_var;
537 utils::serialization::variant_create_object_only (
538 object_var_json, object_var, builder);
539
540 // Set UUID from JSON before registering, so the object is registered
541 // under the correct UUID.
542 std::visit (
543 [&object_var_json] (auto &&ptr) {
544 from_json (
545 object_var_json,
546 static_cast<utils::UuidIdentifiableObject<BaseT> &> (*ptr));
547 },
548 object_var);
549
550 obj.register_object (object_var);
551 deferred.emplace_back (object_var, object_var_json);
552 }
553
554 // Phase 2: Deserialize data into all objects
555 // Now all UUID references can be resolved since all objects are registered.
556 for (auto &[object_var, json] : deferred)
557 {
558 utils::serialization::variant_deserialize_data (json, object_var);
559 }
560 }
561
562private:
569 VariantT unregister_object (const UuidType &id)
570 {
571 if (!objects_by_id_.contains (id))
572 {
573 throw std::runtime_error (
574 fmt::format ("Object with id {} not found", id));
575 }
576
577 z_trace ("Unregistering object with id {}", id);
578
579 auto obj_it = get_iterator_for_id (id);
580 auto obj_var = obj_it->second;
581 objects_by_id_.erase (obj_it);
582 std::visit ([&] (auto &&obj) { obj->setParent (nullptr); }, obj_var);
583 ref_counts_.erase (id);
584 return obj_var;
585 }
586
587 void delete_object_by_id (const UuidType &id)
588 {
589 auto obj_var = unregister_object (id);
590 std::visit ([&] (auto &&obj) { delete obj; }, obj_var);
591 }
592
593 bool is_main_thread () const { return main_thread_id_ == current_thread_id; }
594
595private:
601 boost::unordered::unordered_flat_map<UuidType, VariantT> objects_by_id_;
602
608 boost::unordered::unordered_flat_map<UuidType, int> ref_counts_;
609
610 RTThreadId main_thread_id_;
611
619 bool destroying_ = false;
620};
621
628template <typename RegistryT>
630 : public std::ranges::view_interface<UuidIdentifiableObjectView<RegistryT>>
631{
632public:
633 using UuidType = typename RegistryT::UuidType;
634 using VariantType = typename RegistryT::VariantType;
635 using UuidRefType = UuidReference<RegistryT>;
636
637 // Proxy iterator implementation using Boost.STLInterfaces
638 class Iterator
639 : public boost::stl_interfaces::proxy_iterator_interface<
640#if !BOOST_STL_INTERFACES_USE_DEDUCED_THIS
641 Iterator,
642#endif
643 std::random_access_iterator_tag,
644 VariantType>
645 {
646 public:
647 using base_type = boost::stl_interfaces::proxy_iterator_interface<
648#if !BOOST_STL_INTERFACES_USE_DEDUCED_THIS
649 Iterator,
650#endif
651 std::random_access_iterator_tag,
652 VariantType>;
653 using difference_type = base_type::difference_type;
654
655 constexpr Iterator () noexcept = default;
656
657 // Constructor for direct object range
658 constexpr Iterator (std::span<const VariantType>::iterator it) noexcept
659 : it_var_ (it)
660 {
661 }
662
663 // Constructor for UuidReference range
664 constexpr Iterator (std::span<const UuidRefType>::iterator it) noexcept
665 : it_var_ (it)
666 {
667 }
668
669 // Constructor for Uuid + Registry range
670 constexpr Iterator (
671 std::span<const UuidType>::iterator it,
672 const RegistryT * registry) noexcept
673 : it_var_ (std::pair{ it, registry })
674 {
675 }
676
677 constexpr VariantType operator* () const
678 {
679 return std::visit (
680 [] (auto &&arg) {
681 using T = std::decay_t<decltype (arg)>;
682 if constexpr (
683 std::is_same_v<T, typename std::span<const VariantType>::iterator>)
684 {
685 return *arg;
686 }
687 else if constexpr (
688 std::is_same_v<T, typename std::span<const UuidRefType>::iterator>)
689 {
690 return arg->get_object ();
691 }
692 else if constexpr (
693 std::is_same_v<
694 T,
695 std::pair<
696 typename std::span<const UuidType>::iterator, const RegistryT *>>)
697 {
698 return arg.second->find_by_id_or_throw (*arg.first);
699 }
700 },
701 it_var_);
702 }
703
704 constexpr Iterator &operator+= (difference_type n) noexcept
705 {
706 std::visit (
707 [n] (auto &&arg) {
708 using T = std::decay_t<decltype (arg)>;
709 if constexpr (
710 std::is_same_v<T, typename std::span<const VariantType>::iterator>)
711 {
712 arg += n;
713 }
714 else if constexpr (
715 std::is_same_v<T, typename std::span<const UuidRefType>::iterator>)
716 {
717 arg += n;
718 }
719 else if constexpr (
720 std::is_same_v<
721 T,
722 std::pair<
723 typename std::span<const UuidType>::iterator, const RegistryT *>>)
724 {
725 arg.first += n;
726 }
727 },
728 it_var_);
729 return *this;
730 }
731
732 constexpr difference_type operator- (Iterator other) const
733 {
734 return std::visit (
735 [] (auto &&arg, auto &&other_arg) -> difference_type {
736 using T = std::decay_t<decltype (arg)>;
737 using OtherT = std::decay_t<decltype (other_arg)>;
738 if constexpr (!std::is_same_v<T, OtherT>)
739 {
740 throw std::runtime_error (
741 "Comparing iterators of different types");
742 }
743 else if constexpr (
744 std::is_same_v<T, typename std::span<const VariantType>::iterator>)
745 {
746 return arg - other_arg;
747 }
748 else if constexpr (
749 std::is_same_v<T, typename std::span<const UuidRefType>::iterator>)
750 {
751 return arg - other_arg;
752 }
753 else if constexpr (
754 std::is_same_v<
755 T,
756 std::pair<
757 typename std::span<const UuidType>::iterator, const RegistryT *>>)
758 {
759 return arg.first - other_arg.first;
760 }
761 else
762 {
763 return 0;
764 }
765 },
766 it_var_, other.it_var_);
767 }
768
769 constexpr auto operator<=> (const Iterator &other) const
770 {
771 return std::visit (
772 [] (auto &&arg, auto &&other_arg) -> std::strong_ordering {
773 using T = std::decay_t<decltype (arg)>;
774 using OtherT = std::decay_t<decltype (other_arg)>;
775 if constexpr (!std::is_same_v<T, OtherT>)
776 {
777 throw std::runtime_error (
778 "Comparing iterators of different types");
779 }
780 else if constexpr (
781 std::is_same_v<T, typename std::span<const VariantType>::iterator>)
782 {
783 return arg <=> other_arg;
784 }
785 else if constexpr (
786 std::is_same_v<T, typename std::span<const UuidRefType>::iterator>)
787 {
788 return arg <=> other_arg;
789 }
790 else if constexpr (
791 std::is_same_v<
792 T,
793 std::pair<
794 typename std::span<const UuidType>::iterator, const RegistryT *>>)
795 {
796 return arg.first <=> other_arg.first;
797 }
798 else
799 {
800 return std::strong_ordering::equal;
801 }
802 },
803 it_var_, other.it_var_);
804 }
805
806 private:
807 std::variant<
808 typename std::span<const VariantType>::iterator,
809 typename std::span<const UuidRefType>::iterator,
810 std::pair<typename std::span<const UuidType>::iterator, const RegistryT *>>
811 it_var_;
812 };
813
815 explicit UuidIdentifiableObjectView (std::span<const VariantType> objects)
816 : objects_ (objects)
817 {
818 }
819
821 explicit UuidIdentifiableObjectView (std::span<const UuidRefType> refs)
822 : refs_ (refs)
823 {
824 }
825
828 const RegistryT &registry,
829 std::span<const UuidType> uuids)
830 : uuids_ (uuids), registry_ (&registry)
831 {
832 }
833
835 explicit UuidIdentifiableObjectView (const VariantType &obj)
836 : objects_ (std::span<const VariantType> (&obj, 1))
837 {
838 }
839
840 Iterator begin () const
841 {
842 if (objects_)
843 {
844 return Iterator (objects_->begin ());
845 }
846 else if (refs_)
847 {
848 return Iterator (refs_->begin ());
849 }
850 else
851 {
852 return Iterator (uuids_->begin (), registry_);
853 }
854 }
855
856 Iterator end () const
857 {
858 if (objects_)
859 {
860 return Iterator (objects_->end ());
861 }
862 else if (refs_)
863 {
864 return Iterator (refs_->end ());
865 }
866 else
867 {
868 return Iterator (uuids_->end (), registry_);
869 }
870 }
871
872 VariantType operator[] (size_t index) const
873 {
874 if (objects_)
875 return (*objects_)[index];
876 if (refs_)
877 return (*refs_)[index].get_object ();
878 return registry_->find_by_id_or_throw ((*uuids_)[index]);
879 }
880
881 VariantType front () const { return *begin (); }
882 VariantType back () const
883 {
884 if (empty ())
885 throw std::out_of_range ("Cannot get back() of empty view");
886
887 auto it = end ();
888 --it;
889 return *it;
890 }
891 VariantType at (size_t index) const
892 {
893 if (index >= size ())
894 throw std::out_of_range (
895 "UuidIdentifiableObjectView::at index out of range");
896
897 return (*this)[index];
898 }
899
900 size_t size () const
901 {
902 if (objects_)
903 return objects_->size ();
904 if (refs_)
905 return refs_->size ();
906 return uuids_->size ();
907 }
908
909 bool empty () const { return size () == 0; }
910
911 // Projections and transformations
912 static UuidType uuid_projection (const VariantType &var)
913 {
914 return std::visit ([] (const auto &obj) { return obj->get_uuid (); }, var);
915 }
916
917 template <typename T> auto get_elements_by_type () const
918 {
919 return *this | std::views::filter ([] (const auto &var) {
920 return std::holds_alternative<T *> (var);
921 }) | std::views::transform ([] (const auto &var) {
922 return std::get<T *> (var);
923 });
924 }
925
926 static RegistryT::BaseType * base_projection (const VariantType &var)
927 {
928 return std::visit (
929 [] (const auto &ptr) -> RegistryT::BaseType * { return ptr; }, var);
930 }
931
932 auto as_base_type () const
933 {
934 return std::views::transform (*this, base_projection);
935 }
936
937 template <typename T> static auto type_projection (const VariantType &var)
938 {
939 return std::holds_alternative<T *> (var);
940 }
941
942 template <typename T> bool contains_type () const
943 {
944 return std::ranges::any_of (*this, type_projection<T>);
945 }
946
947 template <typename BaseType>
948 static auto derived_from_type_projection (const VariantType &var)
949 {
950 return std::visit (
951 [] (const auto &ptr) {
952 return std::derived_from<base_type<decltype (ptr)>, BaseType>;
953 },
954 var);
955 }
956
961 template <typename T> static auto type_transformation (const VariantType &var)
962 {
963 return std::get<T *> (var);
964 }
965
966 template <typename T>
967 static auto derived_from_type_transformation (const VariantType &var)
968 {
969 return std::visit (
970 [] (const auto &ptr) -> T * {
971 using ElementT = base_type<decltype (ptr)>;
972 if constexpr (std::derived_from<ElementT, T>)
973 return ptr;
974 throw std::runtime_error ("Not derived from type");
975 },
976 var);
977 }
978
979 // note: assumes all objects are of this type
980 template <typename T> auto as_type () const
981 {
982 return std::views::transform (*this, type_transformation<T>);
983 }
984
985 template <typename T> auto get_elements_derived_from () const
986 {
987 return *this | std::views::filter (derived_from_type_projection<T>)
988 | std::views::transform (derived_from_type_transformation<T>);
989 }
990
991private:
992 std::optional<std::span<const VariantType>> objects_;
993 std::optional<std::span<const UuidRefType>> refs_;
994 std::optional<std::span<const UuidType>> uuids_;
995 const RegistryT * registry_ = nullptr;
996};
997
998} // namespace zrythm::utils
999
1000// Concept to detect UuidIdentifiableObject::Uuid types
1001template <typename T>
1002concept UuidType = requires (T t) {
1003 typename T::UuidTag;
1004 { type_safe::get (t) } -> std::convertible_to<QUuid>;
1005};
1006
1007// Custom formatter for UuidReference - prints UUID and visited object
1008template <typename RegistryT>
1009struct fmt::formatter<zrythm::utils::UuidReference<RegistryT>>
1010 : fmt::formatter<std::string_view>
1011{
1012 template <typename FormatContext>
1013 auto
1014 format (const zrythm::utils::UuidReference<RegistryT> &ref, FormatContext &ctx)
1015 const
1016 {
1017 auto out = ctx.out ();
1018 *out++ = '{';
1019
1020 // Format the UUID
1021 out = fmt::format_to (out, " .id_={}", ref.id ());
1022
1023 // Visit and format the object
1024 out = fmt::format_to (out, ", .object=");
1025 out = std::visit (
1026 [&] (auto &&obj) { return fmt::format_to (out, "{}", *obj); },
1027 ref.get_object ());
1028
1029 *out++ = ' ';
1030 *out++ = '}';
1031 return out;
1032 }
1033};
1034
1035// Formatter for any UUID type from UuidIdentifiableObject
1036template <UuidType T>
1037struct fmt::formatter<T> : fmt::formatter<std::string_view>
1038{
1039 template <typename FormatContext>
1040 auto format (const T &uuid, FormatContext &ctx) const
1041 {
1042 return fmt::formatter<std::string_view>{}.format (
1043 type_safe::get (uuid).toString (QUuid::WithoutBraces).toUtf8 (), ctx);
1044 }
1045};
void register_object(VariantT obj_ptr)
Registers an object.
auto reference_count(const UuidType &id) const
Returns the reference count of an object.
auto get_uuids() const
Returns a list of all UUIDs of the objects in the registry.
std::optional< VariantT > find_by_id(const UuidType &id) const noexcept
Returns an object by id.
UuidIdentifiableObjectView(std::span< const UuidRefType > refs)
Constructor for UuidReference range.
static auto type_transformation(const VariantType &var)
UuidIdentifiableObjectView(std::span< const VariantType > objects)
Constructor for direct object range.
UuidIdentifiableObjectView(const VariantType &obj)
Single object constructor.
UuidIdentifiableObjectView(const RegistryT &registry, std::span< const UuidType > uuids)
Constructor for Uuid + Registry.
A reference-counted RAII wrapper for a UUID in a registry.
void set_id(const UuidType &id)
To be used when using the Registry-only constructor.
String utilities.
@ NewIdentity
Creates a separately identified object.
Definition icloneable.h:30
bool is_null() const
Checks if the UUID is null.