Skip to content

Commit 2b31548

Browse files
committed
Create layout performance benchmarks and DummyBackend
1 parent d9cc075 commit 2b31548

File tree

7 files changed

+957
-2
lines changed

7 files changed

+957
-2
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import SwiftCrossUI
2+
3+
struct Column: View {
4+
var body: some View {
5+
HStack {
6+
Color.orange.frame(width: 5)
7+
Text("Lorem ipsum dolor sit amet.")
8+
}
9+
}
10+
}
11+
12+
struct DoubleColumn: View {
13+
var body: some View {
14+
HStack {
15+
Column()
16+
Column()
17+
}
18+
}
19+
}
20+
21+
struct Row: View {
22+
var body: some View {
23+
HStack {
24+
HStack {
25+
DoubleColumn()
26+
DoubleColumn()
27+
DoubleColumn()
28+
DoubleColumn()
29+
}
30+
HStack {
31+
DoubleColumn()
32+
DoubleColumn()
33+
DoubleColumn()
34+
DoubleColumn()
35+
}
36+
}
37+
}
38+
}
39+
40+
struct DoubleRow: View {
41+
var body: some View {
42+
VStack {
43+
Row()
44+
Row()
45+
}
46+
}
47+
}
48+
49+
struct GridView: TestCaseView {
50+
var body: some View {
51+
VStack {
52+
VStack {
53+
DoubleRow()
54+
DoubleRow()
55+
DoubleRow()
56+
DoubleRow()
57+
}
58+
VStack {
59+
DoubleRow()
60+
DoubleRow()
61+
DoubleRow()
62+
DoubleRow()
63+
}
64+
}
65+
}
66+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import Benchmark
2+
import SwiftCrossUI
3+
import DummyBackend
4+
import Foundation
5+
6+
protocol TestCaseView: View {
7+
init()
8+
}
9+
10+
#if BENCHMARK_VIZ
11+
import DefaultBackend
12+
13+
struct VizApp<V: TestCaseView>: App {
14+
var body: some Scene {
15+
WindowGroup("Benchmark visualisation") {
16+
V()
17+
}
18+
}
19+
}
20+
#endif
21+
22+
@main
23+
struct Benchmarks {
24+
@MainActor
25+
static func main() async {
26+
let backend = DummyBackend()
27+
let defaultEnvironment = EnvironmentValues(backend: backend)
28+
let environment = backend.computeRootEnvironment(defaultEnvironment: defaultEnvironment)
29+
.with(\.window, backend.createWindow(withDefaultSize: nil))
30+
31+
@MainActor
32+
func makeNode<V: View>(_ view: V) -> ViewGraphNode<V, DummyBackend> {
33+
ViewGraphNode(for: view, backend: backend, snapshot: nil, environment: environment)
34+
}
35+
36+
@MainActor
37+
func updateNode<V: View>(_ node: ViewGraphNode<V, DummyBackend>, _ size: SIMD2<Int>) {
38+
_ = node.update(proposedSize: size, environment: environment, dryRun: true)
39+
_ = node.update(proposedSize: size, environment: environment, dryRun: false)
40+
}
41+
42+
#if BENCHMARK_VIZ
43+
print("Benchmark to viz: ", terminator: "")
44+
guard let benchmarkName = readLine() else {
45+
print("Nothing entered")
46+
exit(1)
47+
}
48+
49+
var benchmarkNames: [String] = []
50+
#endif
51+
52+
@MainActor
53+
func benchmarkLayout<V: TestCaseView>(of viewType: V.Type, _ size: SIMD2<Int>, _ label: String) {
54+
#if BENCHMARK_VIZ
55+
if benchmarkName == label {
56+
VizApp<V>.main()
57+
exit(0)
58+
}
59+
benchmarkNames.append(label)
60+
#else
61+
let node = makeNode(V())
62+
benchmark(label) { @MainActor in
63+
updateNode(node, size)
64+
}
65+
#endif
66+
}
67+
68+
benchmarkLayout(of: GridView.self, SIMD2(800, 800), "grid")
69+
benchmarkLayout(of: ScrollableMessageListView.self, SIMD2(800, 800), "message list")
70+
71+
#if BENCHMARK_VIZ
72+
print("\(benchmarkName) didn't match any benchmarks. Valid options: [\(benchmarkNames.joined(separator: ", "))]")
73+
exit(1)
74+
#endif
75+
76+
await Benchmark.main()
77+
}
78+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import SwiftCrossUI
2+
3+
struct MessageView: View {
4+
static let profileSize = 40
5+
6+
var message: ScrollableMessageListView.Message
7+
8+
var body: some View {
9+
HStack(alignment: .top) {
10+
message.author.profileColor
11+
.frame(width: Self.profileSize, height: Self.profileSize)
12+
.cornerRadius(Self.profileSize / 2)
13+
14+
VStack(alignment: .leading) {
15+
HStack {
16+
Text(message.author.name)
17+
.foregroundColor(message.author.handleColor)
18+
.emphasized()
19+
20+
Text(message.time)
21+
.foregroundColor(.gray)
22+
}
23+
24+
Text(message.content)
25+
}
26+
}
27+
}
28+
}
29+
30+
struct ScrollableMessageListView: TestCaseView {
31+
static let authors = [
32+
Author(name: "stackotter", handleColor: .blue, profileColor: .pink),
33+
Author(name: "gregc", handleColor: .yellow, profileColor: .yellow),
34+
Author(name: "bbrk24", handleColor: .purple, profileColor: .green)
35+
]
36+
37+
static let sentences = [
38+
"in the meantime you should be able to disable hot reloading support on linux at line 107 of Package.swift. hot reloading isn't actually supported on linux (it'll tell you that it's unsupported if you try to use hot reloading at runtime), i just left the code enabled to catch cross-platform regressions like this one hahah",
39+
"I assume stroke style gets overridden? if so we can make path store stroke style and then StyledShape can store another strokestyle if it wants, and when it produces the path it just overwrites its strokestyle before passing it on or something",
40+
"I am hesitant to completely mimic SwiftUI here because you can set stroke style on both Path and Shape, but stroke color only on Shape, so I have no idea what happens if you give them conflicting stroke styles",
41+
"I think stroke and fill modifiers on Shape would be a bit nicer. we could get them to return StyledShape or something and then implement the same two modifiers on StyledShape and get StyledShape to store fill and stroke",
42+
"Computer Software Is Hard"
43+
]
44+
45+
static let times = [
46+
"8:54am",
47+
"9:13am",
48+
"10:20pm",
49+
"12:01am",
50+
"1:00pm"
51+
]
52+
53+
static let messages = generateMessages(400)
54+
55+
struct Author {
56+
var name: String
57+
var handleColor: Color
58+
var profileColor: Color
59+
60+
static let empty = Self(
61+
name: "<no name>",
62+
handleColor: .white,
63+
profileColor: .white
64+
)
65+
}
66+
67+
struct Message {
68+
var author: Author
69+
var content: String
70+
var time: String
71+
}
72+
73+
static func generateMessages(_ count: Int) -> [Message] {
74+
var messages: [Message] = []
75+
messages.reserveCapacity(count)
76+
77+
for i in 0..<count {
78+
let message = Message(
79+
author: authors[i % authors.count],
80+
content: sentences[i % sentences.count],
81+
time: times[i % times.count]
82+
)
83+
messages.append(message)
84+
}
85+
return messages
86+
}
87+
88+
var body: some View {
89+
ScrollView {
90+
VStack(alignment: .leading) {
91+
ForEach(Self.messages) { message in
92+
MessageView(message: message)
93+
}
94+
}
95+
.frame(maxWidth: .infinity)
96+
.padding(20)
97+
}
98+
}
99+
}

Package.resolved

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ switch ProcessInfo.processInfo.environment["SCUI_LIBRARY_TYPE"] {
5959
}
6060
}
6161

62+
// When SCUI_BENCHMARK_VIZ is present, we include the DefaultBackend to allow
63+
// viewing of each benchmark test case with an actual backend.
64+
let additionalLayoutPerformanceBenchmarkDependencies: [Target.Dependency]
65+
let layoutPerformanceSwiftSettings: [SwiftSetting]
66+
if ProcessInfo.processInfo.environment["SCUI_BENCHMARK_VIZ"] == "1" {
67+
additionalLayoutPerformanceBenchmarkDependencies = ["DefaultBackend"]
68+
layoutPerformanceSwiftSettings = [.define("BENCHMARK_VIZ")]
69+
} else {
70+
additionalLayoutPerformanceBenchmarkDependencies = []
71+
layoutPerformanceSwiftSettings = []
72+
}
73+
6274
let package = Package(
6375
name: "swift-cross-ui",
6476
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .macCatalyst(.v13), .visionOS(.v1)],
@@ -110,6 +122,10 @@ let package = Package(
110122
url: "https://github.com/stackotter/swift-winui",
111123
revision: "1695ee3ea2b7a249f6504c7f1759e7ec7a38eb86"
112124
),
125+
.package(
126+
url: "https://github.com/stackotter/swift-benchmark",
127+
.upToNextMinor(from: "0.2.0")
128+
),
113129
// .package(
114130
// url: "https://github.com/stackotter/TermKit",
115131
// revision: "163afa64f1257a0c026cc83ed8bc47a5f8fc9704"
@@ -252,6 +268,19 @@ let package = Package(
252268
name: "WinUIInterop",
253269
dependencies: []
254270
),
271+
.target(name: "DummyBackend", dependencies: ["SwiftCrossUI"]),
272+
273+
.executableTarget(
274+
name: "LayoutPerformanceBenchmark",
275+
dependencies: [
276+
.product(name: "Benchmark", package: "swift-benchmark"),
277+
"SwiftCrossUI",
278+
"DummyBackend",
279+
] + additionalLayoutPerformanceBenchmarkDependencies,
280+
path: "Benchmarks/LayoutPerformanceBenchmark",
281+
swiftSettings: layoutPerformanceSwiftSettings
282+
),
283+
255284
// .target(
256285
// name: "CursesBackend",
257286
// dependencies: ["SwiftCrossUI", "TermKit"]

0 commit comments

Comments
 (0)