-
Notifications
You must be signed in to change notification settings - Fork 39
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
Queue buffers when auto demand is low enough #693
Changes from 10 commits
00794c3
9be7be2
4188729
e49d57e
0958822
b075c70
99396d4
6c1da19
b66662d
2430810
6d56f0c
e32c255
5e5f5b3
c65b2bb
242d25f
de2fc22
16f4d4b
2391f09
08a7f4d
3725afe
7a5c71a
b7e79b4
3b9fdcb
2cdd49e
379871c
9018e3c
e1aadcc
9e85059
12c1273
ca1b221
7032d9b
b471d29
01728df
4f02735
95b1848
6ca21dd
2720120
c929433
9313f2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,8 +69,20 @@ defmodule Membrane.Core.Element.BufferController do | |
state = PadModel.set_data!(state, pad_ref, :demand, demand - buf_size) | ||
:atomics.put(stalker_metrics.demand, 1, demand - buf_size) | ||
|
||
state = AutoFlowUtils.auto_adjust_atomic_demand(pad_ref, state) | ||
exec_buffer_callback(pad_ref, buffers, state) | ||
if state.effective_flow_control == :pull and MapSet.size(state.satisfied_auto_output_pads) > 0 do | ||
AutoFlowUtils.store_buffers_in_queue(pad_ref, buffers, state) | ||
else | ||
if pad_ref in state.awaiting_auto_input_pads do | ||
raise "to nie powinno sie zdarzyc dupa 1" | ||
end | ||
|
||
if PadModel.get_data!(state, pad_ref, [:auto_flow_queue]) != Qex.new() do | ||
raise "to nie powinno sie zdarzyc dupa 2" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love it #dupa There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🇵🇱 🇵🇱 🇵🇱 This is still a draft for a reason 🇵🇱 🇵🇱 🇵🇱 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @FelonEkonom out of curiosity - may I participate in the code review process? 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. I will mark this PR as |
||
end | ||
|
||
state = exec_buffer_callback(pad_ref, buffers, state) | ||
AutoFlowUtils.auto_adjust_atomic_demand(pad_ref, state) | ||
end | ||
end | ||
|
||
defp do_handle_buffer(pad_ref, %{flow_control: :manual} = data, buffers, state) do | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,7 +49,23 @@ defmodule Membrane.Core.Element.DemandController do | |
} = pad_data | ||
|
||
if AtomicDemand.get(atomic_demand) > 0 do | ||
AutoFlowUtils.auto_adjust_atomic_demand(associated_pads, state) | ||
# tutaj powinno mieć miejsce | ||
# - usuniecie pada z mapsetu | ||
# - sflushowanie kolejek, jesli mapset jest pusty | ||
# zwroc uwage, czy gdzies w czyms w stylu handle_outgoing_buffers nie wjedzie ci tutaj jakas nieprzyjemna rekurencja | ||
# kolejna rzecz: przerwanie rekurencji moze nastąpić, nawet wtedy, gdy kolejki będą miały w sobie bufory | ||
|
||
state = Map.update!(state, :satisfied_auto_output_pads, &MapSet.delete(&1, pad_data.ref)) | ||
|
||
# dobra, wyglada git | ||
|
||
state = AutoFlowUtils.pop_auto_flow_queues_while_needed(state) | ||
|
||
if MapSet.size(state.satisfied_auto_output_pads) == 0 do | ||
AutoFlowUtils.auto_adjust_atomic_demand(associated_pads, state) | ||
else | ||
state | ||
end | ||
else | ||
state | ||
end | ||
|
@@ -91,9 +107,15 @@ defmodule Membrane.Core.Element.DemandController do | |
buffers_size = Buffer.Metric.from_unit(pad_data.demand_unit).buffers_size(buffers) | ||
|
||
demand = pad_data.demand - buffers_size | ||
atomic_demand = AtomicDemand.decrease(pad_data.atomic_demand, buffers_size) | ||
{decrease_result, atomic_demand} = AtomicDemand.decrease(pad_data.atomic_demand, buffers_size) | ||
|
||
PadModel.set_data!(state, pad_ref, %{ | ||
with {:decreased, new_value} when new_value <= 0 <- decrease_result, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What 0 represent? Could it be dumped into module attr, to avoid magic numbers? |
||
%{flow_control: :auto} <- pad_data do | ||
Map.update!(state, :satisfied_auto_output_pads, &MapSet.put(&1, pad_ref)) | ||
else | ||
_other -> state | ||
end | ||
|> PadModel.set_data!(pad_ref, %{ | ||
pad_data | ||
| demand: demand, | ||
atomic_demand: atomic_demand | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,9 +1,16 @@ | ||||||||||||
defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do | ||||||||||||
@moduledoc false | ||||||||||||
|
||||||||||||
alias Membrane.Buffer | ||||||||||||
alias Membrane.Event | ||||||||||||
alias Membrane.StreamFormat | ||||||||||||
|
||||||||||||
alias Membrane.Core.Element.{ | ||||||||||||
AtomicDemand, | ||||||||||||
State | ||||||||||||
BufferController, | ||||||||||||
EventController, | ||||||||||||
State, | ||||||||||||
StreamFormatController | ||||||||||||
} | ||||||||||||
Comment on lines
8
to
14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: Are you sure you want to use |
||||||||||||
|
||||||||||||
require Membrane.Core.Child.PadModel, as: PadModel | ||||||||||||
|
@@ -59,14 +66,36 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do | |||||||||||
state | ||||||||||||
end | ||||||||||||
|
||||||||||||
@spec auto_adjust_atomic_demand(Pad.ref() | [Pad.ref()], State.t()) :: State.t() | ||||||||||||
def auto_adjust_atomic_demand(pad_ref_list, state) when is_list(pad_ref_list) do | ||||||||||||
Enum.reduce(pad_ref_list, state, &auto_adjust_atomic_demand/2) | ||||||||||||
@spec store_buffers_in_queue(Pad.ref(), [Buffer.t()], State.t()) :: State.t() | ||||||||||||
def store_buffers_in_queue(pad_ref, buffers, state) do | ||||||||||||
state = Map.update!(state, :awaiting_auto_input_pads, &MapSet.put(&1, pad_ref)) | ||||||||||||
store_in_queue(pad_ref, :buffers, buffers, state) | ||||||||||||
end | ||||||||||||
|
||||||||||||
@spec store_event_in_queue(Pad.ref(), Event.t(), State.t()) :: State.t() | ||||||||||||
def store_event_in_queue(pad_ref, event, state) do | ||||||||||||
store_in_queue(pad_ref, :event, event, state) | ||||||||||||
end | ||||||||||||
|
||||||||||||
def auto_adjust_atomic_demand(pad_ref, state) when Pad.is_pad_ref(pad_ref) do | ||||||||||||
PadModel.get_data!(state, pad_ref) | ||||||||||||
|> do_auto_adjust_atomic_demand(state) | ||||||||||||
@spec store_stream_format_in_queue(Pad.ref(), StreamFormat.t(), State.t()) :: State.t() | ||||||||||||
def store_stream_format_in_queue(pad_ref, stream_format, state) do | ||||||||||||
store_in_queue(pad_ref, :stream_format, stream_format, state) | ||||||||||||
end | ||||||||||||
|
||||||||||||
defp store_in_queue(pad_ref, type, item, state) do | ||||||||||||
PadModel.update_data!(state, pad_ref, :auto_flow_queue, &Qex.push(&1, {type, item})) | ||||||||||||
end | ||||||||||||
|
||||||||||||
@spec auto_adjust_atomic_demand(Pad.ref() | [Pad.ref()], State.t()) :: State.t() | ||||||||||||
def auto_adjust_atomic_demand(ref_or_ref_list, state) | ||||||||||||
when Pad.is_pad_ref(ref_or_ref_list) or is_list(ref_or_ref_list) do | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would not the
Suggested change
|
||||||||||||
ref_or_ref_list | ||||||||||||
|> Bunch.listify() | ||||||||||||
|> Enum.reduce(state, fn pad_ref, state -> | ||||||||||||
PadModel.get_data!(state, pad_ref) | ||||||||||||
|> do_auto_adjust_atomic_demand(state) | ||||||||||||
Comment on lines
+169
to
+170
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤭
Suggested change
|
||||||||||||
|> elem(1) # todo: usun to :increased / :unchanged, ktore discardujesz w tym elem(1) | ||||||||||||
end) | ||||||||||||
end | ||||||||||||
|
||||||||||||
defp do_auto_adjust_atomic_demand(pad_data, state) when is_input_auto_pad_data(pad_data) do | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing spec
Suggested change
|
||||||||||||
|
@@ -83,9 +112,11 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do | |||||||||||
:ok = AtomicDemand.increase(atomic_demand, diff) | ||||||||||||
|
||||||||||||
:atomics.put(stalker_metrics.demand, 1, auto_demand_size) | ||||||||||||
PadModel.set_data!(state, ref, :demand, auto_demand_size) | ||||||||||||
|
||||||||||||
state = PadModel.set_data!(state, ref, :demand, auto_demand_size) | ||||||||||||
{:increased, state} | ||||||||||||
else | ||||||||||||
state | ||||||||||||
{:unchanged, state} | ||||||||||||
end | ||||||||||||
end | ||||||||||||
|
||||||||||||
|
@@ -97,14 +128,66 @@ defmodule Membrane.Core.Element.DemandController.AutoFlowUtils do | |||||||||||
state.effective_flow_control == :pull and | ||||||||||||
not pad_data.auto_demand_paused? and | ||||||||||||
pad_data.demand < pad_data.auto_demand_size / 2 and | ||||||||||||
Enum.all?(pad_data.associated_pads, &atomic_demand_positive?(&1, state)) | ||||||||||||
output_auto_demand_positive?(state) | ||||||||||||
end | ||||||||||||
|
||||||||||||
@spec pop_auto_flow_queues_while_needed(State.t()) :: State.t() | ||||||||||||
def pop_auto_flow_queues_while_needed(state) do | ||||||||||||
if (state.effective_flow_control == :push or | ||||||||||||
MapSet.size(state.satisfied_auto_output_pads) == 0) and | ||||||||||||
MapSet.size(state.awaiting_auto_input_pads) > 0 do | ||||||||||||
pop_random_auto_flow_queue(state) | ||||||||||||
|> pop_auto_flow_queues_while_needed() | ||||||||||||
else | ||||||||||||
state | ||||||||||||
end | ||||||||||||
end | ||||||||||||
|
||||||||||||
defp pop_random_auto_flow_queue(state) do | ||||||||||||
pad_ref = Enum.random(state.awaiting_auto_input_pads) | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe popping more than one buffer from each queue at a time would speed it up There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean to do everything in the same way as it is, but just call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes |
||||||||||||
|
||||||||||||
PadModel.get_data!(state, pad_ref, :auto_flow_queue) | ||||||||||||
|> Qex.pop() | ||||||||||||
|> case do | ||||||||||||
{{:value, {:buffers, buffers}}, popped_queue} -> | ||||||||||||
state = PadModel.set_data!(state, pad_ref, :auto_flow_queue, popped_queue) | ||||||||||||
state = BufferController.exec_buffer_callback(pad_ref, buffers, state) | ||||||||||||
pop_stream_formats_and_events(pad_ref, state) | ||||||||||||
|
||||||||||||
{:empty, _empty_queue} -> | ||||||||||||
Map.update!(state, :awaiting_auto_input_pads, &MapSet.delete(&1, pad_ref)) | ||||||||||||
end | ||||||||||||
end | ||||||||||||
|
||||||||||||
defp atomic_demand_positive?(pad_ref, state) do | ||||||||||||
atomic_demand_value = | ||||||||||||
PadModel.get_data!(state, pad_ref, :atomic_demand) | ||||||||||||
|> AtomicDemand.get() | ||||||||||||
defp pop_stream_formats_and_events(pad_ref, state) do | ||||||||||||
PadModel.get_data!(state, pad_ref, :auto_flow_queue) | ||||||||||||
|> Qex.pop() | ||||||||||||
Comment on lines
+280
to
+281
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|> case do | ||||||||||||
{{:value, {type, item}}, popped_queue} when type in [:event, :stream_format] -> | ||||||||||||
state = PadModel.set_data!(state, pad_ref, :auto_flow_queue, popped_queue) | ||||||||||||
state = exec_queue_item_callback(pad_ref, {type, item}, state) | ||||||||||||
pop_stream_formats_and_events(pad_ref, state) | ||||||||||||
|
||||||||||||
{{:value, {:buffers, _buffers}}, _popped_queue} -> | ||||||||||||
state | ||||||||||||
|
||||||||||||
{:empty, _empty_queue} -> | ||||||||||||
Map.update!(state, :awaiting_auto_input_pads, &MapSet.delete(&1, pad_ref)) | ||||||||||||
end | ||||||||||||
end | ||||||||||||
|
||||||||||||
defp output_auto_demand_positive?(%State{satisfied_auto_output_pads: pads}), | ||||||||||||
do: MapSet.size(pads) == 0 | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pattern match is faster than looping over whole map
Suggested change
|
||||||||||||
|
||||||||||||
defp exec_queue_item_callback(pad_ref, {:buffers, buffers}, state) do | ||||||||||||
BufferController.exec_buffer_callback(pad_ref, buffers, state) | ||||||||||||
end | ||||||||||||
|
||||||||||||
defp exec_queue_item_callback(pad_ref, {:event, event}, state) do | ||||||||||||
EventController.exec_handle_event(pad_ref, event, state) | ||||||||||||
end | ||||||||||||
|
||||||||||||
atomic_demand_value > 0 | ||||||||||||
defp exec_queue_item_callback(pad_ref, {:stream_format, stream_format}, state) do | ||||||||||||
StreamFormatController.exec_handle_stream_format(pad_ref, stream_format, state) | ||||||||||||
end | ||||||||||||
end |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -18,6 +18,8 @@ defmodule Membrane.Core.Element.EventController do | |||
State | ||||
} | ||||
|
||||
alias Membrane.Core.Element.DemandController.AutoFlowUtils | ||||
|
||||
require Membrane.Core.Child.PadModel | ||||
require Membrane.Core.Message | ||||
require Membrane.Core.Telemetry | ||||
|
@@ -39,15 +41,22 @@ defmodule Membrane.Core.Element.EventController do | |||
playback: %State{playback: :playing} <- state do | ||||
Telemetry.report_metric(:event, 1, inspect(pad_ref)) | ||||
|
||||
if not Event.async?(event) and buffers_before_event_present?(data) do | ||||
PadModel.update_data!( | ||||
state, | ||||
pad_ref, | ||||
:input_queue, | ||||
&InputQueue.store(&1, :event, event) | ||||
) | ||||
else | ||||
exec_handle_event(pad_ref, event, state) | ||||
cond do | ||||
# events goes to the manual flow control input queue | ||||
not Event.async?(event) and buffers_before_event_present?(data) -> | ||||
PadModel.update_data!( | ||||
state, | ||||
pad_ref, | ||||
:input_queue, | ||||
&InputQueue.store(&1, :event, event) | ||||
) | ||||
|
||||
# event goes to the auto flow control queue | ||||
pad_ref in state.awaiting_auto_input_pads -> | ||||
AutoFlowUtils.store_event_in_queue(pad_ref, event, state) | ||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here we can potentially store async events in the queue There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean the first case of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On master there's an
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I just transformed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it's not :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1st condition in |
||||
true -> | ||||
exec_handle_event(pad_ref, event, state) | ||||
end | ||||
else | ||||
pad: {:error, :unknown_pad} -> | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Little suggestion, to break this conditional block;