defmodule Skuld.Effects.FiberYield do
@moduledoc false
use Skuld.Comp.DefOp
alias Skuld.Comp
alias Skuld.Comp.InternalSuspend
@yield_op Module.concat(Skuld.Effects.Yield, Yield)
def_op notify(value)
@doc """
Install a Yield handler that produces `InternalSuspend` with `FiberYield`
payloads instead of `ExternalSuspend`, and a notify handler for
fire-and-forget notifications.
Use inside a FiberPool context so the scheduler can manage suspended
fibers while other fibers run.
computation
|> FiberYield.with_handler()
|> FiberPool.with_handler()
|> Comp.run()
"""
@spec with_handler(Comp.Types.computation()) :: Comp.Types.computation()
def with_handler(computation) do
computation
|> Comp.with_handler(Skuld.Effects.Yield, &handle_yield/3)
|> Comp.with_handler(__MODULE__, &handle_notify/3)
end
@doc false
def handle_yield({@yield_op, value}, env, k) do
resume = fn input, resume_env ->
{result, final_env} = k.(input, resume_env)
case result do
%InternalSuspend{} -> {result, final_env}
%Comp.ExternalSuspend{} -> {result, final_env}
_ -> Comp.Env.run_leave_scope(final_env, result)
end
end
suspend = InternalSuspend.fiber_yield(value, resume)
{suspend, env}
end
@notify_op @__notify_op__
@doc false
def handle_notify({@notify_op, value}, env, k) do
resume = fn _input, resume_env -> k.(nil, resume_env) end
suspend = InternalSuspend.fiber_notify(value, resume)
{suspend, env}
end
end