diff --git a/Package.resolved b/Package.resolved index 4d55135b..7905b930 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,7 +6,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenGraph", "state" : { "branch" : "main", - "revision" : "10eb096bfdefd73bb50df410b87602208bcc06c1" + "revision" : "2435a0a02b3ab3b97799a8f387284686fe9e6a7a" } }, { diff --git a/Scripts/build.sh b/Scripts/build.sh new file mode 100755 index 00000000..c76a8680 --- /dev/null +++ b/Scripts/build.sh @@ -0,0 +1,14 @@ +#!/bin/zsh + +# A `realpath` alternative using the default C implementation. +filepath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" +} + +OPENSWIFTUI_ROOT="$(dirname $(dirname $(filepath $0)))" + +cd $OPENSWIFTUI_ROOT + +export OPENSWIFTUI_SWIFT_TESTING=0 +export OPENGRAPH_SWIFT_TESTING=0 +swift build diff --git a/Sources/OpenSwiftUI/Core/Attribute/InvalidatableAttribute.swift b/Sources/OpenSwiftUI/Core/Attribute/InvalidatableAttribute.swift new file mode 100644 index 00000000..af23a01c --- /dev/null +++ b/Sources/OpenSwiftUI/Core/Attribute/InvalidatableAttribute.swift @@ -0,0 +1,14 @@ +// +// InvalidatableAttribute.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP + +internal import OpenGraphShims + +// TODO: PlatformViewChild in _A513612C07DFA438E70B9FA90719B40D + +protocol InvalidatableAttribute: _AttributeBody { + static func willInvalidate(attribute: OGAttribute) +} diff --git a/Sources/OpenSwiftUI/Core/Attribute/RemovableAttribute.swift b/Sources/OpenSwiftUI/Core/Attribute/RemovableAttribute.swift new file mode 100644 index 00000000..21f54b4d --- /dev/null +++ b/Sources/OpenSwiftUI/Core/Attribute/RemovableAttribute.swift @@ -0,0 +1,13 @@ +// +// RemovableAttribute.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP + +internal import OpenGraphShims + +protocol RemovableAttribute: _AttributeBody { + static func willRemove(attribute: OGAttribute) + static func didReinsert(attribute: OGAttribute) +} diff --git a/Sources/OpenSwiftUI/Core/Data/Location/ObservableObjectLocation.swift b/Sources/OpenSwiftUI/Core/Data/Location/ObservableObjectLocation.swift index 6634da6a..6848e0bf 100644 --- a/Sources/OpenSwiftUI/Core/Data/Location/ObservableObjectLocation.swift +++ b/Sources/OpenSwiftUI/Core/Data/Location/ObservableObjectLocation.swift @@ -37,3 +37,8 @@ struct ObservableObjectLocation: Location where Root: ObservableObj base[keyPath: keyPath] = value } } + +extension ObservableObjectLocation: TransactionHostProvider { + // TODO + var mutationHost: GraphHost? { nil } +} diff --git a/Sources/OpenSwiftUI/Core/Graph/Extensions/OGGraph.swift b/Sources/OpenSwiftUI/Core/Graph/Extensions/OGGraph.swift new file mode 100644 index 00000000..c49d668d --- /dev/null +++ b/Sources/OpenSwiftUI/Core/Graph/Extensions/OGGraph.swift @@ -0,0 +1,5 @@ +internal import OpenGraphShims + +extension OGGraph { + +} diff --git a/Sources/OpenSwiftUI/Core/Graph/Extensions/OGSubgraph.swift b/Sources/OpenSwiftUI/Core/Graph/Extensions/OGSubgraph.swift new file mode 100644 index 00000000..d3cddf5c --- /dev/null +++ b/Sources/OpenSwiftUI/Core/Graph/Extensions/OGSubgraph.swift @@ -0,0 +1,38 @@ +internal import OpenGraphShims + +extension OGSubgraph { + func willRemove() { + #if canImport(Darwin) + OGSubgraph.apply(self, flags: .removable) { attribute in + let type = attribute._bodyType + if let removableType = type as? RemovableAttribute.Type { + removableType.willRemove(attribute: attribute) + } + } + #endif + } + + func didReinsert() { + #if canImport(Darwin) + OGSubgraph.apply(self, flags: .removable) { attribute in + let type = attribute._bodyType + if let removableType = type as? RemovableAttribute.Type { + removableType.didReinsert(attribute: attribute) + } + } + #endif + } + + func willInvalidate(isInserted: Bool) { + #if canImport(Darwin) + OGSubgraph.apply(self, flags: isInserted ? [.removable, .invalidatable] : [.invalidatable]) { attribute in + let type = attribute._bodyType + if let invalidatableType = type as? InvalidatableAttribute.Type { + invalidatableType.willInvalidate(attribute: attribute) + } else if isInserted, let removableType = type as? RemovableAttribute.Type { + removableType.willRemove(attribute: attribute) + } + } + #endif + } +} diff --git a/Sources/OpenSwiftUI/Core/Graph/GraphHost.swift b/Sources/OpenSwiftUI/Core/Graph/GraphHost.swift index c7575b33..391c2cbe 100644 --- a/Sources/OpenSwiftUI/Core/Graph/GraphHost.swift +++ b/Sources/OpenSwiftUI/Core/Graph/GraphHost.swift @@ -2,47 +2,98 @@ // GraphHost.swift // OpenSwiftUI // -// Lastest Version: iOS 15.5 +// Audited for RELEASE_2021 // Status: WIP // ID: 30C09FF16BC95EC5173809B57186CAC3 internal import COpenSwiftUI internal import OpenGraphShims +private let waitingForPreviewThunks = EnvironmentHelper.value(for: "XCODE_RUNNING_FOR_PREVIEWS") != 0 +private var blockedGraphHosts: [Unmanaged] = [] + class GraphHost { - var data: Data - var isInstantiated: Bool - var hostPreferenceValues: OptionalAttribute - var lastHostPreferencesSeed: VersionSeed - private var pendingTransactions: [AsyncTransaction] - var inTransaction: Bool - var continuations: [() -> ()] - var mayDeferUpdate: Bool - var removedState: RemovedState - - // FIXME - static var isUpdating = false + // MARK: - Properties - private static let sharedGraph = OGGraph() + private(set) var data: Data + private(set) var isInstantiated = false + private(set) var hostPreferenceValues: OptionalAttribute + private(set) var lastHostPreferencesSeed: VersionSeed = .invalid + private var pendingTransactions: [AsyncTransaction] = [] + private(set) var inTransaction = false + private(set) var continuations: [() -> Void] = [] + private(set) var mayDeferUpdate = true + private(set) var removedState: RemovedState = [] - // MARK: - non final methods + // MARK: - static properties and methods + + static var currentHost: GraphHost { + #if canImport(Darwin) + if let currentAttribute = OGAttribute.current { + currentAttribute.graph.graphHost() + } else if let currentSubgraph = OGSubgraph.current { + currentSubgraph.graph.graphHost() + } else { + fatalError("no current graph host") + } + #else + fatalError("Compiler issue on Linux. See #39") + #endif + } + + static var isUpdating: Bool { + sharedGraph.counter(for: ._7) != 0 + } + + static func globalTransaction( + _ transaction: Transaction, + mutation: Mutation, + hostProvider: TransactionHostProvider + ) { + fatalError("TODO") + } + + private static func flushGlobalTransactions() { + fatalError("TODO") + } + + private static let sharedGraph = OGGraph() + private static var pendingGlobalTransactions: [GlobalTransaction] = [] + + // MARK: - inheritable methods init(data: Data) { + #if canImport(Darwin) + if !Thread.isMainThread { + Log.runtimeIssues("calling into OpenSwiftUI on a non-main thread is not supported") + } + #endif + hostPreferenceValues = OptionalAttribute() self.data = data - isInstantiated = false - // TODO - fatalError("TODO") + OGGraph.setUpdateCallback(graph) { [weak self] in + guard let self, + let graphDelegate + else { return } + graphDelegate.updateGraph { _ in } + } + #if canImport(Darwin) + OGGraph.setInvalidationCallback(graph) { [weak self] attribute in + guard let self else { return } + graphInvalidation(from: attribute) + } + #endif + graph.setGraphHost(self) } func invalidate() { if isInstantiated { - // data.globalSubgraph.apply + data.globalSubgraph.willInvalidate(isInserted: false) isInstantiated = false } - if let _ = data.graph { + if let graph = data.graph { data.globalSubgraph.invalidate() - // graph.context = nil - // graph.invalidate() + graph.context = nil + graph.invalidate() data.graph = nil } } @@ -56,6 +107,80 @@ class GraphHost { // MARK: - final methods + deinit { + invalidate() + blockedGraphHosts.removeAll { $0.takeUnretainedValue() === self } + } + + // MARK: - data + + final var graph: OGGraph { data.graph! } + final var graphInputs: _GraphInputs { data.inputs } + + final func setTime(_ time: Time) { + guard data.time == time else { + return + } + data.time = time + timeDidChange() + } + + final var environment: EnvironmentValues { data.environment } + + final func setEnvironment(_ environment: EnvironmentValues) { + data.environment = environment + } + + final func setPhase(_ phase: _GraphInputs.Phase) { + data.phase = phase + } + + // TODO: _ArchivedViewHost.reset() + final func incrementPhase() { + var data = data + data.phase.value += 2 + graphDelegate?.graphDidChange() + } + + final func preferenceValues() -> PreferenceList { + instantiateIfNeeded() + return hostPreferenceValues.value ?? PreferenceList() + } + + final func preferenceValue(_ key: Key.Type) -> Key.Value { + fatalError("TODO") + } + + final func addPreference(_ key: Key.Type) { + fatalError("TODO") + } + + final func updatePreferences() -> Bool { + fatalError("TODO") + } + + final func updateRemovedState() { + fatalError("TODO") + } + + final func intern(_ value: Value, id: _GraphInputs.ConstantID) -> Attribute { + fatalError("TODO") + } + + final func setNeedsUpdate(mayDeferUpdate: Bool) { + fatalError("TODO") + } + + // MARK: - instantiate and uninstantiate + + final var isValid: Bool { data.graph != nil } + final var isUpdating: Bool { + guard let graph = data.graph else { + return false + } + return graph.counter(for: ._6) != 0 + } + final func instantiate() { guard !isInstantiated else { return @@ -64,6 +189,95 @@ class GraphHost { instantiateOutputs() isInstantiated = true } + + final func instantiateIfNeeded() { + guard !isInstantiated else { + return + } + if waitingForPreviewThunks { + if !blockedGraphHosts.contains(where: { $0.takeUnretainedValue() === self }) { + blockedGraphHosts.append(.passUnretained(self)) + } + } else { + instantiate() + } + } + + final func uninstantiate(immediately: Bool) { + guard isInstantiated else { + return + } + // TODO + } + + final func graphInvalidation(from attribute: OGAttribute?) { + #if canImport(Darwin) + guard let attribute else { + graphDelegate?.graphDidChange() + return + } + let host = attribute.graph.graphHost() + let transaction = host.data.transaction + mayDeferUpdate = mayDeferUpdate ? host.mayDeferUpdate : false + if transaction.isEmpty { + graphDelegate?.graphDidChange() + } else { + asyncTransaction( + transaction, + mutation: EmptyGraphMutation(), + style: ._1, + mayDeferUpdate: true + ) + } + #endif + } + + // MARK: - Transaction + + final var hasPendingTransactions: Bool { !pendingTransactions.isEmpty } + + final func asyncTransaction( + _ transaction: Transaction, + mutation: Mutation, + style: _GraphMutation_Style, + mayDeferUpdate: Bool + ) { + // TODO + } + + final func flushTransactions() { + guard isValid else { + return + } + guard !pendingTransactions.isEmpty else { + return + } + let transactions = pendingTransactions + pendingTransactions = [] + for _ in transactions { + instantiateIfNeeded() + // TODO + } + graphDelegate?.graphDidChange() + mayDeferUpdate = true + } + + final func continueTransaction(_ body: @escaping () -> Void) { + var host = self + while(!host.inTransaction) { + guard let parent = host.parentHost else { + asyncTransaction( + Transaction(), + mutation: CustomGraphMutation(body: body), + style: ._1, + mayDeferUpdate: true + ) + return + } + host = parent + } + host.continuations.append(body) + } } // MARK: - GraphHost.Data @@ -149,3 +363,25 @@ private final class AsyncTransaction { } } } + +// MARK: - OGGraph + Extension + +extension OGGraph { + final func graphHost() -> GraphHost { + context!.assumingMemoryBound(to: GraphHost.self).pointee + } + + fileprivate final func setGraphHost(_ graphHost: GraphHost) { + context = UnsafeRawPointer(Unmanaged.passUnretained(graphHost).toOpaque()) + } +} + +// MARK: - GlobalTransaction + +private final class GlobalTransaction { + let hostProvider: TransactionHostProvider + + init(transaction _: Transaction, hostProvider: TransactionHostProvider) { + self.hostProvider = hostProvider + } +} diff --git a/Sources/OpenSwiftUI/Core/Graph/GraphInputs.swift b/Sources/OpenSwiftUI/Core/Graph/GraphInputs.swift index 516ab115..d2b1476a 100644 --- a/Sources/OpenSwiftUI/Core/Graph/GraphInputs.swift +++ b/Sources/OpenSwiftUI/Core/Graph/GraphInputs.swift @@ -1,11 +1,5 @@ internal import OpenGraphShims -// FIXME: Compile crash on non-Darwin platform -// https://github.com/OpenSwiftUIProject/OpenSwiftUI/issues/39 -#if !canImport(Darwin) -typealias OGAttribute = UInt32 -#endif - public struct _GraphInputs { var customInputs: PropertyList var time: Attribute