Skip to content

Commit

Permalink
Use Swift C++ interop to more closely integrate with Flutter engine
Browse files Browse the repository at this point in the history
  • Loading branch information
lhoward committed Mar 4, 2025
1 parent 16b5cf8 commit f56e538
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,4 @@
import FlutterMacOS
import Foundation


func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
}
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {}
6 changes: 5 additions & 1 deletion Examples/counter/swift/runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,11 @@ enum Counter {
title: "Counter",
appId: "com.example.counter"
)
let window = FlutterWindow(properties: viewProperties, project: dartProject)
let window = FlutterWindow(
properties: viewProperties,
project: dartProject,
enableImpeller: true
)
guard let window else {
exit(2)
}
Expand Down
22 changes: 17 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var targetPluginUsages = [Target.PluginUsage]()
var platformCxxSettings: [CXXSetting] = []
var platformSwiftSettings: [SwiftSetting] = [.swiftLanguageMode(.v5)]

func tryGuessSwiftLibRoot() -> String {
func tryGuessSwiftRoot() -> String {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/bin/sh")
task.arguments = ["-c", "which swift"]
Expand All @@ -22,13 +22,14 @@ func tryGuessSwiftLibRoot() -> String {
try task.run()
let outputData = (task.standardOutput as! Pipe).fileHandleForReading.readDataToEndOfFile()
let path = URL(fileURLWithPath: String(decoding: outputData, as: UTF8.self))
return path.deletingLastPathComponent().path + "/../lib/swift"
return path.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
.path
} catch {
return "/usr/lib/swift"
return ""
}
}

let SwiftLibRoot = tryGuessSwiftLibRoot()
let SwiftRoot = tryGuessSwiftRoot()
var FlutterPlatform: String
var FlutterUnsafeLinkerFlags: [String] = []

Expand Down Expand Up @@ -235,10 +236,19 @@ packageDependencies += [

let FlutterELinuxBackend = FlutterELinuxBackendType.defaultBackend

let CxxIncludeDirs: [String] = [
"\(SwiftRoot)/usr/include",
"\(SwiftRoot)/usr/lib/swift",
"/usr/include/drm",
]

let CxxIncludeFlags = CxxIncludeDirs.flatMap { ["-I", $0] }

platformSwiftSettings += [
.define("DISPLAY_BACKEND_TYPE_\(FlutterELinuxBackend.displayBackendType)"),
.define("FLUTTER_TARGET_BACKEND_\(FlutterELinuxBackend.flutterTargetBackend)"),
.interoperabilityMode(.Cxx),
.unsafeFlags(CxxIncludeFlags),
]

targets += [
Expand Down Expand Up @@ -404,7 +414,7 @@ targets += [
.headerSearchPath("flutter-embedded-linux/src/third_party/rapidjson/include"),
// FIXME: .cxxLanguageStandard breaks Foundation compile
// FIXME: include path for swift/bridging.h
.unsafeFlags(["-pthread", "-I", SwiftLibRoot, "-I", "/usr/include/drm", "-std=c++17"]),
.unsafeFlags(["-pthread", "-std=c++17"] + CxxIncludeFlags),
],
linkerSettings: [
// .unsafeFlags(["-pthread"]),
Expand Down Expand Up @@ -446,6 +456,8 @@ platformCxxSettings += [
.headerSearchPath(
"../CxxFlutterSwift/flutter-embedded-linux/src/flutter/shell/platform/common/client_wrapper/include"
),
.headerSearchPath("../CxxFlutterSwift/flutter-embedded-linux/src/third_party/rapidjson/include"),
.unsafeFlags(CxxIncludeFlags),
]

#else
Expand Down
11 changes: 9 additions & 2 deletions Sources/CxxFlutterSwift/include/CxxFlutterSwift.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2023-2024 PADL Software Pty Ltd
// Copyright (c) 2023-2025 PADL Software Pty Ltd
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand All @@ -21,11 +21,18 @@

#include <flutter_messenger.h>
#include <flutter_elinux.h>
#include <flutter_elinux_engine.h>
#include <flutter_elinux_state.h>
#include <flutter_elinux_view.h>
#include <flutter_plugin_registrar.h>
// #include <flutter_elinux_state.h>
#include <flutter_platform_views.h>

#ifdef __cplusplus
#include <vector>
#include <string>

using CxxVectorOfString = std::vector<std::string, std::allocator<std::string>>;

extern "C" {
#endif

Expand Down
3 changes: 3 additions & 0 deletions Sources/CxxFlutterSwift/include/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module CxxFlutterSwift {
header "CxxFlutterSwift.h"
}
19 changes: 18 additions & 1 deletion Sources/FlutterSwift/Base/StringHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
//
//===----------------------------------------------------------------------===//

#if os(Linux)
#if os(Linux) && canImport(Glibc)

@_implementationOnly
import CxxFlutterSwift
import CxxStdlib

/// Compute the prefix sum of `seq`.
func scan<
S: Sequence, U
Expand Down Expand Up @@ -60,4 +65,16 @@ extension String {
return u32.withUnsafeBufferPointer { body($0.baseAddress!) }
}
}

extension Array where Element == String {
var cxxVector: CxxVectorOfString {
var tmp = CxxVectorOfString()

for element in self {
tmp.push_back(std.string(element))
}

return tmp
}
}
#endif
72 changes: 52 additions & 20 deletions Sources/FlutterSwift/Client/FlutterEngine.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2023-2024 PADL Software Pty Ltd
// Copyright (c) 2023-2025 PADL Software Pty Ltd
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand All @@ -17,17 +17,18 @@
#if os(Linux) && canImport(Glibc)
@_implementationOnly
import CxxFlutterSwift
import CxxStdlib

public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
var engine: FlutterDesktopEngineRef! // strong or weak ref
var pluginPublications = [String: Any]()
let project: DartProject
weak var viewController: FlutterViewController?
private var engine: flutter.FlutterELinuxEngine! // strong or weak ref
private var _binaryMessenger: FlutterDesktopMessenger!
private var ownsEngine = true
private var hasBeenRun = false

public init?(project: DartProject) {
public init?(project: DartProject, switches: [String: Any] = [:]) {
var properties = FlutterDesktopEngineProperties()

debugPrint("Initializing Flutter engine with \(project)")
Expand All @@ -48,7 +49,9 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
)
cStrings.withUnsafeMutableBufferPointer { pointer in
properties.dart_entrypoint_argv = pointer.baseAddress
self.engine = FlutterDesktopEngineCreate(&properties)
let engine = FlutterDesktopEngineCreate(&properties)
self.engine = unsafeBitCast(engine, to: flutter.FlutterELinuxEngine.self)
setSwitches(switches.map { key, value in "--\(key)=\(String(describing: value))" })
self._binaryMessenger = FlutterDesktopMessenger(engine: self.engine)
}
}
Expand All @@ -61,6 +64,10 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
shutDown()
}

private var _handle: FlutterDesktopEngineRef {
unsafeBitCast(engine, to: FlutterDesktopEngineRef.self)
}

// note we can't use public private(set) because we need the type to be FlutterDesktopMessenger!
// in order for callbacks to work (otherwise self must be first initialized). But we want to
// present a non-optional type to callers.
Expand All @@ -69,39 +76,31 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
}

public func run(entryPoint: String? = nil) -> Bool {
if hasBeenRun {
debugPrint("Cannot run an engine more than once.")
return false
}
let runSucceeded = FlutterDesktopEngineRun(engine, entryPoint)
if !runSucceeded {
debugPrint("Failed to start engine.")
}
hasBeenRun = true
return runSucceeded
guard !hasBeenRun else { return false }
hasBeenRun = engine.RunWithEntrypoint(entryPoint)
return hasBeenRun
}

public func shutDown() {
pluginPublications.removeAll()
if let engine, ownsEngine {
FlutterDesktopEngineDestroy(engine)
if engine != nil, ownsEngine {
FlutterDesktopEngineDestroy(_handle)
}
engine = nil
}

public func processMessages() -> UInt64 {
precondition(engine != nil)
return FlutterDesktopEngineProcessMessages(engine)
return FlutterDesktopEngineProcessMessages(_handle)
}

public func reloadSystemFonts() {
precondition(engine != nil)
FlutterDesktopEngineReloadSystemFonts(engine)
engine.ReloadSystemFonts()
}

func relinquishEngine() -> FlutterDesktopEngineRef {
ownsEngine = false
return engine
return _handle
}

public func registrar(for pluginKey: String) -> FlutterPluginRegistrar? {
Expand All @@ -116,5 +115,38 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
public func valuePublished(by pluginKey: String) -> Any? {
pluginPublications[pluginKey]
}

public var isRunning: Bool {
engine.running()
}

func onVsync(lastFrameTimeNS: UInt64, vsyncIntervalTimeNS: UInt64) {
engine.OnVsync(lastFrameTimeNS, vsyncIntervalTimeNS)
}

public var isImpellerEnabled: Bool {
engine.IsImpellerEnabled()
}

public func setSystemSettings(textScalingFactor: Float, enableHighContrast: Bool) {
engine.SetSystemSettings(textScalingFactor, enableHighContrast)
}

public func setView(_ view: FlutterView) {
engine.SetView(view.view)
}

public func setSwitches(_ switches: [String]) {
engine.SetSwitches(switches.cxxVector)
}

func getRegistrar(pluginName: String) -> FlutterDesktopPluginRegistrarRef? {
FlutterDesktopEngineGetPluginRegistrar(_handle, pluginName)
}

var textureRegistrar: FlutterDesktopTextureRegistrarRef {
FlutterDesktopEngineGetTextureRegistrar(_handle)
}
}

#endif
2 changes: 1 addition & 1 deletion Sources/FlutterSwift/Client/FlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public final class FlutterDesktopPluginRegistrar: FlutterPluginRegistrar {
) {
self.engine = engine
pluginKey = pluginName
registrar = FlutterDesktopEngineGetPluginRegistrar(engine.engine, pluginName)
registrar = engine.getRegistrar(pluginName: pluginName)
FlutterDesktopPluginRegistrarSetDestructionHandlerBlock(registrar!) { _ in
self.registrar = nil
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/FlutterSwift/Client/FlutterTexture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public struct FlutterDesktopTextureRegistrar {
private let registrar: FlutterDesktopTextureRegistrarRef

public init(engine: FlutterEngine) {
registrar = FlutterDesktopEngineGetTextureRegistrar(engine.engine)
registrar = engine.textureRegistrar
}

init?(plugin: FlutterDesktopPluginRegistrar) {
Expand Down
14 changes: 9 additions & 5 deletions Sources/FlutterSwift/Client/FlutterView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2023-2024 PADL Software Pty Ltd
// Copyright (c) 2023-2025 PADL Software Pty Ltd
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand All @@ -21,7 +21,7 @@ import CxxFlutterSwift
let kChannelName = "flutter/platform_views"

public struct FlutterView {
let view: FlutterDesktopViewRef
let view: flutter.FlutterELinuxView
var platformViewsPluginRegistrar: FlutterPluginRegistrar?
var platformViewsHandler: FlutterPlatformViewsPlugin?
var viewController: FlutterViewController? {
Expand All @@ -38,16 +38,20 @@ public struct FlutterView {
}
}

init(_ view: FlutterDesktopViewRef) {
init(_ view: flutter.FlutterELinuxView) {
self.view = view
}

init(_ view: FlutterDesktopViewRef) {
self.init(unsafeBitCast(view, to: flutter.FlutterELinuxView.self))
}

public func dispatchEvent() -> Bool {
FlutterDesktopViewDispatchEvent(view)
view.DispatchEvent()
}

public var frameRate: Int32 {
FlutterDesktopViewGetFrameRate(view)
view.GetFrameRate()
}
}
#endif
11 changes: 8 additions & 3 deletions Sources/FlutterSwift/Client/FlutterViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,16 @@ public final class FlutterViewController {
}
}

public init?(properties viewProperties: ViewProperties, project: DartProject) {
public init?(
properties viewProperties: ViewProperties,
project: DartProject,
switches: [String: Any] = [:]
) {
var cViewProperties = FlutterDesktopViewProperties()
var controller: FlutterDesktopViewControllerRef?

guard let engine = FlutterEngine(project: project) else { return nil }
guard let engine = FlutterEngine(project: project, switches: switches)
else { return nil }
self.engine = engine

cViewProperties.width = viewProperties.width
Expand Down Expand Up @@ -145,7 +150,7 @@ public final class FlutterViewController {

public var view: FlutterView {
didSet {
FlutterDesktopEngineSetView(engine.engine, view.view)
engine.setView(view)
}
}

Expand Down
10 changes: 8 additions & 2 deletions Sources/FlutterSwift/Client/FlutterWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@ public struct FlutterWindow {

public init?(
properties viewProperties: FlutterViewController.ViewProperties,
project: DartProject
project: DartProject,
enableImpeller: Bool = false
) {
var switches = [String: Any]()
if enableImpeller {
switches["enable-impeller"] = true
}
guard let viewController = FlutterViewController(
properties: viewProperties,
project: project
project: project,
switches: switches
) else {
return nil
}
Expand Down
Loading

0 comments on commit f56e538

Please sign in to comment.