Compare commits

...

4 Commits

Author SHA1 Message Date
Max Desiatov 73a00afc04
Remove irrelevant padding modifier 2021-02-14 21:23:54 +00:00
Max Desiatov 637fdec44c
Convert Environment/Object/Values to classes 2021-02-13 20:42:27 +00:00
Max Desiatov e05110825a
Remove irrelevant modifiers from reproduction code 2021-02-09 16:36:41 +00:00
Max Desiatov 1f48d27c88
Add `print` statements to reproduce the issue 2021-02-05 17:57:56 +00:00
16 changed files with 168 additions and 109 deletions

View File

@ -20,34 +20,41 @@
/// 2. `MountedHostView.update` when reconciling
///
protocol EnvironmentReader {
mutating func setContent(from values: EnvironmentValues)
// mutating func setContent(from values: EnvironmentValues)
var environment: EnvironmentValues! { get set }
}
@propertyWrapper public struct Environment<Value>: DynamicProperty {
@propertyWrapper public final class Environment<Value>: DynamicProperty {
enum Content {
case keyPath(KeyPath<EnvironmentValues, Value>)
case value(Value)
}
private var content: Content
var environment: EnvironmentValues!
private let keyPath: KeyPath<EnvironmentValues, Value>
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
content = .keyPath(keyPath)
// content = .keyPath(keyPath)
self.keyPath = keyPath
}
mutating func setContent(from values: EnvironmentValues) {
content = .value(values[keyPath: keyPath])
}
// mutating func setContent(from values: EnvironmentValues) {
// value = values[keyPath: keyPath]
// // content = .value(values[keyPath: keyPath])
// }
public var wrappedValue: Value {
switch content {
case let .value(value):
return value
case let .keyPath(keyPath):
guard let values = environment else {
// not bound to a view, return the default value.
return EnvironmentValues()[keyPath: keyPath]
}
return values[keyPath: keyPath]
// switch content {
// case let .value(value):
// return value
// case let .keyPath(keyPath):
// // not bound to a view, return the default value.
// return EnvironmentValues()[keyPath: keyPath]
// }
}
}

View File

@ -18,28 +18,28 @@ public protocol EnvironmentKey {
}
protocol EnvironmentModifier {
func modifyEnvironment(_ values: inout EnvironmentValues)
func modifyEnvironment(_ values: EnvironmentValues)
}
public struct _EnvironmentKeyWritingModifier<Value>: ViewModifier, EnvironmentModifier {
public let keyPath: WritableKeyPath<EnvironmentValues, Value>
public let keyPath: ReferenceWritableKeyPath<EnvironmentValues, Value>
public let value: Value
public init(keyPath: WritableKeyPath<EnvironmentValues, Value>, value: Value) {
public init(keyPath: ReferenceWritableKeyPath<EnvironmentValues, Value>, value: Value) {
self.keyPath = keyPath
self.value = value
}
public typealias Body = Never
func modifyEnvironment(_ values: inout EnvironmentValues) {
func modifyEnvironment(_ values: EnvironmentValues) {
values[keyPath: keyPath] = value
}
}
public extension View {
func environment<V>(
_ keyPath: WritableKeyPath<EnvironmentValues, V>,
_ keyPath: ReferenceWritableKeyPath<EnvironmentValues, V>,
_ value: V
) -> some View {
modifier(_EnvironmentKeyWritingModifier(keyPath: keyPath, value: value))

View File

@ -17,7 +17,7 @@
import CombineShim
@propertyWrapper public struct EnvironmentObject<ObjectType>: DynamicProperty
@propertyWrapper public final class EnvironmentObject<ObjectType>: DynamicProperty
where ObjectType: ObservableObject
{
@dynamicMemberLookup public struct Wrapper {
@ -36,11 +36,10 @@ import CombineShim
}
var _store: ObjectType?
var _seed: Int = 0
mutating func setContent(from values: EnvironmentValues) {
_store = values[ObjectIdentifier(ObjectType.self)]
}
// mutating func setContent(from values: EnvironmentValues) {
// _store = values[ObjectIdentifier(ObjectType.self)]
// }
public var wrappedValue: ObjectType {
guard let store = _store else { error() }
@ -63,10 +62,19 @@ import CombineShim
public init() {}
}
extension EnvironmentObject: ObservedProperty, EnvironmentReader {}
extension EnvironmentObject: ObservedProperty, EnvironmentReader {
var environment: EnvironmentValues! {
get {
fatalError()
}
set {
_store = newValue[ObjectIdentifier(ObjectType.self)]
}
}
}
extension ObservableObject {
static var environmentStore: WritableKeyPath<EnvironmentValues, Self?> {
static var environmentStore: ReferenceWritableKeyPath<EnvironmentValues, Self?> {
\.[ObjectIdentifier(self)]
}
}

View File

@ -14,7 +14,7 @@
import CombineShim
public struct EnvironmentValues: CustomStringConvertible {
public final class EnvironmentValues: CustomStringConvertible {
public var description: String {
"EnvironmentValues: \(values.count)"
}
@ -67,8 +67,8 @@ struct _EnvironmentValuesWritingModifier: ViewModifier, EnvironmentModifier {
content
}
func modifyEnvironment(_ values: inout EnvironmentValues) {
values = environmentValues
func modifyEnvironment(_ values: EnvironmentValues) {
// values = environmentValues
}
}

View File

@ -34,9 +34,18 @@ extension ModifiedContent: ModifierContainer {
}
extension ModifiedContent: EnvironmentReader where Modifier: EnvironmentReader {
mutating func setContent(from values: EnvironmentValues) {
modifier.setContent(from: values)
var environment: EnvironmentValues! {
get {
modifier.environment
}
set {
modifier.environment = newValue
}
}
// mutating func setContent(from values: EnvironmentValues) {
// modifier.setContent(from: values)
// }
}
extension ModifiedContent: View, ParentView where Content: View, Modifier: ViewModifier {

View File

@ -9,9 +9,9 @@ public struct _ShadowLayout: ViewModifier, EnvironmentReader {
content
}
mutating func setContent(from values: EnvironmentValues) {
environment = values
}
// mutating func setContent(from values: EnvironmentValues) {
// environment = values
// }
}
public extension View {

View File

@ -35,9 +35,9 @@ public struct _BackgroundModifier<Background>: ViewModifier, EnvironmentReader
}
}
mutating func setContent(from values: EnvironmentValues) {
environment = values
}
// mutating func setContent(from values: EnvironmentValues) {
// environment = values
// }
}
extension _BackgroundModifier: Equatable where Background: Equatable {
@ -78,9 +78,9 @@ public struct _OverlayModifier<Overlay>: ViewModifier, EnvironmentReader
}
}
mutating func setContent(from values: EnvironmentValues) {
environment = values
}
// mutating func setContent(from values: EnvironmentValues) {
// environment = values
// }
}
extension _OverlayModifier: Equatable where Overlay: Equatable {

View File

@ -80,7 +80,9 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> {
}
override func update(with reconciler: StackReconciler<R>) {
print("C.render")
let element = reconciler.render(compositeView: self)
print("C.recon")
reconciler.reconcile(
self,
with: element,
@ -91,5 +93,6 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> {
},
mountChild: { $0.makeMountedView(parentTarget, environmentValues, self) }
)
print("C.done")
}
}

View File

@ -120,12 +120,16 @@ public class MountedElement<R: Renderer> {
let type = element.type
switch element {
case .app:
print("case .app")
environmentValues.inject(into: &app.app, type)
case .scene:
print("case .scene")
environmentValues.inject(into: &scene.scene, type)
case .view:
print("case .view")
environmentValues.inject(into: &view.view, type)
}
print("updateEnvironment done")
}
func mount(
@ -159,34 +163,44 @@ public class MountedElement<R: Renderer> {
}
extension EnvironmentValues {
mutating func inject(into element: inout Any, _ type: Any.Type) {
func inject(into element: inout Any, _ type: Any.Type) {
print("Inj.typeInfo")
guard let info = typeInfo(of: type) else { return }
// Extract the view from the AnyView for modification, apply Environment changes:
if let container = element as? ModifierContainer {
container.environmentModifier?.modifyEnvironment(&self)
print("Inj.container")
container.environmentModifier?.modifyEnvironment(self)
}
// Inject @Environment values
// swiftlint:disable force_cast
// `DynamicProperty`s can have `@Environment` properties contained in them,
// so we have to inject into them as well.
for dynamicProp in info.properties.filter({ $0.type is DynamicProperty.Type }) {
guard let propInfo = typeInfo(of: dynamicProp.type) else { return }
var propWrapper = dynamicProp.get(from: element) as! DynamicProperty
for prop in propInfo.properties.filter({ $0.type is EnvironmentReader.Type }) {
var wrapper = prop.get(from: propWrapper) as! EnvironmentReader
wrapper.setContent(from: self)
prop.set(value: wrapper, on: &propWrapper)
}
dynamicProp.set(value: propWrapper, on: &element)
}
// Inject @Environment values. `DynamicProperty`s can have `@Environment` properties
// contained in them, so we have to inject into them as well.
// for dynamicProp in info.properties.filter({ $0.type is DynamicProperty.Type }) {
// guard let propInfo = typeInfo(of: dynamicProp.type) else { return }
// var propWrapper = dynamicProp.get(from: element) as! DynamicProperty
// print("Inj.prop.pi \(propInfo.properties.count)")
// for prop in propInfo.properties.filter({ $0.type is EnvironmentReader.Type }) {
// var wrapper = prop.get(from: propWrapper) as! EnvironmentReader
// print("*****Inj.prop.pi.prop.setC *****")
// wrapper.setContent(from: self)
// print("*****Inj.prop.pi.prop.set *****")
// prop.set(value: wrapper, on: &propWrapper)
// }
// print("***** Inj.prop.dynamic.set \(dynamicProp.type) *****")
// dynamicProp.set(value: propWrapper, on: &element)
// }
for prop in info.properties.filter({ $0.type is EnvironmentReader.Type }) {
var wrapper = prop.get(from: element) as! EnvironmentReader
wrapper.setContent(from: self)
prop.set(value: wrapper, on: &element)
print("***** Inj.prop.env.setC \(wrapper) *****")
wrapper.environment = self
// wrapper.setContent(from: self)
// print("***** Inj.prop.env.set \(prop.type) *****")
// prop.set(value: wrapper, on: &element)
}
// swiftlint:enable force_cast
print("Inj.done")
}
}
@ -214,7 +228,7 @@ extension TypeInfo {
// swiftlint:disable:next force_cast
var extractedDynamicProp = extracted as! DynamicProperty
extractedDynamicProp.update()
prop.set(value: extractedDynamicProp, on: &source)
// prop.set(value: extractedDynamicProp, on: &source)
}
return dynamicProps
}

View File

@ -83,15 +83,25 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
}
}
private var __mountedChild = 0
override func update(with reconciler: StackReconciler<R>) {
guard let target = target else { return }
let myStack = __stack
__stack += 1
print("Host.upEnv")
updateEnvironment()
target.view = view
print(
"Host.renderer?.update \(reconciler.renderer == nil ? "nil" : "\(Swift.type(of: reconciler.renderer!))")"
)
reconciler.renderer?.update(target: target, with: self)
var childrenViews = view.children
print("Host(\(mountedChildren.isEmpty),\(childrenViews.isEmpty)")
switch (mountedChildren.isEmpty, childrenViews.isEmpty) {
// if existing children present and new children array is empty
// then unmount all existing children
@ -113,10 +123,17 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
// run simple update
while let mountedChild = mountedChildren.first, let childView = childrenViews.first {
let newChild: MountedElement<R>
print(
"BEGIN todo: \(myStack).\(mountedChildren.count) \(childView.typeConstructorName == mountedChildren[0].view.typeConstructorName ? "a - \(childView.typeConstructorName)" : "b")"
)
__mountedChild += 1
if childView.typeConstructorName == mountedChildren[0].view.typeConstructorName {
mountedChild.environmentValues = environmentValues
mountedChild.view = childView
mountedChild.updateEnvironment()
print(
"update -> \(Swift.type(of: mountedChild)), recon -> \(Swift.type(of: reconciler)))"
)
mountedChild.update(with: reconciler)
newChild = mountedChild
} else {
@ -131,6 +148,8 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
newChildren.append(newChild)
mountedChildren.removeFirst()
childrenViews.removeFirst()
print("END mountedChild todo: \(myStack).\(mountedChildren.count)")
}
// more mounted views left than views were to be rendered:
@ -150,6 +169,7 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
}
}
__stack -= 1
mountedChildren = newChildren
// both arrays are empty, nothing to reconcile
@ -157,3 +177,5 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
}
}
}
private var __stack = 0

View File

@ -27,23 +27,26 @@ public struct PropertyInfo {
public let offset: Int
public let ownerType: Any.Type
public func set<TObject>(value: Any, on object: inout TObject) {
withValuePointer(of: &object) { pointer in
set(value: value, pointer: pointer)
}
}
// public func set<TObject>(value: Any, on object: inout TObject) {
// withValuePointer(of: &object) { pointer in
// set(value: value, pointer: pointer)
// }
// }
public func set(value: Any, on object: inout Any) {
withValuePointer(of: &object) { pointer in
set(value: value, pointer: pointer)
}
}
// public func set(value: Any, on object: inout Any) {
// withValuePointer(of: &object) { pointer in
// set(value: value, pointer: pointer)
// }
// }
private func set(value: Any, pointer: UnsafeMutableRawPointer) {
let valuePointer = pointer.advanced(by: offset)
let sets = setters(type: type)
sets.set(value: value, pointer: valuePointer)
}
// private func set(value: Any, pointer: UnsafeMutableRawPointer) {
// let valuePointer = pointer.advanced(by: offset)
// print(
// "using \(type) for the setter, actual type is \(Swift.type(of: value)), offset is \(offset)"
// )
// let sets = setters(type: type)
// sets.set(value: value, pointer: valuePointer)
// }
public func get(from object: Any) -> Any {
var object = object

View File

@ -50,8 +50,12 @@ public struct TypeInfo {
public func typeInfo(of type: Any.Type) -> TypeInfo? {
guard Kind(type: type) == .struct else {
print("TypeInfo: nil")
return nil
}
return StructMetadata(type: type).toTypeInfo()
print("TypeInfo: \(type)")
let result = StructMetadata(type: type).toTypeInfo()
print("TypeInfo.done")
return result
}

View File

@ -150,7 +150,7 @@ public final class StackReconciler<R: Renderer> {
storage.getter = { compositeElement.storage[id] }
guard var writableStorage = storage as? WritableValueStorage else {
return property.set(value: storage, on: &compositeElement[keyPath: bodyKeypath])
return // property.set(value: storage, on: &compositeElement[keyPath: bodyKeypath])
}
// Avoiding an indirect reference cycle here: this closure can be owned by callbacks
@ -160,7 +160,7 @@ public final class StackReconciler<R: Renderer> {
self?.queueStorageUpdate(for: element, id: id) { $0 = newValue }
}
property.set(value: writableStorage, on: &compositeElement[keyPath: bodyKeypath])
// property.set(value: writableStorage, on: &compositeElement[keyPath: bodyKeypath])
}
}
@ -206,27 +206,35 @@ public final class StackReconciler<R: Renderer> {
body bodyKeypath: ReferenceWritableKeyPath<MountedCompositeElement<R>, Any>,
result: KeyPath<MountedCompositeElement<R>, (Any) -> T>
) -> T {
print("SR.env")
compositeElement.updateEnvironment()
print("SR.type")
if let info = typeInfo(of: compositeElement.type) {
var stateIdx = 0
print("SR.dynamicProps")
let dynamicProps = info.dynamicProperties(
&compositeElement.environmentValues,
source: &compositeElement[keyPath: bodyKeypath]
)
print("SR.dynamicProps.done")
compositeElement.transientSubscriptions = []
for property in dynamicProps {
print("SR.begin")
// Setup state/subscriptions
if property.type is ValueStorage.Type {
print("SR.setup storage")
setupStorage(id: stateIdx, for: property, of: compositeElement, body: bodyKeypath)
stateIdx += 1
}
if property.type is ObservedProperty.Type {
print("SR.setup sub")
setupTransientSubscription(for: property, of: compositeElement, body: bodyKeypath)
}
print("SR.done")
}
}
print("SR.type.done")
return compositeElement[keyPath: result](compositeElement[keyPath: bodyKeypath])
}

View File

@ -22,7 +22,7 @@ import TokamakStaticHTML
extension EnvironmentValues {
/// Returns default settings for the DOM environment
static var defaultEnvironment: Self {
static var defaultEnvironment: EnvironmentValues {
var environment = EnvironmentValues()
// `.toggleStyle` property is internal

View File

@ -1,40 +1,21 @@
// Copyright 2020 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.
import TokamakShim
@available(OSX 10.16, iOS 14.0, *)
struct CustomScene: Scene {
@Environment(\.scenePhase) private var scenePhase
struct TokamakApp: App {
var body: some Scene {
print("In CustomScene.body scenePhase is \(scenePhase)")
return WindowGroup("Tokamak Demo") {
TokamakDemoView()
WindowGroup("Spooky Hanger") {
NavigationView {
List {
ForEach(["Item 1"], id: \.self) { childRow in
NavigationLink(
destination: Text(childRow)
) {
Text(childRow)
}
}
}
}
}
}
}
@available(OSX 10.16, iOS 14.0, *)
struct TokamakDemoApp: App {
var body: some Scene {
CustomScene()
}
}
// If @main was supported for executable Swift Packages,
// this would match SwiftUI 100%
if #available(OSX 10.16, iOS 14.0, *) {
TokamakDemoApp.main()
}
TokamakApp.main()

View File

@ -19,7 +19,7 @@ import TokamakCore
extension EnvironmentValues {
/// Returns default settings for the static HTML environment
static var defaultEnvironment: Self {
static var defaultEnvironment: EnvironmentValues {
var environment = EnvironmentValues()
environment[_ColorSchemeKey] = .light