leios / Fable.jl

General purpose animations via iterated function systems
MIT License
27 stars 4 forks source link

Generated functions instead of `fx_string` #63

Closed leios closed 1 year ago

leios commented 1 year ago

The current method with @invokelatest adds ~400 ns cost, whereas using an @generated function we could save this time.

The sticky points are that:

  1. The user is currently writing multiple function systems that they are combining into a larger Hutchinson operator.
  2. These functions need to be configured depending on the method used to solve the IFS (random, semi-random, etc), which means they do not write the function that is actually being executed
  3. The user might specify different symbols / kwargs for different functions and we need to find a way for the generated function to reason about this (maybe just an eval of a namedtuple?)

Other solutions:

  1. runtimegeneratedfunctions
  2. https://github.com/JuliaLabs/Poly.jl/blob/aed538b1fa58b9906aacb812bc6618aac0ba4bc2/src/macros.jl#L402 .
  3. If the user can specify functions that can be called on the GPU, then we can have them specify functions as f(; a = 1, kwargs...) so they can be called in the kernels with different kwargs. Modified NamedTuple interface... more info below
  4. Make the macro more powerful to combine multiple function bodies. This can happen if the user can provide a function that is actually called

More info:

julia> k(; a=3, kwargs...) = a
k (generic function with 1 method)

julia> struct FunctionDescriptor{Desc, F}
          f::F
          FunctionDescriptor{Desc}(f::F) where {Desc, F} = new{Desc, F}(f)
       end

julia> f = FunctionDescriptor{(:a,)}(k)
FunctionDescriptor{(:a,), typeof(k)}(k)

julia> function (f::FunctionDescriptor{Desc, F})(;kwargs...) where {F, Desc}
          filtered_kwargs = NamedTuple{Desc}(NamedTuple(kwargs))
          f.f(;filtered_kwargs...)
       end
julia> struct FunctionDescriptor{Desc, KWDesc, F}
          f::F
          FunctionDescriptor{Desc, KWDesc}(f::F) where {Desc, KWDesc, F} = new{Desc, KWDesc, F}(f)
       end
julia> function (f::FunctionDescriptor{Desc, KWDesc, F})(;kwargs...) where {Desc, KWDesc, F}
          filtered_kwargs = NamedTuple{KWDesc}(NamedTuple(kwargs))
          filtered_args = NamedTuple{Desc}(NamedTuple(kwargs))
          f.f(filtered_args...; filtered_kwargs...)
       end
julia> function (f::FunctionDescriptor{Desc, KWDesc, F})(;kwargs...) where {Desc, KWDesc, F}
          kwargs = NamedTuple(kwargs)
          __KWDesc = intersect(keys(kwargs), KWDesc)
          filtered_kwargs = NamedTuple{(__KWDesc...,)}(kwargs)
          filtered_args = NamedTuple{Desc}(kwargs)
          f.f(filtered_args...; filtered_kwargs...)
       end
leios commented 1 year ago

Ok, so I think the plan is to:

  1. re-write all fums to just be functions that return a tuple of either xy or rgb. The kernels will then manually set the shared tiles appropriately.
  2. Lift fid stuff into the kernel, itself and let users write ((f1, f2), (f3, f4)) and ((f5, f6), f7) as postprocessing operators. Then we just do the "traversal" in the kernel, itself.
  3. use what is written in "more info" above to make sure users can write functions with arbitrary args / kwargs

Note that:

  1. this will remove FractalInputs as that can be dealt with by just using better filtering args / kwargs.
  2. We can probably find a way to refactor Fae to remove FractalOperators as well.
  3. This might fix some of the GPU issues we have been having as well
leios commented 1 year ago

Done in #64