Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration options to App to choose reconciler #495

Merged
merged 14 commits into from
Jun 5, 2022
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,31 @@ This way both [Semantic UI](https://semantic-ui.com/) styles and [moment.js](htt
localized date formatting (or any arbitrary style/script/font added that way) are available in your
app.

### Fiber renderers

A new reconciler modeled after React's [Fiber reconciler](https://reactjs.org/docs/faq-internals.html#what-is-react-fiber)
is optionally available. It can provide faster updates and allow for larger View hierarchies.
It also includes layout steps that can match SwiftUI layouts closer than CSS approximations.

You can specify which reconciler to use in your `App`'s configuration:

```swift
struct CounterApp: App {
static let _configuration: _AppConfiguration = .init(
// Specify `useDynamicLayout` to enable the layout steps in place of CSS approximations.
reconciler: .fiber(useDynamicLayout: true)
)

var body: some Scene {
WindowGroup("Counter Demo") {
Counter(count: 5, limit: 15)
}
}
}
```

> *Note*: Not all `View`s and `ViewModifier`s are supported by Fiber renderers yet.

## Requirements

### For app developers
Expand Down
31 changes: 29 additions & 2 deletions Sources/TokamakCore/App/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,49 @@ public protocol App: _TitledApp {
var body: Body { get }

/// Implemented by the renderer to mount the `App`
static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues)
static func _launch(
_ app: Self,
with configuration: _AppConfiguration
)

/// Implemented by the renderer to update the `App` on `ScenePhase` changes
var _phasePublisher: AnyPublisher<ScenePhase, Never> { get }

/// Implemented by the renderer to update the `App` on `ColorScheme` changes
var _colorSchemePublisher: AnyPublisher<ColorScheme, Never> { get }

static var _configuration: _AppConfiguration { get }

static func main()

init()
}

public struct _AppConfiguration {
public let reconciler: Reconciler
public let rootEnvironment: EnvironmentValues

public init(
reconciler: Reconciler = .stack,
rootEnvironment: EnvironmentValues = .init()
) {
self.reconciler = reconciler
self.rootEnvironment = rootEnvironment
}

public enum Reconciler {
/// Use the `StackReconciler`.
case stack
/// Use the `FiberReconciler` with layout steps optionally enabled.
case fiber(useDynamicLayout: Bool = false)
}
}

public extension App {
static var _configuration: _AppConfiguration { .init() }

static func main() {
let app = Self()
_launch(app, EnvironmentValues())
_launch(app, with: Self._configuration)
}
}
15 changes: 15 additions & 0 deletions Sources/TokamakCore/App/Scenes/Scene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,23 @@ public protocol Scene {
// FIXME: If I put `@SceneBuilder` in front of this
// it fails to build with no useful error message.
var body: Self.Body { get }

/// Override the default implementation for `Scene`s with body types of `Never`
/// or in cases where the body would normally need to be type erased.
///
/// You can `visit(_:)` either another `Scene` or a `View` with a `SceneVisitor`
func _visitChildren<V: SceneVisitor>(_ visitor: V)
carson-katri marked this conversation as resolved.
Show resolved Hide resolved

/// Create `SceneOutputs`, including any modifications to the environment, preferences, or a custom
/// `LayoutComputer` from the `SceneInputs`.
///
/// > At the moment, `SceneInputs`/`SceneOutputs` are identical to `ViewInputs`/`ViewOutputs`.
static func _makeScene(_ inputs: SceneInputs<Self>) -> SceneOutputs
}

public typealias SceneInputs<S: Scene> = ViewInputs<S>
public typealias SceneOutputs = ViewOutputs

protocol TitledScene {
var title: Text? { get }
}
Expand Down
96 changes: 87 additions & 9 deletions Sources/TokamakCore/App/Scenes/SceneBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,30 @@ public extension SceneBuilder {
static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> some Scene where C0: Scene,
C1: Scene
{
_TupleScene((c0, c1), children: [_AnyScene(c0), _AnyScene(c1)])
_TupleScene(
(c0, c1),
children: [_AnyScene(c0), _AnyScene(c1)],
visit: {
$0.visit(c0)
$0.visit(c1)
}
)
}
}

public extension SceneBuilder {
static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> some Scene
where C0: Scene, C1: Scene, C2: Scene
{
_TupleScene((c0, c1, c2), children: [_AnyScene(c0), _AnyScene(c1), _AnyScene(c2)])
_TupleScene(
(c0, c1, c2),
children: [_AnyScene(c0), _AnyScene(c1), _AnyScene(c2)],
visit: {
$0.visit(c0)
$0.visit(c1)
$0.visit(c2)
}
)
}
}

Expand All @@ -50,7 +65,13 @@ public extension SceneBuilder {
) -> some Scene where C0: Scene, C1: Scene, C2: Scene, C3: Scene {
_TupleScene(
(c0, c1, c2, c3),
children: [_AnyScene(c0), _AnyScene(c1), _AnyScene(c2), _AnyScene(c3)]
children: [_AnyScene(c0), _AnyScene(c1), _AnyScene(c2), _AnyScene(c3)],
visit: {
$0.visit(c0)
$0.visit(c1)
$0.visit(c2)
$0.visit(c3)
}
)
}
}
Expand All @@ -65,7 +86,14 @@ public extension SceneBuilder {
) -> some Scene where C0: Scene, C1: Scene, C2: Scene, C3: Scene, C4: Scene {
_TupleScene(
(c0, c1, c2, c3, c4),
children: [_AnyScene(c0), _AnyScene(c1), _AnyScene(c2), _AnyScene(c3), _AnyScene(c4)]
children: [_AnyScene(c0), _AnyScene(c1), _AnyScene(c2), _AnyScene(c3), _AnyScene(c4)],
visit: {
$0.visit(c0)
$0.visit(c1)
$0.visit(c2)
$0.visit(c3)
$0.visit(c4)
}
)
}
}
Expand All @@ -90,7 +118,15 @@ public extension SceneBuilder {
_AnyScene(c3),
_AnyScene(c4),
_AnyScene(c5),
]
],
visit: {
$0.visit(c0)
$0.visit(c1)
$0.visit(c2)
$0.visit(c3)
$0.visit(c4)
$0.visit(c5)
}
)
}
}
Expand All @@ -117,7 +153,16 @@ public extension SceneBuilder {
_AnyScene(c4),
_AnyScene(c5),
_AnyScene(c6),
]
],
visit: {
$0.visit(c0)
$0.visit(c1)
$0.visit(c2)
$0.visit(c3)
$0.visit(c4)
$0.visit(c5)
$0.visit(c6)
}
)
}
}
Expand Down Expand Up @@ -146,7 +191,17 @@ public extension SceneBuilder {
_AnyScene(c5),
_AnyScene(c6),
_AnyScene(c7),
]
],
visit: {
$0.visit(c0)
$0.visit(c1)
$0.visit(c2)
$0.visit(c3)
$0.visit(c4)
$0.visit(c5)
$0.visit(c6)
$0.visit(c7)
}
)
}
}
Expand Down Expand Up @@ -177,7 +232,18 @@ public extension SceneBuilder {
_AnyScene(c6),
_AnyScene(c7),
_AnyScene(c8),
]
],
visit: {
$0.visit(c0)
$0.visit(c1)
$0.visit(c2)
$0.visit(c3)
$0.visit(c4)
$0.visit(c5)
$0.visit(c6)
$0.visit(c7)
$0.visit(c8)
}
)
}
}
Expand Down Expand Up @@ -210,7 +276,19 @@ public extension SceneBuilder {
_AnyScene(c7),
_AnyScene(c8),
_AnyScene(c9),
]
],
visit: {
$0.visit(c0)
$0.visit(c1)
$0.visit(c2)
$0.visit(c3)
$0.visit(c4)
$0.visit(c5)
$0.visit(c6)
$0.visit(c7)
$0.visit(c8)
$0.visit(c9)
}
)
}
}
4 changes: 4 additions & 0 deletions Sources/TokamakCore/App/Scenes/WindowGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,8 @@ public struct WindowGroup<Content>: Scene, TitledScene where Content: View {
// public init(_ titleKey: LocalizedStringKey,
// @ViewBuilder content: () -> Content) {
// }

public func _visitChildren<V>(_ visitor: V) where V: SceneVisitor {
visitor.visit(content)
}
}
6 changes: 5 additions & 1 deletion Sources/TokamakCore/App/_AnyApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public struct _AnyApp: App {
}

@_spi(TokamakCore)
public static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues) {
public static func _launch(_ app: Self, with configuration: _AppConfiguration) {
fatalError("`_AnyApp` cannot be launched. Access underlying `app` value.")
}

Expand All @@ -51,6 +51,10 @@ public struct _AnyApp: App {
fatalError("`title` cannot be set for `AnyApp`. Access underlying `app` value.")
}

public static var _configuration: _AppConfiguration {
fatalError("`configuration` cannot be set for `AnyApp`. Access underlying `app` value.")
}

@_spi(TokamakCore)
public var _phasePublisher: AnyPublisher<ScenePhase, Never> {
fatalError("`_AnyApp` cannot monitor scenePhase. Access underlying `app` value.")
Expand Down
10 changes: 8 additions & 2 deletions Sources/TokamakCore/App/_TupleScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@

struct _TupleScene<T>: Scene, GroupScene {
let value: T
var children: [_AnyScene]
let children: [_AnyScene]
let visit: (SceneVisitor) -> ()

init(_ value: T, children: [_AnyScene]) {
init(
_ value: T,
children: [_AnyScene],
visit: @escaping (SceneVisitor) -> ()
) {
self.value = value
self.children = children
self.visit = visit
}

var body: Never {
Expand Down
21 changes: 21 additions & 0 deletions Sources/TokamakCore/Fiber/App/AppVisitor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2022 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Carson Katri on 5/31/22.
//

/// A type that can visit an `App`.
public protocol AppVisitor: ViewVisitor {
func visit<A: App>(_ app: A)
}
Loading