Skip to content

Commit

Permalink
Apply inlinabble to all methods of AtomTestContext (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
ra1028 authored Sep 28, 2023
1 parent 13efd80 commit e4d1404
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 23 deletions.
71 changes: 48 additions & 23 deletions Sources/Atoms/Context/AtomTestContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ import Foundation
/// by this itself.
@MainActor
public struct AtomTestContext: AtomWatchableContext {
private let state = State()
private let location: SourceLocation

@usableFromInline
internal let _state = State()

/// Creates a new test context instance with fresh internal state.
public init(fileID: String = #fileID, line: UInt = #line) {
location = SourceLocation(fileID: fileID, line: line)
}

/// A callback to perform when any of atoms watched by this context is updated.
@inlinable
public var onUpdate: (() -> Void)? {
get { state.onUpdate }
nonmutating set { state.onUpdate = newValue }
get { _state.onUpdate }
nonmutating set { _state.onUpdate = newValue }
}

/// Waits until any of the atoms watched through this context have been updated up to the
Expand All @@ -44,10 +47,11 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameter duration: The maximum duration that this function can wait until
/// the next update. The default timeout interval is nil.
/// - Returns: A boolean value indicating whether an update is done.
@inlinable
@discardableResult
public func waitForUpdate(timeout duration: TimeInterval? = nil) async -> Bool {
await withTaskGroup(of: Bool.self) { group in
let updates = state.makeUpdateStream()
let updates = _state.makeUpdateStream()

group.addTask { @MainActor in
var iterator = updates.makeAsyncIterator()
Expand Down Expand Up @@ -95,6 +99,7 @@ public struct AtomTestContext: AtomWatchableContext {
///
/// - Returns: A boolean value indicating whether an update is done.
///
@inlinable
@discardableResult
public func wait<Node: Atom>(
for atom: Node,
Expand All @@ -111,7 +116,7 @@ public struct AtomTestContext: AtomWatchableContext {
return predicate(value)
}

let updates = state.makeUpdateStream()
let updates = _state.makeUpdateStream()

group.addTask { @MainActor in
guard !check() else {
Expand Down Expand Up @@ -155,8 +160,9 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameter atom: An atom that associates the value.
///
/// - Returns: The value associated with the given atom.
@inlinable
public func read<Node: Atom>(_ atom: Node) -> Node.Loader.Value {
store.read(atom)
_store.read(atom)
}

/// Sets the new value for the given writable atom.
Expand All @@ -177,8 +183,9 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameters
/// - value: A value to be set.
/// - atom: An atom that associates the value.
@inlinable
public func set<Node: StateAtom>(_ value: Node.Loader.Value, for atom: Node) {
store.set(value, for: atom)
_store.set(value, for: atom)
}

/// Modifies the cached value of the given writable atom.
Expand All @@ -200,8 +207,9 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameters
/// - atom: An atom that associates the value.
/// - body: A value modification body.
@inlinable
public func modify<Node: StateAtom>(_ atom: Node, body: (inout Node.Loader.Value) -> Void) {
store.modify(atom, body: body)
_store.modify(atom, body: body)
}

/// Refreshes and then return the value associated with the given refreshable atom.
Expand All @@ -221,9 +229,10 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameter atom: An atom that associates the value.
///
/// - Returns: The value which completed refreshing associated with the given atom.
@inlinable
@discardableResult
public func refresh<Node: Atom>(_ atom: Node) async -> Node.Loader.Value where Node.Loader: RefreshableAtomLoader {
await store.refresh(atom)
await _store.refresh(atom)
}

/// Resets the value associated with the given atom, and then notify.
Expand All @@ -242,8 +251,9 @@ public struct AtomTestContext: AtomWatchableContext {
/// ```
///
/// - Parameter atom: An atom that associates the value.
@inlinable
public func reset(_ atom: some Atom) {
store.reset(atom)
_store.reset(atom)
}

/// Accesses the value associated with the given atom for reading and initialing watch to
Expand All @@ -263,10 +273,11 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameter atom: An atom that associates the value.
///
/// - Returns: The value associated with the given atom.
@inlinable
@discardableResult
public func watch<Node: Atom>(_ atom: Node) -> Node.Loader.Value {
store.watch(atom, container: container, requiresObjectUpdate: true) { [weak state] in
state?.notifyUpdate()
_store.watch(atom, container: _container, requiresObjectUpdate: true) { [weak _state] in
_state?.notifyUpdate()
}
}

Expand All @@ -285,17 +296,19 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameter atom: An atom that associates the value.
///
/// - Returns: The already cached value associated with the given atom.
@inlinable
public func lookup<Node: Atom>(_ atom: Node) -> Node.Loader.Value? {
store.lookup(atom)
_store.lookup(atom)
}

/// Unwatches the given atom and do not receive any more updates of it.
///
/// It simulates cases where other atoms or views no longer watches to the atom.
///
/// - Parameter atom: An atom that associates the value.
@inlinable
public func unwatch(_ atom: some Atom) {
store.unwatch(atom, container: container)
_store.unwatch(atom, container: _container)
}

/// Overrides the atom value with the given value.
Expand All @@ -306,8 +319,9 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameters:
/// - atom: An atom that to be overridden.
/// - value: A value that to be used instead of the atom's value.
@inlinable
public func override<Node: Atom>(_ atom: Node, with value: @escaping (Node) -> Node.Loader.Value) {
state.overrides[OverrideKey(atom)] = AtomOverride(value: value)
_state.overrides[OverrideKey(atom)] = AtomOverride(value: value)
}

/// Overrides the atom value with the given value.
Expand All @@ -320,22 +334,30 @@ public struct AtomTestContext: AtomWatchableContext {
/// - Parameters:
/// - atomType: An atom type that to be overridden.
/// - value: A value that to be used instead of the atom's value.
@inlinable
public func override<Node: Atom>(_ atomType: Node.Type, with value: @escaping (Node) -> Node.Loader.Value) {
state.overrides[OverrideKey(atomType)] = AtomOverride(value: value)
_state.overrides[OverrideKey(atomType)] = AtomOverride(value: value)
}
}

private extension AtomTestContext {
internal extension AtomTestContext {
@usableFromInline
@MainActor
final class State {
@usableFromInline
let store = AtomStore()
let token = ScopeKey.Token()
let container = SubscriptionContainer()

@usableFromInline
var overrides = [OverrideKey: any AtomOverrideProtocol]()

@usableFromInline
var onUpdate: (() -> Void)?

private let notifier = PassthroughSubject<Void, Never>()

@usableFromInline
func makeUpdateStream() -> AsyncStream<Void> {
AsyncStream { continuation in
let cancellable = notifier.sink(
Expand All @@ -362,22 +384,25 @@ private extension AtomTestContext {
}
}

@usableFromInline
func notifyUpdate() {
onUpdate?()
notifier.send()
}
}

var store: StoreContext {
@usableFromInline
var _store: StoreContext {
.scoped(
key: ScopeKey(token: state.token),
store: state.store,
key: ScopeKey(token: _state.token),
store: _state.store,
observers: [],
overrides: state.overrides
overrides: _state.overrides
)
}

var container: SubscriptionContainer.Wrapper {
state.container.wrapper(location: location)
@usableFromInline
var _container: SubscriptionContainer.Wrapper {
_state.container.wrapper(location: location)
}
}
10 changes: 10 additions & 0 deletions Sources/Atoms/Core/AtomOverride.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@usableFromInline
internal protocol AtomOverrideProtocol {
associatedtype Node: Atom

Expand All @@ -6,9 +7,17 @@ internal protocol AtomOverrideProtocol {
func scoped(key: ScopeKey) -> any AtomScopedOverrideProtocol
}

@usableFromInline
internal struct AtomOverride<Node: Atom>: AtomOverrideProtocol {
@usableFromInline
let value: (Node) -> Node.Loader.Value

@usableFromInline
init(value: @escaping (Node) -> Node.Loader.Value) {
self.value = value
}

@usableFromInline
func scoped(key: ScopeKey) -> any AtomScopedOverrideProtocol {
AtomScopedOverride<Node>(scopeKey: key, value: value)
}
Expand All @@ -20,6 +29,7 @@ internal struct AtomOverride<Node: Atom>: AtomOverrideProtocol {
// their View body is evaluated. This is not ideal from a performance standpoint,
// so it will be improved as soon as an alternative way to grant per-scope keys
// independent of the SwiftUI lifecycle is came up.
@usableFromInline
internal protocol AtomScopedOverrideProtocol {
var scopeKey: ScopeKey { get }
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/Atoms/Core/OverrideKey.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
@usableFromInline
internal struct OverrideKey: Hashable {
private let identifier: Identifier

@usableFromInline
init<Node: Atom>(_ atom: Node) {
let key = AnyHashable(atom.key)
let type = ObjectIdentifier(Node.self)
identifier = .node(key: key, type: type)
}

@usableFromInline
init<Node: Atom>(_: Node.Type) {
let type = ObjectIdentifier(Node.self)
identifier = .type(type)
Expand Down
1 change: 1 addition & 0 deletions Sources/Atoms/Core/ScopeKey.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@usableFromInline
internal struct ScopeKey: Hashable {
final class Token {}

Expand Down
1 change: 1 addition & 0 deletions Sources/Atoms/Core/TaskExtensions.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
internal extension Task where Success == Never, Failure == Never {
@inlinable
static func sleep(seconds duration: Double) async throws {
try await sleep(nanoseconds: UInt64(duration * 1_000_000_000))
}
Expand Down

0 comments on commit e4d1404

Please sign in to comment.