Skip to content

Commit

Permalink
#35: WIP: Starting AsyncLazy again
Browse files Browse the repository at this point in the history
  • Loading branch information
KyNorthstar committed Dec 27, 2024
1 parent 3ffb734 commit b6e31d0
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ Initial movement from 4.x.
- This should also help with SEO and intuition. `import Lazy` makes more sense when you want to import lazy behavior, compared to `import LazyContainers`
- 6.x will still ship a product named `LazyContainers` for transitionary purposes, but it's deprecated and discoruaged. The `LazyContainers` product will be exactly the same as `Lazy` until it is removed in a future version.

- Renaming the `LazyContainer` protocol to `LazyProtocol`
- Included a migration-assistant typealias to facilitate the transition

- Full migration to Swift Package Manager
- It's now clear that SPM is the best way to distribute & use Swift packages, so this update removes CocoaPods support and no longer considers any other way of importing this package.

Expand Down
4 changes: 4 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import PackageDescription
let package = Package(
name: "LazyContainers",

platforms: [
.macOS(.v10_15),
],

products: [
.library(
name: "LazyContainers",
Expand Down
18 changes: 18 additions & 0 deletions Sources/Lazy/AnyLazy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// AnyLazy.swift
// https://github.com/RougeWare/Swift-Lazy-Containers
//
// Created by Ky on 2024-12-26.
// Copyright waived. No rights reserved.
//

import Foundation



/// The protocol to which all lazy protocols & types conform
public protocol AnyLazy {

/// The type of the value that will be lazily-initialized
associatedtype Value
}
151 changes: 151 additions & 0 deletions Sources/Lazy/AsyncLazy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// AsyncLazy.swift
// https://github.com/RougeWare/Swift-Lazy-Containers
//
// Created by Ky on 2024-12-26.
// Copyright waived. No rights reserved.
//

import Foundation



//@propertyWrapper // Property wrappers currently cannot define an 'async' or 'throws' accessor
public struct AsyncLazy<Value: Sendable> {

private var _guts: AsyncValueReference<AsyncValueHolder>


// init(initializer: @escaping AsyncInitializer<Value>) {
// self.init(_guts: .init(wrappedValue: .unset(initializer: initializer)))
// }


/// Allows other initializers to have a shared point of initialization
private init(_guts: AsyncValueReference<AsyncValueHolder>) {
self._guts = _guts
}


public nonisolated var wrappedValue: Value {
get async { await _guts.wrappedValue.wrappedValue }
}
}



extension AsyncLazy: AsyncLazyProtocol
where Value: Sendable
{
public mutating func mutate(_ isolatedMutator: (inout Value) async -> Void) async {
await _guts.mutate { asyncLazyContainerValueHolder in
await asyncLazyContainerValueHolder.mutate { value in
await isolatedMutator(&value)
}
}
}

public func get() async -> Value {
await _guts.wrappedValue.wrappedValue
}


public nonisolated func set(to newValue: sending Value) async {
await _guts.set(to: .hasValue(value: newValue))
}


public static func preinitialized(_ initialValue: Value) -> Self {
Self.init(_guts: .init(wrappedValue: .hasValue(value: initialValue)))
}


public var isInitialized: Bool {
get async { await _guts.wrappedValue.hasValue }
}


public func initializeNow() async {
await _guts.mutate { wrappedValue in
await wrappedValue.initializeNow()
}
}
}



// MARK: - ValueHolder

/// Takes care of keeping track of the state, value, and initializer of a lazy container, as needed
//@propertyWrapper // Property wrappers currently cannot define an 'async' or 'throws' accessor
public enum AsyncLazyContainerValueHolder<Value: Sendable>: Sendable {

/// Indicates that a value has been cached, and contains that cached value
case hasValue(value: Value)

/// Indicates that the value has not yet been created, and contains its initializer
case unset(initializer: AsyncInitializer<Value>)


/// The value held inside this value holder.
/// - Attention: Reading this value may mutate the state in order to compute the value. The complexity of that read
/// operation is equal to the complexity of the initializer.
public var wrappedValue: Value {
mutating get async {
switch self {
case .hasValue(let value):
return value

case .unset(let initializer):
let value = await initializer()
self = .hasValue(value: value)
return value
}
}
}


/// Sets the value asynchronously
public mutating func set(to newValue: Value) async {
self = .hasValue(value: newValue)
}


/// Indicates whether this holder actually holds a value.
/// This will be `true` after reading or writing `wrappedValue`.
public var hasValue: Bool {
switch self {
case .hasValue(value: _): return true
case .unset(initializer: _): return false
}
}


/// Immediately initializes the value held inside this value holder
///
/// If this holder already contains a value, this does nothing
mutating func initializeNow() async {
switch self {
case .hasValue(_): return
case .unset(let initializer):
self = await .hasValue(value: initializer())
}
}


mutating func mutate(_ isolatedMutator: (inout Value) async -> Void) async {
var wrappedValue = await self.wrappedValue
await isolatedMutator(&wrappedValue)
await self.set(to: wrappedValue)
}
}




public extension AnyLazy {

/// Takes care of keeping track of the state, value, and initializer as needed
typealias AsyncValueHolder = AsyncLazyContainerValueHolder<Value>
}

93 changes: 93 additions & 0 deletions Sources/Lazy/AsyncLazyProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// AsyncLazyProtocol.swift
// https://github.com/RougeWare/Swift-Lazy-Containers
//
// Created by Ky on 2024-12-26.
// Copyright waived. No rights reserved.
//

import Foundation



/// Simply initializes a value
public typealias AsyncInitializer<Value> = @Sendable () async -> Value



/// Defines how a lazy container should look when it performs all its operations asynchronously
public protocol AsyncLazyProtocol: AnyLazy {

/// Gets the value, possibly initializing it first
mutating func get() async -> Value


/// Sets the value asynchronously
mutating func set(to newValue: sending Value) async // If you feel like you want this to be nonmutating, see https://GitHub.com/RougeWare/Swift-Safe-Pointer


/// Mutate the value within the context of this container
/// - Parameter isolatedMutator: Mutates the wrapped value within the proper isolation context
mutating func mutate(_ isolatedMutator: (inout Value) async -> Void) async


/// Indicates whether the value has indeed been initialized
var isInitialized: Bool { get async }


/// Immediately initializes the value held inside this lazy container
///
/// If this holder already contains a value, this does nothing
// https://github.com/RougeWare/Swift-Lazy-Containers/issues/40
mutating func initializeNow() async


/// Creates a lazy container that already contains an initialized value.
///
/// This is useful when you need a uniform API (for instance, when implementing a protocol that requires a `Lazy`),
/// but require it to already hold a value up-front
///
/// - Parameter initialValue: The value to immediately store in the otherwise-lazy container
static func preinitialized(_ initialValue: Value) -> Self
}



// MARK: - ValueReference

/// Allows you to use reference-semantics to hold a value inside a lazy container
//@propertyWrapper // Property wrappers currently cannot define an 'async' or 'throws' accessor
public final actor LazyAsyncValueReference<Value: Sendable & Copyable> {

/// Holds some value- or reference-passed instance inside a reference-passed one
public var wrappedValue: Value


/// Creates a reference to the given value- or reference-passed instance
///
/// - Parameter wrappedValue: The instance to wrap
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}


func set(to newValue: Value) {
wrappedValue = newValue
}


func mutate(_ isolatedMutator: @Sendable (inout Value) async -> Void) async {
var wrappedValue = self.wrappedValue
await isolatedMutator(&wrappedValue)
self.wrappedValue = wrappedValue
}
}



public extension AnyLazy {

/// Allows you to use reference semantics to hold a value inside a lazy container.
typealias AsyncValueReference = LazyAsyncValueReference
}

2 changes: 1 addition & 1 deletion Sources/Lazy/FunctionalLazy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Foundation
/// - Attention: This is theoretically thread-safe, but hasn't undergone rigorous real-world testing. A short-lived
/// semaphore was added to mitigate this, but again, it hasn't undergone rigorous real-world testing.
@propertyWrapper
public struct FunctionalLazy<Value>: LazyContainer {
public struct FunctionalLazy<Value>: LazyProtocol {

/// Privatizes the inner-workings of this functional lazy container
@Guts
Expand Down
4 changes: 2 additions & 2 deletions Sources/Lazy/Lazy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Foundation
/// - Attention: Because of the extra logic and memory required for this behavior, it's recommended that you use the
/// language's built-in `lazy` instead wherever possible.
@propertyWrapper
public struct Lazy<Value>: LazyContainer {
public struct Lazy<Value>: LazyProtocol {

/// Privatizes the inner-workings of this functional lazy container
@ValueReference
Expand Down Expand Up @@ -142,7 +142,7 @@ public enum LazyContainerValueHolder<Value> {



public extension LazyContainer {
public extension AnyLazy {

/// Takes care of keeping track of the state, value, and initializer as needed
typealias ValueHolder = LazyContainerValueHolder<Value>
Expand Down
4 changes: 2 additions & 2 deletions Sources/Lazy/LazyContainer + Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation

// MARK: - Encodable

public extension LazyContainer where Self: Encodable, Value: Encodable {
public extension LazyProtocol where Self: Encodable, Value: Encodable {
func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
Expand All @@ -27,7 +27,7 @@ extension FunctionalLazy: Encodable where Value: Encodable {}

// MARK: - Decodable

public extension LazyContainer where Self: Decodable, Value: Decodable {
public extension LazyProtocol where Self: Decodable, Value: Decodable {
init(from decoder: Decoder) throws {
self = .preinitialized(try Value(from: decoder))
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Lazy/LazyContainer + Equatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation



public extension LazyContainer where Self: Equatable, Value: Equatable {
public extension LazyProtocol where Self: Equatable, Value: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.wrappedValue == rhs.wrappedValue
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Lazy/LazyContainer + Hashable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation



public extension LazyContainer where Self: Hashable, Value: Hashable {
public extension LazyProtocol where Self: Hashable, Value: Hashable {
func hash(into hasher: inout Hasher) {
wrappedValue.hash(into: &hasher)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// LazyContainer.swift
// LazyProtocol.swift
// https://github.com/RougeWare/Swift-Lazy-Containers
//
// Created by Ky on 2024-12-26.
Expand All @@ -17,11 +17,10 @@ public typealias Initializer<Value> = () -> Value



// MARK: -

/// Defines how a lazy container should look
public protocol LazyContainer {

/// The type of the value that will be lazily-initialized
associatedtype Value
public protocol LazyProtocol: AsyncLazyProtocol {

/// Gets the value, possibly initializing it first
var wrappedValue: Value {
Expand Down Expand Up @@ -52,6 +51,15 @@ public protocol LazyContainer {



public extension LazyProtocol {
@available(*, deprecated, message: "This is not required for non-Async `LazyContainer`s")
mutating func set(to newValue: Value) {
wrappedValue = newValue
}
}



// MARK: - ValueReference

/// Allows you to use reference-semantics to hold a value inside a lazy container
Expand All @@ -72,7 +80,7 @@ public final class LazyContainerValueReference<Value> {



public extension LazyContainer {
public extension AnyLazy {

/// Allows you to use reference semantics to hold a value inside a lazy container.
typealias ValueReference = LazyContainerValueReference
Expand Down
Loading

0 comments on commit b6e31d0

Please sign in to comment.