-
Notifications
You must be signed in to change notification settings - Fork 6
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
Add pause
and resume
API's to Loop
#17
base: develop
Are you sure you want to change the base?
Conversation
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.
Some points where I'm not certain about the chosen approach 😇
e30f6e4
to
2edaea9
Compare
Having given some thoughts about this over the weekend, I think making I wonder if we would venture a state-driven approach instead. Say given this state definition: struct State {
var messages: [Message] = []
var isPaused: Bool = false
} Then we can define pausable feedbacks through a feedback modifier, that instructs the feedback to listen to a particular predicate. So Loop users also get to control and program the scope and granularity of cancellation. let loop = Loop(
initial: State(),
reducer: ...,
feedbacks: [
Loop.Feedback
.combine(
Self.feedback_pausable_whenLoading(),
Self.feedback_pausable_whenX(),
Self.feedback_pausable_whenY(),
Self.feedback_pausable_whenZ()
)
.autoconnect(condition: { $0.isPaused == false })
]
) (... while P.S. I have a bit of hesitation about pause as the term too, since it usually implies progress preservation which would not be the case for Loop (unless it being explicitly programmed to do so). So I chose the term autoconnect in the snippet above. |
Sorry for the delay, I was off last week 😇 I agree that the On our particular case, the need for this arose because we are using Loop to model Services on our app (e.g. responsible for performing network requests and managing a local state) which we want to start/stop. Even though we currently think of the start/stop mechanics as external to the state machine, I believe that they can (and probably should) be an integral part of it. Following this state-driven approach we keep this behavior nicely packed in the state which makes everything more explicit while also being more extensible and composable. ✨ Regarding the term, I agree that pause is not ideal and I think that autoconnect is definitely an improvement, despite personally not being fully convinced by it either 😅. Since I couldn't come up with a better one, I'll go with it for now. |
On some scenarios, it's useful to "pause" the `Loop` so that we stop processing events for some reason (e.g. to stop a `Loop` backed Service). Following the same reasoning, it becomes necessary to have a "resume" mechanism so that the Loop starts processing events again. Given `Loop` now starts automatically and `stop` is designed as a tear down mechanism to be used on dealloc and dispose all observations, some new API's are required so that we can unplug/replug feedbacks to achieve the above mentioned pause/resume behavior. ## Changes - Create new `plugFeedbacks` and `unplugFeedbacks` API's in `Floodgate`, which establish and dispose feedbacks observations, respectively. Floodgate now retains the feedbacks passed in on `bootstrap` to use them on `plugFeedbacks`. - Add `pause` and `resume` API's to `LoopBoxBase`. - Implement `pause` and `resume` API's in `RootLoopBox`, which unplug and plug the feedbacks on the `Floodgate`, respectively. - Implement `pause` and `resume` API's in `ScopedLoopBox`, which forward the calls to their root, respectively.
- Implement a new `autoconnect` operator on `Feedback` that disposes and restores its "parent" Feedback observation according to a predicate on `State`.
2edaea9
to
d8e39d5
Compare
public func autoconnect(followingChangesIn predicate: @escaping (State) -> Bool) -> Feedback { | ||
return Feedback { state, consumer in | ||
self.events( | ||
state | ||
.skipRepeats { predicate($0.0) == predicate($1.0) } | ||
.flatMap(.latest) { predicate($0.0) ? state.filter { predicate($0.0) } : .empty }, | ||
consumer | ||
) | ||
} | ||
} | ||
|
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.
This is what I managed to come up with, and it appears to be working. However I am not convinced this is the best approach... 🙈
More specifically, I had to add that filter
on the inner state
to avoid the "disconnect-triggering" state from being processed before the inner chain is replaced.
Any ideas to make this tidier and/or more idiomatic?
Motivation
On some scenarios, it's useful to "pause" the
Loop
so that we stop processing events for some reason (e.g. to stop aLoop
backed Service). Following the same reasoning, it becomes necessary to have a "resume" mechanism so that the Loop starts processing events again.Given
Loop
now starts automatically andstop
is designed as a tear down mechanism to be used on dealloc and dispose all observations, some new API's are required so that we can unplug/replug feedbacks to achieve the above mentioned pause/resume behavior.Changes
Create new
plugFeedbacks
andunplugFeedbacks
API's inFloodgate
, which establish and dispose feedbacks observations, respectively. Floodgate now retains the feedbacks passed in onbootstrap
to use them onplugFeedbacks
.Add
pause
andresume
API's toLoopBoxBase
.Implement
pause
andresume
API's inRootLoopBox
, which unplug and replug the feedbacks on theFloodgate
, respectively.Implement
pause
andresume
API's inScopedLoopBox
, which forward the calls to their root, respectively.Fixed .podspec to include swift files from all folders.