#Splitting a `Signal<Option<T>>` (for loading widget)

39 messages · Page 1 of 1 (latest)

little finch
#

contd. #1161405149080731760 message

Since we do not yet have a function like this:

fn break_option(s: Signal<Option<T>>) -> ReadOnlySignal<Option<ReadOnlySignal<T>>

I tried what I can to come up with a similar architecture to decouple the "loading" widget from the actual data rendering widget:

https://github.com/srid/dioxus-desktop-template/pull/8/files

The loader widget looks about right,

fn Loader(cx: Scope, loading: ReadOnlySignal<bool>) -> Element {

But the data rendering widget is not great, because it takes an Option when it shouldn't have to:

fn ViewMemoryStats(cx: Scope, stats: ReadOnlySignal<Option<memory_stats::MemoryStats>>) -> Element {

In absence of a function like break_option, I'm left with the only option of using use_selector to create these separate signals:

    let value: ReadOnlySignal<Option<memory_stats::MemoryStats>> =
        use_selector(cx, move || *state.system.read());
    let loading: ReadOnlySignal<bool> = use_selector(cx, move || state.system.read().is_none());

Is there anything I can do to help this situation?

cc @lament schooner

#

Aside: eventually, I'd like to use an enum similar to use UseFutureState to contain the state where the stale data is rendered while it is being refreshed.

#

Or maybe I'm approaching this problem incorrectly.

lament schooner
#

That will let you turn:

 let value: ReadOnlySignal<Option<memory_stats::MemoryStats>> =
        use_selector(cx, move || *state.system.read());

into:

 let value: ReadOnlySignal<Option<memory_stats::MemoryStats>> =
        state.map(|s| &s.system);

The loading state looks good to me

#

If you are sure that the state.system is Some you can do:

 let value: ReadOnlySignal<Option<memory_stats::MemoryStats>> =
        state.map(|s| s.system.as_ref().unwrap());
little finch
#

Interesting. But shouldn't map be returning ReadOnlySignal?

 pub fn map<O>(self, f: fn(&T) -> &O) -> SignalMap<T, O> {
lament schooner
#

no

#

It runs f every time you read the signal

#

It needs an extra type parameter (T) to track the original type so it can't be combined with read only signal. They could both implement a ReadOnly trait

little finch
#

Unlike use_selector which doesn't re-evaluate the function for every value read (but only every write)?

lament schooner
#

But use selector will clone the value

#

This is just a read only view into a signal

#

Kind of like Ref::map

little finch
#

Ok. For the loading widget separation, we need filter_map more than map, right?

#

Though Signal always has value, so it won't be a Signal. Hmm

#

(Reflex has Event vs Dynamic distinction for this reason)

lament schooner
lament schooner
#

(assuming state doesn't change while you have value)

little finch
#

Well that would panic during loading stage?

#

I think it would be good to have an example that renders this signal:

enum LoadableData<T> {
  Loading,
  Ready(T),
  Reloading(T)
}

Then,

fn ViewLoadableData<T>(cx: Scope, data: Signal<LoadableData<T>>, renderer: Fn(Signal<T>) -> Element) {
#

I'm not sure if ViewLoadableData is actually implementable right now.

lament schooner
lament schooner
lament schooner
#

But you could do something like this:

let element = match data.read() {
  LoadableData::Ready(_) | LoadableData::Reloading(_) => {
    let mapped = data.map(|data| match data {
      LoadableData::Ready(state) | LoadableData::Reloading(state) => state,
      _ => unreachable!(),
    });
    renderer(cx, mapped)
  }
  _ => None
};
#

Filter map would help but it could reach the unreachable statement if mapped lasts too long (if data changes to None after mapped is created)

little finch
# lament schooner But you could do something like this: ```rust let element = match data.read() { ...

Yes, but this is scary, as it provides a (potentially) partial signal. Later when someone modifies the code this could introduce a bug.

I think the only way to solve this is to introduce a second type. While Signal is a constantly changing value (that always exists), an Event is just a stream of values (that doesn't exixt at start, but then is generated over time). Then we can implement:

fn break_signal(val: Signal<Option<T>>) -> Signal<Option<Event<T>>

With helper functions to convert between them, like

fn holdSignal(initial: T, updates: Event<T>) -> Signal<T>
lament schooner
little finch
#

There will not be a read method for Event ...

lament schooner
#

How would you use event then? (Without the original signal)

#

Is it just a history of the value?

little finch
#

These concepts come from traditional FRP: Event, Behavioiur, Dynamic (Dioxus Signal is same as Dynamic afaict)

Like in traditional FRP[4], Reflex has two main concepts: Events and Behaviors. Events are discrete occurrences in time, while Behaviors are continuously defined values for all points in time. Reflex also has Dynamic values, which have the properties o both: they are defined at all points in time and emit events at the discrete points in time when they change https://futureofcoding.org/papers/comprehensible-frp/comprehensible-frp.pdf

little finch
# lament schooner How would you use event then? (Without the original signal)

In reflex there are combinators, like

widgetHold :: (Adjustable t m, MonadHold t m) => m a -> Event t (m a) -> m (Dynamic t a)

https://hackage.haskell.org/package/reflex-dom-core-0.8.1.0/docs/Reflex-Dom-Widget-Basic.html

Which gets used like this: https://docs.reflex-frp.org/en/latest/reflex_docs.html#doing-io-via-performevent

widgetHold (text "Waiting for action to complete")
  (fmap showResultOfAction doneEv)

doneEv is an event of some value, and showResultOfAction is a component that renders it. In the type signature, m a is the rendering monad. So widgetHold takes the initial widget to render, and then takes an event of widgets and swaps them as they appear, returning the final widget (containing the Dynamic of its values).

#

In dioxus-speak it may look like this:

fn widgetHold(initial: Element, updates: Event<Element>) -> Element

But this would involve re-architecturing the whole Dioxus architecture according to FRP. Maybe a subset of ideas can be borrowed without inheriting the whole reactive system ...

little finch
#

(I misread the return value of widgetHold; edited my message above. The function returns a widget, so widgetHold is basically a #[component])

lament schooner
#

That sounds a bit too complex/functional for Dioxus (and I would rather not rewrite the framework again)