-
Notifications
You must be signed in to change notification settings - Fork 11
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
Conveniences for creating state machine as Property
.
#11
Conversation
func testPlaceholder() {} | ||
func test_reduce_with_two_immediate_feedback_loops() { | ||
let feedbackACount = Atomic(0) | ||
let feedbackBCount = Atomic(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need atomic
, right?
9da9f0c
to
d91823f
Compare
What's the advantage of this approach, versus the original spec and for what we do internally? |
@RuiAAPeres Across all examples:
You can observe that because we are exposing properties as public VM APIs for VC bindings, we need the ability to replay the latest state to back these properties up. Especially for Forms (as shown in item 3), form builder layouts are computed as a function of state. So having conveniences to create As for being synchronous by default, it is more of an implementation tweak. Opt-in asynchrony reduces surprises, and is generally easier to reason about. It also aligns with ReactiveSwift, which is synchronous unless one having explicitly requested. To allow feedback to propagate synchronously, a buffer is introduced for flattening recursively delivered
This does not deviate from the original proposal, except now that a |
c9b8450
to
e104aff
Compare
ReactiveFeedback/StateMachine.swift
Outdated
// the initialisation. | ||
let buffer = Atomic(EventBuffer<Event>(isTransitioning: true, events: [])) | ||
|
||
state.startWithSignal { state, interruptHandle in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't we need to add Disposable
to lifetime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did on line 37.
startWithSignal
does not return a Disposable
. What it does is:
- make a
Signal
. - run the given
(Signal, Disposable) -> Void
closure. - start the deferred work.
TBH I still not convinced with this approach
return SignalProducer.deferred {
let (state, observer) = Signal<Value, NoError>.pipe()
let events = feedbacks.map { feedback in
return feedback.events(scheduler, state)
}
return SignalProducer<Event, NoError>(Signal.merge(events))
.scan(initial, reduce)
.prefix(value: initial)
.on(value: observer.send(value:))
} Vs return SignalProducer { observer, lifetime in
let (events, eventsObserver) = Signal<Event, NoError>.pipe()
lifetime += AnyDisposable(eventsObserver.sendInterrupted)
StateMachine.bootstrap(
state: SignalProducer<Event, NoError>(events)
.scan(initial, reduce)
.prefix(value: initial)
.on(value: observer.send(value:)),
process: eventsObserver.send(value:),
feedbacks: feedbacks,
lifetime: lifetime
)
}
internal enum StateMachine {
internal static func bootstrap<State, Event>(
state: SignalProducer<State, NoError>,
process: @escaping (Event) -> Void,
feedbacks: [Feedback<State, Event>],
lifetime: Lifetime
) {
// Treat initialisation as an implicit event, and buffer any event yielded during
// the initialisation.
let buffer = Atomic(EventBuffer<Event>(isTransitioning: true, events: []))
state.startWithSignal { state, interruptHandle in
lifetime += interruptHandle
let events = feedbacks.map { feedback in
return feedback.events(ImmediateScheduler(), state)
}
lifetime += Signal.merge(events)
.observeValues { event in
let shouldProceed: Bool = buffer.modify { buffer in
guard buffer.isTransitioning else {
// This event is not recursively sent during the processing
// of another event.
buffer.isTransitioning = true
return true
}
buffer.events.append(event)
return false
}
guard shouldProceed else { return }
process(event)
// Drain any event recursively yielded during `process(event)` above.
while let event = buffer.modify({ $0.completeOrDequeue() }) {
process(event)
}
}
}
// Drain any event yielded by the initial state.
while let event = buffer.modify({ $0.completeOrDequeue() }) {
process(event)
}
}
}
What I would like to propose is to stop premature optimisation, and focus on shaping naming issues, add documentation, unit tests and maybe add more examples |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, but I'm not convinced with this approach ¯_(ツ)_/¯
Don't you get this from the initial state passed to the reducer + Producer we have with the initial draft?
Do you have a test case for this?
This is an oxymoron. There's a clear deviation, at least from an implementation POV. |
I agree with this:
|
e104aff
to
57c22a1
Compare
Property
.
57c22a1
to
5e14b24
Compare
Property
.Property
& drop SignalProducer.deferred
.
Stripped all the premature optimisation away. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. 👍
@@ -9,17 +9,18 @@ extension SignalProducer where Error == NoError { | |||
reduce: @escaping (Value, Event) -> Value, | |||
feedbacks: [Feedback<Value, Event>] | |||
) -> SignalProducer<Value, NoError> { | |||
return SignalProducer.deferred { | |||
let (state, observer) = Signal<Value, NoError>.pipe() | |||
return SignalProducer { observer, lifetime in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why you don't like deferred
? =)
It's the same but it looks much cleaner IMO
Property
& drop SignalProducer.deferred
.Property
.
Please fix conflicts, so it can be merged. |
c450be4
to
545f1b1
Compare
Introduce two conveniences that yield a
Property
state machine directly.Updated the examples to demonstrate them.
Remove
SignalProducer.deferred
."Property conveniences" turn out to be not so convenient in terms of implementation, if we are to abide with the initial value can only be seen once expectation aroundProperty
.So I have pulled pieces of #10 and managed to find a solution that can back bothSignalProducer
andProperty
based APIs.This PR is marked as blocked though, since it is affected by a freshly identifiedIt seems somehow I circumvented it...SignalProducer
bug in ReactiveSwift & the test case would fail due to this.