Skip to content

Commit 090c89a

Browse files
committed
Use Swift C++ interop to more closely integrate with Flutter engine
1 parent b876c61 commit 090c89a

File tree

13 files changed

+166
-87
lines changed

13 files changed

+166
-87
lines changed

Examples/counter/macos/Flutter/GeneratedPluginRegistrant.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,4 @@
55
import FlutterMacOS
66
import Foundation
77

8-
9-
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
10-
}
8+
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {}

Examples/counter/swift/runner.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ enum Counter {
146146
title: "Counter",
147147
appId: "com.example.counter"
148148
)
149-
let window = FlutterWindow(properties: viewProperties, project: dartProject)
149+
let window = FlutterWindow(
150+
properties: viewProperties,
151+
project: dartProject,
152+
enableImpeller: true
153+
)
150154
guard let window else {
151155
exit(2)
152156
}

Package.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ var targetPluginUsages = [Target.PluginUsage]()
1313
var platformCxxSettings: [CXXSetting] = []
1414
var platformSwiftSettings: [SwiftSetting] = [.swiftLanguageMode(.v5)]
1515

16-
func tryGuessSwiftLibRoot() -> String {
16+
func tryGuessSwiftRoot() -> String {
1717
let task = Process()
1818
task.executableURL = URL(fileURLWithPath: "/bin/sh")
1919
task.arguments = ["-c", "which swift"]
@@ -22,13 +22,14 @@ func tryGuessSwiftLibRoot() -> String {
2222
try task.run()
2323
let outputData = (task.standardOutput as! Pipe).fileHandleForReading.readDataToEndOfFile()
2424
let path = URL(fileURLWithPath: String(decoding: outputData, as: UTF8.self))
25-
return path.deletingLastPathComponent().path + "/../lib/swift"
25+
return path.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
26+
.path
2627
} catch {
27-
return "/usr/lib/swift"
28+
return ""
2829
}
2930
}
3031

31-
let SwiftLibRoot = tryGuessSwiftLibRoot()
32+
let SwiftRoot = tryGuessSwiftRoot()
3233
var FlutterPlatform: String
3334
var FlutterUnsafeLinkerFlags: [String] = []
3435

@@ -231,10 +232,19 @@ enum FlutterELinuxBackendType {
231232

232233
let FlutterELinuxBackend = FlutterELinuxBackendType.defaultBackend
233234

235+
let CxxIncludeDirs: [String] = [
236+
"\(SwiftRoot)/usr/include",
237+
"\(SwiftRoot)/usr/lib/swift",
238+
"/usr/include/drm",
239+
]
240+
241+
let CxxIncludeFlags = CxxIncludeDirs.flatMap { ["-I", $0] }
242+
234243
platformSwiftSettings += [
235244
.define("DISPLAY_BACKEND_TYPE_\(FlutterELinuxBackend.displayBackendType)"),
236245
.define("FLUTTER_TARGET_BACKEND_\(FlutterELinuxBackend.flutterTargetBackend)"),
237246
.interoperabilityMode(.Cxx),
247+
.unsafeFlags(CxxIncludeFlags),
238248
]
239249

240250
targets += [
@@ -401,7 +411,7 @@ targets += [
401411
.headerSearchPath("flutter-embedded-linux/src/third_party/rapidjson/include"),
402412
// FIXME: .cxxLanguageStandard breaks Foundation compile
403413
// FIXME: include path for swift/bridging.h
404-
.unsafeFlags(["-pthread", "-I", SwiftLibRoot, "-I", "/usr/include/drm", "-std=c++17"]),
414+
.unsafeFlags(["-pthread", "-std=c++17"] + CxxIncludeFlags),
405415
],
406416
linkerSettings: [
407417
// .unsafeFlags(["-pthread"]),
@@ -443,6 +453,8 @@ platformCxxSettings += [
443453
.headerSearchPath(
444454
"../CxxFlutterSwift/flutter-embedded-linux/src/flutter/shell/platform/common/client_wrapper/include"
445455
),
456+
.headerSearchPath("../CxxFlutterSwift/flutter-embedded-linux/src/third_party/rapidjson/include"),
457+
.unsafeFlags(CxxIncludeFlags),
446458
]
447459

448460
#else

Sources/CxxFlutterSwift/include/CxxFlutterSwift.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2023-2024 PADL Software Pty Ltd
2+
// Copyright (c) 2023-2025 PADL Software Pty Ltd
33
//
44
// Licensed under the Apache License, Version 2.0 (the License);
55
// you may not use this file except in compliance with the License.
@@ -21,11 +21,18 @@
2121

2222
#include <flutter_messenger.h>
2323
#include <flutter_elinux.h>
24+
#include <flutter_elinux_engine.h>
25+
#include <flutter_elinux_state.h>
26+
#include <flutter_elinux_view.h>
2427
#include <flutter_plugin_registrar.h>
25-
// #include <flutter_elinux_state.h>
2628
#include <flutter_platform_views.h>
2729

2830
#ifdef __cplusplus
31+
#include <vector>
32+
#include <string>
33+
34+
using CxxVectorOfString = std::vector<std::string, std::allocator<std::string>>;
35+
2936
extern "C" {
3037
#endif
3138

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module CxxFlutterSwift {
2+
header "CxxFlutterSwift.h"
3+
}

Sources/FlutterSwift/Base/StringHelpers.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#if os(Linux)
13+
#if os(Linux) && canImport(Glibc)
14+
15+
@_implementationOnly
16+
import CxxFlutterSwift
17+
import CxxStdlib
18+
1419
/// Compute the prefix sum of `seq`.
1520
func scan<
1621
S: Sequence, U
@@ -60,4 +65,16 @@ extension String {
6065
return u32.withUnsafeBufferPointer { body($0.baseAddress!) }
6166
}
6267
}
68+
69+
extension Array where Element == String {
70+
var cxxVector: CxxVectorOfString {
71+
var tmp = CxxVectorOfString()
72+
73+
for element in self {
74+
tmp.push_back(std.string(element))
75+
}
76+
77+
return tmp
78+
}
79+
}
6380
#endif

Sources/FlutterSwift/Channel/FlutterEventChannel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public final class FlutterEventChannel: _FlutterBinaryMessengerConnectionReprese
3838
public let codec: FlutterMessageCodec
3939
public let priority: TaskPriority?
4040

41-
private typealias EventStreamTask = Task<Void, Never>
41+
private typealias EventStreamTask = Task<(), Never>
4242

4343
private let _connection: ManagedAtomic<FlutterBinaryMessengerConnection>
4444
private let tasks: ManagedCriticalState<[String: EventStreamTask]>

Sources/FlutterSwift/Client/FlutterEngine.swift

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2023-2024 PADL Software Pty Ltd
2+
// Copyright (c) 2023-2025 PADL Software Pty Ltd
33
//
44
// Licensed under the Apache License, Version 2.0 (the License);
55
// you may not use this file except in compliance with the License.
@@ -17,17 +17,18 @@
1717
#if os(Linux) && canImport(Glibc)
1818
@_implementationOnly
1919
import CxxFlutterSwift
20+
import CxxStdlib
2021

2122
public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
22-
var engine: FlutterDesktopEngineRef! // strong or weak ref
2323
var pluginPublications = [String: Any]()
2424
let project: DartProject
2525
weak var viewController: FlutterViewController?
26+
private var engine: flutter.FlutterELinuxEngine! // strong or weak ref
2627
private var _binaryMessenger: FlutterDesktopMessenger!
2728
private var ownsEngine = true
2829
private var hasBeenRun = false
2930

30-
public init?(project: DartProject) {
31+
public init?(project: DartProject, switches: [String: Any] = [:]) {
3132
var properties = FlutterDesktopEngineProperties()
3233

3334
debugPrint("Initializing Flutter engine with \(project)")
@@ -48,7 +49,9 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
4849
)
4950
cStrings.withUnsafeMutableBufferPointer { pointer in
5051
properties.dart_entrypoint_argv = pointer.baseAddress
51-
self.engine = FlutterDesktopEngineCreate(&properties)
52+
let engine = FlutterDesktopEngineCreate(&properties)
53+
self.engine = unsafeBitCast(engine, to: flutter.FlutterELinuxEngine.self)
54+
setSwitches(switches.map { key, value in "--\(key)=\(String(describing: value))" })
5255
self._binaryMessenger = FlutterDesktopMessenger(engine: self.engine)
5356
}
5457
}
@@ -61,6 +64,10 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
6164
shutDown()
6265
}
6366

67+
private var _handle: FlutterDesktopEngineRef {
68+
unsafeBitCast(engine, to: FlutterDesktopEngineRef.self)
69+
}
70+
6471
// note we can't use public private(set) because we need the type to be FlutterDesktopMessenger!
6572
// in order for callbacks to work (otherwise self must be first initialized). But we want to
6673
// present a non-optional type to callers.
@@ -69,39 +76,31 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
6976
}
7077

7178
public func run(entryPoint: String? = nil) -> Bool {
72-
if hasBeenRun {
73-
debugPrint("Cannot run an engine more than once.")
74-
return false
75-
}
76-
let runSucceeded = FlutterDesktopEngineRun(engine, entryPoint)
77-
if !runSucceeded {
78-
debugPrint("Failed to start engine.")
79-
}
80-
hasBeenRun = true
81-
return runSucceeded
79+
guard !hasBeenRun else { return false }
80+
hasBeenRun = engine.RunWithEntrypoint(entryPoint)
81+
return hasBeenRun
8282
}
8383

8484
public func shutDown() {
8585
pluginPublications.removeAll()
86-
if let engine, ownsEngine {
87-
FlutterDesktopEngineDestroy(engine)
86+
if engine != nil, ownsEngine {
87+
FlutterDesktopEngineDestroy(_handle)
8888
}
8989
engine = nil
9090
}
9191

9292
public func processMessages() -> UInt64 {
9393
precondition(engine != nil)
94-
return FlutterDesktopEngineProcessMessages(engine)
94+
return FlutterDesktopEngineProcessMessages(_handle)
9595
}
9696

9797
public func reloadSystemFonts() {
98-
precondition(engine != nil)
99-
FlutterDesktopEngineReloadSystemFonts(engine)
98+
engine.ReloadSystemFonts()
10099
}
101100

102101
func relinquishEngine() -> FlutterDesktopEngineRef {
103102
ownsEngine = false
104-
return engine
103+
return _handle
105104
}
106105

107106
public func registrar(for pluginKey: String) -> FlutterPluginRegistrar? {
@@ -116,5 +115,38 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
116115
public func valuePublished(by pluginKey: String) -> Any? {
117116
pluginPublications[pluginKey]
118117
}
118+
119+
public var isRunning: Bool {
120+
engine.running()
121+
}
122+
123+
func onVsync(lastFrameTimeNS: UInt64, vsyncIntervalTimeNS: UInt64) {
124+
engine.OnVsync(lastFrameTimeNS, vsyncIntervalTimeNS)
125+
}
126+
127+
public var isImpellerEnabled: Bool {
128+
engine.IsImpellerEnabled()
129+
}
130+
131+
public func setSystemSettings(textScalingFactor: Float, enableHighContrast: Bool) {
132+
engine.SetSystemSettings(textScalingFactor, enableHighContrast)
133+
}
134+
135+
public func setView(_ view: FlutterView) {
136+
engine.SetView(view.view)
137+
}
138+
139+
public func setSwitches(_ switches: [String]) {
140+
engine.SetSwitches(switches.cxxVector)
141+
}
142+
143+
func getRegistrar(pluginName: String) -> FlutterDesktopPluginRegistrarRef? {
144+
FlutterDesktopEngineGetPluginRegistrar(_handle, pluginName)
145+
}
146+
147+
var textureRegistrar: FlutterDesktopTextureRegistrarRef {
148+
FlutterDesktopEngineGetTextureRegistrar(_handle)
149+
}
119150
}
151+
120152
#endif

Sources/FlutterSwift/Client/FlutterPlugin.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public final class FlutterDesktopPluginRegistrar: FlutterPluginRegistrar {
116116
) {
117117
self.engine = engine
118118
pluginKey = pluginName
119-
registrar = FlutterDesktopEngineGetPluginRegistrar(engine.engine, pluginName)
119+
registrar = engine.getRegistrar(pluginName: pluginName)
120120
FlutterDesktopPluginRegistrarSetDestructionHandlerBlock(registrar!) { _ in
121121
self.registrar = nil
122122
}
@@ -184,13 +184,17 @@ public struct FlutterDesktopTextureRegistrar {
184184
private let registrar: FlutterDesktopTextureRegistrarRef
185185

186186
public init(engine: FlutterEngine) {
187-
registrar = FlutterDesktopEngineGetTextureRegistrar(engine.engine)
187+
registrar = engine.textureRegistrar
188188
}
189189

190190
init?(plugin: FlutterDesktopPluginRegistrar) {
191191
guard let registrar = plugin.registrar else { return nil }
192192
self.registrar = FlutterDesktopRegistrarGetTextureRegistrar(registrar)
193193
}
194+
195+
public func markExternalTextureFrameAvailable(textureID: Int64) {
196+
FlutterDesktopTextureRegistrarMarkExternalTextureFrameAvailable(registrar, textureID)
197+
}
194198
}
195199

196200
#endif

Sources/FlutterSwift/Client/FlutterView.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2023-2024 PADL Software Pty Ltd
2+
// Copyright (c) 2023-2025 PADL Software Pty Ltd
33
//
44
// Licensed under the Apache License, Version 2.0 (the License);
55
// you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import CxxFlutterSwift
2121
let kChannelName = "flutter/platform_views"
2222

2323
public struct FlutterView {
24-
let view: FlutterDesktopViewRef
24+
let view: flutter.FlutterELinuxView
2525
var platformViewsPluginRegistrar: FlutterPluginRegistrar?
2626
var platformViewsHandler: FlutterPlatformViewsPlugin?
2727
var viewController: FlutterViewController? {
@@ -38,16 +38,20 @@ public struct FlutterView {
3838
}
3939
}
4040

41-
init(_ view: FlutterDesktopViewRef) {
41+
init(_ view: flutter.FlutterELinuxView) {
4242
self.view = view
4343
}
4444

45+
init(_ view: FlutterDesktopViewRef) {
46+
self.init(unsafeBitCast(view, to: flutter.FlutterELinuxView.self))
47+
}
48+
4549
public func dispatchEvent() -> Bool {
46-
FlutterDesktopViewDispatchEvent(view)
50+
view.DispatchEvent()
4751
}
4852

4953
public var frameRate: Int32 {
50-
FlutterDesktopViewGetFrameRate(view)
54+
view.GetFrameRate()
5155
}
5256
}
5357
#endif

Sources/FlutterSwift/Client/FlutterViewController.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,16 @@ public final class FlutterViewController {
9191
}
9292
}
9393

94-
public init?(properties viewProperties: ViewProperties, project: DartProject) {
94+
public init?(
95+
properties viewProperties: ViewProperties,
96+
project: DartProject,
97+
switches: [String: Any] = [:]
98+
) {
9599
var cViewProperties = FlutterDesktopViewProperties()
96100
var controller: FlutterDesktopViewControllerRef?
97101

98-
guard let engine = FlutterEngine(project: project) else { return nil }
102+
guard let engine = FlutterEngine(project: project, switches: switches)
103+
else { return nil }
99104
self.engine = engine
100105

101106
cViewProperties.width = viewProperties.width
@@ -145,7 +150,7 @@ public final class FlutterViewController {
145150

146151
public var view: FlutterView {
147152
didSet {
148-
FlutterDesktopEngineSetView(engine.engine, view.view)
153+
engine.setView(view)
149154
}
150155
}
151156

0 commit comments

Comments
 (0)