@momo-kits/native-kits 0.162.1-debug → 0.162.1-gif.1-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compose/build.gradle.kts +5 -1
- package/compose/build.gradle.kts.backup +63 -59
- package/compose/src/androidMain/kotlin/vn/momo/kits/fpsmonitor/FPSGraphView.kt +250 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/fpsmonitor/FPSMonitor.kt +249 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/fpsmonitor/FPSOverlayView.kt +99 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +33 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +28 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +46 -10
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +4 -2
- package/gradle/libs.versions.toml +5 -0
- package/gradle.properties +1 -1
- package/ios/FPSMonitor/FPSMonitor.swift +562 -0
- package/package.json +1 -1
- package/publish.sh +2 -2
- package/example/ios/Example.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -46
- package/local.properties +0 -8
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import QuartzCore
|
|
3
|
+
import Darwin
|
|
4
|
+
|
|
5
|
+
@objc(FPSMonitor)
|
|
6
|
+
public final class FPSMonitor: NSObject {
|
|
7
|
+
public static let shared = FPSMonitor()
|
|
8
|
+
|
|
9
|
+
public var onFPSLow: ((CGFloat) -> Void)?
|
|
10
|
+
public var onRamOverload: ((CGFloat) -> Void)?
|
|
11
|
+
|
|
12
|
+
@objc public private(set) var latestFPS: CGFloat = 0
|
|
13
|
+
@objc public private(set) var latestCPUPercent: CGFloat = 0
|
|
14
|
+
@objc public private(set) var latestGPUms: CGFloat = 0
|
|
15
|
+
@objc public private(set) var latestMemoryMB: CGFloat = 0
|
|
16
|
+
|
|
17
|
+
@objc public var ramThreshold: CGFloat
|
|
18
|
+
@objc public var fpsThreshold: CGFloat = 30
|
|
19
|
+
@objc public var updateInterval: TimeInterval = 0.2
|
|
20
|
+
|
|
21
|
+
private var overlayWindow: FPSOverlayWindow?
|
|
22
|
+
private var graphView: FPSGraphView?
|
|
23
|
+
private var displayLink: CADisplayLink?
|
|
24
|
+
private var lastTimestamp: CFTimeInterval = 0
|
|
25
|
+
private var frameCount = 0
|
|
26
|
+
private var lastFPSAlertTime: CFTimeInterval = 0
|
|
27
|
+
private var uiVisible = false
|
|
28
|
+
|
|
29
|
+
private override init() {
|
|
30
|
+
#if targetEnvironment(simulator)
|
|
31
|
+
ramThreshold = 1500
|
|
32
|
+
#else
|
|
33
|
+
ramThreshold = 500
|
|
34
|
+
#endif
|
|
35
|
+
super.init()
|
|
36
|
+
|
|
37
|
+
NotificationCenter.default.addObserver(
|
|
38
|
+
self,
|
|
39
|
+
selector: #selector(appDidBecomeActive),
|
|
40
|
+
name: UIApplication.didBecomeActiveNotification,
|
|
41
|
+
object: nil
|
|
42
|
+
)
|
|
43
|
+
NotificationCenter.default.addObserver(
|
|
44
|
+
self,
|
|
45
|
+
selector: #selector(appWillResignActive),
|
|
46
|
+
name: UIApplication.willResignActiveNotification,
|
|
47
|
+
object: nil
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@objc(shared)
|
|
52
|
+
public class func sharedObjC() -> FPSMonitor {
|
|
53
|
+
shared
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
deinit {
|
|
57
|
+
NotificationCenter.default.removeObserver(self)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@objc public func startOverlay() {
|
|
61
|
+
runOnMain {
|
|
62
|
+
guard self.displayLink == nil else { return }
|
|
63
|
+
self.lastTimestamp = 0
|
|
64
|
+
self.frameCount = 0
|
|
65
|
+
self.uiVisible = false
|
|
66
|
+
|
|
67
|
+
let link = CADisplayLink(target: self, selector: #selector(self.tick(_:)))
|
|
68
|
+
link.add(to: .main, forMode: .common)
|
|
69
|
+
link.isPaused = false
|
|
70
|
+
self.displayLink = link
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@objc public func stop() {
|
|
75
|
+
runOnMain {
|
|
76
|
+
self.displayLink?.invalidate()
|
|
77
|
+
self.displayLink = nil
|
|
78
|
+
|
|
79
|
+
self.graphView?.removeFromSuperview()
|
|
80
|
+
self.graphView?.isHidden = true
|
|
81
|
+
self.graphView = nil
|
|
82
|
+
|
|
83
|
+
self.overlayWindow?.isHidden = true
|
|
84
|
+
self.overlayWindow?.rootViewController = nil
|
|
85
|
+
self.overlayWindow = nil
|
|
86
|
+
|
|
87
|
+
self.lastTimestamp = 0
|
|
88
|
+
self.frameCount = 0
|
|
89
|
+
self.uiVisible = false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@objc public func showUI() {
|
|
94
|
+
runOnMain {
|
|
95
|
+
if self.overlayWindow == nil {
|
|
96
|
+
let window = FPSOverlayWindow(frame: UIScreen.main.bounds)
|
|
97
|
+
window.windowLevel = .statusBar + 99
|
|
98
|
+
window.backgroundColor = .clear
|
|
99
|
+
let windowScenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
100
|
+
window.windowScene = windowScenes.first { $0.activationState == .foregroundActive } ?? windowScenes.first
|
|
101
|
+
|
|
102
|
+
let rootViewController = UIViewController()
|
|
103
|
+
rootViewController.view.backgroundColor = .clear
|
|
104
|
+
window.rootViewController = rootViewController
|
|
105
|
+
|
|
106
|
+
let graphView = self.graphView ?? FPSGraphView()
|
|
107
|
+
graphView.backgroundColor = .clear
|
|
108
|
+
window.draggableView = graphView
|
|
109
|
+
rootViewController.view.addSubview(graphView)
|
|
110
|
+
|
|
111
|
+
self.graphView = graphView
|
|
112
|
+
self.overlayWindow = window
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if self.graphView == nil {
|
|
116
|
+
self.graphView = FPSGraphView(frame: CGRect(x: 10, y: 50, width: 150, height: 80))
|
|
117
|
+
self.overlayWindow?.draggableView = self.graphView
|
|
118
|
+
self.overlayWindow?.rootViewController?.view.addSubview(self.graphView!)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
self.graphView?.frame = CGRect(x: 10, y: 50, width: 150, height: 80)
|
|
122
|
+
self.graphView?.isHidden = false
|
|
123
|
+
self.overlayWindow?.isHidden = false
|
|
124
|
+
self.uiVisible = true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@objc public func hideUI() {
|
|
129
|
+
runOnMain {
|
|
130
|
+
self.graphView?.isHidden = true
|
|
131
|
+
self.graphView?.frame = .zero
|
|
132
|
+
self.overlayWindow?.isHidden = true
|
|
133
|
+
self.uiVisible = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@objc public func metricsSnapshot() -> [String: String] {
|
|
138
|
+
[
|
|
139
|
+
"fps": String(format: "%.2f", latestFPS),
|
|
140
|
+
"usedCPU": String(format: "%.2f", latestCPUPercent),
|
|
141
|
+
"usedGPU": String(format: "%.2f", latestGPUms),
|
|
142
|
+
"usedMemoryMB": String(format: "%.0f", latestMemoryMB),
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@objc private func appDidBecomeActive() {
|
|
147
|
+
displayLink?.isPaused = false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@objc private func appWillResignActive() {
|
|
151
|
+
displayLink?.isPaused = true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@objc private func tick(_ link: CADisplayLink) {
|
|
155
|
+
if lastTimestamp == 0 {
|
|
156
|
+
lastTimestamp = link.timestamp
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
frameCount += 1
|
|
161
|
+
let delta = link.timestamp - lastTimestamp
|
|
162
|
+
guard delta >= updateInterval else { return }
|
|
163
|
+
|
|
164
|
+
let fps = frameCount > 0 && delta > 0 ? CGFloat(Double(frameCount) / delta) : 0
|
|
165
|
+
let frameTime = frameCount > 0 ? delta / Double(frameCount) : 0
|
|
166
|
+
let baseGpuRatio: CGFloat = 0.4
|
|
167
|
+
let fpsVariation = (60 - min(fps, 60)) / 60
|
|
168
|
+
let gpuRatio = baseGpuRatio + fpsVariation * 0.3
|
|
169
|
+
let gpuTimePerFrameMs = CGFloat(frameTime) * gpuRatio * 1000
|
|
170
|
+
|
|
171
|
+
let cpuPercent = currentCPUPercent()
|
|
172
|
+
let memoryMB = currentMemoryMB()
|
|
173
|
+
|
|
174
|
+
if let onRamOverload, memoryMB > ramThreshold {
|
|
175
|
+
ramThreshold += 100
|
|
176
|
+
onRamOverload(memoryMB)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
latestFPS = fps
|
|
180
|
+
latestCPUPercent = cpuPercent
|
|
181
|
+
latestGPUms = gpuTimePerFrameMs
|
|
182
|
+
latestMemoryMB = memoryMB
|
|
183
|
+
|
|
184
|
+
let now = CFAbsoluteTimeGetCurrent()
|
|
185
|
+
if let onFPSLow, fps < fpsThreshold, now - lastFPSAlertTime >= 0.2 {
|
|
186
|
+
onFPSLow(fps)
|
|
187
|
+
lastFPSAlertTime = now
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if uiVisible, let graphView {
|
|
191
|
+
let bounds = UIScreen.main.bounds.size
|
|
192
|
+
let scale = UIScreen.main.scale
|
|
193
|
+
let resolution = CGSize(width: bounds.width * scale, height: bounds.height * scale)
|
|
194
|
+
let frameTimeMs = fps > 0 ? 1000 / fps : 0
|
|
195
|
+
let cpuTimeMs = frameTimeMs * min(max(cpuPercent, 0), 100) / 100
|
|
196
|
+
|
|
197
|
+
graphView.update(
|
|
198
|
+
resolution: resolution,
|
|
199
|
+
cpuTime: cpuTimeMs,
|
|
200
|
+
gpuTime: gpuTimePerFrameMs,
|
|
201
|
+
memory: memoryMB
|
|
202
|
+
)
|
|
203
|
+
graphView.addFPS(fps)
|
|
204
|
+
graphView.addCPUTime(cpuTimeMs)
|
|
205
|
+
graphView.addGPUTime(gpuTimePerFrameMs)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
lastTimestamp = link.timestamp
|
|
209
|
+
frameCount = 0
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private func currentMemoryMB() -> CGFloat {
|
|
213
|
+
var info = task_vm_info_data_t()
|
|
214
|
+
var count = mach_msg_type_number_t(MemoryLayout<task_vm_info_data_t>.size / MemoryLayout<natural_t>.size)
|
|
215
|
+
|
|
216
|
+
let result = withUnsafeMutablePointer(to: &info) {
|
|
217
|
+
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
|
|
218
|
+
task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
guard result == KERN_SUCCESS else { return 0 }
|
|
223
|
+
return CGFloat(info.phys_footprint) / (1024 * 1024)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private func currentCPUPercent() -> CGFloat {
|
|
227
|
+
var threadList: thread_act_array_t?
|
|
228
|
+
var threadCount = mach_msg_type_number_t()
|
|
229
|
+
|
|
230
|
+
guard task_threads(mach_task_self_, &threadList, &threadCount) == KERN_SUCCESS,
|
|
231
|
+
let threads = threadList else {
|
|
232
|
+
return 0
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
defer {
|
|
236
|
+
let byteCount = vm_size_t(Int(threadCount) * MemoryLayout<thread_t>.stride)
|
|
237
|
+
vm_deallocate(mach_task_self_, vm_address_t(UInt(bitPattern: threads)), byteCount)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
var cpuUsage: Float = 0
|
|
241
|
+
for index in 0..<Int(threadCount) {
|
|
242
|
+
var info = thread_basic_info()
|
|
243
|
+
var infoCount = mach_msg_type_number_t(THREAD_INFO_MAX)
|
|
244
|
+
let result = withUnsafeMutablePointer(to: &info) {
|
|
245
|
+
$0.withMemoryRebound(to: integer_t.self, capacity: Int(infoCount)) {
|
|
246
|
+
thread_info(threads[index], thread_flavor_t(THREAD_BASIC_INFO), $0, &infoCount)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if result == KERN_SUCCESS, (info.flags & TH_FLAGS_IDLE) == 0 {
|
|
251
|
+
cpuUsage += Float(info.cpu_usage) / Float(TH_USAGE_SCALE) * 100
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let cores = max(ProcessInfo.processInfo.activeProcessorCount, 1)
|
|
256
|
+
return CGFloat(cpuUsage / Float(cores))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private func runOnMain(_ block: @escaping () -> Void) {
|
|
260
|
+
if Thread.isMainThread {
|
|
261
|
+
block()
|
|
262
|
+
} else {
|
|
263
|
+
DispatchQueue.main.async(execute: block)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private final class FPSOverlayWindow: UIWindow {
|
|
269
|
+
weak var draggableView: UIView?
|
|
270
|
+
|
|
271
|
+
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
272
|
+
guard let draggableView else { return nil }
|
|
273
|
+
let pointInDragView = draggableView.convert(point, from: self)
|
|
274
|
+
guard draggableView.point(inside: pointInDragView, with: event) else { return nil }
|
|
275
|
+
return super.hitTest(point, with: event)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private final class FPSGraphView: UIView {
|
|
280
|
+
private var fpsValues: [CGFloat] = []
|
|
281
|
+
private var cpuTimeValues: [CGFloat] = []
|
|
282
|
+
private var gpuTimeValues: [CGFloat] = []
|
|
283
|
+
private let maxPoints = 100
|
|
284
|
+
private let pointSpacing: CGFloat = 2
|
|
285
|
+
|
|
286
|
+
private var currentResolution: CGSize = .zero
|
|
287
|
+
private var currentCPUTime: CGFloat = 0
|
|
288
|
+
private var currentGPUTime: CGFloat = 0
|
|
289
|
+
private var currentMemory: CGFloat = 0
|
|
290
|
+
|
|
291
|
+
private var isCollapsed = false
|
|
292
|
+
private var fullFrame: CGRect = .zero
|
|
293
|
+
|
|
294
|
+
override init(frame: CGRect = CGRect(x: 0, y: 100, width: 150, height: 80)) {
|
|
295
|
+
super.init(frame: frame)
|
|
296
|
+
isOpaque = false
|
|
297
|
+
layer.cornerRadius = 8
|
|
298
|
+
clipsToBounds = true
|
|
299
|
+
fullFrame = frame
|
|
300
|
+
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))))
|
|
301
|
+
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(toggleCollapse)))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
required init?(coder: NSCoder) {
|
|
305
|
+
super.init(coder: coder)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
func addFPS(_ fps: CGFloat) {
|
|
309
|
+
append(fps, to: &fpsValues)
|
|
310
|
+
setNeedsDisplay()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
func addCPUTime(_ cpuTime: CGFloat) {
|
|
314
|
+
append(cpuTime, to: &cpuTimeValues)
|
|
315
|
+
setNeedsDisplay()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func addGPUTime(_ gpuTime: CGFloat) {
|
|
319
|
+
append(gpuTime, to: &gpuTimeValues)
|
|
320
|
+
setNeedsDisplay()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
func update(resolution: CGSize, cpuTime: CGFloat, gpuTime: CGFloat, memory: CGFloat) {
|
|
324
|
+
currentResolution = resolution
|
|
325
|
+
currentCPUTime = cpuTime
|
|
326
|
+
currentGPUTime = gpuTime
|
|
327
|
+
currentMemory = memory
|
|
328
|
+
updateFrameIfNeeded()
|
|
329
|
+
setNeedsDisplay()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@objc private func toggleCollapse() {
|
|
333
|
+
if isCollapsed {
|
|
334
|
+
UIView.animate(withDuration: 0.25, animations: {
|
|
335
|
+
self.frame = self.fullFrame
|
|
336
|
+
}, completion: { _ in
|
|
337
|
+
self.isCollapsed = false
|
|
338
|
+
self.setNeedsDisplay()
|
|
339
|
+
})
|
|
340
|
+
} else {
|
|
341
|
+
fullFrame = frame
|
|
342
|
+
isCollapsed = true
|
|
343
|
+
UIView.animate(withDuration: 0.25, animations: {
|
|
344
|
+
self.frame.size = CGSize(width: 44, height: 24)
|
|
345
|
+
}, completion: { _ in
|
|
346
|
+
self.setNeedsDisplay()
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
|
|
352
|
+
guard let superview else { return }
|
|
353
|
+
let translation = gesture.translation(in: superview)
|
|
354
|
+
var newCenter = CGPoint(x: center.x + translation.x, y: center.y + translation.y)
|
|
355
|
+
let halfWidth = bounds.width / 2
|
|
356
|
+
let halfHeight = bounds.height / 2
|
|
357
|
+
let screen = UIScreen.main.bounds.size
|
|
358
|
+
newCenter.x = max(halfWidth, min(newCenter.x, screen.width - halfWidth))
|
|
359
|
+
newCenter.y = max(halfHeight, min(newCenter.y, screen.height - halfHeight))
|
|
360
|
+
center = newCenter
|
|
361
|
+
gesture.setTranslation(.zero, in: superview)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
override func draw(_ rect: CGRect) {
|
|
365
|
+
if isCollapsed {
|
|
366
|
+
drawCollapsed(rect)
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
drawExpanded(rect)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private func drawCollapsed(_ rect: CGRect) {
|
|
373
|
+
guard let context = UIGraphicsGetCurrentContext() else { return }
|
|
374
|
+
context.clear(rect)
|
|
375
|
+
context.setFillColor(UIColor(white: 0, alpha: 0.6).cgColor)
|
|
376
|
+
context.fill(rect)
|
|
377
|
+
|
|
378
|
+
let text = String(format: "%.0f", fpsValues.last ?? 0)
|
|
379
|
+
let attributes: [NSAttributedString.Key: Any] = [
|
|
380
|
+
.font: UIFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium),
|
|
381
|
+
.foregroundColor: UIColor.white,
|
|
382
|
+
]
|
|
383
|
+
let size = text.size(withAttributes: attributes)
|
|
384
|
+
text.draw(
|
|
385
|
+
at: CGPoint(x: (rect.width - size.width) / 2, y: (rect.height - size.height) / 2),
|
|
386
|
+
withAttributes: attributes
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private func drawExpanded(_ rect: CGRect) {
|
|
391
|
+
guard let context = UIGraphicsGetCurrentContext() else { return }
|
|
392
|
+
context.clear(rect)
|
|
393
|
+
context.setFillColor(UIColor(white: 0, alpha: 0.6).cgColor)
|
|
394
|
+
context.fill(rect)
|
|
395
|
+
|
|
396
|
+
let margin: CGFloat = 8
|
|
397
|
+
let labelAttrs = attributes(color: .white)
|
|
398
|
+
let fpsColor = UIColor(red: 0, green: 1, blue: 0, alpha: 1)
|
|
399
|
+
let cpuColor = UIColor(red: 1, green: 0.65, blue: 0, alpha: 1)
|
|
400
|
+
let gpuColor = UIColor(red: 0, green: 0.75, blue: 1, alpha: 1)
|
|
401
|
+
let memColor = UIColor(red: 1, green: 0.41, blue: 0.71, alpha: 1)
|
|
402
|
+
|
|
403
|
+
var y = margin
|
|
404
|
+
let deviceName = UIDevice.current.name
|
|
405
|
+
let resolution = String(format: "[%.0fx%.0f]", currentResolution.width, currentResolution.height)
|
|
406
|
+
drawSplit(left: deviceName, right: resolution, y: y, color: .white, rect: rect)
|
|
407
|
+
y += textHeight(labelAttrs) + 2
|
|
408
|
+
|
|
409
|
+
let scale = String(format: "%.1fx", UIScreen.main.scale)
|
|
410
|
+
let refreshRate = "\(UIScreen.main.maximumFramesPerSecond)Hz"
|
|
411
|
+
drawSplit(left: scale, right: refreshRate, y: y, color: .white, rect: rect)
|
|
412
|
+
y += textHeight(labelAttrs) + 6
|
|
413
|
+
|
|
414
|
+
let cpuMinMax = minMax(cpuTimeValues, fallback: currentCPUTime)
|
|
415
|
+
let gpuMinMax = minMax(gpuTimeValues, fallback: currentGPUTime)
|
|
416
|
+
let fpsMinMax = minMax(fpsValues, fallback: fpsValues.last ?? 0)
|
|
417
|
+
|
|
418
|
+
drawSplit(
|
|
419
|
+
left: String(format: "Pre: %.2fms", currentCPUTime),
|
|
420
|
+
right: String(format: "[%.2f %.2f]", cpuMinMax.min, cpuMinMax.max),
|
|
421
|
+
y: y,
|
|
422
|
+
color: cpuColor,
|
|
423
|
+
rect: rect
|
|
424
|
+
)
|
|
425
|
+
y += textHeight(labelAttrs) + 2
|
|
426
|
+
|
|
427
|
+
drawSplit(
|
|
428
|
+
left: String(format: "GPU: %.2fms", currentGPUTime),
|
|
429
|
+
right: String(format: "[%.2f %.2f]", gpuMinMax.min, gpuMinMax.max),
|
|
430
|
+
y: y,
|
|
431
|
+
color: gpuColor,
|
|
432
|
+
rect: rect
|
|
433
|
+
)
|
|
434
|
+
y += textHeight(labelAttrs) + 2
|
|
435
|
+
|
|
436
|
+
drawText(String(format: "Mem: %.1fMB", currentMemory), at: CGPoint(x: margin, y: y), color: memColor)
|
|
437
|
+
y += textHeight(labelAttrs) + 2
|
|
438
|
+
|
|
439
|
+
drawSplit(
|
|
440
|
+
left: String(format: "FPS: %.2f", fpsValues.last ?? 0),
|
|
441
|
+
right: String(format: "[%.2f %.2f]", fpsMinMax.min, fpsMinMax.max),
|
|
442
|
+
y: y,
|
|
443
|
+
color: fpsColor,
|
|
444
|
+
rect: rect
|
|
445
|
+
)
|
|
446
|
+
y += textHeight(labelAttrs) + 6
|
|
447
|
+
|
|
448
|
+
let graphTop = y
|
|
449
|
+
let graphHeight = rect.height - graphTop - margin
|
|
450
|
+
guard graphHeight > 12 else { return }
|
|
451
|
+
|
|
452
|
+
drawGrid(context: context, x: margin, y: graphTop, width: rect.width - margin * 2, height: graphHeight)
|
|
453
|
+
drawLine(context: context, values: fpsValues, x: margin + 2, y: graphTop, width: rect.width - margin * 2 - 4, height: graphHeight, maxScale: 60, color: fpsColor)
|
|
454
|
+
drawLine(context: context, values: cpuTimeValues, x: margin + 2, y: graphTop, width: rect.width - margin * 2 - 4, height: graphHeight, maxScale: 50, color: cpuColor)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private func updateFrameIfNeeded() {
|
|
458
|
+
guard !isCollapsed else { return }
|
|
459
|
+
let attrs = attributes(color: .white)
|
|
460
|
+
let lines = [
|
|
461
|
+
"\(UIDevice.current.name) [\(Int(currentResolution.width))x\(Int(currentResolution.height))]",
|
|
462
|
+
String(format: "%.1fx %dHz", UIScreen.main.scale, UIScreen.main.maximumFramesPerSecond),
|
|
463
|
+
"Pre: 99.99ms [99.99 99.99]",
|
|
464
|
+
"GPU: 99.99ms [99.99 99.99]",
|
|
465
|
+
"Mem: 999.9MB",
|
|
466
|
+
"FPS: 99.99 [99.99 99.99]",
|
|
467
|
+
]
|
|
468
|
+
let width = lines.map { $0.size(withAttributes: attrs).width }.max() ?? 150
|
|
469
|
+
let finalWidth = max(150, width + 16)
|
|
470
|
+
let finalHeight = max(80, textHeight(attrs) * CGFloat(lines.count) + 26)
|
|
471
|
+
|
|
472
|
+
if abs(frame.width - finalWidth) > 1 || abs(frame.height - finalHeight) > 1 {
|
|
473
|
+
frame.size = CGSize(width: finalWidth, height: finalHeight)
|
|
474
|
+
fullFrame = frame
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private func append(_ value: CGFloat, to values: inout [CGFloat]) {
|
|
479
|
+
if values.count >= maxPoints {
|
|
480
|
+
values.removeFirst()
|
|
481
|
+
}
|
|
482
|
+
values.append(value)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private func drawSplit(left: String, right: String, y: CGFloat, color: UIColor, rect: CGRect) {
|
|
486
|
+
let margin: CGFloat = 8
|
|
487
|
+
let rightSize = right.size(withAttributes: attributes(color: color))
|
|
488
|
+
drawText(left, at: CGPoint(x: margin, y: y), color: color)
|
|
489
|
+
drawText(right, at: CGPoint(x: rect.width - margin - rightSize.width, y: y), color: color)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private func drawText(_ text: String, at point: CGPoint, color: UIColor) {
|
|
493
|
+
text.draw(at: point, withAttributes: attributes(color: color))
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private func drawGrid(context: CGContext, x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) {
|
|
497
|
+
context.setStrokeColor(UIColor(white: 1, alpha: 0.1).cgColor)
|
|
498
|
+
context.setLineWidth(0.5)
|
|
499
|
+
for index in 1...3 {
|
|
500
|
+
let gridY = y + CGFloat(index) * (height / 4)
|
|
501
|
+
context.move(to: CGPoint(x: x, y: gridY))
|
|
502
|
+
context.addLine(to: CGPoint(x: x + width, y: gridY))
|
|
503
|
+
}
|
|
504
|
+
var gridX = x + 20
|
|
505
|
+
while gridX < x + width {
|
|
506
|
+
context.move(to: CGPoint(x: gridX, y: y))
|
|
507
|
+
context.addLine(to: CGPoint(x: gridX, y: y + height))
|
|
508
|
+
gridX += 20
|
|
509
|
+
}
|
|
510
|
+
context.strokePath()
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private func drawLine(
|
|
514
|
+
context: CGContext,
|
|
515
|
+
values: [CGFloat],
|
|
516
|
+
x: CGFloat,
|
|
517
|
+
y: CGFloat,
|
|
518
|
+
width: CGFloat,
|
|
519
|
+
height: CGFloat,
|
|
520
|
+
maxScale: CGFloat,
|
|
521
|
+
color: UIColor
|
|
522
|
+
) {
|
|
523
|
+
guard values.count > 1 else { return }
|
|
524
|
+
context.setStrokeColor(color.cgColor)
|
|
525
|
+
context.setLineWidth(1)
|
|
526
|
+
|
|
527
|
+
let visibleValues = Array(values.suffix(Int(width / pointSpacing)))
|
|
528
|
+
for index in 1..<visibleValues.count {
|
|
529
|
+
let previous = point(for: visibleValues[index - 1], index: index - 1, x: x, y: y, height: height, maxScale: maxScale)
|
|
530
|
+
let current = point(for: visibleValues[index], index: index, x: x, y: y, height: height, maxScale: maxScale)
|
|
531
|
+
context.move(to: previous)
|
|
532
|
+
context.addLine(to: current)
|
|
533
|
+
context.strokePath()
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private func point(for value: CGFloat, index: Int, x: CGFloat, y: CGFloat, height: CGFloat, maxScale: CGFloat) -> CGPoint {
|
|
538
|
+
let normalized = max(0, min(value / maxScale, 1))
|
|
539
|
+
return CGPoint(
|
|
540
|
+
x: x + CGFloat(index) * pointSpacing,
|
|
541
|
+
y: y + height * (1 - normalized)
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private func minMax(_ values: [CGFloat], fallback: CGFloat) -> (min: CGFloat, max: CGFloat) {
|
|
546
|
+
guard let minValue = values.min(), let maxValue = values.max() else {
|
|
547
|
+
return (fallback, fallback)
|
|
548
|
+
}
|
|
549
|
+
return (minValue, maxValue)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private func attributes(color: UIColor) -> [NSAttributedString.Key: Any] {
|
|
553
|
+
[
|
|
554
|
+
.font: UIFont.monospacedDigitSystemFont(ofSize: 10, weight: .regular),
|
|
555
|
+
.foregroundColor: color,
|
|
556
|
+
]
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private func textHeight(_ attributes: [NSAttributedString.Key: Any]) -> CGFloat {
|
|
560
|
+
"FPS".size(withAttributes: attributes).height
|
|
561
|
+
}
|
|
562
|
+
}
|
package/package.json
CHANGED
package/publish.sh
CHANGED
|
@@ -456,7 +456,7 @@ case "$PHASE" in
|
|
|
456
456
|
;;
|
|
457
457
|
"maven")
|
|
458
458
|
echo "Running PHASE 2 only: Publish to Maven"
|
|
459
|
-
|
|
459
|
+
phase_publish_maven
|
|
460
460
|
;;
|
|
461
461
|
"npm")
|
|
462
462
|
echo "Running PHASE 3 only: Publish to NPM"
|
|
@@ -465,7 +465,7 @@ case "$PHASE" in
|
|
|
465
465
|
"all")
|
|
466
466
|
echo "Running ALL PHASES"
|
|
467
467
|
phase_check_version
|
|
468
|
-
|
|
468
|
+
phase_publish_maven
|
|
469
469
|
phase_publish_npm
|
|
470
470
|
echo ""
|
|
471
471
|
echo "🎉 ALL PHASES COMPLETED SUCCESSFULLY!"
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
-
<plist version="1.0">
|
|
4
|
-
<dict>
|
|
5
|
-
<key>SchemeUserState</key>
|
|
6
|
-
<dict>
|
|
7
|
-
<key>Example.xcscheme_^#shared#^_</key>
|
|
8
|
-
<dict>
|
|
9
|
-
<key>orderHint</key>
|
|
10
|
-
<integer>0</integer>
|
|
11
|
-
</dict>
|
|
12
|
-
</dict>
|
|
13
|
-
</dict>
|
|
14
|
-
</plist>
|
|
Binary file
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<Scheme
|
|
3
|
-
LastUpgradeVersion = "1600"
|
|
4
|
-
version = "1.3">
|
|
5
|
-
<BuildAction
|
|
6
|
-
parallelizeBuildables = "YES"
|
|
7
|
-
buildImplicitDependencies = "YES">
|
|
8
|
-
<BuildActionEntries>
|
|
9
|
-
<BuildActionEntry
|
|
10
|
-
buildForTesting = "YES"
|
|
11
|
-
buildForRunning = "YES"
|
|
12
|
-
buildForProfiling = "YES"
|
|
13
|
-
buildForArchiving = "YES"
|
|
14
|
-
buildForAnalyzing = "YES">
|
|
15
|
-
<BuildableReference
|
|
16
|
-
BuildableIdentifier = "primary"
|
|
17
|
-
BlueprintIdentifier = "3B6FB503A75BF5BC1FA6F30BC06B9D28"
|
|
18
|
-
BuildableName = "MoMoUIKits.framework"
|
|
19
|
-
BlueprintName = "MoMoUIKits"
|
|
20
|
-
ReferencedContainer = "container:Pods.xcodeproj">
|
|
21
|
-
</BuildableReference>
|
|
22
|
-
</BuildActionEntry>
|
|
23
|
-
</BuildActionEntries>
|
|
24
|
-
</BuildAction>
|
|
25
|
-
<TestAction
|
|
26
|
-
buildConfiguration = "Debug"
|
|
27
|
-
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
28
|
-
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
29
|
-
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
30
|
-
<Testables>
|
|
31
|
-
</Testables>
|
|
32
|
-
</TestAction>
|
|
33
|
-
<LaunchAction
|
|
34
|
-
buildConfiguration = "Debug"
|
|
35
|
-
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
36
|
-
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
37
|
-
launchStyle = "0"
|
|
38
|
-
useCustomWorkingDirectory = "NO"
|
|
39
|
-
ignoresPersistentStateOnLaunch = "NO"
|
|
40
|
-
debugDocumentVersioning = "YES"
|
|
41
|
-
debugServiceExtension = "internal"
|
|
42
|
-
allowLocationSimulation = "YES">
|
|
43
|
-
</LaunchAction>
|
|
44
|
-
<ProfileAction
|
|
45
|
-
buildConfiguration = "Release"
|
|
46
|
-
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
47
|
-
savedToolIdentifier = ""
|
|
48
|
-
useCustomWorkingDirectory = "NO"
|
|
49
|
-
debugDocumentVersioning = "YES">
|
|
50
|
-
</ProfileAction>
|
|
51
|
-
<AnalyzeAction
|
|
52
|
-
buildConfiguration = "Debug">
|
|
53
|
-
</AnalyzeAction>
|
|
54
|
-
<ArchiveAction
|
|
55
|
-
buildConfiguration = "Release"
|
|
56
|
-
revealArchiveInOrganizer = "YES">
|
|
57
|
-
</ArchiveAction>
|
|
58
|
-
</Scheme>
|