Skip to content

Trying to implement basic signals #17

@Dale-Black

Description

@Dale-Black

I am trying to implement a basic signal-style reactivity (e.g. SolidJS) with Julia and WebAssemblyCompiler.jl

TLDR

Why is this function not compiling?

function create_signal(initial_value)
    return Signal(initial_value, Function[])
end

function create_counter_signal()
    return create_signal(0)
end

compile(
    (create_counter_signal,),
    filepath = "counter/counter.wasm",
    validate = true
)

Details

This is the signal which works as expected in pure Julia:

begin
	mutable struct Signal{T}
	    _value::T
	    subscribers::Vector{Function}
	end
	
	function Base.getproperty(s::Signal, name::Symbol)
	    if name === :value
	        return getfield(s, :_value)
	    else
	        return getfield(s, name)
	    end
	end
	
	function Base.setproperty!(s::Signal, name::Symbol, value)
	    if name === :value
	        setfield!(s, :_value, value)
	        notify(s)
	    else
	        setfield!(s, name, value)
	    end
	end
end

function notify(s::Signal)
    for subscriber in s.subscribers
        subscriber(s._value)
    end
end

function subscribe(s::Signal, subscriber::Function)
    push!(s.subscribers, subscriber)
end

function create_signal(initial_value)
    return Signal(initial_value, Function[])
end

md"""
This example demonstrates the reactivity of the signal. Whenever the value of my_signal is updated, the subscribed function is automatically called, printing the updated value. This showcases how the signal can be used to trigger reactions and update values in a reactive manner.

You can extend this example further by creating more complex subscribers or using the signal values to perform specific actions or update other parts of your Julia program.
"""

begin
    my_signal = create_signal("")
    
    subscribe(my_signal, x -> println("Value changed to: $x"))
    
    my_signal.value = "Hello, World!"
    my_signal.value = "Signals are reactive!"
    my_signal.value = "Updating the value again..."
end

Which outputs:

Value changed to: Hello, World!
Value changed to: Signals are reactive!
Value changed to: Updating the value again...

Now, I want to create a simple Counter App using this but I am having trouble compiling one of the functions. Here is the full code:

begin
	mutable struct Signal{T}
	    _value::T
	    subscribers::Vector{Function}
	end
	
	function Base.getproperty(s::Signal, name::Symbol)
	    if name === :value
	        return getfield(s, :_value)
	    else
	        return getfield(s, name)
	    end
	end
	
	function Base.setproperty!(s::Signal, name::Symbol, value)
	    if name === :value
	        setfield!(s, :_value, value)
	        notify(s)
	    else
	        setfield!(s, name, value)
	    end
	end
end

function notify(s::Signal)
    for subscriber in s.subscribers
        subscriber(s._value)
    end
end

function subscribe(s::Signal, subscriber::Function)
    push!(s.subscribers, subscriber)
end

function create_signal(initial_value)
    return Signal(initial_value, Function[])
end

function create_counter_signal()
    return create_signal(0)
end

function increment(count_signal::Signal{Int})
    count_signal.value += 1
    update_signal(count_signal)
    nothing
end

function update_signal(count_signal::Signal{Int})
    @jscall(
        "(id, src)=> document.getElementById(id).innerHTML=src",
        Nothing,
        Tuple{Externref,Externref},
        JS.object("counter"),
        """<p>Count: $(count_signal.value)</p>"""
    )
    nothing
end

compile(
    (create_counter_signal,),
    (increment, Signal{Int}),
    (update_signal, Signal{Int}),
    filepath = "counter/counter.wasm",
    validate = true
)

And specifically this line (create_counter_signal,), inside of the compile block errors like so:

MethodError: no constructors have been defined for Function

Stack trace
Here is what happened, the most recent locations are first:

default(::Type{Function}) @ utils.jl:282
(::WebAssemblyCompiler.var"#84#181"{WebAssemblyCompiler.CompilerContext, Int64})(args::Vector{Any}) @ compile_block.jl:521
matchforeigncall(fun::WebAssemblyCompiler.var"#84#181"{WebAssemblyCompiler.CompilerContext, Int64}, node::Expr, sym::Symbol) @ utils.jl:88
compile_block(ctx::WebAssemblyCompiler.CompilerContext, cfg::Core.Compiler.CFG, phis::Dict{Int64, Any}, idx::Int64) @ compile_block.jl:517
(::WebAssemblyCompiler.var"#283#284"{WebAssemblyCompiler.CompilerContext, Dict{Int64, Any}, Ptr{WebAssemblyCompiler.LibBinaryen.Relooper}, Core.Compiler.CFG})(idx::Int64) @ 
iterate(::Base.Generator{Base.OneTo{Int64}, WebAssemblyCompiler.var"#283#284"{WebAssemblyCompiler.CompilerContext, Dict{Int64, Any}, Ptr{WebAssemblyCompiler.LibBinaryen.Relooper}, Core.Compiler.CFG}}) @ [generator.jl:47](https://github.com/JuliaLang/julia/tree/48d4fd48430af58502699fdf3504b90589df3852/base/generator.jl#L42)
collect(itr::Base.Generator{Base.OneTo{Int64}, WebAssemblyCompiler.var"#283#284"{WebAssemblyCompiler.CompilerContext, Dict{Int64, Any}, Ptr{WebAssemblyCompiler.LibBinaryen.Relooper}, Core.Compiler.CFG}}) @ [array.jl:834](https://github.com/JuliaLang/julia/tree/48d4fd48430af58502699fdf3504b90589df3852/base/array.jl#L827)
compile_method_body(ctx::WebAssemblyCompiler.CompilerContext) @ compiler.jl:145
compile_method(ctx::WebAssemblyCompiler.CompilerContext, funname::String; sig::Type, exported::Bool) @ compiler.jl:101
compile(::Tuple{typeof(Main.var"workspace#138".create_counter_signal)}, ::Vararg{Tuple}; filepath::String, jspath::String, validate::Bool, optimize::Bool, experimental::Bool, names::Nothing) @ compiler.jl:58
[This cell: line 1](http://localhost:1234/edit?id=353ab39a-29d7-11ef-0548-6d249aabf9fe#03b24a0d-7512-4920-b2ac-14c8500c0299)
compile(
    (create_counter_signal,),

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions