Compare commits

...

13 Commits

33 changed files with 592 additions and 266 deletions

View File

@ -1,4 +1,4 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0
//
// Package.swift
// Meta

View File

@ -39,7 +39,7 @@
/// ```
@propertyWrapper
@dynamicMemberLookup
public struct Binding<Value> {
public struct Binding<Value>: Sendable where Value: Sendable {
/// The value.
public var wrappedValue: Value {
@ -64,11 +64,11 @@ public struct Binding<Value> {
}
/// The closure for getting the value.
private let getValue: () -> Value
private let getValue: @Sendable () -> Value
/// The closure for settings the value.
private let setValue: (Value) -> Void
private let setValue: @Sendable (Value) -> Void
/// Handlers observing whether the binding changes.
private var handlers: [(Value) -> Void] = []
private var handlers: [@Sendable (Value) -> Void] = []
/// Get a property of any content of a `Binding` as a `Binding`.
/// - Parameter keyPath: The path to the member.
@ -85,7 +85,7 @@ public struct Binding<Value> {
/// - Parameters:
/// - get: The closure for getting the value.
/// - set: The closure for setting the value.
public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) {
public init(get: @Sendable @escaping () -> Value, set: @Sendable @escaping (Value) -> Void) {
self.getValue = get
self.setValue = set
}
@ -104,7 +104,7 @@ public struct Binding<Value> {
/// Observe whether data is changed over this binding.
/// - Parameter handler: The handler.
/// - Returns: The binding.
public func onSet(_ handler: @escaping (Value) -> Void) -> Self {
public func onSet(_ handler: @Sendable @escaping (Value) -> Void) -> Self {
var newSelf = self
newSelf.handlers.append(handler)
return newSelf
@ -113,7 +113,7 @@ public struct Binding<Value> {
}
/// Extend bindings.
extension Binding where Value: MutableCollection {
extension Binding where Value: MutableCollection, Value.Index: Sendable, Value.Element: Sendable {
/// Get a child at a certain index of the array as a binding.
/// - Parameters:
@ -136,7 +136,8 @@ extension Binding where Value: MutableCollection {
}
/// Extend bindings.
extension Binding where Value: MutableCollection, Value.Element: Identifiable {
extension Binding
where Value: MutableCollection, Value.Element: Identifiable, Value.Index: Sendable, Value.Element: Sendable {
/// Get a child of the array with a certain id as a binding.
/// - Parameters:

View File

@ -58,7 +58,7 @@ public protocol Model {
}
/// Data about a model's state value.
public struct ModelData {
public struct ModelData: Sendable {
/// The state value's identifier.
var storage: StateContent.Storage
@ -67,23 +67,8 @@ public struct ModelData {
}
/// Extend the model.
extension Model {
/// Get the value as a binding.
public var binding: Binding<Self> {
.init {
getModel()
} set: { newValue in
guard let data = model else {
return
}
data.storage.value = newValue
data.storage.update = true
StateManager.updateViews(force: data.force)
}
}
/// Set the model up.
///
/// At the point this function gets called, the model data is available.
@ -100,7 +85,9 @@ extension Model {
setModel(&model)
data.storage.value = model
data.storage.update = true
StateManager.updateViews(force: data.force)
Task {
await StateManager.updateViews(force: data.force)
}
}
/// Get the current version of the model.
@ -116,3 +103,23 @@ extension Model {
}
}
extension Model where Self: Sendable {
/// Get the value as a binding.
public var binding: Binding<Self> {
.init {
getModel()
} set: { newValue in
guard let data = model else {
return
}
data.storage.value = newValue
data.storage.update = true
Task {
await StateManager.updateViews(force: data.force)
}
}
}
}

View File

@ -8,12 +8,14 @@
import Foundation
/// A type that signalizes an action.
public struct Signal {
public struct Signal: Model, Sendable {
/// An action is signalized by toggling a boolean to `true` and back to `false`.
@State var boolean = false
var boolean = false
/// A signal has a unique identifier.
public let id: UUID = .init()
/// The model data.
public var model: ModelData?
/// Whether the action has caused an update.
public var update: Bool { boolean }
@ -23,7 +25,11 @@ public struct Signal {
/// Activate a signal.
public func signal() {
boolean = true
setModel { $0.boolean = true }
}
/// Destroy a signal.
mutating func destroySignal() {
boolean = false
}

View File

@ -9,7 +9,7 @@ import Foundation
/// A property wrapper for properties in a view that should be stored throughout view updates.
@propertyWrapper
public struct State<Value>: StateProtocol {
public struct State<Value>: StateProtocol, Sendable where Value: Sendable {
/// Access the stored value. This updates the views when being changed.
public var wrappedValue: Value {
@ -19,7 +19,9 @@ public struct State<Value>: StateProtocol {
nonmutating set {
rawValue = newValue
content.update = true
StateManager.updateViews(force: forceUpdates)
Task {
await StateManager.updateViews(force: forceUpdates)
}
}
}
@ -49,7 +51,7 @@ public struct State<Value>: StateProtocol {
var forceUpdates: Bool
/// The closure for initializing the state property's value.
var getInitialValue: () -> Value
var getInitialValue: @Sendable () -> Value
/// The content.
let content: StateContent = .init()
@ -59,7 +61,7 @@ public struct State<Value>: StateProtocol {
/// - wrappedValue: The wrapped value.
/// - id: An explicit identifier.
/// - 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: @Sendable @autoclosure @escaping () -> Value, forceUpdates: Bool = false) {
getInitialValue = wrappedValue
self.forceUpdates = forceUpdates
}

View File

@ -6,7 +6,7 @@
//
/// A class storing the state's content.
class StateContent {
final class StateContent: @unchecked Sendable {
/// The storage.
var storage: Storage?
@ -39,7 +39,7 @@ class StateContent {
init() { }
/// A class storing the value.
class Storage {
class Storage: @unchecked Sendable {
/// The stored value.
var value: Any

View File

@ -8,31 +8,52 @@
import Foundation
/// This type manages view updates.
public enum StateManager {
@globalActor
public actor StateManager {
/// Whether to block updates in general.
public static var blockUpdates = false
@StateManager static var blockAllUpdates = false
/// The application identifier.
static var appID: String?
/// The functions handling view updates.
static var updateHandlers: [(Bool) -> Void] = []
static var updateHandlers: [@Sendable (Bool) async -> Void] = []
/// The shared instance of the actor.
public static let shared = StateManager()
/// Update all of the views.
/// - Parameter force: Whether to force all views to update.
///
/// Nothing happens if ``StateManager/blockUpdates`` is true.
public static func updateViews(force: Bool = false) {
if !blockUpdates {
public static func updateViews(force: Bool = false) async {
if await !blockAllUpdates {
for handler in updateHandlers {
handler(force)
Task {
await 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.
static func addUpdateHandler(handler: @escaping (Bool) -> Void) {
static func addUpdateHandler(handler: @Sendable @escaping (Bool) async -> Void) {
updateHandlers.append(handler)
}
/// Block all updates.
///
/// The user interface will not respond to changes.
@StateManager
public static func blockUpdates() {
blockAllUpdates = true
}
/// Unblock all updates.
///
/// The user interface will respond to changes.
@StateManager
public static func unblockUpdates() {
blockAllUpdates = false
}
}

View File

@ -8,7 +8,7 @@
import Foundation
/// An interface for accessing `State` without specifying the generic type.
protocol StateProtocol {
protocol StateProtocol: Sendable {
/// The state content.
var content: StateContent { get }

View File

@ -44,10 +44,10 @@ extension Array: AnyView where Element == AnyView {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
) async where Data: ViewRenderData {
for (index, element) in filter({ $0.renderable(type: type, data: data) }).enumerated() {
if let storage = storages[safe: index] {
element
await element
.widget(data: data, type: type)
.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}
@ -62,9 +62,9 @@ extension Array: AnyView where Element == AnyView {
public func storages<Data>(
data: WidgetData,
type: Data.Type
) -> [ViewStorage] where Data: ViewRenderData {
compactMap { view in
view.renderable(type: type, data: data) ? view.storage(data: data, type: type) : nil
) async -> [ViewStorage] where Data: ViewRenderData {
await compactMap { view in
await view.renderable(type: type, data: data) ? view.storage(data: data, type: type) : nil
}
}
@ -96,6 +96,78 @@ extension Array {
}
}
/// Returns the first element of the sequence that satisfies the given predicate.
/// - Parameter predicate: A closure that takes an element of the sequence as its argument
/// and returns a Boolean value indicating whether the element is a match.
/// - Returns: The first element of the sequence that satisfies `predicate`,
/// or `nil` if there is no element that satisfies `predicate`.
public func first(where predicate: (Element) async throws -> Bool) async rethrows -> Element? {
for element in self {
let matches = try await predicate(element)
if matches {
return element
}
}
return nil
}
/// Returns the index of the first element of the sequence that satisfies the given predicate.
/// - Parameter predicate: A closure that takes an element of the sequence as its argument
/// and returns a Boolean value indicating whether the element is a match.
/// - Returns: The index of the first element of the sequence that satisfies `predicate`,
/// or `nil` if there is no element that satisfies `predicate`.
public func firstIndex(where predicate: (Element) async throws -> Bool) async rethrows -> Int? {
for (index, element) in enumerated() {
let matches = try await predicate(element)
if matches {
return index
}
}
return nil
}
/// Returns the last element of the sequence that satisfies the given predicate.
/// - Parameter predicate: A closure that takes an element of the sequence as its argument
/// and returns a Boolean value indicating whether the element is a match.
/// - Returns: The last element of the sequence that satisfies `predicate`,
/// or `nil` if there is no element that satisfies `predicate`.
public func last(where predicate: (Element) async throws -> Bool) async rethrows -> Element? {
try await reversed().first(where: predicate)
}
/// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate.
/// - Parameter predicate: A closure that takes an element of the sequence as its argument
/// and returns a Boolean value indicating whether the element is a match.
/// - Returns: Whether the sequence contains an element that satisfies `predicate`.
public func contains(where predicate: (Element) async throws -> Bool) async rethrows -> Bool {
try await first(where: predicate) != nil
}
/// Returns an array containing the results of mapping the given closure over the sequences elements.
/// Remove elements that are `nil`.
/// - Parameter transform: Transforms each element.
/// - Returns: The result.
public func compactMap<ElementOfResult>(
_ transform: (Element) async throws -> ElementOfResult?
) async rethrows -> [ElementOfResult] {
var result: [ElementOfResult] = []
for element in self {
if let element = try await transform(element) {
result.append(element)
}
}
return result
}
/// Returns an array containing the results of mapping the given closure over the sequences elements.
/// - Parameter transform: Transforms each element.
/// - Returns: The result.
public func map<ElementOfResult>(
_ transform: (Element) async throws -> ElementOfResult
) async rethrows -> [ElementOfResult] {
try await compactMap { try await transform($0) }
}
}
/// Extend arrays.

View File

@ -0,0 +1,8 @@
//
// KeyPath.swift
// Meta
//
// Created by david-swift on 18.09.24.
//
extension KeyPath: @retroactive @unchecked Sendable { }

View File

@ -0,0 +1,8 @@
//
// OpaquePointer.swift
// Meta
//
// Created by david-swift on 29.09.24.
//
extension OpaquePointer: @retroactive @unchecked Sendable { }

View File

@ -21,7 +21,7 @@
/// }
/// ```
///
public protocol App {
public protocol App: Sendable {
/// The app storage type.
associatedtype Storage: AppStorage
@ -60,26 +60,30 @@ extension App {
public static func setupApp() -> Self {
var appInstance = self.init()
appInstance.app = Storage(id: appInstance.id)
appInstance.app.storage.app = { appInstance }
StateManager.addUpdateHandler { force in
StateManager.addUpdateHandler { [appInstance] force in
let updateProperties = force || appInstance.getState().contains { $0.value.content.update }
var removeIndices: [Int] = []
for (index, element) in appInstance.app.storage.sceneStorage.enumerated() {
if element.destroy {
for (index, element) in await appInstance.app.storage.sceneStorage.enumerated() {
if await element.destroy {
removeIndices.insert(index, at: 0)
} else if let scene = appInstance.scene.first(
where: { $0.id == element.id }
} else if let scene = await appInstance.scene.first(
where: { await $0.id == element.id }
) as? Storage.SceneElementType as? SceneElement {
scene.update(element, app: appInstance.app, updateProperties: updateProperties)
}
}
for index in removeIndices {
appInstance.app.storage.sceneStorage.remove(at: index)
await appInstance.app.modifyStandardAppStorage { $0.sceneStorage.remove(at: index) }
}
}
StateManager.appID = appInstance.id
let state = appInstance.getState()
appInstance.app.storage.stateStorage = state
Task { [appInstance] in
let state = appInstance.getState()
await appInstance.app.modifyStandardAppStorage { storage in
storage.stateStorage = state
storage.app = { appInstance }
}
}
return appInstance
}

View File

@ -6,7 +6,7 @@
//
/// The app storage protocol.
public protocol AppStorage: AnyObject {
public protocol AppStorage: Actor, Sendable {
/// The type of scene elements (which should be backend-specific).
associatedtype SceneElementType
@ -20,10 +20,28 @@ public protocol AppStorage: AnyObject {
/// Run the application.
/// - Parameter setup: A closure that is expected to be executed right at the beginning.
func run(setup: @escaping () -> Void)
nonisolated func run(setup: @Sendable @escaping () -> Void)
/// Terminate the application.
func quit()
nonisolated func quit()
}
extension AppStorage {
/// Modify the app storage.
/// - Parameter modify: The modifications.
func modifyStandardAppStorage(_ modify: (inout StandardAppStorage) -> Void) {
var copy = storage
modify(&copy)
self.storage = copy
}
/// Append a scene.
/// - Parameter scene: The scene.
public func appendScene(_ scene: SceneStorage) {
modifyStandardAppStorage { $0.sceneStorage.append(scene) }
}
}
@ -32,13 +50,28 @@ extension AppStorage {
/// Focus the scene element with a certain id (if supported). Create the element if it doesn't already exist.
/// - Parameter id: The element's id.
public func showSceneElement(_ id: String) {
storage.sceneStorage.last { $0.id == id && !$0.destroy }?.show() ?? addSceneElement(id)
nonisolated public func showSceneElement(_ id: String) {
Task {
await storage.sceneStorage
.last { scene in
let destroy = await scene.destroy
return await scene.id == id && !destroy
}?
.show() ?? addSceneElement(id)
}
}
/// Add a new scene element with the content of the scene element with a certain id.
/// - Parameter id: The element's id.
public func addSceneElement(_ id: String) {
nonisolated public func addSceneElement(_ id: String) {
Task {
await internalAddSceneElement(id)
}
}
/// Add a new scene element with the content of the scene element with a certain id.
/// - Parameter id: The element's id.
func internalAddSceneElement(_ id: String) {
if let element = storage.app?().scene.last(where: { $0.id == id }) {
let container = element.container(app: self)
storage.sceneStorage.append(container)
@ -48,13 +81,13 @@ extension AppStorage {
/// Focus the window with a certain id (if supported). Create the window if it doesn't already exist.
/// - Parameter id: The window's id.
public func showWindow(_ id: String) {
nonisolated public func showWindow(_ id: String) {
showSceneElement(id)
}
/// Add a new window with the content of the window template with a certain id.
/// - Parameter id: The window template's id.
public func addWindow(_ id: String) {
nonisolated public func addWindow(_ id: String) {
addSceneElement(id)
}

View File

@ -6,7 +6,7 @@
//
/// The app storage protocol.
public struct StandardAppStorage {
public struct StandardAppStorage: Sendable {
/// The scene storage.
public var sceneStorage: [SceneStorage] = []
@ -15,7 +15,7 @@ public struct StandardAppStorage {
var stateStorage: [String: StateProtocol] = [:]
/// The scene.
var app: (() -> any App)?
var app: (@Sendable () -> any App)?
/// Initialize the standard app storage.
public init() { }

View File

@ -6,7 +6,7 @@
//
/// A structure conforming to `SceneElement` can be added to an app's `scene` property.
public protocol SceneElement {
public protocol SceneElement: Sendable {
/// The window type's identifier.
var id: String { get }

View File

@ -6,22 +6,22 @@
//
/// Store a reference to a rendered scene element in a view storage.
public class SceneStorage {
public actor SceneStorage {
/// The scene element's identifier.
public var id: String
/// The pointer.
///
/// It can be a C pointer, a Swift class, or other information depending on the backend.
public var pointer: Any?
public var pointer: Sendable?
/// The scene element's view content.
public var content: [String: [ViewStorage]]
var content: [String: [ViewStorage]]
/// Various properties of a scene element.
public var fields: [String: Any] = [:]
var fields: [String: Sendable] = [:]
/// Whether the reference to the window should disappear in the next update.
public var destroy = false
/// Show the scene element (including moving into the foreground, if possible).
public var show: () -> Void
public var show: @Sendable () -> Void
/// The previous state of the scene element.
public var previousState: SceneElement?
@ -43,9 +43,9 @@ public class SceneStorage {
/// - show: Function called when the scene element should be displayed.
public init(
id: String,
pointer: Any?,
pointer: Sendable?,
content: [String: [ViewStorage]] = [:],
show: @escaping () -> Void
show: @Sendable @escaping () -> Void
) {
self.id = id
self.pointer = pointer
@ -53,4 +53,65 @@ public class SceneStorage {
self.show = show
}
/// Set the pointer.
/// - Parameter value: The new pointer.
public func setPointer(_ value: Sendable?) {
pointer = value
}
/// Set the element of a certain field.
/// - Parameters:
/// - key: The key.
/// - value: The field.
public func setField(key: String, value: Sendable) {
fields[key] = value
}
/// Remove a certain field.
/// - Parameters:
/// - key: The key.
public func removeField(key: String) {
fields.removeValue(forKey: key)
}
/// Get the element of a certain field.
/// - Parameter key: The key.
/// - Returns: The field.
public func getField(key: String) -> Sendable? {
fields[key]
}
/// Set the content elements under a certain key.
/// - Parameters:
/// - key: The key.
/// - value: The content elements.
public func setContent(key: String, value: [ViewStorage]) {
content[key] = value
}
/// Get the content elements under a certain key.
/// - Parameter key: The key.
/// - Returns: The content elements.
public func getContent(key: String) -> [ViewStorage] {
content[key] ?? []
}
/// Set the previous state.
/// - Parameter state: The state.
public func setPreviousState(_ state: SceneElement?) {
previousState = state
}
/// Set whether the scene will be destroyed.
/// - Parameter destroy: Whether the scene will be destroyed.
public func setDestroy(_ destroy: Bool) {
self.destroy = destroy
}
/// Set the closure which shows the scene.
/// - Parameter show: The closure.
public func setShow(_ show: @Sendable @escaping () -> Void) {
self.show = show
}
}

View File

@ -6,7 +6,7 @@
//
/// The view type used for any form of a view.
public protocol AnyView {
public protocol AnyView: Sendable {
/// The view's content.
@ViewBuilder var viewContent: Body { get }
@ -38,8 +38,8 @@ extension AnyView {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
widget(data: data, type: type)
) async where Data: ViewRenderData {
await widget(data: data, type: type)
.update(storage, data: data, updateProperties: updateProperties, type: type)
}
@ -51,8 +51,8 @@ extension AnyView {
public func storage<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
widget(data: data, type: type).container(data: data, type: type)
) async -> ViewStorage where Data: ViewRenderData {
await widget(data: data, type: type).container(data: data, type: type)
}
/// Wrap the view into a widget.

View File

@ -10,14 +10,14 @@
/// This will be used if you do not provide a custom ``Widget/update(_:data:updateProperties:type:)`` method
/// or call the ``Widget/updateProperties(_:updateProperties:)`` method in your custom update method.
@propertyWrapper
public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol {
public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol where Value: Sendable {
/// The wrapped binding.
public var wrappedValue: Binding<Value>
/// Observe the UI element.
var observe: (Pointer, Binding<Value>, ViewStorage) -> Void
var observe: @Sendable (Pointer, Binding<Value>, ViewStorage) async -> Void
/// Set the UI element's property.
var setValue: (Pointer, Value, ViewStorage) -> Void
var setValue: @Sendable (Pointer, Value, ViewStorage) async -> Void
/// Initialize a property.
/// - Parameters:
@ -27,8 +27,8 @@ public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol {
/// - pointer: The type of the pointer.
public init(
wrappedValue: Binding<Value>,
observe: @escaping (Pointer, Binding<Value>, ViewStorage) -> Void,
set setValue: @escaping (Pointer, Value, ViewStorage) -> Void,
observe: @Sendable @escaping (Pointer, Binding<Value>, ViewStorage) async -> Void,
set setValue: @Sendable @escaping (Pointer, Value, ViewStorage) async -> Void,
pointer: Pointer.Type
) {
self.wrappedValue = wrappedValue
@ -44,14 +44,14 @@ public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol {
/// - pointer: The type of the pointer.
public init(
wrappedValue: Binding<Value>,
observe: @escaping (Pointer, Binding<Value>) -> Void,
set setValue: @escaping (Pointer, Value) -> Void,
observe: @Sendable @escaping (Pointer, Binding<Value>) async -> Void,
set setValue: @Sendable @escaping (Pointer, Value) async -> Void,
pointer: Pointer.Type
) {
self.init(
wrappedValue: wrappedValue,
observe: { pointer, value, _ in observe(pointer, value) },
set: { pointer, value, _ in setValue(pointer, value) },
observe: { pointer, value, _ in await observe(pointer, value) },
set: { pointer, value, _ in await setValue(pointer, value) },
pointer: pointer
)
}
@ -59,19 +59,19 @@ public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol {
}
/// The binding property protocol.
protocol BindingPropertyProtocol {
protocol BindingPropertyProtocol: Sendable {
/// The binding's wrapped value.
associatedtype Value
associatedtype Value: Sendable
/// The storage's pointer.
associatedtype Pointer
/// The wrapped value.
var wrappedValue: Binding<Value> { get }
/// Observe the property.
var observe: (Pointer, Binding<Value>, ViewStorage) -> Void { get }
var observe: @Sendable (Pointer, Binding<Value>, ViewStorage) async -> Void { get }
/// Set the property.
var setValue: (Pointer, Value, ViewStorage) -> Void { get }
var setValue: @Sendable (Pointer, Value, ViewStorage) async -> Void { get }
}
@ -84,12 +84,12 @@ extension Widget {
func setBindingProperty<Property>(
property: Property,
storage: ViewStorage
) where Property: BindingPropertyProtocol {
) async where Property: BindingPropertyProtocol {
if let optional = property.wrappedValue.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil {
return
}
if let pointer = storage.pointer as? Property.Pointer {
property.setValue(pointer, property.wrappedValue.wrappedValue, storage)
if let pointer = await storage.pointer as? Property.Pointer {
await property.setValue(pointer, property.wrappedValue.wrappedValue, storage)
}
}

View File

@ -10,10 +10,10 @@
/// This will be used if you do not provide a custom ``Widget/update(_:data:updateProperties:type:)`` method
/// or call the ``Widget/updateProperties(_:updateProperties:)`` method in your custom update method.
@propertyWrapper
public struct Property<Value, Pointer>: PropertyProtocol {
public struct Property<Value, Pointer>: PropertyProtocol where Value: Sendable {
/// The function applying the property to the UI.
public var setProperty: (Pointer, Value, ViewStorage) -> Void
public var setProperty: @Sendable (Pointer, Value, ViewStorage) async -> Void
/// The wrapped value.
public var wrappedValue: Value
/// The update strategy.
@ -27,7 +27,7 @@ public struct Property<Value, Pointer>: PropertyProtocol {
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
public init(
wrappedValue: Value,
set setProperty: @escaping (Pointer, Value, ViewStorage) -> Void,
set setProperty: @Sendable @escaping (Pointer, Value, ViewStorage) async -> Void,
pointer: Pointer.Type,
updateStrategy: UpdateStrategy = .automatic
) {
@ -44,13 +44,13 @@ public struct Property<Value, Pointer>: PropertyProtocol {
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
public init(
wrappedValue: Value,
set setProperty: @escaping (Pointer, Value) -> Void,
set setProperty: @Sendable @escaping (Pointer, Value) async -> Void,
pointer: Pointer.Type,
updateStrategy: UpdateStrategy = .automatic
) {
self.init(
wrappedValue: wrappedValue,
set: { pointer, value, _ in setProperty(pointer, value) },
set: { pointer, value, _ in await setProperty(pointer, value) },
pointer: pointer,
updateStrategy: updateStrategy
)
@ -66,13 +66,13 @@ extension Property where Value: OptionalProtocol {
/// - pointer: The type of the pointer.
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
public init(
set setProperty: @escaping (Pointer, Value.Wrapped, ViewStorage) -> Void,
set setProperty: @Sendable @escaping (Pointer, Value.Wrapped, ViewStorage) async -> Void,
pointer: Pointer.Type,
updateStrategy: UpdateStrategy = .automatic
) {
self.setProperty = { pointer, value, storage in
if let value = value.optionalValue {
setProperty(pointer, value, storage)
await setProperty(pointer, value, storage)
}
}
wrappedValue = nil
@ -86,12 +86,12 @@ extension Property where Value: OptionalProtocol {
/// - pointer: The type of the pointer.
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
public init(
set setProperty: @escaping (Pointer, Value.Wrapped) -> Void,
set setProperty: @Sendable @escaping (Pointer, Value.Wrapped) async -> Void,
pointer: Pointer.Type,
updateStrategy: UpdateStrategy = .automatic
) {
self.init(
set: { pointer, value, _ in setProperty(pointer, value) },
set: { pointer, value, _ in await setProperty(pointer, value) },
pointer: pointer,
updateStrategy: updateStrategy
)
@ -100,7 +100,7 @@ extension Property where Value: OptionalProtocol {
}
/// The property protocol.
protocol PropertyProtocol {
protocol PropertyProtocol: Sendable {
/// The type of the wrapped value.
associatedtype Value
@ -110,14 +110,14 @@ protocol PropertyProtocol {
/// The wrapped value.
var wrappedValue: Value { get }
/// Set the property.
var setProperty: (Pointer, Value, ViewStorage) -> Void { get }
var setProperty: @Sendable (Pointer, Value, ViewStorage) async -> Void { get }
/// The update strategy.
var updateStrategy: UpdateStrategy { get }
}
/// The update strategy for properties.
public enum UpdateStrategy {
public enum UpdateStrategy: Sendable {
/// If equatable, update only when the value changed.
/// If not equatable, this is equivalent to ``UpdateStrategy/always``.
@ -140,10 +140,10 @@ extension Widget {
public func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
) async -> ViewStorage where Data: ViewRenderData {
let storage = ViewStorage(initializeWidget())
initProperties(storage, data: data, type: type)
update(storage, data: data, updateProperties: true, type: type)
await initProperties(storage, data: data, type: type)
await update(storage, data: data, updateProperties: true, type: type)
return storage
}
@ -160,10 +160,10 @@ extension Widget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
self.updateProperties(storage, data: data, updateProperties: updateProperties, type: type)
) async where Data: ViewRenderData {
await self.updateProperties(storage, data: data, updateProperties: updateProperties, type: type)
if updateProperties {
storage.previousState = self
await storage.setPreviousState(self)
}
}
@ -176,16 +176,16 @@ extension Widget {
_ storage: ViewStorage,
data: WidgetData,
type: Data.Type
) where Data: ViewRenderData {
) async where Data: ViewRenderData {
let mirror = Mirror(reflecting: self)
for property in mirror.children {
if let value = property.value as? any ViewPropertyProtocol {
let subview = value.wrappedValue.storage(data: data, type: type)
initViewProperty(value, view: subview, parent: storage)
storage.content[property.label ?? .mainContent] = [subview]
let subview = await value.wrappedValue.storage(data: data, type: type)
await initViewProperty(value, view: subview, parent: storage)
await storage.setContent(key: property.label ?? .mainContent, value: [subview])
}
if let value = property.value as? any BindingPropertyProtocol {
initBindingProperty(value, parent: storage)
await initBindingProperty(value, parent: storage)
}
}
}
@ -199,9 +199,10 @@ extension Widget {
_ value: Property,
view: ViewStorage,
parent: ViewStorage
) where Property: ViewPropertyProtocol {
if let view = view.pointer as? Property.ViewPointer, let pointer = parent.pointer as? Property.Pointer {
value.setView(pointer, view)
) async where Property: ViewPropertyProtocol {
if let view = await view.pointer as? Property.ViewPointer,
let pointer = await parent.pointer as? Property.Pointer {
await value.setView(pointer, view)
}
}
@ -209,9 +210,12 @@ extension Widget {
/// - Parameters:
/// - value: The property.
/// - parent: The view storage.
func initBindingProperty<Property>(_ value: Property, parent: ViewStorage) where Property: BindingPropertyProtocol {
if let view = parent.pointer as? Property.Pointer {
value.observe(
func initBindingProperty<Property>(
_ value: Property,
parent: ViewStorage
) async where Property: BindingPropertyProtocol {
if let view = await parent.pointer as? Property.Pointer {
await value.observe(
view,
.init {
value.wrappedValue.wrappedValue
@ -237,14 +241,20 @@ extension Widget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
) async where Data: ViewRenderData {
let mirror = Mirror(reflecting: self)
updateNotEquatable(mirror: mirror, storage: storage, data: data, updateProperties: updateProperties, type: type)
await updateNotEquatable(
mirror: mirror,
storage: storage,
data: data,
updateProperties: updateProperties,
type: type
)
guard updateProperties else {
return
}
updateAlwaysWhenStateUpdate(mirror: mirror, storage: storage)
updateEquatable(mirror: mirror, storage: storage)
await updateAlwaysWhenStateUpdate(mirror: mirror, storage: storage)
await updateEquatable(mirror: mirror, storage: storage)
}
/// Update the properties which are not equatable and should always be updated (e.g. closures).
@ -260,20 +270,21 @@ extension Widget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
) async where Data: ViewRenderData {
for property in mirror.children {
if let value = property.value as? any PropertyProtocol {
if value.updateStrategy == .always ||
value.wrappedValue as? any Equatable == nil && value.updateStrategy != .alwaysWhenStateUpdate {
setProperty(property: value, storage: storage)
await setProperty(property: value, storage: storage)
}
}
if let value = property.value as? any ViewPropertyProtocol,
let storage = storage.content[property.label ?? .mainContent]?.first {
value.wrappedValue.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
let storage = await storage.getContent(key: property.label ?? .mainContent).first {
await value.wrappedValue
.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}
if let value = property.value as? any BindingPropertyProtocol {
setBindingProperty(property: value, storage: storage)
await setBindingProperty(property: value, storage: storage)
}
}
}
@ -285,11 +296,11 @@ extension Widget {
/// - storage: The view storage.
///
/// Initialize the ``Property`` property wrapper with the ``UpdateStrategy/alwaysWhenStateUpdate``.
func updateAlwaysWhenStateUpdate(mirror: Mirror, storage: ViewStorage) {
func updateAlwaysWhenStateUpdate(mirror: Mirror, storage: ViewStorage) async {
for property in mirror.children {
if let value = property.value as? any PropertyProtocol {
if value.updateStrategy == .alwaysWhenStateUpdate {
setProperty(property: value, storage: storage)
await setProperty(property: value, storage: storage)
}
}
}
@ -299,8 +310,8 @@ extension Widget {
/// - Parameters:
/// - mirror: A mirror of the widget.
/// - storage: The view storage.
func updateEquatable(mirror: Mirror, storage: ViewStorage) {
let previousState: Mirror.Children? = if let previousState = storage.previousState {
func updateEquatable(mirror: Mirror, storage: ViewStorage) async {
let previousState: Mirror.Children? = if let previousState = await storage.previousState {
Mirror(reflecting: previousState).children
} else {
nil
@ -317,7 +328,7 @@ extension Widget {
update = false
}
if update {
setProperty(property: value, storage: storage)
await setProperty(property: value, storage: storage)
}
}
}
@ -354,12 +365,12 @@ extension Widget {
/// - Parameters:
/// - property: The property.
/// - storage: The view storage.
func setProperty<Property>(property: Property, storage: ViewStorage) where Property: PropertyProtocol {
func setProperty<Property>(property: Property, storage: ViewStorage) async where Property: PropertyProtocol {
if let optional = property.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil {
return
}
if let pointer = storage.pointer as? Property.Pointer {
property.setProperty(pointer, property.wrappedValue, storage)
if let pointer = await storage.pointer as? Property.Pointer {
await property.setProperty(pointer, property.wrappedValue, storage)
}
}

View File

@ -15,7 +15,7 @@ public struct ViewProperty<Pointer, ViewPointer>: ViewPropertyProtocol {
/// The wrapped value.
public var wrappedValue: Body = []
/// Set the view.
var setView: (Pointer, ViewPointer) -> Void
var setView: @Sendable (Pointer, ViewPointer) async -> Void
/// Initialize a property.
/// - Parameters:
@ -23,7 +23,7 @@ public struct ViewProperty<Pointer, ViewPointer>: ViewPropertyProtocol {
/// - pointer: The pointer type of the parent view (usually a concrete view type).
/// - subview: The pointer type of the child view (usually a protocol, view class, or similar).
public init(
set setView: @escaping (Pointer, ViewPointer) -> Void,
set setView: @Sendable @escaping (Pointer, ViewPointer) async -> Void,
pointer: Pointer.Type,
subview: ViewPointer.Type
) {
@ -35,7 +35,7 @@ public struct ViewProperty<Pointer, ViewPointer>: ViewPropertyProtocol {
/// The view property protocol.
///
/// Do not use for wrapper widgets.
protocol ViewPropertyProtocol {
protocol ViewPropertyProtocol: Sendable {
/// The type of the view's pointer.
associatedtype Pointer
@ -45,6 +45,6 @@ protocol ViewPropertyProtocol {
/// The wrapped value.
var wrappedValue: Body { get }
/// Set the view.
var setView: (Pointer, ViewPointer) -> Void { get }
var setView: @Sendable (Pointer, ViewPointer) async -> Void { get }
}

View File

@ -6,18 +6,18 @@
//
/// Store a reference to a rendered view in a view storage.
public class ViewStorage {
public actor ViewStorage: Sendable {
/// The pointer.
///
/// It can be a C pointer, a Swift class, or other information depending on the backend.
public var pointer: Any?
public var pointer: Sendable?
/// The view's content for container widgets.
public var content: [String: [ViewStorage]]
var content: [String: [ViewStorage]]
/// The view's state (used in `StateWrapper`).
var state: [String: StateProtocol] = [:]
/// Various properties of a widget.
public var fields: [String: Any] = [:]
var fields: [String: Sendable] = [:]
/// The previous state of the widget.
public var previousState: Widget?
@ -36,7 +36,7 @@ public class ViewStorage {
/// - pointer: The pointer to the widget, its type depends on the backend.
/// - content: The view's content for container widgets.
public init(
_ pointer: Any?,
_ pointer: Sendable?,
content: [String: [ViewStorage]] = [:],
state: Widget? = nil
) {
@ -45,4 +45,88 @@ public class ViewStorage {
self.previousState = state
}
/// Initialize a view storage.
/// - Parameters:
/// - pointer: The opaque pointer.
/// - content: The view's content for container widgets.
public init(
_ pointer: OpaquePointer?,
content: [String: [ViewStorage]] = [:],
state: Widget? = nil
) {
self.pointer = pointer
self.content = content
self.previousState = state
}
/// Set the state under a certain key.
/// - Parameters:
/// - key: The key.
/// - value: The state.
func setState(key: String, value: StateProtocol) {
state[key] = value
}
/// Set the state.
/// - Parameter state: The state.
func setState(_ state: [String: StateProtocol]) {
self.state = state
}
/// Get the state under a certain key.
/// - Parameter key: The key.
/// - Returns: The state.
func getState(key: String) -> StateProtocol? {
state[key]
}
/// Set the pointer.
/// - Parameter value: The new pointer.
public func setPointer(_ value: Sendable?) {
pointer = value
}
/// Set the element of a certain field.
/// - Parameters:
/// - key: The key.
/// - value: The field.
public func setField(key: String, value: Sendable) {
fields[key] = value
}
/// Remove a certain field.
/// - Parameters:
/// - key: The key.
public func removeField(key: String) {
fields.removeValue(forKey: key)
}
/// Get the element of a certain field.
/// - Parameter key: The key.
/// - Returns: The field.
public func getField(key: String) -> Sendable? {
fields[key]
}
/// Set the content elements under a certain key.
/// - Parameters:
/// - key: The key.
/// - value: The content elements.
public func setContent(key: String, value: [ViewStorage]) {
content[key] = value
}
/// Get the content elements under a certain key.
/// - Parameter key: The key.
/// - Returns: The content elements.
public func getContent(key: String) -> [ViewStorage] {
content[key] ?? []
}
/// Set the previous state.
/// - Parameter state: The state.
public func setPreviousState(_ state: Widget?) {
previousState = state
}
}

View File

@ -19,7 +19,7 @@ public protocol Widget: AnyView {
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData
) async -> ViewStorage where Data: ViewRenderData
/// Update the stored content.
/// - Parameters:
@ -32,14 +32,14 @@ public protocol Widget: AnyView {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData
) async where Data: ViewRenderData
/// Get the widget.
/// - Returns: The widget.
///
/// Define this function only if you do not define ``Widget/container(data:type:)``.
/// Otherwise, it will not have an effect.
func initializeWidget() -> Any
func initializeWidget() -> Sendable
}
@ -51,7 +51,7 @@ extension Widget {
/// Print a warning if the widget does not set this function but it gets accessed.
/// - Returns: A dummy pointer.
public func initializeWidget() -> Any {
public func initializeWidget() -> Sendable {
print("Warning: Define initialize widget function or container function for \(Self.self)")
return ""
}

View File

@ -6,16 +6,16 @@
//
/// Data passed to widgets when initializing or updating the container.
public struct WidgetData {
public struct WidgetData: Sendable {
/// The view modifiers.
public var modifiers: [(AnyView) -> AnyView] = []
public var modifiers: [@Sendable (AnyView) -> AnyView] = []
/// The scene storage of the parent scene element.
public var sceneStorage: SceneStorage
/// The app storage of the parent app.
public var appStorage: any AppStorage
/// Fields for custom data.
public var fields: [String: Any] = [:]
public var fields: [String: Sendable] = [:]
/// Modify the data so that there are no modifiers.
public var noModifiers: Self {

View File

@ -9,7 +9,7 @@
struct AppearObserver: ConvenienceWidget {
/// The custom code to edit the widget.
var modify: (ViewStorage) -> Void
var modify: @Sendable (ViewStorage) async -> Void
/// The wrapped view.
var content: AnyView
@ -21,9 +21,9 @@ struct AppearObserver: ConvenienceWidget {
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let storage = content.storage(data: data, type: type)
modify(storage)
) async -> ViewStorage where Data: ViewRenderData {
let storage = await content.storage(data: data, type: type)
await modify(storage)
return storage
}
@ -38,8 +38,8 @@ struct AppearObserver: ConvenienceWidget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
) async where Data: ViewRenderData {
await content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}
}
@ -50,15 +50,15 @@ extension AnyView {
/// Run a function on the widget when it appears for the first time.
/// - Parameter closure: The function.
/// - Returns: A view.
public func inspectOnAppear(_ closure: @escaping (ViewStorage) -> Void) -> AnyView {
public func inspectOnAppear(_ closure: @Sendable @escaping (ViewStorage) async -> Void) -> AnyView {
AppearObserver(modify: closure, content: self)
}
/// Run a function when the view appears for the first time.
/// - Parameter closure: The function.
/// - Returns: A view.
public func onAppear(_ closure: @escaping () -> Void) -> AnyView {
inspectOnAppear { _ in closure() }
public func onAppear(_ closure: @Sendable @escaping () async -> Void) -> AnyView {
inspectOnAppear { _ in await closure() }
}
}

View File

@ -11,7 +11,7 @@ struct ContentModifier<Content>: ConvenienceWidget where Content: AnyView {
/// The wrapped view.
var content: AnyView
/// The closure for the modification.
var modify: (Content) -> AnyView
var modify: @Sendable (Content) -> AnyView
/// The view storage.
/// - Parameters:
@ -21,8 +21,8 @@ struct ContentModifier<Content>: ConvenienceWidget where Content: AnyView {
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
content.storage(data: data.modify { $0.modifiers += [modifyView] }, type: type)
) async -> ViewStorage where Data: ViewRenderData {
await content.storage(data: data.modify { $0.modifiers += [modifyView] }, type: type)
}
/// Update the stored content.
@ -36,8 +36,8 @@ struct ContentModifier<Content>: ConvenienceWidget where Content: AnyView {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
content
) async where Data: ViewRenderData {
await content
.updateStorage(
storage,
data: data.modify { $0.modifiers += [modifyView] },
@ -68,7 +68,7 @@ extension AnyView {
/// - Returns: A view.
public func modifyContent<Content>(
_ type: Content.Type,
modify: @escaping (Content) -> AnyView
modify: @Sendable @escaping (Content) -> AnyView
) -> AnyView where Content: AnyView {
ContentModifier(content: self, modify: modify)
}

View File

@ -23,9 +23,9 @@ struct DummyEitherView: ConvenienceWidget {
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
) async -> ViewStorage where Data: ViewRenderData {
let content = type.EitherViewType(condition) { view1 ?? [] } else: { view2 ?? [] }
let storage = content.storage(data: data, type: type)
let storage = await content.storage(data: data, type: type)
return storage
}
@ -40,9 +40,9 @@ struct DummyEitherView: ConvenienceWidget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
) async where Data: ViewRenderData {
let content = type.EitherViewType(condition) { view1 ?? [] } else: { view2 ?? [] }
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
await content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}
}

View File

@ -21,8 +21,8 @@ struct Freeze: ConvenienceWidget {
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
content.storage(data: data, type: type)
) async -> ViewStorage where Data: ViewRenderData {
await content.storage(data: data, type: type)
}
/// Update the stored content.
@ -36,11 +36,11 @@ struct Freeze: ConvenienceWidget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
) async where Data: ViewRenderData {
guard !freeze else {
return
}
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
await content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}
}

View File

@ -9,7 +9,7 @@
struct InspectorWrapper: ConvenienceWidget {
/// The custom code to edit the widget.
var modify: (ViewStorage, Bool) -> Void
var modify: @Sendable (ViewStorage, Bool) async -> Void
/// The wrapped view.
var content: AnyView
@ -21,9 +21,9 @@ struct InspectorWrapper: ConvenienceWidget {
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let storage = content.storage(data: data, type: type)
modify(storage, true)
) async -> ViewStorage where Data: ViewRenderData {
let storage = await content.storage(data: data, type: type)
await modify(storage, true)
return storage
}
@ -38,9 +38,9 @@ struct InspectorWrapper: ConvenienceWidget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
modify(storage, updateProperties)
) async where Data: ViewRenderData {
await content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
await modify(storage, updateProperties)
}
}
@ -51,15 +51,15 @@ extension AnyView {
/// Run a custom code accessing the view's storage when initializing and updating the view.
/// - Parameter modify: Modify the storage. The boolean indicates whether state in parent views changed.
/// - Returns: A view.
public func inspect(_ modify: @escaping (ViewStorage, Bool) -> Void) -> AnyView {
public func inspect(_ modify: @Sendable @escaping (ViewStorage, Bool) async -> Void) -> AnyView {
InspectorWrapper(modify: modify, content: self)
}
/// Run a function when the view gets updated.
/// - Parameter onUpdate: The function.
/// - Returns: A view.
public func onUpdate(_ onUpdate: @escaping () -> Void) -> AnyView {
inspect { _, _ in onUpdate() }
public func onUpdate(_ onUpdate: @Sendable @escaping () async -> Void) -> AnyView {
inspect { _, _ in await onUpdate() }
}
}

View File

@ -19,8 +19,8 @@ struct ModifierStopper: ConvenienceWidget {
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
content.storage(data: data.noModifiers, type: type)
) async -> ViewStorage where Data: ViewRenderData {
await content.storage(data: data.noModifiers, type: type)
}
/// Update the stored content.
@ -34,8 +34,8 @@ struct ModifierStopper: ConvenienceWidget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
content.updateStorage(storage, data: data.noModifiers, updateProperties: updateProperties, type: type)
) async where Data: ViewRenderData {
await content.updateStorage(storage, data: data.noModifiers, updateProperties: updateProperties, type: type)
}
}

View File

@ -9,13 +9,13 @@
struct StateWrapper: ConvenienceWidget {
/// The content.
var content: () -> Body
var content: @Sendable () -> Body
/// The state information (from properties with the `State` wrapper).
var state: [String: StateProtocol] = [:]
/// Initialize a `StateWrapper`.
/// - Parameter content: The view content.
init(@ViewBuilder content: @escaping () -> Body) {
init(@ViewBuilder content: @Sendable @escaping () -> Body) {
self.content = content
}
@ -23,7 +23,7 @@ struct StateWrapper: ConvenienceWidget {
/// - Parameters:
/// - content: The view content.
/// - state: The state information.
init(content: @escaping () -> Body, state: [String: StateProtocol]) {
init(content: @Sendable @escaping () -> Body, state: [String: StateProtocol]) {
self.content = content
self.state = state
}
@ -40,10 +40,10 @@ struct StateWrapper: ConvenienceWidget {
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
) async where Data: ViewRenderData {
var updateProperties = updateProperties
for property in state {
if let storage = storage.state[property.key]?.content.storage {
if let storage = await storage.getState(key: property.key)?.content.storage {
property.value.content.storage = storage
}
if property.value.content.update {
@ -51,10 +51,16 @@ struct StateWrapper: ConvenienceWidget {
property.value.content.update = false
}
}
guard let storage = storage.content[.mainContent]?.first else {
guard let storage = await storage.getContent(key: .mainContent).first else {
return
}
content().updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
await content().updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
for property in state {
if var value = property.value.content.value as? Signal, value.update {
value.destroySignal()
property.value.content.value = value
}
}
}
/// Get a view storage.
@ -65,10 +71,10 @@ struct StateWrapper: ConvenienceWidget {
func container<Data>(
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let content = content().storage(data: data, type: type)
let storage = ViewStorage(content.pointer, content: [.mainContent: [content]])
storage.state = state
) async -> ViewStorage where Data: ViewRenderData {
let content = await content().storage(data: data, type: type)
let storage = ViewStorage(await content.pointer, content: [.mainContent: [content]])
await storage.setState(state)
for element in state {
element.value.setup()
}

View File

@ -1,5 +1,5 @@
import Foundation
@testable import Meta
import Meta
import SampleBackends
@main
@ -73,7 +73,7 @@ struct TestView: View {
}
struct TestModel: Model {
struct TestModel: Sendable, Model {
var test = "Label"

View File

@ -6,16 +6,16 @@ public enum Backend1 {
public init() { }
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
print("Init test widget 1")
let storage = ViewStorage(nil)
storage.fields["test"] = 0
await storage.setField(key: "test", value: 0)
return storage
}
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
print("Update test widget 1 (#\(storage.fields["test"] ?? ""))")
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async {
print("Update test widget 1 (#\(await storage.getField(key: "test") ?? ""))")
await storage.setField(key: "test", value: (storage.getField(key: "test") as? Int ?? 0) + 1)
}
}
@ -24,16 +24,16 @@ public enum Backend1 {
public init() { }
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
print("Init test widget 3")
let storage = ViewStorage(nil)
storage.fields["test"] = 0
await storage.setField(key: "test", value: 0)
return storage
}
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
print("Update test widget 3 (#\(storage.fields["test"] ?? ""))")
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async {
print("Update test widget 3 (#\(await storage.getField(key: "test") ?? ""))")
await storage.setField(key: "test", value: (storage.getField(key: "test") as? Int ?? 0) + 1)
}
}
@ -42,28 +42,28 @@ public enum Backend1 {
@Property(set: { print("Update button (label = \($1))") }, pointer: Any.self)
var label = ""
@Property(set: { $2.fields["action"] = $1 }, pointer: Any.self)
var action: () -> Void = { }
@Property(set: { $2.setField(key: "action", value: $1) }, pointer: Any.self)
var action: @Sendable () -> Void = { }
public init(_ label: String, action: @escaping () -> Void) {
public init(_ label: String, action: @Sendable @escaping () -> Void) {
self.label = label
self.action = action
}
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
print("Init button")
let storage = ViewStorage(nil)
Task {
try await Task.sleep(nanoseconds: 1_000_000_000)
(storage.fields["action"] as? () -> Void)?()
(await storage.getField(key: "action") as? @Sendable () -> Void)?()
}
storage.fields["action"] = action
storage.previousState = self
await storage.setField(key: "action", value: action)
await storage.setPreviousState(self)
return storage
}
}
public struct Window: BackendSceneElement {
public struct Window: BackendSceneElement, Sendable {
public var id: String
var spawn: Int
@ -77,7 +77,9 @@ public enum Backend1 {
public func setupInitialContainers<Storage>(app: Storage) where Storage: AppStorage {
for _ in 0..<spawn {
app.storage.sceneStorage.append(container(app: app))
Task {
await app.appendScene(container(app: app))
}
}
}
@ -86,17 +88,21 @@ public enum Backend1 {
let storage = SceneStorage(id: id, pointer: nil) {
print("Make visible")
}
let viewStorage = content.storage(data: .init(sceneStorage: storage, appStorage: app), type: MainViewRenderData.self)
storage.content[.mainContent] = [viewStorage]
Task {
let viewStorage = await content.storage(data: .init(sceneStorage: storage, appStorage: app), type: MainViewRenderData.self)
await storage.setContent(key: .mainContent, value: [viewStorage])
}
return storage
}
public func update<Storage>(_ storage: SceneStorage, app: Storage, updateProperties: Bool) where Storage: AppStorage {
print("Update \(id)")
guard let viewStorage = storage.content[.mainContent]?.first else {
return
Task {
print("Update \(id)")
guard let viewStorage = await storage.getContent(key: .mainContent).first else {
return
}
await content.updateStorage(viewStorage, data: .init(sceneStorage: storage, appStorage: app), updateProperties: updateProperties, type: MainViewRenderData.self)
}
content.updateStorage(viewStorage, data: .init(sceneStorage: storage, appStorage: app), updateProperties: updateProperties, type: MainViewRenderData.self)
}
}
@ -109,17 +115,15 @@ public enum Backend1 {
self.content = content()
}
public func container<Data>(data: WidgetData, type: Data.Type) -> Meta.ViewStorage where Data: ViewRenderData {
public func container<Data>(data: WidgetData, type: Data.Type) async -> Meta.ViewStorage where Data: ViewRenderData {
let storage = ViewStorage(nil)
storage.content = [.mainContent: content.storages(data: data, type: type)]
await storage.setContent(key: .mainContent, value: content.storages(data: data, type: type))
return storage
}
public func update<Data>(_ storage: Meta.ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) where Data: ViewRenderData {
guard let storages = storage.content[.mainContent] else {
return
}
content.update(storages, data: data, updateProperties: updateProperties, type: type)
public func update<Data>(_ storage: Meta.ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async where Data: ViewRenderData {
let storages = await storage.getContent(key: .mainContent)
await content.update(storages, data: data, updateProperties: updateProperties, type: type)
}
}
@ -150,19 +154,19 @@ public enum Backend1 {
public protocol BackendSceneElement: SceneElement { }
public class Backend1App: AppStorage {
public actor Backend1App: AppStorage {
public typealias SceneElementType = BackendSceneElement
public var storage: StandardAppStorage = .init()
public required init(id: String) { }
public init(id: String) { }
public func run(setup: @escaping () -> Void) {
nonisolated public func run(setup: @escaping () -> Void) {
setup()
}
public func quit() {
nonisolated public func quit() {
fatalError("Quit")
}

View File

@ -6,16 +6,16 @@ public enum Backend2 {
public init() { }
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
print("Init test widget 2")
let storage = ViewStorage(nil)
storage.fields["test"] = 0
await storage.setField(key: "test", value: 0)
return storage
}
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
print("Update test widget 2 (#\(storage.fields["test"] ?? ""))")
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async {
print("Update test widget 2 (#\(await storage.getField(key: "test") ?? ""))")
await storage.setField(key: "test", value: (storage.getField(key: "test") as? Int ?? 0) + 1)
}
}
@ -24,16 +24,16 @@ public enum Backend2 {
public init() { }
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
print("Init test widget 4")
let storage = ViewStorage(nil)
storage.fields["test"] = 0
await storage.setField(key: "test", value: 0)
return storage
}
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
print("Update test widget 4 (#\(storage.fields["test"] ?? ""))")
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async {
print("Update test widget 4 (#\(await storage.getField(key: "test") ?? ""))")
await storage.setField(key: "test", value: (storage.getField(key: "test") as? Int ?? 0) + 1)
}
}
@ -46,17 +46,15 @@ public enum Backend2 {
self.content = content()
}
public func container<Data>(data: WidgetData, type: Data.Type) -> Meta.ViewStorage where Data: ViewRenderData {
public func container<Data>(data: WidgetData, type: Data.Type) async -> Meta.ViewStorage where Data: ViewRenderData {
let storage = ViewStorage(nil)
storage.content = [.mainContent: content.storages(data: data, type: type)]
await storage.setContent(key: .mainContent, value: content.storages(data: data, type: type))
return storage
}
public func update<Data>(_ storage: Meta.ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) where Data: ViewRenderData {
guard let storages = storage.content[.mainContent] else {
return
}
content.update(storages, data: data, updateProperties: updateProperties, type: type)
public func update<Data>(_ storage: Meta.ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async where Data: ViewRenderData {
let storages = await storage.getContent(key: .mainContent)
await content.update(storages, data: data, updateProperties: updateProperties, type: type)
}
}
@ -65,19 +63,19 @@ public enum Backend2 {
public protocol BackendSceneElement: SceneElement { }
public class Backend2App: AppStorage {
public actor Backend2App: AppStorage{
public typealias SceneElementType = BackendSceneElement
public var storage: StandardAppStorage = .init()
public required init(id: String) { }
public init(id: String) { }
public func run(setup: @escaping () -> Void) {
nonisolated public func run(setup: @escaping () -> Void) {
setup()
}
public func quit() {
nonisolated public func quit() {
fatalError("Quit")
}