Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow to dynamically load and unload Modules #105

Merged
merged 7 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,5 @@ jobs:
uses: StanfordSpezi/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
with:
coveragereports: Spezi-Package-iOS.xcresult Spezi-Package-watchOS.xcresult Spezi-Package-visionOS.xcresult Spezi-Package-tvOS.xcresult Spezi-Package-macOS.xcresult TestApp-iOS.xcresult TestApp-visionOS.xcresult
secrets:
token: ${{ secrets.CODECOV_TOKEN }}
5 changes: 5 additions & 0 deletions Sources/Spezi/Capabilities/ApplicationPropertyWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ extension _ApplicationPropertyWrapper: SpeziPropertyWrapper {
self.shadowCopy = spezi[keyPath: keyPath]
}
}

func clear() {
spezi = nil
shadowCopy = nil
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ public class _CollectPropertyWrapper<Value> {
}


extension _CollectPropertyWrapper: StorageValueCollector {
public func retrieve<Repository: SharedRepository<SpeziAnchor>>(from repository: Repository) {
injectedValues = repository[CollectedModuleValues<Value>.self]?.map { $0.value } ?? []
}

func clear() {
injectedValues = nil
}
}


extension Module {
/// The `@Collect` property wrapper can be used to retrieve data communicated by other `Module`s.
///
Expand All @@ -63,10 +74,3 @@ extension Module {
/// ```
public typealias Collect = _CollectPropertyWrapper
}


extension _CollectPropertyWrapper: StorageValueCollector {
public func retrieve<Repository: SharedRepository<SpeziAnchor>>(from repository: Repository) {
injectedValues = repository[CollectedModuleValue<Value>.self] ?? []
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SpeziFoundation

protocol AnyCollectModuleValue {
associatedtype Value

var moduleReference: ModuleReference { get }
}

protocol AnyCollectModuleValues {
associatedtype Value

var values: [any AnyCollectModuleValue] { get }

mutating func removeValues(from module: any Module) -> Bool

func store(into storage: inout SpeziStorage)
}


struct CollectModuleValue<Value>: AnyCollectModuleValue {
let value: Value
let moduleReference: ModuleReference

init(_ value: Value) {
self.value = value

guard let module = Spezi.moduleInitContext else {
preconditionFailure("Tried to initialize CollectModuleValue with unknown module init context.")

Check warning on line 36 in Sources/Spezi/Capabilities/Communication/CollectedModuleValues.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Capabilities/Communication/CollectedModuleValues.swift#L36

Added line #L36 was not covered by tests
}
self.moduleReference = ModuleReference(module)
}
}

/// Provides the ``KnowledgeSource`` for any value we store in the ``SpeziStorage`` that is
/// provided or request from am ``Module``.
///
/// For more information, look at the ``Module/Provide`` or ``Module/Collect`` property wrappers.
struct CollectedModuleValues<ModuleValue>: DefaultProvidingKnowledgeSource {
typealias Anchor = SpeziAnchor

typealias Value = [CollectModuleValue<ModuleValue>]


static var defaultValue: Value {
[]
}
}


extension Array: AnyCollectModuleValues where Element: AnyCollectModuleValue {
typealias Value = Element.Value

var values: [any AnyCollectModuleValue] {
self
}

Check warning on line 63 in Sources/Spezi/Capabilities/Communication/CollectedModuleValues.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Capabilities/Communication/CollectedModuleValues.swift#L61-L63

Added lines #L61 - L63 were not covered by tests

mutating func removeValues(from module: any Module) -> Bool {
let previousCount = count
removeAll { entry in
entry.moduleReference == ModuleReference(module)
}
return previousCount != count
}

func store(into storage: inout SpeziStorage) {
guard let values = self as? [CollectModuleValue<Value>] else {
preconditionFailure("Unexpected array type: \(type(of: self))")

Check warning on line 75 in Sources/Spezi/Capabilities/Communication/CollectedModuleValues.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Spezi/Capabilities/Communication/CollectedModuleValues.swift#L75

Added line #L75 was not covered by tests
}
storage[CollectedModuleValues<Value>.self] = values
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,27 +116,38 @@ extension _ProvidePropertyWrapper: StorageValueProvider {
} else if let wrapperWithArray = self as? CollectionBasedProvideProperty {
wrapperWithArray.collectArrayElements(into: &repository)
} else {
// concatenation is handled by the `CollectedModuleValue/reduce` implementation.
repository[CollectedModuleValue<Value>.self] = [storedValue]
repository.appendValues([CollectModuleValue(storedValue)])
}

collected = true
}

func clear() {
collected = false
}
}


extension _ProvidePropertyWrapper: CollectionBasedProvideProperty where Value: AnyArray {
func collectArrayElements<Repository: SharedRepository<SpeziAnchor>>(into repository: inout Repository) {
// concatenation is handled by the `CollectedModuleValue/reduce` implementation.
repository[CollectedModuleValue<Value.Element>.self] = storedValue.unwrappedArray
repository.appendValues(storedValue.unwrappedArray.map { CollectModuleValue($0) })
}
}


extension _ProvidePropertyWrapper: OptionalBasedProvideProperty where Value: AnyOptional {
func collectOptional<Repository: SharedRepository<SpeziAnchor>>(into repository: inout Repository) {
if let storedValue = storedValue.unwrappedOptional {
repository[CollectedModuleValue<Value.Wrapped>.self] = [storedValue]
repository.appendValues([CollectModuleValue(storedValue)])
}
}
}


extension SharedRepository where Anchor == SpeziAnchor {
fileprivate mutating func appendValues<Value>(_ values: [CollectModuleValue<Value>]) {
var current = self[CollectedModuleValues<Value>.self]
current.append(contentsOf: values)
self[CollectedModuleValues<Value>.self] = current
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SpeziFoundation
/// data provided by other ``Module``s.
///
/// Data requested through a Storage Value Collector might be provided through a ``_StorageValueProvider``.
protocol StorageValueCollector {
protocol StorageValueCollector: SpeziPropertyWrapper {
/// This method is called to retrieve all the requested values from the given ``SpeziStorage`` repository.
/// - Parameter repository: Provides access to the ``SpeziStorage`` repository for read access.
func retrieve<Repository: SharedRepository<SpeziAnchor>>(from repository: Repository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SpeziFoundation
/// data with other ``Module``s.
///
/// Data provided through a Storage Value Provider can be retrieved through a ``_StorageValueCollector``.
protocol StorageValueProvider {
protocol StorageValueProvider: SpeziPropertyWrapper {
/// This method is called to collect all provided values into the given ``SpeziStorage`` repository.
/// - Parameter repository: Provides access to the ``SpeziStorage`` repository.
func collect<Repository: SharedRepository<SpeziAnchor>>(into repository: inout Repository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ public class _ModelPropertyWrapper<Model: Observable & AnyObject> {
}


extension _ModelPropertyWrapper: SpeziPropertyWrapper {
func clear() {
collected = false
}
}


extension Module {
/// Places an observable object in the global view environment.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public class _ModifierPropertyWrapper<Modifier: ViewModifier> {
}


extension _ModifierPropertyWrapper: SpeziPropertyWrapper {
func clear() {
collected = false
}
}


extension Module {
/// Provide a SwiftUI `ViewModifier` to modify the global view hierarchy.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protocol ViewModifierProvider {

/// Defines the placement order of this view modifier.
///
/// `ViewModifier`s retrieved from a ``Module` might modify the view hierarchy in a different order than they
/// `ViewModifier`s retrieved from a ``Module`` might modify the view hierarchy in a different order than they
/// are supplied. This is important to, e.g., ensure that modifiers injecting model types are placed at the outermost
/// level to ensure other view modifiers supplied by the module can access those model types.
var placement: ModifierPlacement { get }
Expand Down
Loading
Loading