Improve performance of state system
This commit is contained in:
parent
4b9ea1e883
commit
5a59a01980
@ -1,4 +1,4 @@
|
|||||||
// swift-tools-version: 5.8
|
// swift-tools-version: 5.10
|
||||||
//
|
//
|
||||||
// Package.swift
|
// Package.swift
|
||||||
// Meta
|
// Meta
|
||||||
@ -11,6 +11,10 @@ import PackageDescription
|
|||||||
/// The Meta package is the foundation of the Aparoksha project.
|
/// The Meta package is the foundation of the Aparoksha project.
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Meta",
|
name: "Meta",
|
||||||
|
platforms: [
|
||||||
|
.macOS(.v10_15),
|
||||||
|
.iOS(.v13)
|
||||||
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(
|
.library(
|
||||||
name: "Meta",
|
name: "Meta",
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Observation
|
||||||
|
|
||||||
/// A property wrapper for properties in a view that should be stored throughout view updates.
|
/// A property wrapper for properties in a view that should be stored throughout view updates.
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
@ -18,8 +19,8 @@ public struct State<Value>: StateProtocol {
|
|||||||
}
|
}
|
||||||
nonmutating set {
|
nonmutating set {
|
||||||
rawValue = newValue
|
rawValue = newValue
|
||||||
content.storage.update = true
|
StateManager.updateState(id: id)
|
||||||
UpdateManager.updateViews(force: forceUpdates)
|
StateManager.updateViews(force: forceUpdates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,46 +33,45 @@ public struct State<Value>: StateProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable force_cast
|
|
||||||
/// Get and set the value without updating the views.
|
/// Get and set the value without updating the views.
|
||||||
public var rawValue: Value {
|
public var rawValue: Value {
|
||||||
get {
|
get {
|
||||||
content.storage.value as! Value
|
guard let value = StateManager.getState(id: id) as? Value else {
|
||||||
|
let initialValue = getInitialValue()
|
||||||
|
StateManager.setState(id: id, value: initialValue)
|
||||||
|
return initialValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
nonmutating set {
|
nonmutating set {
|
||||||
content.storage.value = newValue
|
StateManager.setState(id: id, value: newValue)
|
||||||
writeValue?(newValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// swiftlint:enable force_cast
|
|
||||||
|
|
||||||
/// The stored value.
|
/// Whether the value is an observable object.
|
||||||
let content: StateContent
|
var isObservable: Bool {
|
||||||
|
if #available(macOS 14, *), #available(iOS 17, *) {
|
||||||
|
return Value.self as? Observable.Type != nil
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The state's identifier for the stored value.
|
||||||
|
var id: UUID = .init()
|
||||||
|
|
||||||
/// Whether to force update the views when the value changes.
|
/// Whether to force update the views when the value changes.
|
||||||
public var forceUpdates: Bool
|
var forceUpdates: Bool
|
||||||
|
|
||||||
/// The function for updating the value in the settings file.
|
/// The closure for initializing the state property's value.
|
||||||
private var writeValue: ((Value) -> Void)?
|
var getInitialValue: () -> Value
|
||||||
|
|
||||||
/// The value with an erased type.
|
|
||||||
public var value: Any {
|
|
||||||
get {
|
|
||||||
wrappedValue
|
|
||||||
}
|
|
||||||
nonmutating set {
|
|
||||||
if let newValue = newValue as? Value {
|
|
||||||
content.storage.value = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initialize a property representing a state in the view with an autoclosure.
|
/// Initialize a property representing a state in the view with an autoclosure.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - wrappedValue: The wrapped value.
|
/// - wrappedValue: The wrapped value.
|
||||||
/// - forceUpdates: Whether to force update all available views when the property gets modified.
|
/// - forceUpdates: Whether to force update all available views when the property gets modified.
|
||||||
public init(wrappedValue: @autoclosure @escaping () -> Value, forceUpdates: Bool = false) {
|
public init(wrappedValue: @autoclosure @escaping () -> Value, forceUpdates: Bool = false) {
|
||||||
content = .init(getInitialValue: wrappedValue)
|
getInitialValue = wrappedValue
|
||||||
self.forceUpdates = forceUpdates
|
self.forceUpdates = forceUpdates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
106
Sources/Model/Data Flow/StateManager.swift
Normal file
106
Sources/Model/Data Flow/StateManager.swift
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
//
|
||||||
|
// StateManager.swift
|
||||||
|
// Meta
|
||||||
|
//
|
||||||
|
// Created by david-swift on 21.06.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// This type manages view updates.
|
||||||
|
public enum StateManager {
|
||||||
|
|
||||||
|
/// Whether to block updates in general.
|
||||||
|
public static var blockUpdates = false
|
||||||
|
/// Whether to save state.
|
||||||
|
public static var saveState = true
|
||||||
|
/// The functions handling view updates.
|
||||||
|
static var updateHandlers: [(Bool) -> Void] = []
|
||||||
|
/// The state.
|
||||||
|
static var state: [State] = []
|
||||||
|
|
||||||
|
/// Information about a piece of state.
|
||||||
|
struct State {
|
||||||
|
|
||||||
|
/// The state's identifiers.
|
||||||
|
var ids: [UUID]
|
||||||
|
/// The state value.
|
||||||
|
var value: Any?
|
||||||
|
/// Whether to update in the next iteration.
|
||||||
|
var update = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update all of the views.
|
||||||
|
/// - Parameter force: Whether to force all views to update.
|
||||||
|
///
|
||||||
|
/// Nothing happens if ``UpdateManager/blockUpdates`` is true.
|
||||||
|
static func updateViews(force: Bool = false) {
|
||||||
|
if !blockUpdates {
|
||||||
|
for handler in updateHandlers {
|
||||||
|
handler(force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a handler that is called when the user interface should update.
|
||||||
|
/// - Parameter handler: The handler. The parameter defines whether the whole UI should be force updated.
|
||||||
|
public static func addUpdateHandler(handler: @escaping (Bool) -> Void) {
|
||||||
|
updateHandlers.append(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the state value for a certain ID.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - id: The identifier.
|
||||||
|
/// - value: The new value.
|
||||||
|
static func setState(id: UUID, value: Any?) {
|
||||||
|
if saveState {
|
||||||
|
guard let index = state.firstIndex(where: { $0.ids.contains(id) }) else {
|
||||||
|
state.append(.init(ids: [id], value: value))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state[safe: index]?.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the state value for a certain ID.
|
||||||
|
/// - Parameter id: The identifier.
|
||||||
|
/// - Returns: The value.
|
||||||
|
static func getState(id: UUID) -> Any? {
|
||||||
|
state[safe: state.firstIndex { $0.ids.contains(id) }]?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the state of a certain id as updated.
|
||||||
|
/// - Parameter id: The identifier.
|
||||||
|
static func updateState(id: UUID) {
|
||||||
|
if saveState {
|
||||||
|
state[safe: state.firstIndex { $0.ids.contains(id) }]?.update = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the state of a certain id as not updated.
|
||||||
|
/// - Parameter id: The identifier.
|
||||||
|
static func updatedState(id: UUID) {
|
||||||
|
if saveState {
|
||||||
|
state[safe: state.firstIndex { $0.ids.contains(id) }]?.update = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get whether to update the state of a certain id.
|
||||||
|
/// - Parameter id: The identifier.
|
||||||
|
/// - Returns: Whether to update the state.
|
||||||
|
static func getUpdateState(id: UUID) -> Bool {
|
||||||
|
state[safe: state.firstIndex { $0.ids.contains(id) }]?.update ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the identifier for a certain state value.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - oldID: The old identifier.
|
||||||
|
/// - newID: The new identifier.
|
||||||
|
static func changeID(old oldID: UUID, new newID: UUID) {
|
||||||
|
if saveState {
|
||||||
|
state[safe: state.firstIndex { $0.ids.contains(oldID) }]?.ids.append(newID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,10 +5,14 @@
|
|||||||
// Created by david-swift on 26.05.24.
|
// Created by david-swift on 26.05.24.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
/// An interface for accessing `State` without specifying the generic type.
|
/// An interface for accessing `State` without specifying the generic type.
|
||||||
protocol StateProtocol {
|
protocol StateProtocol {
|
||||||
|
|
||||||
/// The `StateContent`.
|
/// The identifier for the state property's value.
|
||||||
var content: StateContent { get }
|
var id: UUID { get set }
|
||||||
|
/// Whether the state value is an observable object.
|
||||||
|
var isObservable: Bool { get }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
//
|
|
||||||
// UpdateManager.swift
|
|
||||||
// Meta
|
|
||||||
//
|
|
||||||
// Created by david-swift on 26.05.24.
|
|
||||||
//
|
|
||||||
|
|
||||||
/// This type manages view updates.
|
|
||||||
public enum UpdateManager {
|
|
||||||
|
|
||||||
/// Whether to block updates in general.
|
|
||||||
public static var blockUpdates = false
|
|
||||||
/// The functions handling view updates.
|
|
||||||
static var updateHandlers: [(Bool) -> Void] = []
|
|
||||||
|
|
||||||
/// Update all of the views.
|
|
||||||
/// - Parameter force: Whether to force all views to update.
|
|
||||||
///
|
|
||||||
/// Nothing happens if ``UpdateManager/blockUpdates`` is true.
|
|
||||||
static func updateViews(force: Bool = false) {
|
|
||||||
if !blockUpdates {
|
|
||||||
for handler in updateHandlers {
|
|
||||||
handler(force)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a handler that is called when the user interface should update.
|
|
||||||
/// - Parameter handler: The handler. The parameter defines whether the whole UI should be force updated.
|
|
||||||
public static func addUpdateHandler(handler: @escaping (Bool) -> Void) {
|
|
||||||
updateHandlers.append(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -20,6 +20,8 @@ extension Array: AnyView where Element == AnyView {
|
|||||||
type: WidgetType.Type,
|
type: WidgetType.Type,
|
||||||
modifiers: [(AnyView) -> AnyView] = []
|
modifiers: [(AnyView) -> AnyView] = []
|
||||||
) -> String {
|
) -> String {
|
||||||
|
let oldValue = StateManager.saveState
|
||||||
|
StateManager.saveState = false
|
||||||
var description = ""
|
var description = ""
|
||||||
for view in self where view.renderable(type: type, modifiers: modifiers) {
|
for view in self where view.renderable(type: type, modifiers: modifiers) {
|
||||||
description += view.getDebugTree(parameters: parameters, type: type, modifiers: modifiers) + "\n"
|
description += view.getDebugTree(parameters: parameters, type: type, modifiers: modifiers) + "\n"
|
||||||
@ -27,6 +29,7 @@ extension Array: AnyView where Element == AnyView {
|
|||||||
if !description.isEmpty {
|
if !description.isEmpty {
|
||||||
description.removeLast()
|
description.removeLast()
|
||||||
}
|
}
|
||||||
|
StateManager.saveState = oldValue
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,12 +26,17 @@ extension AnyView {
|
|||||||
type: WidgetType.Type,
|
type: WidgetType.Type,
|
||||||
modifiers: [(AnyView) -> AnyView] = []
|
modifiers: [(AnyView) -> AnyView] = []
|
||||||
) -> String {
|
) -> String {
|
||||||
|
let oldValue = StateManager.saveState
|
||||||
|
StateManager.saveState = false
|
||||||
|
defer {
|
||||||
|
StateManager.saveState = oldValue
|
||||||
|
}
|
||||||
if let body = getModified(modifiers: modifiers) as? Body {
|
if let body = getModified(modifiers: modifiers) as? Body {
|
||||||
return body.getBodyDebugTree(parameters: parameters, type: type, modifiers: modifiers)
|
return body.getBodyDebugTree(parameters: parameters, type: type, modifiers: modifiers)
|
||||||
} else if let widget = getModified(modifiers: modifiers) as? Widget {
|
} else if let widget = getModified(modifiers: modifiers) as? Widget {
|
||||||
return widget.getViewDescription(parameters: parameters, type: type, modifiers: modifiers)
|
return widget.getViewDescription(parameters: parameters, type: type, modifiers: modifiers)
|
||||||
}
|
}
|
||||||
return """
|
let string = """
|
||||||
\(Self.self) {
|
\(Self.self) {
|
||||||
\(indented: viewContent
|
\(indented: viewContent
|
||||||
.map { view in
|
.map { view in
|
||||||
@ -40,6 +45,7 @@ extension AnyView {
|
|||||||
.getBodyDebugTree(parameters: parameters, type: type, modifiers: modifiers))
|
.getBodyDebugTree(parameters: parameters, type: type, modifiers: modifiers))
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getModified(modifiers: [(AnyView) -> AnyView]) -> AnyView {
|
func getModified(modifiers: [(AnyView) -> AnyView]) -> AnyView {
|
||||||
|
|||||||
@ -43,6 +43,11 @@ extension Widget {
|
|||||||
type: WidgetType.Type,
|
type: WidgetType.Type,
|
||||||
modifiers: [(AnyView) -> AnyView]
|
modifiers: [(AnyView) -> AnyView]
|
||||||
) -> String {
|
) -> String {
|
||||||
|
let oldValue = StateManager.saveState
|
||||||
|
StateManager.saveState = false
|
||||||
|
defer {
|
||||||
|
StateManager.saveState = oldValue
|
||||||
|
}
|
||||||
var content = ""
|
var content = ""
|
||||||
for element in debugTreeContent {
|
for element in debugTreeContent {
|
||||||
if content.isEmpty {
|
if content.isEmpty {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import Observation
|
|||||||
public struct StateWrapper: ConvenienceWidget {
|
public struct StateWrapper: ConvenienceWidget {
|
||||||
|
|
||||||
/// The content.
|
/// The content.
|
||||||
var content: Body
|
var content: () -> Body
|
||||||
/// The state information (from properties with the `State` wrapper).
|
/// The state information (from properties with the `State` wrapper).
|
||||||
var state: [String: StateProtocol] = [:]
|
var state: [String: StateProtocol] = [:]
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ public struct StateWrapper: ConvenienceWidget {
|
|||||||
|
|
||||||
/// The debug tree's content.
|
/// The debug tree's content.
|
||||||
public var debugTreeContent: [(String, body: Body)] {
|
public var debugTreeContent: [(String, body: Body)] {
|
||||||
[("content", body: content)]
|
[("content", body: content())]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The identifier of the field storing whether to update the wrapper's content.
|
/// The identifier of the field storing whether to update the wrapper's content.
|
||||||
@ -33,7 +33,7 @@ public struct StateWrapper: ConvenienceWidget {
|
|||||||
/// Initialize a `StateWrapper`.
|
/// Initialize a `StateWrapper`.
|
||||||
/// - Parameter content: The view content.
|
/// - Parameter content: The view content.
|
||||||
public init(@ViewBuilder content: @escaping () -> Body) {
|
public init(@ViewBuilder content: @escaping () -> Body) {
|
||||||
self.content = content()
|
self.content = content
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a `StateWrapper`.
|
/// Initialize a `StateWrapper`.
|
||||||
@ -41,7 +41,7 @@ public struct StateWrapper: ConvenienceWidget {
|
|||||||
/// - content: The view content.
|
/// - content: The view content.
|
||||||
/// - state: The state information.
|
/// - state: The state information.
|
||||||
init(content: @escaping () -> Body, state: [String: StateProtocol]) {
|
init(content: @escaping () -> Body, state: [String: StateProtocol]) {
|
||||||
self.content = content()
|
self.content = content
|
||||||
self.state = state
|
self.state = state
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,18 +60,19 @@ public struct StateWrapper: ConvenienceWidget {
|
|||||||
var updateProperties = storage.fields[updateID] as? Bool ?? false
|
var updateProperties = storage.fields[updateID] as? Bool ?? false
|
||||||
storage.fields[updateID] = false
|
storage.fields[updateID] = false
|
||||||
for property in state {
|
for property in state {
|
||||||
if let storage = storage.state[property.key]?.content.storage {
|
if let oldID = storage.state[property.key]?.id {
|
||||||
property.value.content.storage = storage
|
StateManager.changeID(old: oldID, new: property.value.id)
|
||||||
|
storage.state[property.key]?.id = property.value.id
|
||||||
}
|
}
|
||||||
if property.value.content.storage.update {
|
if StateManager.getUpdateState(id: property.value.id) {
|
||||||
updateProperties = true
|
updateProperties = true
|
||||||
property.value.content.storage.update = false
|
StateManager.updatedState(id: property.value.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guard let storages = storage.content[.mainContent] else {
|
guard let storages = storage.content[.mainContent] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content.update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
content().update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a view storage.
|
/// Get a view storage.
|
||||||
@ -80,22 +81,28 @@ public struct StateWrapper: ConvenienceWidget {
|
|||||||
/// - type: The type of the widgets.
|
/// - type: The type of the widgets.
|
||||||
/// - Returns: The view storage.
|
/// - Returns: The view storage.
|
||||||
public func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage {
|
public func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage {
|
||||||
let content = content.storages(modifiers: modifiers, type: type)
|
let content = content().storages(modifiers: modifiers, type: type)
|
||||||
let storage = ViewStorage(nil, content: [.mainContent: content])
|
let storage = ViewStorage(nil, content: [.mainContent: content])
|
||||||
storage.state = state
|
storage.state = state
|
||||||
observe(storage: storage)
|
if #available(macOS 14, *), #available(iOS 17, *), state.contains(where: { $0.value.isObservable }) {
|
||||||
|
observe(storage: storage)
|
||||||
|
}
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Observe the observable properties accessed in the view.
|
/// Observe the observable properties accessed in the view.
|
||||||
/// - Parameter storage: The view storage
|
/// - Parameter storage: The view storage
|
||||||
|
@available(macOS, introduced: 14)
|
||||||
|
@available(iOS, introduced: 17)
|
||||||
func observe(storage: ViewStorage) {
|
func observe(storage: ViewStorage) {
|
||||||
withObservationTracking {
|
withObservationTracking {
|
||||||
_ = content.getDebugTree(parameters: true, type: AnyView.self)
|
_ = content().getDebugTree(parameters: true, type: AnyView.self)
|
||||||
} onChange: {
|
} onChange: {
|
||||||
storage.fields[updateID] = true
|
Task {
|
||||||
UpdateManager.updateViews()
|
StateManager.updateState(id: storage.state.first?.value.id ?? .init())
|
||||||
observe(storage: storage)
|
StateManager.updateViews()
|
||||||
|
observe(storage: storage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,18 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Meta
|
import Meta
|
||||||
|
import Observation
|
||||||
import SampleBackends
|
import SampleBackends
|
||||||
|
|
||||||
|
@available(macOS 14, *)
|
||||||
|
@available(iOS 17, *)
|
||||||
struct DemoView: View {
|
struct DemoView: View {
|
||||||
|
|
||||||
@State private var test = "Label"
|
@State private var test = TestModel()
|
||||||
|
|
||||||
var view: Body {
|
var view: Body {
|
||||||
Backend1.TestWidget1()
|
Backend1.TestWidget1()
|
||||||
Backend1.Button(test) {
|
Backend1.Button(test.test) {
|
||||||
test = "\(Int.random(in: 1...10))"
|
test.test = "\(Int.random(in: 1...10))"
|
||||||
}
|
}
|
||||||
TestView()
|
TestView()
|
||||||
testContent
|
testContent
|
||||||
@ -23,29 +26,55 @@ struct DemoView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TestView: SimpleView {
|
struct TestView: View {
|
||||||
|
|
||||||
|
@State private var test = "Label"
|
||||||
|
|
||||||
var view: Body {
|
var view: Body {
|
||||||
Backend2.TestWidget4()
|
Backend2.TestWidget4()
|
||||||
|
Backend1.Button(test) {
|
||||||
|
Task {
|
||||||
|
try await Task.sleep(nanoseconds: 100_000_000)
|
||||||
|
test = "\(Int.random(in: 1...10))"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let backendType = Backend1.BackendWidget.self
|
@available(macOS 14, *)
|
||||||
let modifiers: [(AnyView) -> AnyView] = [
|
@available(iOS 17, *)
|
||||||
{ $0 as? Backend2.TestWidget2 != nil ? [Backend1.TestWidget1()] : $0 }
|
@Observable
|
||||||
]
|
class TestModel {
|
||||||
|
|
||||||
|
var test = "Label"
|
||||||
|
|
||||||
print(DemoView().getDebugTree(parameters: true, type: backendType, modifiers: modifiers))
|
|
||||||
let storage = DemoView().storage(modifiers: modifiers, type: backendType)
|
|
||||||
for round in 0...2 {
|
|
||||||
print("#\(round)")
|
|
||||||
DemoView().updateStorage(storage, modifiers: modifiers, updateProperties: true, type: backendType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateManager.addUpdateHandler { _ in
|
@main
|
||||||
print("#Handler")
|
@available(macOS 14, *)
|
||||||
DemoView().updateStorage(storage, modifiers: modifiers, updateProperties: false, type: backendType)
|
@available(iOS 17, *)
|
||||||
}
|
struct DemoApp {
|
||||||
|
|
||||||
sleep(2)
|
static func main() {
|
||||||
|
let backendType = Backend1.BackendWidget.self
|
||||||
|
let modifiers: [(AnyView) -> AnyView] = [
|
||||||
|
{ $0 as? Backend2.TestWidget2 != nil ? [Backend1.TestWidget1()] : $0 }
|
||||||
|
]
|
||||||
|
|
||||||
|
print(DemoView().getDebugTree(parameters: true, type: backendType, modifiers: modifiers))
|
||||||
|
let storage = DemoView().storage(modifiers: modifiers, type: backendType)
|
||||||
|
for round in 0...2 {
|
||||||
|
print("#\(round)")
|
||||||
|
DemoView().updateStorage(storage, modifiers: modifiers, updateProperties: true, type: backendType)
|
||||||
|
}
|
||||||
|
|
||||||
|
StateManager.addUpdateHandler { _ in
|
||||||
|
DemoView().updateStorage(storage, modifiers: modifiers, updateProperties: false, type: backendType)
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(2)
|
||||||
|
DemoView().updateStorage(storage, modifiers: modifiers, updateProperties: true, type: backendType)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -74,8 +74,9 @@ public enum Backend1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func container<WidgetType>(modifiers: [(any AnyView) -> any AnyView], type: WidgetType.Type) -> ViewStorage {
|
public func container<WidgetType>(modifiers: [(any AnyView) -> any AnyView], type: WidgetType.Type) -> ViewStorage {
|
||||||
|
print("Init button")
|
||||||
Task {
|
Task {
|
||||||
try await Task.sleep(for: .seconds(1))
|
try await Task.sleep(nanoseconds: 1_000_000_000)
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
return .init(nil)
|
return .init(nil)
|
||||||
@ -83,9 +84,9 @@ public enum Backend1 {
|
|||||||
|
|
||||||
public func update<WidgetType>(_ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, type: WidgetType.Type) {
|
public func update<WidgetType>(_ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, type: WidgetType.Type) {
|
||||||
if updateProperties {
|
if updateProperties {
|
||||||
print("Update button")
|
print("Update button (label = \(label))")
|
||||||
} else {
|
} else {
|
||||||
print("Do not update button")
|
print("Do not update button (label = \(label))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user