libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
objectbase.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_CORE
13#include <tuple>
14#include <type_traits>
15#include <functional>
16#include <promeki/function.h>
17#include <promeki/namespace.h>
18#include <promeki/list.h>
19#include <promeki/map.h>
20#include <promeki/mutex.h>
21#include <promeki/util.h>
22#include <promeki/logger.h>
23#include <promeki/signal.h>
24#include <promeki/slot.h>
25
26PROMEKI_NAMESPACE_BEGIN
27
28class Variant;
29class VariantList;
30class VariantMap;
31
32class EventLoop;
33class Event;
34class TimerEvent;
35class Thread;
36
37#define PROMEKI_OBJECT(ObjectName, ParentObjectName) \
38public: \
39 static const MetaInfo &metaInfo() { \
40 static const MetaInfo __metaInfo(typeid(ObjectName).name(), &ParentObjectName::metaInfo()); \
41 return __metaInfo; \
42 }
43
44#define PROMEKI_SIGNAL(SIGNALNAME, ...) \
45 static constexpr const char *SIGNALNAME##SignalName = \
46 PROMEKI_STRINGIFY(SIGNALNAME) "(" PROMEKI_STRINGIFY_ARGS(__VA_ARGS__) ")"; \
47 static inline SignalMeta SIGNALNAME##SignalMeta = SignalMeta(metaInfo(), SIGNALNAME##SignalName); \
48 Signal<__VA_ARGS__> SIGNALNAME##Signal = Signal<__VA_ARGS__>(this, SIGNALNAME##SignalName);
49
50#define PROMEKI_SLOT(SLOTNAME, ...) \
51 static constexpr const char *SLOTNAME##SlotName = \
52 PROMEKI_STRINGIFY(SLOTNAME) "(" PROMEKI_STRINGIFY_ARGS(__VA_ARGS__) ")"; \
53 static inline SlotMeta SLOTNAME##SlotMeta = SlotMeta(metaInfo(), SLOTNAME##SlotName); \
54 void SLOTNAME(__VA_ARGS__); \
55 Slot<__VA_ARGS__> SLOTNAME##Slot = \
56 Slot<__VA_ARGS__>([this](auto &&...args) { this->SLOTNAME(std::forward<decltype(args)>(args)...); }, \
57 this, SLOTNAME##SlotName); \
58 int SLOTNAME##SlotID = registerSlot(&SLOTNAME##Slot);
59
60
61class ObjectBase;
62
63using ObjectBaseList = ::promeki::List<ObjectBase *>;
64
90template <typename T = ObjectBase> class ObjectBasePtr {
91 friend class ObjectBase;
92 template <typename U> friend class ObjectBasePtr;
93
94 public:
96 ObjectBasePtr(T *object = nullptr) { linkTo(object); }
97
99 ObjectBasePtr(const ObjectBasePtr &other) { linkFromSource(other.p); }
100
106 template <typename U, typename = std::enable_if_t<std::is_base_of_v<T, U> && !std::is_same_v<T, U>>>
107 ObjectBasePtr(const ObjectBasePtr<U> &other) {
108 linkFromSource(other.p);
109 }
110
112 ~ObjectBasePtr() { unlink(); }
113
115 ObjectBasePtr &operator=(const ObjectBasePtr &other) {
116 if (this == &other) return *this;
117 unlink();
118 linkFromSource(other.p);
119 return *this;
120 }
121
123 bool isValid() const { return p.load(MemoryOrder::Acquire) != nullptr; }
124
126 T *data() { return static_cast<T *>(p.load(MemoryOrder::Acquire)); }
127
129 const T *data() const { return static_cast<const T *>(p.load(MemoryOrder::Acquire)); }
130
131 private:
133 using TrackerPtr = promeki::Atomic<ObjectBase *>;
134
135 TrackerPtr p{nullptr};
136
137 // Atomically (under @ref ObjectBase::objectBasePtrMutex)
138 // sets @c p to @p obj and registers @c &p in
139 // @c obj->_pointerMap. Used by every ObjectBasePtr
140 // constructor / assignment so the store of @c p and
141 // the addition to @c _pointerMap happen as a single
142 // critical section — without this fold a concurrent
143 // @ref ObjectBase::runCleanup can null every existing
144 // tracker, release the mutex, and let a later @c link
145 // write to freed memory while leaving the new tracker
146 // dangling-but-non-null.
147 void linkTo(ObjectBase *obj);
148
149 // Like @ref linkTo but reads the source pointer from
150 // another ObjectBasePtr's atomic under the same lock,
151 // so a concurrent destruction of the tracked object
152 // cannot slip between the read and the registration.
153 void linkFromSource(const TrackerPtr &source);
154
155 void unlink();
156};
157
159template <typename T> ObjectBasePtr(T *) -> ObjectBasePtr<T>;
160
161
186class ObjectBase {
187 template <typename U> friend class ObjectBasePtr;
188 friend class EventLoop;
189
190 public:
191 class SignalMeta;
192 class SlotMeta;
193
197 class MetaInfo {
198 friend class SignalMeta;
199 friend class SlotMeta;
200
201 public:
202 using SignalList = ::promeki::List<SignalMeta *>;
203 using SlotList = ::promeki::List<SlotMeta *>;
209 MetaInfo(const char *n, const MetaInfo *p = nullptr) : _parent(p), _name(n) {}
210
212 const MetaInfo *parent() const { return _parent; }
213
215 const char *name() const;
216
218 const SignalList &signalList() const { return _signalList; }
219
221 const SlotList &slotList() const { return _slotList; }
222
224 void dumpToLog() const;
225
226 private:
227 const MetaInfo *_parent;
228 const char *_name;
229 mutable String _demangledName;
230 mutable SignalList _signalList;
231 mutable SlotList _slotList;
232 };
233
235 class SignalMeta {
236 public:
242 SignalMeta(const MetaInfo &m, const char *n) : _meta(m), _name(n) {
243 m._signalList += this;
244 }
245
247 const char *name() const { return _name; }
248
249 private:
250 const MetaInfo &_meta;
251 const char *_name;
252 };
253
255 class SlotMeta {
256 public:
262 SlotMeta(const MetaInfo &m, const char *n) : _meta(m), _name(n) { m._slotList += this; }
263
265 const char *name() const { return _name; }
266
267 private:
268 const MetaInfo &_meta;
269 const char *_name;
270 };
271
273 static const MetaInfo &metaInfo() {
274 static const MetaInfo __metaInfo(typeid(ObjectBase).name());
275 return __metaInfo;
276 }
277
282 template <typename... Args> static void connect(Signal<Args...> *signal, Slot<Args...> *slot);
283
284
294 ObjectBase(ObjectBase *p = nullptr);
295
296 ObjectBase(const ObjectBase &) = delete;
297 ObjectBase(ObjectBase &&) = delete;
298 ObjectBase &operator=(const ObjectBase &) = delete;
299 ObjectBase &operator=(ObjectBase &&) = delete;
300
302 virtual ~ObjectBase() {
303 aboutToDestroySignal.emit(this);
304 setParent(nullptr);
305 destroyChildren();
306 runCleanup();
307 }
308
313 ObjectBase *parent() const { return _parent; }
314
331 void setParent(ObjectBase *p);
332
337 const ObjectBaseList &childList() const { return _childList; }
338
345 template <typename... Args> int registerSlot(Slot<Args...> *slot) {
346 int ret = _slotList.size();
347 slot->setID(ret);
348 _slotList += SlotItem(ret, slot->prototype());
349 return ret;
350 }
351
360 PROMEKI_SIGNAL(aboutToDestroy, ObjectBase *);
361
366 EventLoop *eventLoop() const { return _eventLoop; }
367
380 Thread *thread() const { return _thread; }
381
393 void moveToThread(Thread *t);
394
405 int startTimer(unsigned int intervalMs, bool singleShot = false);
406
411 void stopTimer(int timerId);
412
446 void deleteLater();
447
449 using CleanupHandler = Function<void(ObjectBase *)>;
450
474 void registerCleanup(ObjectBase *target, CleanupHandler fn);
475
476 protected:
478 ObjectBase *signalSender() { return _signalSender; }
479
488 virtual void event(Event *e);
489
498 virtual void timerEvent(TimerEvent *e);
499
500
501 private:
502 using CleanupFunc = CleanupHandler;
503
504 struct SlotItem {
505 int id;
506 const char *prototype;
507 };
508
509 struct Cleanup {
510 ObjectBasePtr<> object;
511 CleanupFunc func;
512 };
513
514 ObjectBase *_parent = nullptr;
515 ObjectBase *_signalSender = nullptr;
516 Thread *_thread = nullptr;
517 EventLoop *_eventLoop = nullptr;
518 ObjectBaseList _childList;
519 List<SlotItem> _slotList;
520
522 using PointerMapKey = promeki::Atomic<ObjectBase *> *;
523 using PointerMap = promeki::Map<PointerMapKey, PointerMapKey>;
524 PointerMap _pointerMap;
525 List<Cleanup> _cleanupList;
526
527 // A single process-wide mutex serializes every
528 // ObjectBasePtr link / unlink (linkTo, linkFromSource,
529 // unlink) with ObjectBase::runCleanup. A per-object
530 // mutex is unsafe here because unlink reads the obj
531 // pointer via the atomic and *then* needs to take a
532 // lock to manipulate the map — if we put that lock on
533 // the object itself, the object can be destroyed in
534 // between, leaving us with a use-after-free on the
535 // mutex member. Serializing through one global mutex
536 // closes that window: an unlink that observes obj
537 // != nullptr is guaranteed that runCleanup hasn't run
538 // yet (and won't until we release). The link path
539 // folds the source-pointer load and the @c _pointerMap
540 // registration into the same critical section so a
541 // newly constructed tracker is either fully registered
542 // before runCleanup nulls it, or never observes a
543 // doomed pointer at all.
544 static Mutex &objectBasePtrMutex();
545
546 void setOwnerThreadRecursive(Thread *t, EventLoop *loop);
547
548 void addChild(ObjectBase *c) {
549 _childList += c;
550 return;
551 }
552
553 void removeChild(ObjectBase *c) {
554 _childList.removeFirst(c);
555 return;
556 }
557
558 void destroyChildren() {
559 for (auto child : _childList) {
560 child->_parent = nullptr;
561 delete child;
562 }
563 _childList.clear();
564 return;
565 }
566
567 void runCleanup() {
568 // Null out any ObjectBasePtr's that are currently pointing
569 // to this object. Hold the global mutex so a concurrent
570 // ObjectBasePtr link / unlink on another thread can't
571 // observe a half-destroyed object.
572 {
573 Mutex::Locker lock(objectBasePtrMutex());
574 for (auto item : _pointerMap) {
575 item.first->store(nullptr, MemoryOrder::Release);
576 }
577 _pointerMap.clear();
578 }
579
580 // Walk down the cleanup list and run any cleanup functions
581 for (auto &item : _cleanupList) {
582 if (!item.object.isValid()) continue;
583 item.func(this);
584 }
585 _cleanupList.clear();
586 return;
587 }
588};
589
590template <typename T> inline void ObjectBasePtr<T>::linkTo(ObjectBase *obj) {
591 // Single critical section: store @c p and add to the tracked
592 // object's @c _pointerMap atomically. An interleaved
593 // runCleanup can either run entirely before us (it sees no
594 // entry, then we observe the cleared @c _pointerMap and
595 // implicitly do nothing meaningful — though @c obj is then a
596 // dangling pointer, see note below) or entirely after us (it
597 // observes the new entry and nulls @c p). Either way the
598 // invariant "non-null @c p implies registered in the tracked
599 // object's @c _pointerMap" holds for the duration of the
600 // tracker's life.
601 //
602 // Note on the dangling-input case: callers of this constructor
603 // pass a raw pointer they own. If the caller's object is
604 // destroyed before @c linkTo runs, that's a caller-side
605 // lifetime bug, not something this class can plug. The
606 // mutex-protected variant @ref linkFromSource exists for the
607 // copy paths where the source is itself an ObjectBasePtr and
608 // can be invalidated concurrently — that one re-checks the
609 // atomic under the lock.
610 Mutex::Locker lock(ObjectBase::objectBasePtrMutex());
611 p.store(obj, MemoryOrder::Relaxed);
612 if (obj != nullptr) {
613 obj->_pointerMap[&p] = &p;
614 }
615 return;
616}
617
618template <typename T>
619inline void ObjectBasePtr<T>::linkFromSource(const TrackerPtr &source) {
620 // Lock first, then load the source: if a concurrent
621 // runCleanup is mid-flight on the tracked object it has
622 // already nulled the source's atomic before releasing the
623 // mutex, so once we hold the lock the source either still
624 // points at a live object (which can't be destroyed until we
625 // release) or has been nulled (we observe nullptr and link to
626 // nothing). Performing the source load *outside* the lock —
627 // as the previous mem-initializer pattern did — leaves a
628 // window where @c p is set to a doomed pointer, runCleanup
629 // misses us because we're not in @c _pointerMap yet, and the
630 // subsequent registration writes through a freed @c obj.
631 Mutex::Locker lock(ObjectBase::objectBasePtrMutex());
632 ObjectBase *obj = source.load(MemoryOrder::Relaxed);
633 p.store(obj, MemoryOrder::Relaxed);
634 if (obj != nullptr) {
635 obj->_pointerMap[&p] = &p;
636 }
637 return;
638}
639
640template <typename T> inline void ObjectBasePtr<T>::unlink() {
641 // Same ordering as the link path: acquire the global mutex
642 // first so we can't race ObjectBase::runCleanup, which holds
643 // the same lock while it walks _pointerMap and frees the
644 // trackers.
645 Mutex::Locker lock(ObjectBase::objectBasePtrMutex());
646 ObjectBase *obj = p.exchange(nullptr, MemoryOrder::AcqRel);
647 if (obj != nullptr) {
648 auto it = obj->_pointerMap.find(&p);
649 if (it != obj->_pointerMap.end()) {
650 obj->_pointerMap.remove(it);
651 }
652 }
653}
654
655PROMEKI_NAMESPACE_END
656
657// The template bodies for ObjectBase::connect and
658// Signal<Args...>::connect(Function, ObjectBase *) previously lived at
659// the bottom of this file but pulled @c variant.h and @c eventloop.h
660// into every TU that includes @c objectbase.h — ~270 of them. They
661// now live in @c objectbase.tpp; TUs that actually call those methods
662// must include @c promeki/objectbase.tpp.
663
664#endif // PROMEKI_ENABLE_CORE