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,