Skip to content

Commit e5e6128

Browse files
committed
Add support for macOS timers
Done by Abhishek in PR #10 but had gmail address. Task-Id: SRNTY-40
1 parent df0bd31 commit e5e6128

File tree

6 files changed

+158
-11
lines changed

6 files changed

+158
-11
lines changed

src/KDFoundation/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
8282
platform/macos/macos_platform_event_loop.h
8383
platform/macos/macos_platform_integration.mm
8484
platform/macos/macos_platform_integration.h
85+
platform/macos/macos_platform_timer.mm
86+
platform/macos/macos_platform_timer.h
8587
)
8688
endif()
8789

src/KDFoundation/platform/macos/macos_platform_event_loop.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313

1414
#include <KDFoundation/platform/abstract_platform_event_loop.h>
1515
#include <KDFoundation/kdfoundation_global.h>
16+
#include <unordered_map>
1617

1718
namespace KDFoundation {
1819

20+
class MacOSPlatformTimer;
21+
1922
class KDFOUNDATION_API MacOSPlatformEventLoop : public AbstractPlatformEventLoop
2023
{
2124
public:
@@ -37,6 +40,9 @@ class KDFOUNDATION_API MacOSPlatformEventLoop : public AbstractPlatformEventLoop
3740
private:
3841
void waitForEventsImpl(int timeout) override;
3942
std::unique_ptr<AbstractPlatformTimer> createPlatformTimerImpl(Timer *timer) override;
43+
44+
std::unordered_map<void *, MacOSPlatformTimer *> timerMap;
45+
friend class MacOSPlatformTimer;
4046
};
4147

4248
} // namespace KDFoundation

src/KDFoundation/platform/macos/macos_platform_event_loop.mm

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@
1010
*/
1111

1212
#include "macos_platform_event_loop.h"
13-
13+
#include "macos_platform_timer.h"
14+
#import <Foundation/Foundation.h>
1415
#import <AppKit/AppKit.h>
15-
1616
#include <limits>
17-
18-
using namespace KDFoundation;
17+
#include <memory>
1918

2019
constexpr auto KDFoundationCocoaEventSubTypeWakeup = std::numeric_limits<short>::max();
2120

21+
namespace KDFoundation {
22+
2223
MacOSPlatformEventLoop::MacOSPlatformEventLoop()
2324
{
2425
@autoreleasepool {
@@ -78,9 +79,9 @@
7879
// TODO
7980
return false;
8081
}
81-
8282
std::unique_ptr<AbstractPlatformTimer> MacOSPlatformEventLoop::createPlatformTimerImpl(Timer *timer)
8383
{
84-
// TODO
85-
return {};
84+
return std::make_unique<MacOSPlatformTimer>(timer);
8685
}
86+
87+
} // namespace KDFoundation
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
This file is part of KDUtils.
3+
4+
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5+
Author: Paul Lemire <paul.lemire@kdab.com>
6+
7+
SPDX-License-Identifier: MIT
8+
9+
Contact KDAB at <info@kdab.com> for commercial licensing options.
10+
*/
11+
12+
#pragma once
13+
14+
#include <CoreFoundation/CoreFoundation.h>
15+
16+
#include <chrono>
17+
18+
#include <KDFoundation/platform/abstract_platform_timer.h>
19+
#include <KDFoundation/file_descriptor_notifier.h>
20+
21+
namespace KDFoundation {
22+
23+
class Timer;
24+
25+
class KDFOUNDATION_API MacOSPlatformTimer : public AbstractPlatformTimer
26+
{
27+
public:
28+
explicit MacOSPlatformTimer(Timer *timer);
29+
~MacOSPlatformTimer() override;
30+
31+
private:
32+
void arm(std::chrono::microseconds us);
33+
void disarm();
34+
static void timerFired(CFRunLoopTimerRef timer, void *info);
35+
Timer *m_handler;
36+
CFRunLoopTimerRef cfTimer;
37+
};
38+
39+
} // namespace KDFoundation
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
This file is part of KDUtils.
3+
4+
SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5+
Author: Paul Lemire <paul.lemire@kdab.com>
6+
7+
SPDX-License-Identifier: MIT
8+
9+
Contact KDAB at <info@kdab.com> for commercial licensing options.
10+
*/
11+
12+
#include "macos_platform_timer.h"
13+
#include "macos_platform_event_loop.h"
14+
#include <Foundation/Foundation.h>
15+
#include <algorithm>
16+
#include <cassert>
17+
#include <chrono>
18+
#include <cstddef>
19+
#include <type_traits>
20+
#include <unistd.h>
21+
#include "KDFoundation/core_application.h"
22+
#include "KDFoundation/timer.h"
23+
#include "KDFoundation/platform/macos/macos_platform_event_loop.h"
24+
25+
using namespace KDFoundation;
26+
27+
inline MacOSPlatformEventLoop *eventLoop()
28+
{
29+
return static_cast<MacOSPlatformEventLoop *>(CoreApplication::instance()->eventLoop());
30+
}
31+
32+
MacOSPlatformTimer::MacOSPlatformTimer(Timer *timer)
33+
: m_handler{ timer }, cfTimer{ nullptr }
34+
{
35+
timer->running.valueChanged().connect([this, timer](bool running) {
36+
if (running) {
37+
arm(timer->interval.get());
38+
} else {
39+
disarm();
40+
}
41+
});
42+
timer->interval.valueChanged().connect([this, timer]() {
43+
if (timer->running.get()) {
44+
arm(timer->interval.get());
45+
}
46+
});
47+
}
48+
49+
MacOSPlatformTimer::~MacOSPlatformTimer()
50+
{
51+
disarm();
52+
}
53+
54+
void MacOSPlatformTimer::timerFired(CFRunLoopTimerRef timer, void *info)
55+
{
56+
MacOSPlatformEventLoop *ev = eventLoop();
57+
void *key = timer;
58+
if (auto it = ev->timerMap.find(key); it != ev->timerMap.end()) {
59+
it->second->m_handler->timeout.emit();
60+
}
61+
}
62+
63+
void MacOSPlatformTimer::arm(std::chrono::microseconds us)
64+
{
65+
if (cfTimer) {
66+
disarm();
67+
}
68+
69+
CFTimeInterval interval = std::chrono::duration_cast<std::chrono::duration<double>>(us).count();
70+
CFRunLoopTimerContext timerContext = { 0, NULL, NULL, NULL, NULL };
71+
CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent() + interval;
72+
// Create the timer
73+
cfTimer = CFRunLoopTimerCreate(
74+
kCFAllocatorDefault, // Allocator
75+
fireDate, // Fire time
76+
interval, // Interval
77+
0, // Flags
78+
0, // Order
79+
timerFired, // Callback function
80+
&timerContext // Timer context
81+
);
82+
CFRunLoopAddTimer(CFRunLoopGetCurrent(), cfTimer, kCFRunLoopCommonModes);
83+
84+
if (cfTimer) {
85+
void *key = reinterpret_cast<void *>(cfTimer);
86+
eventLoop()->timerMap[key] = this;
87+
}
88+
}
89+
90+
void MacOSPlatformTimer::disarm()
91+
{
92+
if (cfTimer) {
93+
void *key = reinterpret_cast<void *>(cfTimer);
94+
eventLoop()->timerMap.erase(key);
95+
CFRelease(cfTimer);
96+
cfTimer = nullptr;
97+
}
98+
}

tests/auto/foundation/core_application/tst_core_application.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,7 @@ TEST_CASE("Event handling")
187187
}
188188
}
189189

190-
#ifndef KD_PLATFORM_MACOS
191-
TEST_CASE("Timer handling")
190+
TEST_CASE("Timer handling" * doctest::timeout(120))
192191
{
193192
SUBCASE("timer fires correctly")
194193
{
@@ -204,15 +203,18 @@ TEST_CASE("Timer handling")
204203
auto time = startTime;
205204
std::ignore = timer.timeout.connect([&]() {
206205
const auto endTime = std::chrono::steady_clock::now();
206+
const auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - time).count();
207+
SPDLOG_INFO("elapsedTime = {}", elapsedTime);
207208
REQUIRE(endTime - time > 50ms);
208-
REQUIRE(endTime - time < 150ms);
209+
REQUIRE(endTime - time < 250ms);
209210
time = endTime;
210211
timeout++;
211212
});
212213

213214
while (std::chrono::steady_clock::now() - startTime < 500ms) {
214215
app.processEvents(500);
215216
}
217+
SPDLOG_INFO("timeout = {}", timeout);
216218
REQUIRE(timeout > 3);
217219
REQUIRE(timeout < 8);
218220

@@ -260,7 +262,6 @@ TEST_CASE("Timer handling")
260262
REQUIRE(fired == true);
261263
}
262264
}
263-
#endif
264265

265266
TEST_CASE("Main event loop")
266267
{

0 commit comments

Comments
 (0)