Skip to content

Commit 8f4204e

Browse files
committed
Use Swift C++ interop to more closely integrate with Flutter engine
1 parent e5340a8 commit 8f4204e

File tree

14 files changed

+184
-104
lines changed

14 files changed

+184
-104
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
@@ -144,7 +144,11 @@ enum Counter {
144144
title: "Counter",
145145
appId: "com.example.counter"
146146
)
147-
let window = FlutterWindow(properties: viewProperties, project: dartProject)
147+
let window = FlutterWindow(
148+
properties: viewProperties,
149+
project: dartProject,
150+
enableImpeller: true
151+
)
148152
guard let window else {
149153
exit(2)
150154
}

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

@@ -235,10 +236,19 @@ packageDependencies += [
235236

236237
let FlutterELinuxBackend = FlutterELinuxBackendType.defaultBackend
237238

239+
let CxxIncludeDirs: [String] = [
240+
"\(SwiftRoot)/usr/include",
241+
"\(SwiftRoot)/usr/lib/swift",
242+
"/usr/include/drm",
243+
]
244+
245+
let CxxIncludeFlags = CxxIncludeDirs.flatMap { ["-I", $0] }
246+
238247
platformSwiftSettings += [
239248
.define("DISPLAY_BACKEND_TYPE_\(FlutterELinuxBackend.displayBackendType)"),
240249
.define("FLUTTER_TARGET_BACKEND_\(FlutterELinuxBackend.flutterTargetBackend)"),
241250
.interoperabilityMode(.Cxx),
251+
.unsafeFlags(CxxIncludeFlags),
242252
]
243253

244254
targets += [
@@ -404,7 +414,7 @@ targets += [
404414
.headerSearchPath("flutter-embedded-linux/src/third_party/rapidjson/include"),
405415
// FIXME: .cxxLanguageStandard breaks Foundation compile
406416
// FIXME: include path for swift/bridging.h
407-
.unsafeFlags(["-pthread", "-I", SwiftLibRoot, "-I", "/usr/include/drm", "-std=c++17"]),
417+
.unsafeFlags(["-pthread", "-std=c++17"] + CxxIncludeFlags),
408418
],
409419
linkerSettings: [
410420
// .unsafeFlags(["-pthread"]),
@@ -446,6 +456,8 @@ platformCxxSettings += [
446456
.headerSearchPath(
447457
"../CxxFlutterSwift/flutter-embedded-linux/src/flutter/shell/platform/common/client_wrapper/include"
448458
),
459+
.headerSearchPath("../CxxFlutterSwift/flutter-embedded-linux/src/third_party/rapidjson/include"),
460+
.unsafeFlags(CxxIncludeFlags),
449461
]
450462

451463
#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/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: flutter.FlutterELinuxTextureRegistrar! {
148+
engine.texture_registrar()
149+
}
119150
}
151+
120152
#endif

Sources/FlutterSwift/Client/FlutterPlugin.swift

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,25 @@ public final class FlutterDesktopPluginRegistrar: FlutterPluginRegistrar {
116116
) {
117117
self.engine = engine
118118
pluginKey = pluginName
119-
registrar = FlutterDesktopEngineGetPluginRegistrar(engine.engine, pluginName)
120-
FlutterDesktopPluginRegistrarSetDestructionHandlerBlock(registrar!) { _ in
121-
self.registrar = nil
119+
registrar = engine.getRegistrar(pluginName: pluginName)
120+
// FIXME: use std::function
121+
FlutterDesktopPluginRegistrarSetDestructionHandlerBlock(registrar!) { [self] _ in
122+
for (channel, detachFromEngine) in detachFromEngineCallbacks {
123+
try? channel.removeMessageHandler()
124+
detachFromEngine(self)
125+
}
126+
registrar = nil
122127
}
123128
}
124129

125130
public var binaryMessenger: FlutterBinaryMessenger? {
126131
guard let registrar else { return nil }
127-
return FlutterDesktopMessenger(
128-
messenger: FlutterDesktopPluginRegistrarGetMessenger(registrar)
129-
)
132+
return FlutterDesktopMessenger(messenger: registrar.pointee.engine.messenger())
130133
}
131134

132135
public var view: FlutterView? {
133136
guard let registrar else { return nil }
134-
let view = FlutterDesktopPluginRegistrarGetView(registrar)!
137+
let view = registrar.pointee.engine.view()!
135138
return FlutterView(view)
136139
}
137140

@@ -161,13 +164,6 @@ public final class FlutterDesktopPluginRegistrar: FlutterPluginRegistrar {
161164
}
162165
}
163166

164-
deinit {
165-
for (channel, detachFromEngine) in detachFromEngineCallbacks {
166-
try? channel.removeMessageHandler()
167-
detachFromEngine(self)
168-
}
169-
}
170-
171167
public func lookupKey(for asset: String) -> String? {
172168
guard let bundle = Bundle(path: engine.project.assetsPath) else {
173169
return nil

Sources/FlutterSwift/Client/FlutterTexture.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,23 @@ public enum FlutterTexture {
138138
}
139139

140140
public struct FlutterDesktopTextureRegistrar {
141-
private let registrar: FlutterDesktopTextureRegistrarRef
141+
private let registrar: flutter.FlutterELinuxTextureRegistrar
142+
143+
private var _handle: FlutterDesktopTextureRegistrarRef {
144+
unsafeBitCast(registrar, to: FlutterDesktopTextureRegistrarRef.self)
145+
}
142146

143147
public init(engine: FlutterEngine) {
144-
registrar = FlutterDesktopEngineGetTextureRegistrar(engine.engine)
148+
registrar = engine.textureRegistrar
145149
}
146150

147151
init?(plugin: FlutterDesktopPluginRegistrar) {
148152
guard let registrar = plugin.registrar else { return nil }
149-
self.registrar = FlutterDesktopRegistrarGetTextureRegistrar(registrar)
153+
let textureRegistrarHandle = FlutterDesktopRegistrarGetTextureRegistrar(registrar)
154+
self.registrar = unsafeBitCast(
155+
textureRegistrarHandle,
156+
to: flutter.FlutterELinuxTextureRegistrar.self
157+
)
150158
}
151159

152160
public func registerExternalTexture(_ texture: FlutterTexture) -> Int64 {
@@ -166,7 +174,7 @@ public struct FlutterDesktopTextureRegistrar {
166174
user_data: Unmanaged.passUnretained(config).toOpaque()
167175
)
168176
}
169-
return FlutterDesktopTextureRegistrarRegisterExternalTexture(registrar, &textureInfo)
177+
return registrar.RegisterTexture(&textureInfo)
170178
}
171179

172180
public func unregisterExternalTexture(texture: FlutterTexture, id textureID: Int64) {
@@ -177,16 +185,17 @@ public struct FlutterDesktopTextureRegistrar {
177185
case let .eglImageTexture(config): texturePtr = Unmanaged.passUnretained(config).toOpaque()
178186
}
179187

188+
// FIXME: use std::function
180189
FlutterDesktopTextureRegistrarUnregisterExternalTexture(
181-
registrar,
190+
_handle,
182191
textureID,
183192
_releaseAnyObject,
184193
texturePtr
185194
)
186195
}
187196

188197
public func markExternalTextureFrameAvailable(textureID: Int64) {
189-
FlutterDesktopTextureRegistrarMarkExternalTextureFrameAvailable(registrar, textureID)
198+
registrar.MarkTextureFrameAvailable(textureID)
190199
}
191200
}
192201

0 commit comments

Comments
 (0)