Skip to content

Commit af23edc

Browse files
authored
macOS Animation Support (#497)
1 parent 2d7844b commit af23edc

File tree

4 files changed

+566
-6
lines changed

4 files changed

+566
-6
lines changed
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
//
2+
// DisplayLink.c
3+
// COpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
#include "DisplayLink.h"
9+
10+
#if OPENSWIFTUI_TARGET_OS_OSX
11+
12+
#include <os/lock.h>
13+
#include <QuartzCore/QuartzCore.h>
14+
#include <stdatomic.h>
15+
16+
extern void CVDisplayLinkSetPaused(CVDisplayLinkRef displayLink, bool paused);
17+
static void remove_link(DisplayLinkRef displayLink);
18+
19+
typedef struct DisplayLinkManager {
20+
struct DisplayLinkManager *nextManager;
21+
22+
CGDirectDisplayID displayID;
23+
24+
uint32_t _padding1;
25+
26+
CVDisplayLinkRef displayLink;
27+
28+
struct DisplayLink *listHead;
29+
30+
uint32_t totalDisplayLinks;
31+
32+
uint32_t pausedDisplayLinks;
33+
34+
uint32_t state;
35+
36+
uint32_t _padding2;
37+
38+
_Atomic uint64_t timestamp;
39+
} DisplayLinkManager;
40+
41+
struct DisplayLink {
42+
uint32_t unknown_field_1;
43+
44+
uint32_t _padding1;
45+
46+
struct DisplayLinkManager *manager;
47+
48+
struct DisplayLink *nextDisplayLink;
49+
50+
DisplayLinkCallback callback;
51+
52+
double nextTime;
53+
54+
uint32_t state;
55+
56+
struct {
57+
uint8_t isScheduled;
58+
uint8_t isDestroyed;
59+
uint16_t _padding2;
60+
} flags;
61+
};
62+
63+
static os_unfair_lock link_lock = OS_UNFAIR_LOCK_INIT;
64+
static dispatch_queue_t link_queue = NULL;
65+
static DisplayLinkManager *link_list = NULL;
66+
67+
static void dispatch_items(void *context) {
68+
DisplayLinkManager *manager = (DisplayLinkManager *)context;
69+
70+
double callback_time = *(double*)&manager->timestamp;
71+
72+
os_unfair_lock_lock(&link_lock);
73+
74+
DisplayLinkRef *links_to_process = NULL;
75+
76+
size_t num_links = 0;
77+
78+
struct DisplayLink *current = manager->listHead;
79+
size_t count = 0;
80+
while (current) {
81+
count++;
82+
current = current->nextDisplayLink;
83+
}
84+
85+
if (count > 0) {
86+
links_to_process = (DisplayLinkRef *)alloca(count * sizeof(DisplayLinkRef));
87+
88+
current = manager->listHead;
89+
while (current) {
90+
os_unfair_lock_lock((os_unfair_lock_t)current);
91+
92+
if (!current->flags.isDestroyed) {
93+
if (current->nextTime == INFINITY) {
94+
if (current->state != 0) {
95+
current->state--;
96+
if (current->state == 0) {
97+
manager->pausedDisplayLinks++;
98+
manager->state = 5;
99+
}
100+
}
101+
} else {
102+
links_to_process[num_links++] = current;
103+
current->nextTime = INFINITY;
104+
current->flags.isScheduled = 1;
105+
}
106+
}
107+
108+
os_unfair_lock_unlock((os_unfair_lock_t)current);
109+
current = current->nextDisplayLink;
110+
}
111+
}
112+
113+
os_unfair_lock_unlock(&link_lock);
114+
115+
for (size_t i = 0; i < num_links; i++) {
116+
DisplayLinkRef link = links_to_process[i];
117+
if (link->callback) {
118+
link->callback(link, callback_time);
119+
}
120+
}
121+
122+
os_unfair_lock_lock(&link_lock);
123+
124+
for (size_t i = 0; i < num_links; i++) {
125+
DisplayLinkRef link = links_to_process[i];
126+
127+
os_unfair_lock_lock((os_unfair_lock_t)link);
128+
link->flags.isScheduled = 0;
129+
bool should_free = link->flags.isDestroyed;
130+
os_unfair_lock_unlock((os_unfair_lock_t)link);
131+
132+
if (should_free) {
133+
remove_link(link);
134+
Block_release(link->callback);
135+
free(link);
136+
}
137+
}
138+
139+
bool should_pause = false;
140+
bool should_destroy = false;
141+
142+
if (manager->pausedDisplayLinks == manager->totalDisplayLinks) {
143+
if (manager->state != 0) {
144+
manager->state--;
145+
if (manager->state == 0 && manager->pausedDisplayLinks != 0) {
146+
should_pause = true;
147+
}
148+
}
149+
}
150+
151+
if (manager->pausedDisplayLinks == 0 &&
152+
manager->totalDisplayLinks == 0) {
153+
should_destroy = true;
154+
155+
DisplayLinkManager **current_mgr = &link_list;
156+
while (*current_mgr) {
157+
if (*current_mgr == manager) {
158+
*current_mgr = manager->nextManager;
159+
break;
160+
}
161+
current_mgr = &(*current_mgr)->nextManager;
162+
}
163+
}
164+
165+
os_unfair_lock_unlock(&link_lock);
166+
167+
if (should_pause) {
168+
CVDisplayLinkSetPaused(manager->displayLink, true);
169+
}
170+
171+
atomic_store(&manager->timestamp, 0);
172+
173+
if (should_destroy) {
174+
CVDisplayLinkRelease(manager->displayLink);
175+
free(manager);
176+
}
177+
178+
}
179+
180+
static CVReturn link_callback(
181+
CVDisplayLinkRef displayLink,
182+
const CVTimeStamp* inNow,
183+
const CVTimeStamp* inOutputTime,
184+
CVOptionFlags flagsIn,
185+
CVOptionFlags* flagsOut,
186+
void* displayLinkContext
187+
) {
188+
DisplayLinkManager* manager = (DisplayLinkManager*)displayLinkContext;
189+
190+
double current_time = CACurrentMediaTime();
191+
uint64_t time_bits = *(uint64_t*)&current_time;
192+
193+
uint64_t expected = 0;
194+
if (atomic_compare_exchange_strong(&manager->timestamp, &expected, time_bits)) {
195+
dispatch_async_f(link_queue, manager, dispatch_items);
196+
}
197+
198+
return kCVReturnSuccess;
199+
}
200+
201+
static void remove_link(DisplayLinkRef displayLink) {
202+
DisplayLinkManager *manager = displayLink->manager;
203+
204+
struct DisplayLink **current = &manager->listHead;
205+
while (*current) {
206+
if (*current == displayLink) {
207+
*current = displayLink->nextDisplayLink;
208+
209+
manager->totalDisplayLinks--;
210+
break;
211+
}
212+
current = &(*current)->nextDisplayLink;
213+
}
214+
215+
if (displayLink->state == 0) {
216+
manager->pausedDisplayLinks--;
217+
}
218+
}
219+
220+
DisplayLinkRef DisplayLinkCreate(CGDirectDisplayID displayID, DisplayLinkCallback callback) {
221+
DisplayLinkManager *manager = NULL;
222+
DisplayLinkRef displayLink = NULL;
223+
224+
os_unfair_lock_lock(&link_lock);
225+
226+
if (!link_queue) {
227+
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(
228+
DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0
229+
);
230+
link_queue = dispatch_queue_create("com.apple.SwiftUI.DisplayLink", attr);
231+
}
232+
233+
manager = link_list;
234+
while (manager) {
235+
if (manager->displayID == displayID) {
236+
break;
237+
}
238+
manager = manager->nextManager;
239+
}
240+
241+
if (!manager) {
242+
manager = (DisplayLinkManager *)calloc(1, sizeof(DisplayLinkManager));
243+
if (!manager) goto cleanup_and_fail;
244+
245+
manager->displayID = displayID;
246+
manager->state = 5;
247+
248+
CVReturn status = CVDisplayLinkCreateWithCGDisplay(displayID, &manager->displayLink);
249+
250+
if (status != kCVReturnSuccess) {
251+
free(manager);
252+
goto cleanup_and_fail;
253+
}
254+
255+
manager->nextManager = link_list;
256+
link_list = manager;
257+
258+
CVDisplayLinkSetOutputCallback(manager->displayLink, link_callback, manager);
259+
CVDisplayLinkStart(manager->displayLink);
260+
}
261+
262+
displayLink = (DisplayLinkRef)calloc(1, sizeof(struct DisplayLink));
263+
264+
if (!displayLink) goto cleanup_and_fail;
265+
266+
displayLink->manager = manager;
267+
displayLink->callback = Block_copy(callback);
268+
displayLink->nextTime = INFINITY;
269+
displayLink->state = 0;
270+
271+
displayLink->nextDisplayLink = manager->listHead;
272+
manager->listHead = displayLink;
273+
274+
manager->pausedDisplayLinks++;
275+
manager->totalDisplayLinks++;
276+
277+
os_unfair_lock_unlock(&link_lock);
278+
return displayLink;
279+
cleanup_and_fail:
280+
os_unfair_lock_unlock(&link_lock);
281+
return NULL;
282+
}
283+
284+
void DisplayLinkDestroy(DisplayLinkRef displayLink) {
285+
if (!displayLink) return;
286+
287+
os_unfair_lock_lock((os_unfair_lock_t)displayLink);
288+
289+
bool wasScheduled = displayLink->flags.isScheduled;
290+
displayLink->flags.isDestroyed = true;
291+
292+
os_unfair_lock_unlock((os_unfair_lock_t)displayLink);
293+
294+
if (wasScheduled) return;
295+
296+
os_unfair_lock_lock(&link_lock);
297+
298+
DisplayLinkManager *manager = displayLink->manager;
299+
300+
remove_link(displayLink);
301+
302+
bool shouldDestroyManager = (manager->totalDisplayLinks == 0 &&
303+
manager->state == 0);
304+
305+
if (shouldDestroyManager) {
306+
DisplayLinkManager **current = &link_list;
307+
while (*current) {
308+
if (*current == manager) {
309+
*current = manager->nextManager;
310+
break;
311+
}
312+
current = &(*current)->nextManager;
313+
}
314+
}
315+
316+
os_unfair_lock_unlock(&link_lock);
317+
318+
if (shouldDestroyManager) {
319+
CVDisplayLinkRelease(manager->displayLink);
320+
free(manager);
321+
}
322+
323+
Block_release(displayLink->callback);
324+
free(displayLink);
325+
}
326+
327+
void DisplayLinkSetNextTime(DisplayLinkRef displayLink, double nextTime) {
328+
if (!displayLink) return;
329+
330+
os_unfair_lock_lock(&link_lock);
331+
332+
if (displayLink->nextTime != nextTime) {
333+
if (nextTime != INFINITY && displayLink->state == 0) {
334+
335+
DisplayLinkManager *manager = displayLink->manager;
336+
337+
manager->pausedDisplayLinks--;
338+
339+
if (manager->state == 0) {
340+
CVDisplayLinkSetPaused(manager->displayLink, false);
341+
}
342+
343+
manager->state = 5;
344+
displayLink->state = 5;
345+
}
346+
347+
displayLink->nextTime = nextTime;
348+
}
349+
350+
os_unfair_lock_unlock(&link_lock);
351+
}
352+
353+
double DisplayLinkGetNextTime(DisplayLinkRef displayLink) {
354+
if (!displayLink) return NAN;
355+
356+
os_unfair_lock_lock(&link_lock);
357+
double time = displayLink->nextTime;
358+
os_unfair_lock_unlock(&link_lock);
359+
360+
return time;
361+
}
362+
363+
#endif
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// DisplayLink.h
3+
// COpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
8+
#ifndef DisplayLink_h
9+
#define DisplayLink_h
10+
11+
#include "OpenSwiftUIBase.h"
12+
13+
#if OPENSWIFTUI_TARGET_OS_OSX
14+
15+
#include <CoreFoundation/CoreFoundation.h>
16+
#include <CoreVideo/CoreVideo.h>
17+
18+
typedef struct DisplayLink * OPENSWIFTUI_SWIFT_STRUCT DisplayLinkRef OPENSWIFTUI_SWIFT_NAME(DisplayLink);
19+
20+
typedef void(^ DisplayLinkCallback)(DisplayLinkRef __nonnull, double);
21+
22+
extern DisplayLinkRef __nullable DisplayLinkCreate(CGDirectDisplayID displayID, DisplayLinkCallback __nonnull callback);
23+
24+
extern void DisplayLinkDestroy(DisplayLinkRef __nonnull displayLink);
25+
26+
extern void DisplayLinkSetNextTime(DisplayLinkRef __nonnull displayLink, double nextTime);
27+
28+
extern double DisplayLinkGetNextTime(DisplayLinkRef __nonnull displayLink);
29+
30+
#endif
31+
32+
#endif /* DisplayLink_h */

0 commit comments

Comments
 (0)