diff options
Diffstat (limited to 'src/Runtime/Source/stateapplication/Qt3DSStateInterpreter.cpp')
-rw-r--r-- | src/Runtime/Source/stateapplication/Qt3DSStateInterpreter.cpp | 2057 |
1 files changed, 2057 insertions, 0 deletions
diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateInterpreter.cpp b/src/Runtime/Source/stateapplication/Qt3DSStateInterpreter.cpp new file mode 100644 index 00000000..9fc72406 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateInterpreter.cpp @@ -0,0 +1,2057 @@ +/**************************************************************************** +** +** Copyright (C) 2013 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "Qt3DSStateTypes.h" +#include "Qt3DSStateInterpreter.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSIntrinsics.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Qt3DSContainers.h" +#include "foundation/Utils.h" +#include "foundation/Qt3DSPool.h" +#include "foundation/Qt3DSTime.h" +#include "EASTL/string.h" +#include "EASTL/set.h" +#include "EASTL/sort.h" +#include "EASTL/list.h" +#include "Qt3DSStateExecutionContext.h" +#include "Qt3DSStateExecutionTypes.h" +#include "Qt3DSStateScriptContext.h" +#include "Qt3DSStateSharedImpl.h" +#include "foundation/XML.h" +#include "Qt3DSStateIdValue.h" +#include "Qt3DSStateDebugger.h" +#include "Qt3DSStateXMLIO.h" +#include "Qt3DSStateDebuggerValues.h" +#include "Qt3DSStateContext.h" +#include "foundation/FastAllocator.h" + +using namespace qt3ds::state; +using namespace qt3ds::state::debugger; + +#ifdef _DEBUG +#define QT3DS_LOG_ENTER_EXIT 1 +#define QT3DS_LOG_ACTIVE_EVENT 1 +#endif + +namespace qt3ds { +namespace state { + struct SInterpreterData + { + }; +} +} + +namespace { +struct SSimpleEvent : public IEvent +{ + NVAllocatorCallback &m_Alloc; + volatile QT3DSI32 mRefCount; + CRegisteredString m_Name; + + SSimpleEvent(NVAllocatorCallback &alloc, CRegisteredString nm) + : m_Alloc(alloc) + , mRefCount(0) + , m_Name(nm) + { + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Alloc) + + CRegisteredString GetName() const override { return m_Name; } +}; + +struct SStateNodeInterpreterData : public SInterpreterData +{ + + QT3DSI32 m_DocumentOrder; + bool m_Entered; + SStateNode *m_TransitionSubgraphRoot; + + SStateNodeInterpreterData(QT3DSI32 inDocOrder) + : m_DocumentOrder(inDocOrder) + , m_Entered(false) + , m_TransitionSubgraphRoot(NULL) + { + } +}; + +template <typename TDataType, typename THasher = eastl::hash<TDataType>, + typename TEqOp = eastl::equal_to<TDataType>> +struct SOrderedSet +{ + typedef eastl::hash_set<TDataType, THasher, TEqOp, ForwardingAllocator> TSetType; + typedef nvvector<TDataType> TListType; + + TSetType m_Set; + TListType m_List; + typedef typename TListType::iterator iterator; + + SOrderedSet(NVAllocatorCallback &alloc, const char *inTypeName) + : m_Set(ForwardingAllocator(alloc, inTypeName)) + , m_List(alloc, inTypeName) + { + } + + bool insert(const TDataType &inDtype) + { + if (m_Set.insert(inDtype).second) { + m_List.push_back(inDtype); + return true; + } + return false; + } + + void clear() + { + m_Set.clear(); + m_List.clear(); + } + + iterator begin() { return m_List.begin(); } + iterator end() { return m_List.end(); } + QT3DSU32 size() const { return m_List.size(); } + bool contains(const TDataType &inType) const { return m_Set.find(inType) != m_Set.end(); } + TDataType operator[](int inIndex) + { + if (inIndex < (int)size()) + return m_List[inIndex]; + return TDataType(); + } + void erase(const TDataType &inType) + { + typename TSetType::iterator iter = m_Set.find(inType); + if (iter != m_Set.end()) { + m_Set.erase(iter); + typename TListType::iterator iter = eastl::find(m_List.begin(), m_List.end(), inType); + if (iter != m_List.end()) + m_List.erase(iter); + else { + QT3DS_ASSERT(false); + } + } + } +}; + +struct SStateNodeNode +{ + SStateNode *m_Node; + SStateNodeNode *m_NextSibling; + SStateNodeNode() + : m_Node(NULL) + , m_NextSibling(NULL) + { + } + SStateNodeNode(SStateNode *inNode) + : m_Node(inNode) + , m_NextSibling(NULL) + { + } +}; + +DEFINE_INVASIVE_SINGLE_LIST(StateNodeNode); +IMPLEMENT_INVASIVE_SINGLE_LIST(StateNodeNode, m_NextSibling); + +struct SFutureEvent +{ + TEventPtr m_Event; + QT3DSU64 m_FireTime; + CRegisteredString m_CancelId; + bool m_IsExternal; + SFutureEvent(TEventPtr evt, QT3DSU64 ft, CRegisteredString id, bool inExternal) + : m_Event(evt) + , m_FireTime(ft) + , m_CancelId(id) + , m_IsExternal(inExternal) + { + } + SFutureEvent() + : m_FireTime((QT3DSU64)-1) + , m_IsExternal(false) + { + } + // We want the events sorted in *reverse* time order. + bool operator<(const SFutureEvent &inOther) const { return m_FireTime < inOther.m_FireTime; } +}; + +struct StateSystem; + +typedef SOrderedSet<SStateNode *> TStateNodeSet; +typedef SOrderedSet<STransition *> TTransitionSet; +typedef nvvector<STransition *> TTransitionList; + +struct SDebugInterface : public IStateMachineDebugInterface, public IStateLogger +{ + StateSystem &m_StateSystem; + // No listener means no events and no active breakpoints of course + NVScopedRefCounted<IStateMachineListener> m_Listener; + TDebugStr m_LogStr; + TDebugStr m_TempStr; + TDebugStrList m_EnterList; + TDebugStrList m_ExitList; + TTransitionIdList m_TransitionList; + + SDebugInterface(StateSystem &ss) + : m_StateSystem(ss) + , m_Listener(NULL) + { + } + // ignored as this object is part of the state system. + void addRef() override; + void release() override; + bool valid() { return m_Listener.mPtr != NULL; } + + void SetStateMachineListener(IStateMachineListener *inListener) override + { + if (m_Listener.mPtr != inListener) { + m_Listener = inListener; + if (m_Listener) + OnConnect(); + } + } + + // IStateLogger interface + void Log(const char8_t *inLabel, const char8_t *inExpression) override + { + if (m_Listener) { + m_LogStr.assign(nonNull(inLabel)); + m_LogStr.append(" - "); + m_LogStr.append(nonNull(inExpression)); + m_Listener->Log(m_LogStr); + } + } + + IScriptContext &GetScriptContext() override; + + // Functions implemented after StateSystem in order to take advantage of the StateSystem struct + // members directly. + void OnConnect(); + + void EventQueued(const char8_t *inEventName, bool inInternal) + { + m_Listener->EventQueued(inEventName, inInternal); + } + // Functions should not be called in there is not a valid listener + void BeginStep(); + void BeginMicrostep(); + void SetCurrentEvent(const char8_t *inEventName, bool inInternal); + void SetTransitionSet(const TTransitionList &inTransitions); + void SetExitSet(const TStateNodeSet &inSet); + void SetEnterSet(const TStateNodeSet &inSet); + // Log statements run through the debugger as well. + void EndMicrostep(); + void EndStep(); + void OnExternalBreak() override; +}; + +#define DEBUGGER_CALL(function) \ + if (m_DebugInterface.valid()) \ + m_DebugInterface.function(); +#define DEBUGGER_CALL1(function, arg) \ + if (m_DebugInterface.valid()) \ + m_DebugInterface.function(arg); +#define DEBUGGER_CALL2(function, arg, arg2) \ + if (m_DebugInterface.valid()) \ + m_DebugInterface.function(arg, arg2); + +#ifdef _WIN32 +#pragma warning(disable : 4355) +#endif +struct SStateSignalSender : public IStateSignalConnection +{ + NVAllocatorCallback &m_Alloc; + StateSystem *m_System; + IStateInterpreterEventHandler &m_Handler; + volatile QT3DSI32 mRefCount; + SStateSignalSender(NVAllocatorCallback &inAlloc, StateSystem &inSystem, + IStateInterpreterEventHandler &inHandler) + : m_Alloc(inAlloc) + , m_System(&inSystem) + , m_Handler(inHandler) + , mRefCount(0) + { + } + virtual ~SStateSignalSender(); + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Alloc) +}; + +// Note that we explicity don't addref the signal sender internally. This is by design. +typedef nvvector<SStateSignalSender *> TSignalSenderList; + +struct StateSystem : public IStateInterpreter +{ + typedef eastl::basic_string<char8_t, ForwardingAllocator> TStrType; + typedef Pool<SStateNodeInterpreterData, ForwardingAllocator> TInterperterDataPool; + typedef eastl::list<TEventPtr, ForwardingAllocator> TEventQueue; + typedef nvhash_map<SHistory *, TStateNodeNodeList> THistoryMap; + typedef Pool<SStateNodeNode, ForwardingAllocator> TStateNodeNodePool; + typedef nvvector<SFutureEvent> TFutureEventList; + typedef nvhash_set<STransition *> TTransitionHashSet; + typedef Pool<STransition, ForwardingAllocator> TTransitionPool; + + NVFoundationBase &m_Foundation; + NVScopedRefCounted<IStateContext> m_Context; + volatile QT3DSI32 mRefCount; + // Holds the leaves of the current state. + NVScopedRefCounted<IStringTable> m_StringTable; + NVScopedRefCounted<IScriptContext> m_ScriptContext; + NVScopedRefCounted<IExecutionContext> m_ExecutionContext; + nvvector<SStateNode *> m_ParentComparisonLHS; + nvvector<SStateNode *> m_ParentComparisonRHS; + nvvector<SStateNode *> m_AncestorsList; + nvvector<SStateNode *> m_IDRefList; + + THistoryMap m_HistoryMap; + + SOrderedSet<SStateNode *> m_Configuration; + nvvector<SStateNode *> m_StatesToInvoke; + nvvector<SStateNode *> m_TransitionTargetList; + TTransitionList m_EnabledTransitions; + + TEventQueue m_InternalQueue; + TEventQueue m_ExternalQueue; + + SOrderedSet<STransition *> m_TransitionSet; + + TStateNodeSet m_StatesToEnter; + TStateNodeSet m_StatesForDefaultEntry; + TStateNodeSet m_StatesToExit; + + TInterperterDataPool m_InterpreterDataPool; + TStateNodeNodePool m_StateNodeNodePool; + + TStrType m_Workspace; + + TFutureEventList m_FutureEvents; + + bool m_Initialized; + bool m_Running; + + SDebugInterface m_DebugInterface; + TSignalSenderList m_SignalSenders; + + TTransitionHashSet m_InvalidTransitions; + + TTransitionPool m_TransitionPool; + nvvector<STransition *> m_TemporaryTransitions; + nvhash_map<SStateNode *, STransition *> m_StateInitialTransitionMap; + SFastAllocator<> m_TemporaryAllocator; + TStrType m_IdSplitter; + nvvector<SStateNode *> m_TempNodeList; + + StateSystem(NVFoundationBase &inFnd, IStringTable &inStrTable, IScriptContext &inScriptContext, + IExecutionContext &inExecutionContext) + : m_Foundation(inFnd) + , mRefCount(0) + , m_StringTable(inStrTable) + , m_ScriptContext(inScriptContext) + , m_ExecutionContext(inExecutionContext) + , m_ParentComparisonLHS(inFnd.getAllocator(), "StateSystem::m_ParentComparisonLHS") + , m_ParentComparisonRHS(inFnd.getAllocator(), "StateSystem::m_ParentComparisonRHS") + , m_AncestorsList(inFnd.getAllocator(), "StateSystem::m_AncestorsList") + , m_IDRefList(inFnd.getAllocator(), "StateSystem::m_IDRefList") + , m_HistoryMap(inFnd.getAllocator(), "StateSystem::m_HistoryMap") + , m_Configuration(inFnd.getAllocator(), "StateSystem::m_Configuration") + , m_StatesToInvoke(inFnd.getAllocator(), "StateSystem::m_InvokeList") + , m_TransitionTargetList(inFnd.getAllocator(), "StateSystem::m_TransitionTargetList") + , m_EnabledTransitions(inFnd.getAllocator(), "StateSystem::m_TransitionList") + , m_InternalQueue(ForwardingAllocator(inFnd.getAllocator(), "StateSystem::m_InternalQueue")) + , m_ExternalQueue(ForwardingAllocator(inFnd.getAllocator(), "StateSystem::m_ExternalQueue")) + , m_TransitionSet(inFnd.getAllocator(), "StateSystem::m_TransitionSet") + , m_StatesToEnter(inFnd.getAllocator(), "StateSystem::m_StatesToEnter") + , m_StatesForDefaultEntry(inFnd.getAllocator(), "StateSystem::m_StatesForDefaultEntry") + , m_StatesToExit(inFnd.getAllocator(), "StateSystem::m_StatesToExit") + , m_InterpreterDataPool( + ForwardingAllocator(inFnd.getAllocator(), "StateSystem::m_InterpretDataPool")) + , m_StateNodeNodePool( + ForwardingAllocator(inFnd.getAllocator(), "StateSystem::m_StateNodeNodePool")) + , m_Workspace(ForwardingAllocator(inFnd.getAllocator(), "StateSystem::m_Workspace")) + , m_FutureEvents(inFnd.getAllocator(), "StateSystem::m_FutureEvents") + , m_Initialized(false) + , m_Running(false) + , m_DebugInterface(*this) + , m_SignalSenders(inFnd.getAllocator(), "StateSystem::m_SignalSenders") + , m_InvalidTransitions(inFnd.getAllocator(), "StateSystem::m_InvalidTransitions") + , m_TransitionPool( + ForwardingAllocator(inFnd.getAllocator(), "StateSystem::m_TransitionPool")) + , m_TemporaryTransitions(inFnd.getAllocator(), "StateSystem::m_TemporaryTransitions") + , m_StateInitialTransitionMap(inFnd.getAllocator(), + "StateSystem::m_StateInitialTempTransitionMap") + , m_TemporaryAllocator(inFnd.getAllocator(), "StateSystem::m_TemporaryAllocator") + , m_IdSplitter(ForwardingAllocator(inFnd.getAllocator(), "StateSystem::m_IdSplitter")) + , m_TempNodeList(inFnd.getAllocator(), "StateSystem::m_TempNodeList") + { + inExecutionContext.SetInterpreter(*this); + inExecutionContext.SetMachineDebugLogger(m_DebugInterface); + inScriptContext.SetInterpreter(*this); + } + + ~StateSystem() + { + // Ensure the signallers will not attempt to communicate to this object. + for (TSignalSenderList::iterator iter = m_SignalSenders.begin(), + end = m_SignalSenders.end(); + iter != end; ++iter) + (*iter)->m_System = NULL; + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + void InitializeDataStructures() + { + m_Configuration.clear(); + m_StatesToInvoke.clear(); + m_EnabledTransitions.clear(); + m_Running = true; + } + + STransition &CreateTemporaryTransition() + { + STransition *newTrans = m_TransitionPool.construct(__FILE__, __LINE__); + m_TemporaryTransitions.push_back(newTrans); + return *newTrans; + } + + void ReleaseInitialAndTemporaryTransitions() + { + for (QT3DSU32 idx = 0, end = m_TemporaryTransitions.size(); idx < end; ++idx) + m_TransitionPool.deallocate(m_TemporaryTransitions[idx]); + m_TemporaryTransitions.clear(); + m_StateInitialTransitionMap.clear(); + m_TemporaryAllocator.reset(); + } + + void FreeHistoryData(SHistory &inHistory) + { + THistoryMap::iterator historyData = m_HistoryMap.find(&inHistory); + if (historyData != m_HistoryMap.end()) { + for (TStateNodeNodeList::iterator iter = historyData->second.begin(), + end = historyData->second.end(); + iter != end; ++iter) + m_StateNodeNodePool.deallocate(&(*iter)); + // Force clear the list. + historyData->second.m_Head = NULL; + } + } + + TStateNodeNodeList *GetHistoryData(SHistory &inHistory) + { + THistoryMap::iterator historyData = m_HistoryMap.find(&inHistory); + if (historyData != m_HistoryMap.end()) + return &historyData->second; + return NULL; + } + + TStateNodeNodeList &GetOrCreateHistoryData(SHistory &inHistory) + { + THistoryMap::iterator historyData = m_HistoryMap.find(&inHistory); + if (historyData != m_HistoryMap.end()) + return historyData->second; + return m_HistoryMap.insert(eastl::make_pair(&inHistory, TStateNodeNodeList())) + .first->second; + } + + // Setup of the state system, you can add a set of roots to the state graph. + NVConstDataRef<SStateNode *> GetConfiguration() override { return m_Configuration.m_List; } + + void GetPathToRoot(SStateNode &state, nvvector<SStateNode *> &inParents, bool inProper) + { + inParents.clear(); + SStateNode *stateIter = &state; + if (inProper) + stateIter = state.m_Parent; + for (; stateIter; stateIter = stateIter->m_Parent) + inParents.push_back(stateIter); + } + + // Given these two states, get the nearest parent they share in common. + // Return valid is the index taken from the end of the m_ParentComparison* lists. + // the index is valid in both of them. + QT3DSI32 GetSharedParentState(SStateNode &lhs, SStateNode &rhs, bool inProper) + { + GetPathToRoot(lhs, m_ParentComparisonLHS, inProper); + GetPathToRoot(rhs, m_ParentComparisonRHS, inProper); + if (m_ParentComparisonLHS.empty() || m_ParentComparisonRHS.empty()) { + QT3DS_ASSERT(false); + return -1; + } + QT3DS_ASSERT(m_ParentComparisonLHS.back() == m_ParentComparisonRHS.back()); + QT3DSI32 retval = 0; + for (nvvector<SStateNode *>::reverse_iterator lhsComp = m_ParentComparisonLHS.rbegin(), + rhsComp = m_ParentComparisonRHS.rbegin(), + lhsEnd = m_ParentComparisonLHS.rend(), + rhsEnd = m_ParentComparisonRHS.rend(); + lhsComp != lhsEnd && rhsComp != rhsEnd && *lhsComp == *rhsComp; + ++lhsComp, ++rhsComp, ++retval) { + } + // Walk the path to the root backwards and note where it differs. + return retval - 1; + } + + void CreateStateNodeInterpreterData(SStateNode &inNode, QT3DSI32 &inIdx) + { + inNode.m_InterpreterData = m_InterpreterDataPool.construct(inIdx, __FILE__, __LINE__); + ++inIdx; + if (inNode.m_Type == StateNodeTypes::SCXML) { + SSCXML *item = inNode.CastTo<SSCXML>(); + if (item->m_Initial) + CreateStateNodeInterpreterData(*item->m_Initial, inIdx); + } else if (inNode.m_Type == StateNodeTypes::State) { + SState *item = inNode.CastTo<SState>(); + if (item->m_Initial) + CreateStateNodeInterpreterData(*item->m_Initial, inIdx); + } + if (StateNodeTypes::CanHaveChildren(inNode.m_Type)) { + SStateParallelBase &theBase = static_cast<SStateParallelBase &>(inNode); + for (TStateNodeList::iterator iter = theBase.m_Children.begin(), + end = theBase.m_Children.end(); + iter != end; ++iter) + CreateStateNodeInterpreterData(*iter, inIdx); + } + } + + // Creates the interpreter data for the entire graph. + void CreateStateNodeInterpreterData() + { + if (m_Context->GetRoot() == NULL) { + QT3DS_ASSERT(false); + return; + } + QT3DSI32 nodeIdx = 1; + CreateStateNodeInterpreterData(*m_Context->GetRoot(), nodeIdx); + } + + SStateNodeInterpreterData &GetOrCreateInterpreterData(SStateNode &inNode) + { + if (!inNode.m_InterpreterData) + CreateStateNodeInterpreterData(); + + return *static_cast<SStateNodeInterpreterData *>(inNode.m_InterpreterData); + } + struct SStateNodeSorter + { + StateSystem &m_System; + SStateNodeSorter(StateSystem &inS) + : m_System(inS) + { + } + bool operator()(SStateNode *lhs, SStateNode *rhs) const + { + return m_System.GetOrCreateInterpreterData(*lhs).m_DocumentOrder + < m_System.GetOrCreateInterpreterData(*rhs).m_DocumentOrder; + } + }; + + void SortByDocumentOrder(nvvector<SStateNode *> &inList) + { + eastl::sort(inList.begin(), inList.end(), SStateNodeSorter(*this)); + } + + void SortConfiguration() { SortByDocumentOrder(m_Configuration.m_List); } + + void RunDataModel(SDataModel &inDM) + { + QT3DS_ASSERT(inDM.m_Source == NULL); + QT3DS_ASSERT(inDM.m_Expression == NULL); + for (TDataList::iterator iter = inDM.m_Data.begin(), end = inDM.m_Data.end(); iter != end; + ++iter) { + if (iter->m_Source == NULL) + m_ScriptContext->Assign(iter->m_Id.c_str(), iter->m_Expression); + else { + QT3DS_ASSERT(false); + } + } + } + + void RecursiveInitializeDataModel(SStateNode &inState) + { + SDataModel *theDataModel = inState.GetDataModel(); + if (theDataModel) + RunDataModel(*theDataModel); + + TStateNodeList *children = inState.GetChildren(); + if (children) { + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) + RecursiveInitializeDataModel(*iter); + } + } + + // Called during initialization for early binding of the datamodel + void RecursiveInitializeDataModel() + { + QT3DS_ASSERT(m_Context->GetRoot()); + QT3DS_ASSERT(m_Context->GetRoot()->m_Flags.IsLateBinding() == false); + RecursiveInitializeDataModel(*m_Context->GetRoot()); + } + + void MarkTransitionAsInvalid(STransition &inTransition, SStateNode &inParent, int inIndex) + { + if (inIndex >= 0) { + qCCritical(INVALID_OPERATION, "Detected invalid transition %s:%d", + inParent.m_Id.c_str(), inIndex); + } else { + const char *transitionType = "initial"; + if (inIndex == -1) + transitionType = "history"; + qCCritical(INVALID_OPERATION, "Detected invalid %s transition on node %s:%d", + transitionType, inParent.m_Id.c_str(), inIndex); + } + m_InvalidTransitions.insert(&inTransition); + } + + bool IsTransitionValid(STransition &inTransition) + { + return m_InvalidTransitions.find(&inTransition) == m_InvalidTransitions.end(); + } + + bool IsTransitionValid(STransition *inTransition) + { + if (inTransition) + return IsTransitionValid(*inTransition); + + // NULL transitions are of course, invalid. + return false; + } + + bool ValidateTransitionTargetList(STransition &inTransition, SStateNode &inParent, int inIndex) + { + for (QT3DSU32 idx = 0, end = inTransition.m_Target.size(); idx < end; ++idx) { + for (QT3DSU32 outerIdx = idx + 1; outerIdx < end; ++outerIdx) { + // We handle this case dynamically during transition activation + if (IsDescendant(*inTransition.m_Target[idx], *inTransition.m_Target[outerIdx]) + || IsDescendant(*inTransition.m_Target[outerIdx], *inTransition.m_Target[idx])) + continue; + + // If they aren't directly related they must be related indirectly via a parallel. + SStateNode *theLCA = + FindLCA(*inTransition.m_Target[idx], *inTransition.m_Target[outerIdx]); + if (theLCA && theLCA->m_Type != StateNodeTypes::Parallel) { + MarkTransitionAsInvalid(inTransition, inParent, inIndex); + return false; + } + } + } + return true; + } + + void ValidateInitialOrHistoryTransition(STransition *inTransition, SStateNode &inParent, + bool inIsInitial = false) + { + if (inTransition == NULL) + return; + int transIndex = inIsInitial ? -2 : -1; + if (inTransition->m_Target.size() == 0) { + MarkTransitionAsInvalid(*inTransition, inParent, transIndex); + } else { + ValidateTransitionTargetList(*inTransition, inParent, transIndex); + } + } + + void ValidateGeneralTransition(STransition &inTransition, SStateNode &inParent, int inIndex) + { + // Three distinct ways a transition could be invalid. + // 1. targetless, eventless and conditionless + if (inTransition.m_Target.size() == 0 && inTransition.m_Event.IsValid() == false + && isTrivial(inTransition.m_Condition)) + MarkTransitionAsInvalid(inTransition, inParent, inIndex); + else { + // Else eventless ,targetless, and points back to source state + if (inTransition.m_Target.size() == 1) { + if (inTransition.m_Target[0] == inTransition.m_Parent + && inTransition.m_Event.IsValid() == false + && isTrivial(inTransition.m_Condition)) { + MarkTransitionAsInvalid(inTransition, inParent, inIndex); + } + } else { + ValidateTransitionTargetList(inTransition, inParent, inIndex); + } + } + } + + void RecursiveValidateTransitions(SStateNode &inNode, SStateNode *inParent, int inIndex) + { + switch (inNode.m_Type) { + case StateNodeTypes::SCXML: + ValidateInitialOrHistoryTransition(inNode.GetInitialTransition(), inNode); + break; + case StateNodeTypes::State: + ValidateInitialOrHistoryTransition(inNode.GetInitialTransition(), inNode); + break; + case StateNodeTypes::Parallel: + ValidateInitialOrHistoryTransition(inNode.GetInitialTransition(), inNode); + break; + case StateNodeTypes::History: + ValidateInitialOrHistoryTransition(static_cast<SHistory &>(inNode).m_Transition, inNode, + false); + break; + case StateNodeTypes::Transition: + if (inParent) + ValidateGeneralTransition(static_cast<STransition &>(inNode), *inParent, inIndex); + break; + default: // don't care + break; + } + RecursiveValidateTransitions(inNode, inNode.GetChildren()); + } + + void RecursiveValidateTransitions(SStateNode &inParent, TStateNodeList *inNodes) + { + if (inNodes) { + int idx = 0; + for (TStateNodeList::iterator iter = inNodes->begin(), end = inNodes->end(); + iter != end; ++iter, ++idx) + RecursiveValidateTransitions(*iter, &inParent, idx); + } + } + + // The state machine is pretty robust but there are is one category of input that can + // put the machine in a bad state. Transitions with either multiple targets or no target + // or a single target that points back to its owner may be bad depending on different criteria. + // For multiple targets, we invalidate a transition that puts us into a bad state. + // For no targets, in invalidate a transition that would cause the interpreter to loop + // infinitely + // For a single target, we invalidate a transition if it points back to its source but it has no + // condition + // or event. We don't attempt any more sophisticated analysis at this point although one could + // conceive of + // an analysis that could find loops depending on how long you wanted it to run. + void RecursiveValidateTransitions() + { + QT3DS_ASSERT(m_Context->GetRoot()); + RecursiveValidateTransitions(*m_Context->GetRoot(), NULL, 0); + } + + NVConstDataRef<SStateNode *> GetRefList(NVConstDataRef<SStateNode *> inIds) { return inIds; } + + // unsorted, may contain duplicates. + NVDataRef<SStateNode *> GetRefList(NVConstDataRef<CRegisteredString> inIds) + { + m_IDRefList.clear(); + for (QT3DSU32 idx = 0, end = inIds.size(); idx < end; ++idx) { + SStateNode *theNode = m_Context->FindStateNode(inIds[idx]); + if (theNode != NULL) { + m_IDRefList.push_back(theNode); + } + } + return m_IDRefList; + } + + NVConstDataRef<SStateNode *> GetTargetStates(STransition *inTransition) + { + if (inTransition) + return inTransition->m_Target; + return NVDataRef<SStateNode *>(); + } + + static bool IsCompoundState(const SStateNode *inNode) + { + if (inNode == NULL) + return false; + return inNode->IsCompound(); + } + + static bool IsDescendant(SStateNode &inParent, SStateNode &inChild) + { + if (&inChild == &inParent) + return false; + + for (SStateNode *theNode = &inChild; theNode; theNode = theNode->m_Parent) + if (theNode == &inParent) + return true; + return false; + } + + static bool AllAreDescendants(SStateNode &inParent, NVConstDataRef<SStateNode *> inList) + { + for (QT3DSU32 idx = 0, end = inList.size(); idx < end; ++idx) + if (IsDescendant(inParent, *inList[idx]) == false) + return false; + return true; + } + struct SRefListPlusOne + { + NVConstDataRef<SStateNode *> m_List; + SStateNode *m_Extra; + + SRefListPlusOne(NVConstDataRef<SStateNode *> l, SStateNode *e) + : m_List(l) + , m_Extra(e) + { + } + + QT3DSU32 size() { return m_List.size() + (m_Extra ? 1 : 0); } + + SStateNode *operator[](int idx) + { + if (idx < (int)m_List.size()) + return m_List[idx]; + if (idx == (int)m_List.size()) + return m_Extra; + QT3DS_ASSERT(false); + return NULL; + } + }; + + SStateNode *FindLCA(SStateNode &lhs, SStateNode &rhs) + { + QT3DSU32 sharedIdx = GetSharedParentState(lhs, rhs, false); + return *(m_ParentComparisonLHS.rbegin() + sharedIdx); + } + + SStateNode *FindLCCA(NVConstDataRef<SStateNode *> inList, SStateNode *inExtra = NULL) + { + SRefListPlusOne theList(inList, inExtra); + if (theList.size() == 0) + return NULL; + if (theList.size() == 1) { + for (SStateNode *item = theList[0]; item; item = item->m_Parent) { + if (item->IsCompound()) + return item; + } + } + SStateNode *lhs = theList[0]; + for (QT3DSU32 idx = 1, end = theList.size(); idx < end; ++idx) { + SStateNode *rhs = theList[idx]; + QT3DSU32 sharedIdx = GetSharedParentState(*lhs, *rhs, false); + lhs = *(m_ParentComparisonLHS.rbegin() + sharedIdx); + } + for (SStateNode *item = lhs; item; item = item->m_Parent) { + if (item->IsCompound()) + return item; + } + QT3DS_ASSERT(false); + return m_Context->GetRoot(); + } + NVDataRef<SStateNode *> GetProperAncestors(SStateNode &inChild, SStateNode *inStop) + { + m_AncestorsList.clear(); + for (SStateNode *parent = inChild.m_Parent; parent && parent != inStop; + parent = parent->m_Parent) + m_AncestorsList.push_back(parent); + return m_AncestorsList; + } + + bool AddStateToEnterToSet(SStateNode &inNode) + { + if (m_Configuration.contains(&inNode) == false) { + m_StatesToEnter.insert(&inNode); + return true; + } + return false; + } + + void AddStatesToEnter(SHistory &inState) + { + TStateNodeNodeList *history = GetHistoryData(inState); + if (history) { + for (TStateNodeNodeList::iterator iter = history->begin(), end = history->end(); + iter != end; ++iter) { + AddStatesToEnter(*iter->m_Node); + NVDataRef<SStateNode *> ancestors = + GetProperAncestors(*iter->m_Node, inState.m_Parent); + for (QT3DSU32 ancIdx = 0, ancEnd = ancestors.size(); ancIdx < ancEnd; ++ancIdx) + AddStateToEnterToSet(*ancestors[ancIdx]); + } + } else { + if (IsTransitionValid(inState.m_Transition)) { + NVConstDataRef<SStateNode *> theList = GetRefList(inState.m_Transition->m_Target); + for (QT3DSU32 idx = 0, end = theList.size(); idx < end; ++idx) { + AddStatesToEnter(*theList[idx]); + NVDataRef<SStateNode *> ancestors = + GetProperAncestors(*theList[idx], inState.m_Parent); + for (QT3DSU32 ancIdx = 0, ancEnd = ancestors.size(); ancIdx < ancEnd; ++ancIdx) + AddStateToEnterToSet(*ancestors[ancIdx]); + } + } else { + qCCritical(INVALID_OPERATION, + "History node %s with no history, no transition, or invalid transition visited", + inState.m_Id.c_str()); + SStateNode *theParent = inState.m_Parent; + if (theParent != NULL) { + if (theParent->m_Type != StateNodeTypes::Parallel) { + NVConstDataRef<SStateNode *> theDefaultInitial( + GetDefaultInitialState(*theParent)); + + QT3DS_ASSERT(theDefaultInitial.size() != 0); + for (QT3DSU32 idx = 0, end = theDefaultInitial.size(); idx < end; ++idx) + AddStatesToEnter(*theDefaultInitial[idx]); + + EnterAncestors(theDefaultInitial, theParent); + } else { + SParallel *pstate = theParent->CastTo<SParallel>(); + for (TStateNodeList::iterator iter = pstate->m_Children.begin(), + end = pstate->m_Children.end(); + iter != end; ++iter) { + if (StateNodeTypes::IsStateType(iter->m_Type)) + AddStatesToEnter(*iter); + } + } + } else { + QT3DS_ASSERT(false); // invalid configuration + } + } + } + } + NVConstDataRef<SStateNode *> GetDefaultInitialState(SStateNode &inState) + { + TStateNodeList *theChildren = inState.GetChildren(); + NVConstDataRef<SStateNode *> retval; + if (theChildren) { + for (TStateNodeList::iterator iter = theChildren->begin(), end = theChildren->end(); + iter != end && retval.size() == 0; ++iter) { + if (iter->m_Type == StateNodeTypes::State + || iter->m_Type == StateNodeTypes::Parallel + || iter->m_Type == StateNodeTypes::Final) { + SStateNode **newData = + reinterpret_cast<SStateNode **>(m_TemporaryAllocator.allocate( + sizeof(SStateNode *), "TempNode", __FILE__, __LINE__)); + newData[0] = &(*iter); + retval = toDataRef(newData, 1); + } + } + } + return retval; + } + + STransition *GetStateInitialTransition(SStateNode &inNode) + { + // Initialexpr, if it exists, takes precedence over the initial transition. + // they should not both exist but coding defensively, we have to take this into account. + eastl::pair<nvhash_map<SStateNode *, STransition *>::iterator, bool> inserter = + m_StateInitialTransitionMap.insert(eastl::make_pair(&inNode, (STransition *)NULL)); + if (inserter.second) { + const char8_t *initialExpr = inNode.GetInitialExpression(); + if (!isTrivial(initialExpr)) { + STransition *newTransition = &CreateTemporaryTransition(); + newTransition->m_Parent = &inNode; + inserter.first->second = newTransition; + SScriptExecutionResult exprResultData = + m_ScriptContext->ExecuteExpressionToString(initialExpr); + if (exprResultData.Valid()) { + const char8_t *exprResult(exprResultData.Result()); + // split this string into parts and extract ids. + m_IdSplitter.assign(nonNull(exprResult)); + const char8_t *whitespaceData = " \n\t\r"; + size_t charPos = m_IdSplitter.find_first_not_of(whitespaceData); + if (charPos == eastl::string::npos) { + m_IdSplitter.clear(); + m_Workspace.clear(); + } + if (charPos != 0) + m_IdSplitter.erase(m_IdSplitter.begin(), m_IdSplitter.begin() + charPos); + // Loop runs under assumption that idSplitter is empty or position 0 holds start + // of next id + while (m_IdSplitter.size()) { + // Trim to first character + eastl::string::size_type spacePos = + m_IdSplitter.find_first_of(whitespaceData); + + if (spacePos != eastl::string::npos) { + charPos = m_IdSplitter.find_first_not_of(whitespaceData, spacePos); + m_Workspace = m_IdSplitter.c_str(); + m_Workspace.resize(spacePos); + if (charPos != eastl::string::npos) + m_IdSplitter.erase(m_IdSplitter.begin(), + m_IdSplitter.begin() + charPos); + else + m_IdSplitter.clear(); + } else { + m_Workspace = m_IdSplitter; + m_IdSplitter.clear(); + } + + if (m_Workspace.empty() == false) { + CRegisteredString stateId = + m_StringTable->RegisterStr(m_Workspace.c_str()); + qt3ds::state::SStateNode *transitionNode = + m_Context->FindStateNode(stateId); + if (!transitionNode) { + m_TempNodeList.clear(); + m_IdSplitter.clear(); + qCCritical(INVALID_OPERATION, + "initialexpr=\"%s\" evaluated to \"%s\", but " + "this does not match the states in this " + "document. Using the default initial state " + "instead.", + nonNull(initialExpr), nonNull(exprResult)); + + eastl::string errorBuf; + IScriptEvent *newEvent = m_ScriptContext->CreateScriptEvent( + m_StringTable->RegisterStr("error.execution.initialexpr")); + newEvent->SetParamStr(m_StringTable->RegisterStr("expr"), + nonNull(initialExpr)); + errorBuf.assign(nonNull(exprResult)); + errorBuf.append(" does not match the states in this document."); + newEvent->SetParamStr(m_StringTable->RegisterStr("error"), + errorBuf.c_str()); + QueueEvent(*newEvent, false); + } else + m_TempNodeList.push_back(transitionNode); + } + } + if (m_TempNodeList.empty() == false) { + QT3DSU32 allocSize = sizeof(SStateNode *) * m_TempNodeList.size(); + SStateNode **nodeData = + reinterpret_cast<SStateNode **>(m_TemporaryAllocator.allocate( + allocSize, "TempNodes", __FILE__, __LINE__)); + memCopy(nodeData, m_TempNodeList.data(), allocSize); + newTransition->m_Target = toDataRef(nodeData, m_TempNodeList.size()); + m_TempNodeList.clear(); + } + if (newTransition->m_Target.size() != 0) { + bool isTransValid = ValidateTransitionTargetList(*newTransition, inNode, 0); + if (!isTransValid) { + m_InvalidTransitions.erase(newTransition); + // Reset the transition so that we get just the default initial state + // below + newTransition->m_Target = NVConstDataRef<SStateNode *>(); + // Create appropriate messages and events. + qCCritical(INVALID_OPERATION, + "initialexpr=\"%s\" evaluated to \"%s\", but this " + "results in an invalid transition. Using the " + "default initial state instead.", + nonNull(initialExpr), nonNull(exprResult)); + eastl::string errorBuf; + IScriptEvent *newEvent = m_ScriptContext->CreateScriptEvent( + m_StringTable->RegisterStr("error.execution.initialexpr")); + newEvent->SetParamStr(m_StringTable->RegisterStr("expr"), + nonNull(initialExpr)); + errorBuf.assign(nonNull(exprResult)); + errorBuf.append(" results in invalid transition."); + newEvent->SetParamStr(m_StringTable->RegisterStr("error"), + errorBuf.c_str()); + QueueEvent(*newEvent, false); + } + } + } // if script executed successfully + else { + const char8_t *runtimeError = exprResultData.Error(); + IScriptEvent *newEvent = m_ScriptContext->CreateScriptEvent( + m_StringTable->RegisterStr("error.execution.initialexpr")); + newEvent->SetParamStr(m_StringTable->RegisterStr("expr"), nonNull(initialExpr)); + newEvent->SetParamStr(m_StringTable->RegisterStr("error"), + nonNull(runtimeError)); + QueueEvent(*newEvent, false); + } + if (newTransition->m_Target.size() == 0) { + newTransition->m_Target = GetDefaultInitialState(inNode); + } + } + if (inserter.first->second == NULL) { + // Assume already validated + inserter.first->second = inNode.GetInitialTransition(); + } + } + return inserter.first->second; + } + + void AddStatesToEnter(SStateNode &inState) + { + if (inState.m_Type == StateNodeTypes::History) + AddStatesToEnter(*inState.CastTo<SHistory>()); + else { + AddStateToEnterToSet(inState); + if (inState.IsCompound()) { + m_StatesForDefaultEntry.insert(&inState); + NVConstDataRef<SStateNode *> targets; + STransition *initialTrans = GetStateInitialTransition(inState); + if (IsTransitionValid(initialTrans)) + targets = GetTargetStates(initialTrans); + + if (targets.size() == 0) + targets = GetDefaultInitialState(inState); + + QT3DS_ASSERT(targets.size() != 0); + for (QT3DSU32 idx = 0, end = targets.size(); idx < end; ++idx) + AddStatesToEnter(*targets[idx]); + + EnterAncestors(targets, &inState); + } else if (inState.m_Type == StateNodeTypes::Parallel) { + SParallel *pstate = inState.CastTo<SParallel>(); + for (TStateNodeList::iterator iter = pstate->m_Children.begin(), + end = pstate->m_Children.end(); + iter != end; ++iter) { + if (StateNodeTypes::IsStateType(iter->m_Type)) + AddStatesToEnter(*iter); + } + } + } + } + bool AllChildrenInFinalStates(SStateNode &inNode) + { + TStateNodeList *children = inNode.GetChildren(); + if (children) { + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) + if (!IsInFinalState(*iter)) + return false; + return true; + } + return true; + } + + void EraseState(nvvector<SStateNode *> &inList, SStateNode *inItem) + { + nvvector<SStateNode *>::iterator iter = eastl::find(inList.begin(), inList.end(), inItem); + if (iter != inList.end()) + inList.erase(iter); + } + + void ExitStates(NVDataRef<STransition *> inTransitions) + { + m_StatesToExit.clear(); + for (QT3DSU32 idx = 0, end = inTransitions.size(); idx < end; ++idx) { + STransition &transition(*inTransitions[idx]); + if (transition.GetSource() == NULL) { + QT3DS_ASSERT(false); + continue; + } + NVConstDataRef<SStateNode *> theList = GetRefList(transition.m_Target); + SStateNode *ancestor = GetTransitionSubgraphRoot(transition, theList); + if (theList.size() && transition.GetSource()) { + for (QT3DSU32 idx = 0, end = m_Configuration.size(); idx < end; ++idx) + if (IsDescendant(*ancestor, *m_Configuration[idx])) + m_StatesToExit.insert(m_Configuration[idx]); + } + } + + /* + for s in m_StatesToExit: + statesToInvoke.delete(s) + */ + + SortByDocumentOrder(m_StatesToExit.m_List); + DEBUGGER_CALL1(SetExitSet, m_StatesToExit); + // Run through the list in reverse order. + for (nvvector<SStateNode *>::reverse_iterator iter = m_StatesToExit.m_List.rbegin(), + end = m_StatesToExit.m_List.rend(); + iter != end; ++iter) { +#ifdef QT3DS_LOG_ENTER_EXIT + qCInfo(TRACE_INFO, "Exiting state: %s", (*iter)->m_Id.c_str()); +#endif + TStateNodeList *children = (*iter)->GetChildren(); + if (children) { + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) { + if (iter->m_Type == StateNodeTypes::History) { + SHistory &theHistory = *iter->CastTo<SHistory>(); + FreeHistoryData(theHistory); + TStateNodeNodeList &theHistoryData = GetOrCreateHistoryData(theHistory); + + if (theHistory.m_Parent == NULL) { + QT3DS_ASSERT(false); + continue; + } + + // If deep, then record the leaves + if (theHistory.m_Flags.IsDeep()) { + for (TStateNodeSet::iterator configIter = m_Configuration.begin(), + configEnd = m_Configuration.end(); + configIter != configEnd; ++configIter) { + if ((*configIter)->IsAtomic() + && IsDescendant(*theHistory.m_Parent, **configIter)) + theHistoryData.push_back(*m_StateNodeNodePool.construct( + *configIter, __FILE__, __LINE__)); + } + } + // Else record what may be branches. + else { + for (TStateNodeSet::iterator configIter = m_Configuration.begin(), + configEnd = m_Configuration.end(); + configIter != configEnd; ++configIter) { + if ((*configIter)->m_Parent == theHistory.m_Parent) + theHistoryData.push_back(*m_StateNodeNodePool.construct( + *configIter, __FILE__, __LINE__)); + } + } + } + } + } + } + + for (nvvector<SStateNode *>::reverse_iterator iter = m_StatesToExit.m_List.rbegin(), + end = m_StatesToExit.m_List.rend(); + iter != end; ++iter) { + SStateNode *theState = *iter; + TOnExitList *theExitList = theState->GetOnExitList(); + if (theExitList) + m_ExecutionContext->Execute(*theState, *theExitList); + for (QT3DSU32 idx = 0, end = m_SignalSenders.size(); idx < end; ++idx) + m_SignalSenders[idx]->m_Handler.OnInterpreterEvent(InterpreterEventTypes::StateExit, + theState->m_Id); + EraseState(m_StatesToInvoke, theState); + m_Configuration.erase(theState); + } + } + + void EnterAncestor(SStateNode &inNode) + { + m_StatesToEnter.insert(&inNode); + if (inNode.m_Type == StateNodeTypes::Parallel) { + SParallel *anc = inNode.CastTo<SParallel>(); + for (TStateNodeList::iterator childIter = anc->m_Children.begin(), + endChild = anc->m_Children.end(); + childIter != endChild; ++childIter) { + if (!StateNodeTypes::IsStateType(childIter->m_Type)) + continue; + bool hasDescendent = false; + for (TStateNodeSet::iterator existingIter = m_StatesToEnter.begin(), + existingEnd = m_StatesToEnter.end(); + existingIter != existingEnd && hasDescendent == false; ++existingIter) { + hasDescendent = IsDescendant(*childIter, *(*existingIter)); + } + if (hasDescendent == false) + AddStatesToEnter(*childIter); + } + } + } + + void EnterAncestors(NVConstDataRef<SStateNode *> inList, SStateNode *ancestor) + { + for (QT3DSU32 idx = 0, end = inList.size(); idx < end; ++idx) { + NVDataRef<SStateNode *> theAncestors = GetProperAncestors(*inList[idx], ancestor); + for (QT3DSU32 ancIdx = 0, ancEnd = theAncestors.size(); ancIdx < ancEnd; ++ancIdx) { + EnterAncestor(*theAncestors[ancIdx]); + // Break earlier if we have gone back and included the ancestor. + if (theAncestors[ancIdx] == ancestor) + break; + } + } + } + + void EnterStates(NVDataRef<STransition *> inTransitions) + { + m_StatesToEnter.clear(); + m_StatesForDefaultEntry.clear(); + + for (QT3DSU32 idx = 0, end = inTransitions.size(); idx < end; ++idx) { + STransition &transition = *inTransitions[idx]; + NVConstDataRef<SStateNode *> theList = GetRefList(transition.m_Target); + if (transition.GetSource() == NULL) { + QT3DS_ASSERT(false); + continue; + } + + // Multi-target transitions present their own set of problems. One is that a perfectly + // valid + // multi-target transition may + if (theList.size() > 1) { + m_TransitionTargetList.clear(); + // We have to ensure that if two targets are directly related, we take the most + // derived one. + for (QT3DSU32 targetIdx = 0, targetEnd = theList.size(); targetIdx < targetEnd; + ++targetIdx) { + SStateNode *nextTarget = theList[targetIdx]; + for (QT3DSU32 takenTargetListIdx = 0, + takenTargetListEnd = m_TransitionTargetList.size(); + takenTargetListIdx < takenTargetListEnd && nextTarget; + ++takenTargetListIdx) { + SStateNode *previousTarget = m_TransitionTargetList[takenTargetListIdx]; + // If the previous target is more descendant than the original target + if (IsDescendant(*nextTarget, *previousTarget)) { + // Then we don't need to consider next target at all and we can + // continue. + nextTarget = NULL; + } else if (IsDescendant(*previousTarget, *nextTarget)) { + // If next target derives from previous target, then we remove previous + // target from + // the list. + m_TransitionTargetList.erase(m_TransitionTargetList.begin() + + takenTargetListIdx); + --takenTargetListIdx; + takenTargetListEnd = m_TransitionTargetList.size(); + } + // Else we don't care, we will add next target to the list. + } + if (nextTarget != NULL) + m_TransitionTargetList.push_back(nextTarget); + } + theList = m_TransitionTargetList; + } + + for (QT3DSU32 idx = 0, end = theList.size(); idx < end; ++idx) + AddStatesToEnter(*theList[idx]); + } + + for (QT3DSU32 idx = 0, end = inTransitions.size(); idx < end; ++idx) { + STransition &transition = *inTransitions[idx]; + NVConstDataRef<SStateNode *> theList = GetRefList(transition.m_Target); + + if (transition.GetSource() == NULL) { + QT3DS_ASSERT(false); + continue; + } + + SStateNode *ancestor = GetTransitionSubgraphRoot(transition, theList); + EnterAncestors(theList, ancestor); + } + EnterStatesSecondHalf(); + } + + void EnterStatesSecondHalf() + { + nvvector<SStateNode *> &theEnterList(m_StatesToEnter.m_List); + SortByDocumentOrder(theEnterList); + + DEBUGGER_CALL1(SetEnterSet, m_StatesToEnter); + for (QT3DSU32 idx = 0, end = theEnterList.size(); idx < end; ++idx) { + SStateNode *theEnterState(theEnterList[idx]); +#ifdef QT3DS_LOG_ENTER_EXIT + qCInfo(TRACE_INFO, "Entering state: %s", theEnterState->m_Id.c_str()); +#endif + m_Configuration.insert(theEnterState); + m_StatesToInvoke.push_back(theEnterState); + SStateNodeInterpreterData &theData = GetOrCreateInterpreterData(*theEnterState); + if (theData.m_Entered == false) { + theData.m_Entered = true; + if (m_Context->GetRoot()->m_Flags.IsLateBinding()) { + SDataModel *theDataModel = theEnterState->GetDataModel(); + if (theDataModel) + RunDataModel(*theDataModel); + } + } + + TOnEntryList *theList = theEnterState->GetOnEntryList(); + if (theList) + m_ExecutionContext->Execute(*theEnterState, *theList); + for (QT3DSU32 idx = 0, end = m_SignalSenders.size(); idx < end; ++idx) + m_SignalSenders[idx]->m_Handler.OnInterpreterEvent( + InterpreterEventTypes::StateEnter, theEnterState->m_Id); + + if (m_StatesForDefaultEntry.contains(theEnterState)) { + SState *theState = theEnterState->CastTo<SState>(); + if (theState && IsTransitionValid(theState->m_Initial)) + m_ExecutionContext->Execute(*theState->m_Initial); + } + if (theEnterState->m_Type == StateNodeTypes::Final) { + SStateNode *parent = theEnterState->m_Parent; + SStateNode *grandparent = parent->m_Parent; + if (parent && grandparent) { + m_Workspace.assign("done.state."); + m_Workspace.append(parent->m_Id); + + // TODO - donedata + QueueEvent(m_Workspace.c_str(), false); + + if (grandparent && grandparent->m_Type == StateNodeTypes::Parallel + && AllChildrenInFinalStates(*grandparent)) { + m_Workspace.assign("done.state."); + m_Workspace.append(grandparent->m_Id); + QueueEvent(m_Workspace.c_str(), false); + } + } + } + } + + if (IsInFinalState(m_Context->GetRoot())) + m_Running = false; + SortConfiguration(); + } + + bool InConfiguration(SStateNode &inState) + { + return eastl::find(m_Configuration.begin(), m_Configuration.end(), &inState) + != m_Configuration.end(); + } + + bool IsInFinalState(SStateNode &inState) + { + if (inState.m_Type == StateNodeTypes::State && inState.IsCompound()) { + SState *theState = inState.CastTo<SState>(); + for (TStateNodeList::iterator childIter = theState->m_Children.begin(), + endIter = theState->m_Children.end(); + childIter != endIter; ++childIter) { + if (IsInFinalState(*childIter)) + return true; + } + } else if (inState.m_Type == StateNodeTypes::SCXML) { + SSCXML *theState = inState.CastTo<SSCXML>(); + for (TStateNodeList::iterator childIter = theState->m_Children.begin(), + endIter = theState->m_Children.end(); + childIter != endIter; ++childIter) { + if (childIter->m_Type == StateNodeTypes::Final && InConfiguration(*childIter)) + return true; + } + } else if (inState.m_Type == StateNodeTypes::Parallel) { + SParallel *parallel = inState.CastTo<SParallel>(); + for (TStateNodeList::iterator childIter = parallel->m_Children.begin(), + endIter = parallel->m_Children.end(); + childIter != endIter; ++childIter) { + if (!IsInFinalState(*childIter)) + return false; + } + return true; + } else if (inState.m_Type == StateNodeTypes::Final && InConfiguration(inState)) + return true; + + return false; + } + + bool IsInFinalState(SStateNode *inState) + { + if (inState) + return IsInFinalState(*inState); + return false; + } + + SStateNode *GetTransitionSubgraphRoot(STransition &inTransition, + NVConstDataRef<SStateNode *> inTargetList) + { + SStateNodeInterpreterData &data = GetOrCreateInterpreterData(inTransition); + if (data.m_TransitionSubgraphRoot != NULL) + return data.m_TransitionSubgraphRoot; + if (inTransition.GetSource() == NULL) { + QT3DS_ASSERT(false); + return NULL; + } + SStateNode &source = *inTransition.GetSource(); + if (inTransition.m_Target.size() == 0) + data.m_TransitionSubgraphRoot = &source; + else { + if (inTransition.m_Flags.IsInternal() && source.IsCompound() + && AllAreDescendants(source, inTargetList)) + data.m_TransitionSubgraphRoot = &source; + else + data.m_TransitionSubgraphRoot = FindLCCA(inTargetList, &source); + } + + return data.m_TransitionSubgraphRoot; + } + SStateNode *GetTransitionSubgraphRoot(STransition &inTransition) + { + SStateNodeInterpreterData &data = GetOrCreateInterpreterData(inTransition); + if (data.m_TransitionSubgraphRoot != NULL) + return data.m_TransitionSubgraphRoot; + + return GetTransitionSubgraphRoot(inTransition, GetRefList(inTransition.m_Target)); + } + enum PreemptRule { AddNew, KeepExisting, ReplaceExisting }; + + PreemptRule IsPreempted(STransition &existing, STransition &nextTransition) + { + // Targetless transitions can get preempted and can preempt + SStateNode *existingRoot = GetTransitionSubgraphRoot(existing); + SStateNode *nextRoot = GetTransitionSubgraphRoot(nextTransition); + /* + http://www.w3.org/TR/scxml/#SelectingTransitions + + A transition T is optimally enabled by event E in atomic state S if + a) T is enabled by E in S and + b) no transition that precedes T in document order in T's source state is enabled by E in S + and + c) no transition is enabled by E in S in any descendant of T's source state. + */ + + // static bool IsDescendant( SStateNode& inParent, SStateNode& inChild ) + + if (IsDescendant(*nextRoot, *existingRoot)) + return KeepExisting; + if (IsDescendant(*existingRoot, *nextRoot)) + return ReplaceExisting; + + // Else these transactions are completely unrelated + return AddNew; + } + + void FilterPreempted(TTransitionSet &inTransitionSet, TTransitionList &outTransitions) + { + outTransitions.clear(); + for (TTransitionSet::iterator iter = inTransitionSet.begin(), end = inTransitionSet.end(); + iter != end; ++iter) { + PreemptRule preempted = AddNew; + QT3DSU32 idx, endIdx; + for (idx = 0, endIdx = outTransitions.size(); idx < endIdx && preempted == AddNew; + ++idx) + preempted = IsPreempted(*outTransitions[idx], **iter); + + switch (preempted) { + case KeepExisting: // Ignore the result. + break; + case ReplaceExisting: + // The iteration statement is evaluated before the exit test. + outTransitions[idx - 1] = *iter; + break; + case AddNew: + outTransitions.push_back(*iter); + break; + } + } + } + + bool SelectEventlessTransitions(SStateNode &inNode, TTransitionSet &inSet) + { + TStateNodeList *theChildren = inNode.GetChildren(); + if (theChildren == NULL) { + return false; + } + for (TStateNodeList::iterator iter = theChildren->begin(), end = theChildren->end(); + iter != end; ++iter) { + if (iter->m_Type == StateNodeTypes::Transition) { + STransition &trans = *iter->CastTo<STransition>(); + if (IsTransitionValid(&trans)) { + if (trans.m_Event.IsValid() == false) { + if (!isTrivial(trans.m_Condition)) { + Option<bool> condEval = + m_ScriptContext->ExecuteCondition(trans.m_Condition); + if (condEval.hasValue()) { + if (*condEval) { + inSet.insert(&trans); + return true; + } + } else { + QueueEvent("error.execution", false); + return false; + } + } + // Extension for running scxml documents with transitions with nothing but + // content + else { + inSet.insert(&trans); + return true; + } + } + } + } + } + if (inNode.m_Parent) + SelectEventlessTransitions(*inNode.m_Parent, inSet); + return false; + } + + // Precondition - m_Configuration is in document order. + // Postcondition - m_EnabledTransitions contains only the transitions selected + void SelectEventlessTransitions() + { + m_TransitionSet.clear(); + static QT3DSU32 callCount = 0; + ++callCount; + for (QT3DSU32 idx = 0, end = m_Configuration.size(); idx < end; ++idx) { + if (idx) { + QT3DS_ASSERT(GetOrCreateInterpreterData(*m_Configuration[idx]).m_DocumentOrder + > GetOrCreateInterpreterData(*m_Configuration[idx - 1]).m_DocumentOrder); + } + if (m_Configuration[idx]->IsAtomic() == false) + continue; + SelectEventlessTransitions(*m_Configuration[idx], m_TransitionSet); + } + m_EnabledTransitions.clear(); + FilterPreempted(m_TransitionSet, m_EnabledTransitions); + } + void SelectTransition(SStateNode &inNode, IEvent &inEvent, TTransitionSet &outTransitions) + { + TStateNodeList *theChildren = inNode.GetChildren(); + if (theChildren) { + for (TStateNodeList::iterator iter = theChildren->begin(), end = theChildren->end(); + iter != end; ++iter) { + if (iter->m_Type == StateNodeTypes::Transition) { + STransition &theTransition = *iter->CastTo<STransition>(); + if (IsTransitionValid(theTransition)) { + if (theTransition.m_Event.IsValid()) { + if (qt3ds::state::impl::NameMatches(theTransition.m_Event.c_str(), + inEvent.GetName().c_str())) { + if (!isTrivial(theTransition.m_Condition)) { + Option<bool> condResult = m_ScriptContext->ExecuteCondition( + theTransition.m_Condition); + if (condResult.hasValue()) { + if (*condResult) { + outTransitions.insert(&theTransition); + return; + } + } else { + QueueEvent("error.execution", false); + return; + } + } else { + outTransitions.insert(&theTransition); + return; + } + } + } + } + } + } + } + if (inNode.m_Parent) + SelectTransition(*inNode.m_Parent, inEvent, outTransitions); + } + + // Precondition is that m_Configuration is in document order. + void SelectTransitions(IEvent &inEvent) + { + m_TransitionSet.clear(); + for (QT3DSU32 idx = 0, end = m_Configuration.size(); idx < end; ++idx) { + // Ensure that m_Configuration is in document order. + if (idx) { + QT3DS_ASSERT(GetOrCreateInterpreterData(*m_Configuration[idx]).m_DocumentOrder + > GetOrCreateInterpreterData(*m_Configuration[idx - 1]).m_DocumentOrder); + } + + if (!m_Configuration[idx]->IsAtomic()) + continue; + SelectTransition(*m_Configuration[idx], inEvent, m_TransitionSet); + } + + FilterPreempted(m_TransitionSet, m_EnabledTransitions); + } + + bool Initialize(IStateContext &inContext, bool inValidateTransitions) override + { + m_Initialized = false; + InitializeDataStructures(); + m_Context = inContext; + QT3DS_ASSERT(m_Context->GetRoot()); + if (m_Context->GetRoot() == NULL) { + QT3DS_ASSERT(false); + m_Running = false; + return false; + } + + // Disable transitions that can put us into a bad state + if (inValidateTransitions) + RecursiveValidateTransitions(); + m_Initialized = true; + return true; + } + NVConstDataRef<SStateNode *> Start() override + { + if (!m_Initialized) { + QT3DS_ASSERT(false); + return NVConstDataRef<SStateNode *>(); + } + m_Running = true; + SSCXML &rootState = *m_Context->GetRoot(); + if (!rootState.m_Flags.IsLateBinding()) + RecursiveInitializeDataModel(); + eastl::string theSendLocExpr; + NVConstDataRef<SSend *> theSendData = m_Context->GetSendList(); + for (QT3DSU32 idx = 0, end = theSendData.size(); idx < end; ++idx) { + SSend &theSend(*theSendData[idx]); + if (!isTrivial(theSend.m_IdLocation)) { + QT3DS_ASSERT(theSend.m_Id.IsValid()); + m_ScriptContext->AssignStr(theSend.m_IdLocation, theSend.m_Id.c_str()); + } + } + + STransition *initialTrans = this->GetStateInitialTransition(rootState); + if (IsTransitionValid(initialTrans)) { + m_Configuration.insert(&rootState); + m_ExecutionContext->Execute(*initialTrans); + AddStatesToEnter(rootState); + EnterAncestors(initialTrans->m_Target, &rootState); + QT3DS_ASSERT(m_EnabledTransitions.empty()); + m_EnabledTransitions.clear(); + m_EnabledTransitions.push_back(initialTrans); + DEBUGGER_CALL1(SetTransitionSet, m_EnabledTransitions); + m_EnabledTransitions.clear(); + EnterStatesSecondHalf(); + ReleaseInitialAndTemporaryTransitions(); + DEBUGGER_CALL(EndMicrostep); + DEBUGGER_CALL(EndStep); + } else { + m_Running = false; + qFatal("Invalid state machine: root initial configuration is invalid"); + } + return m_Configuration.m_List; + } + + void Microstep(TTransitionList &inTransitions) + { + DEBUGGER_CALL1(SetTransitionSet, inTransitions); + ExitStates(inTransitions); + for (QT3DSU32 idx = 0, end = inTransitions.size(); idx < end; ++idx) { + m_ExecutionContext->Execute(*inTransitions[idx]); + for (QT3DSU32 sigIdx = 0, sigEnd = m_SignalSenders.size(); sigIdx < sigEnd; ++sigIdx) + m_SignalSenders[sigIdx]->m_Handler.OnInterpreterEvent( + InterpreterEventTypes::Transition, inTransitions[idx]->m_Id); + } + + EnterStates(inTransitions); + // Allow them to be selected again at some point. + inTransitions.clear(); + ReleaseInitialAndTemporaryTransitions(); + DEBUGGER_CALL(EndMicrostep); + } + + void CheckForDelayedEvents() + { + QT3DSU64 currentTime = Time::getCurrentTimeInTensOfNanoSeconds(); + TFutureEventList::iterator removeIter = m_FutureEvents.begin(); + TFutureEventList::iterator endIter = m_FutureEvents.end(); + // Future events list is sorted so all we have to do is iterate forward until + // fire time is greater than current time. + + for (; removeIter != endIter && removeIter->m_FireTime < currentTime; ++removeIter) { + } + // remove iter points either to the end or to the first event who's time has come. + for (TFutureEventList::iterator evtIter = m_FutureEvents.begin(); evtIter != removeIter; + ++evtIter) { + qCInfo(TRACE_INFO, "Sending delayed event: %s", evtIter->m_Event->GetName().c_str()); + QT3DS_ASSERT(evtIter->m_FireTime <= currentTime); + QueueEvent(evtIter->m_Event, evtIter->m_IsExternal); + } + if (removeIter != m_FutureEvents.begin()) { + m_FutureEvents.erase(m_FutureEvents.begin(), removeIter); + } + } + + bool CheckForStable() + { + CheckForDelayedEvents(); + if (m_EnabledTransitions.empty()) { + m_ScriptContext->ClearCurrentEvent(); + DEBUGGER_CALL(BeginMicrostep); + SelectEventlessTransitions(); + } + + return m_EnabledTransitions.empty() && m_InternalQueue.empty() && m_ExternalQueue.empty(); + } + + // Execute these events, return what state you are in. + // We process all internal and all external events before returning, so you are free to + // free or just deal with all userdata after this. + NVConstDataRef<SStateNode *> Execute() override + { + if (m_Running == false) + return m_Configuration.m_List; + + DEBUGGER_CALL(BeginStep); + DEBUGGER_CALL(BeginMicrostep); + + m_EnabledTransitions.clear(); + + QT3DSU32 iterationCount = 0; + QT3DSU32 MAX_ITERATION_COUNT = 1000; + + // Here we handle eventless transitions and transitions + // triggered by internal events until machine is stable + while (iterationCount < MAX_ITERATION_COUNT && m_Running && !CheckForStable()) { + ++iterationCount; + if (m_EnabledTransitions.empty() == false) { + Microstep(m_EnabledTransitions); + DEBUGGER_CALL(BeginMicrostep); + } else if (m_InternalQueue.empty() == false) { + NVScopedRefCounted<IEvent> theEvent = m_InternalQueue.front(); + m_ScriptContext->SetCurrentEvent(theEvent); + m_InternalQueue.pop_front(); + DEBUGGER_CALL2(SetCurrentEvent, theEvent->GetName().c_str(), true); +#ifdef QT3DS_LOG_ACTIVE_EVENT + qCInfo(TRACE_INFO, "Current event: %s", theEvent->GetName().c_str()); +#endif + SelectTransitions(*theEvent); + } else if (m_ExternalQueue.empty() == false) { + NVScopedRefCounted<IEvent> theEvent = m_ExternalQueue.front(); + m_ScriptContext->SetCurrentEvent(theEvent); + m_ExternalQueue.pop_front(); + + DEBUGGER_CALL2(SetCurrentEvent, theEvent->GetName().c_str(), false); + +#ifdef QT3DS_LOG_ACTIVE_EVENT + qCInfo(TRACE_INFO, "Current event: %s", theEvent->GetName().c_str()); +#endif + + /* + if isCancelEvent(externalEvent) + running = false + continue + */ + + // TODO datamodel + // datamodel["_event"] = theEvent; + + // TODO invoke + /* + for state in configuration: + for inv in state.invoke: + if inv.invokeid == externalEvent.invokeid: + applyFinalize(inv, externalEvent) + if inv.autoforward: + send(inv.id, externalEvent) + */ + SelectTransitions(*theEvent); + } + } + if (m_Running == false) { + for (nvvector<SStateNode *>::reverse_iterator iter = m_Configuration.m_List.rbegin(), + end = m_Configuration.m_List.rend(); + iter != end; ++iter) { + TOnExitList *theExitList = (*iter)->GetOnExitList(); + if (theExitList) + m_ExecutionContext->Execute(**iter, *theExitList); + /* + for inv in s.invoke: + cancelInvoke(inv) + */ + + // Set final done data. + /* + if isFinalState(s) and isScxmlState(s.parent): + returnDoneEvent(s.donedata) + */ + } + } + DEBUGGER_CALL(EndStep); + return m_Configuration.m_List; + } + + bool EventsPending() const override + { + return m_InternalQueue.empty() == false || m_ExternalQueue.empty() == false + || m_FutureEvents.empty() == false; + } + + bool IsRunning() const override { return m_Running; } + + void QueueEvent(const char8_t *inEventName, QT3DSU64 inDelay, CRegisteredString inCancelId, + bool inIsExternal) override + { + TEventPtr theEvent = QT3DS_NEW(m_Foundation.getAllocator(), SSimpleEvent)( + m_Foundation.getAllocator(), m_StringTable->RegisterStr(inEventName)); + QueueEvent(theEvent, inDelay, inCancelId, inIsExternal); + } + + void QueueEvent(TEventPtr inEvent, QT3DSU64 inDelay, CRegisteredString inCancelId, + bool inIsExternal) override + { + if (inDelay == 0) { + QueueEvent(inEvent, inIsExternal); + } else { + static QT3DSU64 sTensOfNanoSecondsInAMillisecond = + Time::sNumTensOfNanoSecondsInASecond / 1000; + QT3DSU64 fireTime = Time::getCurrentTimeInTensOfNanoSeconds() + + inDelay * sTensOfNanoSecondsInAMillisecond; + SFutureEvent theNewEvent(inEvent, fireTime, inCancelId, inIsExternal); + TFutureEventList::iterator iter = + eastl::upper_bound(m_FutureEvents.begin(), m_FutureEvents.end(), theNewEvent); + m_FutureEvents.insert(iter, theNewEvent); + } + } + + void QueueEvent(TEventPtr inEvent, bool inIsExternal) override + { + if (inIsExternal) + m_ExternalQueue.push_back(inEvent); + else + m_InternalQueue.push_back(inEvent); + DEBUGGER_CALL2(EventQueued, inEvent->GetName().c_str(), inIsExternal); + } + + void QueueEvent(const char8_t *inEventName, bool inIsExternal) override + { + TEventPtr theEvent = QT3DS_NEW(m_Foundation.getAllocator(), SSimpleEvent)( + m_Foundation.getAllocator(), m_StringTable->RegisterStr(inEventName)); + QueueEvent(theEvent, inIsExternal); + } + + void CancelEvent(CRegisteredString inCancelId) override + { + if (inCancelId.IsValid() == false) + return; + qCInfo(TRACE_INFO, "Cancel of id %s requested", inCancelId.c_str()); + for (QT3DSU32 idx = 0; idx < m_FutureEvents.size(); ++idx) { + if (m_FutureEvents[idx].m_CancelId == inCancelId) { + TFutureEventList::iterator iter = m_FutureEvents.begin() + idx; + qCInfo(TRACE_INFO, "Cancelling event: %s, %s", + iter->m_Event->GetName().c_str(), iter->m_CancelId.c_str()); + m_FutureEvents.erase(iter); + --idx; + } + } + } + + TSignalConnectionPtr RegisterEventHandler(IStateInterpreterEventHandler &inHandler) override + { + SStateSignalSender *sender = QT3DS_NEW(m_Foundation.getAllocator(), SStateSignalSender)( + m_Foundation.getAllocator(), *this, inHandler); + m_SignalSenders.push_back(sender); + return sender; + } + + IScriptContext &GetScriptContext() override { return *m_ScriptContext; } + + IStateContext *GetStateContext() override { return m_Context; } + + debugger::IStateMachineDebugInterface &GetDebugInterface() override { return m_DebugInterface; } + + NVFoundationBase &GetFoundation() override { return m_Foundation; } +}; + +//////////////////////////////////////////////////////////////////////////////////// +// SDebugInterface implementation +//////////////////////////////////////////////////////////////////////////////////// + +void SDebugInterface::addRef() +{ + m_StateSystem.addRef(); +} + +void SDebugInterface::release() +{ + m_StateSystem.release(); +} + +struct SDebugStrOutStream : public IOutStream +{ + TDebugStr m_Str; + bool Write(NVConstDataRef<QT3DSU8> data) override + { + m_Str.append((char8_t *)data.begin(), (char8_t *)data.end()); + return true; + } +}; + +NVConstDataRef<TDebugStr> ListToRef(const nvvector<SStateNode *> &inList, TDebugStrList &outData) +{ + outData.resize(inList.size()); + for (QT3DSU32 idx = 0, end = inList.size(); idx < end; ++idx) + outData[idx].assign(nonNull(inList[idx]->m_Id.c_str())); + return toConstDataRef(outData.data(), outData.size()); +} + +IScriptContext &SDebugInterface::GetScriptContext() +{ + return *m_StateSystem.m_ScriptContext.mPtr; +} + +// Functions implemented after StateSystem in order to take advantage of the StateSystem struct +// members directly. +void SDebugInterface::OnConnect() +{ + TDebugStr theFilename; + SDebugStrOutStream theOutStream; + if (m_StateSystem.m_Context->GetRoot()) + theFilename.assign(nonNull(m_StateSystem.m_Context->GetRoot()->m_Filename)); + CXMLIO::SaveSCXMLFile(*m_StateSystem.m_Context, m_StateSystem.m_Foundation, + *m_StateSystem.m_StringTable, theOutStream); + m_Listener->OnConnect(theFilename, theOutStream.m_Str, + ListToRef(m_StateSystem.m_Configuration.m_List, m_EnterList)); +} + +// Functions should not be called in there is not a valid listener +void SDebugInterface::BeginStep() +{ + m_Listener->BeginStep(); +} + +void SDebugInterface::BeginMicrostep() +{ + m_Listener->BeginMicroStep(); +} + +void SDebugInterface::SetCurrentEvent(const char8_t *inEventName, bool inInternal) +{ + m_TempStr.assign(nonNull(inEventName)); + m_Listener->SetEvent(m_TempStr, inInternal); +} + +STransitionId TransitionToId(const STransition &inTransition) +{ + SStateNode *parent(inTransition.m_Parent); + STransitionId retval; + if (parent == NULL) { + QT3DS_ASSERT(false); + return retval; + } + + retval.m_StateId.assign(nonNull(parent->m_Id.c_str())); + if (&inTransition == parent->GetInitialTransition()) + retval.m_TransitionIndex = -1; + else if (parent->m_Type == StateNodeTypes::History) { + SHistory &theHistory = static_cast<SHistory &>(*parent); + if (&inTransition == theHistory.m_Transition) + retval.m_TransitionIndex = -1; + } + + if (retval.m_TransitionIndex == -2) { + TStateNodeList *childList = parent->GetChildren(); + if (childList) { + QT3DSI32 index = 0; + for (TStateNodeList::iterator iter = childList->begin(), end = childList->end(); + iter != end && retval.m_TransitionIndex == -2; ++iter, ++index) { + SStateNode &theNode(*iter); + if (theNode.m_Type == StateNodeTypes::Transition && &inTransition == (&theNode)) + retval.m_TransitionIndex = index; + } + } + } + + return retval; +} + +void SDebugInterface::SetTransitionSet(const TTransitionList &inTransitions) +{ + m_TransitionList.resize(inTransitions.size()); + for (QT3DSU32 idx = 0, end = inTransitions.size(); idx < end; ++idx) + m_TransitionList[idx] = TransitionToId(*inTransitions[idx]); + m_Listener->SetTransitionSet(toDataRef(m_TransitionList.data(), m_TransitionList.size())); +} + +void SDebugInterface::SetExitSet(const TStateNodeSet &inSet) +{ + NVConstDataRef<TDebugStr> theSet(ListToRef(inSet.m_List, this->m_ExitList)); + m_Listener->SetExitSet(theSet); +} +void SDebugInterface::SetEnterSet(const TStateNodeSet &inSet) +{ + NVConstDataRef<TDebugStr> theSet(ListToRef(inSet.m_List, this->m_EnterList)); + m_Listener->SetEnterSet(theSet); +} +// Log statements run through the debugger as well. +void SDebugInterface::EndMicrostep() +{ + m_Listener->EndMicroStep(); +} +void SDebugInterface::EndStep() +{ + m_Listener->EndStep(); +} + +void SDebugInterface::OnExternalBreak() +{ + if (m_Listener) + m_Listener->OnExternalBreak(); +} + +SStateSignalSender::~SStateSignalSender() +{ + if (m_System) { + m_System->m_SignalSenders.erase( + eastl::find(m_System->m_SignalSenders.begin(), m_System->m_SignalSenders.end(), this)); + } +} +} + +IStateInterpreter &IStateInterpreter::Create(NVFoundationBase &inFnd, IStringTable &inStrTable, + IScriptContext &inScriptContext, + IExecutionContext &inExecutionContext) +{ + return *QT3DS_NEW(inFnd.getAllocator(), StateSystem)(inFnd, inStrTable, inScriptContext, + inExecutionContext); +} |