From 4e699bd94fddec5cdd3215807f9257880f6d49f3 Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Tue, 3 Jan 2023 18:13:20 +0000 Subject: [PATCH] audio VTS: Refactor support for non-deterministic SM behavior Use a DAG instead of a list for representing stream state machine transitions. This allows accomodating multiple possible replies from the stream state machine, and enables following different, possibly converging test sequences depending on actual replies. Update the async output state machine graph including possible state transitions that are implemented but were missing from the graph. Bug: 262402957 Test: atest VtsHalAudioCoreTargetTest Change-Id: Ie97f3ce9222eec812d4eb4dfbce1f678370bef26 --- .../audio/core/stream-out-async-sm.gv | 2 + .../vts/VtsHalAudioCoreModuleTargetTest.cpp | 439 +++++++++++------- 2 files changed, 277 insertions(+), 164 deletions(-) diff --git a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv index e25b15a755..501dc0169d 100644 --- a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv +++ b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv @@ -30,6 +30,7 @@ digraph stream_out_async_state_machine { STANDBY -> PAUSED [label="burst"]; // producer -> active IDLE -> STANDBY [label="standby"]; // consumer -> passive IDLE -> TRANSFERRING [label="burst"]; // producer -> active + IDLE -> ACTIVE [label="burst"]; // full write ACTIVE -> PAUSED [label="pause"]; // consumer -> passive (not consuming) ACTIVE -> DRAINING [label="drain"]; // producer -> passive ACTIVE -> TRANSFERRING [label="burst"]; // early unblocking @@ -45,6 +46,7 @@ digraph stream_out_async_state_machine { PAUSED -> IDLE [label="flush"]; // producer -> passive, buffer is cleared DRAINING -> IDLE [label="←IStreamCallback.onDrainReady"]; DRAINING -> TRANSFERRING [label="burst"]; // producer -> active + DRAINING -> ACTIVE [label="burst"]; // full write DRAINING -> DRAIN_PAUSED [label="pause"]; // consumer -> passive (not consuming) DRAIN_PAUSED -> DRAINING [label="start"]; // consumer -> active DRAIN_PAUSED -> TRANSFER_PAUSED [label="burst"]; // producer -> active diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp index 8da475e4a2..b67676488f 100644 --- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -504,10 +505,48 @@ std::string toString(StreamEventReceiver::Event event) { return std::to_string(static_cast(event)); } +// Note: we use a reference wrapper, not a pointer, because methods of std::*list +// return references to inserted elements. This way, we can put a returned reference +// into the children vector without any type conversions, and this makes DAG creation +// code more clear. +template +struct DagNode : public std::pair>>> { + using Children = std::vector>; + DagNode(const T& t, const Children& c) : std::pair(t, c) {} + DagNode(T&& t, Children&& c) : std::pair(std::move(t), std::move(c)) {} + const T& datum() const { return this->first; } + Children& children() { return this->second; } + const Children& children() const { return this->second; } +}; +// Since DagNodes do contain references to next nodes, node links provided +// by the list are not used. Thus, the order of the nodes in the list is not +// important, except that the starting node must be at the front of the list, +// which means, it must always be added last. +template +struct Dag : public std::forward_list> { + Dag() = default; + // We prohibit copying and moving Dag instances because implementing that + // is not trivial due to references between nodes. + Dag(const Dag&) = delete; + Dag(Dag&&) = delete; + Dag& operator=(const Dag&) = delete; + Dag& operator=(Dag&&) = delete; +}; + // Transition to the next state happens either due to a command from the client, // or after an event received from the server. using TransitionTrigger = std::variant; -using StateTransition = std::pair; +std::string toString(const TransitionTrigger& trigger) { + if (std::holds_alternative(trigger)) { + return std::string("'") + .append(toString(std::get(trigger).getTag())) + .append("' command"); + } + return std::string("'") + .append(toString(std::get(trigger))) + .append("' event"); +} + struct StateSequence { virtual ~StateSequence() = default; virtual void rewind() = 0; @@ -517,6 +556,10 @@ struct StateSequence { virtual void advance(StreamDescriptor::State state) = 0; }; +// Defines the current state and the trigger to transfer to the next one, +// thus "state" is the "from" state. +using StateTransitionFrom = std::pair; + static const StreamDescriptor::Command kGetStatusCommand = StreamDescriptor::Command::make(Void{}); static const StreamDescriptor::Command kStartCommand = @@ -542,66 +585,65 @@ static const StreamEventReceiver::Event kTransferReadyEvent = StreamEventReceiver::Event::TransferReady; static const StreamEventReceiver::Event kDrainReadyEvent = StreamEventReceiver::Event::DrainReady; -// Handle possible bifurcations: -// - on burst and on start: 'TRANSFERRING' -> {'ACTIVE', 'TRANSFERRING'} -// - on pause: 'TRANSFER_PAUSED' -> {'PAUSED', 'TRANSFER_PAUSED'} -// It is assumed that the 'steps' provided on the construction contain the sequence -// for the async case, which gets corrected in the case when the HAL decided to do -// a synchronous transfer. -class SmartStateSequence : public StateSequence { +struct StateDag : public Dag { + using Node = StateDag::reference; + using NextStates = StateDag::value_type::Children; + + template + Node makeNode(StreamDescriptor::State s, TransitionTrigger t, Next&&... next) { + return emplace_front(std::make_pair(s, t), NextStates{std::forward(next)...}); + } + Node makeNodes(const std::vector& v, Node last) { + auto helper = [&](auto i, auto&& h) -> Node { + if (i == v.end()) return last; + return makeNode(i->first, i->second, h(++i, h)); + }; + return helper(v.begin(), helper); + } + Node makeNodes(const std::vector& v, StreamDescriptor::State f) { + return makeNodes(v, makeFinalNode(f)); + } + Node makeFinalNode(StreamDescriptor::State s) { + // The actual command used here is irrelevant. Since it's the final node + // in the test sequence, no commands sent after reaching it. + return emplace_front(std::make_pair(s, kGetStatusCommand), NextStates{}); + } +}; + +class StateSequenceFollower : public StateSequence { public: - explicit SmartStateSequence(const std::vector& steps) : mSteps(steps) {} - explicit SmartStateSequence(std::vector&& steps) : mSteps(std::move(steps)) {} - void rewind() override { mCurrentStep = 0; } - bool done() const override { return mCurrentStep >= mSteps.size(); } - TransitionTrigger getTrigger() override { return mSteps[mCurrentStep].first; } + explicit StateSequenceFollower(std::unique_ptr steps) + : mSteps(std::move(steps)), mCurrent(mSteps->front()) {} + void rewind() override { mCurrent = mSteps->front(); } + bool done() const override { return current().children().empty(); } + TransitionTrigger getTrigger() override { return current().datum().second; } std::set getExpectedStates() override { - std::set result = {getState()}; - if (isBurstBifurcation() || isStartBifurcation()) { - result.insert(StreamDescriptor::State::ACTIVE); - } else if (isPauseBifurcation()) { - result.insert(StreamDescriptor::State::PAUSED); - } + std::set result; + std::transform(current().children().cbegin(), current().children().cend(), + std::inserter(result, result.begin()), + [](const auto& node) { return node.get().datum().first; }); + LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(result); return result; } void advance(StreamDescriptor::State state) override { - if (isBurstBifurcation() && state == StreamDescriptor::State::ACTIVE && - mCurrentStep + 1 < mSteps.size() && - mSteps[mCurrentStep + 1].first == TransitionTrigger{kTransferReadyEvent}) { - mCurrentStep++; + if (auto it = std::find_if( + current().children().cbegin(), current().children().cend(), + [&](const auto& node) { return node.get().datum().first == state; }); + it != current().children().cend()) { + LOG(DEBUG) << __func__ << ": " << toString(mCurrent.get().datum().first) << " -> " + << toString(it->get().datum().first); + mCurrent = *it; + } else { + LOG(FATAL) << __func__ << ": state " << toString(state) << " is unexpected"; } - mCurrentStep++; } private: - StreamDescriptor::State getState() const { return mSteps[mCurrentStep].second; } - bool isBurstBifurcation() { - return getTrigger() == TransitionTrigger{kBurstCommand} && - getState() == StreamDescriptor::State::TRANSFERRING; - } - bool isPauseBifurcation() { - return getTrigger() == TransitionTrigger{kPauseCommand} && - getState() == StreamDescriptor::State::TRANSFER_PAUSED; - } - bool isStartBifurcation() { - return getTrigger() == TransitionTrigger{kStartCommand} && - getState() == StreamDescriptor::State::TRANSFERRING; - } - const std::vector mSteps; - size_t mCurrentStep = 0; + StateDag::const_reference current() const { return mCurrent.get(); } + std::unique_ptr mSteps; + std::reference_wrapper mCurrent; }; -std::string toString(const TransitionTrigger& trigger) { - if (std::holds_alternative(trigger)) { - return std::string("'") - .append(toString(std::get(trigger).getTag())) - .append("' command"); - } - return std::string("'") - .append(toString(std::get(trigger))) - .append("' event"); -} - struct StreamLogicDriver { virtual ~StreamLogicDriver() = default; // Return 'true' to stop the worker. @@ -3219,38 +3261,52 @@ static const int kStreamTransientStateTransitionDelayMs = 3000; // TODO: Add async test cases for input once it is implemented. -std::shared_ptr makeBurstCommands(bool isSync, size_t burstCount) { - const auto burst = - isSync ? std::vector{std::make_pair(kBurstCommand, - StreamDescriptor::State::ACTIVE)} - : std::vector{ - std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING), - std::make_pair(kTransferReadyEvent, StreamDescriptor::State::ACTIVE)}; - std::vector result{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE)}; - for (size_t i = 0; i < burstCount; ++i) { - result.insert(result.end(), burst.begin(), burst.end()); +std::shared_ptr makeBurstCommands(bool isSync) { + using State = StreamDescriptor::State; + auto d = std::make_unique(); + StateDag::Node last = d->makeFinalNode(State::ACTIVE); + StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, last); + StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active); + if (!isSync) { + // Allow optional routing via the TRANSFERRING state on bursts. + active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, last)); + idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active)); } - return std::make_shared(result); + d->makeNode(State::STANDBY, kStartCommand, idle); + return std::make_shared(std::move(d)); } static const NamedCommandSequence kReadSeq = - std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true, 3)); -static const NamedCommandSequence kWriteSyncSeq = std::make_tuple( - std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true, 3)); -static const NamedCommandSequence kWriteAsyncSeq = std::make_tuple( - std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false, 3)); + std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true)); +static const NamedCommandSequence kWriteSyncSeq = + std::make_tuple(std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true)); +static const NamedCommandSequence kWriteAsyncSeq = + std::make_tuple(std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false)); std::shared_ptr makeAsyncDrainCommands(bool isInput) { - return std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, isInput ? StreamDescriptor::State::ACTIVE - : StreamDescriptor::State::TRANSFERRING), - std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand, - StreamDescriptor::State::DRAINING), - isInput ? std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE) - : std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING), - std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand, - StreamDescriptor::State::DRAINING)}); + using State = StreamDescriptor::State; + auto d = std::make_unique(); + if (isInput) { + d->makeNodes({std::make_pair(State::STANDBY, kStartCommand), + std::make_pair(State::IDLE, kBurstCommand), + std::make_pair(State::ACTIVE, kDrainInCommand), + std::make_pair(State::DRAINING, kStartCommand), + std::make_pair(State::ACTIVE, kDrainInCommand)}, + State::DRAINING); + } else { + StateDag::Node draining = + d->makeNodes({std::make_pair(State::DRAINING, kBurstCommand), + std::make_pair(State::TRANSFERRING, kDrainOutAllCommand)}, + State::DRAINING); + StateDag::Node idle = + d->makeNodes({std::make_pair(State::IDLE, kBurstCommand), + std::make_pair(State::TRANSFERRING, kDrainOutAllCommand)}, + draining); + // If we get straight into ACTIVE on burst, no further testing is possible. + draining.children().push_back(d->makeFinalNode(State::ACTIVE)); + idle.children().push_back(d->makeFinalNode(State::ACTIVE)); + d->makeNode(State::STANDBY, kStartCommand, idle); + } + return std::make_shared(std::move(d)); } static const NamedCommandSequence kWriteDrainAsyncSeq = std::make_tuple(std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs, @@ -3259,58 +3315,86 @@ static const NamedCommandSequence kDrainInSeq = std::make_tuple( std::string("Drain"), 0, StreamTypeFilter::ANY, makeAsyncDrainCommands(true)); std::shared_ptr makeDrainOutCommands(bool isSync) { - return std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING), - std::make_pair(isSync ? TransitionTrigger(kGetStatusCommand) - : TransitionTrigger(kDrainReadyEvent), - StreamDescriptor::State::IDLE)}); + using State = StreamDescriptor::State; + auto d = std::make_unique(); + StateDag::Node active = d->makeNodes( + {std::make_pair(State::ACTIVE, kDrainOutAllCommand), + std::make_pair(State::DRAINING, isSync ? TransitionTrigger(kGetStatusCommand) + : TransitionTrigger(kDrainReadyEvent))}, + State::IDLE); + StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active); + if (!isSync) { + idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active)); + } + d->makeNode(State::STANDBY, kStartCommand, idle); + return std::make_shared(std::move(d)); } static const NamedCommandSequence kDrainOutSyncSeq = std::make_tuple( std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true)); static const NamedCommandSequence kDrainOutAsyncSeq = std::make_tuple( std::string("Drain"), 0, StreamTypeFilter::ASYNC, makeDrainOutCommands(false)); -std::shared_ptr makeDrainOutPauseCommands(bool isSync) { - return std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE - : StreamDescriptor::State::TRANSFERRING), - std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING), - std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), - std::make_pair(kStartCommand, StreamDescriptor::State::DRAINING), - std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), - std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::PAUSED - : StreamDescriptor::State::TRANSFER_PAUSED)}); +std::shared_ptr makeDrainPauseOutCommands(bool isSync) { + using State = StreamDescriptor::State; + auto d = std::make_unique(); + StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand), + std::make_pair(State::DRAIN_PAUSED, kStartCommand), + std::make_pair(State::DRAINING, kPauseCommand), + std::make_pair(State::DRAIN_PAUSED, kBurstCommand)}, + isSync ? State::PAUSED : State::TRANSFER_PAUSED); + StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutAllCommand, draining); + StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active); + if (!isSync) { + idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining)); + } + d->makeNode(State::STANDBY, kStartCommand, idle); + return std::make_shared(std::move(d)); } static const NamedCommandSequence kDrainPauseOutSyncSeq = std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::SYNC, makeDrainOutPauseCommands(true)); + StreamTypeFilter::SYNC, makeDrainPauseOutCommands(true)); static const NamedCommandSequence kDrainPauseOutAsyncSeq = std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::ASYNC, makeDrainOutPauseCommands(false)); + StreamTypeFilter::ASYNC, makeDrainPauseOutCommands(false)); // This sequence also verifies that the capture / presentation position is not reset on standby. std::shared_ptr makeStandbyCommands(bool isInput, bool isSync) { - return std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY), - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, isInput || isSync - ? StreamDescriptor::State::ACTIVE - : StreamDescriptor::State::TRANSFERRING), - std::make_pair(kPauseCommand, isInput || isSync - ? StreamDescriptor::State::PAUSED - : StreamDescriptor::State::TRANSFER_PAUSED), - std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY - : StreamDescriptor::State::IDLE), - std::make_pair(isInput ? kGetStatusCommand : kStandbyCommand, // no-op for input - StreamDescriptor::State::STANDBY), - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, isInput || isSync - ? StreamDescriptor::State::ACTIVE - : StreamDescriptor::State::TRANSFERRING)}); + using State = StreamDescriptor::State; + auto d = std::make_unique(); + if (isInput) { + d->makeNodes({std::make_pair(State::STANDBY, kStartCommand), + std::make_pair(State::IDLE, kStandbyCommand), + std::make_pair(State::STANDBY, kStartCommand), + std::make_pair(State::IDLE, kBurstCommand), + std::make_pair(State::ACTIVE, kPauseCommand), + std::make_pair(State::PAUSED, kFlushCommand), + std::make_pair(State::STANDBY, kStartCommand), + std::make_pair(State::IDLE, kBurstCommand)}, + State::ACTIVE); + } else { + StateDag::Node idle3 = + d->makeNode(State::IDLE, kBurstCommand, d->makeFinalNode(State::ACTIVE)); + StateDag::Node idle2 = d->makeNodes({std::make_pair(State::IDLE, kStandbyCommand), + std::make_pair(State::STANDBY, kStartCommand)}, + idle3); + StateDag::Node active = d->makeNodes({std::make_pair(State::ACTIVE, kPauseCommand), + std::make_pair(State::PAUSED, kFlushCommand)}, + idle2); + StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active); + if (!isSync) { + idle3.children().push_back(d->makeFinalNode(State::TRANSFERRING)); + StateDag::Node transferring = + d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand), + std::make_pair(State::TRANSFER_PAUSED, kFlushCommand)}, + idle2); + idle.children().push_back(transferring); + } + d->makeNodes({std::make_pair(State::STANDBY, kStartCommand), + std::make_pair(State::IDLE, kStandbyCommand), + std::make_pair(State::STANDBY, kStartCommand)}, + idle); + } + return std::make_shared(std::move(d)); } static const NamedCommandSequence kStandbyInSeq = std::make_tuple( std::string("Standby"), 0, StreamTypeFilter::ANY, makeStandbyCommands(true, false)); @@ -3320,50 +3404,71 @@ static const NamedCommandSequence kStandbyOutAsyncSeq = std::make_tuple(std::string("Standby"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, makeStandbyCommands(false, false)); -static const NamedCommandSequence kPauseInSeq = - std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::ANY, - std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)})); -static const NamedCommandSequence kPauseOutSyncSeq = - std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::SYNC, - std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)})); -/* TODO: Figure out a better way for testing sync/async bursts -static const NamedCommandSequence kPauseOutAsyncSeq = std::make_tuple( - std::string("Pause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, - std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING), - std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED), - std::make_pair(kStartCommand, StreamDescriptor::State::TRANSFERRING), - std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED), - std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAIN_PAUSED), - std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFER_PAUSED)})); -*/ +std::shared_ptr makePauseCommands(bool isInput, bool isSync) { + using State = StreamDescriptor::State; + auto d = std::make_unique(); + if (isInput) { + d->makeNodes({std::make_pair(State::STANDBY, kStartCommand), + std::make_pair(State::IDLE, kBurstCommand), + std::make_pair(State::ACTIVE, kPauseCommand), + std::make_pair(State::PAUSED, kBurstCommand), + std::make_pair(State::ACTIVE, kPauseCommand), + std::make_pair(State::PAUSED, kFlushCommand)}, + State::STANDBY); + } else { + StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand), + std::make_pair(State::ACTIVE, kPauseCommand), + std::make_pair(State::PAUSED, kStartCommand), + std::make_pair(State::ACTIVE, kPauseCommand), + std::make_pair(State::PAUSED, kBurstCommand), + std::make_pair(State::PAUSED, kStartCommand), + std::make_pair(State::ACTIVE, kPauseCommand)}, + State::PAUSED); + if (!isSync) { + idle.children().push_back( + d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand), + std::make_pair(State::TRANSFER_PAUSED, kStartCommand), + std::make_pair(State::TRANSFERRING, kPauseCommand), + std::make_pair(State::TRANSFER_PAUSED, kDrainOutAllCommand), + std::make_pair(State::DRAIN_PAUSED, kBurstCommand)}, + State::TRANSFER_PAUSED)); + } + d->makeNode(State::STANDBY, kStartCommand, idle); + } + return std::make_shared(std::move(d)); +} +static const NamedCommandSequence kPauseInSeq = std::make_tuple( + std::string("Pause"), 0, StreamTypeFilter::ANY, makePauseCommands(true, false)); +static const NamedCommandSequence kPauseOutSyncSeq = std::make_tuple( + std::string("Pause"), 0, StreamTypeFilter::SYNC, makePauseCommands(false, true)); +static const NamedCommandSequence kPauseOutAsyncSeq = + std::make_tuple(std::string("Pause"), kStreamTransientStateTransitionDelayMs, + StreamTypeFilter::ASYNC, makePauseCommands(false, false)); std::shared_ptr makeFlushCommands(bool isInput, bool isSync) { - return std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, isInput || isSync - ? StreamDescriptor::State::ACTIVE - : StreamDescriptor::State::TRANSFERRING), - std::make_pair(kPauseCommand, isInput || isSync - ? StreamDescriptor::State::PAUSED - : StreamDescriptor::State::TRANSFER_PAUSED), - std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY - : StreamDescriptor::State::IDLE)}); + using State = StreamDescriptor::State; + auto d = std::make_unique(); + if (isInput) { + d->makeNodes({std::make_pair(State::STANDBY, kStartCommand), + std::make_pair(State::IDLE, kBurstCommand), + std::make_pair(State::ACTIVE, kPauseCommand), + std::make_pair(State::PAUSED, kFlushCommand)}, + State::STANDBY); + } else { + StateDag::Node last = d->makeFinalNode(State::IDLE); + StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand), + std::make_pair(State::ACTIVE, kPauseCommand), + std::make_pair(State::PAUSED, kFlushCommand)}, + last); + if (!isSync) { + idle.children().push_back( + d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand), + std::make_pair(State::TRANSFER_PAUSED, kFlushCommand)}, + last)); + } + d->makeNode(State::STANDBY, kStartCommand, idle); + } + return std::make_shared(std::move(d)); } static const NamedCommandSequence kFlushInSeq = std::make_tuple( std::string("Flush"), 0, StreamTypeFilter::ANY, makeFlushCommands(true, false)); @@ -3374,13 +3479,19 @@ static const NamedCommandSequence kFlushOutAsyncSeq = StreamTypeFilter::ASYNC, makeFlushCommands(false, false)); std::shared_ptr makeDrainPauseFlushOutCommands(bool isSync) { - return std::make_shared(std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE - : StreamDescriptor::State::TRANSFERRING), - std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING), - std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), - std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)}); + using State = StreamDescriptor::State; + auto d = std::make_unique(); + StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand), + std::make_pair(State::DRAIN_PAUSED, kFlushCommand)}, + State::IDLE); + StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand), + std::make_pair(State::ACTIVE, kDrainOutAllCommand)}, + draining); + if (!isSync) { + idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining)); + } + d->makeNode(State::STANDBY, kStartCommand, idle); + return std::make_shared(std::move(d)); } static const NamedCommandSequence kDrainPauseFlushOutSyncSeq = std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,