forked from RetroAchievements/RAIntegration
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAsyncObject.hh
More file actions
137 lines (115 loc) · 4.17 KB
/
Copy pathAsyncObject.hh
File metadata and controls
137 lines (115 loc) · 4.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#ifndef RA_DATA_ASYNCOBJECT_HH
#define RA_DATA_ASYNCOBJECT_HH
#pragma once
#include "util\GSL.hh"
namespace ra {
namespace data {
/// <summary>
/// Handle owned by an <see cref="AsyncObject"/> for thread safety and destruction tracking.
/// </summary>
class AsyncHandle
{
public:
/// <summary>
/// Determines whether the <see cref="AsyncObject" /> that owns this handle is waiting to be destructed.
/// </summary>
/// <remarks>Allows for early exit in long running code keeping the object alive.</remarks>
bool IsDestroying() const noexcept { return m_bDestroying; }
/// <summary>
/// Determines whether the <see cref="AsyncObject" /> that owns this handle has been destructed.
/// </summary>
bool IsDestroyed() const noexcept { return m_bDestroyed; }
private:
std::mutex m_mtxDestroy;
bool m_bDestroyed = false;
bool m_bDestroying = false;
// allow AsyncKeepAlive access to m_mtxDestroy
friend class AsyncKeepAlive;
// allow AsyncObject access to SetDestroyed()
friend class AsyncObject;
GSL_SUPPRESS_F6 void SetDestroyed() noexcept
{
// immediately mark as destroying
m_bDestroying = true;
// then wait for anything holding the lock
std::lock_guard<std::mutex> guard(m_mtxDestroy);
// then mark as destroyed
m_bDestroyed = true;
}
};
/// <summary>
/// Helper object to ensure an <see cref="AsyncObject"/> is not destroyed while it is being used.
/// </summary>
class AsyncKeepAlive
{
public:
AsyncKeepAlive(AsyncHandle& pAsyncHandle) : m_pLock(pAsyncHandle.m_mtxDestroy) {}
private:
std::lock_guard<std::mutex> m_pLock;
};
/// <summary>
/// Helper class for validating existance of "this" pointer in an asynchronous callback
/// </summary>
/// <remarks>
/// Inheriting from <see cref="AsyncObject"> does not inherently prevent destruction of the
/// "this" pointer unless used in conjunction with an <see cref="AsyncKeepAlive"> object.
/// While an AsyncKeepAlive object has a reference to the AsyncObject's <see cref="AsyncHandle">,
/// the AsyncObject's destructor will wait until the code guarded by the AsyncKeepAlive completes.
/// </remarks>
/// <example>
/// run_async([this, asyncHandle = CreateAsyncHandle()]{
/// AsyncKeepAlive keepAlive(*asyncHandle); // prevents destroying the object while we're in the callback
/// if (asyncHandle->IsDestroyed())
/// return;
///
/// do_stuff(this);
/// });
/// </example>
class AsyncObject
{
public:
AsyncObject() noexcept = default;
virtual ~AsyncObject() noexcept
{
// subclass destructor should have called BeginDestruction, which ensures this is set to nullptr.
assert(m_pAsyncHandle == nullptr);
}
AsyncObject(const AsyncObject&) noexcept = default;
AsyncObject& operator=(const AsyncObject&) noexcept = default;
AsyncObject(AsyncObject&&) noexcept = default;
AsyncObject& operator=(AsyncObject&&) noexcept = default;
/// <summary>
/// Creates a handle to be passed to an async function so the function can validate that
/// the <see cref="AsyncObject"/> is still valid when it runs.
/// </summary>
/// <returns></returns>
std::shared_ptr<AsyncHandle> CreateAsyncHandle()
{
if (!m_pAsyncHandle)
m_pAsyncHandle = std::make_shared<AsyncHandle>();
return m_pAsyncHandle;
}
protected:
/// <summary>
/// Updates the <see cref="AsyncHandle" /> to indicate that the destruction process has begun
/// and then waits while any <see cref="AsyncKeepAlive" /> objects have hold of the AsyncHandle.
/// </summary>
/// <remarks>
/// Should be called by the destructor of a class that inherits from <see cref="AsyncObject" />
/// so any default destruction of member fields is delayed while another thread may be running.
/// </remarks>
void BeginDestruction() noexcept
{
if (m_pAsyncHandle)
{
m_pAsyncHandle->SetDestroyed();
m_pAsyncHandle.reset();
}
assert(m_pAsyncHandle == nullptr);
}
private:
std::shared_ptr<AsyncHandle> m_pAsyncHandle;
};
} // namespace data
} // namespace ra
#endif // !RA_DATA_ASYNCOBJECT_HH