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