|
| 1 | +/* |
| 2 | + * This file is part of mpv. |
| 3 | + * |
| 4 | + * mpv is free software; you can redistribute it and/or |
| 5 | + * modify it under the terms of the GNU Lesser General Public |
| 6 | + * License as published by the Free Software Foundation; either |
| 7 | + * version 2.1 of the License, or (at your option) any later version. |
| 8 | + * |
| 9 | + * mpv is distributed in the hope that it will be useful, |
| 10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | + * GNU Lesser General Public License for more details. |
| 13 | + * |
| 14 | + * You should have received a copy of the GNU Lesser General Public |
| 15 | + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
| 16 | + */ |
| 17 | + |
| 18 | +import Cocoa |
| 19 | + |
| 20 | +struct Timing { |
| 21 | + let time: UInt64 |
| 22 | + let closure: () -> () |
| 23 | +} |
| 24 | + |
| 25 | +class PreciseTimer { |
| 26 | + unowned var common: Common |
| 27 | + var mpv: MPVHelper? { get { return common.mpv } } |
| 28 | + |
| 29 | + let nanoPerSecond: Double = 1e+9 |
| 30 | + let machToNano: Double = { |
| 31 | + var timebase: mach_timebase_info = mach_timebase_info() |
| 32 | + mach_timebase_info(&timebase) |
| 33 | + return Double(timebase.numer) / Double(timebase.denom) |
| 34 | + }() |
| 35 | + |
| 36 | + let condition = NSCondition() |
| 37 | + var events: [Timing] = [] |
| 38 | + var isRunning: Bool = true |
| 39 | + var isHighPrecision: Bool = false |
| 40 | + |
| 41 | + var thread: pthread_t! |
| 42 | + var threadPort: thread_port_t = thread_port_t() |
| 43 | + let policyFlavor = thread_policy_flavor_t(THREAD_TIME_CONSTRAINT_POLICY) |
| 44 | + let policyCount = MemoryLayout<thread_time_constraint_policy>.size / |
| 45 | + MemoryLayout<integer_t>.size |
| 46 | + var typeNumber: mach_msg_type_number_t { |
| 47 | + return mach_msg_type_number_t(policyCount) |
| 48 | + } |
| 49 | + var threadAttr: pthread_attr_t = { |
| 50 | + var attr = pthread_attr_t() |
| 51 | + var param = sched_param() |
| 52 | + pthread_attr_init(&attr) |
| 53 | + param.sched_priority = sched_get_priority_max(SCHED_FIFO) |
| 54 | + pthread_attr_setschedparam(&attr, ¶m) |
| 55 | + pthread_attr_setschedpolicy(&attr, SCHED_FIFO) |
| 56 | + return attr |
| 57 | + }() |
| 58 | + |
| 59 | + init?(common com: Common) { |
| 60 | + common = com |
| 61 | + |
| 62 | + pthread_create(&thread, &threadAttr, entryC, MPVHelper.bridge(obj: self)) |
| 63 | + if thread == nil { |
| 64 | + common.log.sendWarning("Couldn't create pthread for high precision timer") |
| 65 | + return nil |
| 66 | + } |
| 67 | + |
| 68 | + threadPort = pthread_mach_thread_np(thread) |
| 69 | + } |
| 70 | + |
| 71 | + func updatePolicy(periodSeconds: Double = 1 / 60.0) { |
| 72 | + let period = periodSeconds * nanoPerSecond / machToNano |
| 73 | + var policy = thread_time_constraint_policy( |
| 74 | + period: UInt32(period), |
| 75 | + computation: UInt32(0.75 * period), |
| 76 | + constraint: UInt32(0.85 * period), |
| 77 | + preemptible: 1 |
| 78 | + ) |
| 79 | + |
| 80 | + let success = withUnsafeMutablePointer(to: &policy) { |
| 81 | + $0.withMemoryRebound(to: integer_t.self, capacity: policyCount) { |
| 82 | + thread_policy_set(threadPort, policyFlavor, $0, typeNumber) |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + isHighPrecision = success == KERN_SUCCESS |
| 87 | + if !isHighPrecision { |
| 88 | + common.log.sendWarning("Couldn't create a high precision timer") |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + func terminate() { |
| 93 | + condition.lock() |
| 94 | + isRunning = false |
| 95 | + condition.signal() |
| 96 | + condition.unlock() |
| 97 | + pthread_kill(thread, SIGALRM) |
| 98 | + pthread_join(thread, nil) |
| 99 | + } |
| 100 | + |
| 101 | + func scheduleAt(time: UInt64, closure: @escaping () -> ()) { |
| 102 | + condition.lock() |
| 103 | + let firstEventTime = events.first?.time ?? 0 |
| 104 | + let lastEventTime = events.last?.time ?? 0 |
| 105 | + events.append(Timing(time: time, closure: closure)) |
| 106 | + |
| 107 | + if lastEventTime > time { |
| 108 | + events.sort{ $0.time < $1.time } |
| 109 | + } |
| 110 | + |
| 111 | + condition.signal() |
| 112 | + condition.unlock() |
| 113 | + |
| 114 | + if firstEventTime > time { |
| 115 | + pthread_kill(thread, SIGALRM) |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + let threadSignal: @convention(c) (Int32) -> () = { (sig: Int32) in } |
| 120 | + |
| 121 | + let entryC: @convention(c) (UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? = { (ptr: UnsafeMutableRawPointer) in |
| 122 | + let ptimer: PreciseTimer = MPVHelper.bridge(ptr: ptr) |
| 123 | + ptimer.entry() |
| 124 | + return nil |
| 125 | + } |
| 126 | + |
| 127 | + func entry() { |
| 128 | + signal(SIGALRM, threadSignal) |
| 129 | + |
| 130 | + while isRunning { |
| 131 | + condition.lock() |
| 132 | + while events.count == 0 && isRunning { |
| 133 | + condition.wait() |
| 134 | + } |
| 135 | + |
| 136 | + if !isRunning { break } |
| 137 | + |
| 138 | + guard let event = events.first else { |
| 139 | + continue |
| 140 | + } |
| 141 | + condition.unlock() |
| 142 | + |
| 143 | + mach_wait_until(event.time) |
| 144 | + |
| 145 | + condition.lock() |
| 146 | + if events.first?.time == event.time && isRunning { |
| 147 | + event.closure() |
| 148 | + events.removeFirst() |
| 149 | + } |
| 150 | + condition.unlock() |
| 151 | + } |
| 152 | + } |
| 153 | +} |
0 commit comments