Skip to content
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
28 changes: 14 additions & 14 deletions Sources/EffectComponents/EffectObservable/Transducer.observe.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ extension Transducer where Effect == TransducerEffect<Event, Env, Output> {
keyPath: KeyPath<Object, Value>,
id: TaskIdentifier? = "observe",
priority: TaskPriority? = nil,
handler: @escaping @Sendable @isolated(any) (any TransducerInput<Event, Output> & Sendable, Value) async -> Void
handler: @escaping @Sendable @isolated(any) (any TransducerInput<Event, Output> & Sendable, Value, Env) async -> Void
) -> Effect
where Object: Observable & AnyObject & Sendable, Value: Sendable
where Object: Observable & AnyObject & Sendable, Value: Sendable, Env: Sendable
{
let box = SendableKeyPath(keyPath: keyPath)
let envKeyPathBox = SendableKeyPath(keyPath: envKeyPath)
return task(id: id, priority: priority) { input, env in
do {
let weakObject = WeakObject(object: env[keyPath: envKeyPathBox.keyPath])
await handler(input, try observedValue(weakObject, keyPath: box))
await handler(input, try observedValue(weakObject, keyPath: box), env)
while true {
try await _waitForObservationChange(weakObject, keyPath: box)
await handler(input, try observedValue(weakObject, keyPath: box))
await handler(input, try observedValue(weakObject, keyPath: box), env)
}
} catch is CancellationError {
// Transducer logic has cancelled. Do not rethrow.
Expand Down Expand Up @@ -95,7 +95,7 @@ extension Transducer where Effect == TransducerEffect<Event, Env, Output> {
keyPath: KeyPath<Object, Value>,
id: TaskIdentifier? = "observe",
priority: TaskPriority? = nil,
isolatedHandler: @escaping (any TransducerInput<Event, Output>, Value, isolated any Actor) async -> Void
isolatedHandler: @escaping (any TransducerInput<Event, Output>, Value, Env, isolated any Actor) async -> Void
) -> Effect
where Object: Observable & AnyObject & Sendable, Value: Sendable
{
Expand All @@ -108,10 +108,10 @@ extension Transducer where Effect == TransducerEffect<Event, Env, Output> {
"observe(isolatedOperation:) requires a non-nil matching system actor. Actor hosts must provide isolation. Expected \(String(describing: systemActor)), got \(isolation)."
)
let weakObject = WeakObject(object: env[keyPath: envKeyPathBox.keyPath])
await isolatedHandler(input, try observedValue(weakObject, keyPath: box), isolation)
await isolatedHandler(input, try observedValue(weakObject, keyPath: box), env, isolation)
while true {
try await _waitForObservationChange(weakObject, keyPath: box)
await isolatedHandler(input, try observedValue(weakObject, keyPath: box), isolation)
await isolatedHandler(input, try observedValue(weakObject, keyPath: box), env, isolation)
}
}
catch is CancellationError {
Expand Down Expand Up @@ -164,18 +164,18 @@ extension Transducer where Effect == TransducerEffect<Event, Env, Output> {
keyPath: KeyPath<Object, Value>,
id: TaskIdentifier? = "observe",
priority: TaskPriority? = nil,
operation: @escaping @Sendable @isolated(any) (any TransducerInput<Event, Output> & Sendable, Value) async -> Void
operation: @escaping @Sendable @isolated(any) (any TransducerInput<Event, Output> & Sendable, Value, Env) async -> Void
) -> Effect
where Object: Observable & AnyObject & Sendable, Value: Sendable
where Object: Observable & AnyObject & Sendable, Value: Sendable, Env: Sendable
{
let box = SendableKeyPath(keyPath: keyPath)
let weakObject = WeakObject(object: object)
return task(id: id, priority: priority) { input, env in
do {
await operation(input, try observedValue(weakObject, keyPath: box))
await operation(input, try observedValue(weakObject, keyPath: box), env)
while true {
try await _waitForObservationChange(weakObject, keyPath: box)
await operation(input, try observedValue(weakObject, keyPath: box))
await operation(input, try observedValue(weakObject, keyPath: box), env)
}
} catch is CancellationError {
// Expected termination path for explicit cancel(name) or view teardown.
Expand Down Expand Up @@ -207,7 +207,7 @@ extension Transducer where Effect == TransducerEffect<Event, Env, Output> {
keyPath: KeyPath<Object, Value>,
id: TaskIdentifier? = "observe",
priority: TaskPriority? = nil,
isolatedOperation: @escaping (any TransducerInput<Event, Output>, Value, isolated any Actor) async -> Void
isolatedOperation: @escaping (any TransducerInput<Event, Output>, Value, Env, isolated any Actor) async -> Void
) -> Effect
where Object: Observable & AnyObject & Sendable, Value: Sendable
{
Expand All @@ -219,10 +219,10 @@ extension Transducer where Effect == TransducerEffect<Event, Env, Output> {
systemActor != nil && systemActor === isolation,
"observe(isolatedOperation:) requires a non-nil matching system actor. Actor hosts must provide isolation. Expected \(String(describing: systemActor)), got \(isolation)."
)
await isolatedOperation(input, try observedValue(weakObject, keyPath: box), isolation)
await isolatedOperation(input, try observedValue(weakObject, keyPath: box), env, isolation)
while true {
try await _waitForObservationChange(weakObject, keyPath: box)
await isolatedOperation(input, try observedValue(weakObject, keyPath: box), isolation)
await isolatedOperation(input, try observedValue(weakObject, keyPath: box), env, isolation)
}
} catch is CancellationError {
// Expected termination path for explicit cancel(name) or view teardown.
Expand Down
59 changes: 59 additions & 0 deletions Sources/EffectComponents/Transducer/TransducerEffect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,62 @@ enum EffectType<Event, Env, Output> {
case _sequence([TransducerEffect<Event, Env, Output>])
}

extension EffectType: CustomStringConvertible {

public var description: String {

switch self {
case ._task(
id: let id,
priority: let priority,
option: let option,
_
):
return "task(\(id, default: "_"), option: \(option))"

case ._taskIsolated(
id: let id,
priority: let priority,
option: let option,
_
):
return "task(\(id, default: "_"), option: \(option))"


case ._taskNonsending(
id: let id,
priority: let priority,
option: let option,
_
):
return "task(\(id, default: "_"), option: \(option))"

case ._actionSync:
return "action"

case ._actionAsync:
return "action async"

case ._actionAsyncIsolated:
return "action async"

case ._event(let event):
return "event(\(event))"

case ._cancel(let taskIdentifier):
return "cancel(\(taskIdentifier))"

case ._sequence(let effects):
return "sequence(\(effects))"

case .none:
return "none"
}
}
}

extension TransducerEffect: CustomStringConvertible {
public var description: String {
return "\(type)"
}
}
12 changes: 6 additions & 6 deletions Tests/EffectComponents/EffectViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -491,17 +491,17 @@ struct EffectViewTests {
enum Event: Sendable { case watch(ObservableCounter), tick(Int) }
static func update(_ state: inout State, event: Event) -> TransducerEffect<Event, Void, Void>? {
switch event {
case .watch(let c):
return observe(c, keyPath: \.value) { input, v in
case .watch(let observable):
return observe(observable, keyPath: \.value) { input, value, env in
// Regarding: using observe with isolated action
// Note: request is *nonisolated* for this Input. Thus we
// cannot use `isolatedOperation` - we need to have
// a Sendable operation which also requires Input to be
// sendable!
try? await input.request(.tick(v))
try? await input.request(.tick(value))
}
case .tick(let v):
state.latest = v
case .tick(let value):
state.latest = value
return nil
}
}
Expand Down Expand Up @@ -576,7 +576,7 @@ struct EffectViewTests {
static func update(_ state: inout State, event: Event) -> Effect? {
switch event {
case .start:
return observe(\.counter, keyPath: \.value, id: "observe") { input, value in
return observe(\.counter, keyPath: \.value, id: "observe") { input, value, env in
try? await input.request(.tick(value))
print("request(.tick(\(value))) finished")
}
Expand Down
Loading