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-2025 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 static constexpr auto kIdKey = "id"sv;
144
145 // unengaged - need to call set_id to acquire a reference
146 UuidReference (RegistryT &registry) : registry_ (registry) { }
147
148 UuidReference (const UuidType &id, RegistryT &registry)
149 : id_ (id), registry_ (registry)
150 {
151 acquire_ref ();
152 }
153
154 UuidReference (const UuidReference &other)
155 : UuidReference (other.get_registry ())
156 {
157 if (other.id_.has_value ())
158 {
159 set_id (other.id_.value ());
160 }
161 }
162 UuidReference &operator= (const UuidReference &other)
163 {
164 if (this != &other)
165 {
166 id_ = other.id_;
167 registry_ = other.registry_;
168 acquire_ref ();
169 }
170 return *this;
171 }
172
173 UuidReference (UuidReference &&other)
174 : id_ (std::exchange (other.id_, std::nullopt)),
175 registry_ (std::exchange (other.registry_, std::nullopt))
176 {
177 // No need to acquire_ref() here because we're taking over the reference
178 // from 'other' which already holds it
179 }
180
181 UuidReference &operator= (UuidReference &&other)
182 {
183 if (this != &other)
184 {
185 // Release our current reference (if any)
186 release_ref ();
187
188 // Take ownership from other
189 id_ = std::exchange (other.id_, std::nullopt);
190 registry_ = std::exchange (other.registry_, std::nullopt);
191
192 // No need to acquire_ref() - we're taking over the reference
193 }
194 return *this;
195 }
196
197 ~UuidReference () { release_ref (); }
198
199 auto id () const -> UuidType { return *id_; }
200
208 void set_id (const UuidType &id)
209 {
210 if (id_.has_value ())
211 {
212 throw std::runtime_error (
213 "Cannot set id of UuidReference that already has an id");
214 }
215 id_ = id;
216 acquire_ref ();
217 }
218
219 VariantType get_object () const
220 {
221 return get_registry ().find_by_id_or_throw (id ());
222 }
223
224 // Convenience getter
225 template <typename ObjectT>
226 ObjectT * get_object_as () const
227 requires IsInVariant<ObjectT *, VariantType>
228 {
229 return std::get<ObjectT *> (get_object ());
230 }
231
232 RegistryT::BaseType * get_object_base () const
233 {
234 return get_registry ().find_by_id_as_base_or_throw (id ());
235 }
236
237 template <typename... Args>
238 UuidReference clone_new_identity (Args &&... args) const
239 {
240 return std::visit (
241 [&] (auto &&obj) {
242 return get_registry ().clone_object (*obj, std::forward<Args> (args)...);
243 },
244 get_object ());
245 }
246
247 auto get_iterator_in_registry () const
248 {
249 return get_registry ().get_iterator_for_id (id ());
250 }
251
252 auto get_iterator_in_registry ()
253 {
254 return get_registry ().get_iterator_for_id (id ());
255 }
256
257private:
258 void acquire_ref ()
259 {
260 if (id_.has_value ())
261 {
262 get_registry ().acquire_reference (*id_);
263 }
264 }
265 void release_ref ()
266 {
267 if (id_.has_value ())
268 {
269 get_registry ().release_reference (*id_);
270 }
271 }
272 RegistryT &get_registry () const
273 {
274 return const_cast<RegistryT &> (*registry_);
275 }
276 RegistryT &get_registry () { return *registry_; }
277
278 friend void to_json (nlohmann::json &j, const UuidReference &ref)
279 {
280 assert (ref.id_.has_value ());
281 j[kIdKey] = *ref.id_;
282 }
283 friend void from_json (const nlohmann::json &j, UuidReference &ref)
284 {
285 auto tmp = ref;
286 j.at (kIdKey).get_to (ref.id_);
287 ref.acquire_ref ();
288 tmp.release_ref ();
289 }
290
291 friend bool operator== (const UuidReference &lhs, const UuidReference &rhs)
292 {
293 return lhs.id () == rhs.id ();
294 }
295
296private:
297 std::optional<UuidType> id_;
298 OptionalRef<RegistryT> registry_;
299
300 BOOST_DESCRIBE_CLASS (UuidReference<RegistryT>, (), (), (), (id_))
301};
302
303template <typename ReturnType, typename UuidType>
304using UuidIdentifiablObjectResolver =
305 std::function<ReturnType (const UuidType &)>;
306
324template <typename VariantT, UuidIdentifiable BaseT>
325class OwningObjectRegistry : public QObject
326{
327public:
329 using VariantType = VariantT;
330 using BaseType = BaseT;
331
332 OwningObjectRegistry (QObject * parent = nullptr)
333 : QObject (parent), main_thread_id_ (current_thread_id)
334 {
335 }
336
337 // ========================================================================
338 // QML/QObject Interface
339 // ========================================================================
340
341 // TODO signals...
342
343 // ========================================================================
344
345 // Factory method that forwards constructor arguments
346 template <typename CreateType, typename... Args>
347 auto create_object (Args &&... args) -> UuidReference<OwningObjectRegistry>
348 requires std::derived_from<CreateType, BaseT>
349 {
350 assert (is_main_thread ());
351
352 auto * obj = new CreateType (std::forward<Args> (args)...);
353 z_trace (
354 "created object of type {} with ID {}", typeid (CreateType).name (),
355 obj->get_uuid ());
356 register_object (obj);
357 return { obj->get_uuid (), *this };
358 }
359
360 // Creates a clone of the given object and registers it to the registry.
361 template <typename CreateType, typename... Args>
362 auto clone_object (const CreateType &other, Args &&... args)
364 requires std::derived_from<CreateType, BaseT>
365 {
366 assert (is_main_thread ());
367
368 CreateType * obj = clone_raw_ptr (
369 other, ObjectCloneType::NewIdentity, std::forward<Args> (args)...);
370 register_object (obj);
371 return { obj->get_uuid (), *this };
372 }
373
374 [[gnu::hot]] auto get_iterator_for_id (const UuidType &id) const
375 {
376 // TODO: some failures left to fix
377 // assert (is_main_thread ());
378
379 return objects_by_id_.find (id);
380 }
381
382 [[gnu::hot]] auto get_iterator_for_id (const UuidType &id)
383 {
384 // assert (is_main_thread ());
385
386 return objects_by_id_.find (id);
387 }
388
394 [[gnu::hot]] std::optional<VariantT>
395 find_by_id (const UuidType &id) const noexcept [[clang::blocking]]
396 {
397 const auto it = get_iterator_for_id (id);
398 if (it == objects_by_id_.end ())
399 {
400 return std::nullopt;
401 }
402 return it->second;
403 }
404
405 [[gnu::hot]] auto find_by_id_or_throw (const UuidType &id) const
406 {
407 auto val = find_by_id (id);
408 if (!val.has_value ())
409 {
410 throw std::runtime_error (
411 fmt::format ("Object with id {} not found", id));
412 }
413 return val.value ();
414 }
415
416 BaseT * find_by_id_as_base_or_throw (const UuidType &id) const
417 {
418 auto var = find_by_id_or_throw (id);
419 return std::visit ([] (auto &&val) -> BaseT * { return val; }, var);
420 }
421
422 bool contains (const UuidType &id) const
423 {
424 // assert (is_main_thread ());
425
426 return objects_by_id_.contains (id);
427 }
428
434 void register_object (VariantT obj_ptr)
435 {
436 assert (is_main_thread ());
437
438 std::visit (
439 [&] (auto &&obj) {
440 if (contains (obj->get_uuid ()))
441 {
442 throw std::runtime_error (
443 fmt::format ("Object with id {} already exists", obj->get_uuid ()));
444 }
445 z_trace ("Registering (inserting) object {}", obj->get_uuid ());
446 obj->setParent (this);
447 objects_by_id_.emplace (obj->get_uuid (), obj);
448 },
449 obj_ptr);
450 }
451
452 template <typename ObjectT>
453 void register_object (ObjectT &obj)
454 requires std::derived_from<ObjectT, BaseT>
455 {
456 register_object (&obj);
457 }
458
464 auto reference_count (const UuidType &id) const
465 {
466 assert (is_main_thread ());
467
468 return ref_counts_.at (id);
469 }
470
471 void acquire_reference (const UuidType &id)
472 {
473 assert (is_main_thread ());
474
475 ref_counts_[id]++;
476 }
477
478 void release_reference (const UuidType &id)
479 {
480 assert (is_main_thread ());
481
482 if (--ref_counts_[id] <= 0)
483 {
484 delete_object_by_id (id);
485 }
486 }
487
488 auto &get_hash_map () const { return objects_by_id_; }
489
493 auto get_uuids () const
494 {
495 assert (is_main_thread ());
496
497 return objects_by_id_ | std::views::keys | std::ranges::to<std::vector> ();
498 }
499
500 size_t size () const
501 {
502 assert (is_main_thread ());
503
504 return objects_by_id_.size ();
505 }
506
507 friend void to_json (nlohmann::json &j, const OwningObjectRegistry &obj)
508 {
509 auto objects = nlohmann::json::array ();
510 for (const auto &var : obj.objects_by_id_ | std::views::values)
511 {
512 objects.push_back (var);
513 }
514 j[kObjectsKey] = objects;
515 }
516 template <ObjectBuilder BuilderT>
517 friend void from_json_with_builder (
518 const nlohmann::json &j,
519 OwningObjectRegistry &obj,
520 const BuilderT &builder)
521 {
522 auto objects = j.at (kObjectsKey);
523 for (const auto &object_var_json : objects)
524 {
525 VariantT object_var;
526 utils::serialization::variant_from_json_with_builder (
527 object_var_json, object_var, builder);
528 obj.register_object (object_var);
529 }
530 }
531
532private:
533 static constexpr const char * kObjectsKey = "objectsById";
534
541 VariantT unregister_object (const UuidType &id)
542 {
543 if (!objects_by_id_.contains (id))
544 {
545 throw std::runtime_error (
546 fmt::format ("Object with id {} not found", id));
547 }
548
549 z_trace ("Unregistering object with id {}", id);
550
551 auto obj_it = get_iterator_for_id (id);
552 auto obj_var = obj_it->second;
553 objects_by_id_.erase (obj_it);
554 std::visit ([&] (auto &&obj) { obj->setParent (nullptr); }, obj_var);
555 ref_counts_.erase (id);
556 return obj_var;
557 }
558
559 void delete_object_by_id (const UuidType &id)
560 {
561 auto obj_var = unregister_object (id);
562 std::visit ([&] (auto &&obj) { delete obj; }, obj_var);
563 }
564
565 bool is_main_thread () const { return main_thread_id_ == current_thread_id; }
566
567private:
573 boost::unordered::unordered_flat_map<UuidType, VariantT> objects_by_id_;
574
580 boost::unordered::unordered_flat_map<UuidType, int> ref_counts_;
581
582 RTThreadId main_thread_id_;
583};
584
585#define DEFINE_UUID_IDENTIFIABLE_OBJECT_SELECTION_MANAGER_QML_PROPERTIES( \
586 ClassType, FullyQualifiedChildBaseType) \
587public: \
588 static_assert ( \
589 std::is_same_v<FullyQualifiedChildBaseType, RegistryType::BaseType>); \
590 Q_SIGNAL void selectionChanged (); \
591 Q_SIGNAL void objectSelectionStatusChanged ( \
592 RegistryType::BaseType * object, bool selected); \
593 Q_SIGNAL void lastSelectedObjectChanged (); \
594 Q_PROPERTY (/* NOLINTNEXTLINE */ \
595 FullyQualifiedChildBaseType * lastSelectedObject READ \
596 lastSelectedObject NOTIFY lastSelectedObjectChanged) \
597 Q_INVOKABLE bool isSelected (RegistryType::BaseType * object) \
598 { \
599 return is_selected (object->get_uuid ()); \
600 } \
601 Q_INVOKABLE void selectUnique (RegistryType::BaseType * object) \
602 { \
603 select_unique (object->get_uuid ()); \
604 } \
605 Q_INVOKABLE void addToSelection (RegistryType::BaseType * object) \
606 { \
607 append_to_selection (object->get_uuid ()); \
608 } \
609 Q_INVOKABLE void removeFromSelection (RegistryType::BaseType * object) \
610 { \
611 remove_from_selection (object->get_uuid ()); \
612 }
613
614template <typename RegistryT> class UuidIdentifiableObjectSelectionManager
615{
616 using UuidType = typename RegistryT::UuidType;
617
618public:
619 using RegistryType = RegistryT;
620 using UuidSet = std::unordered_set<UuidType>;
621
622 UuidIdentifiableObjectSelectionManager (const RegistryT &registry)
623 : registry_ (registry)
624 {
625 }
626
627 RegistryT::BaseType * lastSelectedObject () const
628 {
629 if (last_selected_object_.has_value ())
630 return get_object_base (last_selected_object_.value ());
631
632 return nullptr;
633 }
634
635 void append_to_selection (this auto &&self, const UuidType &id)
636 {
637 if (!self.is_selected (id))
638 {
639 self.selected_objects_.insert (id);
640 self.last_selected_object_ = id;
641 Q_EMIT self.objectSelectionStatusChanged (
642 self.get_object_base (id), true);
643 Q_EMIT self.lastSelectedObjectChanged ();
644 Q_EMIT self.selectionChanged ();
645 }
646 }
647 void remove_from_selection (this auto &&self, const UuidType &id)
648 {
649 if (self.is_selected (id))
650 {
651 self.selected_objects_.erase (id);
652 if (
653 self.last_selected_object_.has_value ()
654 && self.last_selected_object_.value () == id)
655 {
656 self.last_selected_object_.reset ();
657 Q_EMIT self.lastSelectedObjectChanged ();
658 }
659
660 Q_EMIT self.objectSelectionStatusChanged (
661 self.get_object_base (id), false);
662 Q_EMIT self.selectionChanged ();
663 }
664 }
665 void select_unique (this auto &&self, const UuidType &id)
666 {
667 self.clear_selection ();
668 self.append_to_selection (id);
669 }
670 bool is_selected (const UuidType &id) const
671 {
672 return selected_objects_.contains (id);
673 }
674 bool is_only_selection (const UuidType &id) const
675 {
676 return selected_objects_.size () == 1 && is_selected (id);
677 }
678 bool empty () const { return selected_objects_.empty (); }
679 auto size () const { return selected_objects_.size (); }
680
681 void clear_selection (this auto &&self)
682 { // Make a copy of the selected tracks to iterate over
683 auto selected_objs_copy = self.selected_objects_;
684 for (const auto &uuid : selected_objs_copy)
685 {
686 self.remove_from_selection (uuid);
687 }
688 }
689
690 template <RangeOf<UuidType> UuidRange>
691 void select_only_these (this auto &&self, const UuidRange &uuids)
692 {
693 self.clear_selection ();
694 for (const auto &uuid : uuids)
695 {
696 self.append_to_selection (uuid);
697 }
698 }
699
700private:
701 RegistryT::BaseType * get_object_base (const UuidType &id) const
702 {
703 return registry_.find_by_id_as_base_or_throw (id);
704 }
705
706private:
707 UuidSet selected_objects_;
708 std::optional<UuidType> last_selected_object_;
709 const RegistryT &registry_;
710};
711
718template <typename RegistryT>
720 : public std::ranges::view_interface<UuidIdentifiableObjectView<RegistryT>>
721{
722public:
723 using UuidType = typename RegistryT::UuidType;
724 using VariantType = typename RegistryT::VariantType;
725 using UuidRefType = UuidReference<RegistryT>;
726
727 // Proxy iterator implementation using Boost.STLInterfaces
728 class Iterator
729 : public boost::stl_interfaces::proxy_iterator_interface<
730#if !BOOST_STL_INTERFACES_USE_DEDUCED_THIS
731 Iterator,
732#endif
733 std::random_access_iterator_tag,
734 VariantType>
735 {
736 public:
737 using base_type = boost::stl_interfaces::proxy_iterator_interface<
738#if !BOOST_STL_INTERFACES_USE_DEDUCED_THIS
739 Iterator,
740#endif
741 std::random_access_iterator_tag,
742 VariantType>;
743 using difference_type = base_type::difference_type;
744
745 constexpr Iterator () noexcept = default;
746
747 // Constructor for direct object range
748 constexpr Iterator (std::span<const VariantType>::iterator it) noexcept
749 : it_var_ (it)
750 {
751 }
752
753 // Constructor for UuidReference range
754 constexpr Iterator (std::span<const UuidRefType>::iterator it) noexcept
755 : it_var_ (it)
756 {
757 }
758
759 // Constructor for Uuid + Registry range
760 constexpr Iterator (
761 std::span<const UuidType>::iterator it,
762 const RegistryT * registry) noexcept
763 : it_var_ (std::pair{ it, registry })
764 {
765 }
766
767 constexpr VariantType operator* () const
768 {
769 return std::visit (
770 [] (auto &&arg) {
771 using T = std::decay_t<decltype (arg)>;
772 if constexpr (
773 std::is_same_v<T, typename std::span<const VariantType>::iterator>)
774 {
775 return *arg;
776 }
777 else if constexpr (
778 std::is_same_v<T, typename std::span<const UuidRefType>::iterator>)
779 {
780 return arg->get_object ();
781 }
782 else if constexpr (
783 std::is_same_v<
784 T,
785 std::pair<
786 typename std::span<const UuidType>::iterator, const RegistryT *>>)
787 {
788 return arg.second->find_by_id_or_throw (*arg.first);
789 }
790 },
791 it_var_);
792 }
793
794 constexpr Iterator &operator+= (difference_type n) noexcept
795 {
796 std::visit (
797 [n] (auto &&arg) {
798 using T = std::decay_t<decltype (arg)>;
799 if constexpr (
800 std::is_same_v<T, typename std::span<const VariantType>::iterator>)
801 {
802 arg += n;
803 }
804 else if constexpr (
805 std::is_same_v<T, typename std::span<const UuidRefType>::iterator>)
806 {
807 arg += n;
808 }
809 else if constexpr (
810 std::is_same_v<
811 T,
812 std::pair<
813 typename std::span<const UuidType>::iterator, const RegistryT *>>)
814 {
815 arg.first += n;
816 }
817 },
818 it_var_);
819 return *this;
820 }
821
822 constexpr difference_type operator- (Iterator other) const
823 {
824 return std::visit (
825 [] (auto &&arg, auto &&other_arg) -> difference_type {
826 using T = std::decay_t<decltype (arg)>;
827 using OtherT = std::decay_t<decltype (other_arg)>;
828 if constexpr (!std::is_same_v<T, OtherT>)
829 {
830 throw std::runtime_error (
831 "Comparing iterators of different types");
832 }
833 else if constexpr (
834 std::is_same_v<T, typename std::span<const VariantType>::iterator>)
835 {
836 return arg - other_arg;
837 }
838 else if constexpr (
839 std::is_same_v<T, typename std::span<const UuidRefType>::iterator>)
840 {
841 return arg - other_arg;
842 }
843 else if constexpr (
844 std::is_same_v<
845 T,
846 std::pair<
847 typename std::span<const UuidType>::iterator, const RegistryT *>>)
848 {
849 return arg.first - other_arg.first;
850 }
851 else
852 {
853 return 0;
854 }
855 },
856 it_var_, other.it_var_);
857 }
858
859 constexpr auto operator<=> (const Iterator &other) const
860 {
861 return std::visit (
862 [] (auto &&arg, auto &&other_arg) -> std::strong_ordering {
863 using T = std::decay_t<decltype (arg)>;
864 using OtherT = std::decay_t<decltype (other_arg)>;
865 if constexpr (!std::is_same_v<T, OtherT>)
866 {
867 throw std::runtime_error (
868 "Comparing iterators of different types");
869 }
870 else if constexpr (
871 std::is_same_v<T, typename std::span<const VariantType>::iterator>)
872 {
873 return arg <=> other_arg;
874 }
875 else if constexpr (
876 std::is_same_v<T, typename std::span<const UuidRefType>::iterator>)
877 {
878 return arg <=> other_arg;
879 }
880 else if constexpr (
881 std::is_same_v<
882 T,
883 std::pair<
884 typename std::span<const UuidType>::iterator, const RegistryT *>>)
885 {
886 return arg.first <=> other_arg.first;
887 }
888 else
889 {
890 return std::strong_ordering::equal;
891 }
892 },
893 it_var_, other.it_var_);
894 }
895
896 private:
897 std::variant<
898 typename std::span<const VariantType>::iterator,
899 typename std::span<const UuidRefType>::iterator,
900 std::pair<typename std::span<const UuidType>::iterator, const RegistryT *>>
901 it_var_;
902 };
903
905 explicit UuidIdentifiableObjectView (std::span<const VariantType> objects)
906 : objects_ (objects)
907 {
908 }
909
911 explicit UuidIdentifiableObjectView (std::span<const UuidRefType> refs)
912 : refs_ (refs)
913 {
914 }
915
918 const RegistryT &registry,
919 std::span<const UuidType> uuids)
920 : uuids_ (uuids), registry_ (&registry)
921 {
922 }
923
925 explicit UuidIdentifiableObjectView (const VariantType &obj)
926 : objects_ (std::span<const VariantType> (&obj, 1))
927 {
928 }
929
930 Iterator begin () const
931 {
932 if (objects_)
933 {
934 return Iterator (objects_->begin ());
935 }
936 else if (refs_)
937 {
938 return Iterator (refs_->begin ());
939 }
940 else
941 {
942 return Iterator (uuids_->begin (), registry_);
943 }
944 }
945
946 Iterator end () const
947 {
948 if (objects_)
949 {
950 return Iterator (objects_->end ());
951 }
952 else if (refs_)
953 {
954 return Iterator (refs_->end ());
955 }
956 else
957 {
958 return Iterator (uuids_->end (), registry_);
959 }
960 }
961
962 VariantType operator[] (size_t index) const
963 {
964 if (objects_)
965 return (*objects_)[index];
966 if (refs_)
967 return (*refs_)[index].get_object ();
968 return registry_->find_by_id_or_throw ((*uuids_)[index]);
969 }
970
971 VariantType front () const { return *begin (); }
972 VariantType back () const
973 {
974 if (empty ())
975 throw std::out_of_range ("Cannot get back() of empty view");
976
977 auto it = end ();
978 --it;
979 return *it;
980 }
981 VariantType at (size_t index) const
982 {
983 if (index >= size ())
984 throw std::out_of_range (
985 "UuidIdentifiableObjectView::at index out of range");
986
987 return (*this)[index];
988 }
989
990 size_t size () const
991 {
992 if (objects_)
993 return objects_->size ();
994 if (refs_)
995 return refs_->size ();
996 return uuids_->size ();
997 }
998
999 bool empty () const { return size () == 0; }
1000
1001 // Projections and transformations
1002 static UuidType uuid_projection (const VariantType &var)
1003 {
1004 return std::visit ([] (const auto &obj) { return obj->get_uuid (); }, var);
1005 }
1006
1007 template <typename T> auto get_elements_by_type () const
1008 {
1009 return *this | std::views::filter ([] (const auto &var) {
1010 return std::holds_alternative<T *> (var);
1011 }) | std::views::transform ([] (const auto &var) {
1012 return std::get<T *> (var);
1013 });
1014 }
1015
1016 static RegistryT::BaseType * base_projection (const VariantType &var)
1017 {
1018 return std::visit (
1019 [] (const auto &ptr) -> RegistryT::BaseType * { return ptr; }, var);
1020 }
1021
1022 auto as_base_type () const
1023 {
1024 return std::views::transform (*this, base_projection);
1025 }
1026
1027 template <typename T> static auto type_projection (const VariantType &var)
1028 {
1029 return std::holds_alternative<T *> (var);
1030 }
1031
1032 template <typename T> bool contains_type () const
1033 {
1034 return std::ranges::any_of (*this, type_projection<T>);
1035 }
1036
1037 template <typename BaseType>
1038 static auto derived_from_type_projection (const VariantType &var)
1039 {
1040 return std::visit (
1041 [] (const auto &ptr) {
1042 return std::derived_from<base_type<decltype (ptr)>, BaseType>;
1043 },
1044 var);
1045 }
1046
1051 template <typename T> static auto type_transformation (const VariantType &var)
1052 {
1053 return std::get<T *> (var);
1054 }
1055
1056 template <typename T>
1057 static auto derived_from_type_transformation (const VariantType &var)
1058 {
1059 return std::visit (
1060 [] (const auto &ptr) -> T * {
1061 using ElementT = base_type<decltype (ptr)>;
1062 if constexpr (std::derived_from<ElementT, T>)
1063 return ptr;
1064 throw std::runtime_error ("Not derived from type");
1065 },
1066 var);
1067 }
1068
1069 // note: assumes all objects are of this type
1070 template <typename T> auto as_type () const
1071 {
1072 return std::views::transform (*this, type_transformation<T>);
1073 }
1074
1075 template <typename T> auto get_elements_derived_from () const
1076 {
1077 return *this | std::views::filter (derived_from_type_projection<T>)
1078 | std::views::transform (derived_from_type_transformation<T>);
1079 }
1080
1081private:
1082 std::optional<std::span<const VariantType>> objects_;
1083 std::optional<std::span<const UuidRefType>> refs_;
1084 std::optional<std::span<const UuidType>> uuids_;
1085 const RegistryT * registry_ = nullptr;
1086};
1087
1088} // namespace zrythm::utils
1089
1090// Concept to detect UuidIdentifiableObject::Uuid types
1091template <typename T>
1092concept UuidType = requires (T t) {
1093 typename T::UuidTag;
1094 { type_safe::get (t) } -> std::convertible_to<QUuid>;
1095};
1096
1097// Formatter for any UUID type from UuidIdentifiableObject
1098template <UuidType T>
1099struct fmt::formatter<T> : fmt::formatter<std::string_view>
1100{
1101 template <typename FormatContext>
1102 auto format (const T &uuid, FormatContext &ctx) const
1103 {
1104 return fmt::formatter<QString>{}.format (
1105 type_safe::get (uuid).toString (QUuid::WithoutBraces), ctx);
1106 }
1107};
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.