Skip to content
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

Accessing reactive variable value from a loop running in a handler #300

Open
PGimenez opened this issue Nov 14, 2024 · 16 comments · May be fixed by #304
Open

Accessing reactive variable value from a loop running in a handler #300

PGimenez opened this issue Nov 14, 2024 · 16 comments · May be fixed by #304

Comments

@PGimenez
Copy link
Member

PGimenez commented Nov 14, 2024

Suppose we have a handler, and a loop conditioned on the handler's reactive variable like this one:

@in toggle = false
@onchange toggle begin
  @async begin 
    while toggle
      sleep(1)
    end
  end
end

This loop will never break when toggle is set to false via an element in the UI or from another handler. To break the loop, you need to access the observable directly at the reactive model as

@in toggle = false
@onchange toggle begin
 @async begin 
   while __model__.toggle[]
     sleep(1)
   end
 end
end

Moreover, the updated values can be accessed from another handler. Here's a full MWE and a video:

  • The loop triggered by toggle_A will always run.
  • The loop triggered by toggle_B stops when the variable is false.
  • The handler triggered by print_value correctly prints the current values for toggle_A and toggle_Bat any time.
CleanShot.2024-11-14.at.18.48.20.mp4
using GenieFramework
@genietools

@app begin
  @in toggle_A = false
  @in toggle_B = false
  @in print_value = false
  @in process_on = true
  @private d = 1

  @onchange toggle_A begin
    @async begin
      while toggle_A && process_on
        @info "toggle_A loop running"
        sleep(d)
      end
    end
  end

  @onchange toggle_B begin
    @async begin
      while __model__.toggle_B[] && process_on
        @info "toggle_B loop running"
        sleep(d)
      end
    end
  end

  @onbutton print_value begin
    @info "toggle_A: $toggle_A"
    @info "toggle_B: $toggle_B"
  end
end

ui() = """
  <q-toggle color="primary" label="Toggle A" v-model="toggle_A" size="60px"></q-toggle>
  <q-toggle color="primary" label="Toggle B" v-model="toggle_B" size="60px"></q-toggle>
  <q-btn color="primary" label="Print value" v-on:click="print_value = true"></q-btn>
  <q-toggle color="primary" label="Process on" v-model="process_on" size="60px"></q-toggle>
"""
@page("/", ui)


@PGimenez
Copy link
Member Author

PGimenez commented Nov 14, 2024

I suppose this happens because toggle is scoped within the observable's function, and any changes made to the observable afterwards are not seen from the function.

The @asyncblock does something similar, it localizes regular types but not objects

JuliaLang/julia#7619

@hhaensel
Copy link
Member

I had exactly that Problem in one of my apps recently and solved it the same way.
I assume that the variable substitution of the @onchange macro doesn't work inside the @async macro.
Needs to be investigated...

@essenciary
Copy link
Member

essenciary commented Nov 15, 2024

Yes, we need to do another iteration for the reactive macros. There are a few issues:
1/ the above
2/ ability to reference other vars, previously defined, when defining new vars
3/ use Observables API to link reactive vars and chain update them automatically

To address use cases like

@in firstname
@in lastname
@out fullname = firstname * " " * lastname

@hhaensel
Copy link
Member

hhaensel commented Nov 16, 2024

I understand where this comes from. The syntax of the Observables.on() function is that it passes the values of the observed variables to the function.

on(x) do value_of_x
    println(value_of_x)
end

I chose to use that syntax and use the original names for the function part, so that I use the values instead of their full references in order to save the lookup time.
That fails, of course, if we use the variable in any function definition, including @async calls.
So should we sacrifice speed (not sure, how much that is?) versus usability?
We could also chose to replace x_ref by __model__.x[] or something similar, so that inside function definitions one should use x_ref instead of x`.
But best is we first measure the performance penalty.

EDIT: Most probably the lookup times are small versus typical computational tasks.

@hhaensel
Copy link
Member

Just tested. There's no significant difference in performance, let's always replace then ...
That won't, however, solve the problem with the local variables ...

@hhaensel
Copy link
Member

Found out that with explicit models local variables already work, when they are written as references:

julia> @app MyApp begin
         @in x = 1
         @in y = 2.0 + x[]
       end
(MyApp, handlers)

julia> MyApp()
Instance of 'MyApp'
    channel__ (internal): EVLJILCQALUITGSRUBMWAAOMJNNOPDIO
    modes__ (internal): LittleDict{Symbol, Int64, Vector{Symbol}, Vector{Int64}}()
    isready (autofield, in): false
    isprocessing (autofield, in): false
    fileuploads (autofield, in): Dict{AbstractString, AbstractString}()
    ws_disconnected (autofield, in): false

    x (in): 1
    y (in): 3.0

@hhaensel
Copy link
Member

So with a little trick you can also use that already for implicit models:

@app Main_ReactiveModel begin
    @in x = 1
    @in y = 2.0 + x[]
end __GF_AUTO_HANDLERS__

RT.TYPES[@__MODULE__] = Main_ReactiveModel
julia> @init
Instance of 'Main_ReactiveModel'
    channel__ (internal): JCRJFVAVLCQINPCCFGHTMVBMXWHANYSA
    modes__ (internal): LittleDict{Symbol, Int64, Vector{Symbol}, Vector{Int64}}()
    isready (autofield, in): false
    isprocessing (autofield, in): false
    fileuploads (autofield, in): Dict{AbstractString, AbstractString}()
    ws_disconnected (autofield, in): false

    x (in): 1
    y (in): 3.0

Actually, I think, it's a good idea to use that reference syntax to include model variables in the definition, because otherwise you might not be aware that the local definition is shadowing the module's definition.
So if you want to address a local variable of the same name you have to prefix it with the module's name.

julia> x = 11
11

julia> @app Main_ReactiveModel begin
         @in x = 1
         @in y = 2.0 + Main.x
       end __GF_AUTO_HANDLERS__
(Main_ReactiveModel, __GF_AUTO_HANDLERS__)

julia> @init
Instance of 'Main_ReactiveModel'
    channel__ (internal): ZBLOSXJIPQKKZILUZRVPBAOCSCGIDDGU
    modes__ (internal): LittleDict{Symbol, Int64, Vector{Symbol}, Vector{Int64}}()
    isready (autofield, in): false
    isprocessing (autofield, in): false
    fileuploads (autofield, in): Dict{AbstractString, AbstractString}()
    ws_disconnected (autofield, in): false

    x (in): 1
    y (in): 13.0

Unfortunately, neither __module__ nor @__MODULE work yet, but we could make __module__ work.

@hhaensel
Copy link
Member

This definition would immediately allow for variable references in implicit models:

macro app2(expr)
  modelname = Symbol(Stipple.ReactiveTools.model_typename(__module__))
  quote
    Stipple.ReactiveTools.TYPES[$__module__] = Stipple.@type
    Stipple.ReactiveTools.@app $modelname $expr __GF_AUTO_HANDLERS__
  end |> esc
end

e.g.

julia> Stipple.ReactiveTools.@app2 begin
           @in x = 1
           @in y = 2.0 + x[]
       end
(Main_ReactiveModel, __GF_AUTO_HANDLERS__)

julia> @init
Instance of 'Main_ReactiveModel'
    channel__ (internal): VKOFFIBIWOYUJPZIKMFNWOUCLIGRZXNV
    modes__ (internal): LittleDict{Symbol, Int64, Vector{Symbol}, Vector{Int64}}()
    isready (autofield, in): false
    isprocessing (autofield, in): false
    fileuploads (autofield, in): Dict{AbstractString, AbstractString}()
    ws_disconnected (autofield, in): false

    x (in): 1
    y (in): 3.0

@hhaensel
Copy link
Member

It also holds when the app has been renamed with @appname MyApp

@hhaensel
Copy link
Member

We could redefine @apps(expr), but this currently doesn't populate REACTIVE_STORAGE, so if people should rely on that for looking up their definitions, they might be surprised ...

@essenciary
Copy link
Member

REACTIVE_STORAGE is "private" I would say...

@hhaensel
Copy link
Member

I have a functional version that builds the code without REACTIVE_STORAGE and which is much cleaner

@essenciary
Copy link
Member

OK, we can merge if it works

@hhaensel
Copy link
Member

Needs some refinement, but will get it going the next days.

@hhaensel
Copy link
Member

Got it all working. The macros generate code that can be viewed by @macroexpand.

Shall we remove the old functionality then? That would mean removing all macro definitions @in, @out, etc.
We could decide to change the the API in the long run, but for the time being we would certainly not change it.

@hhaensel hhaensel linked a pull request Nov 23, 2024 that will close this issue
@hhaensel
Copy link
Member

However is interested in trying out can use the branch hh-appmacros, which is a larger refactoring of the @app macro.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants