diff options
Diffstat (limited to 'src/Runtime/Source/stateapplication')
51 files changed, 23518 insertions, 0 deletions
diff --git a/src/Runtime/Source/stateapplication/Qt3DSState.h b/src/Runtime/Source/stateapplication/Qt3DSState.h new file mode 100644 index 00000000..b5686c25 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSState.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_STATE_H +#define QT3DS_STATE_H + +namespace qt3ds { +class NVAllocatorCallback; +class NVFoundationBase; +namespace foundation { + class CRegisteredString; + class IStringTable; + class IOutStream; + class IInStream; + class IDOMFactory; + struct SDOMAttribute; + struct SDOMElement; + struct SNamespacePairNode; + class SocketStream; +} + +namespace intrinsics { +} +} + +namespace qt3ds { +namespace state { + + using namespace qt3ds; + using namespace qt3ds::foundation; + using namespace qt3ds::intrinsics; + using qt3ds::foundation::CRegisteredString; + using qt3ds::foundation::IStringTable; + class IStateContext; + class IStateInterpreter; + class IStateLogger; + class IExecutionContext; + class IScriptContext; + struct SSCXML; + struct SState; + struct STransition; + struct SParallel; + struct SFinal; + struct SHistory; + struct SOnEntry; + struct SOnExit; + struct SSend; + struct SRaise; + struct SIf; + struct SElseIf; + struct SElse; + struct SLog; + struct SAssign; + struct SScript; + struct SDataModel; + struct SData; + struct SStateNode; + struct SCancel; + + namespace editor { + class IEditor; + class IEditorObject; + } + + namespace debugger { + class IDebugOutStream; + struct STransitionId; + class IStateMachineListener; + class IStateMachineDebugInterface; + class IDebugger; + struct SDebugPropertyDeclaration; + class IDebuggedInterpreter; + class IDebuggerMasterListener; + class IDebuggedInterpreter; + struct SMicrostep; + class IScriptStateListener; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateApplication.cpp b/src/Runtime/Source/stateapplication/Qt3DSStateApplication.cpp new file mode 100644 index 00000000..cd89d52f --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateApplication.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateApplication.h" +#include "foundation/FileTools.h" +#include "EASTL/vector.h" +#include "foundation/IOStreams.h" +#include "foundation/XML.h" + +#include <QtCore/qstringlist.h> + +typedef eastl::string TAppStr; + +using namespace qt3ds::foundation; +using namespace qt3ds::state; +using namespace eastl; + +eastl::string IApplication::GetLaunchFile(const char *inFullUIPPath) +{ + eastl::string directory; + eastl::string filestem; + eastl::string extension; + + CFileTools::Split(inFullUIPPath, directory, filestem, extension); + eastl::string uiaPath; + + eastl::vector<eastl::string> dirFiles; + CFileTools::GetDirectoryEntries(directory, dirFiles); + + for (qt3ds::QT3DSU32 idx = 0, end = dirFiles.size(); idx < end && uiaPath.empty(); ++idx) { + eastl::string fileExt; + CFileTools::GetExtension(dirFiles[idx].c_str(), fileExt); + if (fileExt.comparei("uia") == 0) + CFileTools::CombineBaseAndRelative(directory.c_str(), dirFiles[idx].c_str(), uiaPath); + } + return uiaPath.empty() == false ? uiaPath : eastl::string(inFullUIPPath); +} diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateApplication.h b/src/Runtime/Source/stateapplication/Qt3DSStateApplication.h new file mode 100644 index 00000000..3ebe0d4f --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateApplication.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_APPLICATION_H +#define QT3DS_STATE_APPLICATION_H +#include "EASTL/string.h" +#include <QtCore/qvector.h> + +namespace qt3ds { +namespace state { + // Shared code for dealing with .uia files. + class IApplication + { + public: + static eastl::string GetLaunchFile(const char *inFullUIPPath); + }; +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateContext.cpp b/src/Runtime/Source/stateapplication/Qt3DSStateContext.cpp new file mode 100644 index 00000000..60814ed5 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateContext.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateContext.h" +#include "Qt3DSStateXMLIO.h" +#include "foundation/XML.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" + +using namespace qt3ds::state; + +namespace { + +struct SStateContext : public IStateContext +{ + NVFoundationBase &m_Foundation; + TIDStateMap m_IDStateMap; + TSendList m_SendList; + SSCXML *m_Root; + + // the factory needs to be here in order that we can easily just assign variables + // left and right and make things work. + NVScopedRefCounted<IDOMFactory> m_DOMFactory; + TPtrExtensionMap m_ExtensionInfo; + SNamespacePairNode *m_NamespacePairs; + QT3DSI32 mRefCount; + + SStateContext(NVFoundationBase &alloc) + : m_Foundation(alloc) + , m_IDStateMap(alloc.getAllocator(), "SStateContext::m_IDStateMap") + , m_SendList(alloc.getAllocator(), "SStateContext::m_SendList") + , m_Root(NULL) + , m_DOMFactory(NULL) + , m_ExtensionInfo(alloc.getAllocator(), "SStateContext::m_ExtensionInfo") + , m_NamespacePairs(NULL) + , mRefCount(0) + { + } + virtual ~SStateContext() {} + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + void SetDOMFactory(IDOMFactory *inFactory) override; + + void SetRoot(SSCXML &inRoot) override + { + QT3DS_ASSERT(m_Root == NULL); + if (!m_Root) + m_Root = &inRoot; + } + SSCXML *GetRoot() override { return m_Root; } + void AddSendToList(SSend &inSend) override { m_SendList.push_back(&inSend); } + NVConstDataRef<SSend *> GetSendList() override + { + return toConstDataRef(m_SendList.data(), (QT3DSU32)m_SendList.size()); + } + IDOMFactory *GetDOMFactory() override { return m_DOMFactory; } + NVConstDataRef<NVScopedRefCounted<IStateContext>> GetSubContexts() override + { + return NVConstDataRef<NVScopedRefCounted<IStateContext>>(); + } + + // UICStateInterpreter.cpp; + bool InsertId(const CRegisteredString &inId, const SIdValue &inValue) override; + void EraseId(const CRegisteredString &inId) override; + bool ContainsId(const CRegisteredString &inStr) override; + SStateNode *FindStateNode(const CRegisteredString &inStr) override; + SSend *FindSend(const CRegisteredString &inStr) override; + + SItemExtensionInfo *GetExtensionInfo(void *inItem) override; + SItemExtensionInfo &GetOrCreateExtensionInfo(void *inItem) override; + + void SetFirstNSNode(SNamespacePairNode &inNode) override { m_NamespacePairs = &inNode; } + SNamespacePairNode *GetFirstNSNode() override { return m_NamespacePairs; } + + void Save(IOutStream &inOutStream, editor::IEditor *inEditor) override; +}; + +void SStateContext::SetDOMFactory(IDOMFactory *inFactory) +{ + m_DOMFactory = inFactory; +} + +bool SStateContext::InsertId(const CRegisteredString &inId, const SIdValue &inValue) +{ + return m_IDStateMap.insert(eastl::make_pair(inId, inValue)).second; +} + +void SStateContext::EraseId(const CRegisteredString &inId) +{ + m_IDStateMap.erase(inId); +} + +bool SStateContext::ContainsId(const CRegisteredString &inStr) +{ + return m_IDStateMap.find(inStr) != m_IDStateMap.end(); +} + +SStateNode *SStateContext::FindStateNode(const CRegisteredString &inStr) +{ + TIDStateMap::iterator iter = m_IDStateMap.find(inStr); + + if (iter != m_IDStateMap.end()) { + IdValueTypes::Enum typeEnum = iter->second.getType(); + if (typeEnum == IdValueTypes::StateNode) + return iter->second.getData<SStateNode *>(); + } + return NULL; +} + +SSend *SStateContext::FindSend(const CRegisteredString &inStr) +{ + TIDStateMap::iterator iter = m_IDStateMap.find(inStr); + if (iter != m_IDStateMap.end() && iter->second.getType() == IdValueTypes::Send) + return iter->second.getData<SSend *>(); + return NULL; +} + +SItemExtensionInfo *SStateContext::GetExtensionInfo(void *inItem) +{ + TPtrExtensionMap::iterator iter = m_ExtensionInfo.find(inItem); + if (iter != m_ExtensionInfo.end()) + return &iter->second; + return NULL; +} + +SItemExtensionInfo &SStateContext::GetOrCreateExtensionInfo(void *inItem) +{ + return m_ExtensionInfo.insert(eastl::make_pair(inItem, SItemExtensionInfo(inItem))) + .first->second; +} + +void SStateContext::Save(IOutStream &inOutStream, editor::IEditor *inEditor) +{ + CXMLIO::SaveSCXMLFile(*this, m_Foundation, *m_DOMFactory->GetStringTable(), inOutStream, + inEditor); +} +} + +IStateContext *IStateContext::Load(NVAllocatorCallback &inGraphAllocator, + NVFoundationBase &inFoundation, IInStream &inStream, + const char8_t *inFilename, IStringTable *inStrTable, + editor::IEditor *inEditor) +{ + + NVScopedRefCounted<IStringTable> theStringTable = inStrTable; + if (!theStringTable) + theStringTable = IStringTable::CreateStringTable(inFoundation.getAllocator()); + NVScopedRefCounted<IDOMFactory> theFactory = + IDOMFactory::CreateDOMFactory(inFoundation.getAllocator(), theStringTable); + eastl::pair<SNamespacePairNode *, SDOMElement *> readResult = + CDOMSerializer::Read(*theFactory, inStream); + SDOMElement *elem = readResult.second; + if (elem == NULL) + return NULL; + + NVScopedRefCounted<IDOMReader> theReader = IDOMReader::CreateDOMReader( + inFoundation.getAllocator(), *elem, theStringTable, *theFactory); + IStateContext *retval = QT3DS_NEW(inFoundation.getAllocator(), SStateContext)(inFoundation); + retval->SetDOMFactory(theFactory.mPtr); + CXMLIO::LoadSCXMLFile(inGraphAllocator, inFoundation, *theReader, *theStringTable, inFilename, + *retval, inEditor); + if (readResult.first) + retval->SetFirstNSNode(*readResult.first); + return retval; +} + +IStateContext *IStateContext::Create(NVFoundationBase &inFoundation) +{ + NVScopedRefCounted<IStringTable> theStringTable = + IStringTable::CreateStringTable(inFoundation.getAllocator()); + NVScopedRefCounted<IDOMFactory> theFactory = + IDOMFactory::CreateDOMFactory(inFoundation.getAllocator(), theStringTable); + + SStateContext *retval = QT3DS_NEW(inFoundation.getAllocator(), SStateContext)(inFoundation); + retval->SetDOMFactory(theFactory); + return retval; +} diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateContext.h b/src/Runtime/Source/stateapplication/Qt3DSStateContext.h new file mode 100644 index 00000000..31098938 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateContext.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_CONTEXT_H +#define QT3DS_STATE_CONTEXT_H +#include "Qt3DSState.h" +#include "Qt3DSStateTypes.h" + +namespace qt3ds { +namespace state { + + // Thanks to the crazy world we live in, we have to store xml extension information + // on the state context. + struct SItemExtensionInfo + { + void *m_ItemPtr; + TDOMElementNodeList m_ExtensionNodes; + TDOMAttributeNodeList m_ExtensionAttributes; + SItemExtensionInfo(void *inItemPtr) + : m_ItemPtr(inItemPtr) + { + } + }; + typedef nvhash_map<void *, SItemExtensionInfo> TPtrExtensionMap; + // Parsing produces a list of send objects that need to be added into the script + // context if they have their idlocation attribute set. + typedef nvvector<SSend *> TSendList; + + class IStateContext : public NVRefCounted + { + public: + // Can only be done once. + virtual void SetRoot(SSCXML &inRoot) = 0; + virtual SSCXML *GetRoot() = 0; + virtual void AddSendToList(SSend &inSend) = 0; + virtual NVConstDataRef<SSend *> GetSendList() = 0; + virtual void SetDOMFactory(IDOMFactory *inFactory) = 0; + virtual IDOMFactory *GetDOMFactory() = 0; + virtual NVConstDataRef<NVScopedRefCounted<IStateContext>> GetSubContexts() = 0; + virtual bool ContainsId(const CRegisteredString &inId) = 0; + virtual bool InsertId(const CRegisteredString &inId, const SIdValue &inValue) = 0; + virtual void EraseId(const CRegisteredString &inId) = 0; + virtual SStateNode *FindStateNode(const CRegisteredString &inStr) = 0; + virtual SSend *FindSend(const CRegisteredString &inStr) = 0; + + virtual SItemExtensionInfo *GetExtensionInfo(void *inItem) = 0; + virtual SItemExtensionInfo &GetOrCreateExtensionInfo(void *inItem) = 0; + + virtual void SetFirstNSNode(SNamespacePairNode &inNode) = 0; + virtual SNamespacePairNode *GetFirstNSNode() = 0; + + virtual void Save(IOutStream &inOutStream, editor::IEditor *inEditor = NULL) = 0; + + // The graph allocator is expected to release anything allocated via it; the general + // allocator doesn't need to do this. + // Filename is stored on the context for debugging purposes, editor is used if during + // loading so we can load extra + // information that is associated with but not stored on the state graph. + // String table will be created if not passed in, editor is optional. + static IStateContext *Load(NVAllocatorCallback &inGraphAllocator, + NVFoundationBase &inFoundation, IInStream &inStream, + const char8_t *inFilename, IStringTable *inStrTable = NULL, + editor::IEditor *inEditor = NULL); + + static IStateContext *Create(NVFoundationBase &inGeneralAlloc); + }; +} +} +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateExecutionContext.cpp b/src/Runtime/Source/stateapplication/Qt3DSStateExecutionContext.cpp new file mode 100644 index 00000000..4516c2f9 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateExecutionContext.cpp @@ -0,0 +1,332 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateExecutionContext.h" +#include "Qt3DSStateExecutionTypes.h" +#include "Qt3DSStateInterpreter.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/StringConversionImpl.h" +#include "Qt3DSStateScriptContext.h" +#include "foundation/Utils.h" +#include "EASTL/string.h" + +using namespace qt3ds::state; + +namespace { +struct SExecContext : public IExecutionContext +{ + NVFoundationBase &m_Foundation; + NVScopedRefCounted<IStringTable> m_StringTable; + // Referencing this here would create circular references + IStateInterpreter *m_Interpreter; + IStateLogger *m_DebugLogger; + NVScopedRefCounted<IStateLogger> m_Logger; + NVScopedRefCounted<IScriptContext> m_ScriptContext; + bool m_Error; + QT3DSI32 mRefCount; + eastl::string m_DelayStr; + + SExecContext(NVFoundationBase &inFnd, IStringTable &inStrTable, IStateLogger &inLogger, + IScriptContext &inContext) + : m_Foundation(inFnd) + , m_StringTable(inStrTable) + , m_Interpreter(NULL) + , m_DebugLogger(NULL) + , m_Logger(inLogger) + , m_ScriptContext(inContext) + , m_Error(false) + , mRefCount(0) + { + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + void SetInterpreter(IStateInterpreter &inInterpreter) override + { + m_Interpreter = &inInterpreter; + } + + void SetMachineDebugLogger(IStateLogger &inDebugLogger) override + { + m_DebugLogger = &inDebugLogger; + } + + void SignalError() + { + const char8_t *errorInfo = m_ScriptContext->GetErrorInfo(); + if (errorInfo && *errorInfo) { + m_Interpreter->QueueEvent("error.execution", false); + } + m_Error = true; + } + void ExecuteContent(SExecutableContent &content) + { + switch (content.m_Type) { + case ExecutableContentTypes::Send: { + SSend &theSend = *content.CastTo<SSend>(); + CRegisteredString theEvent; + if (!isTrivial(theSend.m_EventExpr)) { + SScriptExecutionResult theStr = + m_ScriptContext->ExecuteExpressionToString(theSend.m_EventExpr); + if (theStr.Valid()) + theEvent = m_StringTable->RegisterStr(theStr.Result()); + } else + theEvent = theSend.m_Event; + + QT3DSU64 theDelay(0); + m_DelayStr.clear(); + if (!isTrivial(theSend.m_DelayExpr)) { + SScriptExecutionResult theStr = + m_ScriptContext->ExecuteExpressionToString(theSend.m_DelayExpr); + if (theStr.Valid()) + m_DelayStr.assign(nonNull(theStr.Result())); + } else + m_DelayStr.assign(nonNull(theSend.m_Delay)); + if (m_DelayStr.size()) + theDelay = ParseTimeStrToMilliseconds(m_DelayStr.c_str()); + + if (theEvent.IsValid()) { + TEventPtr theEventPtr; + if (theSend.m_Children.empty() == false) { + IScriptEvent *theNewEvent = m_ScriptContext->CreateScriptEvent(theEvent); + theEventPtr = theNewEvent; + for (TExecutableContentList::iterator iter = theSend.m_Children.begin(), + end = theSend.m_Children.end(); + iter != end && m_Error == false; ++iter) { + if (iter->m_Type == ExecutableContentTypes::Param) { + SParam &theParam = static_cast<SParam &>(*iter); + if (theParam.m_Location.IsValid() == false) { + bool success = + theNewEvent->SetParam(theParam.m_Name, theParam.m_Expr); + if (!success) + SignalError(); + } else { + QT3DS_ASSERT(false); + } + } else if (iter->m_Type == ExecutableContentTypes::Content) { + SContent &theContent = static_cast<SContent &>(*iter); + if (!isTrivial(theContent.m_Expr)) { + bool success = theNewEvent->SetDataExpr(theContent.m_Expr); + if (!success) + SignalError(); + } else if (!isTrivial(theContent.m_ContentValue)) { + bool success = theNewEvent->SetDataStr(theContent.m_ContentValue); + if (!success) + SignalError(); + } + } else { + QT3DS_ASSERT(false); + } + } + } + if (m_Error == false) { + bool isExternal = true; + if (AreEqual("#_internal", theSend.m_Target)) + isExternal = false; + if (theEventPtr) + m_Interpreter->QueueEvent(theEventPtr, theDelay, theSend.m_Id, isExternal); + else + m_Interpreter->QueueEvent(theEvent, theDelay, theSend.m_Id, isExternal); + } + } + } break; + case ExecutableContentTypes::Cancel: { + SCancel &theCancel = *content.CastTo<SCancel>(); + if (theCancel.m_Send) { + m_Interpreter->CancelEvent(theCancel.m_Send->m_Id); + } else if (!isTrivial(theCancel.m_IdExpression)) { + SScriptExecutionResult theStr = + m_ScriptContext->ExecuteExpressionToString(theCancel.m_IdExpression); + if (theStr.Valid()) { + const char8_t *theStrVal(theStr.Result()); + if (!isTrivial(theStrVal)) + m_Interpreter->CancelEvent(m_StringTable->RegisterStr(theStrVal)); + } + } + } break; + case ExecutableContentTypes::Raise: { + SRaise &theRaise = *content.CastTo<SRaise>(); + m_Interpreter->QueueEvent(theRaise.m_Event, false); + } break; + case ExecutableContentTypes::Log: { + SLog *theLog = content.CastTo<SLog>(); + SScriptExecutionResult str = + m_ScriptContext->ExecuteExpressionToString(theLog->m_Expression); + if (str.Valid()) { + m_Logger->Log(theLog->m_Label, str.Result()); + if (m_DebugLogger) + m_DebugLogger->Log(theLog->m_Label, str.Result()); + } else + SignalError(); + } break; + case ExecutableContentTypes::If: { + SIf &theIf = *content.CastTo<SIf>(); + Option<bool> theCondResult = m_ScriptContext->ExecuteCondition(theIf.m_Cond); + if (theCondResult.hasValue()) { + bool currentConditionResult = *theCondResult; + for (TExecutableContentList::iterator ifIter = theIf.m_Children.begin(), + ifEnd = theIf.m_Children.end(); + ifIter != ifEnd && m_Error == false; ++ifIter) { + if (currentConditionResult) { + switch (ifIter->m_Type) { + case ExecutableContentTypes::Else: + case ExecutableContentTypes::ElseIf: + return; + default: + ExecuteContent(*ifIter); + break; + } + } else { + switch (ifIter->m_Type) { + case ExecutableContentTypes::ElseIf: { + SElseIf &theElseIf = *ifIter->CastTo<SElseIf>(); + theCondResult = m_ScriptContext->ExecuteCondition(theElseIf.m_Cond); + if (theCondResult.hasValue()) + currentConditionResult = *theCondResult; + else { + SignalError(); + return; + } + } break; + case ExecutableContentTypes::Else: + currentConditionResult = true; + break; + // Ignore all content that isn't if or else if we shouldn't be currently + // executing it. + default: + break; + } + } + } + } else + SignalError(); + } break; + case ExecutableContentTypes::Foreach: { + SForeach &theItem = *content.CastTo<SForeach>(); + Option<bool> success; + for (success = m_ScriptContext->BeginForeach(theItem.m_Array, theItem.m_Item, + theItem.m_Index); + success.hasValue() && *success && m_Error == false; + success = m_ScriptContext->NextForeach(theItem.m_Item, theItem.m_Index)) { + ExecuteContent(theItem.m_Children); + } + if (m_Error) { + + } else if (success.hasValue() == false) + SignalError(); + } break; + // We shouldn't get top level else or else if statements, they can only be inside an if + // statement. + case ExecutableContentTypes::Else: + case ExecutableContentTypes::ElseIf: + QT3DS_ASSERT(false); + break; + + case ExecutableContentTypes::Assign: { + SAssign &theAssign = *content.CastTo<SAssign>(); + bool success = m_ScriptContext->Assign(theAssign.m_Location, theAssign.m_Expression); + if (!success) + SignalError(); + } break; + case ExecutableContentTypes::Script: { + SScript &theScript = *content.CastTo<SScript>(); + if (!isTrivial(theScript.m_Data)) + m_ScriptContext->ExecuteScript(theScript.m_Data); + } break; + default: + qCCritical(INTERNAL_ERROR, "Unimplemented executable content %s", + ExecutableContentTypes::ToString(content.m_Type)); + } + } + + void ExecuteContent(TExecutableContentList &inContent) + { + for (TExecutableContentList::iterator iter = inContent.begin(), end = inContent.end(); + iter != end && m_Error == false; ++iter) { + ExecuteContent(*iter); + } + } + + void Execute(STransition &inTransaction) override + { + m_Error = false; + ExecuteContent(inTransaction.m_ExecutableContent); + } + + // These functions take the node as well as the list so a context can cache a fast + // execution path if necessary. + void Execute(SStateNode & /*inNode*/, TOnEntryList &inList) override + { + for (TOnEntryList::iterator iter = inList.begin(), end = inList.end(); iter != end; + ++iter) { + m_Error = false; + ExecuteContent(iter->m_ExecutableContent); + } + } + + void Execute(SStateNode & /*inNode*/, TOnExitList &inList) override + { + for (TOnExitList::iterator iter = inList.begin(), end = inList.end(); iter != end; ++iter) { + m_Error = false; + ExecuteContent(iter->m_ExecutableContent); + } + } +}; +} + +QT3DSU64 IExecutionContext::ParseTimeStrToMilliseconds(const char8_t *timeStr) +{ + if (isTrivial(timeStr)) + return 0; + + char *endPtr; + double theData = strtod(timeStr, &endPtr); + if (!isTrivial(endPtr)) { + if (AreEqual(endPtr, "s")) { + theData *= 1000; + } else if (AreEqual(endPtr, "ms")) { + // empty intentional + } else + theData = 0; + } else + theData = 0; + if (theData < 0) + theData = 0.0; + return static_cast<QT3DSU64>(theData); +} + +IExecutionContext &IExecutionContext::Create(NVFoundationBase &inFoundation, + IStringTable &inStringTable, IStateLogger &inLogger, + IScriptContext &inScriptContext) +{ + return *QT3DS_NEW(inFoundation.getAllocator(), SExecContext)(inFoundation, inStringTable, inLogger, + inScriptContext); +} diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateExecutionContext.h b/src/Runtime/Source/stateapplication/Qt3DSStateExecutionContext.h new file mode 100644 index 00000000..31ff0e22 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateExecutionContext.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EXECUTION_CONTEXT_H +#define QT3DS_STATE_EXECUTION_CONTEXT_H +#pragma once +#include "Qt3DSState.h" +#include "Qt3DSStateTypes.h" +#include "foundation/Qt3DSRefCounted.h" + +namespace qt3ds { +namespace state { + + class IStateLogger : public NVRefCounted + { + protected: + virtual ~IStateLogger() {} + public: + virtual void Log(const char8_t *inLabel, const char8_t *inExpression) = 0; + }; + // Implementation of the execution context of the scxml state specification. + // Implementations if,send,raise,foreach,etc working closely with the scripting + // system. + class IExecutionContext : public NVRefCounted + { + protected: + virtual ~IExecutionContext() {} + public: + virtual void SetInterpreter(IStateInterpreter &inInterpreter) = 0; + virtual void SetMachineDebugLogger(IStateLogger &inDebugLogger) = 0; + virtual void Execute(STransition &inTransaction) = 0; + // These functions take the node as well as the list so a context can cache a fast + // execution path if necessary. + virtual void Execute(SStateNode &inNode, TOnEntryList &inList) = 0; + virtual void Execute(SStateNode &inNode, TOnExitList &inList) = 0; + + // Returns the time string in milliseconds. + static QT3DSU64 ParseTimeStrToMilliseconds(const char8_t *timeStr); + + static IExecutionContext &Create(NVFoundationBase &inFoundation, + IStringTable &inStringTable, IStateLogger &inLogger, + IScriptContext &inScriptContext); + }; +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateExecutionTypes.h b/src/Runtime/Source/stateapplication/Qt3DSStateExecutionTypes.h new file mode 100644 index 00000000..6dc7674f --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateExecutionTypes.h @@ -0,0 +1,392 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EXECUTION_TYPES_H +#define QT3DS_STATE_EXECUTION_TYPES_H +#pragma once +#include "Qt3DSStateTypes.h" + +namespace qt3ds { +namespace state { + + struct ExecutableContentTypes + { + enum Enum { + NoType = 0, + Raise, + If, + ElseIf, + Else, + Foreach, + Log, + Assign, + Script, + Send, + Cancel, + Param, + Content, + }; + static const char8_t *ToString(Enum inVal) + { + switch (inVal) { + case Raise: + return "Raise"; + case If: + return "If"; + case ElseIf: + return "ElseIf"; + case Else: + return "Else"; + case Foreach: + return "Foreach"; + case Log: + return "Log"; + case Assign: + return "Assign"; + case Script: + return "Script"; + case Send: + return "Send"; + case Cancel: + return "Cancel"; + case Param: + return "Param"; + case Content: + return "Content"; + default: + return "Unknown execution type"; + } + } + }; + + struct SRaise; + struct SIf; + struct SElseIf; + struct SElse; + struct SForeach; + struct SLog; + struct SAssign; + struct SScript; + struct SSend; + struct SCancel; + struct SParam; + struct SContent; + + template <typename TDataType> + struct SExecutableContentTypeMap + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::NoType; + } + }; + + template <> + struct SExecutableContentTypeMap<SRaise> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Raise; + } + }; + template <> + struct SExecutableContentTypeMap<SIf> + { + static ExecutableContentTypes::Enum GetContentType() { return ExecutableContentTypes::If; } + }; + template <> + struct SExecutableContentTypeMap<SElseIf> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::ElseIf; + } + }; + template <> + struct SExecutableContentTypeMap<SElse> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Else; + } + }; + template <> + struct SExecutableContentTypeMap<SForeach> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Foreach; + } + }; + template <> + struct SExecutableContentTypeMap<SLog> + { + static ExecutableContentTypes::Enum GetContentType() { return ExecutableContentTypes::Log; } + }; + template <> + struct SExecutableContentTypeMap<SAssign> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Assign; + } + }; + template <> + struct SExecutableContentTypeMap<SScript> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Script; + } + }; + template <> + struct SExecutableContentTypeMap<SSend> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Send; + } + }; + template <> + struct SExecutableContentTypeMap<SCancel> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Cancel; + } + }; + template <> + struct SExecutableContentTypeMap<SParam> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Param; + } + }; + template <> + struct SExecutableContentTypeMap<SContent> + { + static ExecutableContentTypes::Enum GetContentType() + { + return ExecutableContentTypes::Content; + } + }; + + // Defined by the execution context to speed up evaluation of executable data. + struct SExecutionData; + + struct SExecutableContent + { + const ExecutableContentTypes::Enum m_Type; + SStateNode *m_StateNodeParent; + SExecutableContent *m_Parent; + SExecutableContent *m_NextSibling; + SExecutableContent *m_PreviousSibling; + TExecutableContentList m_Children; + + SExecutableContent(ExecutableContentTypes::Enum inType) + : m_Type(inType) + , m_StateNodeParent(NULL) + , m_Parent(NULL) + , m_NextSibling(NULL) + , m_PreviousSibling(NULL) + { + } + + template <typename TDataType> + TDataType *CastTo() + { + if (m_Type == SExecutableContentTypeMap<TDataType>::GetContentType()) + return static_cast<TDataType *>(this); + return NULL; + } + + template <typename TDataType> + const TDataType *CastTo() const + { + if (m_Type == SExecutableContentTypeMap<TDataType>::GetContentType()) + return static_cast<const TDataType *>(this); + return NULL; + } + }; + + IMPLEMENT_INVASIVE_LIST(ExecutableContent, m_PreviousSibling, m_NextSibling); + + struct SRaise : public SExecutableContent + { + CRegisteredString m_Event; + SRaise() + : SExecutableContent(ExecutableContentTypes::Raise) + { + } + }; + + struct SIf : public SExecutableContent + { + const char8_t *m_Cond; + SIf() + : SExecutableContent(ExecutableContentTypes::If) + , m_Cond(NULL) + { + } + }; + + struct SElseIf : public SExecutableContent + { + const char8_t *m_Cond; + SElseIf() + : SExecutableContent(ExecutableContentTypes::ElseIf) + , m_Cond(NULL) + { + } + }; + + struct SElse : public SExecutableContent + { + SElse() + : SExecutableContent(ExecutableContentTypes::Else) + { + } + }; + + struct SForeach : public SExecutableContent + { + CRegisteredString m_Array; + CRegisteredString m_Item; + CRegisteredString m_Index; + SForeach() + : SExecutableContent(ExecutableContentTypes::Foreach) + { + } + }; + + struct SLog : public SExecutableContent + { + CRegisteredString m_Label; + const char8_t *m_Expression; + SLog() + : SExecutableContent(ExecutableContentTypes::Log) + , m_Expression(NULL) + { + } + }; + + struct SAssign : public SExecutableContent + { + const char8_t *m_Location; + const char8_t *m_Expression; + SAssign() + : SExecutableContent(ExecutableContentTypes::Assign) + , m_Location(NULL) + , m_Expression(NULL) + { + } + }; + + struct SScript : public SExecutableContent + { + const char8_t *m_URL; + const char8_t *m_Data; + SScript() + : SExecutableContent(ExecutableContentTypes::Script) + , m_URL(NULL) + , m_Data(NULL) + { + } + }; + + struct SSend : public SExecutableContent + { + CRegisteredString m_Event; + const char8_t *m_EventExpr; + const char8_t *m_Target; + const char8_t *m_TargetExpr; + const char8_t *m_Type; + const char8_t *m_TypeExpr; + CRegisteredString m_Id; + const char8_t *m_IdLocation; + const char8_t *m_Delay; + const char8_t *m_DelayExpr; + const char8_t *m_NameList; + + SSend() + : SExecutableContent(ExecutableContentTypes::Send) + , m_EventExpr(NULL) + , m_Target(NULL) + , m_TargetExpr(NULL) + , m_Type(NULL) + , m_TypeExpr(NULL) + , m_IdLocation(NULL) + , m_Delay(NULL) + , m_DelayExpr(NULL) + , m_NameList(NULL) + { + } + }; + struct SCancel : public SExecutableContent + { + // If we have an id. + SSend *m_Send; + const char8_t *m_IdExpression; + + SCancel() + : SExecutableContent(ExecutableContentTypes::Cancel) + , m_Send(NULL) + , m_IdExpression(NULL) + { + } + }; + + struct SParam : public SExecutableContent + { + CRegisteredString m_Name; + const char8_t *m_Expr; + CRegisteredString m_Location; + SParam() + : SExecutableContent(ExecutableContentTypes::Param) + , m_Expr(NULL) + { + } + }; + + struct SContent : public SExecutableContent + { + const char8_t *m_Expr; + const char8_t *m_ContentValue; + SContent() + : SExecutableContent(ExecutableContentTypes::Content) + , m_Expr(NULL) + , m_ContentValue(NULL) + { + } + }; +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateIdValue.h b/src/Runtime/Source/stateapplication/Qt3DSStateIdValue.h new file mode 100644 index 00000000..ed5beb13 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateIdValue.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_STATE_ID_TYPE_VALUE_H +#define QT3DS_STATE_ID_TYPE_VALUE_H +#include "Qt3DSState.h" +#include "foundation/Qt3DSDiscriminatedUnion.h" + +// Discriminated union to unify different things that may be identified by an id +namespace qt3ds { +namespace state { + + struct IdValueTypes + { + enum Enum { + NoIdValue = 0, + StateNode, + Send, + }; + }; + + template <typename TDataType> + struct SIdValueTypeMap + { + static IdValueTypes::Enum GetType() { return IdValueTypes::NoIdValue; } + }; + + template <> + struct SIdValueTypeMap<SStateNode *> + { + static IdValueTypes::Enum GetType() { return IdValueTypes::StateNode; } + }; + template <> + struct SIdValueTypeMap<SSend *> + { + static IdValueTypes::Enum GetType() { return IdValueTypes::Send; } + }; + + struct SIdValueUnionTraits + { + typedef IdValueTypes::Enum TIdType; + enum { + TBufferSize = sizeof(void *), + }; + + static TIdType getNoDataId() { return IdValueTypes::NoIdValue; } + + template <typename TDataType> + static TIdType getType() + { + return SIdValueTypeMap<TDataType>().GetType(); + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case IdValueTypes::StateNode: + return inVisitor(*NVUnionCast<SStateNode **>(inData)); + case IdValueTypes::Send: + return inVisitor(*NVUnionCast<SSend **>(inData)); + default: + QT3DS_ASSERT(false); + case IdValueTypes::NoIdValue: + return inVisitor(); + } + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case IdValueTypes::StateNode: + return inVisitor(*NVUnionCast<const SStateNode **>(inData)); + case IdValueTypes::Send: + return inVisitor(*NVUnionCast<const SSend **>(inData)); + default: + QT3DS_ASSERT(false); + case IdValueTypes::NoIdValue: + return inVisitor(); + } + } + }; +} +} + +// need some specializations in the original nv foundation namespace +namespace qt3ds { +namespace foundation { + + template <> + struct DestructTraits<qt3ds::state::SStateNode *> + { + void destruct(qt3ds::state::SStateNode *) {} + }; + template <> + struct DestructTraits<qt3ds::state::SSend *> + { + void destruct(qt3ds::state::SSend *) {} + }; +} +} + +namespace qt3ds { +namespace state { + + typedef qt3ds::foundation:: + DiscriminatedUnion<qt3ds::foundation:: + DiscriminatedUnionGenericBase<SIdValueUnionTraits, + SIdValueUnionTraits::TBufferSize>, + SIdValueUnionTraits::TBufferSize> + TIdUnionType; + + struct SIdValue : public TIdUnionType + { + SIdValue() {} + + SIdValue(const SIdValue &inOther) + : TIdUnionType(static_cast<const TIdUnionType &>(inOther)) + { + } + + SIdValue(SStateNode *inDt) + : TIdUnionType(inDt) + { + } + SIdValue(SSend *inDt) + : TIdUnionType(inDt) + { + } + + SIdValue &operator=(const SIdValue &inOther) + { + TIdUnionType::operator=(inOther); + return *this; + } + + bool operator==(const SIdValue &inOther) const { return TIdUnionType::operator==(inOther); } + bool operator!=(const SIdValue &inOther) const { return TIdUnionType::operator!=(inOther); } + + bool empty() const { return getType() == IdValueTypes::NoIdValue; } + }; +} +} + +#endif 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); +} diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateInterpreter.h b/src/Runtime/Source/stateapplication/Qt3DSStateInterpreter.h new file mode 100644 index 00000000..d615d21f --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateInterpreter.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_INTERPRETER_H +#define QT3DS_STATE_INTERPRETER_H +#pragma once +#include "Qt3DSState.h" +#include "Qt3DSStateTypes.h" +#include "Qt3DSStateSignalConnection.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/Qt3DSDataRef.h" + +namespace qt3ds { +namespace state { + struct InterpreterEventTypes + { + enum Enum { + UnknownInterpreterEvent = 0, + StateEnter, + StateExit, + Transition, + }; + }; + + struct IStateInterpreterEventHandler + { + protected: + virtual ~IStateInterpreterEventHandler() {} + + public: + virtual void OnInterpreterEvent(InterpreterEventTypes::Enum inEvent, + CRegisteredString inEventId) = 0; + }; + + class IStateInterpreter : public NVRefCounted + { + protected: + virtual ~IStateInterpreter() {} + public: + // Setup of the state system, you can add a set of roots to the state graph. + virtual NVConstDataRef<SStateNode *> GetConfiguration() = 0; + // State context is referenced by this object. + // We can optionally validate the transitions to ensure that no transition can put us into + // an invalid state + // and so that simple loops don't exist. It is highly recommended to use this; it is done + // once via startup and + // shouldn't add appreciably to the initialization time but it makes the transition system a + // lot more robust. + virtual bool Initialize(IStateContext &inContext, bool inValidateTransitions = true) = 0; + // Bring the state machine to the internal state. + // Returns the list of states for the initial configuration + virtual NVConstDataRef<SStateNode *> Start() = 0; + + // Execute transitions, internal events and external events until nothing is left to + // be done. + virtual NVConstDataRef<SStateNode *> Execute() = 0; + + virtual bool IsRunning() const = 0; + virtual bool EventsPending() const = 0; + + // Queue an event with an optional delay (0 means no delay). + // If the event has a delay then it can optionally be cancelled. + virtual void QueueEvent(const char8_t *inEventName, QT3DSU64 inDelay, + CRegisteredString inCancelId, bool inIsExternal = true) = 0; + virtual void QueueEvent(TEventPtr inEvent, QT3DSU64 inDelay, CRegisteredString inCancelId, + bool inIsExternal = true) = 0; + virtual void QueueEvent(TEventPtr inEvent, bool inIsExternal = true) = 0; + virtual void QueueEvent(const char8_t *inEventName, bool inIsExternal = true) = 0; + + // Cancel an event with a particular id. Only cancels delayed events that have not fired + // yet. + virtual void CancelEvent(CRegisteredString inCancelId) = 0; + + // When the connection gets destroyed, the hander will get no more events. + virtual TSignalConnectionPtr + RegisterEventHandler(IStateInterpreterEventHandler &inHandler) = 0; + + virtual debugger::IStateMachineDebugInterface &GetDebugInterface() = 0; + + virtual NVFoundationBase &GetFoundation() = 0; + + virtual IScriptContext &GetScriptContext() = 0; + + virtual IStateContext *GetStateContext() = 0; + + // The only code that needs to happen in the state system is the code that executes a + // condition. + static IStateInterpreter &Create(NVFoundationBase &inFnd, IStringTable &inStrTable, + IScriptContext &inScriptContext, + IExecutionContext &inExecutionContext); + }; +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateScriptContext.h b/src/Runtime/Source/stateapplication/Qt3DSStateScriptContext.h new file mode 100644 index 00000000..110770a1 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateScriptContext.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_SCRIPT_CONTEXT_H +#define QT3DS_STATE_SCRIPT_CONTEXT_H +#pragma once +#include "Qt3DSState.h" +#include "Qt3DSStateTypes.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/Qt3DSOption.h" +#include "foundation/StringTable.h" + +namespace qt3ds { +namespace state { + + class IScriptEvent : public IEvent + { + public: + // returns true on success, false if error.execution should be signaled. + virtual bool SetParam(CRegisteredString inName, const char8_t *inExpression) = 0; + // Sets the expression as a string + virtual bool SetParamStr(CRegisteredString inName, const char8_t *inStr) = 0; + virtual bool SetDataExpr(const char8_t *inExpression) = 0; + virtual bool SetDataStr(const char8_t *inStr) = 0; + }; + + struct SScriptExecutionResult + { + const char8_t *m_Result; + const char8_t *m_Error; + SScriptExecutionResult(const char8_t *inData, const char8_t *inError) + : m_Result(inData) + , m_Error(inError) + { + } + SScriptExecutionResult() + : m_Result("") + , m_Error("") + { + } + bool Valid() const { return m_Error == NULL || *m_Error == 0; } + const char8_t *Result() const + { + QT3DS_ASSERT(Valid()); + return m_Result; + } + const char8_t *Error() const + { + QT3DS_ASSERT(!Valid()); + return m_Error; + } + }; + + class IScriptContext : public NVRefCounted + { + protected: + virtual ~IScriptContext() {} + public: + // Should functions returning options return othing + virtual Option<bool> ExecuteCondition(const char8_t *inCond) = 0; + // Used for logging. + // Error and result are good until next call into the script context. + virtual SScriptExecutionResult ExecuteExpressionToString(const char8_t *inExpr) = 0; + + // If return value is false, error is signaled with GetErrorInfo. + virtual bool Assign(const char8_t *inVariable, const char8_t *inExpr) = 0; + // Assign a string to this variable location. + virtual void AssignStr(const char8_t *inVariable, const char8_t *inStr) = 0; + + // If return value is false, error is signaled via GetErrorInfo. + // The actual pointer and content used for inscript is expected to not change during + // execution of the system. + // it is legal for contexts to cache information based off the script pointer value. + virtual bool ExecuteScript(const char8_t *inScript) = 0; + // Always return 1 result + virtual int ExecuteStr(const char8_t *inScript, bool withRet) = 0; + + // Return true on success, false on failure, and Empty on error. + virtual Option<bool> BeginForeach(const char8_t *inArray, const char8_t *inItem, + const char8_t *inIdxVar = "") = 0; + virtual Option<bool> NextForeach(const char8_t *inItem, const char8_t *inIdxVar = "") = 0; + virtual void CancelForeach() = 0; + + virtual void SetCurrentEvent(TEventPtr inEvent) = 0; + virtual void ClearCurrentEvent() = 0; + // Create an event we can attach extra data do. + virtual IScriptEvent *CreateScriptEvent(CRegisteredString inName) = 0; + + virtual const char8_t *GetErrorInfo() = 0; + + virtual void SetInterpreter(IStateInterpreter &inInterpreter) = 0; + + virtual CRegisteredString GetContextType() { return CRegisteredString(); } + + // Dumps a differential state from the last time someone asked. This is a debug interface; + // not meant to be used + // by multiple listeners concurrently. + virtual void DumpState(debugger::IScriptStateListener &inListener) = 0; + }; +} +} +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateSharedImpl.h b/src/Runtime/Source/stateapplication/Qt3DSStateSharedImpl.h new file mode 100644 index 00000000..0b82cc3c --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateSharedImpl.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_SHARED_IMPL_H +#define QT3DS_STATE_SHARED_IMPL_H +#include "Qt3DSState.h" +#include "foundation/Utils.h" + +namespace qt3ds { +namespace state { + namespace impl { + + inline bool NameMatchesInternal(const char8_t *inTransEvent, QT3DSU32 transLen, + const char8_t *inEventName, QT3DSU32 eventLen) + { + QT3DSU32 idx, end; + // Empty loop intentional to find first nonmatching character + for (idx = 0, end = NVMin(transLen, eventLen); + idx < end && inTransEvent[idx] == inEventName[idx]; ++idx) { + } + // Note that in this case we point to the first nonmatching character which may be off + // the end of either + // eventStr or transStr + bool match = false; + if (idx == transLen) { + if (idx == eventLen) + match = true; + else if (inEventName[idx] == '.') + match = true; + else if (inTransEvent[idx - 1] == '.') + match = true; + } else if (idx == eventLen) { + if ((transLen - idx) == 1) + match = inTransEvent[idx] == '*' || inTransEvent[idx] == '.'; + + else if ((transLen - idx) == 2) + match = inTransEvent[idx] == '.' && inTransEvent[idx + 1] == '*'; + } else { + if (inTransEvent[idx] == '*') + match = true; + } + return match; + } + + inline const char8_t *FindNextNonSpace(const char8_t *inPtr) + { + for (; *inPtr == ' '; ++inPtr) { + } + return inPtr; + } + + inline const char8_t *FindNextSpaceOrNull(const char8_t *inPtr) + { + for (; *inPtr && *inPtr != ' '; ++inPtr) { + } + return inPtr; + } + + inline bool NameMatches(const char8_t *inTransEvent, const char8_t *inEventName) + { + inTransEvent = nonNull(inTransEvent); + inEventName = nonNull(inEventName); + + QT3DSU32 transLen = StrLen(inTransEvent); + + QT3DSU32 eventLen = StrLen(inEventName); + if (transLen == 0) { + QT3DS_ASSERT(false); + return false; + } + if (eventLen == 0) { + QT3DS_ASSERT(false); + return false; + } + + for (inTransEvent = FindNextNonSpace(inTransEvent); inTransEvent && *inTransEvent; + inTransEvent = FindNextNonSpace(inTransEvent)) { + const char8_t *end = FindNextSpaceOrNull(inTransEvent); + QT3DSU32 len = (QT3DSU32)(end - inTransEvent); + + if (len && NameMatchesInternal(inTransEvent, len, inEventName, eventLen)) + return true; + inTransEvent = end; + } + + return false; + } + } +} +} +#endif
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateSignalConnection.h b/src/Runtime/Source/stateapplication/Qt3DSStateSignalConnection.h new file mode 100644 index 00000000..5bfa59ad --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateSignalConnection.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_SIGNAL_CONNECTION_H +#define QT3DS_STATE_SIGNAL_CONNECTION_H +#pragma once +#include "Qt3DSState.h" +#include "foundation/Qt3DSRefCounted.h" +namespace qt3ds { +namespace state { + + class IStateSignalConnection : public NVRefCounted + { + protected: + virtual ~IStateSignalConnection() {} + public: + }; + + typedef NVScopedRefCounted<IStateSignalConnection> TSignalConnectionPtr; +} +} + +#endif
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateTypes.h b/src/Runtime/Source/stateapplication/Qt3DSStateTypes.h new file mode 100644 index 00000000..41e759fb --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateTypes.h @@ -0,0 +1,720 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_STATE_TYPES_H +#define QT3DS_STATE_TYPES_H +#include "Qt3DSState.h" +#include "foundation/StringTable.h" +#include "foundation/Qt3DSInvasiveLinkedList.h" +#include "foundation/Qt3DSFlags.h" +#include "foundation/Qt3DSDataRef.h" +#include "foundation/Qt3DSContainers.h" +#include "foundation/StringTable.h" +#include "foundation/TaggedPointer.h" +#include "foundation/Qt3DSVec2.h" +#include "foundation/Qt3DSVec3.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Utils.h" +#include "Qt3DSStateIdValue.h" + +namespace qt3ds { +namespace state { + + // Link list definitions. + DEFINE_INVASIVE_LIST(StateNode); + DEFINE_INVASIVE_SINGLE_LIST(OnEntry); + DEFINE_INVASIVE_SINGLE_LIST(OnExit); + DEFINE_INVASIVE_SINGLE_LIST(DataModel); + DEFINE_INVASIVE_LIST(Invoke); + + // Externally defined objects - These objects are defined by the implementation of the + // interpreter and the execution context. They are not used and cannot be manipulated by + // core state types. + + // Standard content defined in another file so as to not clutter up the core state definitions. + struct SExecutableContent; + DEFINE_INVASIVE_LIST(ExecutableContent); + struct SScript; + struct SDataModel; + + // Defined by the execution context; These objects can be defined by the implementation of the + // scripting + // system. + struct STransitionCondition; + + // Defined by the interpreter. Really just a placeholder so the interpreter can cache + // item-specific data on an object. + struct SInterpreterData; + + // We *have* to keep some subset of the data in document order because the algorithms + // defined in the specification rely on this. + struct StateNodeTypes + { + enum Enum { + UnknownType, + State, + Parallel, + Transition, + Final, + SCXML, + History, + }; + static bool CanHaveChildren(StateNodeTypes::Enum val) + { + return val == State || val == Parallel || val == SCXML; + } + static bool CanHaveTransitions(StateNodeTypes::Enum val) + { + return val == State || val == Parallel; + } + static bool IsStateType(StateNodeTypes::Enum val) + { + return CanHaveChildren(val) || val == Final; + } + static bool IsTransitionType(StateNodeTypes::Enum val) { return val == Transition; } + static bool CanHaveInitializeNode(StateNodeTypes::Enum val) { return val == State; } + static bool IsAtomicType(StateNodeTypes::Enum val) { return val == State || val == Final; } + }; + + struct SState; + struct SParallel; + struct STransition; + struct SFinal; + struct SSCXML; + struct SHistory; + + template <typename TDataType> + struct SStateNodeTypeMap + { + static StateNodeTypes::Enum GetStateNodeType() { return StateNodeTypes::UnknownType; } + }; + + template <> + struct SStateNodeTypeMap<SState> + { + static StateNodeTypes::Enum GetStateNodeType() { return StateNodeTypes::State; } + }; + template <> + struct SStateNodeTypeMap<SParallel> + { + static StateNodeTypes::Enum GetStateNodeType() { return StateNodeTypes::Parallel; } + }; + template <> + struct SStateNodeTypeMap<STransition> + { + static StateNodeTypes::Enum GetStateNodeType() { return StateNodeTypes::Transition; } + }; + template <> + struct SStateNodeTypeMap<SFinal> + { + static StateNodeTypes::Enum GetStateNodeType() { return StateNodeTypes::Final; } + }; + template <> + struct SStateNodeTypeMap<SSCXML> + { + static StateNodeTypes::Enum GetStateNodeType() { return StateNodeTypes::SCXML; } + }; + template <> + struct SStateNodeTypeMap<SHistory> + { + static StateNodeTypes::Enum GetStateNodeType() { return StateNodeTypes::History; } + }; + // Some editor info has to be contained on the state node. Description I am fine with eliding + // but + // most of the other information needs to sit on the nodes themselves. + struct StateNodeFlagValues + { + enum Enum { + HasNothing = 0, + HasPosition = 1, + HasDimension = 1 << 1, + HasColor = 1 << 2, + HasEndPosition = 1 << 3, + }; + }; + + struct SStateNodeFlags : NVFlags<StateNodeFlagValues::Enum, QT3DSU32> + { + bool HasPosition() const { return this->operator&(StateNodeFlagValues::HasPosition); } + void SetHasPosition(bool val) { clearOrSet(val, StateNodeFlagValues::HasPosition); } + bool HasDimension() const { return this->operator&(StateNodeFlagValues::HasDimension); } + void SetHasDimension(bool val) { clearOrSet(val, StateNodeFlagValues::HasDimension); } + bool HasColor() const { return this->operator&(StateNodeFlagValues::HasColor); } + void SetHasColor(bool val) { clearOrSet(val, StateNodeFlagValues::HasColor); } + bool HasEndPosition() const { return this->operator&(StateNodeFlagValues::HasEndPosition); } + void SetHasEndPosition(bool val) { clearOrSet(val, StateNodeFlagValues::HasEndPosition); } + }; + + struct SStateNode + { + const StateNodeTypes::Enum m_Type; + CRegisteredString m_Id; + SStateNode *m_Parent; + SStateNode *m_NextSibling; + SStateNode *m_PreviousSibling; + SInterpreterData *m_InterpreterData; + SStateNodeFlags m_StateNodeFlags; + QT3DSVec2 m_Position; + QT3DSVec2 m_Dimension; + QT3DSVec3 m_Color; + + SStateNode(StateNodeTypes::Enum inType) + : m_Type(inType) + , m_Parent(NULL) + , m_NextSibling(NULL) + , m_PreviousSibling(NULL) + , m_InterpreterData(NULL) + { + } + + template <typename TDataType> + TDataType *CastTo() + { + if (m_Type == SStateNodeTypeMap<TDataType>::GetStateNodeType()) + return static_cast<TDataType *>(this); + QT3DS_ASSERT(false); + return NULL; + } + + template <typename TDataType> + const TDataType *CastTo() const + { + if (m_Type == SStateNodeTypeMap<TDataType>::GetStateNodeType()) + return static_cast<const TDataType *>(this); + QT3DS_ASSERT(false); + return NULL; + } + // Helper functions that take care of the drama around getting the various + // shared properties + bool IsCompound() const; + bool IsAtomic() const; + void AppendChild(SStateNode &inChild); + TOnEntryList *GetOnEntryList(); + TOnExitList *GetOnExitList(); + STransition *GetInitialTransition(); + const char8_t *GetInitialExpression(); + TStateNodeList *GetChildren(); + SDataModel *GetDataModel(); + Option<QT3DSVec2> GetPosition() const + { + if (m_StateNodeFlags.HasPosition()) + return m_Position; + return Empty(); + } + void SetPosition(const Option<QT3DSVec2> &pos) + { + if (pos.hasValue()) { + m_Position = *pos; + } + m_StateNodeFlags.SetHasPosition(pos.hasValue()); + } + Option<QT3DSVec2> GetDimension() const + { + if (m_StateNodeFlags.HasDimension()) + return m_Dimension; + return Empty(); + } + void SetDimension(const Option<QT3DSVec2> &pos) + { + if (pos.hasValue()) { + m_Dimension = *pos; + } + m_StateNodeFlags.SetHasDimension(pos.hasValue()); + } + Option<QT3DSVec3> GetColor() const + { + if (m_StateNodeFlags.HasColor()) + return m_Color; + return Empty(); + } + void SetColor(const Option<QT3DSVec3> &pos) + { + if (pos.hasValue()) { + m_Color = *pos; + } + m_StateNodeFlags.SetHasColor(pos.hasValue()); + } + }; + + struct SEntryExitBase : public SStateNode + { + TOnEntryList m_OnEntry; + TOnExitList m_OnExit; + SEntryExitBase(StateNodeTypes::Enum inType) + : SStateNode(inType) + { + } + }; + + // State and parallel objects can have states as children. Nothing aside from + // SCXML can have this. + struct SStateParallelBase : public SEntryExitBase + { + TStateNodeList m_Children; + SDataModel *m_DataModel; + TInvokeList m_InvokeList; + + SStateParallelBase(StateNodeTypes::Enum inType) + : SEntryExitBase(inType) + , m_DataModel(NULL) + { + } + + void RemoveChild(SStateNode &inChild) + { + QT3DS_ASSERT(inChild.m_Parent == this); + m_Children.remove(inChild); + inChild.m_Parent = NULL; + } + + void AppendChild(SStateNode &inChild, SStateNode *inLoc = NULL) + { + if (inChild.m_Parent != NULL) + static_cast<SStateParallelBase *>(inChild.m_Parent)->RemoveChild(inChild); + if (inLoc) + m_Children.insert_after(*inLoc, inChild); + else + m_Children.push_back(inChild); + inChild.m_Parent = this; + } + + void PrependChild(SStateNode &inChild, SStateNode *inLoc = NULL) + { + if (inChild.m_Parent != NULL) + static_cast<SStateParallelBase *>(inChild.m_Parent)->RemoveChild(inChild); + if (inLoc) + m_Children.insert_before(*inLoc, inChild); + else + m_Children.push_front(inChild); + inChild.m_Parent = this; + } + + bool IsAtomic() const + { + for (TStateNodeList::const_iterator iter = m_Children.begin(), end = m_Children.end(); + iter != end; ++iter) { + if (iter->m_Type != StateNodeTypes::Transition) + return false; + } + return true; + } + bool IsCompound() const { return !IsAtomic(); } + }; + + struct SCXMLFlagValues + { + enum Enum { + Late = 1, + }; + }; + + struct SSCXMLFlags : public NVFlags<SCXMLFlagValues::Enum, QT3DSU32> + { + SSCXMLFlags() {} + + void SetLateBinding(bool inValue) { clearOrSet(inValue, SCXMLFlagValues::Late); } + bool IsLateBinding() const { return this->operator&(SCXMLFlagValues::Late); } + }; + + // Begin standard object definitions + + struct SSCXML : public SStateNode + { + STransition *m_Initial; + CRegisteredString m_Name; + TStateNodeList m_Children; + SScript *m_Script; + SSCXMLFlags m_Flags; + SDataModel *m_DataModel; + QT3DSI32 m_Version; + const char8_t *m_Filename; + const char8_t *m_InitialExpr; + + static QT3DSI32 GetCurrentVersion() { return 1; } + SSCXML() + : SStateNode(StateNodeTypes::SCXML) + , m_Initial(NULL) + , m_Script(NULL) + , m_DataModel(NULL) + , m_Version(GetCurrentVersion()) + , m_InitialExpr(NULL) + { + } + + void RemoveChild(SStateNode &inChild) + { + QT3DS_ASSERT(inChild.m_Parent == this); + m_Children.remove(inChild); + inChild.m_Parent = NULL; + } + + void AppendChild(SStateNode &inChild, SStateNode *inLoc = NULL) + { + if (inChild.m_Parent != NULL) + static_cast<SStateParallelBase *>(inChild.m_Parent)->RemoveChild(inChild); + if (inLoc) + m_Children.insert_after(*inLoc, inChild); + else + m_Children.push_back(inChild); + inChild.m_Parent = this; + } + }; + + struct SState : public SStateParallelBase + { + // transition, state, parallel, and final are all handed by SStateNode + STransition *m_Initial; + const char8_t *m_InitialExpr; + + SState() + : SStateParallelBase(StateNodeTypes::State) + , m_Initial(NULL) + , m_InitialExpr(NULL) + { + } + }; + + struct SParallel : public SStateParallelBase + { + SParallel() + : SStateParallelBase(StateNodeTypes::Parallel) + { + } + }; + + struct TransitionFlagValues + { + enum Enum { + Internal = 1, + }; + }; + + struct STransitionFlags : public NVFlags<TransitionFlagValues::Enum, QT3DSU32> + { + STransitionFlags() {} + + void SetInternal(bool inValue) { clearOrSet(inValue, TransitionFlagValues::Internal); } + bool IsInternal() const { return this->operator&(TransitionFlagValues::Internal); } + }; + + struct STransition : public SStateNode + { + CRegisteredString m_Event; + const char8_t *m_Condition; + NVConstDataRef<SStateNode *> m_Target; + STransitionFlags m_Flags; + TExecutableContentList m_ExecutableContent; + NVConstDataRef<QT3DSVec2> m_Path; + // If we have multiple end targets, on order to lay them out we need a branch point + // along with the number of control points in each target. The first index points to the + // branch point in the path vector, then next index tells the number of control points + // for the first end point, etc. + NVConstDataRef<QT3DSU32> m_PathIndexes; + QT3DSVec2 m_EndPosition; + // m_Source of the transition is its parent + + STransition() + : SStateNode(StateNodeTypes::Transition) + , m_Condition(NULL) + , m_EndPosition(0, 0) + { + } + + SStateNode *GetSource() { return m_Parent; } + + Option<QT3DSVec2> GetEndPosition() const + { + if (m_StateNodeFlags.HasEndPosition()) + return m_EndPosition; + return Empty(); + } + void SetEndPosition(const Option<QT3DSVec2> &pos) + { + if (pos.hasValue()) { + m_EndPosition = *pos; + } + m_StateNodeFlags.SetHasEndPosition(pos.hasValue()); + } + }; + + // TODO: DoneData + struct SFinal : public SEntryExitBase + { + SFinal() + : SEntryExitBase(StateNodeTypes::Final) + { + } + }; + + struct HistoryFlagValues + { + enum Enum { + Deep = 1, + }; + }; + + struct SHistoryFlags : public NVFlags<HistoryFlagValues::Enum, QT3DSU32> + { + SHistoryFlags() {} + + void SetDeep(bool inValue) { clearOrSet(inValue, HistoryFlagValues::Deep); } + bool IsDeep() const { return this->operator&(HistoryFlagValues::Deep); } + }; + + struct SHistory : public SStateNode + { + SHistoryFlags m_Flags; + qt3ds::foundation::STaggedPointer m_UserData; + STransition *m_Transition; + + SHistory() + : SStateNode(StateNodeTypes::History) + , m_Transition(NULL) + { + } + }; + + struct SOnEntry + { + SOnEntry *m_NextSibling; + TExecutableContentList m_ExecutableContent; + qt3ds::foundation::STaggedPointer m_UserData; + SOnEntry() + : m_NextSibling(NULL) + { + } + }; + + struct SOnExit + { + SOnExit *m_NextSibling; + TExecutableContentList m_ExecutableContent; + qt3ds::foundation::STaggedPointer m_UserData; + SOnExit() + : m_NextSibling(NULL) + { + } + }; + + struct SInvoke + { + SInvoke *m_NextSibling; + SInvoke *m_PreviousSibling; + // Will have either SCXML content or dom content but not both. + SSCXML *m_SCXMLContent; + SDOMElement *m_DOMContent; + SInvoke() + : m_NextSibling(NULL) + , m_PreviousSibling(NULL) + , m_SCXMLContent(NULL) + , m_DOMContent(NULL) + { + } + }; + + // Events, because they created both inside and outside + // the state system by various parties. + class IEvent : public NVRefCounted + { + protected: + virtual ~IEvent() {} + public: + virtual CRegisteredString GetName() const = 0; + // Optional type string for rtti. No string means this is an opaque event + // that cannot be safely cast to any specific event type. + virtual CRegisteredString GetEventType() const { return CRegisteredString(); } + }; + + typedef NVScopedRefCounted<IEvent> TEventPtr; + + struct SIdValue; + + typedef nvhash_map<CRegisteredString, SIdValue> TIDStateMap; + + struct SDOMElementNode + { + SDOMElementNode *m_NextNode; + SDOMElement *m_Element; + SDOMElementNode(SDOMElement *elem = NULL) + : m_NextNode(NULL) + , m_Element(elem) + { + } + }; + + struct SDOMAttributeNode + { + SDOMAttributeNode *m_NextNode; + SDOMAttribute *m_Attribute; + SDOMAttributeNode(SDOMAttribute *inAtt = NULL) + : m_NextNode(NULL) + , m_Attribute(inAtt) + { + } + }; + DEFINE_INVASIVE_SINGLE_LIST(DOMElementNode); + DEFINE_INVASIVE_SINGLE_LIST(DOMAttributeNode); + + inline bool SStateNode::IsCompound() const + { + if (m_Type == StateNodeTypes::SCXML) + return true; + if (m_Type == StateNodeTypes::State) + return !IsAtomic(); + return false; + } + + inline bool SStateNode::IsAtomic() const + { + if (m_Type == StateNodeTypes::SCXML) + return false; + + if (m_Type == StateNodeTypes::State) { + const SState *theState = CastTo<SState>(); + for (TStateNodeList::iterator iter = theState->m_Children.begin(), + end = theState->m_Children.end(); + iter != end; ++iter) { + if (iter->m_Type != StateNodeTypes::Transition + && iter->m_Type != StateNodeTypes::History) + return false; + } + return true; + } else if (m_Type == StateNodeTypes::Final) + return true; + return false; + } + + inline void SStateNode::AppendChild(SStateNode &inChild) + { + if (m_Type == StateNodeTypes::State || m_Type == StateNodeTypes::Parallel) { + static_cast<SStateParallelBase *>(this)->AppendChild(inChild); + } else if (m_Type == StateNodeTypes::SCXML) { + static_cast<SSCXML *>(this)->AppendChild(inChild); + } else { + QT3DS_ASSERT(false); + } + } + + inline TOnEntryList *SStateNode::GetOnEntryList() + { + if (m_Type == StateNodeTypes::State || m_Type == StateNodeTypes::Parallel) + return &static_cast<SStateParallelBase *>(this)->m_OnEntry; + if (m_Type == StateNodeTypes::Final) + return &CastTo<SFinal>()->m_OnEntry; + return NULL; + } + + inline TOnExitList *SStateNode::GetOnExitList() + { + if (m_Type == StateNodeTypes::State || m_Type == StateNodeTypes::Parallel) + return &static_cast<SStateParallelBase *>(this)->m_OnExit; + if (m_Type == StateNodeTypes::Final) + return &CastTo<SFinal>()->m_OnExit; + return NULL; + } + + inline STransition *SStateNode::GetInitialTransition() + { + if (m_Type == StateNodeTypes::State) + return static_cast<SState *>(this)->m_Initial; + if (m_Type == StateNodeTypes::SCXML) + return static_cast<SSCXML *>(this)->m_Initial; + return NULL; + } + + inline const char8_t *SStateNode::GetInitialExpression() + { + if (m_Type == StateNodeTypes::State) + return nonNull(static_cast<SState *>(this)->m_InitialExpr); + if (m_Type == StateNodeTypes::SCXML) + return nonNull(static_cast<SSCXML *>(this)->m_InitialExpr); + return ""; + } + + inline TStateNodeList *SStateNode::GetChildren() + { + if (m_Type == StateNodeTypes::State || m_Type == StateNodeTypes::Parallel) + return &static_cast<SStateParallelBase *>(this)->m_Children; + if (m_Type == StateNodeTypes::SCXML) + return &static_cast<SSCXML *>(this)->m_Children; + return NULL; + } + + inline SDataModel *SStateNode::GetDataModel() + { + if (m_Type == StateNodeTypes::State || m_Type == StateNodeTypes::Parallel) + return static_cast<SStateParallelBase *>(this)->m_DataModel; + if (m_Type == StateNodeTypes::SCXML) + return static_cast<SSCXML *>(this)->m_DataModel; + return NULL; + } + + IMPLEMENT_INVASIVE_LIST(Invoke, m_PreviousSibling, m_NextSibling); + IMPLEMENT_INVASIVE_LIST(StateNode, m_PreviousSibling, m_NextSibling); + IMPLEMENT_INVASIVE_SINGLE_LIST(OnEntry, m_NextSibling); + IMPLEMENT_INVASIVE_SINGLE_LIST(OnExit, m_NextSibling); + IMPLEMENT_INVASIVE_SINGLE_LIST(DOMElementNode, m_NextNode); + IMPLEMENT_INVASIVE_SINGLE_LIST(DOMAttributeNode, m_NextNode); + + DEFINE_INVASIVE_SINGLE_LIST(Data); + + struct SDataModel + { + CRegisteredString m_Id; + const char8_t *m_Source; + const char8_t *m_Expression; + TDataList m_Data; + + SDataModel() + : m_Source(NULL) + , m_Expression(NULL) + { + } + }; + + struct SData + { + CRegisteredString m_Id; + const char8_t *m_Source; + const char8_t *m_Expression; + SData *m_NextSibling; + SData() + : m_Source(NULL) + , m_Expression(NULL) + , m_NextSibling(NULL) + { + } + }; + + IMPLEMENT_INVASIVE_SINGLE_LIST(Data, m_NextSibling); +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContext.cpp b/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContext.cpp new file mode 100644 index 00000000..62413bae --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContext.cpp @@ -0,0 +1,626 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateVisualBindingContext.h" + +#include "Qt3DSStateVisualBindingContextValues.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/AutoDeallocatorAllocator.h" +#include "EASTL/string.h" +#include "EASTL/hash_map.h" +#include "Qt3DSStateInterpreter.h" +#include "Qt3DSStateContext.h" +#include "foundation/XML.h" +#include "foundation/FileTools.h" +#include "foundation/Qt3DSInvasiveLinkedList.h" +#include "foundation/SerializationTypes.h" +#include "foundation/StringConversionImpl.h" + +using namespace qt3ds::state; + +namespace { + +struct SStateEventKey +{ + InterpreterEventTypes::Enum m_Event; + CRegisteredString m_Id; + SStateEventKey(InterpreterEventTypes::Enum inEvt, CRegisteredString inId) + : m_Event(inEvt) + , m_Id(inId) + { + } + bool operator==(const SStateEventKey &inOther) const + { + return m_Event == inOther.m_Event && m_Id == inOther.m_Id; + } +}; +} +namespace eastl { +template <> +struct hash<SStateEventKey> +{ + size_t operator()(const SStateEventKey &inKey) const + { + return hash<int>()((int)inKey.m_Event) + ^ hash<qt3ds::foundation::CRegisteredString>()(inKey.m_Id); + } +}; +} + +namespace { +struct SVisualStateCommandNode +{ + SVisualStateCommand m_Command; + SVisualStateCommandNode *m_NextSibling; + SVisualStateCommandNode(const SVisualStateCommand &inCommand = SVisualStateCommand()) + : m_Command(inCommand) + , m_NextSibling(NULL) + { + } +}; + +DEFINE_INVASIVE_SINGLE_LIST(VisualStateCommandNode); +IMPLEMENT_INVASIVE_SINGLE_LIST(VisualStateCommandNode, m_NextSibling); + +typedef TVisualStateCommandNodeList TCommandList; + +// Apparently eastl::hash_multimap isn't order preserving for items. +typedef eastl::hash_map<SStateEventKey, TCommandList, eastl::hash<SStateEventKey>, + eastl::equal_to<SStateEventKey>, ForwardingAllocator> + TStateEventCommandMap; + +struct SStateMachineSystem : public IStateInterpreterEventHandler, public NVRefCounted +{ + NVAllocatorCallback &m_Allocator; + CRegisteredString m_Path; + CRegisteredString m_Id; + CRegisteredString m_DatamodelFunction; + + NVScopedRefCounted<IStringTable> m_StringTable; + NVScopedRefCounted<IStateInterpreter> m_Interpreter; + TStateEventCommandMap m_CommandMap; + QT3DSI32 mRefCount; + bool m_Error; + bool m_Running; + + TSignalConnectionPtr m_InterpreterEventConnection; + NVScopedRefCounted<IVisualStateCommandHandler> m_CommandHandler; + NVScopedRefCounted<IVisualStateInterpreterFactory> m_InterpreterFactory; + + SStateMachineSystem(const char8_t *inPath, const char8_t *inId, const char8_t *inFunction, + IStringTable &inStrTable, NVAllocatorCallback &inAlloc) + : m_Allocator(inAlloc) + , m_Path(inStrTable.RegisterStr(inPath)) + , m_Id(inStrTable.RegisterStr(inId)) + , m_DatamodelFunction(inStrTable.RegisterStr(inFunction)) + , m_StringTable(inStrTable) + , m_CommandMap(ForwardingAllocator(inAlloc, "SStateMachineSystem::m_CommandMap")) + , mRefCount(0) + , m_Error(false) + , m_Running(false) + { + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Allocator) + + void Initialize() + { + } + + void Start() + { + if (m_Error || m_Running || !m_Interpreter) + return; + m_Running = true; + m_Interpreter->Start(); + // Event handler must be replaced after interpreter started + m_InterpreterEventConnection = m_Interpreter->RegisterEventHandler(*this); + // Run throw the initial configuration and fire any enter events. This takes care of + // initial states. + NVConstDataRef<SStateNode *> initialConfig = m_Interpreter->GetConfiguration(); + for (QT3DSU32 nodeIdx = 0, nodeEnd = initialConfig.size(); nodeIdx < nodeEnd; ++nodeIdx) { + SStateNode *theNode(initialConfig[nodeIdx]); + OnInterpreterEvent(InterpreterEventTypes::StateEnter, theNode->m_Id); + } + } + + void Update() + { + if (m_Interpreter) { + m_Interpreter->Execute(); + } + } + + void OnInterpreterEvent(InterpreterEventTypes::Enum inEvent, + CRegisteredString inEventId) override + { + if (m_CommandHandler) { + CRegisteredString theId = m_StringTable->RegisterStr(inEventId.c_str()); + SStateEventKey theKey(inEvent, theId); + TStateEventCommandMap::iterator theItem = m_CommandMap.find(theKey); + if (theItem != m_CommandMap.end()) { + for (TCommandList::iterator iter = theItem->second.begin(), + end = theItem->second.end(); + iter != end; ++iter) + m_CommandHandler->Handle(iter->m_Command, m_Interpreter->GetScriptContext()); + } + } + } +}; + +typedef eastl::vector<NVScopedRefCounted<SStateMachineSystem>> TStateMachineList; + +struct SVisualStateContext : public IVisualStateContext +{ + NVFoundationBase &m_Foundation; + SSAutoDeallocatorAllocator m_DataAllocator; + QT3DSI32 mRefCount; + TStateMachineList m_StateMachines; + NVScopedRefCounted<IVisualStateCommandHandler> m_CommandHandler; + NVScopedRefCounted<IVisualStateInterpreterFactory> m_InterpreterFactory; + NVScopedRefCounted<IStringTable> m_StringTable; + nvvector<SElementReference> m_PreparseResults; + + SVisualStateContext(NVFoundationBase &fnd, IStringTable &inStrTable) + : m_Foundation(fnd) + , m_DataAllocator(fnd) + , mRefCount(0) + , m_StringTable(inStrTable) + , m_PreparseResults(fnd.getAllocator(), "SVisualStateContext::m_PreparseResults") + { + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + void LoadStateMachine(const char8_t *id, const char8_t *inRelativePath, + const char8_t *inDatamodelFunction) override + { + SStateMachineSystem *theSystem = QT3DS_NEW(m_Foundation.getAllocator(), SStateMachineSystem)( + inRelativePath, id, inDatamodelFunction, *m_StringTable, m_Foundation.getAllocator()); + theSystem->m_CommandHandler = m_CommandHandler; + theSystem->m_InterpreterFactory = m_InterpreterFactory; + m_StateMachines.push_back(theSystem); + } + + CRegisteredString ParseStrAtt(IDOMReader &inReader, const char8_t *attName) + { + const char8_t *temp = ""; + if (inReader.UnregisteredAtt(attName, temp)) { + return m_StringTable->RegisterStr(temp); + } + return CRegisteredString(); + } + + void PreparseExecutableContent(IDOMReader &inReader) + { + for (bool setatt = inReader.MoveToFirstChild(); setatt; + setatt = inReader.MoveToNextSibling()) { + IDOMReader::Scope __commandScope(inReader); + const char8_t *elemName = inReader.GetElementName(); + + SElementReference theReference; + if (AreEqual(elemName, "goto-slide")) { + theReference = SElementReference(ParseStrAtt(inReader, "element")); + } else if (AreEqual(elemName, "call")) { + theReference = SElementReference(ParseStrAtt(inReader, "element")); + } else if (AreEqual(elemName, "set-attribute")) { + theReference = SElementReference(ParseStrAtt(inReader, "element"), + ParseStrAtt(inReader, "attribute")); + } else if (AreEqual(elemName, "fire-event")) { + theReference = SElementReference(ParseStrAtt(inReader, "element")); + } else if (AreEqual(elemName, "set-presentation")) { + } else { + qCCritical(INVALID_PARAMETER, "Unrecognized child in an enter/exit node: %s", elemName); + } + if (theReference.m_ElementPath.IsValid()) { + m_PreparseResults.push_back(theReference); + } + } + } + + NVConstDataRef<SElementReference> PreParseDocument(IDOMReader &inReader) override + { + m_PreparseResults.clear(); + for (bool success = inReader.MoveToFirstChild("statemachine"); success; + success = inReader.MoveToNextSibling("statemachine")) { + IDOMReader::Scope __readerScope(inReader); + if (inReader.MoveToFirstChild("visual-states")) { + IDOMReader::Scope __bindingsScope(inReader); + for (bool success = inReader.MoveToFirstChild(); success; + success = inReader.MoveToNextSibling()) { + IDOMReader::Scope __stateScope(inReader); + if (AreEqual(inReader.GetElementName().c_str(), "transition")) { + PreparseExecutableContent(inReader); + } else { + for (bool enterExitSuccess = inReader.MoveToFirstChild(); enterExitSuccess; + enterExitSuccess = inReader.MoveToNextSibling()) { + IDOMReader::Scope __enterExitScope(inReader); + PreparseExecutableContent(inReader); + } + } + } + } + } + return m_PreparseResults; + } + + void ParseGotoSlideData(IDOMReader &inReader, SGotoSlideData &inData) + { + const char *tempData; + if (inReader.UnregisteredAtt("direction", tempData) && AreEqual(tempData, "reverse")) + inData.m_Reverse = true; + + if (inReader.UnregisteredAtt("mode", tempData)) { + if (AreEqual(tempData, "stopatend")) { + inData.m_Mode = SlidePlaybackModes::StopAtEnd; + inData.m_PlaythroughTo = CRegisteredString(); + } else if (AreEqual(tempData, "looping")) + inData.m_Mode = SlidePlaybackModes::Looping; + else if (AreEqual(tempData, "pingpong")) + inData.m_Mode = SlidePlaybackModes::PingPong; + else if (AreEqual(tempData, "ping")) + inData.m_Mode = SlidePlaybackModes::Ping; + else if (AreEqual(tempData, "playthrough")) { + if (!inReader.UnregisteredAtt("playthroughto", tempData) || isTrivial(tempData)) { + qCCritical(INVALID_OPERATION, "Goto slide command has playthough " + "mode but no playthroughto attribute; mode will be ignored"); + } else { + inData.m_PlaythroughTo = m_StringTable->RegisterStr(tempData); + } + + if (inData.m_PlaythroughTo.hasValue()) + inData.m_Mode = SlidePlaybackModes::StopAtEnd; + } + } + + if (inReader.UnregisteredAtt("state", tempData)) + inData.m_Paused = AreEqual(tempData, "pause"); + + if (inReader.UnregisteredAtt("rate", tempData)) { + QT3DSF32 temp = 1.0f; + if (StringConversion<QT3DSF32>().StrTo(tempData, temp)) + inData.m_Rate = temp; + } + + if (inReader.UnregisteredAtt("time", tempData)) { + QT3DSF32 temp = 0.0f; + if (StringConversion<QT3DSF32>().StrTo(tempData, temp)) + inData.m_StartTime = static_cast<QT3DSU32>(NVMax(0.0f, temp) * 1000.0f + .5f); + } + } + + void ParseExecutableContent(IDOMReader &inReader, CRegisteredString &inStateId, + InterpreterEventTypes::Enum inEvent, + TStateEventCommandMap &inCommandMap) + { + for (bool success = inReader.MoveToFirstChild(); success; + success = inReader.MoveToNextSibling()) { + IDOMReader::Scope __itemScope(inReader); + const char8_t *elemName = inReader.GetElementName(); + SVisualStateCommand theCommand; + if (AreEqual(elemName, "goto-slide")) { + const char8_t *rel; + if (inReader.UnregisteredAtt("rel", rel)) { + const char8_t *wrap; + inReader.UnregisteredAtt("wrap", wrap); + SGotoSlideRelative::Enum direction = SGotoSlideRelative::Error; + if (AreEqual(rel, "next")) + direction = SGotoSlideRelative::Next; + else if (AreEqual(rel, "previous")) + direction = SGotoSlideRelative::Previous; + else { + qCCritical(INVALID_OPERATION, "Goto slide relative has invalid " + "attribute (neither 'next' nor 'previous')"); + } + bool doWrap = AreEqual(wrap, "true") ? true : false; + + SGotoSlideRelative theCommandData(ParseStrAtt(inReader, "element"), direction, + doWrap); + ParseGotoSlideData(inReader, theCommandData.m_GotoSlideData); + theCommand = SVisualStateCommand(theCommandData); + } else { + SGotoSlide theCommandData(ParseStrAtt(inReader, "element"), + ParseStrAtt(inReader, "slide")); + ParseGotoSlideData(inReader, theCommandData.m_GotoSlideData); + theCommand = SVisualStateCommand(theCommandData); + } + } else if (AreEqual(elemName, "call")) { + theCommand = SVisualStateCommand(SCallFunction(ParseStrAtt(inReader, "element"), + ParseStrAtt(inReader, "handler"), + ParseStrAtt(inReader, "arguments"))); + } else if (AreEqual(elemName, "set-attribute")) { + theCommand = SVisualStateCommand(SSetAttribute(ParseStrAtt(inReader, "element"), + ParseStrAtt(inReader, "attribute"), + ParseStrAtt(inReader, "value"))); + } else if (AreEqual(elemName, "fire-event")) { + theCommand = SVisualStateCommand( + SFireEvent(ParseStrAtt(inReader, "element"), ParseStrAtt(inReader, "event"))); + } else if (AreEqual(elemName, "set-presentation")) { + theCommand = SVisualStateCommand(SPresentationAttribute( + ParseStrAtt(inReader, "ref"), ParseStrAtt(inReader, "attribute"), + ParseStrAtt(inReader, "value"))); + } else if (AreEqual(elemName, "play-sound")) { + theCommand = SVisualStateCommand(SPlaySound(ParseStrAtt(inReader, "file"))); + } else { + qCCritical(INVALID_PARAMETER, "Unrecognized child in an enter/exit node: %s", elemName); + } + if (theCommand.getType() != VisualStateCommandTypes::NoVisualStateCommand) { + TCommandList &theList = inCommandMap + .insert(eastl::make_pair( + SStateEventKey(inEvent, inStateId), TCommandList())) + .first->second; + theList.push_back(*QT3DS_NEW(m_DataAllocator, SVisualStateCommandNode)(theCommand)); + } + } + } + + SStateMachineSystem *FindStateMachine(const char8_t *inId) + { + if (isTrivial(inId)) + return NULL; + if (inId[0] == '#') + ++inId; + for (QT3DSU32 idx = 0, end = m_StateMachines.size(); idx < end; ++idx) + if (AreEqual(m_StateMachines[idx].mPtr->m_Id.c_str(), inId)) + return m_StateMachines[idx].mPtr; + return NULL; + } + + // We have to be careful of string table values coming from the state machine and from the + // reader + // because we don't necessarily share the same string table. + void LoadVisualStateMapping(IDOMReader &inReader) override + { + for (bool success = inReader.MoveToFirstChild("statemachine"); success; + success = inReader.MoveToNextSibling("statemachine")) { + IDOMReader::Scope __readerScope(inReader); + const char8_t *machineId = ""; + if (inReader.UnregisteredAtt("ref", machineId)) { + SStateMachineSystem *theSystem = FindStateMachine(machineId + 1); + if (theSystem == NULL) { + qCCritical(INVALID_OPERATION, "Unknown state machine id: %s", + nonNull(machineId)); + continue; + } + + if (inReader.MoveToFirstChild("visual-states")) { + IDOMReader::Scope __bindingsScope(inReader); + for (bool bindingsSuccess = inReader.MoveToFirstChild(); bindingsSuccess; + bindingsSuccess = inReader.MoveToNextSibling()) { + IDOMReader::Scope __stateScope(inReader); + const char8_t *rawStateId = ""; + inReader.UnregisteredAtt("ref", rawStateId); + if (isTrivial(rawStateId)) + continue; + + if (rawStateId[0] == '#') + ++rawStateId; + CRegisteredString elemName = m_StringTable->RegisterStr(rawStateId); + + if (AreEqual(inReader.GetElementName().c_str(), "transition")) { + ParseExecutableContent(inReader, elemName, + InterpreterEventTypes::Transition, + theSystem->m_CommandMap); + } else { + for (bool stateSuccess = inReader.MoveToFirstChild(); stateSuccess; + stateSuccess = inReader.MoveToNextSibling()) { + IDOMReader::Scope __enterExitScope(inReader); + const char8_t *stateChildName = inReader.GetElementName(); + if (AreEqual(stateChildName, "enter")) { + ParseExecutableContent(inReader, elemName, + InterpreterEventTypes::StateEnter, + theSystem->m_CommandMap); + } else if (AreEqual(stateChildName, "exit")) { + ParseExecutableContent(inReader, elemName, + InterpreterEventTypes::StateExit, + theSystem->m_CommandMap); + } else { + qCCritical(INVALID_PARAMETER, + "Unrecognized child in a visual state bindings state: %s", + stateChildName); + } + } + } + } + } + } else { + qCCritical(INVALID_OPERATION, "visual-state element has no machine attribute"); + } + } + } + + void SetCommandHandler(IVisualStateCommandHandler *inHandler) override + { + m_CommandHandler = inHandler; + for (QT3DSU32 idx = 0, end = m_StateMachines.size(); idx < end; ++idx) + m_StateMachines[idx]->m_CommandHandler = inHandler; + } + + void SetInterpreterFactory(IVisualStateInterpreterFactory *inHandler) override + { + m_InterpreterFactory = inHandler; + for (QT3DSU32 idx = 0, end = m_StateMachines.size(); idx < end; ++idx) + m_StateMachines[idx]->m_InterpreterFactory = inHandler; + } + + void Initialize() override + { + for (QT3DSU32 idx = 0, end = m_StateMachines.size(); idx < end; ++idx) + m_StateMachines[idx]->Initialize(); + } + + void Start() override + { + for (QT3DSU32 idx = 0, end = m_StateMachines.size(); idx < end; ++idx) + m_StateMachines[idx]->Start(); + } + + void Update() override + { + for (QT3DSU32 idx = 0, end = m_StateMachines.size(); idx < end; ++idx) + m_StateMachines[idx]->Update(); + } + + QT3DSU32 CountItems(TCommandList &list) + { + QT3DSU32 retval = 0; + for (TCommandList::iterator iter = list.begin(), end = list.end(); iter != end; ++iter) + ++retval; + return retval; + } + + struct SSaveVisitor + { + const SStrRemapMap &m_RemapMap; + SSaveVisitor(const SStrRemapMap &map) + : m_RemapMap(map) + { + } + template <typename TItemType> + SVisualStateCommand operator()(const TItemType &item) + { + TItemType newItem(item); + newItem.Remap(*this); + return newItem; + } + void Remap(CRegisteredString &inStr) { inStr.Remap(m_RemapMap); } + SVisualStateCommand operator()() { return SVisualStateCommand(); } + }; + + void BinarySave(IOutStream &stream) override + { + qt3ds::foundation::SWriteBuffer theWriteBuffer(m_Foundation.getAllocator(), + "BinarySave::writebuffer"); + // Allocate space for overall size of the data section + theWriteBuffer.writeZeros(4); + theWriteBuffer.write((QT3DSU32)m_StateMachines.size()); + const SStrRemapMap &theRemapMap(m_StringTable->GetRemapMap()); + for (QT3DSU32 idx = 0, end = m_StateMachines.size(); idx < end; ++idx) { + SStateMachineSystem &theSystem = *m_StateMachines[idx]; + CRegisteredString path(theSystem.m_Path); + CRegisteredString id(theSystem.m_Id); + CRegisteredString fn(theSystem.m_DatamodelFunction); + path.Remap(theRemapMap); + id.Remap(theRemapMap); + fn.Remap(theRemapMap); + theWriteBuffer.write(path); + theWriteBuffer.write(id); + theWriteBuffer.write(fn); + theWriteBuffer.write((QT3DSU32)theSystem.m_CommandMap.size()); + for (TStateEventCommandMap::iterator iter = theSystem.m_CommandMap.begin(), + mapEnd = theSystem.m_CommandMap.end(); + iter != mapEnd; ++iter) { + theWriteBuffer.write((QT3DSU32)iter->first.m_Event); + CRegisteredString stateId(iter->first.m_Id); + stateId.Remap(theRemapMap); + theWriteBuffer.write(stateId); + + theWriteBuffer.write(CountItems(iter->second)); + for (TCommandList::iterator cmdIter = iter->second.begin(), + cmdEnd = iter->second.end(); + cmdIter != cmdEnd; ++cmdIter) { + SVisualStateCommand remapped = + cmdIter->m_Command.visit<SVisualStateCommand>(SSaveVisitor(theRemapMap)); + SVisualStateCommandNode theNode(remapped); + theWriteBuffer.write(theNode); + } + } + } + QT3DSU32 totalSize = theWriteBuffer.size(); + QT3DSU32 *data = (QT3DSU32 *)theWriteBuffer.begin(); + data[0] = totalSize - 4; + stream.Write((QT3DSU8 *)data, totalSize); + } + + struct SLoadVisitor + { + NVDataRef<QT3DSU8> m_RemapMap; + SLoadVisitor(const NVDataRef<QT3DSU8> map) + : m_RemapMap(map) + { + } + template <typename TItemType> + void operator()(TItemType &item) + { + item.Remap(*this); + } + + void Remap(CRegisteredString &inStr) { inStr.Remap(m_RemapMap); } + void operator()() {} + }; + + void BinaryLoad(IInStream &stream, NVDataRef<QT3DSU8> inStringTableData) override + { + QT3DSU32 length; + stream.Read(length); + QT3DSU8 *data = (QT3DSU8 *)m_DataAllocator.allocate(length, "Binaryload", __FILE__, __LINE__); + stream.Read(data, length); + + SDataReader theReader(data, data + length); + QT3DSU32 numMachines = theReader.LoadRef<QT3DSU32>(); + m_StateMachines.clear(); + m_StateMachines.reserve(numMachines); + for (QT3DSU32 idx = 0, end = numMachines; idx < end; ++idx) { + CRegisteredString path = theReader.LoadRef<CRegisteredString>(); + CRegisteredString id = theReader.LoadRef<CRegisteredString>(); + CRegisteredString fn = theReader.LoadRef<CRegisteredString>(); + path.Remap(inStringTableData); + id.Remap(inStringTableData); + fn.Remap(inStringTableData); + LoadStateMachine(id, path, fn); + SStateMachineSystem &theSystem(*m_StateMachines.back()); + QT3DSU32 mapSize = theReader.LoadRef<QT3DSU32>(); + for (QT3DSU32 mapIdx = 0; mapIdx < mapSize; ++mapIdx) { + InterpreterEventTypes::Enum evt = + static_cast<InterpreterEventTypes::Enum>(theReader.LoadRef<QT3DSU32>()); + CRegisteredString stateId = theReader.LoadRef<CRegisteredString>(); + stateId.Remap(inStringTableData); + QT3DSU32 numCommands = theReader.LoadRef<QT3DSU32>(); + TCommandList &theList = + theSystem.m_CommandMap + .insert(eastl::make_pair(SStateEventKey(evt, stateId), TCommandList())) + .first->second; + for (QT3DSU32 cmdIdx = 0, cmdEnd = numCommands; cmdIdx < cmdEnd; ++cmdIdx) { + SVisualStateCommandNode *nextNode = theReader.Load<SVisualStateCommandNode>(); + nextNode->m_Command.visit<void>(SLoadVisitor(inStringTableData)); + theList.push_back(*nextNode); + } + } + } + } +}; +} + +IVisualStateContext &IVisualStateContext::Create(NVFoundationBase &inFoundation, + IStringTable &inStrTable) +{ + return *QT3DS_NEW(inFoundation.getAllocator(), SVisualStateContext)(inFoundation, inStrTable); +} diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContext.h b/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContext.h new file mode 100644 index 00000000..192181e4 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContext.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_STATE_VISUAL_BINDING_CONTEXT_H +#define QT3DS_STATE_VISUAL_BINDING_CONTEXT_H +#include "Qt3DSState.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/StringTable.h" +#include "Qt3DSStateVisualBindingContextCommands.h" + +namespace qt3ds { +namespace foundation { + class IDOMReader; + class CStrTableOrDataRef; +} +} + +namespace qt3ds { +namespace state { + + struct SVisualStateCommand; + struct SSetAttribute; + + // Entity responsible for implementing the various visual state commands. + class IVisualStateCommandHandler : public NVRefCounted + { + protected: + virtual ~IVisualStateCommandHandler() {} + public: + virtual void Handle(const SVisualStateCommand &inCommand, + IScriptContext &inScriptContext) = 0; + }; + + class IVisualStateInterpreterFactory : public NVRefCounted + { + protected: + virtual ~IVisualStateInterpreterFactory() {} + }; + + // It is important that the visual state context list the elements it expects to find in a uip + // file + // so that during parse of the uip file, we can generate an error if some element isn't found. + struct SElementReference + { + CRegisteredString m_ElementPath; + CRegisteredString m_Attribute; + SElementReference(CRegisteredString elemPath = CRegisteredString(), + CRegisteredString att = CRegisteredString()) + : m_ElementPath(elemPath) + , m_Attribute(att) + { + } + }; + + class IVisualStateContext : public NVRefCounted + { + protected: + virtual ~IVisualStateContext() {} + public: + // All machines are loaded execute is called on the first update call. + // made after LoadVisualStateMapping + virtual void LoadStateMachine(const char8_t *id, const char8_t *inRelativePath, + const char8_t *inDatamodelFunction) = 0; + virtual void LoadVisualStateMapping(IDOMReader &inReader) = 0; + // We have to pre-parse the xml so we can reference everything in the various presentations + // We run this pass, then during uip file parsing we output errors if things don't match up. + virtual NVConstDataRef<SElementReference> PreParseDocument(IDOMReader &inReader) = 0; + virtual void SetCommandHandler(IVisualStateCommandHandler *inHandler) = 0; + virtual void SetInterpreterFactory(IVisualStateInterpreterFactory *inHandler) = 0; + + // Initialize the state machines. Machines are initialized in order of LoadStateMachine + // calls. + virtual void Initialize() = 0; + virtual void Start() = 0; + + // Initialize the state machines. Machines are updated in order of LoadStateMachine calls. + virtual void Update() = 0; + + // Save out to a format that allows very rapid loading. + virtual void BinarySave(IOutStream &stream) = 0; + virtual void BinaryLoad(IInStream &stream, NVDataRef<QT3DSU8> inStringTableData) = 0; + + static IVisualStateContext &Create(NVFoundationBase &inFoundation, + IStringTable &inStrTable); + }; +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContextCommands.h b/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContextCommands.h new file mode 100644 index 00000000..4d08f695 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContextCommands.h @@ -0,0 +1,348 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_STATE_VISUAL_BINDING_CONTEXT_COMMANDS_H +#define QT3DS_STATE_VISUAL_BINDING_CONTEXT_COMMANDS_H +#include "Qt3DSState.h" +#include "foundation/StringTable.h" + +namespace qt3ds { +namespace state { + + struct VisualStateCommandTypes + { + enum Enum { + NoVisualStateCommand = 0, + GotoSlide, + CallFunction, + SetAttribute, + GotoSlideRelative, + FireEvent, + PresentationAttribute, + PlaySound, + }; + }; + + struct SlidePlaybackModes + { + enum Enum { + StopAtEnd = 0, + Looping, + PingPong, + Ping, + PlaythroughTo, + }; + }; + + struct SGotoSlideData + { + Option<SlidePlaybackModes::Enum> m_Mode; + Option<CRegisteredString> m_PlaythroughTo; + Option<QT3DSU32> m_StartTime; + QT3DSF32 m_Rate; + bool m_Reverse; + Option<bool> m_Paused; + SGotoSlideData() + : m_Rate(1.0f) + , m_Reverse(false) + { + } + }; + + // All the element references in this file need to be absolute, meaning they + // must begin with a presentationid:. The is because the state machine exists at the + // application level where all presentations are pretty much equal and must be referenced + // by id. + + // Go to a particular slide. + + /* + a. If the slide attribute references a slide name not present on the time context an error must + be logged each time the executable would have been run, and the executable ignored. + b. If the time context is already on the slide referenced, no change is made to the time + context. + The slide does not restart. */ + struct SGotoSlide + { + CRegisteredString m_Component; + CRegisteredString m_Slide; + SGotoSlideData m_GotoSlideData; + SGotoSlide(CRegisteredString cmd = CRegisteredString(), + CRegisteredString slide = CRegisteredString()) + : m_Component(cmd) + , m_Slide(slide) + { + } + bool operator==(const SGotoSlide &inOther) const + { + return m_Component == inOther.m_Component && m_Slide == inOther.m_Slide; + } + template <typename TRemapper> + void Remap(TRemapper &inRemapper) + { + inRemapper.Remap(m_Component); + inRemapper.Remap(m_Slide); + } + }; + + /* + 1. A <call.../> executable must have a element="..." attribute that references a behavior + element in one of the presentation assets for the application. + a. If the element attribute is missing, or references an element that cannot be found, + an error must be logged each time the executable would have been run, and the executable + ignored. + b. If the element attribute references an element that is not a behavior then an error + must be logged each time the executable would have been run, and the executable ignored. + 2. A <call.../> executable must have a handler="..." attribute that references a function value + in the table associated with the behavior element. + a. If the handler attribute is missing, or references a value that is not a function + value, an error must be logged each time the executable would have been run, and the executable + ignored. + 3. A <call.../> executable may have an args="..." attribute that specifies an expression to + evaluate. + a. If the result of evaluating this expression is a single value, it is passed as the + first argument to the behavior's function. + b. If the result of evaluating this expression is a table, it is treated as a list that + is unpack'd, the numeric properties sent as arguments in order. + c. If the result of evaluating this expression is an error, an error message must be + logged and the executable ignored. + The function is not invoked with no parameters. + */ + struct SCallFunction + { + CRegisteredString m_Behavior; + CRegisteredString m_Handler; + CRegisteredString m_Arguments; + SCallFunction(CRegisteredString behavior = CRegisteredString(), + CRegisteredString hdler = CRegisteredString(), + CRegisteredString args = CRegisteredString()) + : m_Behavior(behavior) + , m_Handler(hdler) + , m_Arguments(args) + { + } + bool operator==(const SCallFunction &inOther) const + { + return m_Behavior == inOther.m_Behavior && m_Handler == inOther.m_Handler + && m_Arguments == inOther.m_Arguments; + } + template <typename TRemapper> + void Remap(TRemapper &inRemapper) + { + inRemapper.Remap(m_Behavior); + inRemapper.Remap(m_Handler); + inRemapper.Remap(m_Arguments); + } + }; + + /* + 1. A <set-attribute.../> executable must have an element="..." attribute that references an + element in one of the presentation assets for the application. + a. If the element attribute is missing, or references an element that cannot be found, + an error must be logged each time the executable would have been run, and the executable + ignored. + 2. A <set-attribute.../> executable must have an attribute="..." attribute that references an + attribute on the referenced element. + a. If the attribute attribute is missing, or references an attribute not present on the + element, an error must be logged each time the executable would have been run, and the + executable ignored. + 3. A <set-attribute.../> executable must have an value="..." attribute that describes the value + to set. + a. The contents of this attribute are evaluated as an expression and the result used + to set the attribute. + i. If the result of evaluating this expression does not match the type of the + attribute on the element then an error must be logged and the executable ignored. + 4. If a single visual state has both <goto-slide/> and <set-attribute/> executables, and the + slide change affects the same attribute as the set-attribute executable, then the set-attribute + executable must take effect (be applied after the slide change occurs). + In the future we may wish to have the order of this interaction controllable by the + ordering of the executables. + */ + struct SSetAttribute + { + CRegisteredString m_Element; + CRegisteredString m_Attribute; + CRegisteredString m_Value; + SSetAttribute(CRegisteredString elem = CRegisteredString(), + CRegisteredString att = CRegisteredString(), + CRegisteredString val = CRegisteredString()) + : m_Element(elem) + , m_Attribute(att) + , m_Value(val) + { + } + bool operator==(const SSetAttribute &inOther) const + { + return m_Element == inOther.m_Element && m_Attribute == inOther.m_Attribute + && m_Value == inOther.m_Value; + } + + template <typename TRemapper> + void Remap(TRemapper &inRemapper) + { + inRemapper.Remap(m_Element); + inRemapper.Remap(m_Attribute); + inRemapper.Remap(m_Value); + } + }; + + /* + 4. A rel="..." attribute must have either the value next or prev. + a. If the rel attribute has a different value an error must be logged each time the + executable would have been run, and the executable ignored. + b. A value of next causes the time context to go to the next slide. + i. If the time context is at the last slide, and there is no wrap attribute, or + the wrap attribute does not have a value of true, then no change occurs to the time context. + The slide does not restart. + ii. If the time context is at the last slide and there exists a wrap attribute + with a value of true then the time context is taken to the first slide. + c. A value of prev causes the time context to go to the previous slide. + i. If the time context is at the first slide, and there is no wrap attribute, or + the wrap attribute does not have a value of true, then no change occurs to the time context. + The slide does not restart. + ii. If the time context is at the last first and there exists a wrap attribute + with a value of true then the time context is taken to the last slide. + */ + struct SGotoSlideRelative + { + CRegisteredString m_Component; + enum Enum { + Next = 0, + Previous, + Error, + }; + Enum m_Direction; + bool m_Wrap; + SGotoSlideData m_GotoSlideData; + SGotoSlideRelative(CRegisteredString comp = CRegisteredString(), Enum dir = Next, + bool wrap = false) + : m_Component(comp) + , m_Direction(dir) + , m_Wrap(wrap) + { + } + bool operator==(const SGotoSlideRelative &inOther) const + { + return m_Component == inOther.m_Component && m_Direction == inOther.m_Direction + && m_Wrap == inOther.m_Wrap; + } + template <typename TRemapper> + void Remap(TRemapper &inRemapper) + { + inRemapper.Remap(m_Component); + } + }; + + /* + 1. A <fire-event.../> executable must have an element="..." attribute that references an element + in one of the presentation assets for the application. + a. If the element attribute is missing, or references an element that cannot be found, + an error must be logged each time the executable would have been run, and the executable + ignored. + 2. A <fire-event.../> executable must have an event="..." attribute that describes the event + name to fire. + a. If the event attribute is missing an error must be logged each time the executable + would have been run, and the executable ignored. + */ + struct SFireEvent + { + CRegisteredString m_Element; + CRegisteredString m_Event; + SFireEvent(CRegisteredString elem, CRegisteredString evt) + : m_Element(elem) + , m_Event(evt) + { + } + SFireEvent() {} + bool operator==(const SFireEvent &inOther) const + { + return m_Element == inOther.m_Element && m_Event == inOther.m_Event; + } + + template <typename TRemapper> + void Remap(TRemapper &inRemapper) + { + inRemapper.Remap(m_Element); + inRemapper.Remap(m_Event); + } + }; + + struct SPresentationAttribute + { + CRegisteredString m_Presentation; + CRegisteredString m_Attribute; + CRegisteredString m_Value; + SPresentationAttribute(CRegisteredString pres, CRegisteredString att, CRegisteredString val) + : m_Presentation(pres) + , m_Attribute(att) + , m_Value(val) + { + } + SPresentationAttribute() {} + bool operator==(const SPresentationAttribute &inOther) const + { + return m_Presentation == inOther.m_Presentation && m_Attribute == inOther.m_Attribute + && m_Value == inOther.m_Value; + } + + template <typename TRemapper> + void Remap(TRemapper &inRemapper) + { + inRemapper.Remap(m_Presentation); + inRemapper.Remap(m_Attribute); + inRemapper.Remap(m_Value); + } + }; + + struct SPlaySound + { + CRegisteredString m_SoundFilePath; + SPlaySound(CRegisteredString inSoundFilePath) + : m_SoundFilePath(inSoundFilePath) + { + } + SPlaySound() {} + bool operator==(const SPlaySound &inOther) const + { + return m_SoundFilePath == inOther.m_SoundFilePath; + } + + template <typename TRemapper> + void Remap(TRemapper &inRemapper) + { + inRemapper.Remap(m_SoundFilePath); + } + }; + + // defined in UICStateVisualBindingContextValues.h + struct SVisualStateCommand; +} +} +#endif diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContextValues.h b/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContextValues.h new file mode 100644 index 00000000..7bad9b33 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateVisualBindingContextValues.h @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_STATE_VISUAL_BINDING_CONTEXT_VALUES_H +#define QT3DS_STATE_VISUAL_BINDING_CONTEXT_VALUES_H +#include "Qt3DSStateVisualBindingContext.h" +#include "foundation/Qt3DSDiscriminatedUnion.h" +#include "Qt3DSStateVisualBindingContextCommands.h" + +namespace qt3ds { +namespace state { + + template <typename TDataType> + struct SVisualStateCommandTypeMap + { + }; + + template <> + struct SVisualStateCommandTypeMap<SGotoSlide> + { + static VisualStateCommandTypes::Enum GetType() + { + return VisualStateCommandTypes::GotoSlide; + } + }; + template <> + struct SVisualStateCommandTypeMap<SCallFunction> + { + static VisualStateCommandTypes::Enum GetType() + { + return VisualStateCommandTypes::CallFunction; + } + }; + template <> + struct SVisualStateCommandTypeMap<SSetAttribute> + { + static VisualStateCommandTypes::Enum GetType() + { + return VisualStateCommandTypes::SetAttribute; + } + }; + template <> + struct SVisualStateCommandTypeMap<SGotoSlideRelative> + { + static VisualStateCommandTypes::Enum GetType() + { + return VisualStateCommandTypes::GotoSlideRelative; + } + }; + template <> + struct SVisualStateCommandTypeMap<SFireEvent> + { + static VisualStateCommandTypes::Enum GetType() + { + return VisualStateCommandTypes::FireEvent; + } + }; + template <> + struct SVisualStateCommandTypeMap<SPresentationAttribute> + { + static VisualStateCommandTypes::Enum GetType() + { + return VisualStateCommandTypes::PresentationAttribute; + } + }; + template <> + struct SVisualStateCommandTypeMap<SPlaySound> + { + static VisualStateCommandTypes::Enum GetType() + { + return VisualStateCommandTypes::PlaySound; + } + }; + + struct SVisualStateCommandUnionTraits + { + typedef VisualStateCommandTypes::Enum TIdType; + enum { + TBufferSize = sizeof(SGotoSlideRelative), + }; + + static TIdType getNoDataId() { return VisualStateCommandTypes::NoVisualStateCommand; } + + template <typename TDataType> + static TIdType getType() + { + return SVisualStateCommandTypeMap<TDataType>().GetType(); + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case VisualStateCommandTypes::GotoSlide: + return inVisitor(*NVUnionCast<SGotoSlide *>(inData)); + case VisualStateCommandTypes::CallFunction: + return inVisitor(*NVUnionCast<SCallFunction *>(inData)); + case VisualStateCommandTypes::SetAttribute: + return inVisitor(*NVUnionCast<SSetAttribute *>(inData)); + case VisualStateCommandTypes::GotoSlideRelative: + return inVisitor(*NVUnionCast<SGotoSlideRelative *>(inData)); + case VisualStateCommandTypes::FireEvent: + return inVisitor(*NVUnionCast<SFireEvent *>(inData)); + case VisualStateCommandTypes::PresentationAttribute: + return inVisitor(*NVUnionCast<SPresentationAttribute *>(inData)); + case VisualStateCommandTypes::PlaySound: + return inVisitor(*NVUnionCast<SPlaySound *>(inData)); + default: + QT3DS_ASSERT(false); + case VisualStateCommandTypes::NoVisualStateCommand: + return inVisitor(); + } + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case VisualStateCommandTypes::GotoSlide: + return inVisitor(*NVUnionCast<const SGotoSlide *>(inData)); + case VisualStateCommandTypes::CallFunction: + return inVisitor(*NVUnionCast<const SCallFunction *>(inData)); + case VisualStateCommandTypes::SetAttribute: + return inVisitor(*NVUnionCast<const SSetAttribute *>(inData)); + case VisualStateCommandTypes::GotoSlideRelative: + return inVisitor(*NVUnionCast<const SGotoSlideRelative *>(inData)); + case VisualStateCommandTypes::FireEvent: + return inVisitor(*NVUnionCast<const SFireEvent *>(inData)); + case VisualStateCommandTypes::PresentationAttribute: + return inVisitor(*NVUnionCast<const SPresentationAttribute *>(inData)); + case VisualStateCommandTypes::PlaySound: + return inVisitor(*NVUnionCast<const SPlaySound *>(inData)); + default: + QT3DS_ASSERT(false); + case VisualStateCommandTypes::NoVisualStateCommand: + return inVisitor(); + } + } + }; + + typedef qt3ds::foundation:: + DiscriminatedUnion<qt3ds::foundation:: + DiscriminatedUnionGenericBase<SVisualStateCommandUnionTraits, + SVisualStateCommandUnionTraits:: + TBufferSize>, + SVisualStateCommandUnionTraits::TBufferSize> + TVisualStateCommandUnionType; + + struct SVisualStateCommand : public TVisualStateCommandUnionType + { + SVisualStateCommand() {} + + SVisualStateCommand(const SVisualStateCommand &inOther) + : TVisualStateCommandUnionType( + static_cast<const TVisualStateCommandUnionType &>(inOther)) + { + } + + template <typename TDataType> + SVisualStateCommand(const TDataType &inDt) + : TVisualStateCommandUnionType(inDt) + { + } + + SVisualStateCommand &operator=(const SVisualStateCommand &inOther) + { + TVisualStateCommandUnionType::operator=(inOther); + return *this; + } + + bool operator==(const SVisualStateCommand &inOther) const + { + return TVisualStateCommandUnionType::operator==(inOther); + } + bool operator!=(const SVisualStateCommand &inOther) const + { + return TVisualStateCommandUnionType::operator!=(inOther); + } + + bool empty() const { return getType() == VisualStateCommandTypes::NoVisualStateCommand; } + }; +} +} +#ifndef _INTEGRITYPLATFORM +namespace qt3ds { +namespace foundation { + + template <> + struct DestructTraits<qt3ds::state::SGotoSlide> + { + void destruct(qt3ds::state::SGotoSlide &) {} + }; + template <> + struct DestructTraits<qt3ds::state::SCallFunction> + { + void destruct(qt3ds::state::SCallFunction &) {} + }; + template <> + struct DestructTraits<qt3ds::state::SSetAttribute> + { + void destruct(qt3ds::state::SSetAttribute &) {} + }; + template <> + struct DestructTraits<qt3ds::state::SGotoSlideRelative> + { + void destruct(qt3ds::state::SGotoSlideRelative &) {} + }; + template <> + struct DestructTraits<qt3ds::state::SFireEvent> + { + void destruct(qt3ds::state::SFireEvent &) {} + }; + template <> + struct DestructTraits<qt3ds::state::SPresentationAttribute> + { + void destruct(qt3ds::state::SPresentationAttribute &) {} + }; + template <> + struct DestructTraits<qt3ds::state::SPlaySound> + { + void destruct(qt3ds::state::SPlaySound &) {} + }; +} +} +#endif +#endif
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateXMLIO.cpp b/src/Runtime/Source/stateapplication/Qt3DSStateXMLIO.cpp new file mode 100644 index 00000000..44f3242f --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateXMLIO.cpp @@ -0,0 +1,1948 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateXMLIO.h" +#include "foundation/XML.h" +#include "Qt3DSStateTypes.h" +#include "Qt3DSStateContext.h" +#include "foundation/Utils.h" +#include "foundation/StringConversionImpl.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "EASTL/hash_map.h" +#include "Qt3DSStateExecutionTypes.h" +#include "Qt3DSStateEditor.h" +#include "Qt3DSStateEditorValue.h" + +using namespace qt3ds::state; +using namespace qt3ds::state::editor; + +namespace { +#define ITERATE_XML_ELEMENT_NAMES \ + HANDLE_XML_ELEMENT_NAME(scxml) \ + HANDLE_XML_ELEMENT_NAME(state) \ + HANDLE_XML_ELEMENT_NAME(parallel) \ + HANDLE_XML_ELEMENT_NAME(transition) \ + HANDLE_XML_ELEMENT_NAME(initial) \ + HANDLE_XML_ELEMENT_NAME(final) \ + HANDLE_XML_ELEMENT_NAME(onentry) \ + HANDLE_XML_ELEMENT_NAME(onexit) \ + HANDLE_XML_ELEMENT_NAME(history) \ + HANDLE_XML_ELEMENT_NAME(raise) \ + HANDLE_XML_ELEMENT_NAME(if) \ + HANDLE_XML_ELEMENT_NAME(elseif) \ + HANDLE_XML_ELEMENT_NAME(else) \ + HANDLE_XML_ELEMENT_NAME(foreach) \ + HANDLE_XML_ELEMENT_NAME(log) \ + HANDLE_XML_ELEMENT_NAME(param) \ + HANDLE_XML_ELEMENT_NAME(assign) \ + HANDLE_XML_ELEMENT_NAME(script) \ + HANDLE_XML_ELEMENT_NAME(send) \ + HANDLE_XML_ELEMENT_NAME(cancel) \ + HANDLE_XML_ELEMENT_NAME(invoke) \ + HANDLE_XML_ELEMENT_NAME(finalize) \ + HANDLE_XML_ELEMENT_NAME(cond) \ + HANDLE_XML_ELEMENT_NAME(event) \ + HANDLE_XML_ELEMENT_NAME(datamodel) \ + HANDLE_XML_ELEMENT_NAME(data) \ + HANDLE_XML_ELEMENT_NAME(content) \ + HANDLE_XML_ELEMENT_NAME(external_transition) + +struct SXMLName +{ + enum Enum { +#define HANDLE_XML_ELEMENT_NAME(nm) e##nm, + ITERATE_XML_ELEMENT_NAMES +#undef HANDLE_XML_ELEMENT_NAME + LastName, + }; + static const char *GetNameForElemName(Enum inName) + { + switch (inName) { +#define HANDLE_XML_ELEMENT_NAME(nm) \ + case e##nm: \ + return #nm; + ITERATE_XML_ELEMENT_NAMES +#undef HANDLE_XML_ELEMENT_NAME + default: + break; + } + QT3DS_ASSERT(false); + return "unknown element"; + } +}; + +const char8_t *GetSCXMLNamespace() +{ + return "http://www.w3.org/2005/07/scxml"; +} +const char8_t *GetStudioStateNamespace() +{ + return "http://qt.io/qt3dstudio/uicstate"; +} + +typedef eastl::pair<const char8_t *, NVConstDataRef<SStateNode *> *> TIdListPtrPair; +typedef eastl::pair<const char8_t *, SSend **> TIdSendPair; + +TEditorStr DoGenerateUniqueId(const char *inRoot, IStateContext &ioContext, + IStringTable &ioStringTable) +{ + if (isTrivial(inRoot)) + inRoot = "id"; + TEditorStr stem(inRoot); + TEditorStr idStr(stem); + // Make the stem a valid possible id according xml specifications + if (idStr.size()) { + // Replace all spaces with undre + // Check that the first item isn't a space or a number. We can't do a real check here + // because we don't have unicode tables of letters and such. + // replace spaces with underscores. + for (TEditorStr::size_type thePos = idStr.find(' '); thePos != TEditorStr::npos; + thePos = idStr.find(' ', thePos + 1)) + idStr.replace(thePos, 1, "_"); + if (idStr[0] >= '0' && idStr[0] <= '9') + idStr.insert(idStr.begin(), 1, ':'); + } + + QT3DSU32 idx = 0; + + while (ioContext.ContainsId(ioStringTable.RegisterStr(idStr.c_str()))) { + ++idx; + char temp[64]; + sprintf(temp, "%d", idx); + idStr.assign(stem); + idStr.append("_"); + idStr.append(temp); + } + + return idStr; +} + +struct SParseContext +{ + typedef nvvector<eastl::pair<CRegisteredString, STransition *>> TExternalTransitionList; + + NVAllocatorCallback &m_GraphAllocator; + NVFoundationBase &m_Foundation; + IDOMReader &m_Reader; + IStateContext &m_Context; + IStringTable &m_StrTable; + IEditor *m_Editor; + CRegisteredString m_Names[SXMLName::LastName]; + MemoryBuffer<ForwardingAllocator> m_ParseBuffer; + nvvector<char8_t> m_TempBuffer; + // To be filled in on the second parse pass + nvvector<TIdListPtrPair> m_References; + nvvector<TIdSendPair> m_SendReferences; + CXMLIO::TIdRemapMap m_RemapMap; + nvvector<SIdValue> m_GenerateIdList; + TExternalTransitionList m_ExternalTransitions; + CRegisteredString m_SCXMLNamespace; + CRegisteredString m_StudioNamespace; + QT3DSI32 m_Version; + + SParseContext(NVAllocatorCallback &inGraphAlloc, NVFoundationBase &inFnd, IDOMReader &inReader, + IStateContext &inCtx, IStringTable &inStrTable, IEditor *inEditor) + : m_GraphAllocator(inGraphAlloc) + , m_Foundation(inFnd) + , m_Reader(inReader) + , m_Context(inCtx) + , m_StrTable(inStrTable) + , m_Editor(inEditor) + , m_ParseBuffer(ForwardingAllocator(inFnd.getAllocator(), "ParseBuffer")) + , m_TempBuffer(inFnd.getAllocator(), "TempBuffer") + , m_References(inFnd.getAllocator(), "m_References") + , m_SendReferences(inFnd.getAllocator(), "m_StrReferences") + , m_GenerateIdList(inFnd.getAllocator(), "m_GenerateIdList") + , m_ExternalTransitions(inFnd.getAllocator(), "m_ExternalTransitions") + , m_Version(SSCXML::GetCurrentVersion()) + { +#define HANDLE_XML_ELEMENT_NAME(nm) m_Names[SXMLName::e##nm] = inStrTable.RegisterStr(#nm); + ITERATE_XML_ELEMENT_NAMES +#undef HANDLE_XML_ELEMENT_NAME + m_SCXMLNamespace = inStrTable.RegisterStr(GetSCXMLNamespace()); + m_StudioNamespace = inStrTable.RegisterStr(GetStudioStateNamespace()); + } + + inline bool eq(CRegisteredString lhs, SXMLName::Enum rhs) { return lhs == m_Names[rhs]; } + const char8_t *ToGraphStr(const char8_t *temp) + { + temp = nonNull(temp); + QT3DSU32 len = StrLen(temp) + 1; + char8_t *retval = + (char8_t *)m_GraphAllocator.allocate(len, "graph string", __FILE__, __LINE__); + memCopy(retval, temp, len); + // Force null termination regardless. + retval[len] = 0; + return retval; + } + const char8_t *ParseStrAtt(const char8_t *attName) + { + const char8_t *temp; + if (m_Reader.UnregisteredAtt(attName, temp) && temp && *temp) + return ToGraphStr(temp); + return NULL; + } + void ParseStrAtt(const char8_t *attName, const char8_t *&outVal) + { + outVal = ParseStrAtt(attName); + } + void ParseStrAtt(const char8_t *attName, CRegisteredString &outVal) + { + m_Reader.Att(attName, outVal); + } + + SItemExtensionInfo &GetExtensionInfo(void *inItem) + { + return m_Context.GetOrCreateExtensionInfo(inItem); + } + + // Extension attributes are indicated by their namespace. If they have a namespace + // and they aren't scxml namespace and they aren't studio namespace, they are extension. + void ParseExtensionAttributes(void *inItem) + { + for (SDOMAttribute *theAttribute = m_Reader.GetFirstAttribute(); theAttribute; + theAttribute = theAttribute->m_NextAttribute) { + if (theAttribute->m_Namespace != m_StudioNamespace + && theAttribute->m_Namespace != m_SCXMLNamespace + && theAttribute->m_Namespace.IsValid()) { + GetExtensionInfo(inItem).m_ExtensionAttributes.push_back( + *QT3DS_NEW(m_GraphAllocator, SDOMAttributeNode)(theAttribute)); + } + } + } + bool ParseExtensionElement(void *inItem) + { + SDOMElement &theElement(*m_Reader.GetElement()); + + if (theElement.m_Namespace != m_StudioNamespace + && theElement.m_Namespace != m_SCXMLNamespace && theElement.m_Namespace.IsValid()) { + GetExtensionInfo(inItem).m_ExtensionNodes.push_back( + *QT3DS_NEW(m_GraphAllocator, SDOMElementNode)(&theElement)); + return true; + } + return false; + } + + void ParseExtensionElements(void *inItem) + { + IDOMReader::Scope _childElemScope(m_Reader); + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + ParseExtensionElement(inItem); + } + } + + NVConstDataRef<QT3DSF32> ParseFloats(const char8_t *inData) + { + size_t len = StrLen(inData) + 1; + if (len == 1) + return NVConstDataRef<QT3DSF32>(); + m_TempBuffer.resize((QT3DSU32)len); + memCopy(m_TempBuffer.data(), inData, (QT3DSU32)len); + m_ParseBuffer.clear(); + Char8TReader theReader(m_TempBuffer.data(), m_ParseBuffer); + NVConstDataRef<QT3DSF32> retval; + theReader.ReadBuffer(retval); + return retval; + } + bool ParseVec2Att(const char8_t *attName, QT3DSVec2 &outVal) + { + const char8_t *tempVal; + if (m_Reader.UnregisteredAtt(attName, tempVal, GetStudioStateNamespace())) { + NVConstDataRef<QT3DSF32> floats = ParseFloats(tempVal); + if (floats.mSize >= 2) { + memCopy(&outVal.x, floats.mData, sizeof(outVal)); + return true; + } + } + return false; + } + + bool ParseVec3Att(const char8_t *attName, QT3DSVec3 &outVal) + { + const char8_t *tempVal; + if (m_Reader.UnregisteredAtt(attName, tempVal, GetStudioStateNamespace())) { + NVConstDataRef<QT3DSF32> floats = ParseFloats(tempVal); + if (floats.mSize >= 3) { + memCopy(&outVal.x, floats.mData, sizeof(outVal)); + return true; + } + } + return false; + } + + CRegisteredString RemapStr(CRegisteredString inStr) + { + CXMLIO::TIdRemapMap::iterator iter = m_RemapMap.find(inStr.c_str()); + if (iter != m_RemapMap.end()) + inStr = m_StrTable.RegisterStr(iter->second.c_str()); + return inStr; + } + + SStateNode *FindStateNode(CRegisteredString inString) + { + return m_Context.FindStateNode(RemapStr(inString)); + } + + SSend *ParseSendIdSecondPass(const char8_t *inStr) + { + if (isTrivial(inStr)) + return NULL; + return m_Context.FindSend(RemapStr(m_StrTable.RegisterStr(inStr))); + } + + NVConstDataRef<SStateNode *> ParseIDRefSecondPass(const char8_t *inStr) + { + typedef eastl::basic_string<char8_t, ForwardingAllocator> TStrType; + TStrType workspace(ForwardingAllocator(m_Foundation.getAllocator(), "ParseIDRef")); + TStrType str(ForwardingAllocator(m_Foundation.getAllocator(), "ParseIDRef")); + nvvector<SStateNode *> tempVal(m_Foundation.getAllocator(), "ParseIDRef"); + + workspace.assign(inStr); + for (TStrType::size_type startPos = workspace.find_first_not_of(' '), + endPos = workspace.find_first_of(' ', startPos); + startPos != TStrType::npos; startPos = workspace.find_first_not_of(' ', endPos), + endPos = workspace.find_first_of(' ', startPos)) { + if (endPos == TStrType::npos) + endPos = workspace.size(); + str = workspace.substr(startPos, endPos - startPos); + CRegisteredString theStr(m_StrTable.RegisterStr(str.c_str())); + SStateNode *theNode = FindStateNode(theStr); + if (theNode) + tempVal.push_back(theNode); + } + if (tempVal.size() == 0) + return NVConstDataRef<SStateNode *>(); + + SStateNode **dataPtr = (SStateNode **)m_GraphAllocator.allocate( + tempVal.size() * sizeof(SStateNode *), "IDRef", __FILE__, __LINE__); + memCopy(dataPtr, tempVal.data(), tempVal.size() * sizeof(SStateNode *)); + return toDataRef(dataPtr, tempVal.size()); + } + + void ParseIDRef(const char8_t *inStr, NVConstDataRef<SStateNode *> &ioNodes) + { + if (inStr == NULL || *inStr == 0) { + ioNodes = NVConstDataRef<SStateNode *>(); + return; + } + m_References.push_back(eastl::make_pair(inStr, &ioNodes)); + } + + static void AppendChild(SStateNode &inParent, TStateNodeList &outChildren, SStateNode &inChild) + { + inChild.m_Parent = &inParent; + outChildren.push_back(inChild); + } + + static void AppendChild(SStateNode *inNodeParent, SExecutableContent *inParent, + TExecutableContentList &outChildren, SExecutableContent &inChild) + { + if (inNodeParent) { + QT3DS_ASSERT(inParent == NULL); + } else { + QT3DS_ASSERT(inParent); + } + inChild.m_StateNodeParent = inNodeParent; + inChild.m_Parent = inParent; + outChildren.push_back(inChild); + } + + template <typename TStateType> + void ParseEditorAttributes(TStateType &inNode) + { + ParseExtensionAttributes(&inNode); + if (m_Editor) { + const char8_t *name; + if (m_Reader.UnregisteredAtt("id", name, GetStudioStateNamespace())) + m_Editor->GetOrCreate(inNode)->SetPropertyValue("id", eastl::string(nonNull(name))); + + QT3DSVec2 temp; + if (ParseVec2Att("position", temp)) + m_Editor->GetOrCreate(inNode)->SetPropertyValue("position", temp); + + if (ParseVec2Att("dimension", temp)) + m_Editor->GetOrCreate(inNode)->SetPropertyValue("dimension", temp); + + QT3DSVec3 tempv3; + if (ParseVec3Att("color", tempv3)) + m_Editor->GetOrCreate(inNode)->SetPropertyValue("dimension", tempv3); + } + } + + void ParseStateNodeEditorAttributes(SStateNode &inNode) + { + ParseExtensionAttributes(&inNode); + CRegisteredString Id; + if (inNode.m_Id.IsValid() == false) + m_Reader.Att("id", inNode.m_Id); + + QT3DSVec2 temp; + if (ParseVec2Att("position", temp)) + inNode.SetPosition(temp); + + if (ParseVec2Att("dimension", temp)) + inNode.SetDimension(temp); + + QT3DSVec3 tempv3; + if (ParseVec3Att("color", tempv3)) + inNode.SetColor(tempv3); + + if (m_Editor) { + const char8_t *desc; + if (m_Reader.UnregisteredAtt("description", desc, GetStudioStateNamespace())) + m_Editor->ToEditor(inNode)->SetPropertyValue("description", + eastl::string(nonNull(desc))); + } + } + + // Parse the node id. IF it exists, then schedule the id for remapping. + template <typename TDataType> + void ParseNodeId(TDataType &inNode) + { + m_Reader.Att("id", inNode.m_Id); + if (inNode.m_Id.IsValid() == false || m_Context.ContainsId(inNode.m_Id)) { + m_GenerateIdList.push_back(&inNode); + } else { + bool success = m_Context.InsertId(inNode.m_Id, &inNode); + (void)success; + QT3DS_ASSERT(success); + } + } + + SParam &ParseParam() + { + IDOMReader::Scope _itemScope(m_Reader); + SParam *retval = QT3DS_NEW(m_GraphAllocator, SParam)(); + ParseStrAtt("name", retval->m_Name); + ParseStrAtt("expr", retval->m_Expr); + ParseStrAtt("location", retval->m_Location); + ParseExtensionAttributes(retval); + ParseExtensionElements(retval); + return *retval; + } + + SContent &ParseContent() + { + IDOMReader::Scope _itemScope(m_Reader); + SContent *retval = QT3DS_NEW(m_GraphAllocator, SContent)(); + ParseStrAtt("expr", retval->m_Expr); + if (isTrivial(retval->m_Expr)) { + if (m_Reader.CountChildren() == 0) { + const char8_t *val = NULL; + m_Reader.Value(val); + if (!isTrivial(val)) + retval->m_ContentValue = this->ToGraphStr(val); + } + // We don't implement any extensions, so this is most certainly going into the + // extensions bin for this content. + else { + } + } + ParseExtensionAttributes(retval); + ParseExtensionElements(retval); + return *retval; + } + + SExecutableContent &ParseSend() + { + IDOMReader::Scope _itemScope(m_Reader); + SSend *retval = QT3DS_NEW(m_GraphAllocator, SSend)(); + ParseStrAtt("event", retval->m_Event); + ParseStrAtt("eventexpr", retval->m_EventExpr); + ParseStrAtt("target", retval->m_Target); + ParseStrAtt("targetexpr", retval->m_TargetExpr); + ParseStrAtt("type", retval->m_Type); + ParseStrAtt("typeExpr", retval->m_TypeExpr); + ParseNodeId(*retval); + ParseStrAtt("idlocation", retval->m_IdLocation); + ParseStrAtt("delay", retval->m_Delay); + ParseStrAtt("delayexpr", retval->m_DelayExpr); + ParseStrAtt("namelist", retval->m_NameList); + ParseExtensionAttributes(retval); + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + IDOMReader::Scope _loopScope(m_Reader); + CRegisteredString elemName = m_Reader.GetElementName(); + if (eq(elemName, SXMLName::eparam)) + AppendChild(NULL, retval, retval->m_Children, ParseParam()); + else if (eq(elemName, SXMLName::econtent)) + retval->m_Children.push_back(ParseContent()); + else { + if (!ParseExtensionElement(retval)) { + qCCritical(INTERNAL_ERROR, "Failed to parse send child %s", elemName.c_str()); + QT3DS_ASSERT(false); + } + } + } + m_Context.AddSendToList(*retval); + return *retval; + } + SExecutableContent &ParseIf() + { + IDOMReader::Scope _itemScope(m_Reader); + SIf *retval = QT3DS_NEW(m_GraphAllocator, SIf)(); + ParseStrAtt("cond", retval->m_Cond); + ParseExtensionAttributes(retval); + ParseExecutableContent(NULL, retval, retval->m_Children, retval); + return *retval; + } + + SExecutableContent &ParseElseIf() + { + IDOMReader::Scope _itemScope(m_Reader); + SElseIf *retval = QT3DS_NEW(m_GraphAllocator, SElseIf)(); + ParseStrAtt("cond", retval->m_Cond); + ParseExtensionAttributes(retval); + ParseExecutableContent(NULL, retval, retval->m_Children, retval); + return *retval; + } + + SExecutableContent &ParseElse() + { + IDOMReader::Scope _itemScope(m_Reader); + SElse *retval = QT3DS_NEW(m_GraphAllocator, SElse)(); + ParseExtensionAttributes(retval); + ParseExecutableContent(NULL, retval, retval->m_Children, retval); + return *retval; + } + SExecutableContent &ParseForEach() + { + IDOMReader::Scope _itemScope(m_Reader); + SForeach *retval = QT3DS_NEW(m_GraphAllocator, SForeach)(); + ParseStrAtt("array", retval->m_Array); + ParseStrAtt("item", retval->m_Item); + ParseStrAtt("index", retval->m_Index); + ParseExtensionAttributes(retval); + ParseExecutableContent(NULL, retval, retval->m_Children, retval); + return *retval; + } + + SExecutableContent &ParseRaise() + { + IDOMReader::Scope _itemScope(m_Reader); + SRaise *retval = QT3DS_NEW(m_GraphAllocator, SRaise)(); + ParseStrAtt("event", retval->m_Event); + ParseExtensionAttributes(retval); + ParseExtensionElements(retval); + return *retval; + } + + SExecutableContent &ParseLog() + { + IDOMReader::Scope _itemScope(m_Reader); + SLog *retval = QT3DS_NEW(m_GraphAllocator, SLog)(); + ParseStrAtt("label", retval->m_Label); + ParseStrAtt("expr", retval->m_Expression); + ParseExtensionAttributes(retval); + ParseExtensionElements(retval); + return *retval; + } + + SData &ParseData() + { + IDOMReader::Scope _itemScope(m_Reader); + SData *retval = QT3DS_NEW(m_GraphAllocator, SData)(); + ParseStrAtt("id", retval->m_Id); + ParseStrAtt("src", retval->m_Source); + ParseStrAtt("expr", retval->m_Expression); + ParseEditorAttributes(*retval); + ParseExtensionElements(retval); + const char8_t *value; + m_Reader.Value(value); + // Not handling arbitrary content under data right now. + if (isTrivial(retval->m_Expression) && !isTrivial(value)) { + retval->m_Expression = ToGraphStr(value); + } + return *retval; + } + + SDataModel *ParseDataModel() + { + IDOMReader::Scope _itemScope(m_Reader); + SDataModel *retval = QT3DS_NEW(m_GraphAllocator, SDataModel)(); + ParseStrAtt("id", retval->m_Id); + ParseStrAtt("src", retval->m_Source); + ParseStrAtt("expr", retval->m_Expression); + ParseEditorAttributes(*retval); + for (bool success = m_Reader.MoveToFirstChild("data"); success; + success = m_Reader.MoveToNextSibling("data")) + retval->m_Data.push_back(ParseData()); + + ParseExtensionElements(retval); + + return retval; + } + + SExecutableContent &ParseAssign() + { + IDOMReader::Scope _itemScope(m_Reader); + SAssign *retval = QT3DS_NEW(m_GraphAllocator, SAssign)(); + ParseStrAtt("location", retval->m_Location); + ParseStrAtt("expr", retval->m_Expression); + ParseExtensionAttributes(retval); + ParseExtensionElements(retval); + // Not dealing with children. + return *retval; + } + + SExecutableContent &ParseScript() + { + IDOMReader::Scope _itemScope(m_Reader); + SScript *retval = QT3DS_NEW(m_GraphAllocator, SScript)(); + ParseStrAtt("src", retval->m_URL); + ParseExtensionAttributes(retval); + if (m_Reader.Value(retval->m_Data)) + retval->m_Data = ToGraphStr(retval->m_Data); + ParseExtensionElements(retval); + return *retval; + } + + SExecutableContent &ParseCancel() + { + IDOMReader::Scope _itemScope(m_Reader); + SCancel *retval = QT3DS_NEW(m_GraphAllocator, SCancel)(); + const char8_t *sendId; + if (m_Reader.UnregisteredAtt("sendid", sendId)) + m_SendReferences.push_back(eastl::make_pair(sendId, &retval->m_Send)); + ParseStrAtt("sendidexpr", retval->m_IdExpression); + ParseExtensionAttributes(retval); + ParseExtensionElements(retval); + return *retval; + } + struct SExecutableContentParseBinding + { + SXMLName::Enum m_Name; + typedef SExecutableContent &(SParseContext::*TParseFunc)(); + TParseFunc m_ParseFunc; + }; + + static NVDataRef<SExecutableContentParseBinding> GetParseBindings() + { + static SExecutableContentParseBinding s_ExecContentParseBindings[10] = { + { SXMLName::esend, &SParseContext::ParseSend }, + { SXMLName::eif, &SParseContext::ParseIf }, + { SXMLName::eelseif, &SParseContext::ParseElseIf }, + { SXMLName::eelse, &SParseContext::ParseElse }, + { SXMLName::eforeach, &SParseContext::ParseForEach }, + { SXMLName::eraise, &SParseContext::ParseRaise }, + { SXMLName::elog, &SParseContext::ParseLog }, + { SXMLName::eassign, &SParseContext::ParseAssign }, + { SXMLName::escript, &SParseContext::ParseScript }, + { SXMLName::ecancel, &SParseContext::ParseCancel }, + }; + + return toDataRef(s_ExecContentParseBindings, 10); + } + + bool ParseExecutableContentItem(SStateNode *inNodeParent, SExecutableContent *inParent, + TExecutableContentList &outContent) + { + CRegisteredString elemName = m_Reader.GetElementName(); + NVDataRef<SExecutableContentParseBinding> theBindingList(GetParseBindings()); + for (QT3DSU32 idx = 0, end = theBindingList.size(); idx < end; ++idx) { + SExecutableContentParseBinding theBinding(theBindingList[idx]); + if (eq(elemName, theBinding.m_Name)) { + AppendChild(inNodeParent, inParent, outContent, (this->*theBinding.m_ParseFunc)()); + return true; + } + } + return false; + } + + void ParseExecutableContent(SStateNode *inStateNodeParent, SExecutableContent *inParent, + TExecutableContentList &outContent, void *inExtensionPtr) + { + IDOMReader::Scope _itemScope(m_Reader); + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + IDOMReader::Scope _loopScope(m_Reader); + CRegisteredString elemName = m_Reader.GetElementName(); + if (!ParseExecutableContentItem(inStateNodeParent, inParent, outContent)) { + if (!ParseExtensionElement(inExtensionPtr)) { + qCCritical(INTERNAL_ERROR, "Failed to parse extension child %s", elemName.c_str()); + QT3DS_ASSERT(false); + } + } + } + } + + SOnEntry &ParseOnEntry(SStateNode &inParent) + { + IDOMReader::Scope _itemScope(m_Reader); + SOnEntry *retval = QT3DS_NEW(m_GraphAllocator, SOnEntry)(); + ParseExecutableContent(&inParent, NULL, retval->m_ExecutableContent, retval); + return *retval; + } + + SOnExit &ParseOnExit(SStateNode &inParent) + { + IDOMReader::Scope _itemScope(m_Reader); + SOnExit *retval = QT3DS_NEW(m_GraphAllocator, SOnExit)(); + ParseExecutableContent(&inParent, NULL, retval->m_ExecutableContent, retval); + return *retval; + } + + SHistory &ParseHistory() + { + IDOMReader::Scope _itemScope(m_Reader); + SHistory *retval = QT3DS_NEW(m_GraphAllocator, SHistory)(); + ParseNodeId(*retval); + const char8_t *temp; + if (m_Reader.UnregisteredAtt("type", temp) && AreEqual(temp, "deep")) + retval->m_Flags.SetDeep(true); + ParseStateNodeEditorAttributes(*retval); + if (m_Reader.MoveToFirstChild("transition")) { + retval->m_Transition = &ParseTransition(); + retval->m_Transition->m_Parent = retval; + } + ParseExtensionElements(retval); + return *retval; + } + + STransition &ParseTransition() + { + IDOMReader::Scope _itemScope(m_Reader); + STransition *retval = QT3DS_NEW(m_GraphAllocator, STransition)(); + m_Reader.Att("event", retval->m_Event); + ParseNodeId(*retval); + const char8_t *temp; + if (m_Reader.UnregisteredAtt("target", temp)) { + ParseIDRef(temp, retval->m_Target); + } + if (m_Reader.UnregisteredAtt("type", temp) && AreEqual(temp, "internal")) + retval->m_Flags.SetInternal(true); + ParseStateNodeEditorAttributes(*retval); + + retval->m_Condition = ParseStrAtt("cond"); + // Position and such is only valid for transitions after UIC version 0. + if (m_Version > 0) { + QT3DSVec2 endPos; + if (ParseVec2Att("end_position", endPos)) + retval->SetEndPosition(endPos); + + if (m_Reader.UnregisteredAtt("path", temp)) { + NVConstDataRef<QT3DSF32> pathData = ParseFloats(temp); + QT3DS_ASSERT((pathData.size() % 2) == 0); + size_t newDataSize = pathData.size() * sizeof(QT3DSF32); + QT3DSVec2 *newData = (QT3DSVec2 *)m_GraphAllocator.allocate( + newDataSize, "STransition::m_Path", __FILE__, __LINE__); + memCopy(newData, pathData.begin(), (QT3DSU32)newDataSize); + retval->m_Path = toDataRef(newData, (QT3DSU32)(newDataSize / sizeof(QT3DSVec2))); + } + } else { + retval->SetPosition(Empty()); + } + + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + IDOMReader::Scope _loopScope(m_Reader); + CRegisteredString elemName = m_Reader.GetElementName(); + if (!ParseExecutableContentItem(retval, NULL, retval->m_ExecutableContent)) { + if (!ParseExtensionElement(retval)) { + qCCritical(INTERNAL_ERROR, "Failed to parse transition child %s", elemName.c_str()); + QT3DS_ASSERT(false); + } + } + } + return *retval; + } + + SFinal &ParseFinal() + { + IDOMReader::Scope _itemScope(m_Reader); + SFinal *retval = QT3DS_NEW(m_GraphAllocator, SFinal)(); + + ParseNodeId(*retval); + ParseStateNodeEditorAttributes(*retval); + + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + IDOMReader::Scope _loopScope(m_Reader); + CRegisteredString elemName = m_Reader.GetElementName(); + if (eq(elemName, SXMLName::eonentry)) + retval->m_OnEntry.push_back(ParseOnEntry(*retval)); + else if (eq(elemName, SXMLName::eonexit)) + retval->m_OnExit.push_back(ParseOnExit(*retval)); + else if (!ParseExtensionElement(retval)) { + qCCritical(INTERNAL_ERROR, "Failed to parse final child %s", elemName.c_str()); + QT3DS_ASSERT(false); + } + } + return *retval; + } + + bool ParseStateParallelChildren(SStateNode &inParent, CRegisteredString &inElemName, + TStateNodeList &outChildren, TOnEntryList &inOnEntry, + TOnExitList &inOnExit, SDataModel *&dmPtr) + { + if (eq(inElemName, SXMLName::estate)) + AppendChild(inParent, outChildren, ParseState()); + else if (eq(inElemName, SXMLName::etransition)) + AppendChild(inParent, outChildren, ParseTransition()); + else if (eq(inElemName, SXMLName::eparallel)) + AppendChild(inParent, outChildren, ParseParallel()); + else if (eq(inElemName, SXMLName::eonentry)) + inOnEntry.push_back(ParseOnEntry(inParent)); + else if (eq(inElemName, SXMLName::eonexit)) + inOnExit.push_back(ParseOnExit(inParent)); + else if (eq(inElemName, SXMLName::ehistory)) + AppendChild(inParent, outChildren, ParseHistory()); + else if (eq(inElemName, SXMLName::efinal)) + AppendChild(inParent, outChildren, ParseFinal()); + else if (eq(inElemName, SXMLName::edatamodel)) + dmPtr = ParseDataModel(); + else { + return false; + } + return true; + } + + SStateNode &ParseParallel() + { + IDOMReader::Scope _itemScope(m_Reader); + SParallel *retval = QT3DS_NEW(m_GraphAllocator, SParallel)(); + ParseNodeId(*retval); + ParseStateNodeEditorAttributes(*retval); + + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + IDOMReader::Scope _loopScope(m_Reader); + CRegisteredString elemName = m_Reader.GetElementName(); + if (!ParseStateParallelChildren(*retval, elemName, retval->m_Children, + retval->m_OnEntry, retval->m_OnExit, + retval->m_DataModel)) { + if (!ParseExtensionElement(retval)) { + qCCritical(INTERNAL_ERROR, "Failed to parse parallel child %s", elemName.c_str()); + QT3DS_ASSERT(false); + } + } + } + return *retval; + } + + void SetStateInitialTransition(STransition *&ioInitial, TStateNodeList &inChildren, + SStateNode &inSource) + { + if (ioInitial) { + ioInitial->m_Parent = &inSource; + return; + } + SStateNode *firstStateChild = NULL; + for (TStateNodeList::iterator iter = inChildren.begin(); + iter != inChildren.end() && firstStateChild == NULL; ++iter) { + if (iter->m_Type == StateNodeTypes::State || iter->m_Type == StateNodeTypes::Parallel + || iter->m_Type == StateNodeTypes::Final) { + firstStateChild = &(*iter); + } + } + + if (firstStateChild) { + STransition *theTransition = QT3DS_NEW(m_GraphAllocator, STransition)(); + size_t byteSize = sizeof(SStateNode *); + SStateNode **theData = (SStateNode **)m_GraphAllocator.allocate( + byteSize, "InitialTransition", __FILE__, __LINE__); + *theData = firstStateChild; + theTransition->m_Target = toDataRef(theData, 1); + theTransition->m_Parent = &inSource; + ioInitial = theTransition; + } + } + + STransition *ParseInitial() + { + if (m_Reader.MoveToFirstChild("transition")) + return &ParseTransition(); + else { + QT3DS_ASSERT(false); + } + return NULL; + } + + SStateNode &ParseState() + { + IDOMReader::Scope _stateScope(m_Reader); + SState *theNewState = QT3DS_NEW(m_GraphAllocator, SState)(); + ParseNodeId(*theNewState); + const char8_t *initialAtt; + if (m_Reader.UnregisteredAtt("initialexpr", initialAtt, GetStudioStateNamespace())) { + theNewState->m_InitialExpr = ToGraphStr(initialAtt); + const char8_t *errorTest; + if (m_Reader.UnregisteredAtt("initial", errorTest)) { + qCCritical(INVALID_OPERATION, "Attribute initial=\"%s\" conflicts with " + "attribute initialexpr=\"%s\" on <state " + "id=\"%s\">; using initialexpr.", + nonNull(errorTest), nonNull(initialAtt), + nonNull(theNewState->m_Id.c_str())); + } + } else if (m_Reader.UnregisteredAtt("initial", initialAtt) && !isTrivial(initialAtt)) { + STransition *theTransition = QT3DS_NEW(m_GraphAllocator, STransition)(); + ParseIDRef(initialAtt, theTransition->m_Target); + theTransition->m_Parent = theNewState; + theNewState->m_Initial = theTransition; + } + + ParseStateNodeEditorAttributes(*theNewState); + + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + IDOMReader::Scope _loopScope(m_Reader); + CRegisteredString elemName = m_Reader.GetElementName(); + if (!ParseStateParallelChildren(*theNewState, elemName, theNewState->m_Children, + theNewState->m_OnEntry, theNewState->m_OnExit, + theNewState->m_DataModel)) { + // InitialExpr takes precedence over initial transition + if (eq(elemName, SXMLName::einitial) && isTrivial(theNewState->m_InitialExpr)) { + if (!theNewState->m_Initial) + theNewState->m_Initial = ParseInitial(); + } else { + if (!ParseExtensionElement(theNewState)) { + qCCritical(INTERNAL_ERROR, "Failed to parse state child %s", elemName.c_str()); + QT3DS_ASSERT(false); + } + } + } + } + if (isTrivial(theNewState->m_InitialExpr)) + SetStateInitialTransition(theNewState->m_Initial, theNewState->m_Children, + *theNewState); + return *theNewState; + } + + static const char8_t *GetDefaultIdName(SStateNode &theNode) + { + const char8_t *theTemp = "id"; + switch (theNode.m_Type) { + case StateNodeTypes::State: + theTemp = "state"; + break; + case StateNodeTypes::Parallel: + theTemp = "parallel"; + break; + case StateNodeTypes::Final: + theTemp = "final"; + break; + case StateNodeTypes::History: + theTemp = "history"; + break; + case StateNodeTypes::Transition: + theTemp = "transition"; + break; + default: + break; + } + return theTemp; + } + + static const char8_t *GetDefaultIdName(SSend &) { return "send"; } + + template <typename TDataType> + void GenerateIdValue(TDataType &theNode) + { + if (theNode.m_Id.IsValid() == true) { + bool preexisting = m_Context.ContainsId(theNode.m_Id); + if (preexisting) { + CRegisteredString oldId = theNode.m_Id; + theNode.m_Id = CRegisteredString(); + CXMLIO::GenerateUniqueId(theNode, oldId.c_str(), m_Context, m_StrTable); + bool success = m_RemapMap.insert(eastl::make_pair(oldId, theNode.m_Id)).second; + (void)success; + QT3DS_ASSERT(success); + } + } else { + CXMLIO::GenerateUniqueId(theNode, GetDefaultIdName(theNode), m_Context, m_StrTable); + } + } + + void FinishParsing() + { + for (QT3DSU32 idx = 0, end = m_GenerateIdList.size(); idx < end; ++idx) { + SIdValue &theNode(m_GenerateIdList[idx]); + switch (theNode.getType()) { + case IdValueTypes::StateNode: + GenerateIdValue(*(theNode.getData<SStateNode *>())); + break; + case IdValueTypes::Send: + GenerateIdValue(*(theNode.getData<SSend *>())); + break; + default: + QT3DS_ASSERT(false); + break; + } + } + for (QT3DSU32 idx = 0, end = m_References.size(); idx < end; ++idx) + *(m_References[idx].second) = ParseIDRefSecondPass(m_References[idx].first); + + for (QT3DSU32 idx = 0, end = m_SendReferences.size(); idx < end; ++idx) + *(m_SendReferences[idx].second) = ParseSendIdSecondPass(m_SendReferences[idx].first); + + if (m_Editor) { + for (QT3DSU32 idx = 0, end = m_ExternalTransitions.size(); idx < end; ++idx) { + SStateNode *theNode = FindStateNode(m_ExternalTransitions[idx].first); + if (theNode) { + TObjPtr transObj = m_Editor->GetOrCreate(*m_ExternalTransitions[idx].second); + TObjPtr stateObj = m_Editor->ToEditor(*theNode); + stateObj->Append("children", transObj); + } + } + } + } + SSCXML &ParseSCXML() + { + IDOMReader::Scope _stateScope(m_Reader); + SSCXML *retval = QT3DS_NEW(m_GraphAllocator, SSCXML)(); + + if (m_Reader.Att("id", retval->m_Id)) + m_Context.InsertId(retval->m_Id, retval); + const char8_t *initial; + if (m_Reader.UnregisteredAtt("initialexpr", initial, GetStudioStateNamespace())) { + retval->m_InitialExpr = ToGraphStr(initial); + } else if (m_Reader.UnregisteredAtt("initial", initial)) { + STransition *theTransition = QT3DS_NEW(m_GraphAllocator, STransition)(); + ParseIDRef(initial, theTransition->m_Target); + theTransition->m_Parent = retval; + retval->m_Initial = theTransition; + } + ParseStrAtt("name", retval->m_Name); + const char8_t *uicVersion; + if (!m_Reader.UnregisteredAtt("version", uicVersion, GetStudioStateNamespace())) + retval->m_Version = 0; + else { + StringConversion<QT3DSI32>().StrTo(uicVersion, retval->m_Version); + } + m_Version = retval->m_Version; + const char8_t *desc; + if (m_Editor && m_Reader.UnregisteredAtt("description", desc)) + m_Editor->GetOrCreate(*retval)->SetPropertyValue("description", desc); + + const char8_t *temp; + if (m_Reader.UnregisteredAtt("binding", temp) && AreEqual(temp, "late")) + retval->m_Flags.SetLateBinding(true); + + ParseExtensionAttributes(retval); + + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + IDOMReader::Scope _loopScope(m_Reader); + CRegisteredString elemName = m_Reader.GetElementName(); + if (eq(elemName, SXMLName::estate)) + AppendChild(*retval, retval->m_Children, ParseState()); + else if (eq(elemName, SXMLName::eparallel)) + AppendChild(*retval, retval->m_Children, ParseParallel()); + else if (eq(elemName, SXMLName::etransition)) + AppendChild(*retval, retval->m_Children, ParseTransition()); + else if (eq(elemName, SXMLName::efinal)) + AppendChild(*retval, retval->m_Children, ParseFinal()); + else if (eq(elemName, SXMLName::einitial)) + retval->m_Initial = ParseInitial(); + else if (eq(elemName, SXMLName::edatamodel)) + retval->m_DataModel = ParseDataModel(); + else { + if (!ParseExtensionElement(retval)) { + qCCritical(INTERNAL_ERROR, "Failed to parse scxml child %s", elemName.c_str()); + QT3DS_ASSERT(false); + } + } + } + if (isTrivial(retval->m_InitialExpr)) { + SetStateInitialTransition(retval->m_Initial, retval->m_Children, *retval); + } + FinishParsing(); + return *retval; + } + eastl::vector<SStateNode *> ParseFragment() + { + eastl::vector<SStateNode *> retval; + + for (bool success = m_Reader.MoveToFirstChild(); success; + success = m_Reader.MoveToNextSibling()) { + IDOMReader::Scope _loopScope(m_Reader); + CRegisteredString elemName = m_Reader.GetElementName(); + if (eq(elemName, SXMLName::estate)) + retval.push_back(&ParseState()); + else if (eq(elemName, SXMLName::eparallel)) + retval.push_back(&ParseParallel()); + else if (eq(elemName, SXMLName::efinal)) + retval.push_back(&ParseFinal()); + else if (eq(elemName, SXMLName::ehistory)) + retval.push_back(&ParseHistory()); + else if (eq(elemName, SXMLName::eexternal_transition)) { + CRegisteredString src; + m_Reader.Att("source", src); + if (src.IsValid()) + m_ExternalTransitions.push_back(eastl::make_pair(src, &ParseTransition())); + } else { + qCCritical(INTERNAL_ERROR, "Failed to parse scxml child %s", elemName.c_str() ); + QT3DS_ASSERT(false); + } + } + + FinishParsing(); + return retval; + } +}; + +struct SWriteContext +{ + IDOMWriter &m_Writer; + IStateContext &m_Context; + IStringTable &m_StringTable; + const char8_t *m_CurrentNamespace; + const char8_t *m_Names[SXMLName::LastName]; + eastl::string m_IdStr; + eastl::string m_RefListWorkspace; + IEditor *m_Editor; + MemoryBuffer<ForwardingAllocator> m_Buffer; + Option<QT3DSVec2> m_MousePos; + nvvector<SStateNode *> m_WrittenNodes; + nvvector<STransition *> m_ExternalTransitions; + + SWriteContext(IDOMWriter &inWriter, IStateContext &inContext, IStringTable &inStrTable, + IEditor *inEditor, NVAllocatorCallback &inAlloc) + : m_Writer(inWriter) + , m_Context(inContext) + , m_StringTable(inStrTable) + , m_CurrentNamespace(GetSCXMLNamespace()) + , m_Editor(inEditor) + , m_Buffer(ForwardingAllocator(inAlloc, "WriteBuffer")) + , m_WrittenNodes(inAlloc, "m_WrittenNodes") + , m_ExternalTransitions(inAlloc, "m_ExternalTransitions") + { +#define HANDLE_XML_ELEMENT_NAME(nm) m_Names[SXMLName::e##nm] = #nm; + ITERATE_XML_ELEMENT_NAMES +#undef HANDLE_XML_ELEMENT_NAME + } + + void GenerateId(SStateNode &inNode, const char8_t *stem) + { + if (inNode.m_Id.IsValid() == false) + CXMLIO::GenerateUniqueId(inNode, stem, m_Context, m_StringTable); + } + + void WriteExtensionElement(SDOMElement &elem) + { + IDOMWriter::Scope _elemScope(m_Writer, elem.m_Name.c_str(), elem.m_Namespace.c_str()); + for (TAttributeList::iterator iter = elem.m_Attributes.begin(), + end = elem.m_Attributes.end(); + iter != end; ++iter) { + SDOMAttribute &theAttribute(*iter); + m_Writer.Att(theAttribute.m_Name, theAttribute.m_Value, + theAttribute.m_Namespace.c_str()); + } + for (SDOMElement::TElementChildList::iterator iter = elem.m_Children.begin(), + end = elem.m_Children.end(); + iter != end; ++iter) { + WriteExtensionElement(elem); + } + if (!isTrivial(elem.m_Value)) { + m_Writer.Value(elem.m_Value); + } + } + + void WriteExtensionData(void *inItem) + { + SItemExtensionInfo *infoPtr = m_Context.GetExtensionInfo(inItem); + if (infoPtr) { + SItemExtensionInfo &theInfo(*infoPtr); + for (TDOMAttributeNodeList::iterator iter = theInfo.m_ExtensionAttributes.begin(), + end = theInfo.m_ExtensionAttributes.end(); + iter != end; ++iter) { + SDOMAttribute &theAttribute(*iter->m_Attribute); + m_Writer.Att(theAttribute.m_Name.c_str(), theAttribute.m_Value, + theAttribute.m_Namespace.c_str()); + } + for (TDOMElementNodeList::iterator iter = theInfo.m_ExtensionNodes.begin(), + end = theInfo.m_ExtensionNodes.end(); + iter != end; ++iter) { + WriteExtensionElement(*iter->m_Element); + } + } + } + + void Att(const char8_t *inName, const char8_t *inData) + { + if (!isTrivial(inData)) + m_Writer.Att(inName, inData, m_CurrentNamespace); + } + + void Att(const char8_t *inName, CRegisteredString inData) + { + if (inData.IsValid()) + m_Writer.Att(inName, inData.c_str(), m_CurrentNamespace); + } + + void Att(const char8_t *inName, NVConstDataRef<CRegisteredString> inData) + { + m_RefListWorkspace.clear(); + for (QT3DSU32 idx = 0, end = inData.size(); idx < end; ++idx) { + if (m_RefListWorkspace.size()) + m_RefListWorkspace.append(" "); + m_RefListWorkspace.append(inData[idx].c_str()); + } + if (m_RefListWorkspace.size()) + Att(inName, m_RefListWorkspace.c_str()); + } + void Att(const char8_t *inName, NVConstDataRef<SStateNode *> inData) + { + m_RefListWorkspace.clear(); + for (QT3DSU32 idx = 0, end = inData.size(); idx < end; ++idx) { + if (m_RefListWorkspace.size()) + m_RefListWorkspace.append(" "); + if (inData[idx]) + m_RefListWorkspace.append(inData[idx]->m_Id.c_str()); + } + if (m_RefListWorkspace.size()) + Att(inName, m_RefListWorkspace.c_str()); + } + void Att(const char8_t *inName, const QT3DSVec2 &inData) + { + m_Buffer.clear(); + Char8TWriter writer(m_Buffer); + writer.Write(NVConstDataRef<QT3DSF32>(&inData.x, 2)); + m_Buffer.writeZeros(1); + m_Writer.Att(inName, (const char8_t *)m_Buffer.begin(), GetStudioStateNamespace()); + } + + void Att(const char8_t *inName, const QT3DSVec3 &inData) + { + m_Buffer.clear(); + Char8TWriter writer(m_Buffer); + writer.Write(NVConstDataRef<QT3DSF32>(&inData.x, 3)); + m_Buffer.writeZeros(1); + m_Writer.Att(inName, (const char8_t *)m_Buffer.begin(), GetStudioStateNamespace()); + } + + void WriteEditorAttributes(void *inType, bool idId, bool inAdjustPos = false) + { + if (m_Editor) { + TObjPtr editorObj = m_Editor->GetEditor(inType); + if (editorObj != NULL) { + if (idId) { + eastl::string name = editorObj->GetId(); + if (name.empty() == false) + m_Writer.Att("id", name.c_str(), GetStudioStateNamespace()); + } + eastl::string description = editorObj->GetDescription(); + if (description.empty() == false) + m_Writer.Att("description", description.c_str(), GetStudioStateNamespace()); + + Option<SValue> tempData = editorObj->GetPropertyValue("position"); + if (tempData.hasValue()) { + QT3DSVec2 thePos(tempData->getData<QT3DSVec2>()); + if (inAdjustPos && m_MousePos.hasValue()) { + // Get the global pos, not the local position. + for (TObjPtr parentPtr = editorObj->Parent(); parentPtr; + parentPtr = parentPtr->Parent()) { + Option<SValue> parentPos = parentPtr->GetPropertyValue("position"); + if (parentPos.hasValue()) + thePos += parentPos->getData<QT3DSVec2>(); + } + // Store pos in global coords adjusted. + thePos -= *m_MousePos; + } + Att("position", thePos); + } + + tempData = editorObj->GetPropertyValue("dimension"); + if (tempData.hasValue()) + Att("dimension", tempData->getData<QT3DSVec2>()); + + tempData = editorObj->GetPropertyValue("color"); + if (tempData.hasValue()) + Att("color", tempData->getData<QT3DSVec3>()); + } + } + } + + void WriteSend(SSend &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "send", m_CurrentNamespace); + Att("event", inContent.m_Event); + Att("eventexpr", inContent.m_EventExpr); + Att("target", inContent.m_Target); + Att("targetexpr", inContent.m_TargetExpr); + Att("type", inContent.m_Type); + Att("typeExpr", inContent.m_TypeExpr); + Att("id", inContent.m_Id); + Att("idlocation", inContent.m_IdLocation); + Att("delay", inContent.m_Delay); + Att("delayexpr", inContent.m_DelayExpr); + Att("namelist", inContent.m_NameList); + WriteExecutableContentList(inContent.m_Children); + WriteEditorAttributes(&inContent, false); + WriteExtensionData(&inContent); + } + + void WriteParam(SParam &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "param", m_CurrentNamespace); + Att("name", inContent.m_Name); + + if (!isTrivial(inContent.m_Expr)) + Att("expr", inContent.m_Expr); + else if (!isTrivial(inContent.m_Location)) + Att("location", inContent.m_Location); + + WriteEditorAttributes(&inContent, false); + WriteExtensionData(&inContent); + } + + void WriteContent(SContent &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "content", m_CurrentNamespace); + if (!isTrivial(inContent.m_Expr)) + Att("expr", inContent.m_Expr); + else if (!isTrivial(inContent.m_ContentValue)) + m_Writer.Value(inContent.m_ContentValue); + + WriteEditorAttributes(&inContent, false); + WriteExtensionData(&inContent); + } + + void WriteRaise(SRaise &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "raise", m_CurrentNamespace); + Att("event", inContent.m_Event); + WriteEditorAttributes(&inContent, false); + WriteExtensionData(&inContent); + } + + void WriteAssign(SAssign &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "assign", m_CurrentNamespace); + Att("location", inContent.m_Location); + Att("expr", inContent.m_Expression); + WriteEditorAttributes(&inContent, false); + WriteExtensionData(&inContent); + } + + void WriteIf(SIf &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "if", m_CurrentNamespace); + Att("cond", inContent.m_Cond); + WriteEditorAttributes(&inContent, false); + WriteExecutableContentList(inContent.m_Children); + WriteExtensionData(&inContent); + } + + void WriteElseIf(SElseIf &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "elseif", m_CurrentNamespace); + WriteEditorAttributes(&inContent, false); + Att("cond", inContent.m_Cond); + WriteExtensionData(&inContent); + } + + void WriteElse(SElse &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "else", m_CurrentNamespace); + WriteEditorAttributes(&inContent, false); + WriteExtensionData(&inContent); + } + + void WriteLog(SLog &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "log", m_CurrentNamespace); + WriteEditorAttributes(&inContent, false); + Att("label", inContent.m_Label); + Att("expr", inContent.m_Expression); + WriteExtensionData(&inContent); + } + + void WriteScript(SScript &inContent) + { + IDOMWriter::Scope _itemScope(m_Writer, "script", m_CurrentNamespace); + WriteEditorAttributes(&inContent, false); + if (isTrivial(inContent.m_Data) && !isTrivial(inContent.m_URL)) + Att("src", inContent.m_URL); + else + m_Writer.Value(inContent.m_Data); + WriteExtensionData(&inContent); + } + + void WriteCancel(SCancel &inContent) + { + // Do not serialize invalid cancel items. + if (isTrivial(inContent.m_IdExpression) && inContent.m_Send == NULL) + return; + + IDOMWriter::Scope _itemScope(m_Writer, "cancel", m_CurrentNamespace); + if (!inContent.m_Send && inContent.m_IdExpression) + Att("sendidexpr", inContent.m_IdExpression); + else + Att("sendid", inContent.m_Send->m_Id); + + WriteEditorAttributes(&inContent, true); + WriteExtensionData(&inContent); + } + + void WriteExecutableContent(SExecutableContent &inContent) + { + switch (inContent.m_Type) { + case ExecutableContentTypes::Send: + WriteSend(static_cast<SSend &>(inContent)); + break; + case ExecutableContentTypes::Raise: + WriteRaise(static_cast<SRaise &>(inContent)); + break; + case ExecutableContentTypes::Assign: + WriteAssign(static_cast<SAssign &>(inContent)); + break; + case ExecutableContentTypes::If: + WriteIf(static_cast<SIf &>(inContent)); + break; + case ExecutableContentTypes::ElseIf: + WriteElseIf(static_cast<SElseIf &>(inContent)); + break; + case ExecutableContentTypes::Else: + WriteElse(static_cast<SElse &>(inContent)); + break; + case ExecutableContentTypes::Log: + WriteLog(static_cast<SLog &>(inContent)); + break; + case ExecutableContentTypes::Script: + WriteScript(static_cast<SScript &>(inContent)); + break; + case ExecutableContentTypes::Cancel: + WriteCancel(static_cast<SCancel &>(inContent)); + break; + case ExecutableContentTypes::Param: + WriteParam(static_cast<SParam &>(inContent)); + break; + case ExecutableContentTypes::Content: + WriteContent(static_cast<SContent &>(inContent)); + break; + default: + QT3DS_ASSERT(false); + break; + } + } + void WriteExecutableContentList(TExecutableContentList &inList) + { + for (TExecutableContentList::iterator contentIter = inList.begin(), + contentEnd = inList.end(); + contentIter != contentEnd; ++contentIter) + WriteExecutableContent(*contentIter); + } + + void WriteOnEntry(SOnEntry &inItem) + { + IDOMWriter::Scope _itemScope(m_Writer, "onentry", m_CurrentNamespace); + WriteEditorAttributes(&inItem, true); + WriteExecutableContentList(inItem.m_ExecutableContent); + WriteExtensionData(&inItem); + } + + void WriteOnEntryList(TOnEntryList &inList) + { + for (TOnEntryList::iterator iter = inList.begin(), end = inList.end(); iter != end; ++iter) + WriteOnEntry(*iter); + } + + void WriteOnExit(SOnExit &inItem) + { + IDOMWriter::Scope _itemScope(m_Writer, "onexit", m_CurrentNamespace); + WriteEditorAttributes(&inItem, true); + WriteExecutableContentList(inItem.m_ExecutableContent); + WriteExtensionData(&inItem); + } + + void WriteOnExitList(TOnExitList &inList) + { + for (TOnExitList::iterator iter = inList.begin(), end = inList.end(); iter != end; ++iter) + WriteOnExit(*iter); + } + + void WriteDataModel(SDataModel &inDataModel) + { + IDOMWriter::Scope _transitionScope(m_Writer, "datamodel", m_CurrentNamespace); + WriteEditorAttributes(&inDataModel, true); + for (TDataList::iterator iter = inDataModel.m_Data.begin(), end = inDataModel.m_Data.end(); + iter != end; ++iter) { + IDOMWriter::Scope _transitionScope(m_Writer, "data", m_CurrentNamespace); + Att("id", iter->m_Id); + Att("expr", iter->m_Expression); + WriteEditorAttributes(&(*iter), true); + WriteExtensionData(&(*iter)); + } + WriteExtensionData(&inDataModel); + } + + static inline SStateNode *FirstValidChild(SState &inNode) + { + for (TStateNodeList::iterator iter = inNode.m_Children.begin(), + end = inNode.m_Children.end(); + iter != end; ++iter) { + switch (iter->m_Type) { + case StateNodeTypes::State: + case StateNodeTypes::Parallel: + case StateNodeTypes::Final: + return iter.m_Obj; + default: + break; + } + } + return NULL; + } + + void WriteState(SState &inNode, bool inAdjustPos) + { + m_WrittenNodes.push_back(&inNode); + IDOMWriter::Scope _transitionScope(m_Writer, "state", m_CurrentNamespace); + GenerateId(inNode, "state"); + Att("id", inNode.m_Id); + WriteEditorAttributes(&inNode, false, inAdjustPos); + if (!isTrivial(inNode.m_InitialExpr)) { + m_Writer.Att("initialexpr", inNode.m_InitialExpr, GetStudioStateNamespace()); + } else if (inNode.m_Initial) { + // First check to see if this could be an attribute + if (inNode.m_Initial->m_ExecutableContent.empty()) { + // Now check if it has one child and if it has only one child and that child is the + // first valid possible child + // then we don't write out the attribute + bool canElideInitial = inNode.m_Initial->m_Target.size() == 0 + || (inNode.m_Initial->m_Target.size() == 1 + && inNode.m_Initial->m_Target[0] == FirstValidChild(inNode)); + if (!canElideInitial) + Att("initial", inNode.m_Initial->m_Target); + } else { + IDOMWriter::Scope _transitionScope(m_Writer, "initial", m_CurrentNamespace); + WriteTransition(*inNode.m_Initial, false); + } + } + WriteOnEntryList(inNode.m_OnEntry); + WriteOnExitList(inNode.m_OnExit); + WriteStateNodeList(inNode.m_Children); + if (inNode.m_DataModel) + WriteDataModel(*inNode.m_DataModel); + WriteExtensionData(&inNode); + } + + void WriteParallel(SParallel &inNode, bool inAdjustPos) + { + m_WrittenNodes.push_back(&inNode); + IDOMWriter::Scope _transitionScope(m_Writer, "parallel", m_CurrentNamespace); + GenerateId(inNode, "parallel"); + Att("id", inNode.m_Id); + WriteEditorAttributes(&inNode, false, inAdjustPos); + WriteOnEntryList(inNode.m_OnEntry); + WriteOnExitList(inNode.m_OnExit); + WriteStateNodeList(inNode.m_Children); + if (inNode.m_DataModel) + WriteDataModel(*inNode.m_DataModel); + WriteExtensionData(&inNode); + } + + void WriteHistory(SHistory &inNode, bool inAdjustPos) + { + m_WrittenNodes.push_back(&inNode); + IDOMWriter::Scope _transitionScope(m_Writer, "history", m_CurrentNamespace); + GenerateId(inNode, "history"); + Att("id", inNode.m_Id); + if (inNode.m_Flags.IsDeep()) + Att("type", "deep"); + WriteEditorAttributes(&inNode, false, inAdjustPos); + if (inNode.m_Transition) + WriteTransition(*inNode.m_Transition, false); + WriteExtensionData(&inNode); + } + void WriteTransitionData(STransition &inNode) + { + Att("event", inNode.m_Event); + Att("cond", inNode.m_Condition); + Att("target", inNode.m_Target); + if (inNode.m_Flags.IsInternal()) + Att("type", "internal"); + WriteEditorAttributes(&inNode, true, false); + if (inNode.m_StateNodeFlags.HasEndPosition()) { + Att("end_position", inNode.m_EndPosition); + } + if (inNode.m_Path.mSize) { + m_Buffer.clear(); + Char8TWriter writer(m_Buffer); + writer.Write( + NVConstDataRef<QT3DSF32>((const QT3DSF32 *)inNode.m_Path.mData, 2 * inNode.m_Path.mSize), + QT3DS_MAX_U32); + m_Buffer.writeZeros(1); + m_Writer.Att("path", (const char8_t *)m_Buffer.begin(), GetStudioStateNamespace()); + } + WriteExecutableContentList(inNode.m_ExecutableContent); + WriteExtensionData(&inNode); + } + void WriteTransition(STransition &inNode, bool /*inAdjustPos*/) + { + IDOMWriter::Scope _transitionScope(m_Writer, "transition", m_CurrentNamespace); + WriteTransitionData(inNode); + } + void WriteExternalTransition(STransition &inNode) + { + if (inNode.m_Parent != NULL) { + IDOMWriter::Scope _transitionScope(m_Writer, "external_transition", + GetStudioStateNamespace()); + m_Writer.Att("source", inNode.m_Parent->m_Id.c_str(), GetStudioStateNamespace()); + WriteTransitionData(inNode); + } else { + QT3DS_ASSERT(false); + } + } + + void WriteFinal(SFinal &inNode, bool inAdjustPos) + { + m_WrittenNodes.push_back(&inNode); + IDOMWriter::Scope _transitionScope(m_Writer, "final", m_CurrentNamespace); + GenerateId(inNode, "history"); + Att("id", inNode.m_Id); + WriteEditorAttributes(&inNode, false, inAdjustPos); + WriteOnEntryList(inNode.m_OnEntry); + WriteOnExitList(inNode.m_OnExit); + WriteExtensionData(&inNode); + } + + void Write(SStateNode &inNode, bool inAdjustPos) + { + switch (inNode.m_Type) { + case StateNodeTypes::State: + WriteState(static_cast<SState &>(inNode), inAdjustPos); + break; + case StateNodeTypes::Parallel: + WriteParallel(static_cast<SParallel &>(inNode), inAdjustPos); + break; + case StateNodeTypes::History: + WriteHistory(static_cast<SHistory &>(inNode), inAdjustPos); + break; + case StateNodeTypes::Transition: + WriteTransition(static_cast<STransition &>(inNode), inAdjustPos); + break; + case StateNodeTypes::Final: + WriteFinal(static_cast<SFinal &>(inNode), inAdjustPos); + break; + default: + QT3DS_ASSERT(false); + break; + } + } + void WriteStateNodeList(TStateNodeList &inNodeList) + { + for (TStateNodeList::iterator iter = inNodeList.begin(), end = inNodeList.end(); + iter != end; ++iter) { + Write((*iter), false); + } + } + + void Write() + { + SSCXML &item = *m_Context.GetRoot(); + GenerateId(item, "scxml"); + Att("name", item.m_Name); + if (item.m_Flags.IsLateBinding()) + Att("binding", "late"); + Att("version", "1"); + m_Writer.Att("version", SSCXML::GetCurrentVersion(), GetStudioStateNamespace()); + + if (!isTrivial(item.m_InitialExpr)) { + m_Writer.Att("initialexpr", item.m_InitialExpr, GetStudioStateNamespace()); + } else if (item.m_Initial) + Att("initial", item.m_Initial->m_Target); + + WriteStateNodeList(item.m_Children); + if (item.m_DataModel) + WriteDataModel(*item.m_DataModel); + WriteExtensionData(&item); + } + + void CheckTransitionForWrittenNodesList(STransition *transition) + { + if (transition == NULL) + return; + for (QT3DSU32 targetIdx = 0, targetEnd = transition->m_Target.size(); targetIdx < targetEnd; + ++targetIdx) { + if (eastl::find(m_WrittenNodes.begin(), m_WrittenNodes.end(), + transition->m_Target[targetIdx]) + != m_WrittenNodes.end()) { + m_ExternalTransitions.push_back(transition); + return; + } + } + } + + // Not sure the right answer here. I know you can't just blindly work with initials and such + // because you can't create new transitions for them. + void CheckForExternalTransitions(SStateNode &inNode, const eastl::vector<SStateNode *> &inRoots) + { + switch (inNode.m_Type) { + case StateNodeTypes::SCXML: { + SSCXML &theNode(static_cast<SSCXML &>(inNode)); + // CheckTransitionForWrittenNodesList( theNode.m_Initial ); + CheckForExternalTransitions(theNode.m_Children, inRoots); + } break; + case StateNodeTypes::State: { + SState &theNode(static_cast<SState &>(inNode)); + // CheckTransitionForWrittenNodesList( theNode.m_Initial ); + CheckForExternalTransitions(theNode.m_Children, inRoots); + } break; + case StateNodeTypes::Parallel: { + SParallel &theNode(static_cast<SParallel &>(inNode)); + CheckForExternalTransitions(theNode.m_Children, inRoots); + } break; + case StateNodeTypes::Final: + break; + case StateNodeTypes::History: { + // CheckTransitionForWrittenNodesList( theNode.m_Transition ); + } break; + case StateNodeTypes::Transition: { + STransition &theNode(static_cast<STransition &>(inNode)); + CheckTransitionForWrittenNodesList(&theNode); + } break; + default: + QT3DS_ASSERT(false); + break; + } + } + + void CheckForExternalTransitions(TStateNodeList &ioList, + const eastl::vector<SStateNode *> &inRoots) + { + for (TStateNodeList::iterator iter = ioList.begin(), end = ioList.end(); iter != end; + ++iter) { + if (eastl::find(inRoots.begin(), inRoots.end(), &(*iter)) == inRoots.end()) + CheckForExternalTransitions(*iter, inRoots); + } + } + + void WriteFragment(eastl::vector<SStateNode *> &inRoots, const QT3DSVec2 &inMousePos) + { + m_MousePos = inMousePos; + for (QT3DSU32 idx = 0, end = inRoots.size(); idx < end; ++idx) { + Write(*inRoots[idx], true); + } + if (m_Context.GetRoot()) + CheckForExternalTransitions(*m_Context.GetRoot(), inRoots); + m_CurrentNamespace = GetStudioStateNamespace(); + for (QT3DSU32 idx = 0, end = m_ExternalTransitions.size(); idx < end; ++idx) { + WriteExternalTransition(*m_ExternalTransitions[idx]); + } + } +}; + +// True if child is a descedent of parent. False otherwise. +inline bool IsDescendent(SStateNode &parent, SStateNode &child) +{ + if (&parent == &child) + return false; + if (&parent == child.m_Parent) + return true; + + if (child.m_Parent) + return IsDescendent(parent, *child.m_Parent); + + return false; +} + +template <typename TDataType> +void GenerateUniqueIdT(TDataType &inNode, const char8_t *inStem, IStateContext &ioContext, + IStringTable &ioStringTable) +{ + QT3DS_ASSERT(inNode.m_Id.IsValid() == false); + inNode.m_Id = + ioStringTable.RegisterStr(DoGenerateUniqueId(inStem, ioContext, ioStringTable).c_str()); + bool insertResult = ioContext.InsertId(inNode.m_Id, &inNode); + QT3DS_ASSERT(insertResult); + (void)insertResult; +} +} + +namespace qt3ds { +namespace state { + + void CXMLIO::GenerateUniqueId(SStateNode &inNode, const char8_t *inStem, + IStateContext &ioContext, IStringTable &ioStringTable) + { + GenerateUniqueIdT(inNode, inStem, ioContext, ioStringTable); + } + + void CXMLIO::GenerateUniqueId(SSend &inNode, const char8_t *inStem, IStateContext &ioContext, + IStringTable &ioStringTable) + { + GenerateUniqueIdT(inNode, inStem, ioContext, ioStringTable); + } + + bool CXMLIO::LoadSCXMLFile(NVAllocatorCallback &inGraphAllocator, NVFoundationBase &inFnd, + IDOMReader &inReader, IStringTable &inStringTable, + const char8_t *inFilename, IStateContext &outContext, + editor::IEditor *inEditor) + { + // the topmost scxml node is a state, so go from there. + if (AreEqual(inReader.GetElementName().c_str(), "scxml")) { + SParseContext theParseContext(inGraphAllocator, inFnd, inReader, outContext, + inStringTable, inEditor); + outContext.SetRoot(theParseContext.ParseSCXML()); + if (outContext.GetRoot()) { + inFilename = nonNull(inFilename); + outContext.GetRoot()->m_Filename = theParseContext.ToGraphStr(inFilename); + } + return true; + } else { + return false; + } + } + + eastl::pair<eastl::vector<SStateNode *>, CXMLIO::TIdRemapMap> + CXMLIO::LoadSCXMLFragment(NVAllocatorCallback &inGraphAllocator, NVFoundationBase &inFnd, + IDOMReader &inReader, IStringTable &inStringTable, + IStateContext &ioContext, editor::IEditor &inEditor) + { + if (AreEqual(inReader.GetElementName().c_str(), "scxml_fragment")) { + SParseContext theParseContext(inGraphAllocator, inFnd, inReader, ioContext, + inStringTable, &inEditor); + eastl::vector<SStateNode *> retval = theParseContext.ParseFragment(); + return eastl::make_pair(retval, theParseContext.m_RemapMap); + } + return eastl::make_pair(eastl::vector<SStateNode *>(), CXMLIO::TIdRemapMap()); + } + + void CXMLIO::SaveSCXMLFile(IStateContext &inContext, NVFoundationBase &inFnd, + IStringTable &inStringTable, IOutStream &outStream, + editor::IEditor *inEditor) + { + NVScopedRefCounted<IDOMFactory> theFactory = + IDOMFactory::CreateDOMFactory(inFnd.getAllocator(), inStringTable); + NVScopedRefCounted<IDOMWriter> theWriter = + IDOMWriter::CreateDOMWriter(inFnd.getAllocator(), "scxml", inStringTable, + GetSCXMLNamespace()) + .first; + SWriteContext theWriteContext(*theWriter, inContext, inStringTable, inEditor, + inFnd.getAllocator()); + theWriteContext.Write(); + + // Now we actually serialize the data. + eastl::vector<SNamespacePair> thePairs; + thePairs.push_back( + SNamespacePair(inStringTable.RegisterStr(GetSCXMLNamespace()), CRegisteredString())); + thePairs.push_back(SNamespacePair(inStringTable.RegisterStr(GetStudioStateNamespace()), + inStringTable.RegisterStr("Qt3DS"))); + + for (SNamespacePairNode *theNode = inContext.GetFirstNSNode(); theNode; + theNode = theNode->m_NextNode) { + if (AreEqual(theNode->m_Namespace.c_str(), GetSCXMLNamespace()) == false + && AreEqual(theNode->m_Namespace.c_str(), GetStudioStateNamespace()) == false + && theNode->m_Abbreviation.IsValid()) + thePairs.push_back(*theNode); + } + + CDOMSerializer::WriteXMLHeader(outStream); + CDOMSerializer::Write(inFnd.getAllocator(), *theWriter->GetTopElement(), outStream, + inStringTable, toDataRef(thePairs.data(), thePairs.size()), false); + } + + void CXMLIO::FindRoots(NVConstDataRef<SStateNode *> inObjects, + eastl::vector<SStateNode *> &outRoots) + { + // Stupid but effective algorithm. + for (QT3DSU32 idx = 0, end = inObjects.size(); idx < end; ++idx) { + // Transitions never make it into the root, nor does the top scxml object + if (inObjects[idx]->m_Type == StateNodeTypes::Transition + || inObjects[idx]->m_Type == StateNodeTypes::SCXML) + continue; + SStateNode *newNode(inObjects[idx]); + // Note that re-querying size is important. + bool skip = false; + for (QT3DSU32 existingIdx = 0; existingIdx < outRoots.size() && skip == false; + ++existingIdx) { + SStateNode *existingNode(outRoots[existingIdx]); + if (newNode == existingNode || IsDescendent(*existingNode, *newNode)) + skip = true; + + // If the existing item is a descendent of new node, + // then get the existing item out of the list. + if (IsDescendent(*newNode, *existingNode)) { + // Get the existing item out of the list. + outRoots.erase(outRoots.begin() + existingIdx); + --existingIdx; + } + } + if (!skip) + outRoots.push_back(newNode); + } + } + + eastl::vector<SStateNode *> CXMLIO::SaveSCXMLFragment( + IStateContext &inContext, NVFoundationBase &inFnd, IStringTable &inStringTable, + IDOMWriter &ioWriter, NVDataRef<SStateNode *> inObjects, editor::IEditor &inEditor, + const QT3DSVec2 &inMousePos, eastl::vector<SNamespacePair> &outNamespaces) + { + eastl::vector<SStateNode *> theRoots; + FindRoots(inObjects, theRoots); + NVScopedRefCounted<IDOMWriter> theWriter(ioWriter); + SWriteContext theWriteContext(*theWriter, inContext, inStringTable, &inEditor, + inFnd.getAllocator()); + theWriteContext.WriteFragment(theRoots, inMousePos); + + outNamespaces.push_back( + SNamespacePair(inStringTable.RegisterStr(GetSCXMLNamespace()), CRegisteredString())); + outNamespaces.push_back(SNamespacePair(inStringTable.RegisterStr(GetStudioStateNamespace()), + inStringTable.RegisterStr("Qt3DS"))); + return theRoots; + } + + void CXMLIO::ToEditableXml(IStateContext &inContext, NVFoundationBase &inFnd, + IStringTable &inStringTable, IDOMWriter &ioWriter, + SExecutableContent &inContent, editor::IEditor &inEditor) + { + SWriteContext theWriteContext(ioWriter, inContext, inStringTable, &inEditor, + inFnd.getAllocator()); + theWriteContext.WriteExecutableContent(inContent); + } + + SExecutableContent * + CXMLIO::FromEditableXML(IDOMReader &inReader, NVFoundationBase &inFnd, IStateContext &inContext, + IStringTable &inStringTable, NVAllocatorCallback &inGraphAllocator, + editor::IEditor &inEditor, SStateNode *inStateNodeParent, + SExecutableContent *inExecContentParent) + { + SParseContext theParseContext(inGraphAllocator, inFnd, inReader, inContext, inStringTable, + &inEditor); + TExecutableContentList theContentList; + theParseContext.ParseExecutableContentItem(inStateNodeParent, inExecContentParent, + theContentList); + return &(*theContentList.begin()); + } + + eastl::vector<eastl::string> CXMLIO::GetSupportedExecutableContentNames() + { + eastl::vector<eastl::string> retval; + NVConstDataRef<SParseContext::SExecutableContentParseBinding> theBindingList = + SParseContext::GetParseBindings(); + for (QT3DSU32 idx = 0, end = theBindingList.size(); idx < end; ++idx) + retval.push_back( + eastl::string(SXMLName::GetNameForElemName(theBindingList[idx].m_Name))); + return retval; + } +} +} diff --git a/src/Runtime/Source/stateapplication/Qt3DSStateXMLIO.h b/src/Runtime/Source/stateapplication/Qt3DSStateXMLIO.h new file mode 100644 index 00000000..3a639845 --- /dev/null +++ b/src/Runtime/Source/stateapplication/Qt3DSStateXMLIO.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_XML_IO_H +#define QT3DS_STATE_XML_IO_H +#pragma once +#include "Qt3DSState.h" +#include "foundation/Qt3DSDataRef.h" +#include "foundation/Qt3DSContainers.h" +#include "foundation/StringTable.h" +#include "foundation/XML.h" +#include "EASTL/map.h" +#include "EASTL/string.h" + +namespace qt3ds { +namespace foundation { + class IDOMReader; + class IDOMWriter; +} +} + +namespace qt3ds { +namespace state { + + struct SExecutableContent; + class CXMLIO + { + public: + typedef eastl::map<eastl::string, eastl::string> TIdRemapMap; + + static void GenerateUniqueId(SStateNode &inNode, const char8_t *inStem, + IStateContext &ioContext, IStringTable &ioStringTable); + static void GenerateUniqueId(SSend &inNode, const char8_t *inStem, IStateContext &ioContext, + IStringTable &ioStringTable); + // Load an SCXML file and return the root states + // All states, transitions, and memory used in the graph is allocated using the graph + // allocator. + // This makes freeing the memory much easier because you can just free it by releasing the + // graph + // allocator and you don't have to go object by object. + // Filename is just used as another tag or name on the scxml object. + static bool LoadSCXMLFile(NVAllocatorCallback &inGraphAllocator, NVFoundationBase &inFnd, + IDOMReader &inReader, IStringTable &inStringTable, + const char8_t *inFilename, IStateContext &outContext, + editor::IEditor *inEditor = NULL); + + // Loading fragments remaps their ids to avoid conflics. Returns the top nodes from the + // scxml graph. + static eastl::pair<eastl::vector<SStateNode *>, TIdRemapMap> + LoadSCXMLFragment(NVAllocatorCallback &inGraphAllocator, NVFoundationBase &inFnd, + IDOMReader &inReader, IStringTable &inStringTable, + IStateContext &ioContext, editor::IEditor &inEditor); + + // We write all the way to file instead of a DOM writer because we have to add xml + // namespaces and those can only + // be added during the actual serialization process. + static void SaveSCXMLFile(IStateContext &inContext, NVFoundationBase &inFnd, + IStringTable &inStringTable, IOutStream &outStream, + editor::IEditor *inEditor = NULL); + + static void FindRoots(NVConstDataRef<SStateNode *> inObjects, + eastl::vector<SStateNode *> &outRoots); + + // Returns the roots of the copied list + static eastl::vector<SStateNode *> + SaveSCXMLFragment(IStateContext &inContext, NVFoundationBase &inFnd, + IStringTable &inStringTable, IDOMWriter &ioWriter, + NVDataRef<SStateNode *> inObjects, editor::IEditor &inEditor, + const QT3DSVec2 &inMousePos, eastl::vector<SNamespacePair> &outNamespaces); + + static void ToEditableXml(IStateContext &inContext, NVFoundationBase &inFnd, + IStringTable &inStringTable, IDOMWriter &ioWriter, + SExecutableContent &inContent, editor::IEditor &inEditor); + + static SExecutableContent * + FromEditableXML(IDOMReader &inReader, NVFoundationBase &inFnd, IStateContext &inContext, + IStringTable &inStringTable, NVAllocatorCallback &inGraphAllocator, + editor::IEditor &inEditor, SStateNode *inStateNodeParent, + SExecutableContent *inExecContentParent); + + static eastl::vector<eastl::string> GetSupportedExecutableContentNames(); + }; +} +} + +#endif
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebugger.h b/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebugger.h new file mode 100644 index 00000000..38a0b74f --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebugger.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_SCENE_GRAPH_DEBUGGER_H +#define QT3DS_SCENE_GRAPH_DEBUGGER_H +#pragma once +#include "Qt3DSStateDebugger.h" +#include "Qt3DSUIADatamodel.h" + +namespace qt3ds { +namespace state { + namespace debugger { + + struct SSGValue; + + struct SSGPropertyChange; + + struct SGElemIdMap + { + void *m_Elem; + const char *m_Id; + }; + + // Persistant item that sticks around and appends some information to the binary file. + // The runtime debugger must exist for the entire time the runtime is running, not just + // during connection because the mapping from element->id only exists at file loading time. + class ISceneGraphRuntimeDebugger : public NVRefCounted, public IDebugStreamListener + { + public: + static const char *GetProtocolName() { return "Scene Graph Debugger"; } + // Nothing is returned if the object isn't connected. The returned value may not be + // valid + // after next GetOrCreateCall, so don't hold on to it. + virtual void MapPresentationId(void *presentation, const char *id) = 0; + virtual void MapElementIds(void *presentation, NVConstDataRef<SGElemIdMap> inIds) = 0; + virtual void OnPropertyChanged(void *elem, + NVConstDataRef<SSGPropertyChange> changes) = 0; + virtual void OnConnection(IDebugOutStream &outStream) = 0; + virtual void BinarySave(IOutStream &stream) = 0; + // Load the main datastructures, although we know the ids are wrong + virtual void BinaryLoad(IInStream &stream, NVDataRef<QT3DSU8> inStringTableData) = 0; + // Remap the presentation element points using id to map old presentation ptr to new + // presentation ptr. + virtual void BinaryLoadPresentation(void *presentation, const char *id, + size_t inElemOffset) = 0; + virtual void EndFrame() = 0; + virtual bool IsConnected() = 0; + + static ISceneGraphRuntimeDebugger &Create(NVFoundationBase &fnd, + IStringTable &strTable); + }; + + class ISceneGraphArchitectDebuggerListener + { + public: + virtual void OnItemsDirty(NVConstDataRef<app::SAppElement *> inDirtySet) = 0; + }; + + // The architect debugger only exists when debugging. + class ISceneGraphArchitectDebugger : public NVRefCounted, public IDebugStreamListener + { + public: + virtual void SetListener(ISceneGraphArchitectDebuggerListener *listener) = 0; + // Note that we wrap the att or arg list and the initial values to provide extra + // information. + virtual Q3DStudio::TAttOrArgList GetElementAttributes(app::SAppElement &elem) = 0; + + // These may be empty, so don't expect them. Also they are all string, no registered + // strings + // regardless of the type. + virtual eastl::vector<app::SDatamodelValue> + GetElementAttributeValues(app::SAppElement &elem) = 0; + virtual app::IDatamodel &GetDatamodel() = 0; + + virtual void AttachToStream(IDebugOutStream &inStream) = 0; + + virtual void RefreshData(bool inNeedReloadData) = 0; + + static ISceneGraphArchitectDebugger &Create(qt3ds::app::IDatamodel &inDatamodel); + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebuggerProtocol.h b/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebuggerProtocol.h new file mode 100644 index 00000000..4fea4322 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebuggerProtocol.h @@ -0,0 +1,375 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_SCENE_GRAPH_DEBUGGER_PROTOCOL_H +#define QT3DS_SCENE_GRAPH_DEBUGGER_PROTOCOL_H +#include "Qt3DSSceneGraphDebugger.h" +#include "Qt3DSSceneGraphDebuggerValue.h" +#include "foundation/Qt3DSMemoryBuffer.h" +#include "foundation/SerializationTypes.h" + +namespace qt3ds { +namespace state { + namespace debugger { + + // These are the datatypes we will communicate information with + static QT3DSU32 GetSceneGraphProtocolVersion() { return 1; } + + struct SValueUpdate + { + QT3DSI32 m_Hash; + SSGValue m_Value; + SValueUpdate(QT3DSI32 h, const SSGValue &v) + : m_Hash(h) + , m_Value(v) + { + } + SValueUpdate() + : m_Hash(0) + { + } + template <typename Listener> + void IterateProperties(Listener &inListener) + { + inListener.Handle(m_Hash); + inListener.Handle(m_Value); + }; + }; + + struct SElemUpdate + { + QT3DSU64 m_Elem; + NVDataRef<SValueUpdate> m_Updates; + template <typename Listener> + void IterateProperties(Listener &inListener) + { + inListener.Handle(m_Elem); + inListener.HandleRef(m_Updates); + }; + }; + + struct SElemMap + { + QT3DSU64 m_Elem; + CRegisteredString m_Id; + SElemMap() + : m_Elem(0) + { + } + SElemMap(QT3DSU64 ptr, CRegisteredString name) + : m_Elem(ptr) + , m_Id(name) + { + } + + template <typename Listener> + void IterateProperties(Listener &inListener) + { + inListener.Handle(m_Elem); + inListener.Handle(m_Id); + } + }; + + struct SIdUpdate + { + QT3DSU64 m_Presentation; + CRegisteredString m_PresentationId; + NVDataRef<SElemMap> m_IdUpdates; + + template <typename Listener> + void IterateProperties(Listener &inListener) + { + inListener.Handle(m_Presentation); + inListener.Handle(m_PresentationId); + inListener.HandleRef(m_IdUpdates); + } + }; + + struct SSGProtocolMessageTypes + { + enum Enum { + UnknownMessage = 0, + Initialization, + IdUpdate, + ElemUpdate, + Frame, + }; + }; + + // Implemented on runtime side. + struct SSGProtocolWriter + { + IOutStream &m_Stream; + MemoryBuffer<> m_WriteBuffer; + QT3DSU32 m_HighWaterMark; + + SSGProtocolWriter(IOutStream &s, NVAllocatorCallback &alloc, QT3DSU32 highWaterMark = 4096) + : m_Stream(s) + , m_WriteBuffer(ForwardingAllocator(alloc, "WriteBuffer")) + , m_HighWaterMark(highWaterMark) + { + } + + void Handle(QT3DSU64 data) { m_WriteBuffer.write(data); } + + void Handle(QT3DSI32 data) { m_WriteBuffer.write(data); } + + void Handle(CRegisteredString str) + { + QT3DSU32 len = static_cast<QT3DSU32>(strlen(str.c_str()) + 1); + m_WriteBuffer.write(len); + m_WriteBuffer.write(str.c_str(), len); + } + void Handle(SSGValue &value) + { + QT3DSU32 valType = static_cast<QT3DSU32>(value.getType()); + m_WriteBuffer.write(valType); + switch (value.getType()) { + case SGPropertyValueTypes::Float: + m_WriteBuffer.write(value.getData<float>()); + break; + case SGPropertyValueTypes::I32: + m_WriteBuffer.write(value.getData<QT3DSI32>()); + break; + case SGPropertyValueTypes::String: + Handle(value.getData<CRegisteredString>()); + break; + case SGPropertyValueTypes::Elem: + Handle(value.getData<QT3DSU64>()); + break; + case SGPropertyValueTypes::NoSGValue: + break; + default: + QT3DS_ASSERT(false); + } + } + + template <typename TDataType> + void HandleRef(NVDataRef<TDataType> &ref) + { + m_WriteBuffer.write(ref.size()); + for (QT3DSU32 idx = 0, end = ref.size(); idx < end; ++idx) + Handle(ref[idx]); + } + + template <typename TDataType> + void Handle(TDataType &dtype) + { + dtype.IterateProperties(*this); + } + + void Flush() + { + if (m_WriteBuffer.size()) { + NVConstDataRef<QT3DSU8> writeData(m_WriteBuffer); + m_Stream.Write(writeData); + m_WriteBuffer.clear(); + } + } + + void CheckBuffer() + { + if (m_WriteBuffer.size() > m_HighWaterMark) + Flush(); + } + + void Write(SIdUpdate &inIdUpdate) + { + m_WriteBuffer.write((QT3DSU32)SSGProtocolMessageTypes::IdUpdate); + inIdUpdate.IterateProperties(*this); + Flush(); + } + + void Write(SElemUpdate &inIdUpdate) + { + m_WriteBuffer.write((QT3DSU32)SSGProtocolMessageTypes::ElemUpdate); + inIdUpdate.IterateProperties(*this); + CheckBuffer(); + } + + void WriteInitialization() + { + m_WriteBuffer.write((QT3DSU32)SSGProtocolMessageTypes::Initialization); + m_WriteBuffer.write(GetSceneGraphProtocolVersion()); + Flush(); + } + void WriteFrame() + { + m_WriteBuffer.write((QT3DSU32)SSGProtocolMessageTypes::Frame); + Flush(); + } + }; + + struct SSGProtocolReader + { + NVConstDataRef<QT3DSU8> m_Message; + SDataReader m_Reader; + IStringTable &m_StringTable; + eastl::vector<QT3DSU8> m_DataBuffer; + eastl::string m_TempString; + QT3DSU32 m_Allocated; + bool m_RestartRead; + SSGProtocolReader(NVConstDataRef<QT3DSU8> msg, IStringTable &strTable) + : m_Message(msg) + , m_Reader(const_cast<QT3DSU8 *>(msg.begin()), const_cast<QT3DSU8 *>(msg.end())) + , m_StringTable(strTable) + , m_Allocated(0) + , m_RestartRead(false) + { + } + + SSGProtocolMessageTypes::Enum MessageType() + { + QT3DSU32 data = m_Reader.LoadRef<QT3DSU32>(); + return static_cast<SSGProtocolMessageTypes::Enum>(data); + } + + template <typename TDataType> + Option<NVDataRef<TDataType>> AllocateData(size_t size) + { + if (m_RestartRead) + return Empty(); + if (size == 0) + return NVDataRef<TDataType>(); + + QT3DSU32 current = m_Allocated; + QT3DSU32 newAlloc = (QT3DSU32)(size * sizeof(TDataType)); + // 8 byte align + if (newAlloc % 8) + newAlloc += 8 - (newAlloc % 8); + + QT3DSU32 required = current + newAlloc; + + if (required > m_DataBuffer.size()) { + m_RestartRead = true; + m_DataBuffer.resize(required * 2); + return Empty(); + } + TDataType *offset = reinterpret_cast<TDataType *>(&m_DataBuffer[current]); + m_Allocated += newAlloc; + return toDataRef(offset, (QT3DSU32)size); + } + + void Handle(QT3DSU64 &data) { data = m_Reader.LoadRef<QT3DSU64>(); } + + void Handle(QT3DSI32 &data) { data = m_Reader.LoadRef<QT3DSI32>(); } + + void Handle(CRegisteredString &str) + { + QT3DSU32 len = m_Reader.LoadRef<QT3DSU32>(); + m_TempString.clear(); + if (len) + m_TempString.assign((const char *)m_Reader.m_CurrentPtr, (size_t)(len - 1)); + m_Reader.m_CurrentPtr += len; + if (m_Reader.m_CurrentPtr > m_Reader.m_EndPtr) + m_Reader.m_CurrentPtr = m_Reader.m_EndPtr; + + str = m_StringTable.RegisterStr(m_TempString.c_str()); + } + + void Handle(SSGValue &value) + { + QT3DSU32 valType = m_Reader.LoadRef<QT3DSU32>(); + switch (valType) { + case SGPropertyValueTypes::Float: + value = SSGValue(m_Reader.LoadRef<float>()); + break; + case SGPropertyValueTypes::I32: + value = SSGValue(m_Reader.LoadRef<QT3DSI32>()); + break; + case SGPropertyValueTypes::String: { + CRegisteredString temp; + Handle(temp); + value = SSGValue(temp); + } break; + case SGPropertyValueTypes::Elem: + value = m_Reader.LoadRef<QT3DSU64>(); + break; + case SGPropertyValueTypes::NoSGValue: + break; + default: + QT3DS_ASSERT(false); + } + } + + template <typename TDataType> + void HandleRef(NVDataRef<TDataType> &ref) + { + QT3DSU32 numItems = m_Reader.LoadRef<QT3DSU32>(); + Option<NVDataRef<TDataType>> refOpt = AllocateData<TDataType>(numItems); + if (refOpt.hasValue()) { + ref = *refOpt; + for (QT3DSU32 idx = 0, end = ref.size(); idx < end && m_RestartRead == false; + ++idx) + Handle(ref[idx]); + } + } + + template <typename TDataType> + void Handle(TDataType &dtype) + { + dtype.IterateProperties(*this); + } + + template <typename TDataType> + void DoRead(TDataType &ioValue) + { + QT3DSU8 *startPtr = m_Reader.m_CurrentPtr; + QT3DSU32 restartCount = 0; + do { + m_RestartRead = false; + m_Allocated = 0; + m_Reader.m_CurrentPtr = startPtr; + ioValue.IterateProperties(*this); + ++restartCount; + } while (m_RestartRead); + } + + SIdUpdate ReadIdUpdate() + { + SIdUpdate retval; + DoRead(retval); + return retval; + }; + + SElemUpdate ReadElemUpdate() + { + SElemUpdate retval; + DoRead(retval); + return retval; + }; + + QT3DSU32 ReadInitialization() { return m_Reader.LoadRef<QT3DSU32>(); } + + bool Finished() { return m_Reader.m_CurrentPtr >= m_Reader.m_EndPtr; } + }; + } +} +} +#endif diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebuggerValue.h b/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebuggerValue.h new file mode 100644 index 00000000..3b6a4172 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphDebuggerValue.h @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_SCENE_GRAPH_DEBUGGER_VALUE_H +#define QT3DS_SCENE_GRAPH_DEBUGGER_VALUE_H +#include "Qt3DSSceneGraphDebugger.h" +#include "Qt3DSUIADatamodel.h" +#include "Qt3DSUIADatamodelValue.h" +#include "Qt3DSStateEditorValue.h" + +namespace qt3ds { +namespace state { + namespace debugger { + struct SGPropertyValueTypes + { + enum Enum { + NoSGValue = 0, + Float, + I32, + String, + Elem, + }; + }; + + template <typename dtype> + struct SGValueTypeMap + { + }; + + template <> + struct SGValueTypeMap<float> + { + static SGPropertyValueTypes::Enum GetType() { return SGPropertyValueTypes::Float; } + }; + + template <> + struct SGValueTypeMap<QT3DSI32> + { + static SGPropertyValueTypes::Enum GetType() { return SGPropertyValueTypes::I32; } + }; + + template <> + struct SGValueTypeMap<CRegisteredString> + { + static SGPropertyValueTypes::Enum GetType() { return SGPropertyValueTypes::String; } + }; + + template <> + struct SGValueTypeMap<QT3DSU64> + { + static SGPropertyValueTypes::Enum GetType() { return SGPropertyValueTypes::Elem; } + }; + + struct SSGValueUnionTraits + { + typedef SGPropertyValueTypes::Enum TIdType; + enum { + TBufferSize = sizeof(QT3DSU64), + }; + + static TIdType getNoDataId() { return SGPropertyValueTypes::NoSGValue; } + + template <typename TDataType> + static TIdType getType() + { + return SGValueTypeMap<TDataType>().GetType(); + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case SGPropertyValueTypes::Float: + return inVisitor(*NVUnionCast<float *>(inData)); + case SGPropertyValueTypes::I32: + return inVisitor(*NVUnionCast<QT3DSI32 *>(inData)); + case SGPropertyValueTypes::String: + return inVisitor(*NVUnionCast<CRegisteredString *>(inData)); + case SGPropertyValueTypes::Elem: + return inVisitor(*NVUnionCast<QT3DSU64 *>(inData)); + default: + QT3DS_ASSERT(false); + case SGPropertyValueTypes::NoSGValue: + return inVisitor(); + } + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case SGPropertyValueTypes::Float: + return inVisitor(*NVUnionCast<const float *>(inData)); + case SGPropertyValueTypes::I32: + return inVisitor(*NVUnionCast<const QT3DSI32 *>(inData)); + case SGPropertyValueTypes::String: + return inVisitor(*NVUnionCast<const CRegisteredString *>(inData)); + case SGPropertyValueTypes::Elem: + return inVisitor(*NVUnionCast<const QT3DSU64 *>(inData)); + default: + QT3DS_ASSERT(false); + case SGPropertyValueTypes::NoSGValue: + return inVisitor(); + } + } + }; + + typedef qt3ds::foundation:: + DiscriminatedUnion<qt3ds::foundation:: + DiscriminatedUnionGenericBase<SSGValueUnionTraits, + SSGValueUnionTraits::TBufferSize>, + SSGValueUnionTraits::TBufferSize> + TSGValueUnionType; + + struct SSGValue : public TSGValueUnionType + { + SSGValue() {} + SSGValue(const SSGValue &other) + : TSGValueUnionType(static_cast<const TSGValueUnionType &>(other)) + { + } + SSGValue(float val) + : TSGValueUnionType(val) + { + } + SSGValue(QT3DSI32 val) + : TSGValueUnionType(val) + { + } + SSGValue(CRegisteredString val) + : TSGValueUnionType(val) + { + } + SSGValue(QT3DSU64 val) + : TSGValueUnionType(val) + { + } + + SSGValue &operator=(const SSGValue &other) + { + TSGValueUnionType::operator=(static_cast<const TSGValueUnionType &>(other)); + return *this; + } + }; + + struct SSGPropertyChange + { + QT3DSI32 m_Hash; + SSGValue m_Value; + SSGPropertyChange(QT3DSI32 h, const SSGValue &v) + : m_Hash(h) + , m_Value(v) + { + } + SSGPropertyChange() + : m_Hash(0) + { + } + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphRuntimeDebugger.cpp b/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphRuntimeDebugger.cpp new file mode 100644 index 00000000..403fb580 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSSceneGraphRuntimeDebugger.cpp @@ -0,0 +1,345 @@ +/**************************************************************************** +** +** 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 "Qt3DSSceneGraphDebugger.h" +#include "Qt3DSSceneGraphDebuggerValue.h" +#include "Qt3DSSceneGraphDebuggerProtocol.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/StringTable.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/AutoDeallocatorAllocator.h" +#include "EASTL/sort.h" + +using namespace qt3ds::state; +using namespace qt3ds::state::debugger; + +namespace { + +typedef eastl::pair<QT3DSU64, CStringHandle> TElemStringHandlePair; + +struct SRegisteredIDComparator +{ + bool operator()(const TElemStringHandlePair &lhs, const TElemStringHandlePair &rhs) const + { + return lhs.first < rhs.first; + } +}; + +struct SPresentationIdMap +{ + void *m_Presentation; + CRegisteredString m_PresentationId; + nvvector<TElemStringHandlePair> m_RegisteredIdBackingStore; + NVDataRef<TElemStringHandlePair> m_RegisteredIds; + SPresentationIdMap(NVAllocatorCallback &alloc) + : m_RegisteredIdBackingStore(alloc, "registered ids") + { + } +}; + +struct RuntimeDebuggerImpl : public ISceneGraphRuntimeDebugger +{ + NVFoundationBase &m_Foundation; + IStringTable &m_StringTable; + NVScopedRefCounted<IDebugOutStream> m_OutStream; + nvvector<SElemMap> m_ElemMapBuffer; + nvvector<SPresentationIdMap *> m_PresentationIdMap; + nvvector<SValueUpdate> m_ValueUpdates; + // Filter mechanism so we don't send the same thing twice. + nvhash_map<void *, eastl::vector<SValueUpdate>> m_LastUpdates; + SSGProtocolWriter *m_Writer; + SSAutoDeallocatorAllocator m_DataAllocator; + nvhash_map<void *, CStringHandle> m_ElemToNameMap; + QT3DSI32 mRefCount; + + RuntimeDebuggerImpl(NVFoundationBase &fnd, IStringTable &strt) + : m_Foundation(fnd) + , m_StringTable(strt) + , m_ElemMapBuffer(fnd.getAllocator(), "elem map buffer") + , m_PresentationIdMap(fnd.getAllocator(), "Presentations") + , m_ValueUpdates(fnd.getAllocator(), "Value updates") + , m_LastUpdates(fnd.getAllocator(), "Last updates") + , m_Writer(NULL) + , m_DataAllocator(fnd) + , m_ElemToNameMap(fnd.getAllocator(), "ElemToNameMap") + , mRefCount(0) + { + } + + ~RuntimeDebuggerImpl() + { + Disconnect(); + for (QT3DSU32 idx = 0, end = m_PresentationIdMap.size(); idx < end; ++idx) + delete m_PresentationIdMap[idx]; + // the auto-deallocator takes care of the load data. + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + void Disconnect() + { + if (m_Writer) + delete m_Writer; + m_Writer = NULL; + } + + bool CheckConnection() + { + if (m_OutStream) { + bool connected = m_OutStream->Connected(); + if (!connected) + Disconnect(); + + return connected; + } + return false; + } + + void MapPresentationId(void *presentation, const char *id) override + { + CRegisteredString newId = m_StringTable.RegisterStr(nonNull(id)); + for (QT3DSU32 idx = 0, end = m_PresentationIdMap.size(); idx < end; ++idx) { + if (m_PresentationIdMap[idx]->m_Presentation == presentation) { + m_PresentationIdMap[idx]->m_PresentationId = newId; + return; + } + } + + SPresentationIdMap *map = new SPresentationIdMap(m_Foundation.getAllocator()); + map->m_Presentation = presentation; + map->m_PresentationId = newId; + m_PresentationIdMap.push_back(map); + } + + void SendElemIdMap(SPresentationIdMap &map) + { + if (m_ElemMapBuffer.size() && CheckConnection()) { + SIdUpdate theUpdate; + theUpdate.m_Presentation = (QT3DSU64)map.m_Presentation; + theUpdate.m_PresentationId = map.m_PresentationId; + theUpdate.m_IdUpdates = m_ElemMapBuffer; + m_Writer->Write(theUpdate); + } + m_ElemMapBuffer.clear(); + } + + void MapElementIds(void *presentation, NVConstDataRef<SGElemIdMap> inIds) override + { + SPresentationIdMap *map = NULL; + for (QT3DSU32 idx = 0, end = m_PresentationIdMap.size(); idx < end && map == NULL; ++idx) + if (m_PresentationIdMap[idx]->m_Presentation == presentation) + map = m_PresentationIdMap[idx]; + if (map == NULL) { + QT3DS_ASSERT(false); + return; + } + m_ElemMapBuffer.clear(); + m_ElemToNameMap.clear(); + for (QT3DSU32 idx = 0, end = inIds.size(); idx < end; ++idx) { + const SGElemIdMap &item(inIds[idx]); + SElemMap newMap; + newMap.m_Elem = (QT3DSU64)item.m_Elem; + CStringHandle idHandle = m_StringTable.GetHandle(nonNull(item.m_Id)); + newMap.m_Id = m_StringTable.HandleToStr(idHandle); + m_ElemMapBuffer.push_back(newMap); + m_ElemToNameMap[item.m_Elem] = idHandle; + } + SendElemIdMap(*map); + + // store them for later. + map->m_RegisteredIdBackingStore.reserve(m_ElemToNameMap.size()); + for (nvhash_map<void *, CStringHandle>::iterator iter = m_ElemToNameMap.begin(), + end = m_ElemToNameMap.end(); + iter != end; ++iter) { + map->m_RegisteredIdBackingStore.push_back( + eastl::make_pair((QT3DSU64)iter->first, iter->second)); + } + + eastl::sort(map->m_RegisteredIdBackingStore.begin(), map->m_RegisteredIdBackingStore.end(), + SRegisteredIDComparator()); + + map->m_RegisteredIds = NVDataRef<TElemStringHandlePair>( + map->m_RegisteredIdBackingStore.data(), (QT3DSU32)map->m_RegisteredIdBackingStore.size()); + } + + static bool Equals(const SValueUpdate &lhs, const SValueUpdate &rhs) + { + return lhs.m_Hash == rhs.m_Hash && lhs.m_Value == rhs.m_Value; + } + + void OnPropertyChanged(void *elem, NVConstDataRef<SSGPropertyChange> changes) override + { + if (CheckConnection() == false) + return; + eastl::vector<SValueUpdate> &updates = m_LastUpdates[elem]; + updates.resize(changes.size()); + for (QT3DSU32 changeIdx = 0, changeEnd = changes.size(); changeIdx < changeEnd; ++changeIdx) { + SValueUpdate theUpdate; + theUpdate.m_Hash = changes[changeIdx].m_Hash; + theUpdate.m_Value = changes[changeIdx].m_Value; + if (Equals(theUpdate, updates[changeIdx]) == false) { + updates[changeIdx] = theUpdate; + m_ValueUpdates.push_back(theUpdate); + } + } + if (m_ValueUpdates.size()) { + SElemUpdate theUpdate; + theUpdate.m_Elem = (QT3DSU64)elem; + theUpdate.m_Updates = m_ValueUpdates; + m_Writer->Write(theUpdate); + m_ValueUpdates.clear(); + } + } + + void SendPresentation(SPresentationIdMap &pres) + { + if (m_OutStream) { + m_ElemMapBuffer.clear(); + for (TElemStringHandlePair *iter = pres.m_RegisteredIds.begin(), + *end = pres.m_RegisteredIds.end(); + iter != end; ++iter) { + SElemMap newMap; + newMap.m_Elem = (QT3DSU64)iter->first; + newMap.m_Id = m_StringTable.HandleToStr(iter->second); + m_ElemMapBuffer.push_back(newMap); + } + SendElemIdMap(pres); + } + } + + void OnConnection(IDebugOutStream &outStream) override + { + Disconnect(); + m_OutStream = outStream; + m_Writer = new SSGProtocolWriter(outStream, m_Foundation.getAllocator()); + m_Writer->WriteInitialization(); + for (QT3DSU32 idx = 0, end = m_PresentationIdMap.size(); idx < end; ++idx) { + SPresentationIdMap *map = m_PresentationIdMap[idx]; + SendPresentation(*map); + } + } + + bool IsConnected() override { return CheckConnection(); } + + void BinarySave(IOutStream &ioStream) override + { + qt3ds::foundation::SWriteBuffer theWriteBuffer(m_Foundation.getAllocator(), + "BinarySave::writebuffer"); + + theWriteBuffer.writeZeros(4); // Overall binary size + theWriteBuffer.write((QT3DSU32)m_PresentationIdMap.size()); + for (QT3DSU32 idx = 0, end = m_PresentationIdMap.size(); idx < end; ++idx) { + // There is no use to writing out the presentation pointer address. + /* + void* + m_Presentation; + CRegisteredString m_PresentationId; + nvhash_map<void*, CRegisteredString> m_RegisteredIds; + */ + + SPresentationIdMap *map = m_PresentationIdMap[idx]; + CRegisteredString presId(map->m_PresentationId); + presId.Remap(m_StringTable.GetRemapMap()); + theWriteBuffer.write(presId); + theWriteBuffer.write((QT3DSU32)map->m_RegisteredIds.size()); + theWriteBuffer.write(map->m_RegisteredIds.begin(), map->m_RegisteredIds.size()); + } + + QT3DSU32 totalSize = theWriteBuffer.size(); + QT3DSU32 *data = (QT3DSU32 *)theWriteBuffer.begin(); + data[0] = totalSize - 4; + ioStream.Write((QT3DSU8 *)data, totalSize); + } + + void BinaryLoad(IInStream &ioStream, NVDataRef<QT3DSU8> strTableData) override + { + QT3DSU32 length; + ioStream.Read(length); + QT3DSU8 *data = (QT3DSU8 *)m_DataAllocator.allocate(length, "Binaryload", __FILE__, __LINE__); + ioStream.Read(data, length); + SDataReader theReader(data, data + length); + QT3DSU32 numPresentations = theReader.LoadRef<QT3DSU32>(); + QT3DS_ASSERT(m_PresentationIdMap.size() == 0); + m_PresentationIdMap.resize(numPresentations); + for (QT3DSU32 idx = 0, end = numPresentations; idx < end; ++idx) { + m_PresentationIdMap[idx] = new SPresentationIdMap(m_Foundation.getAllocator()); + SPresentationIdMap *map = m_PresentationIdMap[idx]; + map->m_PresentationId = theReader.LoadRef<CRegisteredString>(); + map->m_PresentationId.Remap(strTableData); + QT3DSU32 numElems = theReader.LoadRef<QT3DSU32>(); + map->m_RegisteredIds = + toDataRef((TElemStringHandlePair *)theReader.m_CurrentPtr, numElems); + theReader.m_CurrentPtr += numElems * sizeof(TElemStringHandlePair); + } + } + + void BinaryLoadPresentation(void *presPtr, const char *id, size_t elemOffset) override + { + CRegisteredString presId(m_StringTable.RegisterStr(id)); + SPresentationIdMap *foundPres = NULL; + for (QT3DSU32 idx = 0, end = (QT3DSU32)m_PresentationIdMap.size(); idx < end && foundPres == NULL; + ++idx) { + if (m_PresentationIdMap[idx]->m_PresentationId == presId) + foundPres = m_PresentationIdMap[idx]; + } + if (foundPres == NULL) { + QT3DS_ASSERT(false); + return; + } + + foundPres->m_Presentation = presPtr; + nvvector<eastl::pair<void *, CRegisteredString>> newElemIds(m_Foundation.getAllocator(), + "Temp load map"); + newElemIds.reserve(foundPres->m_RegisteredIds.size()); + for (TElemStringHandlePair *iter = foundPres->m_RegisteredIds.begin(), + *end = foundPres->m_RegisteredIds.end(); + iter != end; ++iter) { + size_t oldId = (size_t)iter->first; + size_t newId = elemOffset + oldId; + iter->first = (QT3DSU64)newId; + } + SendPresentation(*foundPres); + } + + void EndFrame() override + { + if (CheckConnection()) + m_Writer->WriteFrame(); + } + + void OnMessageReceived(const SDebugStreamMessage &) override { QT3DS_ASSERT(false); } +}; +} + +ISceneGraphRuntimeDebugger &ISceneGraphRuntimeDebugger::Create(NVFoundationBase &fnd, + IStringTable &strTable) +{ + return *QT3DS_NEW(fnd.getAllocator(), RuntimeDebuggerImpl)(fnd, strTable); +} diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDataTest.cpp b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDataTest.cpp new file mode 100644 index 00000000..ced1397b --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDataTest.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateTest.h" +#include "foundation/IOStreams.h" +#include "EASTL/string.h" +#include "foundation/Utils.h" +#include "foundation/FileTools.h" +#include "foundation/XML.h" +#include "foundation/Qt3DSAllocator.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/TrackingAllocator.h" +#include "foundation/StringTable.h" +#include "Qt3DSStateContext.h" +#include "foundation/AutoDeallocatorAllocator.h" +#include "Qt3DSStateExecutionContext.h" +#include "Qt3DSStateInterpreter.h" + +using namespace qt3ds::state::test; +using namespace qt3ds::state; + +namespace { + +struct XMLHandler : public qt3ds::foundation::CXmlErrorHandler +{ + IDataLogger &m_Logger; + const char8_t *m_File; + eastl::string m_ErrorString; + XMLHandler(IDataLogger &logger, const char8_t *fname) + : m_Logger(logger) + , m_File(fname) + { + } + + void OnXmlError(qt3ds::foundation::TXMLCharPtr errorName, int line, int /*column*/) override + { + m_ErrorString.assign("Failed to parse test file: "); + m_ErrorString.append(m_File); + m_Logger.Log(LogType::Error, m_File, line, errorName); + } +}; + +Option<STestResults> RunTest(const char8_t *inFullPath, const char8_t *inRoot, + IDataLogger &inLogger) +{ + return STestResults(1, 1); +} +} + +Option<STestResults> IDataTest::RunFile(const char8_t *fname, const char8_t *inRootDir, + IDataLogger &inLogger) +{ + return RunTest(fname, inRootDir, inLogger); +} diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugStreams.cpp b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugStreams.cpp new file mode 100644 index 00000000..707ea73e --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugStreams.cpp @@ -0,0 +1,534 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateDebugStreams.h" +#include "foundation/StringTable.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/Qt3DSFlags.h" +#include "foundation/Qt3DSMutex.h" +#include "foundation/Qt3DSSync.h" +#include "foundation/Qt3DSMemoryBuffer.h" +#include "EASTL/string.h" + +using namespace qt3ds; +using namespace qt3ds::state; +using namespace qt3ds::state::debugger; + +namespace { + +struct MultiProtocolMessageTypes +{ + enum Enum { + UnknownMessageType = 0, + NewProtocol = 1, + ProtocolMessage = 1 << 2, + }; +}; + +struct SMultiProtocolMessageFlags : public NVFlags<MultiProtocolMessageTypes::Enum, QT3DSU32> +{ + bool IsNewProtocol() { return this->operator&(MultiProtocolMessageTypes::NewProtocol); } + void SetNewProtocol(bool inValue) + { + this->clearOrSet(inValue, MultiProtocolMessageTypes::NewProtocol); + } + + bool IsProtocolMessage() { return this->operator&(MultiProtocolMessageTypes::ProtocolMessage); } + void SetProtocolMessage(bool inValue) + { + this->clearOrSet(inValue, MultiProtocolMessageTypes::ProtocolMessage); + } +}; + +struct SMultiProtocolInitializer +{ + static QT3DSU16 GetCurrentMultiProtocolVersion() { return 1; } + + QT3DSU64 m_TimeNumerator; + QT3DSU64 m_TimeDenominator; + QT3DSU32 m_ProtocolVersion; + + SMultiProtocolInitializer() + : m_TimeNumerator(Time::sCounterFreq.mNumerator) + , m_TimeDenominator(Time::sCounterFreq.mDenominator) + , m_ProtocolVersion(GetCurrentMultiProtocolVersion()) + { + } +}; + +struct SMultiProtocolMessageHeader +{ + + SMultiProtocolMessageFlags m_Flags; + QT3DSU32 m_Size; + QT3DSU32 m_ProtocolId; + QT3DSU64 m_Timestamp; + SMultiProtocolMessageHeader(MultiProtocolMessageTypes::Enum inMessageType, QT3DSU32 size, + QT3DSU32 protocolId, QT3DSU64 timestamp) + : m_Size(size) + , m_ProtocolId(protocolId) + , m_Timestamp(timestamp) + { + m_Flags.clearOrSet(true, inMessageType); + } + SMultiProtocolMessageHeader() {} +}; + +struct IProtocolMessageHandler +{ +protected: + virtual ~IProtocolMessageHandler() {} +public: + virtual void OnMessageReceived(SDebugStreamMessage msgData) = 0; +}; + +struct IProtocolHandler +{ +protected: + virtual ~IProtocolHandler() {} +public: + virtual void OnNewProtocol(CRegisteredString inProtocolName) = 0; +}; + +struct SSharedStreamImpl : public NVRefCounted +{ + NVFoundationBase &m_Foundation; + NVScopedRefCounted<SocketStream> m_Stream; + IOutStream &m_WriteStream; + SMultiProtocolInitializer m_Initializer; + eastl::hash_map<CRegisteredString, QT3DSU32> m_ProtocolIdMap; + eastl::hash_map<QT3DSU32, IProtocolMessageHandler *> m_MessageHandlers; + MemoryBuffer<> m_ReadBuffer; + IStringTable &m_StringTable; + IProtocolHandler *m_ProtocolHandler; + QT3DSU32 m_NextProtocolId; + QT3DSI32 mRefCount; + + SSharedStreamImpl(NVFoundationBase &fnd, SocketStream &stream, IOutStream &writeStream, + IStringTable &strTable, IProtocolHandler &pHandler) + : m_Foundation(fnd) + , m_Stream(stream) + , m_WriteStream(writeStream) + , m_ReadBuffer(ForwardingAllocator(fnd.getAllocator(), "ReadBuffer")) + , m_StringTable(strTable) + , m_ProtocolHandler(&pHandler) + , m_NextProtocolId(1) + , mRefCount(0) + { + NVConstDataRef<QT3DSU8> msgData = toU8DataRef(m_Initializer); + bool streamValid = m_Stream->Write(msgData); + if (streamValid == false) + m_Stream = NULL; + } + ~SSharedStreamImpl() {} + + bool Initialize() + { + if (m_Stream) { + NVDataRef<QT3DSU8> msgData = toU8DataRef(m_Initializer); + QT3DSU32 numBytes = m_Stream->Read(msgData); + if (numBytes != sizeof(SMultiProtocolInitializer) + || m_Initializer.m_ProtocolVersion + > SMultiProtocolInitializer::GetCurrentMultiProtocolVersion()) { + m_Stream = NULL; + return false; + } + return true; + } + return false; + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + QT3DSU32 GetIdForProtocol(CRegisteredString protocol) + { + if (protocol.IsValid() == false) + return 0; + eastl::pair<eastl::hash_map<CRegisteredString, QT3DSU32>::iterator, bool> inserter = + m_ProtocolIdMap.insert(eastl::make_pair(protocol, m_NextProtocolId)); + if (inserter.second) { + QT3DSU32 newId = m_NextProtocolId; + ++m_NextProtocolId; + if (m_Stream) { + QT3DSU32 msgLen = (QT3DSU32)strlen(protocol) + 1; + NVConstDataRef<QT3DSU8> writeBuf(reinterpret_cast<const QT3DSU8 *>(protocol.c_str()), + msgLen); + WriteMessage(MultiProtocolMessageTypes::NewProtocol, writeBuf, newId); + } + } + return inserter.first->second; + } + + CRegisteredString GetProtocolForId(QT3DSU32 id) + { + for (eastl::hash_map<CRegisteredString, QT3DSU32>::iterator iter = m_ProtocolIdMap.begin(), + end = m_ProtocolIdMap.end(); + iter != end; ++iter) { + if (iter->second == id) + return iter->first; + } + return CRegisteredString(); + } + + void AddMessageHandler(CRegisteredString protocol, IProtocolMessageHandler &hdl) + { + m_MessageHandlers.insert(eastl::make_pair(GetIdForProtocol(protocol), &hdl)); + } + + void RemoveMessageHandler(CRegisteredString protocol) + { + m_MessageHandlers.erase(GetIdForProtocol(protocol)); + } + + IProtocolMessageHandler *GetMessageHandler(CRegisteredString protocol) + { + return GetMessageHandler(GetIdForProtocol(protocol)); + } + + IProtocolMessageHandler *GetMessageHandler(QT3DSU32 protocolId) + { + eastl::hash_map<QT3DSU32, IProtocolMessageHandler *>::iterator iter = + m_MessageHandlers.find(protocolId); + if (iter != m_MessageHandlers.end()) + return iter->second; + return NULL; + } + + void ProtocolHandlerLeaving() { m_ProtocolHandler = NULL; } + + void DispatchMessage(SMultiProtocolMessageHeader inHeader, NVConstDataRef<QT3DSU8> msg) + { + if (inHeader.m_Flags.IsNewProtocol()) { + char *pId = reinterpret_cast<char *>(const_cast<QT3DSU8 *>(msg.begin())); + // Ensure null terminated, which should be done anyway but we don't know it will be. + pId[inHeader.m_Size] = 0; + CRegisteredString protocolName = m_StringTable.RegisterStr(pId); + eastl::pair<eastl::hash_map<CRegisteredString, QT3DSU32>::iterator, bool> inserter = + m_ProtocolIdMap.insert(eastl::make_pair(protocolName, inHeader.m_ProtocolId)); + if (inserter.second == false) { + // remap id to higher id to reduce the chance of conflicts. + QT3DSU32 potentialNewId = NVMax(inserter.first->second, inHeader.m_ProtocolId); + if (potentialNewId != inserter.first->second) { + m_NextProtocolId = NVMax(m_NextProtocolId, potentialNewId + 1); + CRegisteredString existing = GetProtocolForId(potentialNewId); + if (existing.IsValid()) { + m_ProtocolIdMap.erase(protocolName); + GetIdForProtocol(protocolName); + return; + } else { + inserter.first->second = potentialNewId; + } + } + } else { + if (m_ProtocolHandler != NULL) { + m_ProtocolHandler->OnNewProtocol(protocolName); + } + } + } else { + IProtocolMessageHandler *handler = GetMessageHandler(inHeader.m_ProtocolId); + if (handler != NULL) + handler->OnMessageReceived(SDebugStreamMessage(inHeader.m_Timestamp, msg)); + } + } + + NVDataRef<QT3DSU8> ReadChunk(NVDataRef<QT3DSU8> target) + { + QT3DSU32 totalRead = 0; + do { + NVDataRef<QT3DSU8> nextBuf(target.begin() + totalRead, target.size() - totalRead); + QT3DSU32 readResult = m_Stream->Read(nextBuf); + totalRead += readResult; + if (totalRead < target.size()) { + totalRead = totalRead; + } + } while (connected() && totalRead < target.size()); + + return toDataRef(m_ReadBuffer.begin(), totalRead); + } + + NVDataRef<QT3DSU8> ReadChunk(QT3DSU32 size) + { + m_ReadBuffer.reserve(size); + return ReadChunk(toDataRef(m_ReadBuffer.begin(), size)); + } + + virtual SDebugStreamMessage WaitForNextMessage(CRegisteredString protocol) + { + QT3DSU32 msgId = GetIdForProtocol(protocol); + + while (m_Stream) { + SMultiProtocolMessageHeader header; + NVDataRef<QT3DSU8> buf = toU8DataRef(header); + buf = ReadChunk(buf); + if (buf.size() < sizeof(header)) { + m_Stream = NULL; + QT3DS_ASSERT(false); + } else { + NVDataRef<QT3DSU8> readResult = ReadChunk(header.m_Size); + if (readResult.mSize != header.m_Size) { + m_Stream = NULL; + QT3DS_ASSERT(false); + } else { + if (header.m_ProtocolId == msgId) { + SDebugStreamMessage message; + message.m_Timestamp = header.m_Timestamp; + message.m_Data = readResult; + return message; + } else + DispatchMessage(header, readResult); + } + } + } + + return SDebugStreamMessage(); + } + + virtual void MessagePump() + { + if (m_Stream == NULL) + return; + bool lastMessage = true; + do { + SMultiProtocolMessageHeader header; + NVDataRef<QT3DSU8> buf = toU8DataRef(header); + QT3DSU32 amountRead = m_Stream->nonBlockingRead(buf); + if (amountRead == 0) { + if (m_Stream->connected() == false) + m_Stream = NULL; + lastMessage = false; + } else { + // read the rest of the header. + QT3DSU32 leftover = buf.size() - amountRead; + if (leftover) { + NVDataRef<QT3DSU8> nextPiece(buf.begin() + amountRead, leftover); + nextPiece = ReadChunk(nextPiece); + amountRead += nextPiece.size(); + } + + if (amountRead < sizeof(SMultiProtocolMessageHeader)) { + m_Stream = NULL; + QT3DS_ASSERT(false); + + } else { + NVDataRef<QT3DSU8> msgData = ReadChunk(header.m_Size); + if (msgData.size() == header.m_Size) { + DispatchMessage(header, msgData); + } else { + m_Stream = NULL; + QT3DS_ASSERT(false); + } + } + } + + } while (lastMessage && m_Stream); + } + + SMultiProtocolMessageHeader CreateHeader(MultiProtocolMessageTypes::Enum type, QT3DSU32 size, + QT3DSU32 protocolId) + { + SMultiProtocolMessageHeader retval; + retval.m_Flags.clearOrSet(true, type); + retval.m_ProtocolId = protocolId; + retval.m_Size = size; + retval.m_Timestamp = Time::getCurrentCounterValue(); + return retval; + } + + bool WriteMessage(MultiProtocolMessageTypes::Enum type, NVConstDataRef<QT3DSU8> data, + QT3DSU32 protocolId) + { + if (connected()) { + SMultiProtocolMessageHeader header(CreateHeader(type, data.size(), protocolId)); + NVConstDataRef<QT3DSU8> writeBuf = toU8DataRef(header); + bool success = m_WriteStream.Write(writeBuf); + if (success) { + success = m_WriteStream.Write(data); + } + if (!success) + m_Stream = NULL; + return success; + } + return false; + } + + virtual bool Write(CRegisteredString protocol, NVConstDataRef<QT3DSU8> data) + { + return WriteMessage(MultiProtocolMessageTypes::ProtocolMessage, data, + GetIdForProtocol(protocol)); + } + + bool connected() { return m_Stream != NULL && m_Stream->connected(); } +}; + +struct SMultiProtocolSocketStreamImpl : public IMultiProtocolSocketStream, + public IProtocolMessageHandler +{ + NVFoundationBase &m_Foundation; + CRegisteredString m_Protocol; + IDebugStreamListener *m_Listener; + NVScopedRefCounted<SSharedStreamImpl> m_SharedStream; + bool m_StreamValid; + QT3DSI32 mRefCount; + + SMultiProtocolSocketStreamImpl(NVFoundationBase &fnd, CRegisteredString protocol, + IDebugStreamListener *listener, SSharedStreamImpl &stream) + : m_Foundation(fnd) + , m_Protocol(protocol) + , m_Listener(listener) + , m_SharedStream(stream) + , m_StreamValid(true) + , mRefCount(0) + { + m_SharedStream->AddMessageHandler(m_Protocol, *this); + } + + ~SMultiProtocolSocketStreamImpl() { m_SharedStream->RemoveMessageHandler(m_Protocol); } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + IDebugStreamListener *GetListener() override { return m_Listener; } + CRegisteredString GetProtocolName() override { return m_Protocol; } + + void SetListener(IDebugStreamListener *listener) override { m_Listener = listener; } + + bool Write(NVConstDataRef<QT3DSU8> data) override + { + if (m_StreamValid) + m_StreamValid = m_SharedStream->Write(m_Protocol, data); + + return m_StreamValid; + } + + SDebugStreamMessage WaitForNextMessage() override + { + if (m_StreamValid) + return m_SharedStream->WaitForNextMessage(m_Protocol); + return SDebugStreamMessage(); + } + + void OnMessageReceived(SDebugStreamMessage data) override + { + if (m_Listener) + m_Listener->OnMessageReceived(data); + } + + bool Connected() override { return m_SharedStream->connected(); } +}; + +struct SMultiProtocolSocketImpl : public IMultiProtocolSocket, public IProtocolHandler +{ + NVFoundationBase &m_Foundation; + NVScopedRefCounted<SSharedStreamImpl> m_SharedStream; + NVScopedRefCounted<IMultiProtocolSocketListener> m_ProtocolListener; + QT3DSI32 mRefCount; + SMultiProtocolSocketImpl(NVFoundationBase &fnd, SocketStream &inStream, IStringTable &strTable, + IMultiProtocolSocketListener *protocolListener) + : m_Foundation(fnd) + , m_ProtocolListener(protocolListener) + , mRefCount(0) + { + // At some point I may switch the writer to a buffered stream, at least on the client side. + m_SharedStream = QT3DS_NEW(m_Foundation.getAllocator(), SSharedStreamImpl)( + m_Foundation, inStream, inStream, strTable, *this); + } + + ~SMultiProtocolSocketImpl() { m_SharedStream->ProtocolHandlerLeaving(); } + + bool Initialize() override { return m_SharedStream->Initialize(); } + + bool Connected() override { return m_SharedStream->connected(); } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Foundation.getAllocator()) + + virtual NVScopedRefCounted<IMultiProtocolSocketStream> + CreateProtocol(const char *name, IDebugStreamListener *inListener) override + { + NVScopedRefCounted<IMultiProtocolSocketStream> retval = GetProtocol(name); + if (retval) { + QT3DS_ASSERT(false); + return retval; + } + CRegisteredString protocolName = m_SharedStream->m_StringTable.RegisterStr(name); + if (protocolName.IsValid() == false) { + QT3DS_ASSERT(false); + return retval; + } + SMultiProtocolSocketStreamImpl *newStream = + QT3DS_NEW(m_Foundation.getAllocator(), SMultiProtocolSocketStreamImpl)( + m_Foundation, protocolName, inListener, *m_SharedStream); + return newStream; + } + + NVScopedRefCounted<IMultiProtocolSocketStream> GetProtocol(const char *name) override + { + CRegisteredString protocolName = m_SharedStream->m_StringTable.RegisterStr(name); + IProtocolMessageHandler *handler = m_SharedStream->GetMessageHandler(protocolName); + if (handler) { + SMultiProtocolSocketStreamImpl *theImpl = + static_cast<SMultiProtocolSocketStreamImpl *>(handler); + return theImpl; + } + return NVScopedRefCounted<IMultiProtocolSocketStream>(); + } + + void OnNewProtocol(CRegisteredString inProtocolName) override + { + if (m_ProtocolListener) { + // We can expect the user to call create protocol at this point. + IDebugStreamListener *handler = m_ProtocolListener->OnNewProtocol(inProtocolName); + if (handler) { + SMultiProtocolSocketStreamImpl *newStream = + QT3DS_NEW(m_Foundation.getAllocator(), SMultiProtocolSocketStreamImpl)( + m_Foundation, inProtocolName, handler, *m_SharedStream); + m_ProtocolListener->OnNewProtocolStream(inProtocolName, *newStream); + } + } + } + + CounterFrequencyToTensOfNanos SourceConversion() override + { + return CounterFrequencyToTensOfNanos(m_SharedStream->m_Initializer.m_TimeNumerator, + m_SharedStream->m_Initializer.m_TimeDenominator); + } + + void MessagePump() override { m_SharedStream->MessagePump(); } +}; +} + +NVScopedRefCounted<IMultiProtocolSocket> +IMultiProtocolSocket::CreateProtocolSocket(NVFoundationBase &fnd, SocketStream &inStream, + IStringTable &strTable, + IMultiProtocolSocketListener *protocolListener) +{ + return QT3DS_NEW(fnd.getAllocator(), SMultiProtocolSocketImpl)(fnd, inStream, strTable, + protocolListener); +} + diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugStreams.h b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugStreams.h new file mode 100644 index 00000000..5bbc0b26 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugStreams.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_DEBUG_STREAMS_H +#define QT3DS_STATE_DEBUG_STREAMS_H +#pragma once +#include "Qt3DSState.h" +#include "foundation/Socket.h" +#include "foundation/Qt3DSTime.h" +#include "Qt3DSStateDebugger.h" + +struct script_State; + +namespace qt3ds { +namespace state { + namespace debugger { + + class IMultiProtocolSocketStream : public IDebugOutStream + { + public: + virtual CRegisteredString GetProtocolName() = 0; + }; + + class IMultiProtocolSocketListener : public NVRefCounted + { + public: + // If a listener is returned from on new protocol, the system creates a stream + // with the returned listener + virtual IDebugStreamListener *OnNewProtocol(CRegisteredString inProtocolName) = 0; + // Created with the listener returned from OnNewProtocol. + virtual void OnNewProtocolStream(CRegisteredString inProtocolName, + IMultiProtocolSocketStream &inStream) = 0; + }; + + // Create a system of multiplexing multipler unrelated protocols + // through a single network socket. + class IMultiProtocolSocket : public NVRefCounted + { + public: + virtual bool Initialize() = 0; + virtual NVScopedRefCounted<IMultiProtocolSocketStream> + CreateProtocol(const char *name, IDebugStreamListener *inListener) = 0; + virtual NVScopedRefCounted<IMultiProtocolSocketStream> + GetProtocol(const char *name) = 0; + virtual bool Connected() = 0; + + // Upon connection the multi protocol system does a handshake where both sides send + // their nanosecond + // conversions across. Note that the times sent on the multi protocol packets are in a + // system-specific 64 bit quantity that + // needs conversion to actual nanoseconds to be useful (identical to + // QueryHighPerformanceFrequency, QueryHighPerformanceCounter + virtual CounterFrequencyToTensOfNanos SourceConversion() = 0; + + // Manually do a nonblocking check on the network socket for any new information and + // call the various stream listeners + // with information packets if found. + virtual void MessagePump() = 0; + + static NVScopedRefCounted<IMultiProtocolSocket> + CreateProtocolSocket(NVFoundationBase &fnd, SocketStream &inStream, + IStringTable &strTable, + IMultiProtocolSocketListener *protocolListener); + }; + + class CProtocolNames + { + public: + static const char *getMobdebugProtocolName() { return "mobdebug"; } + static const char *getSCXMLProtocolName() { return "scxml"; } + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggedInterpreter.cpp b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggedInterpreter.cpp new file mode 100644 index 00000000..a64e8589 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggedInterpreter.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateDebugger.h" +#include "Qt3DSStateDebuggerProtocol.h" +#include "EASTL/map.h" +#include "EASTL/set.h" +#include "foundation/Qt3DSAtomic.h" + +using namespace qt3ds::state; +using namespace qt3ds::state::debugger; + +namespace { + +// Horrible name, I know. +struct SDebuggedInterpreter : public IDebuggedInterpreter +{ + typedef eastl::set<TDebugStr> TDebugStrSet; + typedef eastl::vector<SBreakpoint> TBreakpointList; + typedef eastl::map<TDebugStr, SDatamodelValue> TDataModelTable; + typedef eastl::hash_map<SDatamodelTable *, TDataModelTable> TTableMap; + typedef eastl::vector<STableEntry> TTableEntryList; + // Null is the datamodel table. + + NVAllocatorCallback &m_Allocator; + NVScopedRefCounted<IDebugOutStream> m_Stream; + QT3DSI32 mRefCount; + TEditorPtr m_Editor; + TDebugStr m_Filename; + SMessageSerializer m_Serializer; + QT3DSI32 m_StreamId; + TBreakpointList m_Breakpoints; + TDebugStrList m_StrList; + TDebugStrSet m_Configuration; + bool m_BreakOnMicrostep; + bool m_IsBroken; + Option<SBreakpoint> m_BrokenBreakpoint; + IDebuggerMasterListener &m_Listener; + TTableMap m_DatamodelValues; + mutable TTableEntryList m_TempEntries; + bool m_MicrostepHasData; + SMicrostepData m_MicrostepData; + TDebugStr m_MicrostepEvent; + TDebugStrList m_ConfigurationList; + TDebugStrList m_PreviousConfiguration; + + SDebuggedInterpreter(NVAllocatorCallback &inAlloc, IDebugOutStream &inStream, + TEditorPtr inEditor, const TDebugStr &inFname, QT3DSI32 inStreamId, + NVConstDataRef<TDebugStr> inConfiguration, + IDebuggerMasterListener &inListener) + : m_Allocator(inAlloc) + , m_Stream(inStream) + , mRefCount(0) + , m_Editor(inEditor) + , m_Filename(inFname) + , m_StreamId(inStreamId) + , m_BreakOnMicrostep(false) + , m_IsBroken(false) + , m_Listener(inListener) + , m_MicrostepHasData(false) + { + m_ConfigurationList.assign(inConfiguration.begin(), inConfiguration.end()); + m_Configuration.insert(inConfiguration.begin(), inConfiguration.end()); + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Allocator) + + // Get the editor that represents the state graph for this debugged interpreter. + TEditorPtr GetEditor() override { return m_Editor; } + TDebugStr GetFilename() const override { return m_Filename; } + + template <typename TMessageType> + void SendMessage(const TMessageType &inMessage) + { + m_Serializer.Serialize(m_StreamId, inMessage, 0); + m_Stream->Write(m_Serializer.ToRawMessage()); + } + + // Used when connecting to a live session. + void SetBreakpoint(const SBreakpoint &inBreakpoint) override + { + TBreakpointList::iterator iter = + eastl::find(m_Breakpoints.begin(), m_Breakpoints.end(), inBreakpoint); + if (iter == m_Breakpoints.end()) { + SendMessage(SSetBreakpoint(inBreakpoint)); + m_Breakpoints.push_back(inBreakpoint); + } + } + + void ClearBreakpoint(const SBreakpoint &inBreakpoint) override + { + TBreakpointList::iterator iter = + eastl::find(m_Breakpoints.begin(), m_Breakpoints.end(), inBreakpoint); + if (iter != m_Breakpoints.end()) { + SendMessage(SClearBreakpoint(inBreakpoint)); + m_Breakpoints.erase(iter); + } + } + + NVConstDataRef<SBreakpoint> GetBreakpoints() override + { + return toDataRef(m_Breakpoints.data(), m_Breakpoints.size()); + } + + void ClearAllBreakpoints() override + { + SendMessage(SClearAllBreakpoints()); + m_Breakpoints.clear(); + } + + // break at the *end* of the microstep so you can see the data of the microstep. + void SetBreakOnMicrostep(bool inBreak) override + { + if (m_BreakOnMicrostep != inBreak) { + m_BreakOnMicrostep = inBreak; + SendMessage(SBreakOnMicrostep(inBreak)); + } + } + bool IsBreakOnMicrostep() const override { return m_BreakOnMicrostep; } + + NVConstDataRef<STableEntry> TableToList(const TDataModelTable &inTable) const + { + m_TempEntries.resize(inTable.size()); + QT3DSU32 idx = 0; + for (TDataModelTable::const_iterator iter = inTable.begin(), end = inTable.end(); + iter != end; ++iter, ++idx) + m_TempEntries[idx] = STableEntry(iter->first, iter->second); + return toConstDataRef(m_TempEntries.data(), m_TempEntries.size()); + } + + NVConstDataRef<STableEntry> GetTableValues(SDatamodelTable *inTable) const override + { + TTableMap::const_iterator iter = m_DatamodelValues.find(inTable); + if (iter != m_DatamodelValues.end()) + return TableToList(iter->second); + return NVConstDataRef<STableEntry>(); + } + + NVConstDataRef<TDebugStr> GetPreviousConfiguration() const override + { + return toConstDataRef(m_PreviousConfiguration.data(), m_PreviousConfiguration.size()); + } + NVConstDataRef<TDebugStr> GetConfiguration() const override + { + return toConstDataRef(m_ConfigurationList.data(), m_ConfigurationList.size()); + } + const TDebugStr &GetMicrostepEvent() const override { return m_MicrostepEvent; } + NVConstDataRef<STransitionId> GetMicrostepTransitions() const override + { + return toConstDataRef(m_MicrostepData.m_Transitions.data(), + m_MicrostepData.m_Transitions.size()); + } + NVConstDataRef<TDebugStr> GetMicrostepEnterStates() const override + { + return toConstDataRef(m_MicrostepData.m_EnterStates.data(), + m_MicrostepData.m_EnterStates.size()); + } + NVConstDataRef<TDebugStr> GetMicrostepExitStates() const override + { + return toConstDataRef(m_MicrostepData.m_ExitStates.data(), + m_MicrostepData.m_ExitStates.size()); + } + void Continue() override + { + SendMessage(SContinue()); + m_IsBroken = false; + m_BrokenBreakpoint = Option<SBreakpoint>(); + } + + void Disconnect() override + { + ClearAllBreakpoints(); + SetBreakOnMicrostep(false); + if (m_IsBroken == true) + Continue(); + + SendMessage(SDisconnect()); + } + + void BreakpointHit(const SBreakpoint &inBreakpoint) override + { + m_BrokenBreakpoint = inBreakpoint; + m_IsBroken = true; + m_Listener.OnInterpreterBroken(*this, inBreakpoint); + } + + void OnEventQueued(const SEventQueued &inMsg) override + { + m_Listener.OnEventQueued(inMsg.m_Event, inMsg.m_Internal); + } + + void OnBeginStep(const SBeginStep &) override { m_Listener.OnBeginStep(*this); } + + void OnBeginMicrostep(const SBeginMicrostep &) override + { + m_MicrostepEvent.clear(); + m_MicrostepData.m_Transitions.clear(); + m_MicrostepData.m_EnterStates.clear(); + m_MicrostepData.m_ExitStates.clear(); + m_MicrostepHasData = false; + m_Listener.OnBeginMicrostep(*this); + } + + void OnMicrostepEvent(const SMicrostepEvent &inMsg) override + { + m_MicrostepEvent = inMsg.m_Event; + } + + void OnMicrostepData(const SMicrostepData &inMsg) override + { + m_MicrostepData = inMsg; + m_MicrostepHasData = true; + } + void OnEndMicrostep(const SEndMicrostep & /*inMsg*/) override + { + if (m_MicrostepHasData == false && m_MicrostepEvent.empty() == false) { + m_Listener.OnEventProcessed(m_MicrostepEvent, false); + } else { + m_PreviousConfiguration = m_ConfigurationList; + + for (TDebugStrList::const_iterator iter = m_MicrostepData.m_ExitStates.begin(), + end = m_MicrostepData.m_ExitStates.end(); + iter != end; ++iter) + m_Configuration.erase(*iter); + + m_Configuration.insert(m_MicrostepData.m_EnterStates.begin(), + m_MicrostepData.m_EnterStates.end()); + + m_ConfigurationList.clear(); + m_ConfigurationList.insert(m_ConfigurationList.end(), m_Configuration.begin(), + m_Configuration.end()); + if (m_BreakOnMicrostep) { + m_IsBroken = true; + m_Listener.OnInterpreterBroken(*this, Option<SBreakpoint>()); + } else + m_Listener.OnMicrostep(*this); + } + } + + void OnModifyTable(const SModifyTableValues &inValues) override + { + TTableMap::iterator iter = + m_DatamodelValues.insert(eastl::make_pair(inValues.m_TablePtr, TDataModelTable())) + .first; + for (QT3DSU32 idx = 0, end = inValues.m_Modifications.size(); idx < end; ++idx) { + const STableModification &theMod(inValues.m_Modifications[idx]); + switch (theMod.m_Type) { + case TableModificationType::RemoveKey: + iter->second.erase(theMod.m_Entry.m_Key); + break; + case TableModificationType::SetKey: + (iter->second)[theMod.m_Entry.m_Key] = theMod.m_Entry.m_Value; + break; + default: + QT3DS_ASSERT(false); + break; + } + } + m_Listener.OnDatamodelChange( + *this, inValues.m_TablePtr, + toConstDataRef(inValues.m_Modifications.data(), inValues.m_Modifications.size())); + } + + bool IsBroken() const override { return m_IsBroken; } +}; +} + +IDebuggedInterpreter &IDebuggedInterpreter::Create(NVAllocatorCallback &inAlloc, + IDebugOutStream &inStream, TEditorPtr inEditor, + const TDebugStr &inFilename, QT3DSI32 inStreamId, + NVConstDataRef<TDebugStr> inConfiguration, + IDebuggerMasterListener &inListener) +{ + return *QT3DS_NEW(inAlloc, SDebuggedInterpreter)(inAlloc, inStream, inEditor, inFilename, + inStreamId, inConfiguration, inListener); +} diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugger.cpp b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugger.cpp new file mode 100644 index 00000000..5917db60 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugger.cpp @@ -0,0 +1,312 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateDebugger.h" +#include "Qt3DSStateDebuggerValues.h" +#include "Qt3DSStateDebuggerProtocol.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/IOStreams.h" + +using namespace qt3ds::state::debugger; +using namespace qt3ds::state; + +namespace { + +static MallocAllocator g_MallocAlloc; + +struct SDebugger : public IDebugger +{ + typedef eastl::vector<NVScopedRefCounted<IStateMachineDebugInterface>> TDebugList; + typedef eastl::hash_map<IStateMachineDebugInterface *, QT3DSI32> TMachineIdMap; + typedef eastl::hash_map<QT3DSI32, eastl::pair<NVScopedRefCounted<IStateMachineDebugInterface>, + NVScopedRefCounted<IStateMachineListener>>> + TIdMachineMap; + QT3DSI32 mRefCount; + QT3DSI32 m_NextStateMachineId; + TDebugList m_StateMachineList; + TMachineIdMap m_StateMachines; + TIdMachineMap m_IdToStateMachines; + QT3DSU64 m_StartTime; + NVScopedRefCounted<IDebugOutStream> m_OutStream; + SMessageParser<SDebugger> m_Parser; + SMessageSerializer m_MessageSerializer; + TDebugStr m_MessageStr; + + SDebugger() + : mRefCount(0) + , m_NextStateMachineId(1) + , m_StartTime(Time::getCurrentCounterValue()) + { + } + + virtual ~SDebugger() { DisconnectFromServer(); } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(g_MallocAlloc) + + void ConnectStateMachine(IStateMachineDebugInterface &inMachine) + { + TMachineIdMap::iterator theIter = m_StateMachines.find(&inMachine); + if (theIter != m_StateMachines.end()) + return; + QT3DSI32 theId(m_NextStateMachineId); + ++m_NextStateMachineId; + IStateMachineListener *theListener = + &IStateMachineListener::Create(g_MallocAlloc, theId, *m_OutStream.mPtr, m_StartTime, + *this, inMachine.GetScriptContext()); + inMachine.SetStateMachineListener(theListener); + m_StateMachines.insert(eastl::make_pair(&inMachine, theId)); + m_IdToStateMachines.insert( + eastl::make_pair(theId, eastl::make_pair(&inMachine, theListener))); + } + + void OnServerConnected(IDebugOutStream &inStream) override + { + m_OutStream = inStream; + inStream.SetListener(this); + m_MessageSerializer.Serialize(0, SInitialization(), m_StartTime); + m_OutStream->Write(m_MessageSerializer.ToRawMessage()); + for (QT3DSU32 idx = 0, end = m_StateMachineList.size(); idx < end; ++idx) + ConnectStateMachine(*m_StateMachineList[idx]); + } + + void Connect(IStateMachineDebugInterface &inMachine) override + { + TDebugList::iterator theFind = + eastl::find(m_StateMachineList.begin(), m_StateMachineList.end(), &inMachine); + if (theFind == m_StateMachineList.end()) { + m_StateMachineList.push_back(&inMachine); + if (m_OutStream) + ConnectStateMachine(inMachine); + } + } + + void DisconnectStateMachine(IStateMachineDebugInterface &inMachine) + { + TMachineIdMap::iterator theIter = m_StateMachines.find(&inMachine); + if (theIter == m_StateMachines.end()) + return; + + QT3DSI32 theId = theIter->second; + m_MessageSerializer.Serialize(theId, SDisconnect(), + Time::getCurrentCounterValue() - m_StartTime); + m_OutStream->Write(m_MessageSerializer.ToRawMessage()); + inMachine.SetStateMachineListener(NULL); + m_StateMachines.erase(&inMachine); + m_IdToStateMachines.erase(theId); + } + void Disconnect(IStateMachineDebugInterface &inMachine) override + { + TDebugList::iterator theFind = + eastl::find(m_StateMachineList.begin(), m_StateMachineList.end(), &inMachine); + if (theFind != m_StateMachineList.end()) { + DisconnectStateMachine(inMachine); + m_StateMachineList.erase(theFind); + } + } + + void DisconnectFromServer() override + { + if (m_OutStream) { + m_MessageSerializer.Serialize(-1, SDisconnect(), + Time::getCurrentCounterValue() - m_StartTime); + m_OutStream->Write(m_MessageSerializer.ToRawMessage()); + } + m_StateMachines.clear(); + m_IdToStateMachines.clear(); + m_StateMachineList.clear(); + m_OutStream = NULL; + } + + void OnMessageReceived(const SDebugStreamMessage &msg) override + { + if (msg.m_Data.size()) + m_Parser.Parse(msg.m_Data, *this); + } + +// Set of ignored messages +#define IGNORE_DEBUG_MESSAGE_TYPE(tname) \ + void OnMessage(QT3DSI32, QT3DSU64, const S##tname &) { QT3DS_ASSERT(false); } + + // this are outgoing messages; we shouldn't be receiving them. + IGNORE_DEBUG_MESSAGE_TYPE(Initialization) + IGNORE_DEBUG_MESSAGE_TYPE(Connect) + IGNORE_DEBUG_MESSAGE_TYPE(BreakpointHit) + IGNORE_DEBUG_MESSAGE_TYPE(DebugLog) + IGNORE_DEBUG_MESSAGE_TYPE(EventQueued) + IGNORE_DEBUG_MESSAGE_TYPE(BeginStep) + IGNORE_DEBUG_MESSAGE_TYPE(BeginMicrostep) + IGNORE_DEBUG_MESSAGE_TYPE(MicrostepEvent) + IGNORE_DEBUG_MESSAGE_TYPE(MicrostepData) + IGNORE_DEBUG_MESSAGE_TYPE(EndMicrostep) + IGNORE_DEBUG_MESSAGE_TYPE(ModifyTableValues) + + IStateMachineDebugInterface *GetStateMachine(QT3DSI32 inStreamId) + { + TIdMachineMap::iterator theIter = m_IdToStateMachines.find(inStreamId); + if (theIter == m_IdToStateMachines.end()) + return NULL; + return theIter->second.first.mPtr; + } + + IStateMachineListener *GetStateMachineListener(QT3DSI32 inStreamId) + { + TIdMachineMap::iterator theIter = m_IdToStateMachines.find(inStreamId); + if (theIter == m_IdToStateMachines.end()) + return NULL; + return theIter->second.second.mPtr; + } + + void OnMessage(QT3DSI32 sid, QT3DSU64, const SSetBreakpoint &inBp) + { + IStateMachineListener *iface = GetStateMachineListener(sid); + if (iface) + iface->SetBreakpoint(inBp.m_Breakpoint); + } + + void OnMessage(QT3DSI32 sid, QT3DSU64, const SClearBreakpoint &inBp) + { + IStateMachineListener *iface = GetStateMachineListener(sid); + if (iface) + iface->ClearBreakpoint(inBp.m_Breakpoint); + } + + void OnMessage(QT3DSI32 sid, QT3DSU64, const SClearAllBreakpoints &) + { + IStateMachineListener *iface = GetStateMachineListener(sid); + if (iface) + iface->ClearAllBreakpoints(); + } + + void OnMessage(QT3DSI32 sid, QT3DSU64, const SBreakOnMicrostep &inCmd) + { + IStateMachineListener *iface = GetStateMachineListener(sid); + if (iface) + iface->SetBreakOnMicrostep(inCmd.m_Value); + } + + void OnMessage(QT3DSI32 sid, QT3DSU64, const SContinue &) + { + IStateMachineListener *iface = GetStateMachineListener(sid); + if (iface) + iface->Continue(); + } + + void OnMessage(QT3DSI32 sid, QT3DSU64, const SDisconnect &) + { + if (sid != 0 && sid != (QT3DSI32)QT3DS_MAX_U32) { + IStateMachineDebugInterface *iface = GetStateMachine(sid); + if (iface) + Disconnect(*iface); + } else { + if (sid == (QT3DSI32)QT3DS_MAX_U32) + DisconnectFromServer(); + } + } + + void error(const char8_t *, const char8_t *) {} +}; + +static inline NVConstDataRef<QT3DSU8> ToRef(const TDebugStr &str) +{ + return NVConstDataRef<QT3DSU8>((const QT3DSU8 *)str.begin(), str.size()); +} + +bool TestBasicParseRobustness() +{ + typedef STestMessageHandler<SInitialization> THandlerType; + typedef SMessageParser<THandlerType> TParserType; + // Just ensure things don't crash. + SInitialization testType; + THandlerType theHandler(2, 5, testType); + TDebugStr theStr; + TParserType theParser; + theParser.Parse(ToRef(theStr), theHandler); + + theStr = "Initialization"; + theParser.Parse(ToRef(theStr), theHandler); + + theStr = "Initialization "; + theParser.Parse(ToRef(theStr), theHandler); + return true; +} + +template <typename TDataType> +bool TestSerialization(const TDataType &testType, SMessageSerializer &ioSerializer, + QT3DSU64 inTimestamp) +{ + typedef STestMessageHandler<TDataType> THandlerType; + typedef SMessageParser<THandlerType> TParserType; + + ioSerializer.Serialize(5, testType, inTimestamp); + THandlerType theHandler(5, inTimestamp, testType); + TParserType theParser; + theParser.Parse(ioSerializer.ToRawMessage(), theHandler); + return theHandler.m_Result; +} +} + +IDebugger &IDebugger::CreateDebugger() +{ + return *QT3DS_NEW(g_MallocAlloc, SDebugger)(); +} + +bool CDebuggerTests::TestStreamProtocol() +{ + // for each message type, fill on some values, serialize to string and back and see what happens + TDebugStrList theList; + theList.push_back("one"); + theList.push_back("two"); + TTransitionIdList theTransList; + theTransList.push_back(STransitionId("one", 2)); + theTransList.push_back(STransitionId("two", -1)); + SBreakpoint bp1(SStateEnterBreakpoint("one")); + SBreakpoint bp2(SStateExitBreakpoint("two")); + SBreakpoint bp3(STransitionId("three", 4)); + SMessageSerializer theSerializer; + bool retval = TestBasicParseRobustness(); + // breaking out the large && statement to make testing easier. + retval = retval && TestSerialization(SInitialization(), theSerializer, 5); + retval = retval && TestSerialization(SConnect("abe.scxml", "<one>hey you</one>", theList), + theSerializer, 6); + retval = retval + && TestSerialization(SDebugLog(TDebugStr("Hey joe, where you goin'")), theSerializer, 7); + retval = retval && TestSerialization(SBeginMicrostep(), theSerializer, 8); + retval = retval && TestSerialization(SMicrostepEvent("evt1", true), theSerializer, 9); + retval = retval + && TestSerialization(SMicrostepData(theTransList, theList, theList), theSerializer, 9); + retval = retval && TestSerialization(SEndMicrostep(), theSerializer, 10); + retval = retval && TestSerialization(SEventQueued("evt1", false), theSerializer, 11); + retval = retval && TestSerialization(SEventQueued("evt1", false), theSerializer, 12); + retval = retval && TestSerialization(SSetBreakpoint(bp1), theSerializer, 13); + retval = retval && TestSerialization(SSetBreakpoint(bp2), theSerializer, 14); + retval = retval && TestSerialization(SSetBreakpoint(bp3), theSerializer, 15); + retval = retval && TestSerialization(SClearBreakpoint(bp1), theSerializer, 16); + retval = retval && TestSerialization(SClearAllBreakpoints(), theSerializer, 17); + return retval; +} diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugger.h b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugger.h new file mode 100644 index 00000000..00814e42 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebugger.h @@ -0,0 +1,386 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_DEBUGGER_H +#define QT3DS_STATE_DEBUGGER_H +#pragma once +#include "Qt3DSState.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/Qt3DSDataRef.h" +#include "foundation/StringTable.h" +#include "foundation/IOStreams.h" +#include "Qt3DSStateEditor.h" + +namespace qt3ds { +namespace state { + namespace debugger { + using namespace editor; + + typedef TEditorStr TDebugStr; + struct SDebugValue; + struct SBreakpoint; + struct SNil; + struct SDatamodelTable; + struct SDatamodelUserData; + struct SDatamodelFunction; + struct SDatamodelCFunction; + struct SDatamodelThread; + + typedef eastl::vector<SBreakpoint> TBreakpointList; + + struct BreakpointTypes + { + enum Enum { + UnknownBreakpointType = 0, + StateEnter, + StateExit, + Transition, + }; + }; + + struct SDebugStreamMessage + { + // Timestamp needs to be converted using the SourceConversion on the protocol socket. + QT3DSU64 m_Timestamp; + NVConstDataRef<QT3DSU8> m_Data; + SDebugStreamMessage() + : m_Timestamp(0) + { + } + SDebugStreamMessage(QT3DSU64 ts, NVConstDataRef<QT3DSU8> msg) + : m_Timestamp(ts) + , m_Data(msg) + { + } + }; + + // Note the stream listeners are not ref counted. + // it is your job to ensure it lasts as long as debug stream. + class IDebugStreamListener + { + protected: + virtual ~IDebugStreamListener() {} + public: + // Async message interface. Clients must provide this. Only called on main thread + virtual void OnMessageReceived(const SDebugStreamMessage &inMessage) = 0; + }; + + class IDebugOutStream : public NVRefCounted, public IOutStream + { + protected: + virtual ~IDebugOutStream() {} + public: + virtual void SetListener(IDebugStreamListener *listener) = 0; + virtual IDebugStreamListener *GetListener() = 0; + // return true if the stream is still connected to an output, false if + // something caused the connection to fail or close. + virtual SDebugStreamMessage WaitForNextMessage() = 0; + virtual bool Connected() = 0; + }; + + struct STransitionId + { + TDebugStr m_StateId; + // Index in file order of the transition. + //-1 means initial or history::transition, depending on if the state is + // a normal state or history state. + QT3DSI32 m_TransitionIndex; + STransitionId(const TDebugStr &inId = TDebugStr(), QT3DSI32 inIdx = -2) + : m_StateId(inId) + , m_TransitionIndex(inIdx) + { + } + bool operator==(const STransitionId &inOther) const + { + return m_StateId == inOther.m_StateId + && m_TransitionIndex == inOther.m_TransitionIndex; + } + }; + + typedef eastl::vector<STransitionId> TTransitionIdList; + + /////////////////////////////////////////////////////////////////////// + // Client (runtime) side types + /////////////////////////////////////////////////////////////////////// + + struct DatamodelValueTypes + { + enum Enum { + UnknownType = 0, + Nil, + Boolean, + Number, + String, + Table, + UserData, + Function, + CFunction, + Thread, + }; + }; + + struct SDatamodelValue; + + struct SDatamodelTable; + struct SDatamodelUserData; + struct SDatamodelFunction; + struct SDatamodelCFunction; + struct SDatamodelThread; + struct STableEntry; + + class IScriptStateListener + { + protected: + virtual ~IScriptStateListener() {} + public: + virtual void SetKey(void *inTable, const TDebugStr &inStr, + const SDatamodelValue &inValue) = 0; + virtual void RemoveKey(void *inTable, const TDebugStr &inStr) = 0; + }; + + class IDebugger; + // Information coming from the state machine + class IStateMachineListener : public NVRefCounted + { + protected: + virtual ~IStateMachineListener() {} + public: + // Events coming from the interpreter and sent over the interface. + virtual void OnConnect(const TDebugStr &SCXMLFilename, const TDebugStr &SCXMLFileData, + NVConstDataRef<TDebugStr> inConfiguration) = 0; + + virtual void Log(const TDebugStr &inStr) = 0; + virtual void EventQueued(const TDebugStr &inEventName, bool inInternal) = 0; + virtual void BeginStep() = 0; + virtual void BeginMicroStep() = 0; + virtual void SetEvent(const TDebugStr &inEventName, bool inInternal) = 0; + virtual void SetTransitionSet(NVConstDataRef<STransitionId> inTransitions) = 0; + virtual void SetExitSet(NVConstDataRef<TDebugStr> inSet) = 0; + virtual void SetEnterSet(NVConstDataRef<TDebugStr> inSet) = 0; + // Log statements run through the debugger as well. + virtual void EndMicroStep() = 0; + virtual void EndStep() = 0; + + // So far the breakpoints have all been things that are internal to the + // state machine. + virtual void SetBreakOnMicrostep(bool inEnableStep) = 0; + virtual void SetBreakpoint(const SBreakpoint &inBreakpoint) = 0; + virtual void ClearBreakpoint(const SBreakpoint &inBreakpoint) = 0; + virtual void ClearAllBreakpoints() = 0; + + // Called internally. + virtual void Continue() = 0; + virtual void OnExternalBreak() = 0; + + static IStateMachineListener &Create(NVAllocatorCallback &inAlloc, QT3DSU32 inStreamId, + IDebugOutStream &inOutStr, QT3DSU64 inStartTime, + IDebugger &inDebugger, + IScriptContext &inScriptContext); + }; + + // Information coming from the network stream will call these iterfaces + class IStateMachineDebugInterface : public NVRefCounted + { + protected: + virtual ~IStateMachineDebugInterface() {} + public: + virtual void SetStateMachineListener(IStateMachineListener *inListener) = 0; + virtual void OnExternalBreak() = 0; + virtual IScriptContext &GetScriptContext() = 0; + }; + + // The debugger element is the object that sits in the debug process interpreter and sends + // information. + // There should be only one of these per network connection. + class IDebugger : public IDebugStreamListener, public NVRefCounted + { + protected: + virtual ~IDebugger() {} + public: + virtual void OnServerConnected(IDebugOutStream &inStream) = 0; + virtual void Connect(IStateMachineDebugInterface &inMachine) = 0; + virtual void Disconnect(IStateMachineDebugInterface &inMachine) = 0; + // Release any references to any state machines and to the output stream + virtual void DisconnectFromServer() = 0; + + static IDebugger &CreateDebugger(); + }; + + /////////////////////////////////////////////////////////////////////// + // Server (Architect) side types + /////////////////////////////////////////////////////////////////////// + + struct DebugValueTypes + { + enum Enum { NoDebugValue = 0, String, StringList, TransitionIdList }; + }; + + typedef eastl::vector<TDebugStr> TDebugStrList; + + struct STableModification; + + class IDebuggedInterpreter; + + class IDebuggerMasterListener : public NVRefCounted + { + protected: + virtual ~IDebuggerMasterListener() {} + public: + virtual void OnInterpreterConnected(IDebuggedInterpreter &inInterpreter) = 0; + // If no breakpoint then the interpreter was broken on microstep. + virtual void OnEventQueued(const TDebugStr &inEvent, bool inInternal) = 0; + virtual void OnInterpreterBroken(IDebuggedInterpreter &inInterpreter, + const Option<SBreakpoint> &inBreakpoint) = 0; + // Event processed but no microstep was taken. + virtual void OnEventProcessed(const TDebugStr &inEvent, bool inInternal) = 0; + virtual void OnBeginStep(IDebuggedInterpreter &inInterpreter) = 0; + virtual void OnBeginMicrostep(IDebuggedInterpreter &inInterpreter) = 0; + // Event processed and microstep was taken. + virtual void OnMicrostep(IDebuggedInterpreter &inInterpreter) = 0; + // if inmodifications is empty the table needs to be replaced. + virtual void OnDatamodelChange(IDebuggedInterpreter &inInterpreter, + SDatamodelTable *inTable, + NVConstDataRef<STableModification> inModifications) = 0; + virtual void OnInterpreterDisconnected(IDebuggedInterpreter &inInterpreter) = 0; + virtual void OnLog(IDebuggedInterpreter *inInterpreter, const TDebugStr &inMessage) = 0; + }; + + struct STableEntry; + + typedef eastl::vector<STableEntry> TTableEntryList; + + // defined in UICStateDebuggerProtocol.h" + struct SBeginStep; + struct SBeginMicrostep; + struct SMicrostepEvent; + struct SMicrostepData; + struct SEndMicrostep; + struct SEventQueued; + struct SModifyTableValues; + struct SDatamodelTable; + + // Represents the data stream coming from a single interpreter + class IDebuggedInterpreter : public NVRefCounted + { + protected: + virtual ~IDebuggedInterpreter() {} + public: + /* Playback interface, leaving until basic functionality is working. + virtual QT3DSU32 GetStepCount() = 0; + virtual void SetCurrentStep( QT3DSU32 inStep ) = 0; + virtual QT3DSU32 GetMicrostepCount() = 0; + virtual QT3DSU32 SetCurrentMicroStep( QT3DSU32 inStep ) = 0; + */ + + // Get the editor that represents the state graph for this debugged interpreter. + virtual TEditorPtr GetEditor() = 0; + virtual TDebugStr GetFilename() const = 0; + + // Used when connecting to a live session. + virtual void SetBreakpoint(const SBreakpoint &inBreakpoint) = 0; + virtual void ClearBreakpoint(const SBreakpoint &inBreakpoint) = 0; + virtual NVConstDataRef<SBreakpoint> GetBreakpoints() = 0; + virtual void ClearAllBreakpoints() = 0; + // break at the *end* of the microstep so you can see the data of the microstep. + virtual void SetBreakOnMicrostep(bool inBreak) = 0; + virtual bool IsBreakOnMicrostep() const = 0; + + // Return values not valid after *next* call. + // Null means the root. + virtual NVConstDataRef<STableEntry> + GetTableValues(SDatamodelTable *inTable = NULL) const = 0; + + virtual NVConstDataRef<TDebugStr> GetPreviousConfiguration() const = 0; + virtual NVConstDataRef<TDebugStr> GetConfiguration() const = 0; + virtual const TDebugStr &GetMicrostepEvent() const = 0; + virtual NVConstDataRef<STransitionId> GetMicrostepTransitions() const = 0; + virtual NVConstDataRef<TDebugStr> GetMicrostepEnterStates() const = 0; + virtual NVConstDataRef<TDebugStr> GetMicrostepExitStates() const = 0; + + virtual bool IsBroken() const = 0; + virtual void Continue() = 0; + // Get the set of changed properties since the last time you asked. Obviously this is + // assuming there + // is only one 'you' asking. + virtual void Disconnect() = 0; + + // Semi-advanced debugger functionality that we don't need for version 1. + + // virtual void SetPropertyValue( const char8_t* inName, const SDebugValue& inValue ) = + // 0; + // Set a property value on the running state machine. + // virtual void SetPropertyValue( TObjPtr inEditorObj, const char8_t* inName, const + // SValueOpt& inValue ) = 0; + + // Eval code in the current state machine context. Works when connected live, wouldn't + // work if you + // were not connected live. + // virtual TDebugStr EvalCode( const TDebugStr& inStr ) = 0; + + // Information coming from the stream, clients should not call these functions + virtual void BreakpointHit(const SBreakpoint &inBreakpoint) = 0; + virtual void OnEventQueued(const SEventQueued &inMsg) = 0; + virtual void OnBeginStep(const SBeginStep &inMsg) = 0; + virtual void OnBeginMicrostep(const SBeginMicrostep &inMsg) = 0; + virtual void OnMicrostepEvent(const SMicrostepEvent &inMsg) = 0; + virtual void OnMicrostepData(const SMicrostepData &inMsg) = 0; + virtual void OnEndMicrostep(const SEndMicrostep &inMsg) = 0; + virtual void OnModifyTable(const SModifyTableValues &inValues) = 0; + + static IDebuggedInterpreter &Create(NVAllocatorCallback &inAlloc, + IDebugOutStream &inStream, TEditorPtr inEditor, + const TDebugStr &inFilename, QT3DSI32 inStreamId, + NVConstDataRef<TDebugStr> inConfiguration, + IDebuggerMasterListener &inListener); + }; + + // Debugger sites in the master process (Architect process) and controls one or more + // debuggers + // for one or more state machines running in the debug process. + class IDebuggerMaster : public IDebugStreamListener, public NVRefCounted + { + protected: + virtual ~IDebuggerMaster() {} + public: + virtual void Disconnect() = 0; + + static IDebuggerMaster &CreateMaster(IDebugOutStream &outStream, + IDebuggerMasterListener &inListener); + }; + + class CDebuggerTests + { + public: + static bool TestStreamProtocol(); + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerListener.cpp b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerListener.cpp new file mode 100644 index 00000000..2045f84f --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerListener.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateDebugger.h" +#include "Qt3DSStateDebuggerValues.h" +#include "Qt3DSStateDebuggerProtocol.h" +#include "foundation/IOStreams.h" +#include "foundation/Qt3DSAtomic.h" +#include "Qt3DSStateScriptContext.h" + +using namespace qt3ds::state; +using namespace qt3ds::state::debugger; + +namespace { + +// Component sits in the state machine and receives messages from the state machine +// itself. +struct SListener : public IStateMachineListener, public IScriptStateListener +{ + typedef eastl::hash_map<SDatamodelTable *, SModifyTableValues> TTableModificationMap; + NVAllocatorCallback &m_Allocator; + QT3DSI32 mRefCount; + QT3DSU32 m_StreamId; + NVScopedRefCounted<IDebugOutStream> m_OutStream; + SMessageSerializer m_Serializer; + TDebugStrList m_DebugStrList; + TDebugStrList m_DebugStrAltList; + TTransitionIdList m_TransitionIdList; + QT3DSU64 m_StartTime; + bool m_BreakOnMicrostep; + bool m_Blocking; + IDebugger &m_Debugger; + TDebugStr m_BreakStr; + NVScopedRefCounted<IScriptContext> m_ScriptContext; + TTableModificationMap m_TableModifications; + TBreakpointList m_Breakpoints; + SMicrostepData m_MicrostepData; + bool m_MicrostepBeginSent; + bool m_StepSent; + + SListener(NVAllocatorCallback &alloc, QT3DSU32 inStreamId, IDebugOutStream &inOutStr, + QT3DSU64 inStartTime, IDebugger &inDebugger, IScriptContext &inScriptContext) + : m_Allocator(alloc) + , mRefCount(0) + , m_StreamId(inStreamId) + , m_OutStream(inOutStr) + , m_StartTime(inStartTime) + , m_BreakOnMicrostep(false) + , m_Blocking(false) + , m_Debugger(inDebugger) + , m_ScriptContext(inScriptContext) + , m_MicrostepBeginSent(false) + , m_StepSent(false) + { + } + + virtual ~SListener() { m_Blocking = false; } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Allocator) + + template <typename TDataType> + static const eastl::vector<TDataType> &RefToList(NVConstDataRef<TDataType> inRef, + eastl::vector<TDataType> &outList) + { + outList.assign(inRef.begin(), inRef.end()); + return outList; + } + + template <typename TDataType> + void Serialize(const TDataType &dtype, QT3DSU64 inTime) + { + m_Serializer.Serialize(m_StreamId, dtype, inTime); + m_OutStream->Write(m_Serializer.ToRawMessage()); + } + + template <typename TDataType> + void Serialize(const TDataType &dtype) + { + Serialize(dtype, CurrentTime()); + } + + void OnConnect(const TDebugStr &SCXMLFilename, const TDebugStr &SCXMLFileData, + NVConstDataRef<TDebugStr> inConfiguration) override + { + Serialize( + SConnect(SCXMLFilename, SCXMLFileData, RefToList(inConfiguration, m_DebugStrList))); + DumpScriptState(); + } + + QT3DSU64 CurrentTime() { return Time::getCurrentCounterValue() - m_StartTime; } + + void DumpScriptState() + { + // Run through the global state and output any keys that have changed. + m_TableModifications.clear(); + m_ScriptContext->DumpState(*this); + for (TTableModificationMap::iterator iter = m_TableModifications.begin(), + end = m_TableModifications.end(); + iter != end; ++iter) { + Serialize(iter->second); + } + m_TableModifications.clear(); + } + virtual void OnBreakpointHit(const SBreakpoint &inBreakpointId) + { + DumpScriptState(); + Serialize(SBreakpointHit(inBreakpointId)); + Break(); + } + + void Log(const TDebugStr &inStr) override { Serialize(SDebugLog(inStr)); } + + void SetBreakpoint(const SBreakpoint &inBreakpoint) override + { + m_Breakpoints.push_back(inBreakpoint); + } + + void ClearBreakpoint(const SBreakpoint &inBreakpoint) override + { + TBreakpointList::iterator removeIter = + eastl::remove(m_Breakpoints.begin(), m_Breakpoints.end(), inBreakpoint); + if (removeIter != m_Breakpoints.end()) + m_Breakpoints.erase(removeIter, m_Breakpoints.end()); + } + void ClearAllBreakpoints() override { m_Breakpoints.clear(); } + void EventQueued(const TDebugStr &inEventName, bool inInternal) override + { + Serialize(SEventQueued(inEventName, inInternal)); + } + void SendMicrostepBegin() + { + if (m_MicrostepBeginSent == false) { + if (m_StepSent == false) { + m_StepSent = true; + Serialize(SBeginStep()); + } + + m_MicrostepBeginSent = true; + Serialize(SBeginMicrostep()); + } + } + + void BeginStep() override { m_StepSent = false; } + + void BeginMicroStep() override + { + m_MicrostepBeginSent = false; + m_MicrostepData.m_Transitions.clear(); + m_MicrostepData.m_EnterStates.clear(); + m_MicrostepData.m_ExitStates.clear(); + } + + void SetEvent(const TDebugStr &inEvent, bool inInternal) override + { + if (!inEvent.empty()) { + SendMicrostepBegin(); + Serialize(SMicrostepEvent(inEvent, inInternal)); + } + } + void SetTransitionSet(NVConstDataRef<STransitionId> inTransitions) override + { + SendMicrostepBegin(); + RefToList(inTransitions, m_MicrostepData.m_Transitions); + } + void SetExitSet(NVConstDataRef<TDebugStr> inSet) override + { + SendMicrostepBegin(); + RefToList(inSet, m_MicrostepData.m_ExitStates); + } + void SetEnterSet(NVConstDataRef<TDebugStr> inSet) override + { + SendMicrostepBegin(); + RefToList(inSet, m_MicrostepData.m_EnterStates); + } + + static inline bool FindInList(const TDebugStr &inStr, const TDebugStrList &inStrList) + { + if (eastl::find(inStrList.begin(), inStrList.end(), inStr) != inStrList.end()) + return true; + return false; + } + + static inline bool FindInList(const STransitionId &inStr, const TTransitionIdList &inStrList) + { + if (eastl::find(inStrList.begin(), inStrList.end(), inStr) != inStrList.end()) + return true; + return false; + } + + const SBreakpoint *FindHitBreakpoint() + { + // Now check for breakpoints. + for (QT3DSU32 breakIdx = 0, breakEnd = m_Breakpoints.size(); breakIdx < breakEnd; ++breakIdx) { + const SBreakpoint &theBreakpoint = m_Breakpoints[breakIdx]; + switch (theBreakpoint.getType()) { + case BreakpointTypes::StateEnter: + if (FindInList(theBreakpoint.getData<SStateEnterBreakpoint>().m_ObjectId, + m_MicrostepData.m_EnterStates)) + return &theBreakpoint; + case BreakpointTypes::StateExit: + if (FindInList(theBreakpoint.getData<SStateExitBreakpoint>().m_ObjectId, + m_MicrostepData.m_ExitStates)) + return &theBreakpoint; + case BreakpointTypes::Transition: + if (FindInList(theBreakpoint.getData<STransitionId>(), + m_MicrostepData.m_Transitions)) + return &theBreakpoint; + default: + QT3DS_ASSERT(false); + break; + } + } + + return NULL; + } + + // Log statements run through the debugger as well. + void EndMicroStep() override + { + DumpScriptState(); + if (m_MicrostepBeginSent) { + if (m_MicrostepData.m_Transitions.empty() == false) + Serialize(m_MicrostepData); + + Serialize(SEndMicrostep()); + const SBreakpoint *theHitBreakpoint = FindHitBreakpoint(); + if (theHitBreakpoint) + OnBreakpointHit(*theHitBreakpoint); + else if (m_BreakOnMicrostep) + Break(); + } + } + + void EndStep() override { m_StepSent = false; } + + void SetBreakOnMicrostep(bool inEnableStep) override { m_BreakOnMicrostep = inEnableStep; } + + virtual void Break() + { + if (!m_Blocking) { + // We may get disconnected *while* we are breaking. + // We need to ensure we don't get nuked while we are breaking. + NVScopedRefCounted<SListener> tempVar(this); + m_Blocking = true; + while (m_Blocking) { + SDebugStreamMessage msg = m_OutStream->WaitForNextMessage(); + // Some out streams will have a reference to the debugger and some will not. + // If they do, then we do not have to hand our string to the debugger, we can assume + // the underlying implementation will have done that. If not, then we need to + // pass the message to the debugger. + if (m_Blocking && msg.m_Data.size()) + m_Debugger.OnMessageReceived(msg); + + if (!m_OutStream->Connected()) { + m_Blocking = false; + m_Debugger.DisconnectFromServer(); + } + } + } + } + + void Continue() override { m_Blocking = false; } + + void OnExternalBreak() override { DumpScriptState(); } + + // IScriptStateListener + void SetKey(void *inTable, const TDebugStr &inStr, const SDatamodelValue &inValue) override + { + SDatamodelTable *theTable((SDatamodelTable *)inTable); + SModifyTableValues &theModification = + m_TableModifications.insert(eastl::make_pair(theTable, SModifyTableValues(theTable))) + .first->second; + theModification.m_Modifications.push_back( + STableModification(STableEntry(inStr, inValue), TableModificationType::SetKey)); + } + + void RemoveKey(void *inTable, const TDebugStr &inStr) override + { + SDatamodelTable *theTable((SDatamodelTable *)inTable); + SModifyTableValues &theModification = + m_TableModifications.insert(eastl::make_pair(theTable, SModifyTableValues(theTable))) + .first->second; + theModification.m_Modifications.push_back( + STableModification(STableEntry(inStr), TableModificationType::RemoveKey)); + } +}; +} + +IStateMachineListener &IStateMachineListener::Create(NVAllocatorCallback &inAlloc, QT3DSU32 inStreamId, + IDebugOutStream &inOutStr, QT3DSU64 inStartTime, + IDebugger &inDebugger, + IScriptContext &inScriptContext) +{ + return *QT3DS_NEW(inAlloc, SListener)(inAlloc, inStreamId, inOutStr, inStartTime, inDebugger, + inScriptContext); +} diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerProtocol.h b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerProtocol.h new file mode 100644 index 00000000..03230a61 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerProtocol.h @@ -0,0 +1,925 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_DEBUGGER_PROTOCOL_H +#define QT3DS_STATE_DEBUGGER_PROTOCOL_H +#include "Qt3DSStateDebugger.h" +#include "Qt3DSStateDebuggerValues.h" +#include "foundation/StringConversion.h" +#include "foundation/StringConversionImpl.h" +#include "foundation/Qt3DSTime.h" + +// Stream protocol, regardless of binary vs. text. +namespace qt3ds { +namespace state { + namespace debugger { + + struct SInitialization + { + QT3DSI32 m_Version; + QT3DSI32 m_Padding; + CounterFrequencyToTensOfNanos m_TimerFrequency; + static QT3DSI32 GetCurrentVersion() { return 1; } + SInitialization() + : m_Version(GetCurrentVersion()) + , m_Padding(0) + , m_TimerFrequency(Time::sCounterFreq) + { + } + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.visit(m_Version); + inVisitor.visit(m_TimerFrequency.mNumerator); + inVisitor.visit(m_TimerFrequency.mDenominator); + } + bool operator==(const SInitialization &inOther) const + { + return m_Version == inOther.m_Version + && m_TimerFrequency.mNumerator == inOther.m_TimerFrequency.mNumerator + && m_TimerFrequency.mDenominator == inOther.m_TimerFrequency.mDenominator; + } + }; + + // Listener interface. For on connect, we send the current timestamp for that state + // machine. + struct SConnect + { + TDebugStr m_Filename; + TDebugStr m_SCXMLData; // scxml file contents + TDebugStrList m_Configuration; + SConnect() {} + SConnect(const TDebugStr &fn, const TDebugStr &xmlData, const TDebugStrList &config) + : m_Filename(fn) + , m_SCXMLData(xmlData) + , m_Configuration(config) + { + } + + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.randomText(m_Filename); + inVisitor.randomText(m_SCXMLData); + inVisitor.visitIdList(m_Configuration); + } + bool operator==(const SConnect &inOther) const + { + return m_Filename == inOther.m_Filename && m_SCXMLData == inOther.m_SCXMLData + && m_Configuration == inOther.m_Configuration; + } + }; + + struct SBreakpointHit + { + SBreakpoint m_Breakpoint; + SBreakpointHit() {} + SBreakpointHit(const SBreakpoint &bp) + : m_Breakpoint(bp) + { + } + + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.visit(m_Breakpoint); + } + bool operator==(const SBreakpointHit &inOther) const + { + return m_Breakpoint == inOther.m_Breakpoint; + } + }; + + struct SDebugLog + { + TDebugStr m_Message; + SDebugLog() {} + SDebugLog(const TDebugStr &msg) + : m_Message(msg) + { + } + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.randomText(m_Message); + } + bool operator==(const SDebugLog &inOther) const + { + return m_Message == inOther.m_Message; + } + }; + + struct SModifyTableValues + { + SDatamodelTable *m_TablePtr; + TTableModificationList m_Modifications; + + SModifyTableValues(SDatamodelTable *inTable = NULL, + const TTableModificationList &inList = TTableModificationList()) + : m_TablePtr(inTable) + , m_Modifications(inList) + { + } + + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.visit(m_TablePtr); + inVisitor.visit(m_Modifications); + } + + bool operator==(const SModifyTableValues &inOther) const + { + return m_TablePtr == inOther.m_TablePtr + && m_Modifications == inOther.m_Modifications; + } + }; + + struct SBeginStep + { + SBeginStep() {} + template <typename TVisitor> + void Visit(TVisitor &) + { + } + + bool operator==(const SBeginStep &) const { return true; } + }; + + struct SBeginMicrostep + { + SBeginMicrostep() {} + template <typename TVisitor> + void Visit(TVisitor &) + { + } + + bool operator==(const SBeginMicrostep &) const { return true; } + }; + + struct SMicrostepEvent + { + TDebugStr m_Event; + bool m_Internal; + + SMicrostepEvent(const TDebugStr &inEvent = TDebugStr(), bool inIsInternal = false) + : m_Event(inEvent) + , m_Internal(inIsInternal) + { + } + + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.visitId(m_Event); + inVisitor.visit(m_Internal); + } + + bool operator==(const SMicrostepEvent &inOther) const + { + return m_Event == inOther.m_Event && m_Internal == inOther.m_Internal; + } + }; + + struct SMicrostepData + { + TTransitionIdList m_Transitions; + TDebugStrList m_ExitStates; + TDebugStrList m_EnterStates; + + SMicrostepData() {} + SMicrostepData(const TTransitionIdList &inTransitions, + const TDebugStrList &inExitStates, const TDebugStrList &inEnterStates) + : m_Transitions(inTransitions) + , m_ExitStates(inExitStates) + , m_EnterStates(inEnterStates) + { + } + + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + // Eventless transitions cause us to need to always write the string length. + inVisitor.visit(m_Transitions); + inVisitor.visitIdList(m_ExitStates); + inVisitor.visitIdList(m_EnterStates); + } + bool operator==(const SMicrostepData &inOther) const + { + return m_Transitions == inOther.m_Transitions + && m_ExitStates == inOther.m_ExitStates + && m_EnterStates == inOther.m_EnterStates; + } + }; + + struct SEndMicrostep + { + SEndMicrostep() {} + template <typename TVisitor> + void Visit(TVisitor & /*inVisitor*/) + { + } + + bool operator==(const SEndMicrostep &) const { return true; } + }; + + struct SEventQueued + { + TDebugStr m_Event; + bool m_Internal; + SEventQueued() + : m_Internal(false) + { + } + SEventQueued(const TDebugStr &evt, bool intern) + : m_Event(evt) + , m_Internal(intern) + { + } + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.visitEventName(m_Event); + inVisitor.visit(m_Internal); + } + bool operator==(const SEventQueued &inOther) const + { + return m_Event == inOther.m_Event && m_Internal == inOther.m_Internal; + } + }; + + // Debug interface + + struct SSetBreakpoint + { + SBreakpoint m_Breakpoint; + SSetBreakpoint() {} + SSetBreakpoint(const SBreakpoint &bp) + : m_Breakpoint(bp) + { + } + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.visit(m_Breakpoint); + } + bool operator==(const SSetBreakpoint &inOther) const + { + return m_Breakpoint == inOther.m_Breakpoint; + } + }; + + struct SClearBreakpoint + { + SBreakpoint m_Breakpoint; + SClearBreakpoint() {} + SClearBreakpoint(const SBreakpoint &bp) + : m_Breakpoint(bp) + { + } + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.visit(m_Breakpoint); + } + bool operator==(const SClearBreakpoint &inOther) const + { + return m_Breakpoint == inOther.m_Breakpoint; + } + }; + + struct SClearAllBreakpoints + { + SClearAllBreakpoints() {} + template <typename TVisitor> + void Visit(TVisitor & /*inVisitor*/) + { + } + bool operator==(const SClearAllBreakpoints & /*inOther*/) const { return true; } + }; + + struct SBreakOnMicrostep + { + bool m_Value; + SBreakOnMicrostep(bool val = false) + : m_Value(val) + { + } + template <typename TVisitor> + void Visit(TVisitor &inVisitor) + { + inVisitor.visit(m_Value); + } + bool operator==(const SBreakOnMicrostep &inOther) const + { + return m_Value == inOther.m_Value; + } + }; + + struct SContinue + { + SContinue() {} + template <typename TVisitor> + void Visit(TVisitor & /*inVisitor*/) + { + } + bool operator==(const SContinue & /*inOther*/) const { return true; } + }; + + struct SDisconnect + { + SDisconnect() {} + template <typename TVisitor> + void Visit(TVisitor & /*inVisitor*/) + { + } + bool operator==(const SDisconnect & /*inOther*/) const { return true; } + }; + + // Visitors to handle the breakpoint subtypes. + // The empty top visitor will force a compile error if there is a breakpoint type we don't + // recognize. + template <typename TDataType> + struct SBreakpointSerializationVisitor + { + }; + + template <> + struct SBreakpointSerializationVisitor<SStateEnterBreakpoint> + { + enum { Type = BreakpointTypes::StateEnter }; + template <typename TVisitor> + static void Visit(TVisitor &inVisitor, SStateEnterBreakpoint &inBp) + { + inVisitor.visitId(inBp.m_ObjectId); + } + }; + + template <> + struct SBreakpointSerializationVisitor<SStateExitBreakpoint> + { + enum { Type = BreakpointTypes::StateExit }; + template <typename TVisitor> + static void Visit(TVisitor &inVisitor, SStateExitBreakpoint &inBp) + { + inVisitor.visitId(inBp.m_ObjectId); + } + }; + + template <> + struct SBreakpointSerializationVisitor<STransitionId> + { + enum { Type = BreakpointTypes::Transition }; + template <typename TVisitor> + static void Visit(TVisitor &inVisitor, STransitionId &inBp) + { + inVisitor.visitId(inBp.m_StateId); + inVisitor.visit(inBp.m_TransitionIndex); + } + }; + +#define ITERATE_BREAKPOINT_TYPES \ + HANDLE_BREAKPOINT_TYPE(StateEnter, SStateEnterBreakpoint) \ + HANDLE_BREAKPOINT_TYPE(StateExit, SStateExitBreakpoint) \ + HANDLE_BREAKPOINT_TYPE(Transition, STransitionId) + +#define ITERATE_DEBUG_MESSAGE_TYPES \ + HANDLE_DEBUG_MESSAGE_TYPE(Initialization) \ + HANDLE_DEBUG_MESSAGE_TYPE(Connect) \ + HANDLE_DEBUG_MESSAGE_TYPE(BreakpointHit) \ + HANDLE_DEBUG_MESSAGE_TYPE(DebugLog) \ + HANDLE_DEBUG_MESSAGE_TYPE(ModifyTableValues) \ + HANDLE_DEBUG_MESSAGE_TYPE(BeginStep) \ + HANDLE_DEBUG_MESSAGE_TYPE(BeginMicrostep) \ + HANDLE_DEBUG_MESSAGE_TYPE(MicrostepEvent) \ + HANDLE_DEBUG_MESSAGE_TYPE(MicrostepData) \ + HANDLE_DEBUG_MESSAGE_TYPE(EndMicrostep) \ + HANDLE_DEBUG_MESSAGE_TYPE(EventQueued) \ + HANDLE_DEBUG_MESSAGE_TYPE(SetBreakpoint) \ + HANDLE_DEBUG_MESSAGE_TYPE(ClearBreakpoint) \ + HANDLE_DEBUG_MESSAGE_TYPE(ClearAllBreakpoints) \ + HANDLE_DEBUG_MESSAGE_TYPE(Disconnect) \ + HANDLE_DEBUG_MESSAGE_TYPE(BreakOnMicrostep) \ + HANDLE_DEBUG_MESSAGE_TYPE(Continue) + + template <typename TStructType> + struct STypeToName + { + }; + +#define HANDLE_BREAKPOINT_TYPE(enumType, structType) \ + template <> \ + struct STypeToName<structType> \ + { \ + static const char8_t *GetName() { return #enumType; } \ + }; +#define HANDLE_DEBUG_MESSAGE_TYPE(msgType) \ + template <> \ + struct STypeToName<S##msgType> \ + { \ + static const char8_t *GetName() { return #msgType; } \ + }; + + ITERATE_BREAKPOINT_TYPES + ITERATE_DEBUG_MESSAGE_TYPES + +#undef HANDLE_BREAKPOINT_TYPE +#undef HANDLE_DEBUG_MESSAGE_TYPE + + template <typename TMessageHandler> + struct SMessageParser + { + TDebugStr m_MessageHeaderStr; + TDebugStr m_TimestampStr; + TDebugStr m_ParseStr; + TDebugStr m_BreakpointTypeStr; + TDebugStr m_TempStr[4]; + TMessageHandler *m_CurrentHandler; + bool m_Valid; + static void Trim(TDebugStr &inStr) + { + if (inStr.empty()) + return; + eastl::string::size_type nonSpace = inStr.find_first_not_of(' '); + eastl::string::size_type lastNonSpace = inStr.find_last_not_of(' '); + if (nonSpace == TDebugStr::npos || lastNonSpace == TDebugStr::npos) { + inStr.clear(); + return; + } + + eastl::string::size_type msgLen = lastNonSpace - nonSpace; + inStr.erase(inStr.begin(), inStr.begin() + nonSpace); + inStr.resize(msgLen + 1); + } + + void TrimForward(eastl::string::size_type startPos) + { + eastl::string::size_type nextPos = m_ParseStr.find_first_not_of(' ', startPos); + if (nextPos != TDebugStr::npos) + m_ParseStr.erase(m_ParseStr.begin(), m_ParseStr.begin() + nextPos); + else + m_ParseStr.clear(); + } + + void TrimForward() + { + eastl::string::size_type nextSpace = m_ParseStr.find_first_of(' '); + TrimForward(nextSpace); + } + + void visitId(TDebugStr &inStr) + { + eastl::string::size_type nextSpace = m_ParseStr.find_first_of(' '); + if (nextSpace == TDebugStr::npos) + nextSpace = m_ParseStr.size(); + inStr.assign(m_ParseStr.begin(), m_ParseStr.begin() + nextSpace); + TrimForward(nextSpace); + } + void visit(TDebugStr &inStr) { randomText(inStr); } + void visit(QT3DSI32 &inData) + { + StringConversion<QT3DSI32>().StrTo(m_ParseStr.c_str(), inData); + TrimForward(); + } + void visit(QT3DSU32 &inData) + { + StringConversion<QT3DSU32>().StrTo(m_ParseStr.c_str(), inData); + TrimForward(); + } + + void visit(QT3DSU64 &inData) + { + StringConversion<QT3DSU64>().StrTo(m_ParseStr.c_str(), inData); + TrimForward(); + } + + void visit(float &inData) + { + StringConversion<QT3DSF32>().StrTo(m_ParseStr.c_str(), inData); + TrimForward(); + } + + void visit(bool &inData) + { + if (CompareNChars(m_ParseStr.c_str(), "True", 4) == 0) + inData = true; + else + inData = false; + + TrimForward(); + } + + template <typename TPtrType> + void visitOpaquePtr(TPtrType *&inPtr) + { + QT3DSU64 temp; + visit(temp); + inPtr = reinterpret_cast<TPtrType *>(static_cast<size_t>(temp)); + } + + void visit(SDatamodelTable *&inData) { visitOpaquePtr(inData); } + void visit(SDatamodelUserData *&inData) { visitOpaquePtr(inData); } + void visit(SDatamodelFunction *&inData) { visitOpaquePtr(inData); } + void visit(SDatamodelCFunction *&inData) { visitOpaquePtr(inData); } + void visit(SDatamodelThread *&inData) { visitOpaquePtr(inData); } + void visit(SNil &) {} + + void visit(SDatamodelValue &inValue) + { + visitId(m_TempStr[0]); +#define HANDLE_DATAMODEL_VALUE_TYPE(name, dtype) \ + if (AreEqual(m_TempStr[0].c_str(), #name)) { \ + dtype theData; \ + visit(theData); \ + inValue = theData; \ + } else + ITERATE_DATAMODEL_VALUE_TYPES +#undef HANDLE_DATAMODEL_VALUE_TYPE + { + QT3DS_ASSERT(false); + } + } + + void visit(TTableModificationList &inModifications) + { + QT3DSU32 numMods = 0; + visit(numMods); + inModifications.resize(numMods); + for (QT3DSU32 idx = 0, end = inModifications.size(); idx < end; ++idx) { + STableModification &theModification(inModifications[idx]); + visitId(m_TempStr[0]); + if (AreEqual(m_TempStr[0].c_str(), "SetKey")) + theModification.m_Type = TableModificationType::SetKey; + else if (AreEqual(m_TempStr[0].c_str(), "RemoveKey")) + theModification.m_Type = TableModificationType::RemoveKey; + else { + QT3DS_ASSERT(false); + continue; + } + visit(theModification.m_Entry.m_Key); + if (theModification.m_Type == TableModificationType::SetKey) + visit(theModification.m_Entry.m_Value); + } + } + + void visit(TTableEntryList &inEntryList) + { + QT3DSU32 numMods = 0; + visit(numMods); + inEntryList.resize(numMods); + for (QT3DSU32 idx = 0, end = inEntryList.size(); idx < end; ++idx) { + STableEntry &theEntry(inEntryList[idx]); + visit(theEntry.m_Key); + visit(theEntry.m_Value); + } + } + + void randomText(TDebugStr &outStr) + { + QT3DSU32 strLen = 0; + visit(strLen); + outStr.assign(m_ParseStr.begin(), m_ParseStr.begin() + strLen); + TrimForward(strLen); + } + + void visitIdList(TDebugStrList &inList) + { + QT3DSU32 numItems = 0; + visit(numItems); + inList.resize(numItems); + for (QT3DSU32 idx = 0, end = numItems; idx < end; ++idx) + visitId(inList[idx]); + } + + void visit(TTransitionIdList &inList) + { + QT3DSU32 numItems = 0; + visit(numItems); + inList.resize(numItems); + for (QT3DSU32 idx = 0, end = numItems; idx < end; ++idx) + inList[idx] = BreakpointTransition(); + } + + void visitEventName(TDebugStr &outStr) { visitId(outStr); } + + SStateEnterBreakpoint BreakpointStateEnter() + { + SStateEnterBreakpoint retval; + SBreakpointSerializationVisitor<SStateEnterBreakpoint>::Visit(*this, retval); + return retval; + } + + SStateExitBreakpoint BreakpointStateExit() + { + SStateExitBreakpoint retval; + SBreakpointSerializationVisitor<SStateExitBreakpoint>::Visit(*this, retval); + return retval; + } + + STransitionId BreakpointTransition() + { + STransitionId retval; + SBreakpointSerializationVisitor<STransitionId>::Visit(*this, retval); + return retval; + } + + void visit(SBreakpoint &breakpoint) + { + visitId(m_BreakpointTypeStr); +#define HANDLE_BREAKPOINT_TYPE(enumType, bpType) \ + if (AreEqual(STypeToName<bpType>::GetName(), m_BreakpointTypeStr.c_str())) { \ + breakpoint = Breakpoint##enumType(); \ + } else + ITERATE_BREAKPOINT_TYPES +#undef HANDLE_BREAKPOINT_TYPE + // else is defined in the iterate breakpoints. + { + m_Valid = false; + m_CurrentHandler->error("Unrecognized breakpoint type: ", + m_BreakpointTypeStr.c_str()); + } + } + + template <typename TDataType> + void DoParseT(TDebugStr &inStr, QT3DSU64 inTimestamp, QT3DSI32 streamId, + TMessageHandler &inHandler) + { + TDataType theType; + m_ParseStr = inStr; + m_Valid = true; + m_CurrentHandler = &inHandler; + theType.Visit(*this); + if (m_Valid) + inHandler.OnMessage(streamId, inTimestamp, theType); + } + + // destructive parsing. + void Parse(NVConstDataRef<QT3DSU8> data, TMessageHandler &inHandler) + { + m_ParseStr.assign((const char *)data.begin(), (const char *)data.end()); + Trim(m_ParseStr); + size_t spacePos = m_ParseStr.find_first_of(' '); + if (spacePos == TDebugStr::npos) { + m_ParseStr.assign((const char *)data.begin(), (const char *)data.end()); + inHandler.error("Failed to parse message: ", m_ParseStr.c_str()); + return; + } + qt3ds::QT3DSI32 streamId = 0; + visitId(m_MessageHeaderStr); + visit(streamId); + qt3ds::QT3DSU64 timestamp = 0; + visit(timestamp); +#define HANDLE_DEBUG_MESSAGE_TYPE(name) \ + if (AreEqual(STypeToName<S##name>::GetName(), m_MessageHeaderStr.c_str())) { \ + DoParseT<S##name>(m_ParseStr, timestamp, streamId, inHandler); \ + } else + ITERATE_DEBUG_MESSAGE_TYPES +#undef HANDLE_DEBUG_MESSAGE_TYPE + { + inHandler.error("Failed to parse message type: ", m_MessageHeaderStr.c_str()); + } + } + }; + + struct SMessageSerializer + { + TDebugStr m_Message; + void appendSpace() { m_Message.append(1, ' '); } + + void visitId(const TDebugStr &inStr) + { + m_Message.append(inStr); + appendSpace(); + } + template <typename TDataType> + void rawTypeToStr(const TDataType &inType) + { + char8_t buf[256] = { 0 }; + StringConversion<TDataType>().ToStr(inType, toDataRef(buf, 256)); + m_Message.append(buf); + appendSpace(); + } + void visit(QT3DSI32 inData) { rawTypeToStr(inData); } + void visit(QT3DSU32 inData) { rawTypeToStr(inData); } + + void visit(QT3DSU64 inData) { rawTypeToStr(inData); } + + void visit(bool inData) { rawTypeToStr(inData); } + + void visit(const TDebugStr &inStr) { randomText(inStr); } + + void visit(const SNil &) {} + + void visit(const TTransitionIdList &inData) + { + QT3DSU32 len = (QT3DSU32)inData.size(); + visit(len); + for (QT3DSU32 idx = 0; idx < len; ++idx) + Breakpoint(inData[idx]); + } + + void randomText(const TDebugStr &outStr) + { + QT3DSU32 len = (QT3DSU32)outStr.size(); + visit(len); + if (len) + m_Message.append(outStr); + appendSpace(); + } + + void visitIdList(const TDebugStrList &inList) + { + QT3DSU32 numItems = (QT3DSU32)inList.size(); + visit(numItems); + for (QT3DSU32 idx = 0; idx < numItems; ++idx) + visitId(inList[idx]); + } + + void visitEventName(const TDebugStr &outStr) { visitId(outStr); } + void visit(float inValue) { rawTypeToStr(inValue); } + template <typename TPtrType> + void visitOpaquePtr(const TPtrType *inPtr) + { + size_t ptrValue = reinterpret_cast<size_t>(inPtr); + QT3DSU64 fullValue = static_cast<QT3DSU64>(ptrValue); + visit(fullValue); + } + + void visit(SDatamodelTable *inData) { visitOpaquePtr(inData); } + void visit(SDatamodelUserData *inData) { visitOpaquePtr(inData); } + void visit(SDatamodelFunction *inData) { visitOpaquePtr(inData); } + void visit(SDatamodelCFunction *inData) { visitOpaquePtr(inData); } + void visit(SDatamodelThread *inData) { visitOpaquePtr(inData); } + + void visit(const SDatamodelValue &inValue) + { + switch (inValue.getType()) { +#define HANDLE_DATAMODEL_VALUE_TYPE(name, dtype) \ + case DatamodelValueTypes::name: { \ + m_Message.append(#name); \ + appendSpace(); \ + visit(inValue.getData<dtype>()); \ + break; \ + } + ITERATE_DATAMODEL_VALUE_TYPES +#undef HANDLE_DATAMODEL_VALUE_TYPE + default: + QT3DS_ASSERT(false); + break; + } + } + + void visit(const TTableModificationList &inModifications) + { + QT3DSU32 numItems = (QT3DSU32)inModifications.size(); + visit(numItems); + for (QT3DSU32 idx = 0, end = inModifications.size(); idx < end; ++idx) { + const STableModification &theMod(inModifications[idx]); + switch (theMod.m_Type) { + case TableModificationType::SetKey: { + m_Message.append("SetKey"); + appendSpace(); + visit(theMod.m_Entry.m_Key); + visit(theMod.m_Entry.m_Value); + break; + } + case TableModificationType::RemoveKey: { + m_Message.append("RemoveKey"); + appendSpace(); + visit(theMod.m_Entry.m_Key); + break; + } + default: + QT3DS_ASSERT(false); + m_Message.append("badvalue"); + appendSpace(); + continue; + } + } + } + void visit(TTableEntryList &inEntries) + { + QT3DSU32 numItems = (QT3DSU32)inEntries.size(); + visit(numItems); + for (QT3DSU32 idx = 0, end = inEntries.size(); idx < end; ++idx) { + const STableEntry &theEntry(inEntries[idx]); + visit(theEntry.m_Key); + visit(theEntry.m_Value); + } + } + + template <typename TBreakpointType> + void Breakpoint(const TBreakpointType &inBp) + { + SBreakpointSerializationVisitor<TBreakpointType>::Visit( + *this, const_cast<TBreakpointType &>(inBp)); + } + + void visit(const SBreakpoint &inBp) + { + switch (inBp.getType()) { +#define HANDLE_BREAKPOINT_TYPE(enumName, typeName) \ + case BreakpointTypes::enumName: \ + m_Message.append(STypeToName<typeName>::GetName()); \ + appendSpace(); \ + Breakpoint(inBp.getData<typeName>()); \ + break; + ITERATE_BREAKPOINT_TYPES +#undef HANDLE_BREAKPOINT_TYPE + default: + QT3DS_ASSERT(false); + break; + } + } + + template <typename TMsgType> + void Serialize(QT3DSI32 inStreamId, const TMsgType &inMsg, QT3DSU64 inTimestamp) + { + m_Message.clear(); + m_Message.append(STypeToName<TMsgType>::GetName()); + appendSpace(); + visit(inStreamId); + visit(inTimestamp); + const_cast<TMsgType &>(inMsg).Visit(*this); + } + + NVConstDataRef<QT3DSU8> ToRawMessage() + { + if (m_Message.size() == 0) + return NVConstDataRef<QT3DSU8>(); + return NVConstDataRef<QT3DSU8>(reinterpret_cast<const QT3DSU8 *>(m_Message.c_str()), + (QT3DSU32)m_Message.size()); + } + }; + + template <typename TLhs, typename TRhs> + struct SMsgComparer + { + static bool Compare(const TLhs &, const TRhs &) { return false; } + }; +#define HANDLE_DEBUG_MESSAGE_TYPE(typeName) \ + template <> \ + struct SMsgComparer<S##typeName, S##typeName> \ + { \ + static bool Compare(const S##typeName &lhs, const S##typeName &rhs) { return lhs == rhs; } \ + }; + ITERATE_DEBUG_MESSAGE_TYPES; +#undef HANDLE_DEBUG_MESSAGE_TYPE + + template <typename TMessageType> + struct STestMessageHandler + { + bool m_Result; + QT3DSU64 m_Timestamp; + QT3DSU32 m_StreamId; + const TMessageType &m_ExpectedResult; + + STestMessageHandler(QT3DSU32 sid, QT3DSU64 ts, const TMessageType &res) + : m_Result(false) + , m_Timestamp(ts) + , m_StreamId(sid) + , m_ExpectedResult(res) + { + } + + template <typename TCrapType> + void OnMessage(QT3DSU32 sid, QT3DSU64 ts, const TCrapType &msgType) + { + if (ts == m_Timestamp && m_StreamId == sid) + m_Result = + SMsgComparer<TCrapType, TMessageType>::Compare(msgType, m_ExpectedResult); + } + + void error(const char8_t *, const char8_t *) {} + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerValues.h b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerValues.h new file mode 100644 index 00000000..340f46cb --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateDebuggerValues.h @@ -0,0 +1,540 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_DEBUGGER_VALUES_H +#define QT3DS_STATE_DEBUGGER_VALUES_H +#pragma once +#include "Qt3DSStateDebugger.h" +#include "foundation/Qt3DSDiscriminatedUnion.h" + +namespace qt3ds { +namespace state { + namespace debugger { + using namespace qt3ds::state::editor; + + // Force compile error if unsupported datatype requested + template <typename TDataType> + struct SDebugValueTypeMap + { + }; + + template <> + struct SDebugValueTypeMap<TDebugStr> + { + static DebugValueTypes::Enum GetType() { return DebugValueTypes::String; } + }; + template <> + struct SDebugValueTypeMap<TDebugStrList> + { + static DebugValueTypes::Enum GetType() { return DebugValueTypes::StringList; } + }; + template <> + struct SDebugValueTypeMap<TTransitionIdList> + { + static DebugValueTypes::Enum GetType() { return DebugValueTypes::TransitionIdList; } + }; + + struct SDebugValueUnionTraits + { + typedef DebugValueTypes::Enum TIdType; + enum { + TBufferSize = sizeof(TDebugStrList), + }; + + static TIdType getNoDataId() { return DebugValueTypes::NoDebugValue; } + + template <typename TDataType> + static TIdType getType() + { + return SDebugValueTypeMap<TDataType>().GetType(); + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case DebugValueTypes::String: + return inVisitor(*NVUnionCast<TDebugStr *>(inData)); + case DebugValueTypes::StringList: + return inVisitor(*NVUnionCast<TDebugStrList *>(inData)); + case DebugValueTypes::TransitionIdList: + return inVisitor(*NVUnionCast<TTransitionIdList *>(inData)); + default: + QT3DS_ASSERT(false); + case DebugValueTypes::NoDebugValue: + return inVisitor(); + } + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case DebugValueTypes::String: + return inVisitor(*NVUnionCast<const TDebugStr *>(inData)); + case DebugValueTypes::StringList: + return inVisitor(*NVUnionCast<const TDebugStrList *>(inData)); + case DebugValueTypes::TransitionIdList: + return inVisitor(*NVUnionCast<const TTransitionIdList *>(inData)); + default: + QT3DS_ASSERT(false); + case DebugValueTypes::NoDebugValue: + return inVisitor(); + } + } + }; + + typedef qt3ds::foundation:: + DiscriminatedUnion<qt3ds::foundation:: + DiscriminatedUnionGenericBase<SDebugValueUnionTraits, + SDebugValueUnionTraits:: + TBufferSize>, + SDebugValueUnionTraits::TBufferSize> + TDebugValueUnionType; + + struct SDebugValue : public TDebugValueUnionType + { + SDebugValue() {} + + SDebugValue(const SDebugValue &inOther) + : TDebugValueUnionType(static_cast<const TDebugValueUnionType &>(inOther)) + { + } + + SDebugValue(const char8_t *inOther) + : TDebugValueUnionType(TDebugStr(inOther)) + { + } + + template <typename TDataType> + SDebugValue(const TDataType &inDt) + : TDebugValueUnionType(inDt) + { + } + + SDebugValue &operator=(const SDebugValue &inOther) + { + TDebugValueUnionType::operator=(inOther); + return *this; + } + + bool operator==(const SDebugValue &inOther) const + { + return TDebugValueUnionType::operator==(inOther); + } + bool operator!=(const SDebugValue &inOther) const + { + return TDebugValueUnionType::operator!=(inOther); + } + + bool empty() const { return getType() == DebugValueTypes::NoDebugValue; } + }; + + struct SDebugValueOpt : public Option<SDebugValue> + { + SDebugValueOpt(const SDebugValueOpt &inOther) + : Option<SDebugValue>(inOther) + { + } + SDebugValueOpt(const Empty &) + : Option<SDebugValue>() + { + } + SDebugValueOpt() {} + template <typename TDataType> + SDebugValueOpt(const TDataType &inOther) + : Option<SDebugValue>(SDebugValue(inOther)) + { + } + SDebugValueOpt &operator=(const SDebugValueOpt &inOther) + { + Option<SDebugValue>::operator=(inOther); + return *this; + } + }; + + // Breakpoint subtypes + + struct SStateEnterBreakpoint + { + TDebugStr m_ObjectId; + SStateEnterBreakpoint(const TDebugStr &inObj = TDebugStr()) + : m_ObjectId(inObj) + { + } + bool operator==(const SStateEnterBreakpoint &inOther) const + { + return m_ObjectId == inOther.m_ObjectId; + } + }; + + struct SStateExitBreakpoint + { + TDebugStr m_ObjectId; + SStateExitBreakpoint(const TDebugStr &inObj = TDebugStr()) + : m_ObjectId(inObj) + { + } + bool operator==(const SStateExitBreakpoint &inOther) const + { + return m_ObjectId == inOther.m_ObjectId; + } + }; + + template <typename TDataType> + struct SBreakpointTypeMap + { + static BreakpointTypes::Enum GetType() + { + return BreakpointTypes::UnknownBreakpointType; + } + }; + + template <> + struct SBreakpointTypeMap<SStateEnterBreakpoint> + { + static BreakpointTypes::Enum GetType() { return BreakpointTypes::StateEnter; } + }; + template <> + struct SBreakpointTypeMap<SStateExitBreakpoint> + { + static BreakpointTypes::Enum GetType() { return BreakpointTypes::StateExit; } + }; + template <> + struct SBreakpointTypeMap<STransitionId> + { + static BreakpointTypes::Enum GetType() { return BreakpointTypes::Transition; } + }; + + struct SBreakpointUnionTraits + { + typedef BreakpointTypes::Enum TIdType; + enum { + TBufferSize = sizeof(STransitionId), + }; + + static TIdType getNoDataId() { return BreakpointTypes::UnknownBreakpointType; } + + template <typename TDataType> + static TIdType getType() + { + return SBreakpointTypeMap<TDataType>().GetType(); + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case BreakpointTypes::StateEnter: + return inVisitor(*NVUnionCast<SStateEnterBreakpoint *>(inData)); + case BreakpointTypes::StateExit: + return inVisitor(*NVUnionCast<SStateExitBreakpoint *>(inData)); + case BreakpointTypes::Transition: + return inVisitor(*NVUnionCast<STransitionId *>(inData)); + default: + QT3DS_ASSERT(false); + case BreakpointTypes::UnknownBreakpointType: + return inVisitor(); + } + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case BreakpointTypes::StateEnter: + return inVisitor(*NVUnionCast<const SStateEnterBreakpoint *>(inData)); + case BreakpointTypes::StateExit: + return inVisitor(*NVUnionCast<const SStateExitBreakpoint *>(inData)); + case BreakpointTypes::Transition: + return inVisitor(*NVUnionCast<const STransitionId *>(inData)); + default: + QT3DS_ASSERT(false); + case BreakpointTypes::UnknownBreakpointType: + return inVisitor(); + } + } + }; + + typedef qt3ds::foundation:: + DiscriminatedUnion<qt3ds::foundation:: + DiscriminatedUnionGenericBase<SBreakpointUnionTraits, + SBreakpointUnionTraits:: + TBufferSize>, + SBreakpointUnionTraits::TBufferSize> + TBreakpointUnionType; + + struct SBreakpoint : public TBreakpointUnionType + { + SBreakpoint() {} + + SBreakpoint(const SBreakpoint &inOther) + : TBreakpointUnionType(static_cast<const TBreakpointUnionType &>(inOther)) + { + } + + SBreakpoint(const char8_t *inOther) + : TBreakpointUnionType(TEditorStr(inOther)) + { + } + + template <typename TDataType> + SBreakpoint(const TDataType &inDt) + : TBreakpointUnionType(inDt) + { + } + + SBreakpoint &operator=(const SBreakpoint &inOther) + { + TBreakpointUnionType::operator=(inOther); + return *this; + } + + bool operator==(const SBreakpoint &inOther) const + { + return TBreakpointUnionType::operator==(inOther); + } + bool operator!=(const SBreakpoint &inOther) const + { + return TBreakpointUnionType::operator!=(inOther); + } + + bool empty() const { return getType() == SBreakpointUnionTraits::getNoDataId(); } + }; + + struct SNil + { + bool operator==(const SNil &) const { return true; } + }; + +#define ITERATE_DATAMODEL_VALUE_TYPES \ + HANDLE_DATAMODEL_VALUE_TYPE(Number, float) \ + HANDLE_DATAMODEL_VALUE_TYPE(String, TDebugStr) \ + HANDLE_DATAMODEL_VALUE_TYPE(Nil, SNil) \ + HANDLE_DATAMODEL_VALUE_TYPE(Boolean, bool) \ + HANDLE_DATAMODEL_VALUE_TYPE(Table, SDatamodelTable *) \ + HANDLE_DATAMODEL_VALUE_TYPE(UserData, SDatamodelUserData *) \ + HANDLE_DATAMODEL_VALUE_TYPE(Function, SDatamodelFunction *) \ + HANDLE_DATAMODEL_VALUE_TYPE(CFunction, SDatamodelCFunction *) \ + HANDLE_DATAMODEL_VALUE_TYPE(Thread, SDatamodelThread *) + + template <typename TDataType> + struct SDatamodelValueTypeMap + { + }; + +#define HANDLE_DATAMODEL_VALUE_TYPE(name, type) \ + template <> \ + struct SDatamodelValueTypeMap<type> \ + { \ + static DatamodelValueTypes::Enum GetType() { return DatamodelValueTypes::name; } \ + }; + ITERATE_DATAMODEL_VALUE_TYPES +#undef HANDLE_DATAMODEL_VALUE_TYPE + + struct SDatamodelValueUnionTraits + { + typedef DatamodelValueTypes::Enum TIdType; + enum { + TBufferSize = sizeof(TDebugStr), + }; + + static TIdType getNoDataId() { return DatamodelValueTypes::UnknownType; } + + template <typename TDataType> + static TIdType getType() + { + return SDatamodelValueTypeMap<TDataType>().GetType(); + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { +#define HANDLE_DATAMODEL_VALUE_TYPE(name, type) \ + case DatamodelValueTypes::name: \ + return inVisitor(*NVUnionCast<type *>(inData)); + ITERATE_DATAMODEL_VALUE_TYPES +#undef HANDLE_DATAMODEL_VALUE_TYPE + default: + QT3DS_ASSERT(false); + case DatamodelValueTypes::UnknownType: + return inVisitor(); + } + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { +#define HANDLE_DATAMODEL_VALUE_TYPE(name, type) \ + case DatamodelValueTypes::name: \ + return inVisitor(*NVUnionCast<const type *>(inData)); + ITERATE_DATAMODEL_VALUE_TYPES +#undef HANDLE_DATAMODEL_VALUE_TYPE + default: + QT3DS_ASSERT(false); + case DatamodelValueTypes::UnknownType: + return inVisitor(); + } + } + }; + + typedef qt3ds::foundation:: + DiscriminatedUnion<qt3ds::foundation:: + DiscriminatedUnionGenericBase<SDatamodelValueUnionTraits, + SDatamodelValueUnionTraits:: + TBufferSize>, + SDatamodelValueUnionTraits::TBufferSize> + TDatamodelValueUnionType; + + struct SDatamodelValue : public TDatamodelValueUnionType + { + SDatamodelValue() {} + + SDatamodelValue(const SDatamodelValue &inOther) + : TDatamodelValueUnionType(static_cast<const TDatamodelValueUnionType &>(inOther)) + { + } + + SDatamodelValue(const char8_t *inOther) + : TDatamodelValueUnionType(TDebugStr(inOther)) + { + } + + template <typename TDataType> + SDatamodelValue(const TDataType &inDt) + : TDatamodelValueUnionType(inDt) + { + } + + SDatamodelValue &operator=(const SDatamodelValue &inOther) + { + TDatamodelValueUnionType::operator=(inOther); + return *this; + } + + bool operator==(const SDatamodelValue &inOther) const + { + return TDatamodelValueUnionType::operator==(inOther); + } + bool operator!=(const SDatamodelValue &inOther) const + { + return TDatamodelValueUnionType::operator!=(inOther); + } + + bool empty() const { return getType() == SDatamodelValueUnionTraits::getNoDataId(); } + }; + + struct STableEntry + { + TDebugStr m_Key; + SDatamodelValue m_Value; + STableEntry(const TDebugStr &inKey = TDebugStr(), + const SDatamodelValue &inValue = SDatamodelValue()) + : m_Key(inKey) + , m_Value(inValue) + { + } + + bool operator==(const STableEntry &inOther) const + { + return m_Key == inOther.m_Key && m_Value == inOther.m_Value; + } + }; + + struct TableModificationType + { + enum Enum { + UnknownModification = 0, + SetKey = 1, + RemoveKey = 2, + }; + }; + + struct STableModification + { + STableEntry m_Entry; + TableModificationType::Enum m_Type; + STableModification( + const STableEntry &inEntry = STableEntry(), + TableModificationType::Enum inEnum = TableModificationType::UnknownModification) + : m_Entry(inEntry) + , m_Type(inEnum) + { + } + bool operator==(const STableModification &inMod) const + { + return inMod.m_Entry == m_Entry && inMod.m_Type == m_Type; + } + }; + + typedef eastl::vector<STableModification> TTableModificationList; + } +} +} + +#ifndef _INTEGRITYPLATFORM +namespace qt3ds { +namespace foundation { + template <> + struct DestructTraits<qt3ds::state::debugger::SNil> + { + void destruct(qt3ds::state::debugger::SNil &) {} + }; + template <> + struct DestructTraits<qt3ds::state::debugger::SDatamodelTable *> + { + void destruct(qt3ds::state::debugger::SDatamodelTable *) {} + }; + template <> + struct DestructTraits<qt3ds::state::debugger::SDatamodelUserData *> + { + void destruct(qt3ds::state::debugger::SDatamodelUserData *) {} + }; + template <> + struct DestructTraits<qt3ds::state::debugger::SDatamodelFunction *> + { + void destruct(qt3ds::state::debugger::SDatamodelFunction *) {} + }; + template <> + struct DestructTraits<qt3ds::state::debugger::SDatamodelCFunction *> + { + void destruct(qt3ds::state::debugger::SDatamodelCFunction *) {} + }; + template <> + struct DestructTraits<qt3ds::state::debugger::SDatamodelThread *> + { + void destruct(qt3ds::state::debugger::SDatamodelThread *) {} + }; +} +} +#endif + +#endif diff --git a/src/Runtime/Source/stateapplication/debugger/Qt3DSStateTest.h b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateTest.h new file mode 100644 index 00000000..6edbe366 --- /dev/null +++ b/src/Runtime/Source/stateapplication/debugger/Qt3DSStateTest.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_TEST_H +#define QT3DS_STATE_TEST_H +#include "Qt3DSState.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/Qt3DSOption.h" + +namespace qt3ds { +namespace state { + namespace test { + + struct LogType + { + enum Enum { + Error = 0, + Info = 1, + }; + }; + + struct STestResults + { + QT3DSU32 m_TotalTests; + QT3DSU32 m_PassingTests; + STestResults(QT3DSU32 totalTests = 0, QT3DSU32 testPassed = 0) + : m_TotalTests(totalTests) + , m_PassingTests(testPassed) + { + } + STestResults &operator+=(const STestResults &inOther) + { + m_TotalTests += inOther.m_TotalTests; + m_PassingTests += inOther.m_PassingTests; + return *this; + } + + bool Failed() const { return m_TotalTests != m_PassingTests; } + bool Passed() const { return !Failed(); } + }; + + class IDataLogger + { + protected: + virtual ~IDataLogger() {} + public: + virtual void Log(LogType::Enum inLogType, const char8_t *file, int line, + const char8_t *message) = 0; + }; + + // Run a data test returning a true/false result for pass/fail. + class IDataTest + { + public: + static Option<STestResults> RunFile(const char8_t *fname, const char8_t *inRootDir, + IDataLogger &inLogger); + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSSceneGraphArchitectDebugger.cpp b/src/Runtime/Source/stateapplication/editor/Qt3DSSceneGraphArchitectDebugger.cpp new file mode 100644 index 00000000..e15f587d --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSSceneGraphArchitectDebugger.cpp @@ -0,0 +1,574 @@ +/**************************************************************************** +** +** 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 "Qt3DSSceneGraphDebugger.h" +#include "Qt3DSSceneGraphDebuggerValue.h" +#include "Qt3DSSceneGraphDebuggerProtocol.h" +#include "Qt3DSUIADatamodel.h" +#include "Qt3DSStateEditorValue.h" +#include "Qt3DSUIADatamodelValue.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/Qt3DSContainers.h" +#include "Qt3DSKernelTypes.h" +#include "Qt3DSHash.h" //we need to duplicate the hash attribute calls +#include "EASTL/set.h" + +using namespace qt3ds; +using namespace qt3ds::state; +using namespace qt3ds::app; +using namespace qt3ds::state::debugger; + +namespace { + +struct SAppElemVectorSet +{ + nvvector<SAppElement *> m_Elements; + eastl::hash_set<SAppElement *, eastl::hash<SAppElement *>, eastl::equal_to<SAppElement *>, + ForwardingAllocator> + m_Set; + SAppElemVectorSet(NVAllocatorCallback &alloc) + : m_Elements(alloc, "AppElemVectorSet") + , m_Set(ForwardingAllocator(alloc, "AppElemVectorSet")) + { + } + + void insert(SAppElement *elem) + { + if (m_Set.insert(elem).second) + m_Elements.push_back(elem); + } + + void clear() + { + m_Elements.clear(); + m_Set.clear(); + } + NVConstDataRef<SAppElement *> ToDataRef() { return m_Elements; } +}; + +struct SValueIndex +{ + QT3DSU32 m_ValueIndex; + QT3DSU32 m_Component; + SValueIndex(QT3DSU32 vi, QT3DSU32 component) + : m_ValueIndex(vi) + , m_Component(component) + { + } +}; + +struct SAppElementEntry +{ + Q3DStudio::TAttOrArgList m_AttList; + eastl::hash_map<QT3DSI32, SValueIndex> m_HashToIndexMap; + eastl::vector<app::SDatamodelValue> m_RuntimeValues; +}; + +struct SPresentationEntry +{ + CRegisteredString m_Id; + eastl::hash_map<CRegisteredString, SAppElement *> m_ElementMap; +}; + +Option<float> ValueToFloat(const SSGValue &val) +{ + if (val.getType() == SGPropertyValueTypes::Float) + return val.getData<float>(); + return Empty(); +} + +Option<QT3DSI32> ValueToInteger(const SSGValue &val) +{ + if (val.getType() == SGPropertyValueTypes::I32) + return val.getData<QT3DSI32>(); + return Empty(); +} + +Option<CRegisteredString> ValueToString(const SSGValue &val) +{ + if (val.getType() == SGPropertyValueTypes::String) + return val.getData<CRegisteredString>(); + return Empty(); +} + +template <typename TDtype> +struct SValueSetter +{ +}; + +template <> +struct SValueSetter<float> +{ + static void SetValue(const SSGValue &incoming, float &outgoing, QT3DSU32 /*component*/) + { + Option<float> val = ValueToFloat(incoming); + if (val.hasValue()) + outgoing = *val; + } +}; + +template <> +struct SValueSetter<QT3DSVec2> +{ + static void SetValue(const SSGValue &incoming, QT3DSVec2 &outgoing, QT3DSU32 component) + { + Option<float> val = ValueToFloat(incoming); + if (val.hasValue()) { + switch (component) { + case 0: + outgoing.x = *val; + break; + case 1: + outgoing.y = *val; + break; + default: + QT3DS_ASSERT(false); + break; + } + } + } +}; + +template <> +struct SValueSetter<QT3DSVec3> +{ + static void SetValue(const SSGValue &incoming, QT3DSVec3 &outgoing, QT3DSU32 component) + { + Option<float> val = ValueToFloat(incoming); + if (val.hasValue()) { + switch (component) { + case 0: + outgoing.x = *val; + break; + case 1: + outgoing.y = *val; + break; + case 2: + outgoing.z = *val; + break; + default: + QT3DS_ASSERT(false); + break; + } + } + } +}; + +template <> +struct SValueSetter<QT3DSI32> +{ + static void SetValue(const SSGValue &incoming, QT3DSI32 &outgoing, QT3DSU32 /*component*/) + { + Option<QT3DSI32> val = ValueToInteger(incoming); + if (val.hasValue()) + outgoing = *val; + } +}; + +template <> +struct SValueSetter<eastl::string> +{ + static void SetValue(const SSGValue &incoming, eastl::string &outgoing, QT3DSU32 /*component*/) + { + Option<CRegisteredString> val = ValueToString(incoming); + if (val.hasValue()) + outgoing = eastl::string(val->c_str()); + } +}; + +template <> +struct SValueSetter<bool> +{ + static void SetValue(const SSGValue &incoming, bool &outgoing, QT3DSU32 /*component*/) + { + Option<QT3DSI32> val = ValueToInteger(incoming); + if (val.hasValue()) + outgoing = (*val) != 0 ? true : false; + } +}; + +// Element ref. Not sure what to do about this right now +template <> +struct SValueSetter<SGuid> +{ + static void SetValue(const SSGValue & /*incoming*/, SGuid & /*outgoing*/, QT3DSU32 /*component*/) + { + } +}; + +template <> +struct SValueSetter<CRegisteredString> +{ + static void SetValue(const SSGValue & /*incoming*/, CRegisteredString & /*outgoing*/, + QT3DSU32 /*component*/) + { + QT3DS_ASSERT(false); + } +}; + +template <> +struct SValueSetter<SObjectRef> +{ + static void SetValue(const SSGValue & /*incoming*/, SObjectRef & /*outgoing*/, + QT3DSU32 /*component*/) + { + } +}; + +template <> +struct SValueSetter<CStringOrInt> +{ + static void SetValue(const SSGValue & /*incoming*/, CStringOrInt & /*outgoing*/, + QT3DSU32 /*component*/) + { + } +}; + +struct SValueSetterVisitor +{ + const SSGValue &m_Incoming; + QT3DSU32 m_Component; + SValueSetterVisitor &operator=(const SValueSetterVisitor &); + SValueSetterVisitor(const SSGValue &i, QT3DSU32 c) + : m_Incoming(i) + , m_Component(c) + { + } + template <typename TDataType> + void operator()(TDataType &dt) + { + SValueSetter<TDataType>::SetValue(m_Incoming, dt, m_Component); + } + // object refs can generate this response. + void operator()() {} +}; + +void SetComponentValue(const SSGValue &incoming, app::SDatamodelValue &outgoing, QT3DSU32 component) +{ + SValueSetterVisitor visitor(incoming, component); + outgoing.visit<void>(visitor); +} + +struct SArchitectDebuggerImpl : public ISceneGraphArchitectDebugger +{ + NVFoundationBase &m_Foundation; + NVScopedRefCounted<IDatamodel> m_Datamodel; + NVScopedRefCounted<IDebugOutStream> m_Stream; + ISceneGraphArchitectDebuggerListener *m_Listener; + nvhash_map<QT3DSU64, SAppElement *> m_ElemAppElemMap; + nvhash_map<SAppElement *, SAppElementEntry> m_RuntimeValues; + SAppElemVectorSet m_DirtySet; + eastl::vector<SPresentationEntry> m_Presentations; + bool m_Valid; + QT3DSI32 mRefCount; + + SArchitectDebuggerImpl(IDatamodel &dm) + : m_Foundation(dm.GetFoundation()) + , m_Datamodel(dm) + , m_Stream(NULL) + , m_Listener(NULL) + , m_ElemAppElemMap(dm.GetFoundation().getAllocator(), "ElemAppMap") + , m_RuntimeValues(dm.GetFoundation().getAllocator(), "RuntimeValues") + , m_DirtySet(dm.GetFoundation().getAllocator()) + , m_Valid(true) + , mRefCount(0) + { + } + + void addRef() { atomicIncrement(&mRefCount); } + void release() + { + // ensure the datamodel sticks around until after we are destroyed + NVScopedRefCounted<IDatamodel> dm(m_Datamodel); + atomicDecrement(&mRefCount); + if (mRefCount <= 0) { + NVDelete(m_Foundation.getAllocator(), this); + } + } + + virtual IDatamodel &GetDatamodel() { return *m_Datamodel; } + + static QT3DSI32 GetHashValue(const char *name, const char *component, eastl::string &ioWorkspace) + { + ioWorkspace.assign(nonNull(name)); + ioWorkspace.append("."); + ioWorkspace.append(component); + return Q3DStudio::CHash::HashAttribute(ioWorkspace.c_str()); + } + + void AppendAttribute(const char *name, const char *formalName, ERuntimeDataModelDataType dtype, + SAppElementEntry &theEntry) + { + SAttOrArg newValue; + newValue.m_Name = name; + newValue.m_FormalName = formalName; + newValue.m_DataType = dtype; + theEntry.m_AttList.push_back(newValue); + theEntry.m_RuntimeValues.push_back(qt3ds::app::SDatamodelValue()); + } + + void LoadAppElement(SAppElement &elem, SPresentationEntry &entry) + { + entry.m_ElementMap.insert(eastl::make_pair( + m_Datamodel->GetStringTable().RegisterStr(m_Datamodel->GetElementId(elem).c_str()), + &elem)); + SAppElementEntry &theEntry = m_RuntimeValues[&elem]; + theEntry.m_AttList = m_Datamodel->GetElementAttributes(elem); + theEntry.m_RuntimeValues = m_Datamodel->GetElementAttributeInitialValues(elem); + AppendAttribute("active", "Active", ERuntimeDataModelDataTypeBool, theEntry); + + if (m_Datamodel->IsComponent(elem)) { + AppendAttribute("slide", "(Slide)", ERuntimeDataModelDataTypeLong, theEntry); + AppendAttribute("time", "(Time)", ERuntimeDataModelDataTypeLong, theEntry); + AppendAttribute("paused", "(Mode)", ERuntimeDataModelDataTypeBool, theEntry); + } + eastl::string hashTemp; + for (QT3DSU32 idx = 0, end = (QT3DSU32)theEntry.m_AttList.size(); idx < end; ++idx) { + // Build out the component hash names. + const Q3DStudio::SAttOrArg &theProp(theEntry.m_AttList[idx]); + switch (theProp.m_DataType) { + // one component, one hash, just hash the name + default: + theEntry.m_HashToIndexMap.insert( + eastl::make_pair((QT3DSI32)Q3DStudio::CHash::HashAttribute(theProp.m_Name.c_str()), + SValueIndex(idx, 0))); + break; + case ERuntimeDataModelDataTypeFloat2: { + theEntry.m_HashToIndexMap.insert(eastl::make_pair( + GetHashValue(theProp.m_Name.c_str(), "x", hashTemp), SValueIndex(idx, 0))); + theEntry.m_HashToIndexMap.insert(eastl::make_pair( + GetHashValue(theProp.m_Name.c_str(), "y", hashTemp), SValueIndex(idx, 1))); + } break; + case ERuntimeDataModelDataTypeFloat3: { + const char *compNames[3] = { "x", "y", "z" }; + if (theProp.m_MetaType == ERuntimeAdditionalMetaDataTypeColor) { + compNames[0] = "r"; + compNames[1] = "g"; + compNames[2] = "b"; + } + + theEntry.m_HashToIndexMap.insert( + eastl::make_pair(GetHashValue(theProp.m_Name.c_str(), compNames[0], hashTemp), + SValueIndex(idx, 0))); + theEntry.m_HashToIndexMap.insert( + eastl::make_pair(GetHashValue(theProp.m_Name.c_str(), compNames[1], hashTemp), + SValueIndex(idx, 1))); + theEntry.m_HashToIndexMap.insert( + eastl::make_pair(GetHashValue(theProp.m_Name.c_str(), compNames[2], hashTemp), + SValueIndex(idx, 2))); + } break; + } + // Set a value + if (theEntry.m_RuntimeValues[idx].getType() == ERuntimeDataModelDataTypeNone) { + switch (theProp.m_DataType) { + case ERuntimeDataModelDataTypeFloat: + theEntry.m_RuntimeValues[idx] = 0.0f; + break; + case ERuntimeDataModelDataTypeFloat2: + theEntry.m_RuntimeValues[idx] = QT3DSVec2(0, 0); + break; + case ERuntimeDataModelDataTypeFloat3: + theEntry.m_RuntimeValues[idx] = QT3DSVec3(0, 0, 0); + break; + case ERuntimeDataModelDataTypeLong: + theEntry.m_RuntimeValues[idx] = (QT3DSI32)0; + break; + case ERuntimeDataModelDataTypeString: + theEntry.m_RuntimeValues[idx] = eastl::string(); + break; + case ERuntimeDataModelDataTypeBool: + theEntry.m_RuntimeValues[idx] = false; + break; + case ERuntimeDataModelDataTypeStringRef: + theEntry.m_RuntimeValues[idx] = eastl::string(); + break; + // object references are stored as string values. + case ERuntimeDataModelDataTypeObjectRef: + theEntry.m_RuntimeValues[idx] = eastl::string(); + break; + case ERuntimeDataModelDataTypeStringOrInt: + theEntry.m_RuntimeValues[idx] = eastl::string(); + break; + } + } + } + eastl::vector<SAppElement *> children = m_Datamodel->GetElementChildren(elem); + for (size_t childIdx = 0, childEnd = children.size(); childIdx < childEnd; ++childIdx) { + LoadAppElement(*children[childIdx], entry); + } + } + + void LoadDatamodel() + { + eastl::vector<app::SPresentation> presentations = m_Datamodel->GetPresentations(); + m_Presentations.clear(); + m_RuntimeValues.clear(); + m_ElemAppElemMap.clear(); + m_Presentations.resize(presentations.size()); + for (size_t idx = 0, end = presentations.size(); idx < end; ++idx) { + app::SPresentation &incoming(presentations[idx]); + SPresentationEntry &pres = m_Presentations[idx]; + pres.m_Id = m_Datamodel->GetStringTable().RegisterStr(incoming.m_Id.c_str()); + if (incoming.m_Scene) + LoadAppElement(*incoming.m_Scene, pres); + } + } + + SAppElementEntry *GetRuntimeValues(app::SAppElement &elem) + { + nvhash_map<SAppElement *, SAppElementEntry>::iterator iter = m_RuntimeValues.find(&elem); + if (iter != m_RuntimeValues.end()) + return &iter->second; + return NULL; + } + + SPresentationEntry *FindPresentation(CRegisteredString &str) + { + for (size_t idx = 0, end = m_Presentations.size(); idx < end; ++idx) + if (m_Presentations[idx].m_Id == str) + return &m_Presentations[idx]; + return NULL; + } + + /*External interface for clients*/ + + void SetListener(ISceneGraphArchitectDebuggerListener *listener) { m_Listener = listener; } + + Q3DStudio::TAttOrArgList GetElementAttributes(app::SAppElement &elem) + { + SAppElementEntry *entry = GetRuntimeValues(elem); + if (entry) + return entry->m_AttList; + return Q3DStudio::TAttOrArgList(); + } + + eastl::vector<app::SDatamodelValue> GetElementAttributeValues(app::SAppElement &elem) + { + SAppElementEntry *entry = GetRuntimeValues(elem); + if (entry) + return entry->m_RuntimeValues; + return eastl::vector<app::SDatamodelValue>(); + } + + void OnMessageReceived(const SDebugStreamMessage &inMessage) + { + if (!m_Valid) + return; + SSGProtocolReader theReader(inMessage.m_Data, m_Datamodel->GetStringTable()); + while (theReader.Finished() == false && m_Valid) { + SSGProtocolMessageTypes::Enum theMessageType = theReader.MessageType(); + switch (theMessageType) { + case SSGProtocolMessageTypes::Initialization: { + QT3DSU32 version = theReader.ReadInitialization(); + if (version > GetSceneGraphProtocolVersion()) { + QT3DS_ASSERT(false); + m_Foundation.error(QT3DS_INVALID_OPERATION, + "Invalid scene graph debugger protocol version"); + m_Valid = false; + } + } break; + case SSGProtocolMessageTypes::IdUpdate: { + SIdUpdate theUpdate = theReader.ReadIdUpdate(); + SPresentationEntry *theEntry = FindPresentation(theUpdate.m_PresentationId); + if (theEntry) { + for (size_t idx = 0, end = theUpdate.m_IdUpdates.size(); idx < end; ++idx) { + const SElemMap &elemMap(theUpdate.m_IdUpdates[idx]); + eastl::hash_map<CRegisteredString, SAppElement *>::iterator iter = + theEntry->m_ElementMap.find(elemMap.m_Id); + if (iter != theEntry->m_ElementMap.end()) + m_ElemAppElemMap[elemMap.m_Elem] = iter->second; + else { + QT3DS_ASSERT(false); + m_Foundation.error(QT3DS_WARN, "Failed to map element"); + } + } + } + } break; + case SSGProtocolMessageTypes::ElemUpdate: { + SElemUpdate theUpdate = theReader.ReadElemUpdate(); + nvhash_map<QT3DSU64, SAppElement *>::iterator ptrToElem = + m_ElemAppElemMap.find(theUpdate.m_Elem); + if (ptrToElem != m_ElemAppElemMap.end()) { + nvhash_map<SAppElement *, SAppElementEntry>::iterator elemToEntry = + m_RuntimeValues.find(ptrToElem->second); + if (elemToEntry != m_RuntimeValues.end()) { + SAppElementEntry &theEntry(elemToEntry->second); + m_DirtySet.insert(elemToEntry->first); + for (size_t idx = 0, end = theUpdate.m_Updates.size(); idx < end; ++idx) { + const SValueUpdate &theValue(theUpdate.m_Updates[idx]); + eastl::hash_map<QT3DSI32, SValueIndex>::iterator hashIndex = + theEntry.m_HashToIndexMap.find(theValue.m_Hash); + if (hashIndex != theEntry.m_HashToIndexMap.end()) { + const SValueIndex theIdx = hashIndex->second; + SetComponentValue(theValue.m_Value, + theEntry.m_RuntimeValues[theIdx.m_ValueIndex], + theIdx.m_Component); + } + } + } else { + QT3DS_ASSERT(false); + m_Foundation.error(QT3DS_WARN, "Failed to map element"); + } + } else { + QT3DS_ASSERT(false); + m_Foundation.error(QT3DS_WARN, "Failed to map element"); + } + } break; + case SSGProtocolMessageTypes::Frame: { + NVConstDataRef<SAppElement *> dirtyItems = m_DirtySet.ToDataRef(); + if (dirtyItems.size() && m_Listener) { + m_Listener->OnItemsDirty(dirtyItems); + m_DirtySet.clear(); + } + } break; + } + } // end while + } + + void AttachToStream(IDebugOutStream &inStream) { m_Stream = &inStream; } + + void RefreshData(bool inNeedReloadData) + { + if (inNeedReloadData) + m_Datamodel->RefreshFile(); + LoadDatamodel(); + } +}; +} + +ISceneGraphArchitectDebugger & +ISceneGraphArchitectDebugger::Create(qt3ds::app::IDatamodel &inDatamodel) +{ + SArchitectDebuggerImpl &retval = + *QT3DS_NEW(inDatamodel.GetFoundation().getAllocator(), SArchitectDebuggerImpl)(inDatamodel); + retval.LoadDatamodel(); + return retval; +} diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateDebuggerMaster.cpp b/src/Runtime/Source/stateapplication/editor/Qt3DSStateDebuggerMaster.cpp new file mode 100644 index 00000000..83fe016f --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateDebuggerMaster.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateDebugger.h" +#include "Qt3DSStateDebuggerProtocol.h" +#include "foundation/IOStreams.h" +#include "foundation/Qt3DSAtomic.h" + +using namespace qt3ds::state; +using namespace qt3ds::state::debugger; + +namespace { +MallocAllocator g_MallocAlloc; + +struct SDebugStrInStream : public IInStream +{ + size_t m_Pos; + const TDebugStr &m_Str; + SDebugStrInStream(const TDebugStr &inStr) + : m_Pos(0) + , m_Str(inStr) + { + } + virtual QT3DSU32 Read(NVDataRef<QT3DSU8> data) + { + size_t available = NVMin((size_t)data.size(), m_Str.size() - m_Pos); + if (available) { + memCopy(data.begin(), m_Str.data() + m_Pos, (QT3DSU32)available); + m_Pos += available; + } + return (QT3DSU32)available; + } +}; + +struct SDebuggerMaster : public IDebuggerMaster +{ + typedef eastl::hash_map<QT3DSI32, NVScopedRefCounted<IDebuggedInterpreter>> TIdInterpreterMap; + + QT3DSI32 mRefCount; + NVScopedRefCounted<IDebugOutStream> m_OutStream; + TIdInterpreterMap m_Interpreters; + IDebuggerMasterListener &m_Listener; + SMessageSerializer m_Serializer; + SMessageParser<SDebuggerMaster> m_Parser; + TDebugStr m_LogStr; + TDebugStr m_MessageStr; + bool m_Invalid; + + SDebuggerMaster(IDebugOutStream &inStream, IDebuggerMasterListener &listener) + : mRefCount(0) + , m_OutStream(inStream) + , m_Listener(listener) + , m_Invalid(false) + { + } + + virtual ~SDebuggerMaster() + { + // Disconnect(); + } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(g_MallocAlloc); + + template <typename TMessageType> + void SendMessage(QT3DSI32 inStreamId, const TMessageType &inMessage) + { + m_Serializer.Serialize(inStreamId, inMessage, 0); + m_OutStream->Write(m_Serializer.ToRawMessage()); + } + + virtual void OnMessageReceived(const SDebugStreamMessage &inMessage) + { + if (m_Invalid) + return; + m_Parser.Parse(inMessage.m_Data, *this); + } + + void ReleaseAllInterpreters() + { + for (TIdInterpreterMap::iterator iter = m_Interpreters.begin(), end = m_Interpreters.end(); + iter != end; ++iter) { + iter->second->Disconnect(); + m_Listener.OnInterpreterDisconnected(*iter->second.mPtr); + } + m_Interpreters.clear(); + } + + virtual void Disconnect() + { + SendMessage(-1, SDisconnect()); + ReleaseAllInterpreters(); + } + + IDebuggedInterpreter *FindInterpreter(QT3DSI32 inStreamId) + { + TIdInterpreterMap::iterator iter = m_Interpreters.find(inStreamId); + if (iter != m_Interpreters.end()) + return iter->second.mPtr; + return NULL; + } + +// Set of ignored messages +#define IGNORE_DEBUG_MESSAGE_TYPE(tname) \ + void OnMessage(QT3DSU32, QT3DSU64, const S##tname &) { QT3DS_ASSERT(false); } + + // this are outgoing messages; we shouldn't be receiving them. + IGNORE_DEBUG_MESSAGE_TYPE(SetBreakpoint) + IGNORE_DEBUG_MESSAGE_TYPE(ClearBreakpoint) + IGNORE_DEBUG_MESSAGE_TYPE(BreakOnMicrostep) + IGNORE_DEBUG_MESSAGE_TYPE(ClearAllBreakpoints) + IGNORE_DEBUG_MESSAGE_TYPE(Continue) + + void OnMessage(QT3DSU32 sid, QT3DSU64, const SConnect &inMessage) + { + SDebugStrInStream theStream(inMessage.m_SCXMLData); + TEditorPtr theEditor(IEditor::CreateEditor(inMessage.m_Filename.c_str(), theStream)); + if (theEditor) { + IDebuggedInterpreter &theInterpreter = IDebuggedInterpreter::Create( + g_MallocAlloc, *m_OutStream.mPtr, theEditor, inMessage.m_Filename, sid, + toConstDataRef(inMessage.m_Configuration.data(), + (QT3DSU32)inMessage.m_Configuration.size()), + m_Listener); + m_Interpreters.insert(eastl::make_pair(sid, &theInterpreter)); + m_Listener.OnInterpreterConnected(theInterpreter); + } else { + m_LogStr.assign("Connection failed: "); + m_LogStr.append(inMessage.m_Filename); + // log the failure somehow. + m_Listener.OnLog(NULL, m_LogStr); + } + } + + void OnMessage(QT3DSU32 sid, QT3DSU64, const SBreakpointHit &inMessage) + { + IDebuggedInterpreter *interp = FindInterpreter(sid); + if (interp) + interp->BreakpointHit(inMessage.m_Breakpoint); + } + + void OnMessage(QT3DSU32, QT3DSU64, const SInitialization &inMessage) + { + if (inMessage.m_Version != SInitialization::GetCurrentVersion()) { + QT3DS_ASSERT(false); + m_Invalid = true; + } + } + + void OnMessage(QT3DSU32 sid, QT3DSU64, const SDebugLog &inMessage) + { + IDebuggedInterpreter *interp = FindInterpreter(sid); + m_Listener.OnLog(interp, inMessage.m_Message); + } + +#define FORWARD_INTERPRETER_EVENT(evnType, interpFun) \ + void OnMessage(QT3DSI32 sid, QT3DSU64, const evnType &inMsg) \ + { \ + IDebuggedInterpreter *interp = FindInterpreter(sid); \ + if (interp) \ + interp->interpFun(inMsg); \ + } + + FORWARD_INTERPRETER_EVENT(SEventQueued, OnEventQueued); + FORWARD_INTERPRETER_EVENT(SBeginStep, OnBeginStep); + FORWARD_INTERPRETER_EVENT(SBeginMicrostep, OnBeginMicrostep); + FORWARD_INTERPRETER_EVENT(SMicrostepEvent, OnMicrostepEvent); + FORWARD_INTERPRETER_EVENT(SMicrostepData, OnMicrostepData); + FORWARD_INTERPRETER_EVENT(SEndMicrostep, OnEndMicrostep); + FORWARD_INTERPRETER_EVENT(SModifyTableValues, OnModifyTable); + + void OnMessage(QT3DSI32 sid, QT3DSU64, const SDisconnect &) + { + if (sid > 0) { + IDebuggedInterpreter *interp = FindInterpreter(sid); + if (interp) { + m_Listener.OnInterpreterDisconnected(*interp); + m_Interpreters.erase(sid); + } + } else { + ReleaseAllInterpreters(); + } + } + + void error(const char8_t *inPrefix, const char8_t *inSuffix) + { + m_LogStr.assign(nonNull(inPrefix)); + m_LogStr.append(nonNull(inSuffix)); + m_Listener.OnLog(NULL, m_LogStr); + } +}; +} + +IDebuggerMaster &IDebuggerMaster::CreateMaster(IDebugOutStream &outStream, + IDebuggerMasterListener &inListener) +{ + return *QT3DS_NEW(g_MallocAlloc, SDebuggerMaster)(outStream, inListener); +}
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditor.cpp b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditor.cpp new file mode 100644 index 00000000..6891fab0 --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditor.cpp @@ -0,0 +1,1880 @@ +/**************************************************************************** +** +** 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 "Qt3DSState.h" +#include "Qt3DSStateEditorEditorsImpl.h" +#include "Qt3DSStateExecutionContext.h" +#include "EASTL/sort.h" + +using namespace qt3ds::state; +using namespace qt3ds::state::editor; + +namespace qt3ds { +namespace state { + namespace editor { + +#pragma warning(disable : 4355) + + SEditorImpl::SEditorImpl(TFoundationPtr inFoundation, + NVScopedRefCounted<IStringTable> inStringTable) + : m_EditorFoundation(inFoundation) + , m_StringTable(inStringTable) + , m_AutoAllocator(m_EditorFoundation->getFoundation()) + , m_StateContext(IStateContext::Create(m_EditorFoundation->getFoundation())) + , m_Editors(m_EditorFoundation->getAllocator(), "SEditorImpl::m_Editors") + , mRefCount(0) + , m_TransactionManager( + QT3DS_NEW(inFoundation->getAllocator(), STransactionManagerImpl)(inFoundation)) + , m_Accessors(m_EditorFoundation->getAllocator(), "SEditorImpl::m_Accessors") + , m_CopyPasteListener(NULL) + { + } + + void SEditorImpl::addRef() { atomicIncrement(&mRefCount); } + + void SEditorImpl::release() + { + QT3DSI32 count = atomicDecrement(&mRefCount); + if (count <= 0) { + TFoundationPtr theFoundation(m_EditorFoundation); + NVDelete(theFoundation->getAllocator(), this); + } + } + + TObjPtr SEditorImpl::InsertEditor(void *inData, IEditorObject *inEditor) + { + if (inEditor) { + bool insertResult = m_Editors.insert(eastl::make_pair(inData, inEditor)).second; + QT3DS_ASSERT(insertResult); + (void)insertResult; + } + return inEditor; + } + + template <typename TStateType> + TObjPtr SEditorImpl::ToEditor(TStateType &inItem) + { + TStateEditorMap::iterator iter = m_Editors.find(&inItem); + if (iter != m_Editors.end()) + return iter->second; + + TObjPtr retval = SStateEditorMap<TStateType>::CreateEditor(inItem, *this, m_Accessors); + if (retval) + InsertEditor(&inItem, retval.mPtr); + return retval; + } + + template <typename TStateType> + TObjPtr SEditorImpl::ToEditor(TStateType *inItem) + { + if (inItem == NULL) + return TObjPtr(); + + return ToEditor(*inItem); + } + + TObjPtr SEditorImpl::ToEditor(SStateNode &inItem) + { + switch (inItem.m_Type) { + case StateNodeTypes::State: + return ToEditor(static_cast<SState &>(inItem)); + case StateNodeTypes::Parallel: + return ToEditor(static_cast<SParallel &>(inItem)); + case StateNodeTypes::Transition: + return ToEditor(static_cast<STransition &>(inItem)); + case StateNodeTypes::Final: + return ToEditor(static_cast<SFinal &>(inItem)); + case StateNodeTypes::SCXML: + return ToEditor(static_cast<SSCXML &>(inItem)); + case StateNodeTypes::History: + return ToEditor(static_cast<SHistory &>(inItem)); + default: + QT3DS_ASSERT(false); + return TObjPtr(); + } + } + + TObjPtr SEditorImpl::ExecutableContentToEditor(SExecutableContent &inItem) + { + TStateEditorMap::iterator iter = m_Editors.find(&inItem); + if (iter != m_Editors.end()) + return iter->second; + + switch (inItem.m_Type) { + case ExecutableContentTypes::Send: + return ToEditor(static_cast<SSend &>(inItem)); + case ExecutableContentTypes::Raise: + return ToEditor(static_cast<SRaise &>(inItem)); + case ExecutableContentTypes::Log: + return ToEditor(static_cast<SLog &>(inItem)); + case ExecutableContentTypes::Assign: + return ToEditor(static_cast<SAssign &>(inItem)); + case ExecutableContentTypes::If: + return ToEditor(static_cast<SIf &>(inItem)); + case ExecutableContentTypes::ElseIf: + return ToEditor(static_cast<SElseIf &>(inItem)); + case ExecutableContentTypes::Else: + return ToEditor(static_cast<SElse &>(inItem)); + case ExecutableContentTypes::Script: + return ToEditor(static_cast<SScript &>(inItem)); + case ExecutableContentTypes::Cancel: + return ToEditor(static_cast<SCancel &>(inItem)); + default: + QT3DS_ASSERT(false); + return TObjPtr(); + } + } + + template <typename TStateType> + TStateType *SEditorImpl::FromEditor(TObjPtr inPtr) + { + if (inPtr.mPtr == NULL) + return NULL; + const char8_t *typeName = inPtr->TypeName(); + typedef typename SStateEditorMap<TStateType>::TEditorType TProspectiveType; + if (AreEqual(typeName, TProspectiveType::GetTypeStr())) { + return reinterpret_cast<TStateType *>( + &static_cast<TProspectiveType *>(inPtr.mPtr)->m_Data); + } + return NULL; + } + + SStateNode *SEditorImpl::StateNodeFromEditor(TObjPtr inPtr) + { + if (inPtr.mPtr == NULL) + return NULL; + const char8_t *typeName = inPtr->TypeName(); + if (AreEqual(typeName, SSCXMLEditor::GetTypeStr())) + return FromEditor<SSCXML>(inPtr); + if (AreEqual(typeName, SStateEditor::GetTypeStr())) + return FromEditor<SState>(inPtr); + if (AreEqual(typeName, STransitionEditor::GetTypeStr())) + return FromEditor<STransition>(inPtr); + if (AreEqual(typeName, SParallelEditor::GetTypeStr())) + return FromEditor<SParallel>(inPtr); + if (AreEqual(typeName, SFinalEditor::GetTypeStr())) + return FromEditor<SFinal>(inPtr); + if (AreEqual(typeName, SHistoryEditor::GetTypeStr())) + return FromEditor<SHistory>(inPtr); + return NULL; + } + + SExecutableContent *SEditorImpl::ExecutableContentFromEditor(TObjPtr inPtr) + { + if (inPtr.mPtr == NULL) + return NULL; + const char8_t *typeName = inPtr->TypeName(); + if (AreEqual(typeName, SSendEditor::GetTypeStr())) { + return FromEditor<SSend>(inPtr); + } + if (AreEqual(typeName, SRaiseEditor::GetTypeStr())) { + return FromEditor<SRaise>(inPtr); + } + if (AreEqual(typeName, SLogEditor::GetTypeStr())) { + return FromEditor<SLog>(inPtr); + } + if (AreEqual(typeName, SAssignEditor::GetTypeStr())) { + return FromEditor<SAssign>(inPtr); + } + if (AreEqual(typeName, SIfEditor::GetTypeStr())) { + return FromEditor<SIf>(inPtr); + } + if (AreEqual(typeName, SElseIfEditor::GetTypeStr())) { + return FromEditor<SElseIf>(inPtr); + } + if (AreEqual(typeName, SElseEditor::GetTypeStr())) { + return FromEditor<SElse>(inPtr); + } + if (AreEqual(typeName, SScriptEditor::GetTypeStr())) { + return FromEditor<SScript>(inPtr); + } + if (AreEqual(typeName, SCancelEditor::GetTypeStr())) { + return FromEditor<SCancel>(inPtr); + } + + return NULL; + } + + void SEditorImpl::GenerateUniqueId(SStateNode &inNode, const char8_t *inStem) + { + QT3DS_ASSERT(inNode.m_Id.IsValid() == false); + CXMLIO::GenerateUniqueId(inNode, inStem, *m_StateContext, *m_StringTable); + } + + void SEditorImpl::GenerateUniqueId(SSend &inNode, const char8_t *inStem) + { + CXMLIO::GenerateUniqueId(inNode, inStem, *m_StateContext, *m_StringTable); + } + + TObjPtr SEditorImpl::GetRoot() { return ToEditor(m_StateContext->GetRoot()); } + + template <typename TStateType> + eastl::pair<TStateType *, TObjPtr> SEditorImpl::CreateEditorAndObject() + { + typedef typename SStateEditorMap<TStateType>::TEditorType TEditorType; + TObjPtr newEditor = SStateEditorMap<TStateType>::CreateEditor(*this, m_Accessors); + TStateType *theState = &static_cast<TEditorType *>(newEditor.mPtr)->m_Data; + eastl::pair<TStateType *, TObjPtr> retval = eastl::make_pair(theState, newEditor); + InsertEditor(retval.first, retval.second.mPtr); + return retval; + } + + TObjPtr SEditorImpl::DoCreate(const char8_t *inTypeName, const char8_t *inId) + { + if (AreEqual(inTypeName, SSCXMLEditor::GetTypeStr())) { + QT3DS_ASSERT(m_StateContext->GetRoot() == NULL); + eastl::pair<SSCXML *, TObjPtr> theNewEditor = CreateEditorAndObject<SSCXML>(); + GenerateUniqueId(*theNewEditor.first, inId == NULL ? "scxml" : inId); + m_StateContext->SetRoot(*theNewEditor.first); + return theNewEditor.second; + } else if (AreEqual(inTypeName, SStateEditor::GetTypeStr())) { + eastl::pair<SState *, TObjPtr> theNewEditor = CreateEditorAndObject<SState>(); + GenerateUniqueId(*theNewEditor.first, inId == NULL ? "state" : inId); + return theNewEditor.second; + } else if (AreEqual(inTypeName, SParallelEditor::GetTypeStr())) { + eastl::pair<SParallel *, TObjPtr> theNewEditor = CreateEditorAndObject<SParallel>(); + GenerateUniqueId(*theNewEditor.first, inId == NULL ? "parallel" : inId); + return theNewEditor.second; + } else if (AreEqual(inTypeName, SFinalEditor::GetTypeStr())) { + eastl::pair<SFinal *, TObjPtr> theNewEditor = CreateEditorAndObject<SFinal>(); + GenerateUniqueId(*theNewEditor.first, inId == NULL ? "final" : inId); + return theNewEditor.second; + } else if (AreEqual(inTypeName, SHistoryEditor::GetTypeStr())) { + eastl::pair<SHistory *, TObjPtr> theNewEditor = CreateEditorAndObject<SHistory>(); + GenerateUniqueId(*theNewEditor.first, inId == NULL ? "history" : inId); + return theNewEditor.second; + } else if (AreEqual(inTypeName, STransitionEditor::GetTypeStr())) { + eastl::pair<STransition *, TObjPtr> theNewEditor = + CreateEditorAndObject<STransition>(); + GenerateUniqueId(*theNewEditor.first, inId == NULL ? "transition" : inId); + return theNewEditor.second; + } else if (AreEqual(inTypeName, SDataModelEditor::GetTypeStr())) { + eastl::pair<SDataModel *, TObjPtr> theNewEditor = + CreateEditorAndObject<SDataModel>(); + return theNewEditor.second; + } else if (AreEqual(inTypeName, SDataEditor::GetTypeStr())) { + eastl::pair<SData *, TObjPtr> theNewEditor = CreateEditorAndObject<SData>(); + return theNewEditor.second; + } + + QT3DS_ASSERT(false); + return TObjPtr(); + } + + TObjPtr SEditorImpl::Create(const char8_t *inTypeName, const char8_t *inId) + { + TObjPtr retval = DoCreate(inTypeName, inId); + m_TransactionManager->OnObjectCreated(retval); + return retval; + } + + TObjPtr SEditorImpl::GetOrCreate(SSCXML &inData) { return ToEditor(inData); } + TObjPtr SEditorImpl::GetOrCreate(SState &inData) { return ToEditor(inData); } + TObjPtr SEditorImpl::GetOrCreate(STransition &inData) { return ToEditor(inData); } + TObjPtr SEditorImpl::GetOrCreate(SParallel &inData) { return ToEditor(inData); } + TObjPtr SEditorImpl::GetOrCreate(SFinal &inData) { return ToEditor(inData); } + TObjPtr SEditorImpl::GetOrCreate(SHistory &inData) { return ToEditor(inData); } + TObjPtr SEditorImpl::GetOrCreate(SDataModel &inData) { return ToEditor(inData); } + TObjPtr SEditorImpl::GetOrCreate(SData &inData) { return ToEditor(inData); } + + TObjPtr SEditorImpl::GetObjectById(const char8_t *inId) + { + if (isTrivial(inId)) + return TObjPtr(); + SStateNode *theNode = m_StateContext->FindStateNode(RegisterStr(inId)); + if (theNode) + return ToEditor(*theNode); + return TObjPtr(); + } + + TObjPtr SEditorImpl::GetEditor(void *inGraphData) + { + TStateEditorMap::iterator iter = m_Editors.find(inGraphData); + if (iter != m_Editors.end()) + return iter->second; + return TObjPtr(); + } + + TSignalConnectionPtr SEditorImpl::AddChangeListener(IEditorChangeListener &inListener) + { + return m_TransactionManager->AddChangeListener(inListener); + } + + TTransactionPtr SEditorImpl::BeginTransaction(const TEditorStr &inName) + { + return m_TransactionManager->BeginTransaction(inName); + } + + TTransactionPtr SEditorImpl::GetOpenTransaction() + { + return m_TransactionManager->GetOpenTransaction(); + } + + void SEditorImpl::RollbackTransaction() { m_TransactionManager->RollbackTransaction(); } + + void SEditorImpl::EndTransaction() { m_TransactionManager->EndTransaction(); } + + TEditorStr SEditorImpl::Copy(TObjList inObjects, const QT3DSVec2 &inMousePos) + { + eastl::vector<SStateNode *> theNodeList; + for (size_t idx = 0, end = inObjects.size(); idx < end; ++idx) { + SStateNode *theNode = StateNodeFromEditor(inObjects[idx]); + if (theNode && theNode->m_Type != StateNodeTypes::Transition + && theNode->m_Type != StateNodeTypes::SCXML) { + theNodeList.push_back(theNode); + } + } + eastl::vector<SNamespacePair> theNamespaces; + NVScopedRefCounted<IDOMFactory> theFactory( + IDOMFactory::CreateDOMFactory(m_EditorFoundation->getAllocator(), m_StringTable)); + NVScopedRefCounted<IDOMWriter> theWriter( + IDOMWriter::CreateDOMWriter(m_EditorFoundation->getAllocator(), "scxml_fragment", + m_StringTable) + .first); + + eastl::vector<SStateNode *> rootNodes = CXMLIO::SaveSCXMLFragment( + *m_StateContext, *m_EditorFoundation->m_Foundation, *m_StringTable, *theWriter, + toDataRef(theNodeList.data(), theNodeList.size()), *this, inMousePos, + theNamespaces); + + if (m_CopyPasteListener) + m_CopyPasteListener->OnCopy(this, rootNodes, *theWriter, theNamespaces); + + SEditorImplStrIOStream theStream; + CDOMSerializer::WriteXMLHeader(theStream); + CDOMSerializer::Write(m_EditorFoundation->getAllocator(), *theWriter->GetTopElement(), + theStream, *m_StringTable, + toDataRef(theNamespaces.data(), theNamespaces.size()), false); + return theStream.m_Str; + } + + bool SEditorImpl::CanPaste(TObjPtr inTarget) + { + SStateNode *theNode = StateNodeFromEditor(inTarget); + if (theNode) { + return theNode->m_Type != StateNodeTypes::Transition + && theNode->m_Type != StateNodeTypes::History + && theNode->m_Type != StateNodeTypes::Final; + } + + return false; + } + + void SEditorImpl::AddNewPasteObjectToTransaction(SStateNode &inNode) + { + TObjPtr editorPtr = ToEditor(inNode); + if (editorPtr) { + m_TransactionManager->m_Transaction->m_Changes.push_back(new SChange(*editorPtr)); + m_TransactionManager->OnObjectCreated(editorPtr); + Option<SValue> children = editorPtr->GetPropertyValue("children"); + if (children.hasValue()) { + TObjList childList = children->getData<TObjList>(); + for (size_t idx = 0, end = childList.size(); idx < end; ++idx) { + SStateNode *childNode = StateNodeFromEditor(childList[idx]); + if (childNode) + AddNewPasteObjectToTransaction(*childNode); + } + } + } + } + + void RecurseAndCheckForValidHistoryAfterPaste(TObjPtr inNode, SEditorImpl &inEditor) + { + if (AreEqual(inNode->TypeName(), "history")) { + inEditor.CheckAndSetValidHistoryDefault(inNode); + } else { + Option<SValue> childrenOpt = inNode->GetPropertyValue("children"); + if (childrenOpt.hasValue() == false) { + return; + } + TObjList children = childrenOpt->getData<TObjList>(); + for (size_t childIdx = 0, childEnd = children.size(); childIdx < childEnd; + ++childIdx) { + RecurseAndCheckForValidHistoryAfterPaste(children[childIdx], inEditor); + } + } + } + + void SEditorImpl::Paste(const TEditorStr &inCopiedObjects, TObjPtr inTarget, + const QT3DSVec2 &inMousePos) + { + Option<SValue> childrenOpt = inTarget->GetPropertyValue("children"); + if (childrenOpt.hasValue() == false) { + QT3DS_ASSERT(false); + return; + } + + TObjList children = childrenOpt->getData<TObjList>(); + + SEditorImplStrInStream inStream(inCopiedObjects); + NVScopedRefCounted<IDOMFactory> theFactory = + IDOMFactory::CreateDOMFactory(m_EditorFoundation->getAllocator(), m_StringTable); + eastl::pair<SNamespacePairNode *, SDOMElement *> readResult = + CDOMSerializer::Read(*theFactory, inStream); + SDOMElement *elem = readResult.second; + if (elem == NULL) { + QT3DS_ASSERT(false); + return; + } + NVScopedRefCounted<IDOMReader> theReader = IDOMReader::CreateDOMReader( + m_EditorFoundation->getAllocator(), *elem, m_StringTable, *theFactory); + eastl::pair<eastl::vector<SStateNode *>, CXMLIO::TIdRemapMap> theRootsPair; + { + IDOMReader::Scope __readerScope(*theReader); + theRootsPair = + CXMLIO::LoadSCXMLFragment(m_AutoAllocator, *m_EditorFoundation->m_Foundation, + *theReader, *m_StringTable, *m_StateContext, *this); + } + eastl::vector<SStateNode *> &theObjects(theRootsPair.first); + if (m_CopyPasteListener) + m_CopyPasteListener->OnPaste(this, *theReader, theRootsPair.second); + // Merge any namespaces into the context's namespace list. + if (m_StateContext->GetDOMFactory()) { + for (SNamespacePairNode *fragNode = readResult.first; fragNode; + fragNode = fragNode->m_NextNode) { + bool found = false; + for (SNamespacePairNode *ctxNode = m_StateContext->GetFirstNSNode(); + ctxNode && !found; ctxNode = ctxNode->m_NextNode) { + if (ctxNode->m_Namespace == fragNode->m_Namespace) + found = true; + } + if (!found) { + SNamespacePairNode *newNode = + m_StateContext->GetDOMFactory()->NextNSPairNode( + fragNode->m_Namespace, fragNode->m_Abbreviation); + newNode->m_NextNode = m_StateContext->GetFirstNSNode(); + m_StateContext->SetFirstNSNode(*newNode); + } + } + } + QT3DSVec2 globalPos(0, 0); + if (inTarget) { + for (TObjPtr posObj = inTarget; posObj; posObj = posObj->Parent()) { + Option<SValue> theData = posObj->GetPropertyValue("position"); + if (theData.hasValue()) + globalPos += theData->getData<QT3DSVec2>(); + } + } + QT3DSVec2 theMousePos = inMousePos - globalPos; + for (size_t idx = 0, end = theObjects.size(); idx < end; ++idx) { + SStateNode *currentObject = theObjects[idx]; + QT3DSVec2 objPos(0, 0); + if (currentObject->m_StateNodeFlags.HasPosition()) + objPos = currentObject->m_Position; + currentObject->SetPosition(objPos + theMousePos); + if (m_TransactionManager->m_Transaction) + AddNewPasteObjectToTransaction(*currentObject); + TObjPtr editorPtr = ToEditor(*currentObject); + if (editorPtr.mPtr) + children.push_back(editorPtr); + } + // This sets up the valid parents. Without that, checking if a history node is valid + // is meaningless. + inTarget->SetPropertyValue("children", children); + for (size_t idx = 0, end = children.size(); idx < end; ++idx) { + RecurseAndCheckForValidHistoryAfterPaste(children[idx], *this); + } + } + + bool SEditorImpl::IsDerivedFrom(SStateNode &child, SStateNode &parent) + { + if (&child == &parent) + return true; + if (child.m_Parent) + return IsDerivedFrom(*child.m_Parent, parent); + return false; + } + + // This method should always return a value because the scxml root item + // is the parent of everyone else. + SStateNode &SEditorImpl::GetLeastCommonAncestor(SStateNode &lhs, SStateNode &rhs) + { + // If lhs is derived from rhs, return rhs + if (IsDerivedFrom(lhs, rhs)) + return rhs; + // vice versa + else if (IsDerivedFrom(rhs, lhs)) + return lhs; + + // Else wander up the tree till we find a common ancestor. + QT3DS_ASSERT(lhs.m_Parent != NULL); + if (lhs.m_Parent != NULL) + return GetLeastCommonAncestor(*lhs.m_Parent, rhs); + + return lhs; + } + + TObjPtr SEditorImpl::GetLeastCommonAncestor(NVConstDataRef<TObjPtr> inObjects) + { + SStateNode *lastAncestor = NULL; + if (inObjects.size() == 0) + return TObjPtr(); + + for (QT3DSU32 idx = 0, end = inObjects.size(); idx < end; ++idx) { + if (lastAncestor == NULL) { + lastAncestor = StateNodeFromEditor(inObjects[idx]); + } else { + SStateNode *nextNode = StateNodeFromEditor(inObjects[idx]); + if (nextNode != NULL) { + lastAncestor = &GetLeastCommonAncestor(*lastAncestor, *nextNode); + } + } + } + + if (lastAncestor) + return ToEditor(*lastAncestor); + else + return TObjPtr(); + } + + void GetCancelableSendIds(TExecutableContentList &inList, TObjList &outList, + SEditorImpl &inEditor); + + void RecursiveGetCancelableSendIds(SExecutableContent &inNode, TObjList &outList, + SEditorImpl &inEditor) + { + if (inNode.m_Type == ExecutableContentTypes::Send) { + SSend &theSend = static_cast<SSend &>(inNode); + if (IExecutionContext::ParseTimeStrToMilliseconds(theSend.m_Delay) + || !isTrivial(theSend.m_DelayExpr)) { + outList.push_back(inEditor.ExecutableContentToEditor(theSend)); + } + } + GetCancelableSendIds(inNode.m_Children, outList, inEditor); + } + + void GetCancelableSendIds(TExecutableContentList &inList, TObjList &outList, + SEditorImpl &inEditor) + { + for (TExecutableContentList::iterator iter = inList.begin(), end = inList.end(); + iter != end; ++iter) + RecursiveGetCancelableSendIds(*iter, outList, inEditor); + } + + void RecursiveGetCancelableSendIds(SStateNode &inNode, TObjList &outList, + SEditorImpl &inEditor) + { + if (inNode.m_Type == StateNodeTypes::Transition) { + STransition &transition = static_cast<STransition &>(inNode); + GetCancelableSendIds(transition.m_ExecutableContent, outList, inEditor); + } else { + TOnEntryList *entryList = inNode.GetOnEntryList(); + TOnExitList *exitList = inNode.GetOnExitList(); + if (entryList) { + for (TOnEntryList::iterator iter = entryList->begin(), end = entryList->end(); + iter != end; ++iter) + GetCancelableSendIds(iter->m_ExecutableContent, outList, inEditor); + } + if (exitList) { + for (TOnExitList::iterator iter = exitList->begin(), end = exitList->end(); + iter != end; ++iter) + GetCancelableSendIds(iter->m_ExecutableContent, outList, inEditor); + } + } + TStateNodeList *childList = inNode.GetChildren(); + if (childList) { + for (TStateNodeList::iterator childIter = childList->begin(), + childEnd = childList->end(); + childIter != childEnd; ++childIter) + RecursiveGetCancelableSendIds(*childIter, outList, inEditor); + } + } + + // TODO - think about a fast and easy way to cache things (and keep the cache correct). + TObjList SEditorImpl::GetCancelableSendIds() + { + TObjList retval; + if (m_StateContext->GetRoot()) + RecursiveGetCancelableSendIds(*m_StateContext->GetRoot(), retval, *this); + return retval; + } + + static const char8_t *whitespace = "\n\r\t "; + + void StripIfLastChar(TEditorStr &str, char data) + { + if (str.size() && str.back() == data) + str.resize(str.size() - 1); + } + + void AddUnique(TEditorStrList &list, const TEditorStr &str) + { + if (eastl::find(list.begin(), list.end(), str) == list.end()) + list.push_back(str); + } + + void RecursiveScanExecutableContentForEvents(TExecutableContentList &contentList, + TEditorStrList &ioStrList) + { + for (TExecutableContentList::iterator iter = contentList.begin(), + end = contentList.end(); + iter != end; ++iter) { + if (iter->m_Type == ExecutableContentTypes::Send) { + SSend &theSend = static_cast<SSend &>(*iter); + if (!isTrivial(theSend.m_Event)) { + AddUnique(ioStrList, theSend.m_Event.c_str()); + } + } else if (iter->m_Type == ExecutableContentTypes::Raise) { + SRaise &theRaise = static_cast<SRaise &>(*iter); + if (!isTrivial(theRaise.m_Event)) { + AddUnique(ioStrList, theRaise.m_Event.c_str()); + } + } + RecursiveScanExecutableContentForEvents(iter->m_Children, ioStrList); + } + } + + template <typename TListType> + void ScanItemListForEvents(TListType *itemList, TEditorStrList &ioEvents) + { + if (itemList) { + typedef typename TListType::iterator iterator; + for (iterator iter = itemList->begin(), end = itemList->end(); iter != end; + ++iter) { + RecursiveScanExecutableContentForEvents(iter->m_ExecutableContent, ioEvents); + } + } + } + + void RecursiveGetStateMachineEvents(SStateNode &inNode, TEditorStrList &ioList) + { + if (inNode.m_Type == StateNodeTypes::Transition) { + STransition &theTransition = static_cast<STransition &>(inNode); + TEditorStr transStr(theTransition.m_Event); + for (size_t pos = transStr.find_first_not_of(whitespace); pos != TEditorStr::npos; + pos = transStr.find_first_not_of(whitespace, pos)) { + size_t end = transStr.find_first_of(whitespace, pos); + if (end == TEditorStr::npos) + end = transStr.size(); + TEditorStr subStr = transStr.substr(pos, end - pos); + pos = end; + StripIfLastChar(subStr, '*'); + StripIfLastChar(subStr, '.'); + AddUnique(ioList, subStr); + } + RecursiveScanExecutableContentForEvents(theTransition.m_ExecutableContent, ioList); + } else { + ScanItemListForEvents(inNode.GetOnEntryList(), ioList); + ScanItemListForEvents(inNode.GetOnExitList(), ioList); + } + TStateNodeList *children = inNode.GetChildren(); + if (children) { + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) + RecursiveGetStateMachineEvents(*iter, ioList); + } + } + + TEditorStrList SEditorImpl::GetStateMachineEvents() + { + TEditorStrList retval; + if (m_StateContext->GetRoot()) + RecursiveGetStateMachineEvents(*m_StateContext->GetRoot(), retval); + eastl::sort(retval.begin(), retval.end()); + return retval; + } + + eastl::pair<SExecutableContent *, TObjPtr> + SEditorImpl::CreateExecutableContent(const char8_t *inTypeName) + { + SExecutableContent *newExecutable = NULL; + TObjPtr newObj; + if (AreEqual(inTypeName, SSendEditor::GetTypeStr())) { + eastl::pair<SSend *, TObjPtr> theNewEditor = CreateEditorAndObject<SSend>(); + GenerateUniqueId(*theNewEditor.first, "send"); + newExecutable = theNewEditor.first; + newObj = theNewEditor.second; + } else if (AreEqual(inTypeName, SScriptEditor::GetTypeStr())) { + eastl::pair<SScript *, TObjPtr> theNewEditor = CreateEditorAndObject<SScript>(); + newExecutable = theNewEditor.first; + newObj = theNewEditor.second; + } else if (AreEqual(inTypeName, SCancelEditor::GetTypeStr())) { + eastl::pair<SCancel *, TObjPtr> theNewEditor = CreateEditorAndObject<SCancel>(); + newExecutable = theNewEditor.first; + newObj = theNewEditor.second; + } else { + QT3DS_ASSERT(false); + } + return eastl::make_pair(newExecutable, newObj); + } + + struct SExecListAddChange : public IChange + { + TExecutableContentList &parentList; + SExecutableContent *prevSibling; + SExecutableContent &item; + TObjPtr editorObj; + QT3DSI32 mRefCount; + bool m_AddOnDo; + SExecListAddChange(TExecutableContentList &plist, SExecutableContent *prev, + SExecutableContent &_item, TObjPtr objPtr, bool addOnDo) + : parentList(plist) + , prevSibling(prev) + , item(_item) + , editorObj(objPtr) + , mRefCount(0) + , m_AddOnDo(addOnDo) + { + } + + void Add() + { + if (prevSibling) + parentList.insert_after(*prevSibling, item); + else + parentList.push_front(item); + } + void Remove() { parentList.remove(item); } + + virtual void Do() + { + if (m_AddOnDo) + Add(); + else + Remove(); + } + + virtual void Undo() + { + if (m_AddOnDo) + Remove(); + else + Add(); + } + + virtual TObjPtr GetEditor() { return editorObj; } + + virtual void addRef() { ++mRefCount; } + + virtual void release() + { + --mRefCount; + if (mRefCount <= 0) + delete this; + } + }; + + void SEditorImpl::ReplaceExecutableContent(SExecutableContent &oldContent, + SExecutableContent &newContent) + { + SStateNode *stateNodeParent = oldContent.m_StateNodeParent; + SExecutableContent *execContentParent = oldContent.m_Parent; + SExecutableContentParentInfo parentInfo(oldContent, *this); + SExecutableContent *prevSibling = oldContent.m_PreviousSibling; + + // Can't use remove object from graph here because it is too aggressive. + NVScopedRefCounted<SExecListAddChange> oldChange = new SExecListAddChange( + *parentInfo.parentList, prevSibling, oldContent, parentInfo.editorObj, false); + oldChange->Do(); + newContent.m_StateNodeParent = stateNodeParent; + newContent.m_Parent = execContentParent; + NVScopedRefCounted<SExecListAddChange> newChange = new SExecListAddChange( + *parentInfo.parentList, prevSibling, newContent, parentInfo.editorObj, true); + newChange->Do(); + if (GetOpenTransactionImpl()) { + GetOpenTransactionImpl()->m_Changes.push_back(oldChange.mPtr); + GetOpenTransactionImpl()->m_Changes.push_back(newChange.mPtr); + } + } + + TObjPtr SEditorImpl::ChangeExecutableContentType(TObjPtr inContent, const char8_t *newType) + { + SExecutableContent *theContent = ExecutableContentFromEditor(inContent); + if (!theContent) { + QT3DS_ASSERT(false); + return TObjPtr(); + } + SExecutableContentParentInfo parentInfo(*theContent, *this); + // find the index of the item in the parent list. + if (parentInfo.parentList == NULL) { + QT3DS_ASSERT(false); + return TObjPtr(); + } + + eastl::pair<SExecutableContent *, TObjPtr> theNewContent = + CreateExecutableContent(newType); + if (!theNewContent.first) { + QT3DS_ASSERT(false); + return TObjPtr(); + } + ReplaceExecutableContent(*theContent, *theNewContent.first); + return theNewContent.second; + } + + TEditorStr SEditorImpl::ToXML(TObjPtr inContent) + { + SExecutableContent *theContent = ExecutableContentFromEditor(inContent); + if (!theContent) { + QT3DS_ASSERT(false); + return TEditorStr(); + } + NVScopedRefCounted<IDOMFactory> theFactory( + IDOMFactory::CreateDOMFactory(m_EditorFoundation->getAllocator(), m_StringTable)); + NVScopedRefCounted<IDOMWriter> theWriter( + IDOMWriter::CreateDOMWriter(m_EditorFoundation->getAllocator(), "innerXML", + m_StringTable, 0) + .first); + CXMLIO::ToEditableXml(*m_StateContext, m_EditorFoundation->getFoundation(), + *m_StringTable, *theWriter, *theContent, *this); + SDOMElement *theTopElem = theWriter->GetTopElement(); + SDOMElement *xmlElem = &theTopElem->m_Children.front(); + SEditorImplStrIOStream theStream; + if (xmlElem) { + + eastl::vector<SNamespacePair> theNamespaces; + for (SNamespacePairNode *theNode = m_StateContext->GetFirstNSNode(); theNode; + theNode = theNode->m_NextNode) + theNamespaces.push_back(*theNode); + if (theNamespaces.empty()) { + theNamespaces.push_back(SNamespacePair( + m_StringTable->RegisterStr("http://www.w3.org/2005/07/scxml"))); + theNamespaces.push_back( + SNamespacePair(m_StringTable->RegisterStr("http://qt.io/qt3dstudio/uicstate"), + m_StringTable->RegisterStr("Qt3DS"))); + } + CDOMSerializer::Write( + m_EditorFoundation->getAllocator(), *xmlElem, theStream, *m_StringTable, + toDataRef(theNamespaces.data(), theNamespaces.size()), false, 0, false); + } + return theStream.m_Str; + } + + struct SStrXMLErrorHandler : public CXmlErrorHandler + { + TEditorStr m_Error; + virtual void OnXmlError(TXMLCharPtr errorName, int line, int /*column*/) + { + char buf[256]; + sprintf(buf, "Error on line %d: ", line); + m_Error.assign(buf); + m_Error.append(nonNull(errorName)); + } + }; + + eastl::pair<TEditorStr, TObjPtr> SEditorImpl::FromXML(TObjPtr inContent, + const TEditorStr &ioEditedXML) + { + TEditorStr wrappedXML("<snippet "); + eastl::vector<SNamespacePair> theNamespaces; + for (SNamespacePairNode *theNode = m_StateContext->GetFirstNSNode(); theNode; + theNode = theNode->m_NextNode) + theNamespaces.push_back(*theNode); + if (theNamespaces.empty()) { + theNamespaces.push_back( + SNamespacePair(m_StringTable->RegisterStr("http://www.w3.org/2005/07/scxml"))); + theNamespaces.push_back( + SNamespacePair(m_StringTable->RegisterStr("http://qt.io/qt3dstudio/uicstate"), + m_StringTable->RegisterStr("Qt3DS"))); + } + for (size_t idx = 0, end = theNamespaces.size(); idx < end; ++idx) { + SNamespacePair &theNode(theNamespaces[idx]); + wrappedXML.append(" xmlns"); + if (theNode.m_Abbreviation.IsValid()) { + wrappedXML.append(":"); + wrappedXML.append(theNode.m_Abbreviation.c_str()); + } + wrappedXML.append("=\""); + wrappedXML.append(theNode.m_Namespace.c_str()); + wrappedXML.append("\""); + } + wrappedXML.append(">"); + wrappedXML.append(ioEditedXML); + wrappedXML.append("</snippet>"); + SEditorImplStrInStream theStream(wrappedXML); + + NVScopedRefCounted<IDOMFactory> theFactory( + IDOMFactory::CreateDOMFactory(m_EditorFoundation->getAllocator(), m_StringTable)); + SStrXMLErrorHandler errorHandler; + SDOMElement *theElem = + CDOMSerializer::Read(*theFactory, theStream, &errorHandler).second; + TObjPtr retval; + if (theElem) { + NVScopedRefCounted<IDOMReader> theReader(IDOMReader::CreateDOMReader( + m_EditorFoundation->getAllocator(), theElem->m_Children.front(), m_StringTable, + theFactory)); + SExecutableContent *oldContent = ExecutableContentFromEditor(inContent); + SExecutableContent *theContent = + CXMLIO::FromEditableXML(*theReader, m_EditorFoundation->getFoundation(), + *m_StateContext, *m_StringTable, m_AutoAllocator, *this, + oldContent->m_StateNodeParent, oldContent->m_Parent); + if (theContent) { + ReplaceExecutableContent(*oldContent, *theContent); + retval = ExecutableContentToEditor(*theContent); + } else { + errorHandler.m_Error.assign("The \""); + errorHandler.m_Error.append(theReader->GetElementName().c_str()); + errorHandler.m_Error.append("\" action is not recognized. It must be one of:"); + eastl::vector<eastl::string> contentNames = + CXMLIO::GetSupportedExecutableContentNames(); + for (size_t idx = 0, end = contentNames.size(); idx < end; ++idx) { + if (idx != 0) + errorHandler.m_Error.append(","); + + errorHandler.m_Error.append(" \""); + errorHandler.m_Error.append(contentNames[idx].c_str()); + errorHandler.m_Error.append("\""); + } + errorHandler.m_Error.append("."); + } + } + + return eastl::make_pair(errorHandler.m_Error, retval); + } + + void SEditorImpl::ReleaseEditor(void *inData) + { + TStateEditorMap::iterator iter = m_Editors.find(inData); + if (iter != m_Editors.end()) + m_Editors.erase(iter); + } + + bool SEditorImpl::Save(const char8_t *inFileName) + { + CFileSeekableIOStream theFile(inFileName, FileWriteFlags()); + if (theFile.IsOpen() == false) { + QT3DS_ASSERT(false); + return false; + } + + Save(theFile); + return true; + } + + void SEditorImpl::Save(IOutStream &inStream) { m_StateContext->Save(inStream, this); } + + bool SEditorImpl::Load(IInStream &inStream, const char8_t *inFilename) + { + bool theRetval = false; + m_StateContext = IStateContext::Load(m_AutoAllocator, *m_EditorFoundation->m_Foundation, + inStream, inFilename, m_StringTable.mPtr, this); + if (m_StateContext.mPtr == 0) + m_StateContext = IStateContext::Create(*m_EditorFoundation->m_Foundation); + else + theRetval = true; + return theRetval; + } + + STransaction *SEditorImpl::GetOpenTransactionImpl() + { + return m_TransactionManager->m_Transaction.mPtr; + } + + void SEditorImpl::SetIdProperty(SStateNode &inNode, const SValue &inData, + CRegisteredString & /*inTarget*/) + { + IStateContext &theContext = *m_StateContext; + theContext.EraseId(inNode.m_Id); + TEditorStr potentialId = inData.getData<TEditorStr>(); + if (potentialId.size() == 0) { + switch (inNode.m_Type) { + case StateNodeTypes::State: + potentialId = "state"; + break; + case StateNodeTypes::Parallel: + potentialId = "parallel"; + break; + case StateNodeTypes::Transition: + potentialId = "transition"; + break; + case StateNodeTypes::Final: + potentialId = "final"; + break; + case StateNodeTypes::History: + potentialId = "history"; + break; + case StateNodeTypes::SCXML: + potentialId = "scxml"; + break; + default: + QT3DS_ASSERT(false); + potentialId = "id"; + break; + } + } + CRegisteredString oldId = inNode.m_Id; + inNode.m_Id = CRegisteredString(); + GenerateUniqueId(inNode, potentialId.c_str()); + // This is a bad hack to get around: + // 1. user changes id calling this function + if (m_CopyPasteListener) { + m_CopyPasteListener->OnIDChange(this, inNode, oldId); + } + } + + void SEditorImpl::SetIdProperty(SSend &inNode, const SValue &inData, + CRegisteredString & /*inTarget*/) + { + IStateContext &theContext = *m_StateContext; + theContext.EraseId(inNode.m_Id); + TEditorStr potentialId = inData.getData<TEditorStr>(); + if (potentialId.size() == 0) + potentialId = "send"; + + inNode.m_Id = CRegisteredString(); + GenerateUniqueId(inNode, potentialId.c_str()); + } + + void SEditorImpl::Set(const Option<SValue> &inData, NVConstDataRef<SStateNode *> &inTarget) + { + if (inTarget.mData != NULL) { + m_AutoAllocator.deallocate((void *)inTarget.mData); + inTarget.mData = NULL; + inTarget.mSize = 0; + } + if (inData.hasValue()) { + TObjList inList(inData->getData<TObjList>()); + eastl::vector<SStateNode *> validObjects; + for (size_t idx = 0, end = inList.size(); idx < end; ++idx) { + TObjPtr theObj = inList[idx]; + SStateNode *theNode = StateNodeFromEditor(theObj); + if (theNode) { + validObjects.push_back(theNode); + } else { + QT3DS_ASSERT(false); + } + } + + if (validObjects.empty() == false) { + NVConstDataRef<SStateNode *> &outList(inTarget); + size_t byteCount = validObjects.size() * sizeof(SStateNode *); + outList.mData = (SStateNode * const *)m_AutoAllocator.allocate( + byteCount, "graph ref list data", __FILE__, __LINE__); + memCopy((void *)outList.mData, validObjects.data(), byteCount); + outList.mSize = validObjects.size(); + } + } + } + + SValue SEditorImpl::Get(NVConstDataRef<SStateNode *> &inTarget) + { + TObjList retval; + NVConstDataRef<SStateNode *> inRefList(inTarget); + for (QT3DSU32 idx = 0, end = inRefList.size(); idx < end; ++idx) { + TObjPtr obj = ToEditor(inRefList[idx]); + if (obj) + retval.push_back(obj); + else { + // This shouldn't ever happen, but it might when loading + // an invalid file. + QT3DS_ASSERT(false); + } + } + return retval; + } + + void SEditorImpl::Set(const Option<SValue> &inData, STransition *&inTarget) + { + inTarget = NULL; + if (inData.hasValue()) { + TObjPtr theData(inData->getData<TObjPtr>()); + inTarget = FromEditor<STransition>(theData); + } + } + + Option<SValue> SEditorImpl::Get(STransition *&inTarget) + { + return SValue(ToEditor(inTarget)); + } + + void SEditorImpl::SetInitial(const TObjList &inList, STransition *&outTransitionPtr) + { + if (outTransitionPtr) { + Set(SValue(inList), outTransitionPtr->m_Target); + if (outTransitionPtr->m_Target.mSize == 0 + && outTransitionPtr->m_ExecutableContent.empty()) + outTransitionPtr = NULL; + } else { + NVConstDataRef<SStateNode *> theTemp; + Set(SValue(inList), theTemp); + if (theTemp.mSize) { + outTransitionPtr = QT3DS_NEW(m_AutoAllocator, STransition)(); + outTransitionPtr->m_Target = theTemp; + } + } + } + + SValue SEditorImpl::GetInitial(STransition *inInitialTrans) + { + if (inInitialTrans) + return Get(inInitialTrans->m_Target); + + return TObjList(); + } + static inline STransition **GetInitialTransitionPtr(SStateNode &inParent) + { + StateNodeTypes::Enum typeEnum(inParent.m_Type); + STransition **theParentTransPtr = NULL; + if (typeEnum == StateNodeTypes::SCXML) + theParentTransPtr = &static_cast<SSCXML &>(inParent).m_Initial; + else if (typeEnum == StateNodeTypes::State) + theParentTransPtr = &static_cast<SState &>(inParent).m_Initial; + return theParentTransPtr; + } + + static inline bool IsFirstChild(SStateNode &inChild, SStateNode &inParent) + { + TStateNodeList *childList = inParent.GetChildren(); + SStateNode *firstViableChild = NULL; + if (childList) { + for (TStateNodeList::iterator iter = childList->begin(), end = childList->end(); + iter != end && firstViableChild == NULL; ++iter) { + StateNodeTypes::Enum typeEnum = iter->m_Type; + if (typeEnum == StateNodeTypes::State || typeEnum == StateNodeTypes::Parallel + || typeEnum == StateNodeTypes::Final) { + firstViableChild = &(*iter); + } + } + } + return firstViableChild == &inChild; + } + + struct SInitialTargetChange : public IChange + { + TObjPtr m_EditorObj; + SStateNode &m_Parent; + STransition *m_InitialTransition; + STransition *m_FinalTransition; + QT3DSI32 m_RefCount; + // Implicitly setting c to be the initial target. + SInitialTargetChange(TObjPtr editorObj, SStateNode &p, SStateNode &c, + SEditorImpl &editor) + : m_EditorObj(editorObj) + , m_Parent(p) + , m_InitialTransition(NULL) + , m_FinalTransition(NULL) + , m_RefCount(0) + { + STransition **theTransitionPtr = GetInitialTransitionPtr(m_Parent); + QT3DS_ASSERT(theTransitionPtr); + if (theTransitionPtr) { + m_InitialTransition = *theTransitionPtr; + theTransitionPtr[0] = NULL; + bool firstChild = IsFirstChild(c, p); + if (!firstChild) { + TObjPtr editorObj = editor.Create("transition", ""); + TObjList targets; + targets.push_back(editor.ToEditor(c)); + editorObj->SetPropertyValue("target", targets); + SStateNode *stateNode = editor.StateNodeFromEditor(editorObj); + m_FinalTransition = static_cast<STransition *>(stateNode); + m_FinalTransition->m_Parent = &p; + *theTransitionPtr = m_FinalTransition; + } + } + } + virtual void addRef() { ++m_RefCount; } + virtual void release() + { + --m_RefCount; + if (m_RefCount <= 0) + delete this; + } + virtual TObjPtr GetEditor() { return m_EditorObj; } + + template <typename TNodeType> + void Apply(STransition *trans, TNodeType &node) + { + node.m_Initial = trans; + } + + virtual void Do() + { + if (m_Parent.m_Type == StateNodeTypes::SCXML) + Apply(m_FinalTransition, static_cast<SSCXML &>(m_Parent)); + else if (m_Parent.m_Type == StateNodeTypes::State) + Apply(m_FinalTransition, static_cast<SState &>(m_Parent)); + } + + virtual void Undo() + { + if (m_Parent.m_Type == StateNodeTypes::SCXML) + Apply(m_InitialTransition, static_cast<SSCXML &>(m_Parent)); + else if (m_Parent.m_Type == StateNodeTypes::State) + Apply(m_InitialTransition, static_cast<SState &>(m_Parent)); + } + }; + + void SEditorImpl::SetInitialTarget(const Option<SValue> &inData, SStateNode &inNode) + { + if (inNode.m_Type == StateNodeTypes::Transition + /**|| inNode.m_Type == StateNodeTypes::History **/) { + QT3DS_ASSERT(false); + } + SStateNode *theParent = inNode.m_Parent; + if (theParent) { + STransition **theParentTransPtr = GetInitialTransitionPtr(*theParent); + + if (theParentTransPtr) { + bool newValue = false; + if (inData.hasValue()) + newValue = inData->getData<bool>(); + + NVScopedRefCounted<SInitialTargetChange> theChange = + new SInitialTargetChange(ToEditor(*theParent), *theParent, inNode, *this); + theChange->Do(); + STransaction *theTransaction = GetOpenTransactionImpl(); + if (theTransaction) { + theTransaction->m_Changes.push_back(theChange.mPtr); + TStateNodeList *children = theParent->GetChildren(); + for (TStateNodeList::iterator iter = children->begin(), + end = children->end(); + iter != end; ++iter) { + theTransaction->m_Changes.push_back(new SChange(*ToEditor(*iter))); + } + } + } + } + } + + SValue SEditorImpl::IsInitialTarget(SStateNode &inNode) + { + if (inNode.m_Type == StateNodeTypes::Transition + /** || inNode.m_Type == StateNodeTypes::History **/) + return SValue(false); + + SStateNode *theParent(inNode.m_Parent); + if (theParent) { + if (!isTrivial(theParent->GetInitialExpression())) + return false; + + STransition *theTransition = theParent->GetInitialTransition(); + if (theTransition) { + for (QT3DSU32 idx = 0, end = theTransition->m_Target.size(); idx < end; ++idx) + if (&inNode == theTransition->m_Target[idx]) + return SValue(true); + } else { + if (GetInitialTransitionPtr(*theParent)) + return SValue(IsFirstChild(inNode, *theParent)); + } + } + return SValue(false); + } + + TEditorStr SEditorImpl::GetDefaultInitialValue(SStateNode &inNode) + { + TStateNodeList *children = inNode.GetChildren(); + if (children == NULL) + return TEditorStr(); + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) { + if (iter->m_Type != StateNodeTypes::History + && iter->m_Type != StateNodeTypes::Transition) + return TEditorStr(iter->m_Id.c_str()); + } + + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) { + if (iter->m_Type != StateNodeTypes::Transition) + return TEditorStr(iter->m_Id.c_str()); + } + + return TEditorStr(); + } + + void SEditorImpl::GetLegalInitialValues(SStateNode &inNode, + eastl::vector<CRegisteredString> &outValues) + { + TStateNodeList *children = inNode.GetChildren(); + if (children == NULL) + return; + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) { + if (/** iter->m_Type != StateNodeTypes::History && **/ iter->m_Type + != StateNodeTypes::Transition) + outValues.push_back(iter->m_Id); + } + } + + struct SInitialComboChange : public IChange + { + SStateNode &m_Node; + TObjPtr m_EditorObj; + const TEditorStr m_PreEditorComboValue; + const TEditorStr m_PostEditorComboValue; + const char8_t *m_PreInitialExpression; + const char8_t *m_PostInitialExpression; + STransition *m_PreComboTransition; + STransition *m_PostComboTransition; + QT3DSI32 m_RefCount; + SInitialComboChange(SStateNode &node, TObjPtr editObj, const char8_t *preExpr, + const char8_t *postExpr, STransition *preCombo, + STransition *postCombo, const TEditorStr &preEditorComboValue, + const TEditorStr &postEditorComboValue) + : m_Node(node) + , m_EditorObj(editObj) + , m_RefCount(0) + , m_PreInitialExpression(preExpr) + , m_PostInitialExpression(postExpr) + , m_PreComboTransition(preCombo) + , m_PostComboTransition(postCombo) + , m_PreEditorComboValue(preEditorComboValue) + , m_PostEditorComboValue(postEditorComboValue) + { + } + + virtual void addRef() { ++m_RefCount; } + virtual void release() + { + --m_RefCount; + if (m_RefCount <= 0) + delete this; + } + virtual TObjPtr GetEditor() { return m_EditorObj; } + + template <typename TNodeType, typename TEditorType> + void Apply(const char8_t *expr, STransition *trans, const TEditorStr &comboValue, + TNodeType &node, TEditorType &editor) + { + node.m_Initial = trans; + node.m_InitialExpr = expr; + editor.ApplyInitial(comboValue); + // editor.m_InitialComboValue = comboValue; + } + + virtual void Do() + { + if (m_Node.m_Type == StateNodeTypes::SCXML) + Apply(m_PostInitialExpression, m_PostComboTransition, m_PostEditorComboValue, + static_cast<SSCXML &>(m_Node), static_cast<SSCXMLEditor &>(*m_EditorObj)); + else if (m_Node.m_Type == StateNodeTypes::State) + Apply(m_PostInitialExpression, m_PostComboTransition, m_PostEditorComboValue, + static_cast<SState &>(m_Node), static_cast<SStateEditor &>(*m_EditorObj)); + } + + virtual void Undo() + { + if (m_Node.m_Type == StateNodeTypes::SCXML) + Apply(m_PreInitialExpression, m_PreComboTransition, m_PreEditorComboValue, + static_cast<SSCXML &>(m_Node), static_cast<SSCXMLEditor &>(*m_EditorObj)); + else if (m_Node.m_Type == StateNodeTypes::State) + Apply(m_PreInitialExpression, m_PreComboTransition, m_PreEditorComboValue, + static_cast<SState &>(m_Node), static_cast<SStateEditor &>(*m_EditorObj)); + } + }; + + void SEditorImpl::SetInitialAttribute(const Option<SValue> &inData, SStateNode &inNode) + { + TObjPtr theEditor = ToEditor(inNode); + STransition **targetPtr = NULL; + const char8_t **exprPtr = NULL; + TEditorStr comboValue; + if (inNode.m_Type == StateNodeTypes::State) { + targetPtr = &static_cast<SState &>(inNode).m_Initial; + exprPtr = &static_cast<SState &>(inNode).m_InitialExpr; + comboValue = static_cast<SStateEditor &>(*theEditor).m_InitialComboValue; + } else if (inNode.m_Type == StateNodeTypes::SCXML) { + targetPtr = &static_cast<SSCXML &>(inNode).m_Initial; + exprPtr = &static_cast<SSCXML &>(inNode).m_InitialExpr; + comboValue = static_cast<SSCXMLEditor &>(*theEditor).m_InitialComboValue; + } + + if (targetPtr == NULL) { + QT3DS_ASSERT(false); + return; + } + + STransition *initialTrans = *targetPtr; + const char8_t *initialExpr = *exprPtr; + TEditorStr initialComboValue = comboValue; + + if (inData.isEmpty()) { + *targetPtr = NULL; + comboValue = "(script expression)"; + } else { + TEditorStr stateId = inData->getData<TEditorStr>(); + SStateNode *theNode = + this->m_StateContext->FindStateNode(RegisterStr(stateId.c_str())); + if (theNode) { + *targetPtr = QT3DS_NEW(this->m_AutoAllocator, STransition)(); + SStateNode **newNode = + reinterpret_cast<SStateNode **>(this->m_AutoAllocator.allocate( + sizeof(SStateNode *), "TargetList", __FILE__, __LINE__)); + targetPtr[0]->m_Target = toDataRef(newNode, 1); + *const_cast<SStateNode **>(targetPtr[0]->m_Target.begin()) = theNode; + targetPtr[0]->m_Target.mSize = 1; + comboValue = stateId; + } else { + *targetPtr = NULL; + comboValue = ""; + } + *exprPtr = ""; + } + STransition *postTrans = *targetPtr; + const char8_t *postExpr = *exprPtr; + TEditorStr postComboValue = comboValue; + STransaction *theTransaction = GetOpenTransactionImpl(); + if (theTransaction) { + SInitialComboChange *theChange = new SInitialComboChange( + inNode, ToEditor(inNode), initialExpr, postExpr, initialTrans, postTrans, + initialComboValue, postComboValue); + theChange->Do(); + theTransaction->m_Changes.push_back(theChange); + TStateNodeList *children = inNode.GetChildren(); + if (children) { + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) { + theTransaction->m_Changes.push_back(new SChange(*ToEditor(*iter))); + } + } + } + } + + void SEditorImpl::Set(const Option<SValue> &inData, SDataModel *&inTarget) + { + if (inData.hasValue()) { + TObjPtr thePtr = inData->getData<TObjPtr>(); + if (thePtr) + inTarget = FromEditor<SDataModel>(thePtr); + else + inTarget = NULL; + } else + inTarget = NULL; + } + + SValue SEditorImpl::Get(SDataModel *inTarget) { return ToEditor(inTarget); } + + void SEditorImpl::Set(const Option<SValue> &inData, TDataList &inTarget) + { + while (inTarget.empty() == false) + inTarget.remove(inTarget.front()); + if (inData.hasValue()) { + TObjList newData = inData->getData<TObjList>(); + for (TObjList::iterator iter = newData.begin(), end = newData.end(); iter != end; + ++iter) { + SData *entry = FromEditor<SData>(*iter); + if (entry) + inTarget.push_back(*entry); + } + } + } + + SValue SEditorImpl::Get(TDataList &inTarget) + { + TObjList retval; + for (TDataList::iterator iter = inTarget.begin(), end = inTarget.end(); iter != end; + ++iter) { + TObjPtr item = ToEditor(*iter); + if (item) + retval.push_back(item); + } + return retval; + } + + void SEditorImpl::Set(const Option<SValue> &inData, NVConstDataRef<QT3DSVec2> &inTarget) + { + if (inTarget.size()) { + m_AutoAllocator.deallocate((void *)inTarget.mData); + inTarget.mData = NULL; + inTarget.mSize = 0; + } + if (inData.hasValue()) { + TVec2List newData(inData->getData<TVec2List>()); + if (newData.size()) { + size_t newDataSize = newData.size() * sizeof(QT3DSVec2); + QT3DSVec2 *newTargetData = (QT3DSVec2 *)m_AutoAllocator.allocate( + newDataSize, "Transition::m_Path", __FILE__, __LINE__); + memCopy(newTargetData, newData.data(), newDataSize); + inTarget = toDataRef(newTargetData, newData.size()); + } + } + } + + SValue SEditorImpl::Get(const NVConstDataRef<QT3DSVec2> &inTarget) + { + TVec2List retval; + if (inTarget.mSize) + retval.insert(retval.end(), inTarget.begin(), inTarget.end()); + return retval; + } + + void SEditorImpl::Set(const Option<SValue> &inData, SSend *&inTarget) + { + inTarget = NULL; + if (inData.hasValue() && inData->getType() == ValueTypes::ObjPtr) { + TObjPtr object = inData->getData<TObjPtr>(); + SSend *newPtr = FromEditor<SSend>(object); + inTarget = newPtr; + } + } + + SValue SEditorImpl::Get(SSend *inTarget) + { + if (!inTarget) + return SValue(TObjPtr()); + return ToEditor(inTarget); + } + + TObjPtr SEditorImpl::CreateExecutableContent(SStateNode &inParent, + const char8_t *inTypeName) + { + SExecutableContent *newExecutable = NULL; + TObjPtr newObj; + if (AreEqual(inTypeName, SSendEditor::GetTypeStr())) { + eastl::pair<SSend *, TObjPtr> theNewEditor = CreateEditorAndObject<SSend>(); + GenerateUniqueId(*theNewEditor.first, "send"); + newExecutable = theNewEditor.first; + newObj = theNewEditor.second; + } else if (AreEqual(inTypeName, SScriptEditor::GetTypeStr())) { + eastl::pair<SScript *, TObjPtr> theNewEditor = CreateEditorAndObject<SScript>(); + newExecutable = theNewEditor.first; + newObj = theNewEditor.second; + } else if (AreEqual(inTypeName, SCancelEditor::GetTypeStr())) { + eastl::pair<SCancel *, TObjPtr> theNewEditor = CreateEditorAndObject<SCancel>(); + newExecutable = theNewEditor.first; + newObj = theNewEditor.second; + } else { + QT3DS_ASSERT(false); + return TObjPtr(); + } + if (newExecutable) + newExecutable->m_StateNodeParent = &inParent; + return newObj; + } + + SValue SEditorImpl::GetSendId(const char8_t *expression) + { + if (isTrivial(expression)) + return SValue((QT3DSU32)0); + return static_cast<QT3DSU32>(IExecutionContext::ParseTimeStrToMilliseconds(expression)); + } + + void SEditorImpl::SetSendId(const Option<SValue> &inData, const char8_t *&outExpression) + { + if (!isTrivial(outExpression)) + m_AutoAllocator.deallocate(const_cast<char8_t *>(outExpression)); + + outExpression = ""; + + if (inData.hasValue() && inData->getType() == ValueTypes::U32) { + QT3DSU32 expr = inData->getData<QT3DSU32>(); + char buffer[64]; + sprintf(buffer, "%u", expr); + int len = strlen(buffer); + if (len < 61) { + buffer[len] = 'm'; + buffer[len + 1] = 's'; + buffer[len + 2] = 0; + } + outExpression = ToGraphStr(buffer); + } + } + + IStateContext &SEditorImpl::GetStateContext() { return *m_StateContext; } + + void SEditorImpl::SetTransactionManager(STransactionManagerImpl &manager) + { + m_TransactionManager = &manager; + } + + bool IsLegalHistoryTarget(StateNodeTypes::Enum inType) + { + return inType == StateNodeTypes::State || inType == StateNodeTypes::Final + || inType == StateNodeTypes::Parallel; + } + + void RecurseGetChildrenForHistoryTarget(SState &inNode, + eastl::vector<CRegisteredString> &retval) + { + for (TStateNodeList::iterator iter = inNode.m_Children.begin(), + end = inNode.m_Children.end(); + iter != end; ++iter) { + if (IsLegalHistoryTarget(iter->m_Type)) { + retval.push_back(iter->m_Id); + if (iter->m_Type == StateNodeTypes::State) + RecurseGetChildrenForHistoryTarget(static_cast<SState &>(*iter), retval); + } + } + } + + eastl::vector<CRegisteredString> SEditorImpl::GetLegalHistoryDefaultValues(SHistory &inData) + { + eastl::vector<CRegisteredString> retval; + if (inData.m_Parent && inData.m_Parent->m_Type == StateNodeTypes::State) { + SState &theState = static_cast<SState &>(*inData.m_Parent); + for (TStateNodeList::iterator iter = theState.m_Children.begin(), + end = theState.m_Children.end(); + iter != end; ++iter) { + if (IsLegalHistoryTarget(iter->m_Type)) { + retval.push_back(iter->m_Id); + if (inData.m_Flags.IsDeep()) { + if (iter->m_Type == StateNodeTypes::State) + RecurseGetChildrenForHistoryTarget(static_cast<SState &>(*iter), + retval); + } + } + } + } + return retval; + } + + void RecurseGetLegalParents(eastl::vector<CRegisteredString> &outResult, + SStateNode &inTarget, SStateNode &inCurrent) + { + // Basic checks + if (inCurrent.m_Type == StateNodeTypes::History + || inCurrent.m_Type == StateNodeTypes::Final || &inCurrent == &inTarget) + return; + + // Parallel's cannot have finals, but their children could + bool isIllegalChild = (inTarget.m_Type == StateNodeTypes::Final + && inCurrent.m_Type == StateNodeTypes::Parallel) + || inCurrent.m_Type == StateNodeTypes::SCXML; + + if (isIllegalChild == false) { + outResult.push_back(inCurrent.m_Id); + } + TStateNodeList *children = inCurrent.GetChildren(); + if (children) { + for (TStateNodeList::iterator iter = children->begin(), end = children->end(); + iter != end; ++iter) { + RecurseGetLegalParents(outResult, inTarget, *iter); + } + } + } + + static bool lexi(CRegisteredString lhs, CRegisteredString rhs) + { + return strcmp(lhs.c_str(), rhs.c_str()) < 0; + } + + eastl::vector<CRegisteredString> SEditorImpl::GetLegalParentIds(SStateNode &inNode) + { + // Do a depth first search and just do not include this node or any history or final + // nodes. + eastl::vector<CRegisteredString> retval; + RecurseGetLegalParents(retval, inNode, *m_StateContext->GetRoot()); + eastl::sort(retval.begin(), retval.end(), lexi); + return retval; + } + + struct SParentChange : public IChange + { + TObjPtr m_ChildObj; + TStateNodeList *m_OldList; + TStateNodeList &m_NewList; + SStateNode *m_OldParent; + SStateNode &m_NewParent; + SStateNode &m_Child; + QT3DSI32 m_RefCount; + SParentChange(TObjPtr childObj, TStateNodeList *oldL, TStateNodeList &newL, + SStateNode *oldp, SStateNode &newp, SStateNode &c) + : m_ChildObj(childObj) + , m_OldList(oldL) + , m_NewList(newL) + , m_OldParent(oldp) + , m_NewParent(newp) + , m_Child(c) + , m_RefCount(0) + { + } + + void addRef() { ++m_RefCount; } + void release() + { + --m_RefCount; + if (m_RefCount <= 0) + delete this; + } + + virtual void Do() + { + if (m_OldList) + m_OldList->remove(m_Child); + m_NewList.push_back(m_Child); + m_Child.m_Parent = &m_NewParent; + } + virtual void Undo() + { + m_NewList.remove(m_Child); + if (m_OldList) + m_OldList->push_back(m_Child); + m_Child.m_Parent = m_OldParent; + } + + virtual TObjPtr GetEditor() { return m_ChildObj; } + }; + + void SEditorImpl::SetParent(SStateNode &inNode, const Option<SValue> &inValue) + { + STransaction *theTransaction = m_TransactionManager->GetOpenTransactionImpl(); + SStateNode *oldParent = inNode.m_Parent; + TEditorStr newParentId; + if (inValue.hasValue()) + newParentId = inValue->getData<TEditorStr>(); + + SStateNode *newParent = + m_StateContext->FindStateNode(m_StringTable->RegisterStr(newParentId.c_str())); + if (newParent == NULL) + newParent = m_StateContext->GetRoot(); + TStateNodeList *newList = newParent->GetChildren(); + if (newList == NULL) { + QT3DS_ASSERT(false); + return; + } + SParentChange *theChange = + new SParentChange(ToEditor(inNode), oldParent ? oldParent->GetChildren() : NULL, + *newList, oldParent, *newParent, inNode); + theChange->Do(); + if (theTransaction) { + theTransaction->m_Changes.push_back(theChange); + if (oldParent) + theTransaction->m_Changes.push_back(new SChange(*ToEditor(*oldParent))); + theTransaction->m_Changes.push_back(new SChange(*ToEditor(*newParent))); + } + CheckAndSetValidHistoryDefault(ToEditor(inNode)); + // Fix the initial state of the old parent if it exists + if (oldParent) { + if (isTrivial(oldParent->GetInitialExpression()) + && oldParent->GetInitialTransition()) { + STransition *theTransition = oldParent->GetInitialTransition(); + if (theTransition != NULL && theTransition->m_Target.size()) { + bool foundItem = false; + for (QT3DSU32 idx = 0, end = theTransition->m_Target.size(); + idx < end && foundItem == false; ++idx) { + if (theTransition->m_Target[idx] == &inNode) + foundItem = true; + } + if (foundItem) { + SetInitialAttribute(SValue(TEditorStr()), *oldParent); + } + } + } + // fix all history nodes + TStateNodeList *parentChildren = oldParent->GetChildren(); + if (parentChildren) { + for (TStateNodeList::iterator iter = parentChildren->begin(), + end = parentChildren->end(); + iter != end; ++iter) { + if (iter->m_Type == StateNodeTypes::History) { + CheckAndSetValidHistoryDefault(ToEditor(*iter)); + } + } + } + } + // Set the new object inside the new parent. + ToEditor(inNode)->SetPropertyValue("position", QT3DSVec2(0, 0)); + } + + void SEditorImpl::CheckAndSetValidHistoryDefault(TObjPtr inHistoryNode) + { + if (inHistoryNode && AreEqual(inHistoryNode->TypeName(), "history")) { + eastl::vector<CRegisteredString> validChildren = + inHistoryNode->GetLegalValues("default"); + TObjList existing = inHistoryNode->GetPropertyValue("default")->getData<TObjList>(); + if (existing.size()) { + // Ensure all of the existing nodes are in the valid children list. + bool allValid = true; + for (size_t existingIdx = 0, existingEnd = existing.size(); + existingIdx < existingEnd && allValid; ++existingIdx) { + CRegisteredString idStr = + m_StringTable->RegisterStr(existing[existingIdx]->GetId().c_str()); + if (eastl::find(validChildren.begin(), validChildren.end(), idStr) + == validChildren.end()) + allValid = false; + } + if (allValid) + return; + } + SetValidHistoryDefault(inHistoryNode); + } + } + + void SEditorImpl::SetValidHistoryDefault(TObjPtr inHistoryNode) + { + if (inHistoryNode && AreEqual(inHistoryNode->TypeName(), "history")) { + eastl::vector<CRegisteredString> legalValues = + inHistoryNode->GetLegalValues("default"); + if (legalValues.size()) { + TObjPtr firstLegal = GetObjectById(legalValues[0]); + TObjList propValue; + propValue.push_back(firstLegal); + inHistoryNode->SetPropertyValue("default", propValue); + } + } + } + + MallocAllocator SBaseEditorFoundation::g_BaseAlloc; + + bool SPropertyDecNameFinder::operator()(const SPropertyDeclaration &dec) const + { + return AreEqual(m_NameStr, dec.m_Name.c_str()); + } + + void IEditorObject::Append(const char8_t *inPropName, TObjPtr inObj) + { + Option<SValue> theProp = GetPropertyValue(inPropName); + if (theProp.hasValue() && theProp->getType() == ValueTypes::ObjPtrList + && inObj != NULL) { + TObjList theData = theProp->getData<TObjList>(); + theData.push_back(inObj); + SetPropertyValue(inPropName, theData); + } + } + + void IEditorObject::Remove(const char8_t *inPropName, TObjPtr inObj) + { + Option<SValue> theProp = GetPropertyValue(inPropName); + if (theProp.hasValue() && theProp->getType() == ValueTypes::ObjPtrList + && inObj != NULL) { + TObjList theData = theProp->getData<TObjList>(); + TObjList::iterator theFind = eastl::find(theData.begin(), theData.end(), inObj); + if (theFind != theData.end()) + theData.erase(theFind); + SetPropertyValue(inPropName, theData); + } + } + + IEditor &IEditor::CreateEditor() + { + TFoundationPtr theFoundation = SBaseEditorFoundation::Create(); + return *QT3DS_NEW(theFoundation->getAllocator(), SEditorImpl)( + theFoundation, IStringTable::CreateStringTable(theFoundation->getAllocator())); + } + + IEditor *IEditor::CreateEditor(const char8_t *inFname) + { + CFileSeekableIOStream theFile(inFname, FileReadFlags()); + if (theFile.IsOpen() == false) + return NULL; + + return CreateEditor(inFname, theFile); + } + + IEditor *IEditor::CreateEditor(const char8_t *inFname, IInStream &inStream) + { + IEditor &theEditor(CreateEditor()); + if (static_cast<SEditorImpl &>(theEditor).Load(inStream, inFname)) + return &theEditor; + + theEditor.release(); + return NULL; + } + } +} +} diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditor.h b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditor.h new file mode 100644 index 00000000..31c83afe --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditor.h @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EDITOR_H +#define QT3DS_STATE_EDITOR_H +#pragma once +#include "Qt3DSState.h" +#include "Qt3DSStateSignalConnection.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/TaggedPointer.h" +#include "EASTL/vector.h" +#include "EASTL/string.h" +#include "foundation/Qt3DSVec2.h" +#include "foundation/Qt3DSVec3.h" +#include "Qt3DSStateInterpreter.h" + +namespace qt3ds { +namespace state { + namespace editor { + + class IEditorObject; + typedef NVScopedRefCounted<IEditorObject> TObjPtr; + typedef eastl::vector<TObjPtr> TObjList; + + class IEditorChangeListener + { + protected: + virtual ~IEditorChangeListener() {} + public: + // The same object may be represented multiple times in this list. + virtual void OnDataChange(const TObjList &inChangedObjects, + const TObjList &inRemovedObjects) = 0; + }; + + struct EditorPropertyTypes + { + enum Enum { + NoProperty = 0, + Event, // string + Id, // string + Object, // IEditorObject + ObjectList, // IEditorObject list + // A string, but one from a set of strings. + StringSet, // string + String, // string, single line + BigString, // string than may be multiple lines long + Position, // vec2 + Dimension, // vec2 + PositionList, // vector<QT3DSVec2> + Script, // string + Color, + Boolean, + U32, + }; + }; + + struct SPropertyDeclaration + { + CRegisteredString m_Name; + EditorPropertyTypes::Enum m_Type; + // for enumerations, the list of enumerations + SPropertyDeclaration() {} + SPropertyDeclaration(CRegisteredString inName, EditorPropertyTypes::Enum inType) + : m_Name(inName) + , m_Type(inType) + { + } + }; + + typedef eastl::vector<SPropertyDeclaration> TPropertyDeclarationList; + + struct SPropertyDecNameFinder + { + const char8_t *m_NameStr; + SPropertyDecNameFinder(const char8_t *nmStr) + : m_NameStr(nmStr) + { + } + // Implemented in UICStateEditor.cpp to include "foundation/Utils.h" + bool operator()(const SPropertyDeclaration &dec) const; + }; + + typedef eastl::vector<QT3DSVec2> TVec2List; + typedef eastl::string TEditorStr; + typedef eastl::vector<TEditorStr> TEditorStrList; + + // Our generic discriminated union type for all property values. + struct SValue; + struct SValueOpt; + + class IEditor; + + class IEditorObject : public NVRefCounted + { + protected: + virtual ~IEditorObject() {} + const char8_t *m_TypeName; + + public: + IEditorObject(const char8_t *tn) + : m_TypeName(tn) + { + } + const char8_t *TypeName() const { return m_TypeName; } + // implemented in UICStateEditor.cpp using getpropertyvalue. + virtual TEditorStr GetId() = 0; + virtual TEditorStr GetDescription() = 0; + virtual void SetId(const TEditorStr &inId) = 0; + virtual void SetDescription(const TEditorStr &inName) = 0; + // Things only have one canonical parent. + virtual TObjPtr Parent() = 0; + + virtual void GetProperties(eastl::vector<SPropertyDeclaration> &outProperties) = 0; + virtual Option<SPropertyDeclaration> FindProperty(const char8_t *propName) = 0; + // The legal values for a property tend to be something that have to be calculated + // dynamically. + virtual eastl::vector<CRegisteredString> GetLegalValues(const char8_t *propName) = 0; + + virtual Option<SValue> GetPropertyValue(const char8_t *inPropName) = 0; + virtual void SetPropertyValue(const char8_t *inPropName, const SValueOpt &inValue) = 0; + virtual bool endEdit() { return false; } // should SetPropertyValue end property edit + + // Utility function implemented in UICStateEditor.cpp + virtual void Append(const char8_t *inPropName, TObjPtr inObj); + virtual void Remove(const char8_t *inPropName, TObjPtr inObj); + + // Remove this from any parents. Remove any transitions pointing to this. + // Only works for state-node derived types. + // Executable content, onEntry onExit and such will not work with this. + // Also removes this item id from the id map. This is what is used to delete objects. + // Do not reattach yourself; callers should release all references to this object after + // calling + // this. + virtual void RemoveObjectFromGraph() = 0; + + // Internal calls, don't call externally + virtual void RemoveIdFromContext() = 0; + virtual void AddIdToContext() = 0; + + // Most things do not have any executable content. + virtual NVConstDataRef<InterpreterEventTypes::Enum> GetExecutableContentTypes() + { + return NVConstDataRef<InterpreterEventTypes::Enum>(); + } + + // Be aware that there are more types of executable content than indicated in the store; + // a lot more. + // So just ignore the types the story ignores. + virtual TObjList GetExecutableContent(InterpreterEventTypes::Enum /*inType*/) + { + return TObjList(); + } + + // inName can be an executable content: + //'script', 'send', 'cancel' + virtual TObjPtr CreateAndAppendExecutableContent(InterpreterEventTypes::Enum /*inType*/, + const char8_t * /*inName*/) + { + return TObjPtr(); + } + + virtual IEditor &GetEditor() = 0; + }; + + class ITransaction : public NVRefCounted + { + protected: + virtual ~ITransaction() {} + TEditorStr m_Name; + + public: + ITransaction(const TEditorStr inName = TEditorStr()) + : m_Name(inName) + { + } + TEditorStr GetName() const { return m_Name; } + // Send the signals collected during the creation of this transaction + virtual void SendDoSignals() = 0; + virtual TObjList GetEditedObjects() = 0; + virtual void Do() = 0; + virtual void Undo() = 0; + virtual bool Empty() = 0; + }; + + typedef NVScopedRefCounted<ITransaction> TTransactionPtr; + + class ITransactionManager : public NVRefCounted + { + protected: + virtual ~ITransactionManager() {} + public: + virtual TSignalConnectionPtr AddChangeListener(IEditorChangeListener &inListener) = 0; + // Undo/redo is supported via a transparent transaction system. + // calls are reentrant but last call will close the transaction object. + // Any changes to any editor objects will go through this transaction when they happen. + virtual TTransactionPtr BeginTransaction(const TEditorStr &inName = TEditorStr()) = 0; + virtual TTransactionPtr GetOpenTransaction() = 0; + virtual void RollbackTransaction() = 0; + virtual void EndTransaction() = 0; + }; + + // Editor interface to a UICState dataset + class IEditor : public ITransactionManager + { + protected: + virtual ~IEditor() {} + public: + // Traditional editor interface. + virtual TObjPtr GetRoot() = 0; + // You can force the id to be a particular ID in some cases, this is useful for testing. + // ID is ignored for objects that don't have an id. + virtual TObjPtr Create(const char8_t *inTypeName, const char8_t *inId = 0) = 0; + virtual TObjPtr GetOrCreate(SSCXML &inData) = 0; + virtual TObjPtr GetOrCreate(SState &inData) = 0; + virtual TObjPtr GetOrCreate(STransition &inData) = 0; + virtual TObjPtr GetOrCreate(SParallel &inData) = 0; + virtual TObjPtr GetOrCreate(SHistory &inData) = 0; + virtual TObjPtr GetOrCreate(SFinal &inData) = 0; + virtual TObjPtr GetOrCreate(SDataModel &inData) = 0; + virtual TObjPtr GetOrCreate(SData &inData) = 0; + virtual TObjPtr ToEditor(SStateNode &inItem) = 0; + + // Get a particular object by id. Useful in testing scenarios. + virtual TObjPtr GetObjectById(const char8_t *inId) = 0; + + // Get an editor, if it already has been created, for this piece of graph data. + // Note that if it has not been created, this function couldn't possibly create it + // due to lack of type information. + virtual TObjPtr GetEditor(void *inGraphData) = 0; + + // Copy save a subgraph to a string. Converts all top level positions to be relative to + // mouse pos. + virtual TEditorStr Copy(TObjList inObjects, const QT3DSVec2 &inMousePos) = 0; + virtual bool CanPaste(TObjPtr inTarget) = 0; + // Paste in, adding relative pos to all top level positions. + virtual void Paste(const TEditorStr &inCopiedObjects, TObjPtr inTarget, + const QT3DSVec2 &inRelativePos) = 0; + + virtual TObjPtr GetLeastCommonAncestor(NVConstDataRef<TObjPtr> inObjects) = 0; + + // Returns the list of send ids that have a delay on them. + virtual TObjList GetCancelableSendIds() = 0; + + // Return the set of events in use in the state machine. + virtual TEditorStrList GetStateMachineEvents() = 0; + + // Change content from one type to another. + virtual TObjPtr ChangeExecutableContentType(TObjPtr inContent, + const char8_t *newType) = 0; + + virtual TEditorStr ToXML(TObjPtr inContent) = 0; + // Returns the error from parsing the xml or if everything went correctly replaces + // inContent + // with the result of parsing the xml and returns the new content. + virtual eastl::pair<TEditorStr, TObjPtr> FromXML(TObjPtr inContent, + const TEditorStr &ioEditedXML) = 0; + + virtual bool Save(const char8_t *inFname) = 0; + virtual void Save(IOutStream &inStream) = 0; + + // This must be called on history creation once it has been added to the proper parent. + virtual void SetValidHistoryDefault(TObjPtr inHistoryNode) = 0; + + static IEditor &CreateEditor(); + + static IEditor *CreateEditor(const char8_t *inFname); + static IEditor *CreateEditor(const char8_t *inFname, IInStream &inStream); + }; + + typedef NVScopedRefCounted<IEditor> TEditorPtr; + + struct SEditorImplTransactionScope + { + ITransactionManager &m_Editor; + TTransactionPtr m_Transaction; + SEditorImplTransactionScope(ITransactionManager &inEditor) + : m_Editor(inEditor) + , m_Transaction(inEditor.BeginTransaction()) + { + } + ~SEditorImplTransactionScope() { m_Editor.EndTransaction(); } + + TTransactionPtr operator->() { return m_Transaction; } + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorEditorsImpl.h b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorEditorsImpl.h new file mode 100644 index 00000000..fc9411d2 --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorEditorsImpl.h @@ -0,0 +1,1772 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EDITOR_EDITORS_IMPL_H +#define QT3DS_STATE_EDITOR_EDITORS_IMPL_H +#include "Qt3DSState.h" +#include "Qt3DSStateEditor.h" +#include "Qt3DSStateEditorValue.h" +#include "Qt3DSStateEditorTransactionImpl.h" +#include "Qt3DSStateTypes.h" +#include "Qt3DSStateEditorFoundation.h" +#include "Qt3DSStateEditorProperties.h" +#include "foundation/Utils.h" +#include "foundation/XML.h" +#include "Qt3DSStateXMLIO.h" +#include "foundation/IOStreams.h" +#include "Qt3DSStateExecutionTypes.h" +#include "Qt3DSStateContext.h" +#include "Qt3DSStateEditorImpl.h" + +namespace qt3ds { +namespace state { + namespace editor { + + struct EditorTypes + { + enum Enum { + UnknownEditor = 0, + SCXML, + State, + Parallel, + Transition, + History, + Final, + ExecutableContent, + OnEntry, + OnExit, + Send, + Raise, + If, + Else, + ElseIf, + Log, + Assign, + Script, + DataModel, + Data, + Cancel, + }; + }; + + struct SEditorImplObject : public IEditorObject + { + TFoundationPtr m_Foundation; + SEditorImpl &m_Editor; + volatile QT3DSI32 mRefCount; + eastl::string m_Id; + eastl::string m_Description; + Option<QT3DSVec3> m_Color; + TPropertyAccessorList m_PropertyAccessors; + + static void CreateAccessors(SEditorImpl &inData, TPropertyAccessorList &outAccessors, + bool includeId, bool includeColor) + { + if (includeId) + outAccessors.push_back(CreateEditorAccessor(inData, &SEditorImplObject::m_Id, + "id", EditorPropertyTypes::String)); + + outAccessors.push_back( + CreateEditorAccessor(inData, &SEditorImplObject::m_Description, "description", + EditorPropertyTypes::String)); + if (includeColor) + outAccessors.push_back(CreateEditorAccessor( + inData, &SEditorImplObject::m_Color, "color", EditorPropertyTypes::Color)); + } + + SEditorImplObject(const char8_t *tn, SEditorImpl &inData, + const TPropertyAccessorList &inAccessors) + : IEditorObject(tn) + , m_Foundation(inData.m_EditorFoundation) + , m_Editor(inData) + , mRefCount(0) + , m_PropertyAccessors(inAccessors) + { + } + + IPropertyAccessor *FindPropertyAccessor(const char8_t *inName) + { + CRegisteredString nameStr = m_Editor.m_StringTable->RegisterStr(inName); + for (size_t idx = 0, end = m_PropertyAccessors.size(); idx < end; ++idx) + if (m_PropertyAccessors[idx]->m_Declaration.m_Name == nameStr) + return m_PropertyAccessors[idx].mPtr; + return NULL; + } + + virtual void GetProperties(eastl::vector<SPropertyDeclaration> &outProperties) + { + outProperties.clear(); + for (size_t idx = 0, end = m_PropertyAccessors.size(); idx < end; ++idx) + outProperties.push_back(m_PropertyAccessors[idx]->m_Declaration); + } + + virtual Option<SPropertyDeclaration> FindProperty(const char8_t *propName) + { + for (size_t idx = 0, end = m_PropertyAccessors.size(); idx < end; ++idx) { + if (AreEqual(m_PropertyAccessors[idx]->m_Declaration.m_Name.c_str(), propName)) + return m_PropertyAccessors[idx]->m_Declaration; + } + return Empty(); + } + + virtual eastl::vector<CRegisteredString> GetLegalValues(const char8_t *propName) + { + IPropertyAccessor *accessor = FindPropertyAccessor(propName); + if (accessor) + return accessor->GetLegalValues(*this); + return eastl::vector<CRegisteredString>(); + } + + virtual Option<SValue> GetPropertyValue(const char8_t *inPropName) + { + IPropertyAccessor *accessor = FindPropertyAccessor(inPropName); + if (accessor) { + return accessor->Get(*this); + } + return Empty(); + } + virtual void SetPropertyValue(const char8_t *inPropName, const SValueOpt &inValue) + { + IPropertyAccessor *accessor = FindPropertyAccessor(inPropName); + if (accessor) { + if (accessor->HandlesTransaction() == false) { + STransaction *theTransaction = m_Editor.GetOpenTransactionImpl(); + if (theTransaction) { + Option<SValue> existing = accessor->Get(*this); + theTransaction->m_Changes.push_back( + new SChange(existing, inValue, *accessor, *this)); + } + } + accessor->Set(*this, inValue); + } else { + QT3DS_ASSERT(false); + } + } + + virtual TEditorStr GetId() + { + Option<SValue> propValue = GetPropertyValue("id"); + if (propValue.hasValue()) + return propValue->getData<TEditorStr>(); + return TEditorStr(); + } + virtual TEditorStr GetDescription() { return m_Description; } + // We have to go through the SPV interface to take transactions into account. + + virtual void SetId(const TEditorStr &inName) { SetPropertyValue("id", SValue(inName)); } + virtual void SetDescription(const TEditorStr &inName) + { + SetPropertyValue("description", SValue(inName)); + } + + virtual TObjPtr Parent() { return TObjPtr(); } + virtual void RemoveObjectFromGraph() { QT3DS_ASSERT(false); } + + virtual IEditor &GetEditor() { return m_Editor; } + }; + +#define QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE \ + void addRef() { atomicIncrement(&mRefCount); } \ + void release() \ + { \ + TFoundationPtr fnd(m_Foundation); \ + /*Ensure the editor sticks around till *after* we are gone. */ \ + /*This is because our objects will keep several bits of the editor around */ \ + QT3DS_IMPLEMENT_REF_COUNT_RELEASE(m_Foundation->getAllocator()); \ + } + + struct SSCXMLEditor : public SEditorImplObject + { + typedef SSCXML TStateType; + enum { EditorType = EditorTypes::SCXML }; + + SSCXML &m_Data; + eastl::string m_InitialComboValue; + bool m_endEdit; + + static const char8_t *GetTypeStr() { return "scxml"; } + + static TPropertyAccessorList CreateAccessors(SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + typedef SDataProp<SSCXMLEditor, SSCXML, STransition *> TInitialProp; + typedef SFlagBooleanProperty<SSCXMLEditor, SSCXMLFlags> TBindingProp; + TPropertyAccessorList &retval(inList); + NVAllocatorCallback &alloc(inEditorData.m_EditorFoundation->getAllocator()); + typedef SDataProp<SSCXMLEditor, SSCXML, CRegisteredString> TNameProp; + typedef SInitialComboProp<SSCXMLEditor, SSCXML> TInitialComboProp; + typedef SDataProp<SSCXMLEditor, SSCXML, const char8_t *> TCharProp; + IPropertyAccessor *newAccessor = + QT3DS_NEW(alloc, TNameProp)(inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("name"), + EditorPropertyTypes::String), + &SSCXML::m_Name); + retval.push_back(newAccessor); + retval.push_back(CreateEditorAccessor(inEditorData, + &SEditorImplObject::m_Description, + "description", EditorPropertyTypes::String)); + newAccessor = QT3DS_NEW(alloc, TBindingProp)( + inEditorData.m_EditorFoundation, *inEditorData.m_StringTable, "binding", + "early", "late", &SSCXMLFlags::IsLateBinding, &SSCXMLFlags::SetLateBinding); + retval.push_back(newAccessor); + + TInitialComboProp *theInitialCombo = QT3DS_NEW(alloc, TInitialComboProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("initial"), + EditorPropertyTypes::StringSet)); + retval.push_back(theInitialCombo); + + TCharProp *theInitialExprProp = QT3DS_NEW(alloc, TCharProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("initialexpr"), + EditorPropertyTypes::BigString), + &SSCXML::m_InitialExpr); + retval.push_back(theInitialExprProp); + + retval.push_back(CreateDataAccessor<SSCXMLEditor>( + inEditorData, &SSCXML::m_DataModel, "datamodel", EditorPropertyTypes::Object)); + newAccessor = QT3DS_NEW(alloc, SChildrenProperty<SSCXMLEditor>)( + inEditorData.m_EditorFoundation, *inEditorData.m_StringTable); + retval.push_back(newAccessor); + // Replace the name property with one that writes to our data + return retval; + } + + SSCXMLEditor(SSCXML &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SEditorImplObject(GetTypeStr(), inEditorData, + CreateAccessors(inEditorData, inList)) + , m_Data(inData) + , m_endEdit(false) + { + } + + void ApplyInitial(const TEditorStr &comboValue) + { + m_endEdit = false; + + if (m_InitialComboValue == "(script expression)" + || comboValue == "(script expression)") + m_endEdit = true; + + m_InitialComboValue = comboValue; + } + + virtual bool endEdit() + { + bool bTemp = m_endEdit; + m_endEdit = false; + return bTemp; + } + + virtual CRegisteredString GetId() const { return m_Data.m_Id; } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual void *GetWrappedObject() { return &m_Data; } + + virtual void RemoveObjectFromGraph() { QT3DS_ASSERT(false); } + + virtual void RemoveIdFromContext() { m_Editor.GetStateContext().EraseId(m_Data.m_Id); } + + virtual void AddIdToContext() + { + if (m_Data.m_Id.IsValid()) + m_Editor.GetStateContext().InsertId(m_Data.m_Id, &m_Data); + } + + virtual void GetProperties(eastl::vector<SPropertyDeclaration> &outProperties) + { + outProperties.clear(); + eastl::vector<CRegisteredString> temp; + m_Editor.GetLegalInitialValues(m_Data, temp); + bool hasInitialState = temp.size() > 1; + bool hasInitialExpr = false; + CRegisteredString nameStr = m_Editor.RegisterStr("initial"); + CRegisteredString initialExprStr = m_Editor.RegisterStr("initialexpr"); + + for (size_t idx = 0, end = m_PropertyAccessors.size(); idx < end; ++idx) { + if (m_PropertyAccessors[idx]->m_Declaration.m_Name == nameStr) { + if (hasInitialState) + outProperties.push_back(m_PropertyAccessors[idx]->m_Declaration); + } else if (m_PropertyAccessors[idx]->m_Declaration.m_Name == initialExprStr) { + if (hasInitialExpr) + outProperties.push_back(m_PropertyAccessors[idx]->m_Declaration); + } else { + outProperties.push_back(m_PropertyAccessors[idx]->m_Declaration); + } + + if (m_PropertyAccessors[idx]->m_Declaration.m_Name == nameStr) { + TEditorStr initialValue = + m_PropertyAccessors[idx]->Get(*this)->getData<TEditorStr>(); + if (initialValue == "(script expression)") + hasInitialExpr = true; + } + } + } + }; + + struct SPositionalEditor : public SEditorImplObject + { + SPositionalEditor(const char8_t *inTypeName, SEditorImpl &inData, + const TPropertyAccessorList &inAccessors) + : SEditorImplObject(inTypeName, inData, inAccessors) + { + } + }; + + template <typename TListType, typename TListItem> + struct TListInsertDeleteChange : public IChange + { + TObjPtr m_EditorObject; + QT3DSI32 mRefCount; + TListType *m_ParentList; + TListItem *m_TargetContent; + QT3DSI32 m_ContentIdx; + bool m_AddOnDo; + // The item must be in the list for this to work. + TListInsertDeleteChange(TObjPtr inEditor, TListType *plist, TListItem *tc, bool addOnDo) + : m_EditorObject(inEditor) + , mRefCount(0) + , m_ParentList(plist) + , m_TargetContent(tc) + , m_ContentIdx(-1) + , m_AddOnDo(addOnDo) + { + for (typename TListType::iterator iter = m_ParentList->begin(), + end = m_ParentList->end(); + iter != end; ++iter) { + if (&(*iter) == m_TargetContent) + break; + // setup content idx to point to the item just our target item. + ++m_ContentIdx; + } + } + virtual void addRef() { atomicIncrement(&mRefCount); } + virtual void release() + { + atomicDecrement(&mRefCount); + if (mRefCount <= 0) { + delete this; + } + } + void add() + { + if (m_ContentIdx > -1) { + QT3DSI32 idx = m_ContentIdx - 1; + typename TListType::iterator iter = m_ParentList->begin(); + + for (typename TListType::iterator end = m_ParentList->end(); + iter != end && idx > -1; ++iter, --idx) { + }; + + if (iter != m_ParentList->end()) + m_ParentList->insert_after(*iter, *m_TargetContent); + else + m_ParentList->push_back(*m_TargetContent); + } else + m_ParentList->push_front(*m_TargetContent); + } + void remove() { m_ParentList->remove(*m_TargetContent); } + + virtual void Do() + { + if (m_AddOnDo) + add(); + else + remove(); + } + virtual void Undo() + { + if (m_AddOnDo) + remove(); + else + add(); + } + virtual TObjPtr GetEditor() { return m_EditorObject; } + }; + + struct SExecutableContentChange + : public TListInsertDeleteChange<TExecutableContentList, SExecutableContent> + { + typedef TListInsertDeleteChange<TExecutableContentList, SExecutableContent> TBase; + SExecutableContentChange(TObjPtr inEditor, TExecutableContentList *plist, + SExecutableContent *tc, bool addOnDo) + : TBase(inEditor, plist, tc, addOnDo) + { + } + }; + + struct SStateEditor : public SPositionalEditor + { + typedef SState TStateType; + enum { EditorType = EditorTypes::State }; + SState &m_Data; + eastl::string m_InitialComboValue; + bool m_endEdit; + static const char8_t *GetTypeStr() { return "state"; } + + template <typename TEditorType> + static void CreateStateNodeChildren(SEditorImpl &inEditorData, + TPropertyAccessorList &retval) + { + NVAllocatorCallback &alloc(inEditorData.m_EditorFoundation->getAllocator()); + IPropertyAccessor *newAccessor = QT3DS_NEW(alloc, SChildrenProperty<TEditorType>)( + inEditorData.m_EditorFoundation, *inEditorData.m_StringTable); + retval.push_back(newAccessor); + } + + template <typename TEditorType> + static void CreatePositionColorAccessors(SEditorImpl &inEditorData, + TPropertyAccessorList &retval) + { + typedef SOptionAccessorProp<TEditorType, SStateNode, QT3DSVec2> TVec2Access; + typedef SOptionAccessorProp<TEditorType, SStateNode, QT3DSVec3> TVec3Access; + NVAllocatorCallback &alloc(inEditorData.m_EditorFoundation->getAllocator()); + retval.push_back(QT3DS_NEW(alloc, TVec2Access)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("position"), + EditorPropertyTypes::Position), + &SStateNode::GetPosition, &SStateNode::SetPosition)); + retval.push_back(QT3DS_NEW(alloc, TVec2Access)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("dimension"), + EditorPropertyTypes::Position), + &SStateNode::GetDimension, &SStateNode::SetDimension)); + + retval.push_back(QT3DS_NEW(alloc, TVec3Access)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("color"), + EditorPropertyTypes::Color), + &SStateNode::GetColor, &SStateNode::SetColor)); + } + + static TPropertyAccessorList CreateAccessors(SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + typedef SDataProp<SStateEditor, SState, STransition *> TInitialProp; + typedef SDataIdProp<SStateEditor, SState> TIdPropType; + typedef SInitialTargetProp<SStateEditor, SState> TInitialTargetProp; + typedef SInitialComboProp<SStateEditor, SState> TInitialComboProp; + typedef SDataProp<SStateEditor, SState, const char8_t *> TCharProp; + + NVAllocatorCallback &alloc(inEditorData.m_EditorFoundation->getAllocator()); + + TPropertyAccessorList &retval(inList); + retval.push_back(QT3DS_NEW(alloc, TIdPropType)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("id"), EditorPropertyTypes::Id), + &SState::m_Id)); + + typedef SParentProp<SStateEditor> TParentPropType; + retval.push_back(QT3DS_NEW(alloc, TParentPropType)(inEditorData.m_EditorFoundation, + *inEditorData.m_StringTable)); + SEditorImplObject::CreateAccessors(inEditorData, retval, false, false); + + TInitialComboProp *theInitialCombo = QT3DS_NEW(alloc, TInitialComboProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("initial"), + EditorPropertyTypes::StringSet)); + retval.push_back(theInitialCombo); + + TCharProp *theInitialExprProp = QT3DS_NEW(alloc, TCharProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("initialexpr"), + EditorPropertyTypes::BigString), + &SState::m_InitialExpr); + retval.push_back(theInitialExprProp); + retval.push_back(CreateDataAccessor<SStateEditor>( + inEditorData, &SState::m_DataModel, "datamodel", EditorPropertyTypes::Object)); + CreateStateNodeChildren<SStateEditor>(inEditorData, retval); + CreatePositionColorAccessors<SStateEditor>(inEditorData, retval); + retval.push_back(QT3DS_NEW(alloc, TInitialTargetProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("is initial target"), + EditorPropertyTypes::Boolean))); + // Replace the name property with one that writes to our data + return retval; + } + SStateEditor(SState &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SPositionalEditor(GetTypeStr(), inEditorData, + CreateAccessors(inEditorData, inList)) + , m_Data(inData) + , m_endEdit(false) + { + } + + virtual void GetProperties(eastl::vector<SPropertyDeclaration> &outProperties) + { + outProperties.clear(); + eastl::vector<CRegisteredString> temp; + m_Editor.GetLegalInitialValues(m_Data, temp); + bool hasInitialState = temp.size() > 1; + bool hasInitialExpr = false; + CRegisteredString nameStr = m_Editor.RegisterStr("initial"); + CRegisteredString initialExprStr = m_Editor.RegisterStr("initialexpr"); + for (size_t idx = 0, end = m_PropertyAccessors.size(); idx < end; ++idx) { + if (m_PropertyAccessors[idx]->m_Declaration.m_Name == nameStr) { + if (hasInitialState) + outProperties.push_back(m_PropertyAccessors[idx]->m_Declaration); + } else if (m_PropertyAccessors[idx]->m_Declaration.m_Name == initialExprStr) { + if (hasInitialExpr) + outProperties.push_back(m_PropertyAccessors[idx]->m_Declaration); + } else + outProperties.push_back(m_PropertyAccessors[idx]->m_Declaration); + + if (hasInitialState + && m_PropertyAccessors[idx]->m_Declaration.m_Name == nameStr) { + TEditorStr initialValue = + m_PropertyAccessors[idx]->Get(*this)->getData<TEditorStr>(); + if (initialValue == "(script expression)") + hasInitialExpr = true; + } + } + } + + void ApplyInitial(const TEditorStr &comboValue) + { + m_endEdit = false; + + if (m_InitialComboValue == "(script expression)" + || comboValue == "(script expression)") + m_endEdit = true; + + m_InitialComboValue = comboValue; + } + + virtual bool endEdit() + { + bool bTemp = m_endEdit; + m_endEdit = false; + return bTemp; + } + + virtual CRegisteredString GetId() const { return m_Data.m_Id; } + virtual void RemoveIdFromContext() { m_Editor.GetStateContext().EraseId(m_Data.m_Id); } + virtual void AddIdToContext() + { + if (m_Data.m_Id.IsValid()) + m_Editor.GetStateContext().InsertId(m_Data.m_Id, &m_Data); + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual void *GetWrappedObject() { return &m_Data; } + + struct SObjListContains + { + const TObjList &m_Targets; + SObjListContains(const TObjList &t) + : m_Targets(t) + { + } + bool operator()(const TObjPtr &inObj) const + { + return eastl::find(m_Targets.begin(), m_Targets.end(), inObj.mPtr) + != m_Targets.end(); + } + }; + + static void CheckAndRemoveStateFromTransitionProperty(TObjList &removedItems, + IEditorObject &inEditorObj, + const char8_t *propName) + { + Option<SValue> propVal = inEditorObj.GetPropertyValue(propName); + if (propVal.hasValue()) { + if (propVal->getType() == ValueTypes::ObjPtr) { + TObjPtr transProp = propVal->getData<TObjPtr>(); + if (transProp) { + TObjList propValValues = + transProp->GetPropertyValue("target")->getData<TObjList>(); + TObjList::iterator lastIter = + eastl::remove_if(propValValues.begin(), propValValues.end(), + SObjListContains(removedItems)); + if (lastIter != propValValues.end()) { + propValValues.erase(lastIter, propValValues.end()); + if (propValValues.empty()) + inEditorObj.SetPropertyValue(propName, TObjPtr()); + else + transProp->SetPropertyValue("target", propValValues); + } + } + } else if (propVal->getType() == ValueTypes::String) { + TEditorStr theIdStr = propVal->getData<TEditorStr>(); + for (size_t idx = 0, end = removedItems.size(); idx < end; ++idx) { + if (removedItems[idx]->GetId() == theIdStr) { + inEditorObj.SetPropertyValue(propName, TEditorStr()); + break; + } + } + } + } + } + + static void RemoveTransitionsPointingTo(TObjList &removedItems, + IEditorObject &inEditorObj) + { + if (AreEqual(inEditorObj.TypeName(), "transition")) { + TObjList targets = inEditorObj.GetPropertyValue("target")->getData<TObjList>(); + TObjList::iterator lastIter = eastl::remove_if(targets.begin(), targets.end(), + SObjListContains(removedItems)); + if (lastIter != targets.end()) { + targets.erase(lastIter, targets.end()); + inEditorObj.SetPropertyValue("target", targets); + if (targets.size() == 0) + inEditorObj.RemoveObjectFromGraph(); + } + } else { + // state and parallel + CheckAndRemoveStateFromTransitionProperty(removedItems, inEditorObj, "initial"); + // history + CheckAndRemoveStateFromTransitionProperty(removedItems, inEditorObj, + "transition"); + } + Option<SValue> childListOpt = inEditorObj.GetPropertyValue("children"); + if (childListOpt.hasValue()) { + TObjList childList = childListOpt->getData<TObjList>(); + for (size_t idx = 0, end = childList.size(); idx < end; ++idx) + RemoveTransitionsPointingTo(removedItems, *childList[idx]); + } + } + static void RemoveObjectFromGraph(IEditorObject &inEditorObj, + TObjList &outRemovedObjects) + { + TObjPtr parentPtr = inEditorObj.Parent(); + outRemovedObjects.push_back(&inEditorObj); + if (parentPtr) + parentPtr->Remove("children", &inEditorObj); + + Option<SValue> nodeChildrenOpt = inEditorObj.GetPropertyValue("children"); + if (nodeChildrenOpt.hasValue()) { + TObjList nodeChildren(nodeChildrenOpt->getData<TObjList>()); + for (size_t idx = 0, end = nodeChildren.size(); idx < end; ++idx) + RemoveObjectFromGraph(*nodeChildren[idx], outRemovedObjects); + } + } + + virtual TObjPtr Parent() { return m_Editor.ToEditor(m_Data.m_Parent); } + + // Simple and slow as hell. + static void RemoveObjectFromGraph(SEditorImplObject &inEditorObj) + { + TObjList removedItems; + TObjPtr oldParent = inEditorObj.Parent(); + RemoveObjectFromGraph(inEditorObj, removedItems); + RemoveTransitionsPointingTo(removedItems, *inEditorObj.m_Editor.GetRoot()); + for (size_t idx = 0, end = removedItems.size(); idx < end; ++idx) { + inEditorObj.m_Editor.m_TransactionManager->OnObjectDeleted(removedItems[idx]); + inEditorObj.RemoveIdFromContext(); + } + if (oldParent) { + TObjList children = + oldParent->GetPropertyValue("children")->getData<TObjList>(); + for (size_t childIdx = 0, childEnd = children.size(); childIdx < childEnd; + ++childIdx) { + inEditorObj.m_Editor.CheckAndSetValidHistoryDefault(children[childIdx]); + } + } + } + + virtual void RemoveObjectFromGraph() { RemoveObjectFromGraph(*this); } + + virtual TEditorStr GetId() { return m_Data.m_Id.c_str(); } + + virtual NVConstDataRef<InterpreterEventTypes::Enum> GetExecutableContentTypes() + { + static InterpreterEventTypes::Enum retval[] = { InterpreterEventTypes::StateEnter, + InterpreterEventTypes::StateExit }; + return toConstDataRef(retval, 2); + } + + template <typename TListType> + static TObjList ListDataToEditor(TListType &inList, SEditorImplObject &ioObj) + { + TObjList retval; + typename TListType::iterator iter = inList.begin(); + // Take the first one. + if (iter != inList.end()) { + for (TExecutableContentList::iterator + contentIter = iter->m_ExecutableContent.begin(), + contentEnd = iter->m_ExecutableContent.end(); + contentIter != contentEnd; ++contentIter) { + TObjPtr editorData = ioObj.m_Editor.ExecutableContentToEditor(*contentIter); + retval.push_back(editorData); + } + } + return retval; + } + + static TObjList ListDataToEditor(InterpreterEventTypes::Enum inType, + SEntryExitBase &inItem, SEditorImplObject &ioObj) + { + switch (inType) { + case InterpreterEventTypes::StateEnter: + return ListDataToEditor(inItem.m_OnEntry, ioObj); + case InterpreterEventTypes::StateExit: + return ListDataToEditor(inItem.m_OnExit, ioObj); + default: + break; + } + QT3DS_ASSERT(false); + return TObjList(); + } + + virtual TObjList GetExecutableContent(InterpreterEventTypes::Enum inType) + { + return SStateEditor::ListDataToEditor(inType, m_Data, *this); + } + + template <typename TListItemType, typename TListType> + static TObjPtr DoCreateAppendExecutableContent(SEditorImplObject &inObj, + TListType &inList, const char8_t *inName, + SStateNode &inNode) + { + NVScopedRefCounted<IChange> theNewListItemChange; + if (inList.empty()) { + TListItemType *newType = + QT3DS_NEW(inObj.m_Editor.m_AutoAllocator, TListItemType)(); + inList.push_back(*newType); + theNewListItemChange = new TListInsertDeleteChange<TListType, TListItemType>( + inObj, &inList, newType, true); + } + TListItemType &theFront = *inList.begin(); + TObjPtr retval = inObj.m_Editor.CreateExecutableContent(inNode, inName); + SExecutableContent *theContent = inObj.m_Editor.ExecutableContentFromEditor(retval); + theFront.m_ExecutableContent.push_back(*theContent); + if (inObj.m_Editor.GetOpenTransactionImpl()) { + if (theNewListItemChange) + inObj.m_Editor.GetOpenTransactionImpl()->m_Changes.push_back( + theNewListItemChange); + + NVScopedRefCounted<SExecutableContentChange> newChange = + new SExecutableContentChange(inObj, &theFront.m_ExecutableContent, + theContent, true); + inObj.m_Editor.GetOpenTransactionImpl()->m_Changes.push_back(newChange.mPtr); + } + return retval; + } + + static TObjPtr DoCreateAppendExecutableContent(InterpreterEventTypes::Enum inType, + const char8_t *inName, + SEditorImplObject &inObj, + SEntryExitBase &inItem) + { + switch (inType) { + case InterpreterEventTypes::StateEnter: + return DoCreateAppendExecutableContent<SOnEntry>(inObj, inItem.m_OnEntry, + inName, inItem); + case InterpreterEventTypes::StateExit: + return DoCreateAppendExecutableContent<SOnExit>(inObj, inItem.m_OnExit, inName, + inItem); + default: + break; + } + + QT3DS_ASSERT(false); + return TObjPtr(); + } + + virtual TObjPtr CreateAndAppendExecutableContent(InterpreterEventTypes::Enum inType, + const char8_t *inName) + { + return SStateEditor::DoCreateAppendExecutableContent(inType, inName, *this, m_Data); + } + }; + + struct SParallelEditor : public SPositionalEditor + { + typedef SParallel TStateType; + enum { EditorType = EditorTypes::Parallel }; + SParallel &m_Data; + static const char8_t *GetTypeStr() { return "parallel"; } + + static TPropertyAccessorList CreateAccessors(SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + + typedef SInitialTargetProp<SParallelEditor, SParallel> TInitialTargetProp; + TPropertyAccessorList &retval(inList); + typedef SDataIdProp<SParallelEditor, SParallel> TIdPropType; + typedef SParentProp<SParallelEditor> TParallelParentProp; + NVAllocatorCallback &alloc(inEditorData.m_EditorFoundation->getAllocator()); + retval.push_back(QT3DS_NEW(alloc, TIdPropType)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("id"), EditorPropertyTypes::Id), + &SParallel::m_Id)); + + retval.push_back(QT3DS_NEW(alloc, TParallelParentProp)(inEditorData.m_EditorFoundation, + *inEditorData.m_StringTable)); + + SEditorImplObject::CreateAccessors(inEditorData, retval, false, false); + retval.push_back(CreateDataAccessor<SParallelEditor>( + inEditorData, &SState::m_DataModel, "datamodel", EditorPropertyTypes::Object)); + SStateEditor::CreateStateNodeChildren<SParallelEditor>(inEditorData, retval); + SStateEditor::CreatePositionColorAccessors<SParallelEditor>(inEditorData, retval); + // Replace the name property with one that writes to our data + retval.push_back(QT3DS_NEW(alloc, TInitialTargetProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("is initial target"), + EditorPropertyTypes::Boolean))); + return retval; + } + + SParallelEditor(SParallel &inData, SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + : SPositionalEditor(GetTypeStr(), inEditorData, + CreateAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + virtual CRegisteredString GetId() const { return m_Data.m_Id; } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual void *GetWrappedObject() { return &m_Data; } + + virtual TObjPtr Parent() { return m_Editor.ToEditor(m_Data.m_Parent); } + + virtual void RemoveObjectFromGraph() { SStateEditor::RemoveObjectFromGraph(*this); } + + virtual TEditorStr GetId() { return m_Data.m_Id.c_str(); } + virtual void RemoveIdFromContext() { m_Editor.GetStateContext().EraseId(m_Data.m_Id); } + + virtual void AddIdToContext() + { + if (m_Data.m_Id.IsValid()) + m_Editor.GetStateContext().InsertId(m_Data.m_Id, &m_Data); + } + + virtual NVConstDataRef<InterpreterEventTypes::Enum> GetExecutableContentTypes() + { + static InterpreterEventTypes::Enum retval[] = { InterpreterEventTypes::StateEnter, + InterpreterEventTypes::StateExit }; + return toConstDataRef(retval, 2); + } + + virtual TObjList GetExecutableContent(InterpreterEventTypes::Enum inType) + { + return SStateEditor::ListDataToEditor(inType, m_Data, *this); + } + + virtual TObjPtr CreateAndAppendExecutableContent(InterpreterEventTypes::Enum inType, + const char8_t *inName) + { + return SStateEditor::DoCreateAppendExecutableContent(inType, inName, *this, m_Data); + } + }; + + struct SFinalEditor : public SPositionalEditor + { + typedef SFinal TStateType; + enum { EditorType = EditorTypes::Final }; + + SFinal &m_Data; + static const char8_t *GetTypeStr() { return "final"; } + + static TPropertyAccessorList CreateAccessors(SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + typedef SDataIdProp<SFinalEditor, SFinal> TIdPropType; + typedef SInitialTargetProp<SFinalEditor, SFinal> TInitialTargetProp; + NVAllocatorCallback &alloc(inEditorData.m_EditorFoundation->getAllocator()); + typedef SParentProp<SFinalEditor> TFinalParentProp; + retval.push_back(QT3DS_NEW(alloc, TIdPropType)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("id"), EditorPropertyTypes::Id), + &SFinal::m_Id)); + retval.push_back(QT3DS_NEW(alloc, TFinalParentProp)(inEditorData.m_EditorFoundation, + *inEditorData.m_StringTable)); + SEditorImplObject::CreateAccessors(inEditorData, retval, false, false); + SStateEditor::CreatePositionColorAccessors<SFinalEditor>(inEditorData, retval); + retval.push_back(QT3DS_NEW(alloc, TInitialTargetProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("is initial target"), + EditorPropertyTypes::Boolean))); + // Replace the name property with one that writes to our data + return retval; + } + + SFinalEditor(SFinal &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SPositionalEditor(GetTypeStr(), inEditorData, + CreateAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + virtual CRegisteredString GetId() const { return m_Data.m_Id; } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual void *GetWrappedObject() { return &m_Data; } + + virtual TObjPtr Parent() { return m_Editor.ToEditor(m_Data.m_Parent); } + + virtual void RemoveObjectFromGraph() { SStateEditor::RemoveObjectFromGraph(*this); } + + virtual TEditorStr GetId() { return m_Data.m_Id.c_str(); } + virtual void RemoveIdFromContext() { m_Editor.GetStateContext().EraseId(m_Data.m_Id); } + + virtual void AddIdToContext() + { + if (m_Data.m_Id.IsValid()) + m_Editor.GetStateContext().InsertId(m_Data.m_Id, &m_Data); + } + + virtual NVConstDataRef<InterpreterEventTypes::Enum> GetExecutableContentTypes() + { + static InterpreterEventTypes::Enum retval[] = { InterpreterEventTypes::StateEnter, + InterpreterEventTypes::StateExit }; + return toConstDataRef(retval, 2); + } + + virtual TObjList GetExecutableContent(InterpreterEventTypes::Enum inType) + { + return SStateEditor::ListDataToEditor(inType, m_Data, *this); + } + + virtual TObjPtr CreateAndAppendExecutableContent(InterpreterEventTypes::Enum inType, + const char8_t *inName) + { + return SStateEditor::DoCreateAppendExecutableContent(inType, inName, *this, m_Data); + } + }; + + struct SHistoryEditor : public SPositionalEditor + { + typedef SHistory TStateType; + enum { EditorType = EditorTypes::History }; + SHistory &m_Data; + static const char8_t *GetTypeStr() { return "history"; } + + static TPropertyAccessorList CreateAccessors(SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + typedef SDataProp<SHistoryEditor, SHistory, STransition *> TTransitionProp; + + typedef SFlagBooleanProperty<SHistoryEditor, SHistoryFlags> THistoryFlagsProp; + typedef SDataIdProp<SHistoryEditor, SHistory> TIdPropType; + typedef SHistoryTransitionProp<SHistoryEditor> THistoryTransitionProp; + typedef SParentProp<SHistoryEditor> THistoryParentProp; + typedef SInitialTargetProp<SHistoryEditor, SHistory> TInitialTargetProp; + + TPropertyAccessorList &retval(inList); + NVAllocatorCallback &alloc(inEditorData.m_EditorFoundation->getAllocator()); + retval.push_back(QT3DS_NEW(alloc, TIdPropType)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("id"), EditorPropertyTypes::Id), + &SHistory::m_Id)); + retval.push_back(QT3DS_NEW(alloc, THistoryParentProp)(inEditorData.m_EditorFoundation, + *inEditorData.m_StringTable)); + SEditorImplObject::CreateAccessors(inEditorData, retval, false, false); + SStateEditor::CreatePositionColorAccessors<SHistoryEditor>(inEditorData, retval); + retval.push_back(QT3DS_NEW( + alloc, THistoryFlagsProp(inEditorData.m_EditorFoundation, + *inEditorData.m_StringTable, "type", "shallow", "deep", + &SHistoryFlags::IsDeep, &SHistoryFlags::SetDeep))); + + retval.push_back(QT3DS_NEW(alloc, TTransitionProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("transition"), + EditorPropertyTypes::ObjectList), + &SHistory::m_Transition)); + + retval.push_back(QT3DS_NEW(alloc, THistoryTransitionProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("default"), + EditorPropertyTypes::ObjectList))); + + retval.push_back(QT3DS_NEW(alloc, TInitialTargetProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("is initial target"), + EditorPropertyTypes::Boolean))); + + return retval; + } + + SHistoryEditor(SHistory &inData, SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + : SPositionalEditor(GetTypeStr(), inEditorData, + CreateAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + virtual CRegisteredString GetId() const { return m_Data.m_Id; } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual void *GetWrappedObject() { return &m_Data; } + + virtual void RemoveObjectFromGraph() { SStateEditor::RemoveObjectFromGraph(*this); } + + virtual TObjPtr Parent() { return m_Editor.ToEditor(m_Data.m_Parent); } + + virtual TEditorStr GetId() { return m_Data.m_Id.c_str(); } + virtual void RemoveIdFromContext() { m_Editor.GetStateContext().EraseId(m_Data.m_Id); } + + virtual void AddIdToContext() + { + if (m_Data.m_Id.IsValid()) + m_Editor.GetStateContext().InsertId(m_Data.m_Id, &m_Data); + } + }; + + struct STransitionEditor : public SEditorImplObject + { + typedef STransition TStateType; + STransition &m_Data; + enum { EditorType = EditorTypes::Transition }; + static const char8_t *GetTypeStr() { return "transition"; } + TVec2List m_PathList; + + static TPropertyAccessorList CreateAccessors(SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + typedef SDataProp<STransitionEditor, STransition, const char8_t *> TTransCharProp; + typedef SDataProp<STransitionEditor, STransition, CRegisteredString> + TTransRegStrProp; + typedef SDataProp<STransitionEditor, STransition, NVConstDataRef<SStateNode *>> + TTransTargetProp; + typedef SDataProp<STransitionEditor, STransition, NVConstDataRef<QT3DSVec2>> + TTransPathProp; + typedef SFlagBooleanProperty<STransitionEditor, STransitionFlags> + TTransitionFlagsProp; + typedef SDataIdProp<STransitionEditor, STransition> TIdPropType; + typedef SOptionAccessorProp<STransitionEditor, STransition, QT3DSVec2> TVec2Access; + typedef SOptionAccessorProp<STransitionEditor, STransition, QT3DSVec3> TVec3Access; + typedef SDataIdProp<STransitionEditor, STransition> TIdPropType; + + TPropertyAccessorList &retval(inList); + NVAllocatorCallback &alloc(inEditorData.m_EditorFoundation->getAllocator()); + + retval.push_back( + QT3DS_NEW(alloc, TIdPropType)(inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("id"), + EditorPropertyTypes::String), + &STransition::m_Id)); + + retval.push_back(QT3DS_NEW(alloc, TTransRegStrProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("event"), + EditorPropertyTypes::String), + &STransition::m_Event)); + + SEditorImplObject::CreateAccessors(inEditorData, retval, false, false); + IPropertyAccessor *accessor = QT3DS_NEW(alloc, TTransCharProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("cond"), + EditorPropertyTypes::BigString), + &STransition::m_Condition); + + retval.push_back(accessor); + accessor = QT3DS_NEW(alloc, TTransTargetProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("target"), + EditorPropertyTypes::ObjectList), + &STransition::m_Target); + retval.push_back(accessor); + retval.push_back(QT3DS_NEW(alloc, TTransitionFlagsProp)( + inEditorData.m_EditorFoundation, *inEditorData.m_StringTable, "type", + "external", "internal", &STransitionFlags::IsInternal, + &STransitionFlags::SetInternal)); + + accessor = QT3DS_NEW(alloc, TTransPathProp)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("path"), + EditorPropertyTypes::PositionList), + &STransition::m_Path); + retval.push_back(accessor); + retval.push_back(QT3DS_NEW(alloc, TVec2Access)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("position"), + EditorPropertyTypes::PositionList), + &STransition::GetPosition, &STransition::SetPosition)); + + retval.push_back(QT3DS_NEW(alloc, TVec2Access)( + inEditorData.m_EditorFoundation, + SPropertyDeclaration(inEditorData.RegisterStr("end position"), + EditorPropertyTypes::PositionList), + &STransition::GetEndPosition, &STransition::SetEndPosition)); + + return retval; + } + + STransitionEditor(STransition &inData, SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + : SEditorImplObject(GetTypeStr(), inEditorData, + CreateAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual void *GetWrappedObject() { return &m_Data; } + + virtual void RemoveObjectFromGraph() { SStateEditor::RemoveObjectFromGraph(*this); } + + virtual TEditorStr GetId() { return m_Data.m_Id.c_str(); } + virtual void RemoveIdFromContext() { m_Editor.GetStateContext().EraseId(m_Data.m_Id); } + + virtual void AddIdToContext() + { + if (m_Data.m_Id.IsValid()) + m_Editor.GetStateContext().InsertId(m_Data.m_Id, &m_Data); + } + + virtual TObjPtr Parent() { return m_Editor.ToEditor(m_Data.m_Parent); } + + virtual NVConstDataRef<InterpreterEventTypes::Enum> GetExecutableContentTypes() + { + static InterpreterEventTypes::Enum data[] = { InterpreterEventTypes::Transition }; + return toConstDataRef(data, 1); + } + + virtual TObjList GetExecutableContent(InterpreterEventTypes::Enum inType) + { + TObjList retval; + if (inType == InterpreterEventTypes::Transition) { + for (TExecutableContentList::iterator iter = m_Data.m_ExecutableContent.begin(), + end = m_Data.m_ExecutableContent.end(); + iter != end; ++iter) { + retval.push_back(m_Editor.ExecutableContentToEditor(*iter)); + } + } + return retval; + } + virtual TObjPtr CreateAndAppendExecutableContent(InterpreterEventTypes::Enum inType, + const char8_t *inName) + { + TObjPtr retval; + if (inType == InterpreterEventTypes::Transition) { + retval = m_Editor.CreateExecutableContent(m_Data, inName); + SExecutableContent *theContent = m_Editor.ExecutableContentFromEditor(retval); + m_Data.m_ExecutableContent.push_back(*theContent); + if (m_Editor.GetOpenTransactionImpl()) { + NVScopedRefCounted<SExecutableContentChange> newChange = + new SExecutableContentChange(*this, &m_Data.m_ExecutableContent, + theContent, true); + m_Editor.GetOpenTransactionImpl()->m_Changes.push_back(newChange.mPtr); + } + } + return retval; + } + }; + + struct SExecutableContentParentInfo + { + SExecutableContent &content; + SEditorImpl &m_Editor; + TExecutableContentList *parentList; + TObjPtr editorObj; + TOnEntryList *entryList; + SOnEntry *entryItem; + TOnExitList *exitList; + SOnExit *exitItem; + SExecutableContentParentInfo(SExecutableContent &c, SEditorImpl &e) + : content(c) + , m_Editor(e) + , parentList(NULL) + , entryList(NULL) + , entryItem(NULL) + , exitList(NULL) + , exitItem(NULL) + { + if (GetContent().m_StateNodeParent) { + editorObj = m_Editor.ToEditor(*GetContent().m_StateNodeParent); + if (GetContent().m_StateNodeParent->m_Type == StateNodeTypes::Transition) + parentList = &static_cast<STransition *>(GetContent().m_StateNodeParent) + ->m_ExecutableContent; + else { + SExecutableContent *targetContent = &GetContent(); + entryList = GetContent().m_StateNodeParent->GetOnEntryList(); + exitList = GetContent().m_StateNodeParent->GetOnExitList(); + if (entryList) { + for (TOnEntryList::iterator iter = entryList->begin(), + end = entryList->end(); + iter != end && parentList == NULL; ++iter) { + for (TExecutableContentList::iterator + contentIter = iter->m_ExecutableContent.begin(), + contentEnd = iter->m_ExecutableContent.end(); + contentIter != contentEnd && parentList == NULL; + ++contentIter) { + if (&(*contentIter) == targetContent) { + parentList = &iter->m_ExecutableContent; + exitList = NULL; + entryItem = &(*iter); + } + } + } + } + if (parentList == NULL && exitList != NULL) { + for (TOnExitList::iterator iter = exitList->begin(), + end = exitList->end(); + iter != end && parentList == NULL; ++iter) { + for (TExecutableContentList::iterator + contentIter = iter->m_ExecutableContent.begin(), + contentEnd = iter->m_ExecutableContent.end(); + contentIter != contentEnd && parentList == NULL; + ++contentIter) { + if (&(*contentIter) == targetContent) { + parentList = &iter->m_ExecutableContent; + entryList = NULL; + exitItem = &(*iter); + } + } + } + } + } + } else { + editorObj = m_Editor.ToEditor(GetContent().m_Parent); + parentList = &GetContent().m_Parent->m_Children; + } + } + SExecutableContent &GetContent() { return content; } + }; + + struct SExecutableContentEditor : public SEditorImplObject + { + SExecutableContentEditor(const char8_t *inTypeStr, SEditorImpl &inEditorData, + const TPropertyAccessorList &inList) + : SEditorImplObject(inTypeStr, inEditorData, inList) + { + } + + virtual SExecutableContent &GetContent() = 0; + + virtual void RemoveObjectFromGraph() + { + SExecutableContentParentInfo parentInfo(GetContent(), m_Editor); + TExecutableContentList *parentList = parentInfo.parentList; + TObjPtr editorObj = parentInfo.editorObj; + TOnEntryList *entryList = parentInfo.entryList; + SOnEntry *entryItem = parentInfo.entryItem; + TOnExitList *exitList = parentInfo.exitList; + SOnExit *exitItem = parentInfo.exitItem; + + if (parentList) { + NVScopedRefCounted<SExecutableContentChange> change = + new SExecutableContentChange(editorObj, parentList, &GetContent(), false); + change->Do(); + if (m_Editor.GetOpenTransactionImpl()) + m_Editor.GetOpenTransactionImpl()->m_Changes.push_back(change.mPtr); + // Perhaps remove the item itself + if (parentList->empty()) { + NVScopedRefCounted<IChange> newChange; + if (entryItem) + newChange = new TListInsertDeleteChange<TOnEntryList, SOnEntry>( + editorObj, entryList, entryItem, false); + else if (exitItem) + newChange = new TListInsertDeleteChange<TOnExitList, SOnExit>( + editorObj, exitList, exitItem, false); + + if (newChange) { + newChange->Do(); + if (m_Editor.GetOpenTransactionImpl()) + m_Editor.GetOpenTransactionImpl()->m_Changes.push_back(newChange); + } + } + } + } + }; + + struct SSendEditor : public SExecutableContentEditor + { + typedef SSend TStateType; + enum { EditorType = EditorTypes::Send }; + SSend &m_Data; + static const char8_t *GetTypeStr() { return "send"; } + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + typedef SDataIdProp<SSendEditor, SSend> TIdPropType; + + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + retval.push_back(CreateDataAccessor<SSendEditor>(inData, &SSend::m_Event, "event", + EditorPropertyTypes::String)); + NVFoundationBase &fnd = inData.m_EditorFoundation->getFoundation(); + + retval.push_back(QT3DS_NEW(fnd.getAllocator(), TIdPropType)( + inData.m_EditorFoundation, + SPropertyDeclaration(inData.RegisterStr("id"), EditorPropertyTypes::Id), + &SSend::m_Id)); + + retval.push_back(QT3DS_NEW(fnd.getAllocator(), SDelayProp<SSendEditor>)( + inData.m_EditorFoundation, + SPropertyDeclaration(inData.RegisterStr("delay"), EditorPropertyTypes::U32))); + return retval; + } + SSendEditor(SSend &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SRaiseEditor : public SExecutableContentEditor + { + typedef SRaise TStateType; + enum { EditorType = EditorTypes::Raise }; + SRaise &m_Data; + static const char8_t *GetTypeStr() { return "raise"; } + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + retval.push_back(CreateDataAccessor<SRaiseEditor>(inData, &SRaise::m_Event, "event", + EditorPropertyTypes::String)); + return retval; + } + SRaiseEditor(SRaise &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SLogEditor : public SExecutableContentEditor + { + typedef SLog TStateType; + enum { EditorType = EditorTypes::Log }; + SLog &m_Data; + static const char8_t *GetTypeStr() { return "log"; } + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + retval.push_back(CreateDataAccessor<SLogEditor>(inData, &SLog::m_Label, "label", + EditorPropertyTypes::String)); + retval.push_back(CreateDataAccessor<SLogEditor>(inData, &SLog::m_Expression, "expr", + EditorPropertyTypes::String)); + return retval; + } + SLogEditor(SLog &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SAssignEditor : public SExecutableContentEditor + { + typedef SAssign TStateType; + enum { EditorType = EditorTypes::Assign }; + SAssign &m_Data; + static const char8_t *GetTypeStr() { return "assign"; } + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + retval.push_back(CreateDataAccessor<SAssignEditor>( + inData, &SAssign::m_Location, "location", EditorPropertyTypes::String)); + retval.push_back(CreateDataAccessor<SAssignEditor>( + inData, &SAssign::m_Expression, "expr", EditorPropertyTypes::String)); + return retval; + } + SAssignEditor(SAssign &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SIfEditor : public SExecutableContentEditor + { + typedef SIf TStateType; + enum { EditorType = EditorTypes::If }; + SIf &m_Data; + static const char8_t *GetTypeStr() { return "if"; } + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + retval.push_back(CreateDataAccessor<SIfEditor>(inData, &SIf::m_Cond, "cond", + EditorPropertyTypes::String)); + return retval; + } + + SIfEditor(SIf &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SElseIfEditor : public SExecutableContentEditor + { + typedef SElseIf TStateType; + enum { EditorType = EditorTypes::ElseIf }; + SElseIf &m_Data; + static const char8_t *GetTypeStr() { return "elseif"; } + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + retval.push_back(CreateDataAccessor<SElseIfEditor>(inData, &SElseIf::m_Cond, "cond", + EditorPropertyTypes::String)); + return retval; + } + + SElseIfEditor(SElseIf &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SElseEditor : public SExecutableContentEditor + { + typedef SElse TStateType; + enum { EditorType = EditorTypes::Else }; + SElse &m_Data; + static const char8_t *GetTypeStr() { return "else"; } + + SElseEditor(SElse &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, inList) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SScriptEditor : public SExecutableContentEditor + { + typedef SScript TStateType; + enum { EditorType = EditorTypes::Script }; + SScript &m_Data; + static const char8_t *GetTypeStr() { return "script"; } + + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + retval.push_back(CreateDataAccessor<SScriptEditor>( + inData, &SScript::m_Data, "content", EditorPropertyTypes::BigString)); + return retval; + } + + SScriptEditor(SScript &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SCancelEditor : public SExecutableContentEditor + { + typedef SData TStateType; + enum { EditorType = EditorTypes::Cancel }; + SCancel &m_Data; + static const char8_t *GetTypeStr() { return "cancel"; } + + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList retval; + retval.push_back(CreateDataAccessor<SCancelEditor>( + inData, &SCancel::m_Send, "sendid", EditorPropertyTypes::Object)); + retval.push_back(CreateDataAccessor<SCancelEditor>( + inData, &SCancel::m_IdExpression, "sendidexpr", EditorPropertyTypes::String)); + return retval; + } + + SCancelEditor(SCancel &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SExecutableContentEditor(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual SExecutableContent &GetContent() { return m_Data; } + virtual void *GetWrappedObject() { return &m_Data; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SDataModelEditor : public SEditorImplObject + { + typedef SDataModel TStateType; + enum { EditorType = EditorTypes::DataModel }; + SDataModel &m_Data; + static const char8_t *GetTypeStr() { return "datamodel"; } + + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList &retval(inList); + retval.push_back(CreateDataAccessor<SDataModelEditor>( + inData, &SDataModel::m_Data, "data", EditorPropertyTypes::ObjectList)); + return retval; + } + + SDataModelEditor(SDataModel &inData, SEditorImpl &inEditorData, + TPropertyAccessorList &inList) + : SEditorImplObject(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual void *GetWrappedObject() { return &m_Data; } + + virtual CRegisteredString GetId() const { return m_Data.m_Id; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SDataEditor : public SEditorImplObject + { + typedef SData TStateType; + enum { EditorType = EditorTypes::Data }; + SData &m_Data; + static const char8_t *GetTypeStr() { return "data"; } + static TPropertyAccessorList GetPropertyAccessors(SEditorImpl &inData, + TPropertyAccessorList &inList) + { + if (inList.size()) + return inList; + TPropertyAccessorList retval; + retval.push_back(CreateDataAccessor<SDataEditor>(inData, &SData::m_Id, "id", + EditorPropertyTypes::String)); + retval.push_back(CreateDataAccessor<SDataEditor>( + inData, &SData::m_Expression, "expr", EditorPropertyTypes::String)); + return retval; + } + + SDataEditor(SData &inData, SEditorImpl &inEditorData, TPropertyAccessorList &inList) + : SEditorImplObject(GetTypeStr(), inEditorData, + GetPropertyAccessors(inEditorData, inList)) + , m_Data(inData) + { + } + + QT3DS_STATE_EDITOR_OBJECT_IMPLEMENT_ADDREF_RELEASE; + + virtual void *GetWrappedObject() { return &m_Data; } + + virtual CRegisteredString GetId() const { return m_Data.m_Id; } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + }; + + struct SNullEditor + { + int m_Data; + static const char8_t *GetTypeStr() { return ""; } + }; + + template <typename TStateType> + struct SStateEditorMap + { + typedef SNullEditor TEditorType; + template <typename TIgnored> + static TObjPtr CreateEditor(TIgnored &, SEditorImpl &, TAccessorMap &) + { + return TObjPtr(); + } + }; + + template <typename TEditorType> + struct SEditorImplStateMap + { + typedef int TStateType; + }; + + static inline TPropertyAccessorList &GetAccessorList(TAccessorMap &inMap, int inAccessor) + { + return inMap.insert(eastl::make_pair(inAccessor, TPropertyAccessorList())) + .first->second; + } + + template <> + struct SStateEditorMap<STransition> + { + typedef STransition stateType; + typedef STransitionEditor TEditorType; + typedef STransitionEditor editorType; + static TObjPtr CreateEditor(stateType &inData, SEditorImpl &inEditorData, + TAccessorMap &inAccessors) + { + return QT3DS_NEW(inEditorData.m_EditorFoundation->getAllocator(), TEditorType)( + inData, inEditorData, + GetAccessorList(inAccessors, (int)editorType::EditorType)); + } + static TObjPtr CreateEditor(SEditorImpl &inEditorData, TAccessorMap &inAccessors) + { + STransition *newTransition = QT3DS_NEW(inEditorData.m_AutoAllocator, stateType)(); + newTransition->m_Flags.SetInternal(true); + return CreateEditor(*newTransition, inEditorData, inAccessors); + } + }; + template <> + struct SEditorImplStateMap<STransitionEditor> + { + typedef STransition TStateType; + }; + +#define DEFINE_STATE_EDITOR_TYPE_MAP(stateType, editorType) \ + template <> \ + struct SStateEditorMap<stateType> \ + { \ + typedef editorType TEditorType; \ + static TObjPtr CreateEditor(stateType &inData, SEditorImpl &inEditorData, \ + TAccessorMap &inAccessors) \ + { \ + return QT3DS_NEW(inEditorData.m_EditorFoundation->getAllocator(), TEditorType)( \ + inData, inEditorData, GetAccessorList(inAccessors, (int)editorType::EditorType)); \ + } \ + static TObjPtr CreateEditor(SEditorImpl &inEditorData, TAccessorMap &inAccessors) \ + { \ + return CreateEditor(*QT3DS_NEW(inEditorData.m_AutoAllocator, stateType)(), inEditorData, \ + inAccessors); \ + } \ + }; \ + template <> \ + struct SEditorImplStateMap<editorType> \ + { \ + typedef stateType TStateType; \ + }; + + DEFINE_STATE_EDITOR_TYPE_MAP(SSCXML, SSCXMLEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SState, SStateEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SParallel, SParallelEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SHistory, SHistoryEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SFinal, SFinalEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SSend, SSendEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SRaise, SRaiseEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SLog, SLogEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SAssign, SAssignEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SIf, SIfEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SElseIf, SElseIfEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SElse, SElseEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SScript, SScriptEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SDataModel, SDataModelEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SData, SDataEditor); + DEFINE_STATE_EDITOR_TYPE_MAP(SCancel, SCancelEditor); + +#undef DEFINE_STATE_EDITOR_TYPE_MAP + } +} +} +#endif diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorFoundation.h b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorFoundation.h new file mode 100644 index 00000000..edb55ee3 --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorFoundation.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EDITOR_FOUNDATION_H +#define QT3DS_STATE_EDITOR_FOUNDATION_H +#pragma once + +#include "Qt3DSState.h" +#include "foundation/Qt3DSAtomic.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/AutoDeallocatorAllocator.h" +#include "foundation/TrackingAllocator.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/IOStreams.h" + +namespace qt3ds { +namespace state { + namespace editor { + + struct SBaseEditorFoundation + { + static MallocAllocator g_BaseAlloc; + QT3DSI32 mRefCount; + CAllocator m_BaseAllocator; + NVScopedRefCounted<NVFoundation> m_Foundation; + SBaseEditorFoundation() + : mRefCount(0) + , m_BaseAllocator() + , m_Foundation(NVCreateFoundation(QT3DS_FOUNDATION_VERSION, m_BaseAllocator)) + { + } + + ~SBaseEditorFoundation() {} + + NVFoundationBase &getFoundation() { return *m_Foundation; } + NVAllocatorCallback &getAllocator() { return m_Foundation->getAllocator(); } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(g_BaseAlloc); + + static SBaseEditorFoundation &Create() + { + SBaseEditorFoundation *fndPtr = + (SBaseEditorFoundation *)malloc(sizeof(SBaseEditorFoundation)); + new (fndPtr) SBaseEditorFoundation(); + return *fndPtr; + } + }; + + typedef NVScopedRefCounted<SBaseEditorFoundation> TFoundationPtr; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorImpl.h b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorImpl.h new file mode 100644 index 00000000..c1a691ee --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorImpl.h @@ -0,0 +1,406 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EDITOR_IMPL_H +#define QT3DS_STATE_EDITOR_IMPL_H +#include "Qt3DSStateEditor.h" +#include "Qt3DSStateEditorValue.h" +#include "Qt3DSStateTypes.h" +#include "Qt3DSStateEditorFoundation.h" +#include "foundation/Utils.h" +#include "foundation/XML.h" +#include "Qt3DSStateXMLIO.h" +#include "foundation/IOStreams.h" +#include "Qt3DSStateExecutionTypes.h" +#include "Qt3DSStateContext.h" + +namespace qt3ds { +namespace state { + namespace editor { + + struct SEditorImpl; + + typedef nvhash_map<void *, TObjPtr> TStateEditorMap; + + class IPropertyAccessor : public NVRefCounted + { + protected: + virtual ~IPropertyAccessor() {} + public: + SPropertyDeclaration m_Declaration; + IPropertyAccessor(const SPropertyDeclaration &inDec) + : m_Declaration(inDec) + { + } + virtual eastl::vector<CRegisteredString> GetLegalValues(IEditorObject & /*inObj*/) + { + return eastl::vector<CRegisteredString>(); + } + + virtual Option<SValue> Get(IEditorObject &inObj) = 0; + virtual void Set(IEditorObject &inObj, const Option<SValue> &inValue) = 0; + // Return true if this access handles the transaction code itself. + // Currently only used for the is initial target property. + virtual bool HandlesTransaction() { return false; } + }; + + typedef NVScopedRefCounted<IPropertyAccessor> TPropertyAccessorPtr; + typedef eastl::vector<TPropertyAccessorPtr> TPropertyAccessorList; + typedef nvhash_map<int, TPropertyAccessorList> TAccessorMap; + +#define QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(foundationPtr, refcountVar) \ + void addRef() { atomicIncrement(&(refcountVar)); } \ + void release() \ + { \ + TFoundationPtr temp(foundationPtr); \ + QT3DSI32 value = atomicDecrement(&(refcountVar)); \ + if (value <= 0) \ + NVDelete(foundationPtr->getAllocator(), this); \ + } + + struct SEditorImplStrIOStream : public IOutStream + { + TEditorStr m_Str; + bool Write(NVConstDataRef<QT3DSU8> data) + { + if (data.size()) + m_Str.append((const char8_t *)data.begin(), (const char8_t *)data.end()); + return true; + } + }; + + struct SEditorImplStrInStream : public IInStream + { + const char8_t *m_Pos; + const char8_t *m_End; + SEditorImplStrInStream(const TEditorStr &inStr) + : m_Pos(inStr.data()) + , m_End(inStr.data() + inStr.size()) + { + } + + virtual QT3DSU32 Read(NVDataRef<QT3DSU8> data) + { + QT3DSU32 amountLeft = (QT3DSU32)(m_End - m_Pos); + QT3DSU32 amountToRead = NVMin(amountLeft, data.size()); + memCopy(data.mData, m_Pos, amountToRead); + m_Pos += amountToRead; + return amountToRead; + } + }; + + struct STransaction; + + typedef eastl::vector<IEditorChangeListener *> TChangeListenerList; + struct STransactionManagerImpl; + + class IEditorCopyPasteListener + { + protected: + virtual ~IEditorCopyPasteListener() {} + + public: + virtual void OnCopy(TEditorPtr inEditor, eastl::vector<SStateNode *> &ioCopiedRoots, + IDOMWriter &ioWriter, + eastl::vector<SNamespacePair> &ioNamespaces) = 0; + virtual void OnPaste(TEditorPtr inEditor, IDOMReader &ioReader, + CXMLIO::TIdRemapMap &inStateIdRemapMap) = 0; + virtual void OnIDChange(TEditorPtr inEditor, SStateNode &inNode, + const char8_t *inOldId) = 0; + }; + + // Forward declaration of the editor interface that the properties and the editor objects + // can all use. Implementation in UICStateEditor.cpp + struct SEditorImpl : public IEditor + { + // Note that *we* keep references to our editors + // but our editor objects *cannot* keep a reference to us. + // This means that our editor objects need to be able to function (not crash) if we + // ourselves go out of scope. + TFoundationPtr m_EditorFoundation; + NVScopedRefCounted<IStringTable> m_StringTable; + SSAutoDeallocatorAllocator m_AutoAllocator; + NVScopedRefCounted<IStateContext> m_StateContext; + TStateEditorMap m_Editors; + volatile QT3DSI32 mRefCount; + NVScopedRefCounted<STransactionManagerImpl> m_TransactionManager; + TAccessorMap m_Accessors; + IEditorCopyPasteListener *m_CopyPasteListener; + + SEditorImpl(TFoundationPtr inFoundation, + NVScopedRefCounted<IStringTable> inStringTable); + void addRef(); + void release(); + + TObjPtr InsertEditor(void *inData, IEditorObject *inEditor); + + template <typename TStateType> + TObjPtr ToEditor(TStateType &inItem); + + template <typename TStateType> + TObjPtr ToEditor(TStateType *inItem); + TObjPtr ToEditor(SStateNode &inItem); + TObjPtr ExecutableContentToEditor(SExecutableContent &inItem); + + template <typename TStateType> + TStateType *FromEditor(TObjPtr inPtr); + + SStateNode *StateNodeFromEditor(TObjPtr inPtr); + + SExecutableContent *ExecutableContentFromEditor(TObjPtr inPtr); + void GenerateUniqueId(SStateNode &inNode, const char8_t *inStem); + void GenerateUniqueId(SSend &inNode, const char8_t *inStem); + + virtual TObjPtr GetRoot(); + + template <typename TStateType> + eastl::pair<TStateType *, TObjPtr> CreateEditorAndObject(); + + TObjPtr DoCreate(const char8_t *inTypeName, const char8_t *inId); + + virtual TObjPtr Create(const char8_t *inTypeName, const char8_t *inId); + + virtual TObjPtr GetOrCreate(SSCXML &inData); + virtual TObjPtr GetOrCreate(SState &inData); + virtual TObjPtr GetOrCreate(STransition &inData); + virtual TObjPtr GetOrCreate(SParallel &inData); + virtual TObjPtr GetOrCreate(SFinal &inData); + virtual TObjPtr GetOrCreate(SHistory &inData); + virtual TObjPtr GetOrCreate(SDataModel &inData); + virtual TObjPtr GetOrCreate(SData &inData); + + virtual TObjPtr GetObjectById(const char8_t *inId); + + virtual TObjPtr GetEditor(void *inGraphData); + + virtual TSignalConnectionPtr AddChangeListener(IEditorChangeListener &inListener); + + void RemoveChangeListener(IEditorChangeListener &inListener); + + virtual TTransactionPtr BeginTransaction(const TEditorStr &inName); + + virtual TTransactionPtr GetOpenTransaction(); + + STransaction *GetOpenTransactionImpl(); + + virtual void RollbackTransaction(); + + virtual void EndTransaction(); + + virtual TEditorStr Copy(TObjList inObjects, const QT3DSVec2 &inMousePos); + + virtual bool CanPaste(TObjPtr inTarget); + + void AddNewPasteObjectToTransaction(SStateNode &inNode); + + virtual void Paste(const TEditorStr &inCopiedObjects, TObjPtr inTarget, + const QT3DSVec2 &inMousePos); + + static bool IsDerivedFrom(SStateNode &child, SStateNode &parent); + + // This method should always return a value because the scxml root item + // is the parent of everyone else. + static SStateNode &GetLeastCommonAncestor(SStateNode &lhs, SStateNode &rhs); + + TObjPtr GetLeastCommonAncestor(NVConstDataRef<TObjPtr> inObjects); + + TObjList GetCancelableSendIds(); + + TObjPtr ChangeExecutableContentType(TObjPtr inContent, const char8_t *newType); + + TEditorStr ToXML(TObjPtr inContent); + + eastl::pair<TEditorStr, TObjPtr> FromXML(TObjPtr inContent, + const TEditorStr &ioEditedXML); + + // Return the set of events in use in the state machine. + TEditorStrList GetStateMachineEvents(); + + void ReleaseEditor(void *inData); + + virtual bool Save(const char8_t *inFileName); + virtual void Save(IOutStream &inStream); + + bool Load(IInStream &inStream, const char8_t *inFilename); + + ///////////////////////////////////////////////////////////////////// + // Property access helpers + ///////////////////////////////////////////////////////////////////// + void SetIdProperty(SStateNode &inNode, const SValue &inData, + CRegisteredString &inTarget); + void SetIdProperty(SSend &inNode, const SValue &inData, CRegisteredString &inTarget); + + void Set(const SValue &inData, CRegisteredString &inTarget) + { + TEditorStr theStr(inData.getData<TEditorStr>()); + inTarget = m_StringTable->RegisterStr(theStr.c_str()); + } + + void Set(const SValue &inData, const char8_t *&inTarget) + { + TEditorStr theStr(inData.getData<TEditorStr>()); + const char8_t *theData(theStr.c_str()); + + if (inTarget && *inTarget != 0) + m_AutoAllocator.deallocate(const_cast<char8_t *>(inTarget)); + + size_t len = StrLen(theData); + if (len == 0) + inTarget = NULL; + + ++len; // account for null terminate + + char8_t *newTarget = (char8_t *)m_AutoAllocator.allocate(len + 1, "graph string", + __FILE__, __LINE__); + memCopy(newTarget, theData, len); + inTarget = newTarget; + } + SValue Get(CRegisteredString &inTarget) { return inTarget.c_str(); } + SValue Get(const char8_t *inItem) { return nonNull(inItem); } + + void Set(const Option<SValue> &inData, NVConstDataRef<SStateNode *> &inTarget); + SValue Get(NVConstDataRef<SStateNode *> &inTarget); + + void Set(const Option<SValue> &inData, STransition *&inTarget); + Option<SValue> Get(STransition *&inTarget); + + void SetInitial(const TObjList &inList, STransition *&outInitialTransition); + SValue GetInitial(STransition *inInitialTransition); + + void SetInitialTarget(const Option<SValue> &inData, SStateNode &inNode); + SValue IsInitialTarget(SStateNode &inNode); + + void Set(const Option<SValue> &inData, SDataModel *&inTarget); + SValue Get(SDataModel *inTarget); + + void Set(const Option<SValue> &inData, TDataList &inTarget); + SValue Get(TDataList &inTarget); + + void Set(const Option<SValue> &inData, NVConstDataRef<QT3DSVec2> &inTarget); + SValue Get(const NVConstDataRef<QT3DSVec2> &inTarget); + + void Set(const Option<SValue> &inData, SSend *&inTarget); + SValue Get(SSend *inTarget); + + void Set(const Option<SValue> &inData, TEditorStr &inTarget) + { + inTarget.clear(); + if (inData.hasValue()) + inTarget = inData->getData<TEditorStr>(); + } + + SValue Get(const TEditorStr &inTarget) { return inTarget; } + + void Set(const Option<SValue> &inData, Option<QT3DSVec2> &inTarget) + { + if (inData.hasValue()) { + inTarget = inData->getData<QT3DSVec2>(); + } else { + inTarget = Empty(); + } + } + Option<SValue> Get(const Option<QT3DSVec2> &inTarget) + { + Option<SValue> retval; + if (inTarget.hasValue()) + retval = SValue(inTarget.getValue()); + return retval; + } + + void Set(const Option<SValue> &inData, TVec2List &inTarget) + { + if (inData.hasValue()) { + inTarget = inData->getData<TVec2List>(); + } else { + inTarget.clear(); + } + } + SValue Get(const TVec2List &inTarget) { return inTarget; } + + void Set(const Option<SValue> &inData, Option<QT3DSVec3> &inTarget) + { + if (inData.hasValue()) { + inTarget = inData->getData<QT3DSVec3>(); + } else { + inTarget = Empty(); + } + } + const Option<SValue> Get(const Option<QT3DSVec3> &inTarget) + { + Option<SValue> retval; + if (inTarget.hasValue()) + retval = SValue(inTarget.getValue()); + return retval; + } + + const char8_t *ToGraphStr(const char8_t *inStr) + { + if (isTrivial(inStr)) + return ""; + QT3DSU32 len = StrLen(inStr) + 1; + char8_t *retval = + (char8_t *)m_AutoAllocator.allocate(len, "GraphStr", __FILE__, __LINE__); + memCopy(retval, inStr, len); + return retval; + } + + SValue GetSendId(const char8_t *expression); + void SetSendId(const Option<SValue> &inData, const char8_t *&outExpression); + + TObjPtr CreateExecutableContent(SStateNode &inParent, const char8_t *inTypeName); + eastl::pair<SExecutableContent *, TObjPtr> + CreateExecutableContent(const char8_t *inTypeName); + + CRegisteredString RegisterStr(const char8_t *str) + { + return m_StringTable->RegisterStr(str); + } + IStateContext &GetStateContext(); + + TEditorStr GetDefaultInitialValue(SStateNode &inNode); + void GetLegalInitialValues(SStateNode &inNode, + eastl::vector<CRegisteredString> &outValues); + void SetInitialAttribute(const Option<SValue> &inData, SStateNode &inNode); + + void SetTransactionManager(STransactionManagerImpl &trans); + + void ReplaceExecutableContent(SExecutableContent &oldContent, + SExecutableContent &newContent); + + eastl::vector<CRegisteredString> GetLegalHistoryDefaultValues(SHistory &inData); + + eastl::vector<CRegisteredString> GetLegalParentIds(SStateNode &inNode); + void SetParent(SStateNode &inNode, const Option<SValue> &inValue); + void CheckAndSetValidHistoryDefault(TObjPtr inHistoryNode); + void SetValidHistoryDefault(TObjPtr inHistoryNode); + }; + } +} +} +#endif
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorProperties.h b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorProperties.h new file mode 100644 index 00000000..5f06fbdb --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorProperties.h @@ -0,0 +1,599 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EDITOR_PROPERTIES_H +#define QT3DS_STATE_EDITOR_PROPERTIES_H +#pragma once + +#include "Qt3DSStateEditorFoundation.h" +#include "Qt3DSStateEditorImpl.h" + +namespace qt3ds { +namespace state { + namespace editor { + + template <typename TEditorType> + struct SPropertyAccessorBase : public IPropertyAccessor + { + TFoundationPtr m_Allocator; + volatile QT3DSI32 mRefCount; + + SPropertyAccessorBase(TFoundationPtr alloc, const SPropertyDeclaration &inDec) + : IPropertyAccessor(inDec) + , m_Allocator(alloc) + , mRefCount(0) + { + } + + virtual Option<SValue> Get(IEditorObject &inObj) + { + return DoGet(static_cast<TEditorType &>(inObj)); + } + virtual void Set(IEditorObject &inObj, const Option<SValue> &inValue) + { + DoSet(static_cast<TEditorType &>(inObj), inValue); + } + + virtual Option<SValue> DoGet(TEditorType &inObj) = 0; + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) = 0; + }; + + template <typename TEditorType> + struct SInitialProperty : SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + static SPropertyDeclaration CreatePropertyDeclaration(IStringTable &inStrTable) + { + SPropertyDeclaration theDeclaration; + theDeclaration.m_Name = inStrTable.RegisterStr("initial"); + theDeclaration.m_Type = EditorPropertyTypes::Object; + return theDeclaration; + } + SInitialProperty(TFoundationPtr inAlloc, IStringTable &inStrTable) + : TBaseType(inAlloc, CreatePropertyDeclaration(inStrTable)) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + return SValue(inObj.Get(inObj.m_Data.m_Initial)); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + inObj.m_Initial = + inObj.template FromEditor<STransition>(inValue->getData<TObjPtr>()); + } + }; + + template <typename TEditorType, typename TFlagsType> + struct SFlagBooleanProperty : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + typedef bool (TFlagsType::*TGetPropPtr)() const; + typedef void (TFlagsType::*TSetPropPtr)(bool); + + TGetPropPtr m_GetProp; + TSetPropPtr m_SetProp; + eastl::vector<CRegisteredString> m_LegalValues; + virtual eastl::vector<CRegisteredString> GetLegalValues(IEditorObject & /*inObj*/) + { + return m_LegalValues; + } + + static SPropertyDeclaration CreatePropertyDeclaration(IStringTable &inStrTable, + const char8_t *propName) + { + SPropertyDeclaration theDeclaration; + theDeclaration.m_Name = inStrTable.RegisterStr(propName); + theDeclaration.m_Type = EditorPropertyTypes::StringSet; + return theDeclaration; + } + SFlagBooleanProperty(TFoundationPtr inFnd, IStringTable &inStrTable, + const char8_t *inPropName, const char8_t *falseName, + const char8_t *trueName, TGetPropPtr inGetProp, + TSetPropPtr inSetProp) + : TBaseType(inFnd, CreatePropertyDeclaration(inStrTable, inPropName)) + , m_GetProp(inGetProp) + , m_SetProp(inSetProp) + { + m_LegalValues.push_back(inStrTable.RegisterStr(falseName)); + m_LegalValues.push_back(inStrTable.RegisterStr(trueName)); + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + bool boolVal = (inObj.m_Data.m_Flags.*m_GetProp)(); + TEditorStr retval = boolVal ? TEditorStr(m_LegalValues[1].c_str()) + : TEditorStr(m_LegalValues[0].c_str()); + return SValue(retval); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValueOpt) + { + if (inValueOpt.hasValue()) { + TEditorStr data = inValueOpt->getData<TEditorStr>(); + + if (AreEqual(data.c_str(), m_LegalValues[1].c_str())) + (inObj.m_Data.m_Flags.*m_SetProp)(true); + + else if (AreEqual(data.c_str(), m_LegalValues[0].c_str())) + (inObj.m_Data.m_Flags.*m_SetProp)(false); + + else { + QT3DS_ASSERT(false); + } + } + } + }; + + template <typename TEditorType> + struct SChildrenProperty : SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + static SPropertyDeclaration CreatePropertyDeclaration(IStringTable &inStrTable) + { + SPropertyDeclaration theDeclaration; + theDeclaration.m_Name = inStrTable.RegisterStr("children"); + theDeclaration.m_Type = EditorPropertyTypes::ObjectList; + return theDeclaration; + } + + SChildrenProperty(TFoundationPtr inAlloc, IStringTable &inStrTable) + : TBaseType(inAlloc, CreatePropertyDeclaration(inStrTable)) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + TObjList retval; + for (TStateNodeList::iterator iter = inObj.m_Data.m_Children.begin(), + end = inObj.m_Data.m_Children.end(); + iter != end; ++iter) + retval.push_back(inObj.m_Editor.ToEditor(*iter)); + return SValue(retval); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + if (inValue.hasValue()) { + const TObjList &data = inValue->getData<TObjList>(); + TStateNodeList &theChildren(inObj.m_Data.m_Children); + // De-set all children. + while (theChildren.empty() == false) + inObj.m_Data.RemoveChild(theChildren.front()); + + for (TObjList::const_iterator iter = data.begin(), end = data.end(); + iter != end; ++iter) { + SStateNode *theNode = inObj.m_Editor.StateNodeFromEditor(*iter); + if (theNode) + inObj.m_Data.AppendChild(*theNode); + else { + QT3DS_ASSERT(false); + } + } + } + } + }; + + // Property that is represented by a data item's member. + template <typename TEditorType, typename TStateType, typename TDataType> + struct SDataProp : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + typedef TDataType TStateType::*TPropertyPtr; + TPropertyPtr m_Ptr; + eastl::vector<CRegisteredString> m_LegalValues; + + SDataProp(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec, TPropertyPtr inPtr) + : TBaseType(inAlloc, inDec) + , m_Ptr(inPtr) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual eastl::vector<CRegisteredString> GetLegalValues(IEditorObject & /*inObj*/) + { + return m_LegalValues; + } + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + return inObj.m_Editor.Get(inObj.m_Data.*(this->m_Ptr)); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + inObj.m_Editor.Set(inValue, inObj.m_Data.*(this->m_Ptr)); + } + }; + + template <typename TEditorType, typename TStateType> + struct SInitialTargetProp : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + + SInitialTargetProp(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec) + : TBaseType(inAlloc, inDec) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + return inObj.m_Editor.IsInitialTarget(inObj.m_Data); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + inObj.m_Editor.SetInitialTarget(inValue, inObj.m_Data); + } + + virtual bool HandlesTransaction() { return true; } + }; + + template <typename TEditorType, typename TStateType> + struct SInitialComboProp : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + + SInitialComboProp(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec) + : TBaseType(inAlloc, inDec) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual eastl::vector<CRegisteredString> GetLegalValues(IEditorObject &inEditor) + { + eastl::vector<CRegisteredString> retval; + TEditorType &theEditor = static_cast<TEditorType &>(inEditor); + retval.push_back(theEditor.m_Editor.RegisterStr("(script expression)")); + theEditor.m_Editor.GetLegalInitialValues(theEditor.m_Data, retval); + return retval; + } + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + TEditorType &theEditor = inObj; + if (theEditor.m_InitialComboValue.size() == 0) { + if (!isTrivial(inObj.m_Data.GetInitialExpression())) + theEditor.m_InitialComboValue = "(script expression)"; + else if (inObj.m_Data.GetInitialTransition() + && inObj.m_Data.GetInitialTransition()->m_Target.size()) + theEditor.m_InitialComboValue.assign( + inObj.m_Data.GetInitialTransition()->m_Target[0]->m_Id.c_str()); + else + theEditor.m_InitialComboValue = + theEditor.m_Editor.GetDefaultInitialValue(theEditor.m_Data); + } + return theEditor.m_InitialComboValue; + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + if (inValue->getData<TEditorStr>() != "(script expression)") { + inObj.m_Editor.SetInitialAttribute(inValue, inObj.m_Data); + } else { + inObj.m_Editor.SetInitialAttribute(Option<SValue>(), inObj.m_Data); + } + } + virtual bool HandlesTransaction() { return true; } + }; + + template <typename TEditorType, typename TStateType, typename TDataType> + struct SOptionAccessorProp : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + typedef Option<TDataType> TOptType; + typedef TOptType (TStateType::*TGetPtr)() const; + typedef void (TStateType::*TSetPtr)(const TOptType &inOpt); + TGetPtr m_Getter; + TSetPtr m_Setter; + + SOptionAccessorProp(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec, + TGetPtr inPtr, TSetPtr inSetPtr) + : TBaseType(inAlloc, inDec) + , m_Getter(inPtr) + , m_Setter(inSetPtr) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + TOptType theOpt = (inObj.m_Data.*m_Getter)(); + if (theOpt.hasValue()) + return SValue(*theOpt); + return Empty(); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + TOptType theNewValue; + if (inValue.hasValue()) + theNewValue = TOptType(inValue->getData<TDataType>()); + (inObj.m_Data.*m_Setter)(theNewValue); + } + }; + + template <typename TEditorType, typename TStateType> + struct SDataIdProp : SDataProp<TEditorType, TStateType, CRegisteredString> + { + typedef SDataProp<TEditorType, TStateType, CRegisteredString> TBaseType; + typedef CRegisteredString TStateType::*TPropertyPtr; + + SDataIdProp(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec, + TPropertyPtr inPtr) + : TBaseType(inAlloc, inDec, inPtr) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + // This may mangle the id in order to find a unique id. + inObj.m_Editor.SetIdProperty(inObj.m_Data, inValue, inObj.m_Data.*(this->m_Ptr)); + } + }; + + template <typename TEditorObject, typename TStateType, typename TDataType> + IPropertyAccessor *CreateDataAccessor(SEditorImpl &inData, TDataType TStateType::*inDataPtr, + const char8_t *inName, + EditorPropertyTypes::Enum inPropType) + { + typedef SDataProp<TEditorObject, TStateType, TDataType> TPropType; + return QT3DS_NEW(inData.m_EditorFoundation->getAllocator(), TPropType)( + inData.m_EditorFoundation, + SPropertyDeclaration(inData.RegisterStr(inName), inPropType), inDataPtr); + } + + template <typename TEditorType, typename TDataType> + struct SEditorImplProp : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + typedef TDataType TEditorType::*TPropertyPtr; + TPropertyPtr m_Ptr; + + SEditorImplProp(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec, + TPropertyPtr inPtr) + : TBaseType(inAlloc, inDec) + , m_Ptr(inPtr) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + return inObj.m_Editor.Get(inObj.*(this->m_Ptr)); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + inObj.m_Editor.Set(inValue, inObj.*(this->m_Ptr)); + } + }; + + template <typename TEditorObject, typename TDataType> + IPropertyAccessor * + CreateEditorAccessor(SEditorImpl &inData, TDataType TEditorObject::*inDataPtr, + const char8_t *inName, EditorPropertyTypes::Enum inPropType) + { + typedef SEditorImplProp<TEditorObject, TDataType> TPropType; + return QT3DS_NEW(inData.m_EditorFoundation->getAllocator(), TPropType)( + inData.m_EditorFoundation, + SPropertyDeclaration(inData.RegisterStr(inName), inPropType), inDataPtr); + } + + // General property for initial and history transition properties + template <typename TEditorType, typename TStateType> + struct SSCXMLInitialPtr : public SPropertyAccessorBase<TEditorType> + { + typedef STransition *TStateType::*TPropertyType; + typedef SPropertyAccessorBase<TEditorType> TBaseType; + TPropertyType m_PropPtr; + + SSCXMLInitialPtr(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec, + TPropertyType inPropPtr) + : TBaseType(inAlloc, inDec) + , m_PropPtr(inPropPtr) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + return inObj.GetInitial(inObj.m_Data.*m_PropPtr); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + inObj.SetInitial(inValue->getData<TObjList>(), inObj.m_Data.*m_PropPtr); + } + }; + + template <typename TEditorType, typename TStateType> + struct SSCXMLInitialContent : public SPropertyAccessorBase<TEditorType> + { + typedef STransition *TStateType::*TPropertyType; + typedef SPropertyAccessorBase<TEditorType> TBaseType; + TPropertyType m_PropPtr; + + SSCXMLInitialContent(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec, + TPropertyType inPropPtr) + : TBaseType(inAlloc, inDec) + , m_PropPtr(inPropPtr) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + return inObj.GetInitialContent(inObj.m_Data.*m_PropPtr); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + if (inValue.hasValue()) { + inObj.SetInitialContent(inValue->getData<TObjList>(), inObj.m_Data.*m_PropPtr); + } else { + inObj.SetInitialContent(TObjList(), inObj.m_Data.*m_PropPtr); + } + } + }; + + template <typename TEditorType> + struct SDelayProp : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + + SDelayProp(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec) + : TBaseType(inAlloc, inDec) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + if (!isTrivial(inObj.m_Data.m_DelayExpr)) + return Empty(); + + return inObj.m_Editor.GetSendId(inObj.m_Data.m_Delay); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + inObj.m_Data.m_DelayExpr = ""; + inObj.m_Editor.SetSendId(inValue, inObj.m_Data.m_Delay); + } + }; + + template <typename TEditorType> + struct SHistoryTransitionProp : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + + SHistoryTransitionProp(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec) + : TBaseType(inAlloc, inDec) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual eastl::vector<CRegisteredString> GetLegalValues(IEditorObject &inEditor) + { + TEditorType &theEditor = static_cast<TEditorType &>(inEditor); + return theEditor.m_Editor.GetLegalHistoryDefaultValues(theEditor.m_Data); + } + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + if (inObj.m_Data.m_Transition) + return inObj.m_Editor.Get(inObj.m_Data.m_Transition->m_Target); + + return SValue(TObjList()); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + TObjList newObjects; + + if (inValue.hasValue()) + newObjects = inValue->getData<TObjList>(); + + if (newObjects.size()) { + if (!inObj.m_Data.m_Transition) { + inObj.m_Data.m_Transition = + (STransition *)inObj.m_Editor.m_AutoAllocator.allocate( + sizeof(STransition), "transition", __FILE__, __LINE__); + new (inObj.m_Data.m_Transition) STransition(); + inObj.m_Data.m_Transition->m_Parent = &inObj.m_Data; + } + inObj.m_Editor.Set(inValue, inObj.m_Data.m_Transition->m_Target); + } else + inObj.m_Data.m_Transition = NULL; + } + }; + + template <typename TEditorType> + struct SParentProp : public SPropertyAccessorBase<TEditorType> + { + typedef SPropertyAccessorBase<TEditorType> TBaseType; + + SParentProp(TFoundationPtr inAlloc, IStringTable &inStrTable) + : TBaseType(inAlloc, SPropertyDeclaration(inStrTable.RegisterStr("parent"), + EditorPropertyTypes::StringSet)) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + static const char *SCXMLParentName() { return "(none)"; } + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + TObjPtr current = inObj.Parent(); + if (current && AreEqual(current->TypeName(), "scxml") == false) + return SValue(current->GetId()); + return Option<SValue>(SValue(eastl::string(SCXMLParentName()))); + } + + virtual eastl::vector<CRegisteredString> GetLegalValues(IEditorObject &inEditor) + { + TEditorType &theEditor = static_cast<TEditorType &>(inEditor); + eastl::vector<CRegisteredString> retval = + theEditor.m_Editor.GetLegalParentIds(theEditor.m_Data); + CRegisteredString parentName(theEditor.m_Editor.RegisterStr(SCXMLParentName())); + retval.insert(retval.begin(), parentName); + return retval; + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + inObj.m_Editor.SetParent(inObj.m_Data, inValue); + } + + virtual bool HandlesTransaction() { return true; } + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransactionImpl.cpp b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransactionImpl.cpp new file mode 100644 index 00000000..1bc0faf7 --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransactionImpl.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateEditorTransactionImpl.h" + +namespace qt3ds { +namespace state { + namespace editor { + + // Some things have to be implemented below so that we can take advantage of the full + // SEditorImpl definition. + + STransaction::STransaction(TFoundationPtr alloc, STransactionManagerImpl &inEditor, + const TEditorStr &inName) + : ITransaction(inName) + , m_Alloc(alloc) + , mRefCount(0) + , m_Editor(inEditor) + { + } + + STransaction::~STransaction() {} + + void STransaction::addRef() { atomicIncrement(&mRefCount); } + + void STransaction::release() + { + atomicDecrement(&mRefCount); + if (mRefCount <= 0) { + TFoundationPtr fnd(m_Alloc); + NVDelete(fnd->getAllocator(), this); + } + } + + void STransaction::ChangesToEditors() + { + m_EditorsList.clear(); + m_EditorsList.resize(m_Changes.size()); + eastl::transform(m_Changes.begin(), m_Changes.end(), m_EditorsList.begin(), + SChangeToEditor()); + } + + void STransaction::SendDoSignals() + { + ChangesToEditors(); + CreateRemovedEditorsList(true); + for (TChangeListenerList::iterator listenerIter = m_Editor->m_ChangeListeners.begin(), + listenerEnd = m_Editor->m_ChangeListeners.end(); + listenerIter != listenerEnd; ++listenerIter) + (*listenerIter)->OnDataChange(m_EditorsList, m_RemovedEditorsList); + } + + void STransaction::Do() + { + for (TChangeList::iterator iter = m_Changes.begin(), end = m_Changes.end(); iter != end; + ++iter) + (*iter)->Do(); + + for (TRemovePairList::iterator iter = m_RemovedPairs.begin(), + end = m_RemovedPairs.end(); + iter != end; ++iter) { + if (iter->first) + iter->second->RemoveIdFromContext(); + else + iter->second->AddIdToContext(); + } + + SendDoSignals(); + } + + void STransaction::SilentUndo() + { + for (TChangeList::reverse_iterator iter = m_Changes.rbegin(), end = m_Changes.rend(); + iter != end; ++iter) + (*iter)->Undo(); + + for (TRemovePairList::iterator iter = m_RemovedPairs.begin(), + end = m_RemovedPairs.end(); + iter != end; ++iter) { + if (iter->first) + iter->second->AddIdToContext(); + else + iter->second->RemoveIdFromContext(); + } + } + + void STransaction::Undo() + { + SilentUndo(); + m_EditorsList.clear(); + m_EditorsList.resize(m_Changes.size()); + eastl::transform(m_Changes.rbegin(), m_Changes.rend(), m_EditorsList.begin(), + SChangeToEditor()); + CreateRemovedEditorsList(false); + for (TChangeListenerList::iterator listenerIter = m_Editor->m_ChangeListeners.begin(), + listenerEnd = m_Editor->m_ChangeListeners.end(); + listenerIter != listenerEnd; ++listenerIter) + (*listenerIter)->OnDataChange(m_EditorsList, m_RemovedEditorsList); + } + + struct STransactionImplConnection : public IStateSignalConnection + { + TFoundationPtr &m_Alloc; + QT3DSI32 mRefCount; + STransactionManagerImpl &m_Editor; + IEditorChangeListener &m_ChangeListener; + STransactionImplConnection(TFoundationPtr &alloc, STransactionManagerImpl &e, + IEditorChangeListener &listener) + : m_Alloc(alloc) + , mRefCount(0) + , m_Editor(e) + , m_ChangeListener(listener) + { + } + // Implemented below editor to use editor's interfaces + virtual ~STransactionImplConnection() + { + m_Editor.RemoveChangeListener(m_ChangeListener); + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Alloc, mRefCount); + }; + + void STransactionManagerImpl::addRef() { atomicIncrement(&mRefCount); } + void STransactionManagerImpl::release() + { + atomicDecrement(&mRefCount); + if (mRefCount <= 0) { + TFoundationPtr fnd(m_EditorFoundation); + NVDelete(fnd->getAllocator(), this); + } + } + + TSignalConnectionPtr + STransactionManagerImpl::AddChangeListener(IEditorChangeListener &inListener) + { + m_ChangeListeners.push_back(&inListener); + // If we have any listeners, then we can't be destroyed as their connections will still + // need + // to talk to us to release its stuff. + if (m_ChangeListeners.size() == 1) + addRef(); + return QT3DS_NEW(m_EditorFoundation->getAllocator(), + STransactionImplConnection)(m_EditorFoundation, *this, inListener); + } + + void STransactionManagerImpl::RemoveChangeListener(IEditorChangeListener &inListener) + { + TChangeListenerList::iterator iter = + eastl::find(m_ChangeListeners.begin(), m_ChangeListeners.end(), &inListener); + if (iter != m_ChangeListeners.end()) + m_ChangeListeners.erase(iter); + if (m_ChangeListeners.size() == 0) + release(); + } + TTransactionPtr STransactionManagerImpl::BeginTransaction(const TEditorStr &inName) + { + if (!m_Transaction) { + QT3DS_ASSERT(m_OpenCount == 0); + m_Transaction = QT3DS_NEW(m_EditorFoundation->getAllocator(), + STransaction)(m_EditorFoundation, *this, inName); + } + ++m_OpenCount; + return m_Transaction.mPtr; + } + + TTransactionPtr STransactionManagerImpl::GetOpenTransaction() { return m_Transaction.mPtr; } + STransaction *STransactionManagerImpl::GetOpenTransactionImpl() + { + return m_Transaction.mPtr; + } + + void STransactionManagerImpl::RollbackTransaction() + { + --m_OpenCount; + if (m_OpenCount <= 0) { + if (m_Transaction) { + m_Transaction->Undo(); + m_Transaction = NULL; + } + } + QT3DS_ASSERT(m_OpenCount == 0); + m_OpenCount = 0; + } + + void STransactionManagerImpl::EndTransaction() + { + TTransactionPtr retval = m_Transaction.mPtr; + --m_OpenCount; + if (m_OpenCount <= 0) + m_Transaction = NULL; + QT3DS_ASSERT(m_OpenCount >= 0); + m_OpenCount = 0; + } + + void STransactionManagerImpl::OnObjectDeleted(TObjPtr inObj) + { + if (m_Transaction) + m_Transaction->m_RemovedPairs.push_back(eastl::make_pair(true, inObj)); + + if (m_ObjListener) + m_ObjListener->OnObjectDeleted(inObj); + } + void STransactionManagerImpl::OnObjectCreated(TObjPtr inObj) + { + if (m_Transaction) + m_Transaction->m_RemovedPairs.push_back(eastl::make_pair(false, inObj)); + + if (m_ObjListener) + m_ObjListener->OnObjectCreated(inObj); + } + } +} +}
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransactionImpl.h b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransactionImpl.h new file mode 100644 index 00000000..9996c351 --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransactionImpl.h @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EDITOR_TRANSACTION_IMPL_H +#define QT3DS_STATE_EDITOR_TRANSACTION_IMPL_H +#include "Qt3DSStateEditor.h" +#include "Qt3DSStateEditorFoundation.h" +#include "Qt3DSStateEditorProperties.h" + +namespace qt3ds { +namespace state { + namespace editor { + + class IChange : public NVRefCounted + { + protected: + virtual ~IChange() {} + public: + virtual void Do() = 0; + virtual void Undo() = 0; + virtual TObjPtr GetEditor() = 0; + }; + + typedef eastl::vector<NVScopedRefCounted<IChange>> TChangeList; + + struct SChange : public IChange + { + Option<SValue> m_OldVal; + Option<SValue> m_NewVal; + TPropertyAccessorPtr m_Accessor; + TObjPtr m_Editor; + QT3DSI32 mRefCount; + + SChange(const Option<SValue> &inOldVal, const Option<SValue> &inNewVal, + IPropertyAccessor &inAccessor, IEditorObject &inEditor) + : m_OldVal(inOldVal) + , m_NewVal(inNewVal) + , m_Accessor(inAccessor) + , m_Editor(inEditor) + , mRefCount(0) + { + } + // Sometimes we need to create a change just to signal a new object. + // In this case there isn't an accessor and there aren't old values and + // new values. + SChange(IEditorObject &inEditor) + : m_Editor(inEditor) + , mRefCount(0) + { + } + SChange() + : mRefCount(0) + { + } + void Do() + { + if (m_Accessor) + m_Accessor->Set(*m_Editor, m_NewVal); + } + + void Undo() + { + if (m_Accessor) + m_Accessor->Set(*m_Editor, m_OldVal); + } + void addRef() { atomicIncrement(&mRefCount); } + void release() + { + atomicDecrement(&mRefCount); + if (mRefCount <= 0) + delete this; + } + + TObjPtr GetEditor() { return m_Editor; } + }; + + // true if removed on do, false if removed on false; + typedef eastl::pair<bool, TObjPtr> TRemovePair; + typedef eastl::vector<TRemovePair> TRemovePairList; + + struct STransactionManagerImpl; + + struct STransaction : public ITransaction + { + TFoundationPtr m_Alloc; + TChangeList m_Changes; + volatile QT3DSI32 mRefCount; + // We have to keep a refcount to the editor ourselves. + NVScopedRefCounted<STransactionManagerImpl> m_Editor; + TObjList m_EditorsList; + TObjList m_RemovedEditorsList; + TRemovePairList m_RemovedPairs; + + STransaction(TFoundationPtr alloc, STransactionManagerImpl &inEditor, + const TEditorStr &inName); + virtual ~STransaction(); + virtual void addRef(); + virtual void release(); + + void CreateRemovedEditorsList(bool inDo) + { + m_RemovedEditorsList.clear(); + for (TRemovePairList::iterator iter = m_RemovedPairs.begin(), + end = m_RemovedPairs.end(); + iter != end; ++iter) + if (iter->first == inDo) + m_RemovedEditorsList.push_back(iter->second); + } + + struct SChangeToEditor + { + TObjPtr operator()(IChange *inChange) const { return inChange->GetEditor(); } + }; + virtual void SendDoSignals(); + virtual void Do(); + virtual void SilentUndo(); + virtual void Undo(); + virtual bool Empty() { return m_Changes.empty() && m_RemovedPairs.empty(); } + virtual TObjList GetEditedObjects() + { + ChangesToEditors(); + return m_EditorsList; + } + void ChangesToEditors(); + }; + + class ITransManagerImplListener + { + protected: + virtual ~ITransManagerImplListener() {} + public: + virtual void OnObjectDeleted(TObjPtr inObj) = 0; + virtual void OnObjectCreated(TObjPtr inObj) = 0; + }; + + struct STransactionManagerImpl : public ITransactionManager + { + TFoundationPtr m_EditorFoundation; + volatile QT3DSI32 mRefCount; + TChangeListenerList m_ChangeListeners; + NVScopedRefCounted<STransaction> m_Transaction; + QT3DSI32 m_OpenCount; + ITransManagerImplListener *m_ObjListener; + + STransactionManagerImpl(TFoundationPtr fnd) + : m_EditorFoundation(fnd) + , mRefCount(0) + , m_OpenCount(0) + , m_ObjListener(NULL) + { + } + virtual void addRef(); + virtual void release(); + virtual TSignalConnectionPtr AddChangeListener(IEditorChangeListener &inListener); + virtual TTransactionPtr BeginTransaction(const TEditorStr &inName = TEditorStr()); + virtual TTransactionPtr GetOpenTransaction(); + STransaction *GetOpenTransactionImpl(); + virtual void RollbackTransaction(); + virtual void EndTransaction(); + + void OnObjectDeleted(TObjPtr inObj); + void OnObjectCreated(TObjPtr inObj); + + void RemoveChangeListener(IEditorChangeListener &inListener); + }; + } +} +} +#endif
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp new file mode 100644 index 00000000..7fb4ab9f --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp @@ -0,0 +1,813 @@ +/**************************************************************************** +** +** 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 "Qt3DSStateEditorTransitionPath.h" + +namespace { + +using namespace qt3ds::state::editor; +using namespace qt3ds::state; + +bool inBounds(QT3DSF32 item, QT3DSF32 lower, QT3DSF32 upper) +{ + if (item >= lower && item <= upper) + return true; + return false; +} + +DirectionTypes::Enum EdgeTypeToDirectionType(EdgeTypes::Enum inEdgeType) +{ + switch (inEdgeType) { + case EdgeTypes::Bottom: + case EdgeTypes::Top: + return DirectionTypes::Vertical; + default: + return DirectionTypes::Horizontal; + } +} + +static inline SEndPoint DoGetActualEndPoint(const SEndPoint &inPoint, const SRect &inMyRect, + const QT3DSVec2 &inOtherCenter) +{ + SEndPoint theEndPoint = inPoint; + if (inPoint.m_EdgeType == EdgeTypes::UnsetEdgeType) + theEndPoint = CEditorTransitionPath::CalculateDefaultEndPoint(inMyRect, inOtherCenter); + return theEndPoint; +} + +static inline eastl::pair<QT3DSVec2, EdgeTypes::Enum> +GetEndpointPoint(const SEndPoint &inPoint, const SRect &inMyRect, const QT3DSVec2 &inOtherCenter) +{ + SEndPoint theEndPoint = DoGetActualEndPoint(inPoint, inMyRect, inOtherCenter); + SLine theRectLine; + switch (theEndPoint.m_EdgeType) { + case EdgeTypes::Top: + theRectLine = inMyRect.topEdge(); + break; + case EdgeTypes::Bottom: + theRectLine = inMyRect.bottomEdge(); + break; + case EdgeTypes::Left: + theRectLine = inMyRect.leftEdge(); + break; + default: + QT3DS_ASSERT(false); + // fallthrough intentional + case EdgeTypes::Right: + theRectLine = inMyRect.rightEdge(); + break; + } + + return eastl::make_pair(theRectLine.toPoint(theEndPoint.m_Interp), theEndPoint.m_EdgeType); +} + +inline bool RectDiffers(const Option<SRect> &inLhs, const SRect &inRhs) +{ + if (inLhs.isEmpty()) + return true; + SRect lhs = *inLhs; + return CEditorTransitionPath::AreAlmostEqual(lhs.m_TopLeft, inRhs.m_TopLeft) == false + || CEditorTransitionPath::AreAlmostEqual(lhs.m_WidthHeight, inRhs.m_WidthHeight) == false; +} +} + +namespace qt3ds { +namespace state { + namespace editor { + + bool SRect::contains(const QT3DSVec2 &inPoint) const + { + if (inPoint.x >= left() && inPoint.x <= right() && inPoint.y >= top() + && inPoint.y <= bottom()) + return true; + return false; + } + + void CEditorTransitionPath::SetPathType(TransitionPathTypes::Enum inType) + { + m_TransitionPathType = inType; + if (m_TransitionPathType == TransitionPathTypes::BeginToBegin + || m_TransitionPathType == TransitionPathTypes::Targetless) + m_ControlPoints.clear(); + MarkDirty(); + } + + bool CEditorTransitionPath::UpdateBeginEndRects(const SRect &inStartRect, + const SRect &inEndRect) + { + bool dataChanged = false; + + if (m_BeginRect.hasValue() && m_EndRect.hasValue()) { + QT3DSVec2 beginDiff = inStartRect.center() - m_BeginRect->center(); + QT3DSVec2 endDiff = inEndRect.center() - m_EndRect->center(); + + if (AreAlmostEqual(beginDiff, QT3DSVec2(0, 0)) == false + && AreAlmostEqual(beginDiff, endDiff, 1)) { + dataChanged = m_ControlPoints.empty() == false; + // Move all the control points. + for (size_t idx = 0, end = m_ControlPoints.size(); idx < end; ++idx) { + SControlPoint &thePoint(m_ControlPoints[idx]); + QT3DSF32 diffComponent = + SControlPoint::GetComponent(beginDiff, thePoint.m_Direction); + thePoint.m_Position += diffComponent; + } + } + } + bool rectDiffers = + RectDiffers(m_BeginRect, inStartRect) || RectDiffers(m_EndRect, inEndRect); + m_BeginRect = inStartRect; + m_EndRect = inEndRect; + if (rectDiffers) + MarkDirty(); + m_TransitionPathType = TransitionPathTypes::BeginToEnd; + return dataChanged; + } + + bool CEditorTransitionPath::UpdateBeginEndRects(const SRect &inStartRect, + TransitionPathTypes::Enum inPathType) + { + bool dataChanged = m_ControlPoints.empty() == false; + QT3DS_ASSERT(inPathType == TransitionPathTypes::BeginToBegin + || inPathType == TransitionPathTypes::Targetless); + bool rectDiffers = m_EndRect.hasValue() || RectDiffers(m_BeginRect, inStartRect); + m_BeginRect = inStartRect; + m_EndRect = Empty(); + if (rectDiffers || dataChanged) + MarkDirty(); + m_TransitionPathType = inPathType; + return dataChanged; + } + + eastl::pair<QT3DSVec2, EdgeTypes::Enum> CEditorTransitionPath::GetBeginPointAndEdge() const + { + eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint(QT3DSVec2(0, 0), + EdgeTypes::UnsetEdgeType); + if (m_BeginRect.hasValue()) { + QT3DSVec2 center(m_BeginRect->center()); + center.x += 1; + if (m_EndRect.hasValue()) + center = m_EndRect->center(); + theStartPoint = GetEndpointPoint(m_Begin, *m_BeginRect, center); + } + return theStartPoint; + } + + eastl::pair<QT3DSVec2, QT3DSVec2> CEditorTransitionPath::GetBeginEndPoints() const + { + eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint(GetBeginPointAndEdge()); + eastl::pair<QT3DSVec2, EdgeTypes::Enum> theEndPoint(QT3DSVec2(0, 0), + EdgeTypes::UnsetEdgeType); + if (m_EndRect.hasValue()) + theEndPoint = GetEndpointPoint(m_End, *m_EndRect, m_BeginRect->center()); + + return eastl::make_pair(theStartPoint.first, theEndPoint.first); + } + + SEndPoint CEditorTransitionPath::CalculateEndPoint(const SRect &inRect, + const QT3DSVec2 &inPoint, + QT3DSF32 inEdgeBoundary) + { + if (inRect.width() == 0 || inRect.height() == 0) { + QT3DS_ASSERT(false); + return SEndPoint(); + } + + SLine centerToPoint = SLine(inRect.center(), inPoint); + SLine leftOrRight; + SLine topOrBottom; + Option<QT3DSF32> isect; + EdgeTypes::Enum theEdge = EdgeTypes::UnsetEdgeType; + // If line runs right, test against right edge + QT3DSF32 distance = 0; + SLine theRectLine; + if (centerToPoint.dx() > 0) { + theRectLine = inRect.rightEdge(); + isect = theRectLine.intersect(centerToPoint); + // If we are out of range for the right edge + if (isect.hasValue() && inBounds(*isect, 0.0f, 1.0f)) { + distance = theRectLine.dy(); + theEdge = EdgeTypes::Right; + } + } else { + theRectLine = inRect.leftEdge(); + isect = theRectLine.intersect(centerToPoint); + if (isect.hasValue() && inBounds(*isect, 0.0f, 1.0f)) { + distance = theRectLine.dy(); + theEdge = EdgeTypes::Left; + } + } + // If we haven't resolved the edge type + if (theEdge == EdgeTypes::UnsetEdgeType) { + if (centerToPoint.dy() < 0) { + theRectLine = inRect.topEdge(); + isect = theRectLine.intersect(centerToPoint); + theEdge = EdgeTypes::Top; + distance = theRectLine.dx(); + } else { + theRectLine = inRect.bottomEdge(); + isect = theRectLine.intersect(centerToPoint); + theEdge = EdgeTypes::Bottom; + distance = theRectLine.dx(); + } + } + // Now drop a perpendicular from the point to the rect line. + SLine normalLine(inPoint, inPoint + QT3DSVec2(theRectLine.dy(), -theRectLine.dx())); + isect = theRectLine.intersect(normalLine); + if (isect.isEmpty()) { + theEdge = EdgeTypes::Right; + isect = .5f; + } + SEndPoint retval(theEdge, *isect); + QT3DSF32 normalizedBoundary = + NVMax(0.0f, inEdgeBoundary / static_cast<float>(fabs(distance))); + + QT3DSF32 edgeLowerBound = NVMax(0.0f, normalizedBoundary); + QT3DSF32 edgeUpperBound = NVMin(1.0f, 1.0f - normalizedBoundary); + retval.m_Interp = NVMax(edgeLowerBound, retval.m_Interp); + retval.m_Interp = NVMin(edgeUpperBound, retval.m_Interp); + return retval; + } + + // Default end points always are in the middle of the rect edge + SEndPoint CEditorTransitionPath::CalculateDefaultEndPoint(const SRect &inRect, + const QT3DSVec2 &inPoint) + { + SEndPoint ep = CalculateEndPoint(inRect, inPoint, 0.0f); + return SEndPoint(ep.m_EdgeType, .5f); + } + + void CEditorTransitionPath::SetEndPoint(const QT3DSVec2 &inWorldPoint) + { + QT3DS_ASSERT(m_EndRect.hasValue()); + m_End = CalculateEndPoint(*m_EndRect, inWorldPoint, m_StateEdgeBuffer); + MarkDirty(); + } + + void CEditorTransitionPath::SetBeginPoint(const QT3DSVec2 &inWorldPoint) + { + QT3DS_ASSERT(m_BeginRect.hasValue()); + m_Begin = CalculateEndPoint(*m_BeginRect, inWorldPoint, m_StateEdgeBuffer); + MarkDirty(); + } + + SEndPoint CEditorTransitionPath::GetActualBeginPoint() const + { + return DoGetActualEndPoint(m_Begin, *m_BeginRect, m_EndRect->center()); + } + + SEndPoint CEditorTransitionPath::GetActualEndPoint() const + { + return DoGetActualEndPoint(m_End, *m_EndRect, m_BeginRect->center()); + } + + void CEditorTransitionPath::SetControlPoint(QT3DSI32 idx, const QT3DSVec2 &inPosition, + bool inMoveAdjacentEndPoint) + { + if (idx < 0 || idx >= (QT3DSI32)m_ControlPoints.size()) { + QT3DS_ASSERT(false); + return; + } + m_ControlPoints[idx].Set(inPosition); + if (inMoveAdjacentEndPoint) { + // Move the end points adjacent to the handle. + QT3DSI32 possiblePoint = MapControlPointToPossibleControlPoint(idx); + size_t numPossible = m_PossibleControlPoints.size(); + if (possiblePoint == 0) + SetBeginPoint(inPosition); + if (possiblePoint == ((QT3DSI32)numPossible - 1)) + SetEndPoint(inPosition); + } + + MarkDirty(); + } + + void CEditorTransitionPath::SetControlPoints(const TPointList &inList) + { + QT3DS_ASSERT(inList.size() == m_Path.size()); + for (size_t idx = 0, end = m_ControlPoints.size(); idx < end; ++idx) { + SControlPoint &theControlPoint(m_ControlPoints[idx]); + if (theControlPoint.m_PathIndex >= 0 + && theControlPoint.m_PathIndex < (QT3DSI32)inList.size()) + theControlPoint.Set(inList[theControlPoint.m_PathIndex]); + else { + QT3DS_ASSERT(false); + } + } + } + + TPointList CEditorTransitionPath::GetPath() const + { + MaybeRegeneratePath(); + return m_Path; + } + + TControlPointList CEditorTransitionPath::GetPossibleControlPoints() const + { + MaybeRegeneratePath(); + return m_PossibleControlPoints; + } + + SControlPoint CEditorTransitionPath::GetPossibleControlPoint(QT3DSI32 inIdx) const + { + MaybeRegeneratePath(); + if (inIdx >= 0 && inIdx < (QT3DSI32)m_PossibleControlPoints.size()) + return m_PossibleControlPoints[inIdx]; + QT3DS_ASSERT(false); + return SControlPoint(); + } + + // This may create a new control point thus invalidating the path and possible control + // points. + QT3DSI32 + CEditorTransitionPath::MapPossibleControlPointToControlPoint(QT3DSI32 inPossiblePointIndex) + { + if (inPossiblePointIndex < 0 + || inPossiblePointIndex >= (QT3DSI32)m_PossibleControlPoints.size()) { + QT3DS_ASSERT(false); + return -1; + } + const SControlPoint &thePossiblePoint(m_PossibleControlPoints[inPossiblePointIndex]); + TControlPointList::iterator iter = eastl::lower_bound( + m_ControlPoints.begin(), m_ControlPoints.end(), thePossiblePoint); + QT3DSI32 retval = (QT3DSI32)m_ControlPoints.size(); + if (iter != m_ControlPoints.end()) { + retval = (QT3DSI32)(iter - m_ControlPoints.begin()); + if (iter->m_PathIndex == thePossiblePoint.m_PathIndex) + return retval; + } + + m_ControlPoints.insert(iter, thePossiblePoint); + return retval; + } + + QT3DSI32 CEditorTransitionPath::MapControlPointToPossibleControlPoint(QT3DSI32 inPointIndex) + { + if (inPointIndex < 0 || inPointIndex >= (QT3DSI32)m_ControlPoints.size()) { + QT3DS_ASSERT(false); + return -1; + } + const SControlPoint &theControlPoint(m_ControlPoints[inPointIndex]); + TControlPointList::iterator iter = eastl::lower_bound( + m_PossibleControlPoints.begin(), m_PossibleControlPoints.end(), theControlPoint); + if (iter != m_PossibleControlPoints.end()) { + QT3DSI32 retval = (QT3DSI32)(iter - m_PossibleControlPoints.begin()); + if (iter->m_PathIndex == theControlPoint.m_PathIndex) + return retval; + } + QT3DS_ASSERT(false); + return -1; + } + + bool CEditorTransitionPath::DoesControlPointExist(QT3DSI32 inPossiblePointIndex) const + { + if (inPossiblePointIndex < 0 + || inPossiblePointIndex >= (QT3DSI32)m_PossibleControlPoints.size()) { + QT3DS_ASSERT(false); + return false; + } + const SControlPoint &thePossiblePoint(m_PossibleControlPoints[inPossiblePointIndex]); + TControlPointList::const_iterator iter = eastl::lower_bound( + m_ControlPoints.begin(), m_ControlPoints.end(), thePossiblePoint); + if (iter != m_ControlPoints.end() && iter->m_PathIndex == thePossiblePoint.m_PathIndex) + return true; + return false; + } + + // Run through the control point list and if you find another control point on the line, + // return it's index. Else + // bail. + // If you are finding the remove algorithm is too specific or hard to use increase the 2.0f + // numbers below. + inline Option<size_t> NextParallelControlPointOnLine(const SControlPoint &inItem, + const SControlPoint &inEndPoint, + const TControlPointList &inList, + size_t inStartIdx) + { + for (size_t idx = inStartIdx, end = inList.size(); idx < end; ++idx) { + const SControlPoint &theTestPoint(inList[idx]); + if (theTestPoint.m_Direction == inItem.m_Direction) { + if (CEditorTransitionPath::AreAlmostEqual(inItem.m_Position, + theTestPoint.m_Position, 2.0f)) + return idx + 1; + else + return Empty(); + } + } + + // Check if beginning and end lie in the same path, or within just a couple pixels. + if (inItem.m_Direction == inEndPoint.m_Direction + && CEditorTransitionPath::AreAlmostEqual(inItem.m_Position, inEndPoint.m_Position, + 2.0f)) + return inList.size(); + + return Empty(); + } + + // We try to find control point that point the same direction and lie on the same line with + // only control points with + // orthogonal directions in between. If we find points that fullfill this criteria, we know + // we can remove all intermediate + // points because the transition path will end up making a straight line. + bool CEditorTransitionPath::RemoveRedundantControlPoints() + { + if (m_ControlPoints.empty()) + return false; + + eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint = GetBeginPointAndEdge(); + eastl::pair<QT3DSVec2, EdgeTypes::Enum> theEndPoint; + if (m_EndRect.hasValue()) + theEndPoint = GetEndpointPoint(m_End, *m_EndRect, m_BeginRect->center()); + else + theEndPoint = theStartPoint; + // Find runs of control points in the same line. Remove the points in the middle of the + // line. + SControlPoint theLastControlPoint(EdgeTypeToDirectionType(theStartPoint.second)); + theLastControlPoint.Set(theStartPoint.first); + SControlPoint theEndControlPoint(EdgeTypeToDirectionType(theEndPoint.second)); + theEndControlPoint.Set(theEndPoint.first); + size_t numControlPoints(m_ControlPoints.size()); + for (size_t idx = 0, end = numControlPoints; idx < end; ++idx) { + Option<size_t> removeEnd = NextParallelControlPointOnLine( + theLastControlPoint, theEndControlPoint, m_ControlPoints, idx); + if (removeEnd.isEmpty() == false) { + size_t lastItem = *removeEnd; + m_ControlPoints.erase(m_ControlPoints.begin() + idx, + m_ControlPoints.begin() + lastItem); + --idx; + end = m_ControlPoints.size(); + } else + theLastControlPoint = m_ControlPoints[idx]; + } + if (m_ControlPoints.size() != numControlPoints) { + MarkDirty(); + return true; + } + return false; + } + + void CEditorTransitionPath::RestoreAutoTransition() + { + m_ControlPoints.clear(); + m_Begin = SEndPoint(); + m_End = SEndPoint(); + MarkDirty(); + } + + bool CEditorTransitionPath::IsManualMode() const + { + return m_Begin.m_EdgeType != EdgeTypes::UnsetEdgeType + || m_End.m_EdgeType != EdgeTypes::UnsetEdgeType || m_ControlPoints.empty() == false; + } + + SPointQueryResult CEditorTransitionPath::Pick(QT3DSVec2 inPoint, QT3DSVec2 inControlBoxDims, + QT3DSVec2 inEndBoxDims) + { + MaybeRegeneratePath(); + + if (inEndBoxDims.x && inEndBoxDims.y) { + SRect endBox(QT3DSVec2(-1.0f * inEndBoxDims.x / 2, -1.0f * inEndBoxDims.y / 2), + inEndBoxDims); + SRect testRect(endBox); + testRect.translate(m_Path.front()); + if (testRect.contains(inPoint)) + return SPointQueryResult(PointQueryResultType::Begin); + testRect = SRect(endBox); + testRect.translate(m_Path.back()); + if (testRect.contains(inPoint)) + return SPointQueryResult(PointQueryResultType::End); + } + if (inControlBoxDims.x && inControlBoxDims.y) { + SRect theControlBox( + QT3DSVec2(-1.0f * inControlBoxDims.x / 2.0f, -1.0f * inControlBoxDims.y / 2.0f), + inControlBoxDims); + for (size_t idx = 0, end = m_PossibleControlPoints.size(); idx < end; ++idx) { + const SControlPoint &thePoint(m_PossibleControlPoints[idx]); + QT3DSVec2 startPoint = m_Path[thePoint.m_PathIndex]; + QT3DSVec2 endPoint = m_Path[thePoint.m_PathIndex + 1]; + // We stretch the rect to contain the entire line, not just where we display the + // point. + QT3DSVec2 lineDims = endPoint - startPoint; + QT3DSF32 lineRectHeight = SControlPoint::GetComponent(theControlBox.m_WidthHeight, + thePoint.m_Direction); + QT3DSF32 lineRectLength = + fabs(SControlPoint::GetOrthogonalComponent(lineDims, thePoint.m_Direction)); + QT3DSVec2 rectDims = SControlPoint::FromComponentToVector( + lineRectHeight, lineRectLength, thePoint.m_Direction); + QT3DSVec2 rectTopLeft = + QT3DSVec2(NVMin(startPoint.x, endPoint.x), NVMin(startPoint.y, endPoint.y)); + QT3DSF32 rectComponent = + SControlPoint::GetComponent(rectTopLeft, thePoint.m_Direction); + rectComponent -= lineRectHeight / 2.0f; // Center the box about the line. + rectTopLeft = SControlPoint::SetComponent(rectTopLeft, rectComponent, + thePoint.m_Direction); + SRect testRect(rectTopLeft, rectDims); + if (testRect.contains(inPoint)) + return SPointQueryResult(PointQueryResultType::Control, (QT3DSI32)idx); + } + } + return PointQueryResultType::NoPoint; + } + + SPointQueryResult + CEditorTransitionPath::PickClosestControlPoint(QT3DSVec2 inPoint, + DirectionTypes::Enum inDirectionType) + { + MaybeRegeneratePath(); + QT3DSI32 closestIdx = -1; + QT3DSF32 minDistance = QT3DS_MAX_F32; + + for (size_t idx = 0, end = m_PossibleControlPoints.size(); idx < end; ++idx) { + const SControlPoint &thePoint(m_PossibleControlPoints[idx]); + QT3DSVec2 startPoint = m_Path[thePoint.m_PathIndex]; + QT3DSVec2 endPoint = m_Path[thePoint.m_PathIndex + 1]; + SLine theLine(startPoint, endPoint); + QT3DSF32 distance = theLine.distance(inPoint); + + if (distance < minDistance && thePoint.m_Direction == inDirectionType) { + closestIdx = idx; + minDistance = distance; + } + } + if (closestIdx == -1) + return SPointQueryResult(); + return SPointQueryResult(PointQueryResultType::Control, closestIdx); + } + + // The output functions are both setup under these assumptions: + // 1. The current point does *not* have representation in the possible control points list. + // 2. The last point *does* have representation in the possible control points list. + // 3. The algorithm should output all path elements up and to but not including the + // current point. + // So, given straight line do not output any possible points. + // Given zig-zag, output two points. + SControlPoint + CEditorTransitionPath::OutputParallelPoints(const SControlPoint &inLastPoint, + const SControlPoint &inCurrentPoint, + QT3DSF32 runWidth) const + { + const SControlPoint &theRunPoint(inCurrentPoint); + const SControlPoint theLastControlPoint(inLastPoint); + DirectionTypes::Enum runDirection(inLastPoint.m_Direction); + if (AreAlmostEqual(theRunPoint.m_Position, theLastControlPoint.m_Position, 1.0)) { + // Straigh line. Perhaps remove this point? + theRunPoint.m_PathIndex = theLastControlPoint.m_PathIndex; + return theRunPoint; + } else { + // First, output a possible control point inline with inLastPoint + SControlPoint possiblePoint(inLastPoint); + possiblePoint.m_PathIndex = (QT3DSI32)(m_Path.size() - 1); + m_PossibleControlPoints.push_back(possiblePoint); + // Output zig-zag, we zig zag from last control point to theRunPoint. We need to + // push two points and two control points. + QT3DSVec2 startPos(m_Path.back()); + QT3DSF32 startComponent = SControlPoint::GetComponent(startPos, runDirection); + QT3DSF32 orthoStartComponent = + SControlPoint::GetOrthogonalComponent(startPos, runDirection); + QT3DSF32 endComponent = theRunPoint.m_Position; + QT3DSF32 orthoEndComponent = orthoStartComponent + runWidth; + QT3DSVec2 endPos = SControlPoint::FromComponentToVector( + endComponent, orthoEndComponent, runDirection); + QT3DSF32 zigZagOrthoPos = orthoStartComponent + runWidth / 2; + QT3DSI32 crossbarIndex = (QT3DSI32)m_Path.size(); + QT3DSVec2 crossbarStart = SControlPoint::FromComponentToVector( + startComponent, zigZagOrthoPos, runDirection); + m_Path.push_back(crossbarStart); + m_PossibleControlPoints.push_back( + SControlPoint(SControlPoint::OppositeDirection(theRunPoint.m_Direction), + orthoStartComponent, crossbarIndex)); + QT3DSVec2 crossbarEnd = SControlPoint::FromComponentToVector( + endComponent, zigZagOrthoPos, theRunPoint.m_Direction); + m_Path.push_back(crossbarEnd); + theRunPoint.m_PathIndex = crossbarIndex + 1; + // Do not, however, output the run point. This will happen in the next step. + return inCurrentPoint; + } + } + + // Given right angle, output 1 point. + SControlPoint + CEditorTransitionPath::OutputOrthogonalPoints(const SControlPoint &inLastPoint, + const SControlPoint &inCurrentPoint) const + { + SLine lastLine = inLastPoint.ToLine(); + SLine currentLine = inCurrentPoint.ToLine(); + Option<QT3DSF32> isect = lastLine.intersect(currentLine); + QT3DS_ASSERT(isect.hasValue()); + inLastPoint.m_PathIndex = (QT3DSI32)(m_Path.size() - 1); + m_PossibleControlPoints.push_back(inLastPoint); + if (isect.hasValue()) { + QT3DSVec2 theIsectPoint = lastLine.toPoint(*isect); + m_Path.push_back(theIsectPoint); + } + inCurrentPoint.m_PathIndex = (QT3DSI32)(m_Path.size() - 1); + return inCurrentPoint; + } + + void CEditorTransitionPath::MaybeRegeneratePath() const + { + if (IsDirty() == false) + return; + + if (m_TransitionPathType == TransitionPathTypes::BeginToEnd) { + // Ensure intermediate information is cleared. + const_cast<CEditorTransitionPath *>(this)->MarkDirty(); + // We don't have the begin and end states. + if (m_BeginRect.isEmpty() || m_EndRect.isEmpty()) { + QT3DS_ASSERT(false); + return; + } + // Find the start and end points. + eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint = + GetEndpointPoint(m_Begin, *m_BeginRect, m_EndRect->center()); + eastl::pair<QT3DSVec2, EdgeTypes::Enum> theEndPoint = + GetEndpointPoint(m_End, *m_EndRect, m_BeginRect->center()); + m_Path.push_back(theStartPoint.first); + SControlPoint theLastControlPoint(EdgeTypeToDirectionType(theStartPoint.second)); + theLastControlPoint.Set(theStartPoint.first); + theLastControlPoint.m_PathIndex = 0; + SControlPoint theEndControlPoint(EdgeTypeToDirectionType(theEndPoint.second)); + theEndControlPoint.Set(theEndPoint.first); + for (size_t idx = 0, end = m_ControlPoints.size(); idx < end; ++idx) { + const SControlPoint &thePoint(m_ControlPoints[idx]); + if (thePoint.m_Direction == theLastControlPoint.m_Direction) { + // zig zag. Requires us to find the first point that *isn't a zig zag in + // order to + // calculate where the zig zag should be. We could have a section composed + // of only + // parallel directions and we can't lay then out until we find how much + // distance we have + // to space each on out. + // Image you get to this point and you find you have a set of control points + // with vertical direction. + // Their positions will tell us how far left each one should sit. But we + // don't have enough information + // to lay them out without knowing how much vertical space this section + // should fill. So we would have to + // search forward until we can figure this out. + QT3DSVec2 runStart = m_Path.back(); + // Search forward until either we run out of points or until we find a point + // who's direction + // does not match the current direction. We call a contiguous set of + // control points who all + // have the same direction a 'run'. + size_t runEnd; + size_t zigzagCount = 1; + DirectionTypes::Enum runDirection = theLastControlPoint.m_Direction; + // Search forward till we find a point that is different. + for (runEnd = idx + 1; runEnd < end + && m_ControlPoints[runEnd].m_Direction == thePoint.m_Direction; + ++runEnd) { + // Skip items that are in line. They shouldn't be counted towards our + // zigzag count. + if (AreAlmostEqual(m_ControlPoints[runEnd].m_Position, + m_ControlPoints[runEnd - 1].m_Position) + == false) + ++zigzagCount; + } + // Two possible cases. Either we find a control point that has a different + // direction in which case we then figure out + // how much space we need overall *or* we ran out of control points in which + // case we use the end point. + QT3DSVec2 runEndPoint(0, 0); + if (runEnd == end) { + // check if the end point direction is the same. This could be the + // final zig zag. Else it will be a righthand turn + if (EdgeTypeToDirectionType(theEndPoint.second) == runDirection + && AreAlmostEqual(theEndControlPoint.m_Position, + thePoint.m_Position) + == false) + ++zigzagCount; + + runEndPoint = theEndPoint.first; + } else { + SLine thePointLine(thePoint.ToLine()); + Option<QT3DSF32> isect = + thePointLine.intersect(m_ControlPoints[runEnd].ToLine()); + if (isect.hasValue()) + runEndPoint = thePointLine.toPoint(*isect); + else { + QT3DS_ASSERT(false); + } + } + QT3DSF32 runOrthoStart = + SControlPoint::GetOrthogonalComponent(runStart, runDirection); + QT3DSF32 runOrthoEnd = + SControlPoint::GetOrthogonalComponent(runEndPoint, runDirection); + QT3DSF32 runRange = runOrthoEnd - runOrthoStart; + QT3DSF32 runWidth = runRange / (QT3DSF32)zigzagCount; + // Now we iterate through the run itself and output path elements. + for (; idx < runEnd; ++idx) { + theLastControlPoint = OutputParallelPoints( + theLastControlPoint, m_ControlPoints[idx], runWidth); + } + // Subtract one to account for the loop upate that happens next + --idx; + } else // right angle + { + theLastControlPoint = + OutputOrthogonalPoints(theLastControlPoint, m_ControlPoints[idx]); + } + } + // Finished iterating through the control points. Now we have the sticky situation + // of the very last point + // and how it joins with the end point. + QT3DSVec2 lastPoint(m_Path.back()); + if (theEndControlPoint.m_Direction == theLastControlPoint.m_Direction) { + QT3DSF32 lastPointOrthoComponent = SControlPoint::GetOrthogonalComponent( + lastPoint, theLastControlPoint.m_Direction); + QT3DSF32 endOrthoComponent = SControlPoint::GetOrthogonalComponent( + theEndPoint.first, theLastControlPoint.m_Direction); + QT3DSF32 runWidth = endOrthoComponent - lastPointOrthoComponent; + OutputParallelPoints(theLastControlPoint, theEndControlPoint, runWidth); + } else { + theLastControlPoint = + OutputOrthogonalPoints(theLastControlPoint, theEndControlPoint); + } + // Now output the last possible point which matches the end control point's + // direction and such. + theEndControlPoint.m_PathIndex = (QT3DSI32)(m_Path.size() - 1); +#ifdef _DEBUG + // The directly previous possible point in the list should not match this point. In + // fact, it really should be orthogonal. + if (m_PossibleControlPoints.size()) { + QT3DS_ASSERT(m_PossibleControlPoints.back().m_Direction + != theEndControlPoint.m_Direction + || AreAlmostEqual(m_PossibleControlPoints.back().m_Position, + theEndControlPoint.m_Position) + == false); + } +#endif + m_PossibleControlPoints.push_back(theEndControlPoint); + // Finally push back the last item. + m_Path.push_back(theEndPoint.first); + } else if (m_TransitionPathType == TransitionPathTypes::BeginToBegin + || m_TransitionPathType == TransitionPathTypes::Targetless) { + QT3DSVec2 beginCenter(m_BeginRect->center()); + beginCenter.x += 1; + eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint = + GetEndpointPoint(m_Begin, *m_BeginRect, beginCenter); + QT3DSVec2 lineDir; + m_Path.push_back(theStartPoint.first); + if (m_TransitionPathType == TransitionPathTypes::BeginToBegin) { + switch (theStartPoint.second) { + case EdgeTypes::Top: + lineDir = QT3DSVec2(0, -1); + break; + case EdgeTypes::Bottom: + lineDir = QT3DSVec2(0, 1); + break; + case EdgeTypes::Left: + lineDir = QT3DSVec2(-1, 0); + break; + case EdgeTypes::Right: + lineDir = QT3DSVec2(1, 0); + break; + default: + QT3DS_ASSERT(false); + break; + } + QT3DSF32 squareDiagLen = 30; + QT3DSF32 halfDiag = squareDiagLen / 2.0f; + QT3DSVec2 theOppPoint = theStartPoint.first + lineDir * squareDiagLen; + QT3DSVec2 middle = (theStartPoint.first + theOppPoint) / 2.0f; + QT3DSVec2 orthoLineDir = QT3DSVec2(lineDir.y, lineDir.x); + QT3DSVec2 loopTop = middle + orthoLineDir * halfDiag; + QT3DSVec2 loopBottom = middle - orthoLineDir * halfDiag; + m_Path.push_back(loopTop); + m_Path.push_back(theOppPoint); + m_Path.push_back(loopBottom); + m_Path.push_back(theStartPoint.first); + } + } else { + QT3DS_ASSERT(false); + } + } + } +} +}
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.h b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.h new file mode 100644 index 00000000..bacf60f2 --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.h @@ -0,0 +1,467 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EDITOR_TRANSITION_PATH_H +#define QT3DS_STATE_EDITOR_TRANSITION_PATH_H +#pragma once +#include "Qt3DSState.h" +#include "foundation/Qt3DSVec2.h" +#include "EASTL/vector.h" +#include "foundation/Qt3DSOption.h" + +namespace qt3ds { +namespace state { + namespace editor { + + struct EdgeTypes + { + enum Enum { + UnsetEdgeType = 0, + Left, + Right, + Top, + Bottom, + }; + }; + + struct SEndPoint + { + // Which edge are we on + EdgeTypes::Enum m_EdgeType; + // How far from the start of the edge are we. + QT3DSF32 m_Interp; + SEndPoint(EdgeTypes::Enum inType = EdgeTypes::UnsetEdgeType, QT3DSF32 interp = .5f) + : m_EdgeType(inType) + , m_Interp(interp) + { + } + }; + + struct DirectionTypes + { + enum Enum { + UnknownDirection = 0, + Horizontal, + Vertical, + }; + }; + + struct SLine + { + QT3DSVec2 m_Begin; + QT3DSVec2 m_End; + + SLine(const QT3DSVec2 &beg = QT3DSVec2(0, 0), const QT3DSVec2 &end = QT3DSVec2(0, 0)) + : m_Begin(beg) + , m_End(end) + { + } + + QT3DSF32 dx() const { return m_End.x - m_Begin.x; } + QT3DSF32 dy() const { return m_End.y - m_Begin.y; } + QT3DSVec2 begin() const { return m_Begin; } + QT3DSVec2 end() const { return m_End; } + QT3DSVec2 toPoint(QT3DSF32 interp) { return m_Begin + QT3DSVec2(dx() * interp, dy() * interp); } + Option<QT3DSF32> slope() const + { + QT3DSF32 run = dx(); + if (fabs(run) > .0001f) + return dy() / dx(); + return Empty(); + } + // If this function returns a value, it returns an interpolation value such that if you + // call toPoint on the return value it gives you the intersection point. + // http://tog.acm.org/resources/GraphicsGems/gemsiii/insectc.c + // Simplifications taken from Qt's line intersect + Option<QT3DSF32> intersect(const SLine &other) const + { + // ipmlementation is based on Graphics Gems III's "Faster Line Segment Intersection" + QT3DSVec2 a = m_End - m_Begin; + QT3DSVec2 b = other.m_End - other.m_Begin; + QT3DSVec2 c = m_Begin - other.m_Begin; + + QT3DSF32 denominator = a.y * b.x - a.x * b.y; + + if (denominator == 0 || fabs(denominator) < .0001f) + return Empty(); + + QT3DSF32 reciprocal = 1.0f / denominator; + return (b.y * c.x - b.x * c.y) * reciprocal; + } + + // Caculates the top half of the distance to line equation with the fabs or sqrt. + QT3DSF32 distanceNumerator(const QT3DSVec2 &inPoint) const + { + QT3DSF32 x2 = inPoint.x; + QT3DSF32 y2 = inPoint.y; + + QT3DSF32 x1 = m_End.x; + QT3DSF32 y1 = m_End.y; + + QT3DSF32 x0 = m_Begin.x; + QT3DSF32 y0 = m_Begin.y; + return ((x2 - x1) * (y1 - y0)) - ((x1 - x0) * (y2 - y1)); + } + + QT3DSF32 distance(const QT3DSVec2 &inPoint) const + { + QT3DSF32 theDx = dx(); + QT3DSF32 theDy = dy(); + return fabs(distanceNumerator(inPoint)) / sqrtf(theDx * theDx + theDy * theDy); + } + }; + + // Rect in same coordinate space as QT. + struct SRect + { + QT3DSVec2 m_TopLeft; + QT3DSVec2 m_WidthHeight; + SRect(const QT3DSVec2 &tl = QT3DSVec2(0, 0), const QT3DSVec2 &wh = QT3DSVec2(0, 0)) + : m_TopLeft(tl) + , m_WidthHeight(wh) + { + } + QT3DSF32 left() const { return m_TopLeft.x; } + QT3DSF32 top() const { return m_TopLeft.y; } + QT3DSF32 right() const { return m_TopLeft.x + width(); } + QT3DSF32 bottom() const { return m_TopLeft.y + height(); } + QT3DSF32 width() const { return m_WidthHeight.x; } + QT3DSF32 height() const { return m_WidthHeight.y; } + QT3DSVec2 topLeft() const { return m_TopLeft; } + QT3DSVec2 bottomLeft() const { return QT3DSVec2(left(), bottom()); } + QT3DSVec2 bottomRight() const { return QT3DSVec2(right(), bottom()); } + QT3DSVec2 topRight() const { return QT3DSVec2(right(), top()); } + QT3DSVec2 center() const + { + return QT3DSVec2(left() + width() / 2.0f, top() + height() / 2.0f); + } + SLine leftEdge() const { return SLine(topLeft(), bottomLeft()); } + SLine rightEdge() const { return SLine(topRight(), bottomRight()); } + SLine topEdge() const { return SLine(topLeft(), topRight()); } + SLine bottomEdge() const { return SLine(bottomLeft(), bottomRight()); } + void translate(QT3DSF32 x, QT3DSF32 y) + { + m_TopLeft.x += x; + m_TopLeft.y += y; + } + void translate(const QT3DSVec2 &vec) + { + m_TopLeft.x += vec.x; + m_TopLeft.y += vec.y; + } + bool contains(const QT3DSVec2 &inPoint) const; + }; + + struct SControlPoint + { + DirectionTypes::Enum m_Direction; + // World space position + QT3DSF32 m_Position; + // This is a calculated value. Values set will be ignored in favor of recaculating by + // re-deriving + // the transition path. + mutable QT3DSI32 m_PathIndex; + SControlPoint(DirectionTypes::Enum inDir = DirectionTypes::UnknownDirection, + QT3DSF32 pos = 0.0f, QT3DSI32 idx = -1) + : m_Direction(inDir) + , m_Position(pos) + , m_PathIndex(idx) + { + } + bool operator<(const SControlPoint &inOther) const + { + return m_PathIndex < inOther.m_PathIndex; + } + static QT3DSF32 GetComponent(const QT3DSVec2 &inPoint, DirectionTypes::Enum inDir) + { + switch (inDir) { + case DirectionTypes::Horizontal: + return inPoint.y; + break; + case DirectionTypes::Vertical: + return inPoint.x; + break; + default: + QT3DS_ASSERT(false); + break; + } + return 0; + } + static DirectionTypes::Enum OppositeDirection(DirectionTypes::Enum inDir) + { + switch (inDir) { + case DirectionTypes::Horizontal: + return DirectionTypes::Vertical; + default: + return DirectionTypes::Horizontal; + } + } + static QT3DSF32 GetOrthogonalComponent(const QT3DSVec2 &inPoint, DirectionTypes::Enum inDir) + { + switch (inDir) { + case DirectionTypes::Horizontal: + return inPoint.x; + default: + return inPoint.y; + } + } + static QT3DSVec2 FromComponentToVector(QT3DSF32 inComponent, QT3DSF32 orthoComponent, + DirectionTypes::Enum inDir) + { + switch (inDir) { + case DirectionTypes::Horizontal: + return QT3DSVec2(orthoComponent, inComponent); + default: + return QT3DSVec2(inComponent, orthoComponent); + } + } + static QT3DSVec2 SetComponent(const QT3DSVec2 &inPoint, QT3DSF32 inValue, + DirectionTypes::Enum inDir) + { + switch (inDir) { + case DirectionTypes::Horizontal: + return QT3DSVec2(inPoint.x, inValue); + default: + return QT3DSVec2(inValue, inPoint.y); + } + } + void Set(const QT3DSVec2 &inPoint) { m_Position = GetComponent(inPoint, m_Direction); } + SLine ToLine() const + { + switch (m_Direction) { + case DirectionTypes::Horizontal: + return SLine(QT3DSVec2(0, m_Position), QT3DSVec2(1, m_Position)); + default: + return SLine(QT3DSVec2(m_Position, 0), QT3DSVec2(m_Position, 1)); + } + } + }; + + typedef eastl::vector<SControlPoint> TControlPointList; + typedef eastl::vector<QT3DSVec2> TPointList; + + struct PointQueryResultType + { + enum Enum { + NoPoint = 0, + Begin, + End, + Control, + }; + }; + + struct SPointQueryResult + { + PointQueryResultType::Enum m_QueryType; + QT3DSI32 m_Index; + SPointQueryResult( + PointQueryResultType::Enum inResultType = PointQueryResultType::NoPoint, + QT3DSI32 inIndex = -1) + : m_QueryType(inResultType) + , m_Index(inIndex) + { + } + }; + + struct TransitionPathTypes + { + enum Enum { + UnknownPathType = 0, + BeginToEnd = 1, + BeginToBegin = 2, + Targetless = 3, + }; + }; + + class CEditorTransitionPath + { + QT3DSF32 m_StateEdgeBuffer; + Option<SRect> m_BeginRect; + Option<SRect> m_EndRect; + SEndPoint m_Begin; + SEndPoint m_End; + // Control points that the user has edited. + TControlPointList m_ControlPoints; + TransitionPathTypes::Enum m_TransitionPathType; + + // ephemeral data, can be derived from start,end points (along with start/end state) and + // the + // user control points. + mutable TPointList m_Path; + // The full set of possible control points can be derived from the path. + mutable TControlPointList m_PossibleControlPoints; + + public: + CEditorTransitionPath(QT3DSF32 inStateEdgeBuffer = 10.0f) + : m_StateEdgeBuffer(inStateEdgeBuffer) + , m_TransitionPathType(TransitionPathTypes::BeginToEnd) + { + } + Option<SRect> GetBeginRect() const { return m_BeginRect; } + void SetBeginRect(const Option<SRect> &inRect) + { + m_BeginRect = inRect; + MarkDirty(); + } + + Option<SRect> GetEndRect() const { return m_EndRect; } + void SetEndRect(const Option<SRect> &inRect) + { + m_EndRect = inRect; + MarkDirty(); + } + + TransitionPathTypes::Enum GetPathType() const { return m_TransitionPathType; } + // May delete all the control points. + void SetPathType(TransitionPathTypes::Enum inType); + // This may move control points if instart rect and inendrect have shifted by similar + // amounts + // Returns true if persistent data changed, false otherwise. + bool UpdateBeginEndRects(const SRect &inStartRect, const SRect &inEndRect); + bool UpdateBeginEndRects(const SRect &inStartRect, + TransitionPathTypes::Enum inPathType); + eastl::pair<QT3DSVec2, QT3DSVec2> GetBeginEndPoints() const; + + SEndPoint GetEndPoint() const { return m_End; } + void SetEndPoint(const SEndPoint &pt) + { + m_End = pt; + MarkDirty(); + } + void SetEndPoint(const QT3DSVec2 &inWorldPoint); + + SEndPoint GetBeginPoint() const { return m_Begin; } + void SetBeginPoint(const SEndPoint &pt) + { + m_Begin = pt; + MarkDirty(); + } + void SetBeginPoint(const QT3DSVec2 &inWorldPoint); + // Return where the end point will be. Will not return an invalid in point + // or an end point where information is not set. + SEndPoint GetActualBeginPoint() const; + SEndPoint GetActualEndPoint() const; + + TControlPointList GetControlPoints() const { return m_ControlPoints; } + SControlPoint GetControlPoint(QT3DSI32 inIndex) const + { + if (inIndex < (QT3DSI32)m_ControlPoints.size()) + return m_ControlPoints[inIndex]; + return SControlPoint(); + } + void SetControlPoints(const TControlPointList &list) + { + m_ControlPoints = list; + MarkDirty(); + } + void SetControlPoint(QT3DSI32 inControlPointIndex, const QT3DSVec2 &inPosition, + bool inMoveAdjacentEndPoint); + + // Set the control points by updating the individual items in the path list. + // This works if you do not add/remove points from the list. + // + // list = GetPath + // move points, don't add,delete + // setControlPoints(list) + void SetControlPoints(const TPointList &inList); + TPointList GetPath() const; + TControlPointList GetPossibleControlPoints() const; + SControlPoint GetPossibleControlPoint(QT3DSI32 inIdx) const; + + // This may create a new control point thus invalidating the path and possible control + // points. + QT3DSI32 MapPossibleControlPointToControlPoint(QT3DSI32 inPossiblePointIndex); + QT3DSI32 MapControlPointToPossibleControlPoint(QT3DSI32 inPointIndex); + // Returns true if MapPossibleControlPointToControlPoint will *not* create a new point + // false otherwise. + bool DoesControlPointExist(QT3DSI32 inPossiblePointIndex) const; + + // Returns true if any points were removed. + bool RemoveRedundantControlPoints(); + + void RestoreAutoTransition(); + + bool IsManualMode() const; + + // Query against the dataset to see what got picked. Includes the endoints. + // The rects should be centered about the original and just describe the picking + // rect to hit against. Empty rects will not get hit. + // Note that the result is the index into the possible point list where the hit + // happened. + // If you intend to manipulate the point, you need to call + // MapPossibleControlPointToControlPoint, + // Which may add a control point which you then can manipulate. + SPointQueryResult Pick(QT3DSVec2 inPoint, QT3DSVec2 inControlBoxDims, + QT3DSVec2 inEndBoxDims = QT3DSVec2(0, 0)); + SPointQueryResult PickClosestControlPoint(QT3DSVec2 inPoint, + DirectionTypes::Enum inDirectionType); + + void MarkDirty() + { + m_Path.clear(); + m_PossibleControlPoints.clear(); + for (size_t idx = 0, end = m_ControlPoints.size(); idx < end; ++idx) + m_ControlPoints[idx].m_PathIndex = -1; + } + + bool IsDirty() const { return m_Path.empty(); } + + static SEndPoint CalculateEndPoint(const SRect &inRect, const QT3DSVec2 &inPoint, + QT3DSF32 inEdgeBoundary); + static SEndPoint CalculateDefaultEndPoint(const SRect &inRect, const QT3DSVec2 &inPoint); + static bool AreAlmostEqual(QT3DSF32 lhs, QT3DSF32 rhs, QT3DSF32 error = .001f) + { + if (fabs(lhs - rhs) < error) + return true; + return false; + } + + static bool AreAlmostEqual(const QT3DSVec2 &lhs, const QT3DSVec2 &rhs, QT3DSF32 error = .001f) + { + return AreAlmostEqual(lhs.x, rhs.x, error) && AreAlmostEqual(lhs.y, rhs.y, error); + } + + // Regenerates the path if this object is dirty. + void MaybeRegeneratePath() const; + + eastl::pair<QT3DSVec2, EdgeTypes::Enum> GetBeginPointAndEdge() const; + + protected: + // Output the path between points that have the same direction. + SControlPoint OutputParallelPoints(const SControlPoint &inLastPoint, + const SControlPoint &inCurrentPoint, + QT3DSF32 inRunWidth) const; + SControlPoint OutputOrthogonalPoints(const SControlPoint &inLastPoint, + const SControlPoint &inCurrentPoint) const; + }; + } +} +} + +#endif
\ No newline at end of file diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorValue.h b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorValue.h new file mode 100644 index 00000000..d47e6c99 --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorValue.h @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QT3DS_STATE_EDITOR_VALUE_H +#define QT3DS_STATE_EDITOR_VALUE_H +#pragma once +#include "Qt3DSState.h" +#include "Qt3DSStateEditor.h" +#include "foundation/Qt3DSDiscriminatedUnion.h" +#include "foundation/Qt3DSVec3.h" + +namespace qt3ds { +namespace state { + namespace editor { + + struct ValueTypes + { + enum Enum { + NoEditorValue = 0, + String, + ObjPtr, + ObjPtrList, + Vec2, + Vec2List, + Vec3, + Boolean, + U32, + }; + }; + + template <typename TDataType> + struct SValueTypeMap + { + static ValueTypes::Enum GetType() { return ValueTypes::NoEditorValue; } + }; + + template <> + struct SValueTypeMap<TEditorStr> + { + static ValueTypes::Enum GetType() { return ValueTypes::String; } + }; + template <> + struct SValueTypeMap<TObjPtr> + { + static ValueTypes::Enum GetType() { return ValueTypes::ObjPtr; } + }; + template <> + struct SValueTypeMap<TObjList> + { + static ValueTypes::Enum GetType() { return ValueTypes::ObjPtrList; } + }; + template <> + struct SValueTypeMap<QT3DSVec2> + { + static ValueTypes::Enum GetType() { return ValueTypes::Vec2; } + }; + template <> + struct SValueTypeMap<TVec2List> + { + static ValueTypes::Enum GetType() { return ValueTypes::Vec2List; } + }; + template <> + struct SValueTypeMap<QT3DSVec3> + { + static ValueTypes::Enum GetType() { return ValueTypes::Vec3; } + }; + template <> + struct SValueTypeMap<bool> + { + static ValueTypes::Enum GetType() { return ValueTypes::Boolean; } + }; + template <> + struct SValueTypeMap<QT3DSU32> + { + static ValueTypes::Enum GetType() { return ValueTypes::U32; } + }; + + struct SValueUnionTraits + { + typedef ValueTypes::Enum TIdType; + enum { + TBufferSize = sizeof(TVec2List), + }; + + static TIdType getNoDataId() { return ValueTypes::NoEditorValue; } + + template <typename TDataType> + static TIdType getType() + { + return SValueTypeMap<TDataType>().GetType(); + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case ValueTypes::String: + return inVisitor(*NVUnionCast<TEditorStr *>(inData)); + case ValueTypes::ObjPtr: + return inVisitor(*NVUnionCast<TObjPtr *>(inData)); + case ValueTypes::ObjPtrList: + return inVisitor(*NVUnionCast<TObjList *>(inData)); + case ValueTypes::Vec2: + return inVisitor(*NVUnionCast<QT3DSVec2 *>(inData)); + case ValueTypes::Vec2List: + return inVisitor(*NVUnionCast<TVec2List *>(inData)); + case ValueTypes::Vec3: + return inVisitor(*NVUnionCast<QT3DSVec3 *>(inData)); + case ValueTypes::Boolean: + return inVisitor(*NVUnionCast<bool *>(inData)); + case ValueTypes::U32: + return inVisitor(*NVUnionCast<QT3DSU32 *>(inData)); + default: + QT3DS_ASSERT(false); + case ValueTypes::NoEditorValue: + return inVisitor(); + } + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case ValueTypes::String: + return inVisitor(*NVUnionCast<const TEditorStr *>(inData)); + case ValueTypes::ObjPtr: + return inVisitor(*NVUnionCast<const TObjPtr *>(inData)); + case ValueTypes::ObjPtrList: + return inVisitor(*NVUnionCast<const TObjList *>(inData)); + case ValueTypes::Vec2: + return inVisitor(*NVUnionCast<const QT3DSVec2 *>(inData)); + case ValueTypes::Vec2List: + return inVisitor(*NVUnionCast<const TVec2List *>(inData)); + case ValueTypes::Vec3: + return inVisitor(*NVUnionCast<const QT3DSVec3 *>(inData)); + case ValueTypes::Boolean: + return inVisitor(*NVUnionCast<const bool *>(inData)); + case ValueTypes::U32: + return inVisitor(*NVUnionCast<const QT3DSU32 *>(inData)); + default: + QT3DS_ASSERT(false); + case ValueTypes::NoEditorValue: + return inVisitor(); + } + } + }; + } +} +} + +#ifndef _INTEGRITYPLATFORM +// need some specializations in the original nv foundation namespace +namespace qt3ds { +namespace foundation { + + template <> + struct DestructTraits<QT3DSVec2> + { + void destruct(QT3DSVec2 &) {} + }; + template <> + struct DestructTraits<QT3DSVec3> + { + void destruct(QT3DSVec3 &) {} + }; +} +} +#endif + +namespace qt3ds { +namespace state { + namespace editor { + + typedef qt3ds::foundation:: + DiscriminatedUnion<qt3ds::foundation:: + DiscriminatedUnionGenericBase<SValueUnionTraits, + SValueUnionTraits::TBufferSize>, + SValueUnionTraits::TBufferSize> + TEditorUnionType; + + struct SValue : public TEditorUnionType + { + SValue() {} + + SValue(const SValue &inOther) + : TEditorUnionType(static_cast<const TEditorUnionType &>(inOther)) + { + } + + SValue(const char8_t *inOther) + : TEditorUnionType(TEditorStr(inOther)) + { + } + + template <typename TDataType> + SValue(const TDataType &inDt) + : TEditorUnionType(inDt) + { + } + + SValue &operator=(const SValue &inOther) + { + TEditorUnionType::operator=(inOther); + return *this; + } + + bool operator==(const SValue &inOther) const + { + return TEditorUnionType::operator==(inOther); + } + bool operator!=(const SValue &inOther) const + { + return TEditorUnionType::operator!=(inOther); + } + + bool empty() const { return getType() == ValueTypes::NoEditorValue; } + }; + + struct SValueOpt : public Option<SValue> + { + SValueOpt(const SValueOpt &inOther) + : Option<SValue>(inOther) + { + } + SValueOpt(const Empty &) + : Option<SValue>() + { + } + SValueOpt() {} + template <typename TDataType> + SValueOpt(const TDataType &inOther) + : Option<SValue>(SValue(inOther)) + { + } + SValueOpt &operator=(const SValueOpt &inOther) + { + Option<SValue>::operator=(inOther); + return *this; + } + }; + } +} +} + +#endif diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodel.cpp b/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodel.cpp new file mode 100644 index 00000000..f21fb27b --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodel.cpp @@ -0,0 +1,2527 @@ +/**************************************************************************** +** +** 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 "Qt3DSUIADatamodel.h" +#include "foundation/IOStreams.h" +#include "foundation/XML.h" +#include "foundation/FileTools.h" +#include "Qt3DSStateEditorFoundation.h" +#include "Qt3DSStateApplication.h" +#include "Qt3DSRenderInputStreamFactory.h" +#include "EASTL/map.h" +#include "EASTL/sort.h" +#include "Qt3DSStateEditorTransactionImpl.h" +#include "Qt3DSUIADatamodelValue.h" +#include "foundation/StringConversion.h" +#include "foundation/StringConversionImpl.h" +#include "Qt3DSDMStringTable.h" + +using namespace qt3ds::app; +using qt3ds::render::IInputStreamFactory; + +namespace qt3ds { +namespace app { + + struct ElementSubTypes + { + enum Enum { + NoSubType = 0, + Component = 1, + Behavior = 2, + }; + }; + + struct SAppElement + { + TEditorStr m_Path; + TEditorStr m_Type; + TEditorStr m_Name; + TEditorStr m_Id; + Q3DStudio::TAttOrArgList m_Attributes; + Q3DStudio::TVisualEventList m_VisualEvents; + eastl::vector<NVScopedRefCounted<SAppElement>> m_Children; + eastl::vector<SDatamodelValue> m_InitialValues; + + private: + QT3DSI32 m_RefCount; + + public: + SAppElement() + : m_RefCount(0) + { + } + + virtual ~SAppElement() {} + + void addRef() { ++m_RefCount; } + + void release() + { + --m_RefCount; + if (m_RefCount <= 0) + delete this; + } + + virtual ElementSubTypes::Enum GetSubType() { return ElementSubTypes::NoSubType; } + }; +} +} + +namespace { + +typedef eastl::basic_string<char8_t> TStrType; +struct IPropertyParser +{ + virtual ~IPropertyParser() {} + virtual Option<TStrType> ParseStr(const char8_t *inName) = 0; + virtual Option<QT3DSF32> ParseFloat(const char8_t *inName) = 0; + virtual Option<QT3DSVec2> ParseVec2(const char8_t *inName) = 0; + virtual Option<QT3DSVec3> ParseVec3(const char8_t *inName) = 0; + virtual Option<bool> ParseBool(const char8_t *inName) = 0; + virtual Option<QT3DSU32> ParseU32(const char8_t *inName) = 0; + virtual Option<QT3DSI32> ParseI32(const char8_t *inName) = 0; +}; + +struct SMetaPropertyParser : public IPropertyParser +{ + Q3DStudio::IRuntimeMetaData &m_MetaData; + IStringTable &m_StringTable; + TStrType m_TempStr; + qt3ds::foundation::CRegisteredString m_Type; + qt3ds::foundation::CRegisteredString m_ClassId; + + SMetaPropertyParser(const char8_t *inType, const char8_t *inClass, + Q3DStudio::IRuntimeMetaData &inMeta, IStringTable &stringTable) + : m_MetaData(inMeta) + , m_StringTable(stringTable) + , m_Type(stringTable.RegisterStr(inType)) + , m_ClassId(stringTable.RegisterStr(inClass)) + { + } + + qt3ds::foundation::CRegisteredString Register(const char8_t *inName) + { + return m_StringTable.RegisterStr(inName); + } + + virtual Option<TStrType> ParseStr(const char8_t *inName) + { + qt3ds::foundation::CRegisteredString theName(Register(inName)); + Q3DStudio::ERuntimeDataModelDataType theType( + m_MetaData.GetPropertyType(m_Type, theName, m_ClassId)); + if (theType != Q3DStudio::ERuntimeDataModelDataTypeObjectRef + && theType != Q3DStudio::ERuntimeDataModelDataTypeLong4) { + return m_MetaData.GetPropertyValueString(m_Type, theName, m_ClassId); + } + return Empty(); + } + virtual Option<QT3DSF32> ParseFloat(const char8_t *inName) + { + return m_MetaData.GetPropertyValueFloat(m_Type, Register(inName), m_ClassId); + } + virtual Option<QT3DSVec2> ParseVec2(const char8_t *inName) + { + Option<qt3ds::QT3DSVec3> theProperty = + m_MetaData.GetPropertyValueVector2(m_Type, Register(inName), m_ClassId); + if (theProperty.hasValue()) { + return QT3DSVec2(theProperty->x, theProperty->y); + } + return Empty(); + } + virtual Option<QT3DSVec3> ParseVec3(const char8_t *inName) + { + Option<qt3ds::QT3DSVec3> theProperty = + m_MetaData.GetPropertyValueVector3(m_Type, Register(inName), m_ClassId); + if (theProperty.hasValue()) { + return *theProperty; + } + return Empty(); + } + virtual Option<bool> ParseBool(const char8_t *inName) + { + return m_MetaData.GetPropertyValueBool(m_Type, Register(inName), m_ClassId); + } + + virtual Option<QT3DSU32> ParseU32(const char8_t *inName) + { + Option<long> retval = m_MetaData.GetPropertyValueLong(m_Type, Register(inName), m_ClassId); + if (retval.hasValue()) + return (QT3DSU32)retval.getValue(); + return Empty(); + } + + virtual Option<QT3DSI32> ParseI32(const char8_t *inName) + { + Option<long> retval = m_MetaData.GetPropertyValueLong(m_Type, Register(inName), m_ClassId); + if (retval.hasValue()) + return (QT3DSI32)retval.getValue(); + return Empty(); + } +}; + +struct SDomReaderPropertyParser : public IPropertyParser +{ + IDOMReader &m_Reader; + nvvector<char8_t> &m_TempBuf; + MemoryBuffer<ForwardingAllocator> &m_ReadBuffer; + + SDomReaderPropertyParser(IDOMReader &reader, nvvector<char8_t> &inTempBuf, + MemoryBuffer<ForwardingAllocator> &readBuf) + : m_Reader(reader) + , m_TempBuf(inTempBuf) + , m_ReadBuffer(readBuf) + { + } + virtual Option<TStrType> ParseStr(const char8_t *inName) + { + const char8_t *retval; + if (m_Reader.UnregisteredAtt(inName, retval)) + return TStrType(retval); + return Empty(); + } + virtual Option<QT3DSF32> ParseFloat(const char8_t *inName) + { + QT3DSF32 retval; + if (m_Reader.Att(inName, retval)) + return retval; + return Empty(); + } + virtual Option<QT3DSVec2> ParseVec2(const char8_t *inName) + { + QT3DSVec2 retval; + const char8_t *tempVal; + + if (m_Reader.UnregisteredAtt(inName, tempVal)) { + eastl::string tempBuffer(tempVal); + Char8TReader theReader(const_cast<char *>(tempBuffer.c_str()), m_ReadBuffer); + NVDataRef<QT3DSF32> theDataRef(toDataRef(&retval.x, 2)); + theReader.ReadRef(theDataRef); + return retval; + } + return Empty(); + } + virtual Option<QT3DSVec3> ParseVec3(const char8_t *inName) + { + QT3DSVec3 retval; + const char8_t *tempVal; + if (m_Reader.UnregisteredAtt(inName, tempVal)) { + eastl::string tempBuffer(tempVal); + Char8TReader theReader(const_cast<char *>(tempBuffer.c_str()), m_ReadBuffer); + NVDataRef<QT3DSF32> theDataRef(toDataRef(&retval.x, 3)); + theReader.ReadRef(theDataRef); + return retval; + } + return Empty(); + } + virtual Option<bool> ParseBool(const char8_t *inName) + { + bool retval; + if (m_Reader.Att(inName, retval)) + return retval; + return Empty(); + } + + virtual Option<QT3DSU32> ParseU32(const char8_t *inName) + { + QT3DSU32 retval; + if (m_Reader.Att(inName, retval)) + return retval; + return Empty(); + } + + virtual Option<QT3DSI32> ParseI32(const char8_t *inName) + { + QT3DSI32 retval; + if (m_Reader.Att(inName, retval)) + return retval; + return Empty(); + } +}; + +template <typename TDataType> +struct SParserHelper +{ +}; +template <> +struct SParserHelper<TStrType> +{ + static Option<TStrType> Parse(const char8_t *inName, IPropertyParser &inParser) + { + return inParser.ParseStr(inName); + } +}; +template <> +struct SParserHelper<QT3DSF32> +{ + static Option<QT3DSF32> Parse(const char8_t *inName, IPropertyParser &inParser) + { + return inParser.ParseFloat(inName); + } +}; +template <> +struct SParserHelper<QT3DSVec2> +{ + static Option<QT3DSVec2> Parse(const char8_t *inName, IPropertyParser &inParser) + { + return inParser.ParseVec2(inName); + } +}; +template <> +struct SParserHelper<QT3DSVec3> +{ + static Option<QT3DSVec3> Parse(const char8_t *inName, IPropertyParser &inParser) + { + return inParser.ParseVec3(inName); + } +}; +template <> +struct SParserHelper<bool> +{ + static Option<bool> Parse(const char8_t *inName, IPropertyParser &inParser) + { + return inParser.ParseBool(inName); + } +}; +template <> +struct SParserHelper<QT3DSU32> +{ + static Option<QT3DSU32> Parse(const char8_t *inName, IPropertyParser &inParser) + { + return inParser.ParseU32(inName); + } +}; +template <> +struct SParserHelper<QT3DSI32> +{ + static Option<QT3DSI32> Parse(const char8_t *inName, IPropertyParser &inParser) + { + return inParser.ParseI32(inName); + } +}; + +struct SSlideInfo +{ + TEditorStr m_Name; +}; + +struct SAppComponent : public SAppElement +{ + TEditorStrList m_Slides; + virtual ElementSubTypes::Enum GetSubType() { return ElementSubTypes::Component; } +}; + +struct SAppBehavior : public SAppElement +{ + Q3DStudio::THandlerList m_Handlers; + virtual ElementSubTypes::Enum GetSubType() { return ElementSubTypes::Behavior; } +}; + +struct SVSEditorObject; + +struct SVSEditor +{ + TFoundationPtr m_Foundation; + NVScopedRefCounted<STransactionManagerImpl> m_TransactionManager; + NVScopedRefCounted<IStringTable> m_StringTable; + SVSEditor(TFoundationPtr fnd, IStringTable &inStringTable) + : m_Foundation(fnd) + , m_TransactionManager(QT3DS_NEW(m_Foundation->getAllocator(), STransactionManagerImpl)(fnd)) + , m_StringTable(inStringTable) + { + } + + virtual void RemoveObjectFromGraph(SVSEditorObject &inObj) = 0; + STransaction *GetOpenTransactionImpl() + { + return m_TransactionManager->GetOpenTransactionImpl(); + } +}; + +struct SVSEditorObject : public IEditorObject +{ + TFoundationPtr m_Foundation; + SVSEditor &m_Editor; + TObjPtr m_ParentObject; + TPropertyAccessorList m_PropertyAccessors; + QT3DSI32 mRefCount; + SVSEditorObject(SVSEditor &editor, TObjPtr parent, const char8_t *typeName, + const TPropertyAccessorList &ioList) + : IEditorObject(typeName) + , m_Foundation(editor.m_Foundation) + , m_Editor(editor) + , m_ParentObject(parent) + , m_PropertyAccessors(ioList) + , mRefCount(0) + { + } + + virtual TEditorStr GetId() { return TEditorStr(); } + virtual TEditorStr GetDescription() { return TEditorStr(); } + virtual void SetId(const TEditorStr &) { QT3DS_ASSERT(false); } + virtual void SetDescription(const TEditorStr &) { QT3DS_ASSERT(false); } + + virtual TObjPtr Parent() { return m_ParentObject; } + + IPropertyAccessor *FindPropertyAccessor(const char8_t *inName) + { + CRegisteredString nameStr = m_Editor.m_StringTable->RegisterStr(inName); + for (size_t idx = 0, end = m_PropertyAccessors.size(); idx < end; ++idx) + if (m_PropertyAccessors[idx]->m_Declaration.m_Name == nameStr) + return m_PropertyAccessors[idx].mPtr; + return NULL; + } + + virtual void GetProperties(eastl::vector<SPropertyDeclaration> &outProperties) + { + outProperties.clear(); + for (size_t idx = 0, end = m_PropertyAccessors.size(); idx < end; ++idx) + outProperties.push_back(m_PropertyAccessors[idx]->m_Declaration); + } + + virtual Option<SPropertyDeclaration> FindProperty(const char8_t *propName) + { + for (size_t idx = 0, end = m_PropertyAccessors.size(); idx < end; ++idx) { + if (AreEqual(m_PropertyAccessors[idx]->m_Declaration.m_Name.c_str(), propName)) + return m_PropertyAccessors[idx]->m_Declaration; + } + return Empty(); + } + + virtual eastl::vector<CRegisteredString> GetLegalValues(const char8_t *) + { + return eastl::vector<CRegisteredString>(); + } + + virtual Option<SValue> GetPropertyValue(const char8_t *inPropName) + { + IPropertyAccessor *accessor = FindPropertyAccessor(inPropName); + if (accessor) { + return accessor->Get(*this); + } + return Empty(); + } + virtual void SetPropertyValue(const char8_t *inPropName, const SValueOpt &inValue) + { + IPropertyAccessor *accessor = FindPropertyAccessor(inPropName); + if (accessor) { + STransaction *theTransaction = m_Editor.GetOpenTransactionImpl(); + if (theTransaction) { + Option<SValue> existing = accessor->Get(*this); + theTransaction->m_Changes.push_back( + new SChange(existing, inValue, *accessor, *this)); + } + accessor->Set(*this, inValue); + } else { + QT3DS_ASSERT(false); + } + } + + virtual void RemoveObjectFromGraph() { m_Editor.RemoveObjectFromGraph(*this); } + virtual void RemoveIdFromContext() {} + virtual void AddIdToContext() {} + virtual IEditor &GetEditor() { return m_ParentObject->GetEditor(); } +}; + +template <typename TEditorType> +struct SGenericPropertyStringAccessor : public SPropertyAccessorBase<TEditorType> +{ + typedef TEditorStr TDataType; + typedef TDataType TEditorType::*TPropertyPtr; + typedef SPropertyAccessorBase<TEditorType> TBaseType; + + TPropertyPtr m_Property; + + SGenericPropertyStringAccessor(TFoundationPtr inAlloc, const SPropertyDeclaration &inDec, + TPropertyPtr inPtr) + : TBaseType(inAlloc, inDec) + , m_Property(inPtr) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + TDataType &dtype = inObj.*(this->m_Property); + return SValue(dtype); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + TDataType &dtype = inObj.*(this->m_Property); + dtype.clear(); + if (inValue.hasValue() && inValue->getType() == ValueTypes::String) + dtype = inValue->getData<TEditorStr>(); + } +}; + +template <typename TEditorType> +struct SGenericPropertyStringFunctionAccessor : public SPropertyAccessorBase<TEditorType> +{ + typedef TEditorStr TDataType; + typedef TDataType (TEditorType::*TGetter)() const; + typedef void (TEditorType::*TSetter)(const TDataType &); + typedef SPropertyAccessorBase<TEditorType> TBaseType; + + TGetter m_Getter; + TSetter m_Setter; + + SGenericPropertyStringFunctionAccessor(TFoundationPtr inAlloc, + const SPropertyDeclaration &inDec, TGetter inGetter, + TSetter inSetter) + : TBaseType(inAlloc, inDec) + , m_Getter(inGetter) + , m_Setter(inSetter) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Allocator, this->mRefCount); + + virtual Option<SValue> DoGet(TEditorType &inObj) + { + TDataType dtype = (inObj.*m_Getter)(); + return SValue(dtype); + } + + virtual void DoSet(TEditorType &inObj, const Option<SValue> &inValue) + { + TDataType dtype; + dtype.clear(); + if (inValue.hasValue() && inValue->getType() == ValueTypes::String) + dtype = inValue->getData<TEditorStr>(); + + (inObj.*m_Setter)(dtype); + } +}; + +struct SGotoSlideEditor : public SVSEditorObject +{ + TEditorStr m_Component; + TEditorStr m_Slide; + TEditorStr m_Rel; + TEditorStr m_Wrap; + TEditorStr m_Direction; + TEditorStr m_State; + TEditorStr m_Mode; + TEditorStr m_PlayThroughTo; + TEditorStr m_Rate; + TEditorStr m_Time; + static void CreateProperties(TPropertyAccessorList &ioList, SVSEditor &editor) + { + if (ioList.size()) + return; + + typedef SGenericPropertyStringAccessor<SGotoSlideEditor> TStrProp; + typedef SGenericPropertyStringFunctionAccessor<SGotoSlideEditor> TStrFunProp; + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("element"), + EditorPropertyTypes::String), + &SGotoSlideEditor::m_Component)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrFunProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("slide"), + EditorPropertyTypes::String), + &SGotoSlideEditor::GetSlide, &SGotoSlideEditor::SetSlide)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrFunProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("rel"), + EditorPropertyTypes::String), + &SGotoSlideEditor::GetRel, &SGotoSlideEditor::SetRel)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("wrap"), + EditorPropertyTypes::String), + &SGotoSlideEditor::m_Wrap)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, + SPropertyDeclaration(editor.m_StringTable->RegisterStr("direction"), + EditorPropertyTypes::String), + &SGotoSlideEditor::m_Direction)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("state"), + EditorPropertyTypes::String), + &SGotoSlideEditor::m_State)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("mode"), + EditorPropertyTypes::String), + &SGotoSlideEditor::m_Mode)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, + SPropertyDeclaration(editor.m_StringTable->RegisterStr("playthroughto"), + EditorPropertyTypes::String), + &SGotoSlideEditor::m_PlayThroughTo)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("rate"), + EditorPropertyTypes::String), + &SGotoSlideEditor::m_Rate)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("time"), + EditorPropertyTypes::String), + &SGotoSlideEditor::m_Time)); + } + + SGotoSlideEditor(SVSEditor &editor, TObjPtr parent, const TPropertyAccessorList &ioList) + : SVSEditorObject(editor, parent, ElementName(), ioList) + { + } + + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Foundation, this->mRefCount); + + static const char8_t *ElementName() { return "goto-slide"; } + + void SetSlide(const TEditorStr &inSlide) + { + if (inSlide.empty() == false) { + if (m_Editor.GetOpenTransactionImpl()) + SetPropertyValue("rel", SValueOpt()); + else + m_Rel.clear(); + } + m_Slide = inSlide; + } + + TEditorStr GetSlide() const { return m_Slide; } + + void SetRel(const TEditorStr &inRel) + { + if (inRel.empty() == false) { + if (m_Editor.GetOpenTransactionImpl()) + SetPropertyValue("slide", SValueOpt()); + else + m_Slide.clear(); + } + m_Rel = inRel; + } + + TEditorStr GetRel() const { return m_Rel; } +}; + +struct SRunHandlerEditor : public SVSEditorObject +{ + TEditorStr m_Behavior; + TEditorStr m_Handler; + TEditorStr m_ArgumentStr; + static void CreateProperties(TPropertyAccessorList &ioList, SVSEditor &editor) + { + if (ioList.size()) + return; + + typedef SGenericPropertyStringAccessor<SRunHandlerEditor> TStrProp; + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("element"), + EditorPropertyTypes::String), + &SRunHandlerEditor::m_Behavior)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("handler"), + EditorPropertyTypes::String), + &SRunHandlerEditor::m_Handler)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, + SPropertyDeclaration(editor.m_StringTable->RegisterStr("arguments"), + EditorPropertyTypes::String), + &SRunHandlerEditor::m_ArgumentStr)); + } + SRunHandlerEditor(SVSEditor &editor, TObjPtr parent, const TPropertyAccessorList &ioList) + : SVSEditorObject(editor, parent, ElementName(), ioList) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Foundation, this->mRefCount); + static const char8_t *ElementName() { return "call"; } +}; + +struct SSetAttributeEditor : public SVSEditorObject +{ + TEditorStr m_Element; + TEditorStr m_Attribute; + TEditorStr m_Value; + + static void CreateProperties(TPropertyAccessorList &ioList, SVSEditor &editor) + { + if (ioList.size()) + return; + + typedef SGenericPropertyStringAccessor<SSetAttributeEditor> TStrProp; + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("element"), + EditorPropertyTypes::String), + &SSetAttributeEditor::m_Element)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, + SPropertyDeclaration(editor.m_StringTable->RegisterStr("attribute"), + EditorPropertyTypes::String), + &SSetAttributeEditor::m_Attribute)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("value"), + EditorPropertyTypes::String), + &SSetAttributeEditor::m_Value)); + } + SSetAttributeEditor(SVSEditor &editor, TObjPtr parent, const TPropertyAccessorList &ioList) + : SVSEditorObject(editor, parent, ElementName(), ioList) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Foundation, this->mRefCount); + static const char8_t *ElementName() { return "set-attribute"; } +}; + +struct SFireEventEditor : public SVSEditorObject +{ + TEditorStr m_Element; + TEditorStr m_Event; + + static void CreateProperties(TPropertyAccessorList &ioList, SVSEditor &editor) + { + if (ioList.size()) + return; + + typedef SGenericPropertyStringAccessor<SFireEventEditor> TStrProp; + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("element"), + EditorPropertyTypes::String), + &SFireEventEditor::m_Element)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("event"), + EditorPropertyTypes::String), + &SFireEventEditor::m_Event)); + } + SFireEventEditor(SVSEditor &editor, TObjPtr parent, const TPropertyAccessorList &ioList) + : SVSEditorObject(editor, parent, ElementName(), ioList) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Foundation, this->mRefCount); + static const char8_t *ElementName() { return "fire-event"; } +}; + +struct SSetPresentationEditor : public SVSEditorObject +{ + TEditorStr m_Ref; + TEditorStr m_Attribute; + TEditorStr m_Value; + + static void CreateProperties(TPropertyAccessorList &ioList, SVSEditor &editor) + { + if (ioList.size()) + return; + + typedef SGenericPropertyStringAccessor<SSetPresentationEditor> TStrProp; + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("ref"), + EditorPropertyTypes::String), + &SSetPresentationEditor::m_Ref)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, + SPropertyDeclaration(editor.m_StringTable->RegisterStr("attribute"), + EditorPropertyTypes::String), + &SSetPresentationEditor::m_Attribute)); + + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("value"), + EditorPropertyTypes::String), + &SSetPresentationEditor::m_Value)); + } + SSetPresentationEditor(SVSEditor &editor, TObjPtr parent, const TPropertyAccessorList &ioList) + : SVSEditorObject(editor, parent, ElementName(), ioList) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Foundation, this->mRefCount); + static const char8_t *ElementName() { return "set-presentation"; } +}; + +struct SPlaySoundEditor : public SVSEditorObject +{ + TEditorStr m_FilePath; + + static void CreateProperties(TPropertyAccessorList &ioList, SVSEditor &editor) + { + if (ioList.size()) + return; + + typedef SGenericPropertyStringAccessor<SPlaySoundEditor> TStrProp; + ioList.push_back(QT3DS_NEW(editor.m_Foundation->getAllocator(), TStrProp)( + editor.m_Foundation, SPropertyDeclaration(editor.m_StringTable->RegisterStr("file"), + EditorPropertyTypes::String), + &SPlaySoundEditor::m_FilePath)); + } + SPlaySoundEditor(SVSEditor &editor, TObjPtr parent, const TPropertyAccessorList &ioList) + : SVSEditorObject(editor, parent, ElementName(), ioList) + { + } + QT3DS_STATE_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(this->m_Foundation, this->mRefCount); + static const char8_t *ElementName() { return "play-sound"; } +}; + +struct SVSEntry +{ + QT3DSI32 mRefCount; + InterpreterEventTypes::Enum m_EventType; + TObjList m_Editors; + + SVSEntry(InterpreterEventTypes::Enum evtType = InterpreterEventTypes::UnknownInterpreterEvent) + : mRefCount(0) + , m_EventType(evtType) + { + } + void addRef() { ++mRefCount; } + void release() + { + --mRefCount; + if (mRefCount <= 0) + delete this; + } +}; + +struct SVSEntryListChange : public IChange +{ + TObjPtr m_Object; + NVScopedRefCounted<SVSEntry> m_Entry; + QT3DSI32 m_Index; + bool m_AddOnDo; + TObjPtr m_EditorObj; + QT3DSI32 m_RefCount; + + SVSEntryListChange(TObjPtr inObj, TObjPtr inPositionObj, SVSEntry &entry, bool addOnDo, + TObjPtr inEditorObj) + : m_Object(inObj) + , m_Entry(entry) + , m_AddOnDo(addOnDo) + , m_EditorObj(inEditorObj) + , m_RefCount(0) + { + TObjList::iterator iter = + eastl::find(entry.m_Editors.begin(), entry.m_Editors.end(), inPositionObj); + m_Index = iter - entry.m_Editors.begin(); + } + void addRef() { ++m_RefCount; } + void release() + { + --m_RefCount; + if (m_RefCount <= 0) + delete this; + } + void add() { m_Entry->m_Editors.insert(m_Entry->m_Editors.begin() + m_Index, m_Object); } + void remove() + { + TObjList::iterator iter = + eastl::find(m_Entry->m_Editors.begin(), m_Entry->m_Editors.end(), m_Object); + if (iter != m_Entry->m_Editors.end()) + m_Entry->m_Editors.erase(iter); + else { + QT3DS_ASSERT(false); + } + } + + virtual void Do() + { + if (m_AddOnDo) + add(); + else + remove(); + } + virtual void Undo() + { + if (m_AddOnDo) + remove(); + else + add(); + } + virtual TObjPtr GetEditor() { return m_EditorObj; } +}; + +typedef eastl::pair<NVScopedRefCounted<SVSEntry>, NVScopedRefCounted<SVSEntry>> TEntryExitPair; +typedef eastl::map<TEditorStr, TEntryExitPair> TIdEntryExitMap; +typedef eastl::map<TEditorStr, TIdEntryExitMap> TIdStateMapMap; + +struct SIdEntryExitDeleteChange : public IChange +{ + TIdEntryExitMap &m_Map; + TEditorStr m_Id; + TEntryExitPair m_Data; + QT3DSI32 m_RefCount; + TObjPtr m_EditorObject; + bool m_RemoveOnDo; + SIdEntryExitDeleteChange(TIdEntryExitMap &inMap, const TEditorStr &inId, TObjPtr inObj, + bool removeOnDo = true) + : m_Map(inMap) + , m_Id(inId) + , m_RefCount(0) + , m_EditorObject(inObj) + , m_RemoveOnDo(removeOnDo) + { + m_Data = m_Map[m_Id]; + } + virtual void addRef() { ++m_RefCount; } + virtual void release() + { + --m_RefCount; + if (m_RefCount <= 0) + delete this; + } + void remove() + { + TIdEntryExitMap::iterator iter = m_Map.find(m_Id); + if (iter != m_Map.end()) + m_Map.erase(iter); + else { + QT3DS_ASSERT(false); + } + } + void insert() { m_Map[m_Id] = m_Data; } + virtual void Do() + { + if (m_RemoveOnDo) + remove(); + else + insert(); + } + virtual void Undo() + { + if (m_RemoveOnDo) + insert(); + else + remove(); + } + virtual TObjPtr GetEditor() { return m_EditorObject; } +}; + +struct SEditorEntry +{ + TEditorPtr m_Editor; + TEditorStr m_Id; + TEditorStr m_FilePath; +}; + +template <typename TDataType> +SDatamodelValue ParseInitialProperty(const char8_t *inName, IPropertyParser &metaParser, + IPropertyParser &runtimeParser) +{ + Option<TDataType> val = SParserHelper<TDataType>::Parse(inName, runtimeParser); + if (val.isEmpty()) + val = SParserHelper<TDataType>::Parse(inName, metaParser); + if (val.hasValue()) + return SDatamodelValue(*val); + return SDatamodelValue(); +} + +template <typename TDataType> +SDatamodelValue ParseSlideProperty(const char8_t *inName, IPropertyParser &runtimeParser) +{ + Option<TDataType> val = SParserHelper<TDataType>::Parse(inName, runtimeParser); + if (val.hasValue()) + return *val; + return SDatamodelValue(); +} + +struct DatamodelImpl : public IDatamodel, + public ITransManagerImplListener, + public SVSEditor, + public IEditorCopyPasteListener +{ + TEditorStr m_FilePath; + QT3DSI32 mRefCount; + NVScopedRefCounted<IInputStreamFactory> m_InputStreamFactory; + eastl::pair<NVScopedRefCounted<IDOMWriter>, NVScopedRefCounted<IDOMReader>> m_UIADocument; + nvvector<SNamespacePair> m_UIANamespaces; + bool m_Dirty; + nvvector<NVScopedRefCounted<SAppElement>> m_Elements; + NVScopedRefCounted<SAppElement> m_FinderElement; + nvvector<SEditorEntry> m_Editors; + TIdStateMapMap m_IdToStateMaps; + eastl::vector<SPresentation> m_Presentations; + nvvector<char8_t> m_ParseBuf; + Q3DStudio::TAttOrArgList m_SlideProperties; + eastl::string m_LoadingErrorString; + IStateMachineEditorManager &m_StateMachineEditorManager; + + DatamodelImpl(TFoundationPtr inFoundation, const TEditorStr &inPath, const TEditorStr &inAppDir, + IStateMachineEditorManager &inStateMachineEditorManager, + IStringTable &inStringTable) + : SVSEditor(inFoundation, inStringTable) + , m_FilePath(inPath) + , mRefCount(0) + , m_InputStreamFactory(IInputStreamFactory::Create(m_Foundation->getFoundation())) + , m_UIANamespaces(m_Foundation->getAllocator(), "m_UIANamespaces") + , m_Dirty(false) + , m_Elements(m_Foundation->getAllocator(), "m_Elements") + , m_Editors(m_Foundation->getAllocator(), "m_Editors") + , m_ParseBuf(inFoundation->getAllocator(), "tempbuf") + , m_StateMachineEditorManager(inStateMachineEditorManager) + { + TEditorStr ext; + CFileTools::GetExtension(m_FilePath.c_str(), ext); + if (ext.comparei("uia") != 0) { + m_FilePath = IApplication::GetLaunchFile(inPath.c_str()); + } + // Check extension, if it isn't what we expect then just set it to uia + ext.clear(); + CFileTools::GetExtension(m_FilePath.c_str(), ext); + if (ext.comparei("uia") != 0) + CFileTools::SetExtension(m_FilePath, "uia"); + + m_InputStreamFactory->AddSearchDirectory(inAppDir.c_str()); + + m_TransactionManager->m_ObjListener = this; + } + + ~DatamodelImpl() {} + + void addRef() { atomicIncrement(&mRefCount); } + + void release() + { + TFoundationPtr tempFoundation(m_Foundation); + atomicDecrement(&mRefCount); + if (mRefCount <= 0) { + NVDelete(m_Foundation->getAllocator(), this); + } + } + typedef eastl::map<TEditorStr, Q3DStudio::THandlerList> TIdHandlerMap; + typedef eastl::map<TEditorStr, Q3DStudio::TVisualEventList> TIdVisualEventMap; + typedef eastl::map<TEditorStr, NVScopedRefCounted<SAppElement>> TIdElemMap; + + SAppElement &ParseElement(IDOMReader &inReader, Q3DStudio::IRuntimeMetaData &inMetaData, + eastl::vector<NVScopedRefCounted<SAppElement>> &ioChildList, + TIdElemMap &ioElemMap) + { + IDOMReader::Scope __elemScope(inReader); + const char8_t *className = inReader.GetElementName(); + const char8_t *classRef; + inReader.UnregisteredAtt("class", classRef); + const char8_t *id; + inReader.UnregisteredAtt("id", id); + Q3DStudio::SElementInfo elemInfo = inMetaData.LoadElement(className, classRef, id); + NVScopedRefCounted<SAppElement> newElem; + if (elemInfo.m_ClassName.compare("Behavior") == 0) { + SAppBehavior *behavior = new SAppBehavior(); + behavior->m_Handlers = inMetaData.GetCustomHandlers(id); + newElem = behavior; + } else if (elemInfo.m_IsComponent) { + SAppComponent *component = new SAppComponent(); + newElem = component; + } else { + newElem = new SAppElement(); + } + newElem->m_Attributes = elemInfo.m_Attributes; + newElem->m_VisualEvents = inMetaData.GetVisualEvents(id); + newElem->m_Type = elemInfo.m_ClassName; + newElem->m_Id = id; + const char8_t *elemName; + if (inReader.UnregisteredAtt("name", elemName)) + newElem->m_Name.assign(elemName); + else { + Q3DStudio::TRuntimeMetaDataStrType str = inMetaData.GetPropertyValueString( + inMetaData.Register(className), inMetaData.Register("name"), + inMetaData.Register(id)); + newElem->m_Name = str; + } + + ioChildList.push_back(newElem); + m_Elements.push_back(newElem); + ioElemMap.insert(eastl::make_pair(TEditorStr(id), newElem)); + { + SMetaPropertyParser theMetaParser(newElem->m_Type.c_str(), classRef, inMetaData, + inMetaData.GetStringTable()->GetRenderStringTable()); + SDomReaderPropertyParser theDOMParser(inReader, m_ParseBuf, inReader.m_TempBuf); + for (size_t idx = 0, end = newElem->m_Attributes.size(); idx < end; ++idx) { + const SAttOrArg &theAtt(newElem->m_Attributes[idx]); + SDatamodelValue newValue; + switch (theAtt.m_DataType) { + case ERuntimeDataModelDataTypeFloat: + newValue = ParseInitialProperty<float>(theAtt.m_Name.c_str(), theMetaParser, + theDOMParser); + break; + case ERuntimeDataModelDataTypeFloat2: + newValue = ParseInitialProperty<QT3DSVec2>(theAtt.m_Name.c_str(), theMetaParser, + theDOMParser); + break; + case ERuntimeDataModelDataTypeFloat3: + newValue = ParseInitialProperty<QT3DSVec3>(theAtt.m_Name.c_str(), theMetaParser, + theDOMParser); + break; + case ERuntimeDataModelDataTypeLong: + newValue = ParseInitialProperty<QT3DSI32>(theAtt.m_Name.c_str(), theMetaParser, + theDOMParser); + break; + case ERuntimeDataModelDataTypeString: + newValue = ParseInitialProperty<eastl::string>(theAtt.m_Name.c_str(), + theMetaParser, theDOMParser); + break; + case ERuntimeDataModelDataTypeBool: + newValue = ParseInitialProperty<bool>(theAtt.m_Name.c_str(), theMetaParser, + theDOMParser); + break; + case ERuntimeDataModelDataTypeStringRef: + newValue = ParseInitialProperty<eastl::string>(theAtt.m_Name.c_str(), + theMetaParser, theDOMParser); + break; + case ERuntimeDataModelDataTypeObjectRef: + newValue = + ParseSlideProperty<eastl::string>(theAtt.m_Name.c_str(), theDOMParser); + break; + default: + break; + } + newElem->m_InitialValues.push_back(newValue); + } + } + for (bool success = inReader.MoveToFirstChild(); success; + success = inReader.MoveToNextSibling()) + ParseElement(inReader, inMetaData, newElem->m_Children, ioElemMap); + + return *newElem; + } + + // Get the item names if not overridden + // Pull the names off so we can construct the item path. + void LoadSlide(IDOMReader &inReader, TIdElemMap &inMap) + { + IDOMReader::Scope __topScope(inReader); + for (bool commandSuccess = inReader.MoveToFirstChild(); commandSuccess; + commandSuccess = inReader.MoveToNextSibling()) { + IDOMReader::Scope __commandScope(inReader); + const char8_t *elemName = inReader.GetElementName(); + if (AreEqual(elemName, "Set") || AreEqual(elemName, "Add")) { + const char8_t *name; + TIdElemMap::iterator iter = inMap.end(); + const char8_t *itemRef; + inReader.UnregisteredAtt("ref", itemRef); + if (!isTrivial(itemRef)) { + if (itemRef[0] == '#') + ++itemRef; + iter = inMap.find(TEditorStr(itemRef)); + } + + if (inReader.UnregisteredAtt("name", name)) { + if (iter != inMap.end()) { + iter->second->m_Name.assign(name); + } else { + QT3DS_ASSERT(false); + } + } + if (iter != inMap.end() && AreEqual(elemName, "Add")) { + nvvector<char8_t> theTempBuf(this->m_Foundation->getAllocator(), "tempbuf"); + SDomReaderPropertyParser theDOMParser(inReader, m_ParseBuf, inReader.m_TempBuf); + SAppElement *newElem = iter->second.mPtr; + for (size_t idx = 0, end = newElem->m_Attributes.size(); idx < end; ++idx) { + const SAttOrArg &theAtt(newElem->m_Attributes[idx]); + SDatamodelValue newValue; + switch (theAtt.m_DataType) { + case ERuntimeDataModelDataTypeFloat: + newValue = + ParseSlideProperty<float>(theAtt.m_Name.c_str(), theDOMParser); + break; + case ERuntimeDataModelDataTypeFloat2: + newValue = + ParseSlideProperty<QT3DSVec2>(theAtt.m_Name.c_str(), theDOMParser); + break; + case ERuntimeDataModelDataTypeFloat3: + newValue = + ParseSlideProperty<QT3DSVec3>(theAtt.m_Name.c_str(), theDOMParser); + break; + case ERuntimeDataModelDataTypeLong: + newValue = + ParseSlideProperty<QT3DSI32>(theAtt.m_Name.c_str(), theDOMParser); + break; + case ERuntimeDataModelDataTypeString: + newValue = ParseSlideProperty<eastl::string>(theAtt.m_Name.c_str(), + theDOMParser); + break; + case ERuntimeDataModelDataTypeBool: + newValue = + ParseSlideProperty<bool>(theAtt.m_Name.c_str(), theDOMParser); + break; + case ERuntimeDataModelDataTypeStringRef: + newValue = ParseSlideProperty<eastl::string>(theAtt.m_Name.c_str(), + theDOMParser); + break; + case ERuntimeDataModelDataTypeObjectRef: + newValue = ParseSlideProperty<eastl::string>(theAtt.m_Name.c_str(), + theDOMParser); + break; + default: + break; + } + if (newValue.getType() != ERuntimeDataModelDataTypeNone) + newElem->m_InitialValues[idx] = newValue; + } + } + } + } + } + + void ResolveElemPath(SAppElement &inElem, const eastl::string &parentId) + { + inElem.m_Path = parentId; + if (inElem.m_Path.back() != ':') + inElem.m_Path.append(1, '.'); + inElem.m_Path.append(inElem.m_Name); + for (size_t idx = 0, end = inElem.m_Children.size(); idx < end; ++idx) + ResolveElemPath(*inElem.m_Children[idx], inElem.m_Path); + } + + void LoadUIPFile(const char *inRelativePath, const char *inUIPId, + Q3DStudio::IRuntimeMetaData &inMetaData) + { + eastl::string appDir(m_FilePath); + CFileTools::GetDirectory(appDir); + eastl::string uipPath; + CFileTools::CombineBaseAndRelative(appDir.c_str(), inRelativePath, uipPath); + CFileSeekableIOStream uipStream(uipPath.c_str(), FileReadFlags()); + if (!uipStream.IsOpen()) { + QT3DS_ASSERT(false); + return; + } + NVScopedRefCounted<IDOMFactory> theFactory( + IDOMFactory::CreateDOMFactory(m_Foundation->getAllocator(), m_StringTable)); + SDOMElement *domElem = CDOMSerializer::Read(*theFactory, uipStream).second; + if (!domElem) { + QT3DS_ASSERT(false); + return; + } + + NVScopedRefCounted<IDOMReader> theReader(IDOMReader::CreateDOMReader( + m_Foundation->getAllocator(), *domElem, m_StringTable, theFactory)); + if (!theReader->MoveToFirstChild("Project")) { + QT3DS_ASSERT(false); + return; + } + + Q3DStudio::IRuntimeMetaData &theMetaData(inMetaData); + theMetaData.ClearPerProjectData(); + + TEditorStr rootPath(inUIPId); + rootPath.append(1, ':'); + eastl::vector<NVScopedRefCounted<SAppElement>> topElements; + TIdElemMap idElemMap; + m_Presentations.push_back(SPresentation()); + SPresentation ¤tPresentation(m_Presentations.back()); + currentPresentation.m_Id.assign(inUIPId); + currentPresentation.m_SrcPath.assign(inRelativePath); + { + IDOMReader::Scope __projectScope(*theReader); + if (theReader->MoveToFirstChild("ProjectSettings")) { + const char8_t *temp; + theReader->UnregisteredAtt("author", temp); + currentPresentation.m_Author.assign(nonNull(temp)); + theReader->UnregisteredAtt("company", temp); + currentPresentation.m_Company.assign(nonNull(temp)); + theReader->Att("presentationWidth", currentPresentation.m_Width); + theReader->Att("presentationHeight", currentPresentation.m_Height); + } + // Presentation width and height as specified in the file. + } + // Classes/handlers + { + IDOMReader::Scope __projectScope(*theReader); + if (theReader->MoveToFirstChild("Classes")) { + // Load each class system into the meta data and then pull back the handlers + for (bool success = theReader->MoveToFirstChild(); success; + success = theReader->MoveToNextSibling()) { + const char8_t *id, *srcPath, *name; + theReader->UnregisteredAtt("id", id); + theReader->UnregisteredAtt("sourcepath", srcPath); + theReader->UnregisteredAtt("name", name); + eastl::string classItemPath; + CFileTools::CombineBaseAndRelative(appDir.c_str(), srcPath, classItemPath); + + if (AreEqual(theReader->GetElementName().c_str(), "Behavior")) { + bool theScriptFile = + theMetaData.LoadScriptFile("Behavior", id, name, classItemPath.c_str()); + QT3DS_ASSERT(theScriptFile); + (void)theScriptFile; + } else if (AreEqual(theReader->GetElementName().c_str(), "Effect")) { + bool theEffectFile = theMetaData.LoadEffectXMLFile("Effect", id, name, + classItemPath.c_str()); + QT3DS_ASSERT(theEffectFile); + (void)theEffectFile; + } else if (AreEqual(theReader->GetElementName().c_str(), "RenderPlugin")) { + theMetaData.LoadPluginXMLFile("RenderPlugin", id, name, + classItemPath.c_str()); + } else if (AreEqual(theReader->GetElementName().c_str(), "CustomMaterial")) { + theMetaData.LoadMaterialXMLFile("CustomMaterial", id, name, + classItemPath.c_str()); + } else { + QT3DS_ASSERT(false); + } + } + } + } + + // Graph + { + IDOMReader::Scope __projectScope(*theReader); + if (theReader->MoveToFirstChild("Graph")) { + for (bool success = theReader->MoveToFirstChild(); success; + success = theReader->MoveToNextSibling()) { + currentPresentation.m_Scene = + &ParseElement(*theReader, theMetaData, topElements, idElemMap); + } + } + } + // States/Slides + { + // This is where the name *may* be set, + IDOMReader::Scope __projectScope(*theReader); + if (theReader->MoveToFirstChild("Logic")) { + // Slides are just slightly hierarchical, master and then nonmaster children. + for (bool success = theReader->MoveToFirstChild(); success; + success = theReader->MoveToNextSibling()) { + IDOMReader::Scope __masterScope(*theReader); + LoadSlide(*theReader, idElemMap); + const char8_t *component; + theReader->UnregisteredAtt("component", component); + NVScopedRefCounted<SAppComponent> theComponent; + if (!isTrivial(component)) { + if (component[0] == '#') + ++component; + TIdElemMap::iterator iter = idElemMap.find(TEditorStr(component)); + if (iter != idElemMap.end()) { + if (iter->second->GetSubType() == ElementSubTypes::Component) + theComponent = static_cast<SAppComponent *>(iter->second.mPtr); + else { + QT3DS_ASSERT(false); + } + } else { + QT3DS_ASSERT(false); + } + } + + for (bool childSuccess = theReader->MoveToFirstChild("State"); childSuccess; + childSuccess = theReader->MoveToNextSibling("State")) { + IDOMReader::Scope __slideScope(*theReader); + const char8_t *slideName; + if (theReader->UnregisteredAtt("name", slideName)) + theComponent->m_Slides.push_back(TEditorStr(slideName)); + LoadSlide(*theReader, idElemMap); + } + } + } + } + // Now resolve all the names to create full paths. + for (size_t idx = 0, end = topElements.size(); idx < end; ++idx) { + ResolveElemPath(*topElements[idx], rootPath); + } + } + + struct SElemLessThan + { + bool operator()(NVScopedRefCounted<SAppElement> lhs, + NVScopedRefCounted<SAppElement> rhs) const + { + return lhs->m_Path < rhs->m_Path; + } + }; + + static const char *GetNamespace() { return "http://qt.io/qt3dstudio/uia"; } + static const char *GetOldNamespace() { return "http://qt.io/qt3dstudio/uicomposer"; } + + void LoadUIADatabase() + { + CFileSeekableIOStream theStream(m_FilePath.c_str(), FileReadFlags()); + if (theStream.IsOpen()) { + LoadUIADatabaseFromStream(theStream); + } + } + + struct SXmlErrorHandler : public CXmlErrorHandler + { + SXmlErrorHandler(const eastl::string &inFilePath, eastl::string &inErrorString) + : m_FilePath(inFilePath) + , m_ErrorString(inErrorString) + { + } + virtual ~SXmlErrorHandler() {} + virtual void OnXmlError(TXMLCharPtr errorName, int line, int column) + { + if (m_ErrorString.empty()) + m_ErrorString.sprintf("%s(%d, %d): %s", m_FilePath.c_str(), line, column, + errorName); + } + eastl::string m_FilePath; + eastl::string &m_ErrorString; + }; + + void LoadUIADatabaseFromStream(IInStream &inStream) + { + NVScopedRefCounted<IDOMFactory> theFactory( + IDOMFactory::CreateDOMFactory(m_Foundation->getAllocator(), m_StringTable)); + m_UIADocument.first = NULL; + m_UIADocument.second = NULL; + m_UIANamespaces.clear(); + m_Elements.clear(); + m_IdToStateMaps.clear(); + m_Editors.clear(); + m_Presentations.clear(); + m_SlideProperties.clear(); + m_LoadingErrorString.clear(); + eastl::string appDir(m_FilePath); + CFileTools::GetDirectory(appDir); + + SXmlErrorHandler theXmlErrorWriter(m_FilePath, m_LoadingErrorString); + eastl::pair<SNamespacePairNode *, SDOMElement *> parseResult = + CDOMSerializer::Read(*theFactory, inStream, &theXmlErrorWriter); + if (parseResult.second != NULL) { + qt3ds::foundation::CRegisteredString theRegisteredOldNamespace = + m_StringTable->RegisterStr(GetOldNamespace()); + qt3ds::foundation::CRegisteredString theRegisteredNamespace = + m_StringTable->RegisterStr(GetNamespace()); + + ReplaceDOMNamespace(*parseResult.second, theRegisteredOldNamespace, + theRegisteredNamespace); + + m_UIADocument = + IDOMWriter::CreateDOMWriter(theFactory, *parseResult.second, m_StringTable); + for (SNamespacePairNode *nodePtr = parseResult.first; nodePtr; + nodePtr = nodePtr->m_NextNode) { + if (nodePtr->m_Namespace == theRegisteredOldNamespace) + nodePtr->m_Namespace = theRegisteredNamespace; + m_UIANamespaces.push_back(*nodePtr); + } + } + + if (m_UIADocument.first.mPtr == NULL) { + m_UIADocument = IDOMWriter::CreateDOMWriter(m_Foundation->getAllocator(), "application", + m_StringTable, GetNamespace()); + m_Dirty = true; + } + if (m_UIANamespaces.empty()) + m_UIANamespaces.push_back( + SNamespacePair(m_StringTable->RegisterStr(GetNamespace()))); + + { + NVScopedReleasable<Q3DStudio::IRuntimeMetaData> theMetaData( + Q3DStudio::IRuntimeMetaData::Create(*m_InputStreamFactory)); + + if (theMetaData) { + m_SlideProperties = theMetaData->GetSlideAttributes(); + + eastl::vector<eastl::pair<TEditorStr, TEditorStr>> machinePathsAndIds; + IDOMReader::Scope __appScope(m_UIADocument.second); + + if (m_UIADocument.second->MoveToFirstChild("assets")) { + IDOMReader::Scope __assetsScope(m_UIADocument.second); + for (bool success = m_UIADocument.second->MoveToFirstChild(); success; + success = m_UIADocument.second->MoveToNextSibling()) { + IDOMReader::Scope __assetScope(m_UIADocument.second); + const char8_t *elemName = m_UIADocument.second->GetElementName(); + if (AreEqual(elemName, "presentation")) { + const char8_t *id, *relativePath; + m_UIADocument.second->UnregisteredAtt("id", id); + m_UIADocument.second->UnregisteredAtt("src", relativePath); + LoadUIPFile(relativePath, id, *theMetaData); + } else if (AreEqual(elemName, "statemachine")) { + const char8_t *id, *relativePath; + m_UIADocument.second->UnregisteredAtt("id", id); + m_UIADocument.second->UnregisteredAtt("src", relativePath); + TEditorStr fullPath; + CFileTools::CombineBaseAndRelative(appDir.c_str(), relativePath, + fullPath); + CreateSCXMLEditor(fullPath, nonNull(id)); + } + } + } + + // Now sort our list of elements by path after we have loaded all uip files. + eastl::sort(m_Elements.begin(), m_Elements.end(), SElemLessThan()); + } else { + QT3DS_ASSERT(false); + } + } + { + IDOMReader::Scope __appScope(m_UIADocument.second); + for (bool success = m_UIADocument.second->MoveToFirstChild("statemachine"); success; + success = m_UIADocument.second->MoveToNextSibling("statemachine")) { + IDOMReader::Scope __machineScope(m_UIADocument.second); + const char8_t *idref; + m_UIADocument.second->UnregisteredAtt("ref", idref); + if (!isTrivial(idref)) { + if (idref[0] == '#') + ++idref; + for (size_t idx = 0, end = m_Editors.size(); idx < end; ++idx) { + if (m_Editors[idx].m_Id.compare(idref) == 0) + ParseStateMachine(m_Editors[idx].m_Id, m_Editors[idx].m_Editor); + } + } + } + } + } + + virtual bool IsDirty() const { return m_Dirty; } + // General queries of the dataset defined by the uia file and all uip files and scxml files it + // includes. + + SAppElement *FindElementByPath(const TEditorStr &inPath) + { + if (!m_FinderElement) + m_FinderElement = new SAppElement(); + + m_FinderElement->m_Path = inPath; + + nvvector<NVScopedRefCounted<SAppElement>>::iterator iter = eastl::lower_bound( + m_Elements.begin(), m_Elements.end(), m_FinderElement, SElemLessThan()); + if (iter != m_Elements.end() && (*iter)->m_Path == m_FinderElement->m_Path) { + return iter->mPtr; + } + return NVScopedRefCounted<SAppElement>(); + } + + virtual TEditorStrList GetComponents() + { + TEditorStrList retval; + for (size_t idx = 0, end = m_Elements.size(); idx < end; ++idx) { + if (m_Elements[idx]->GetSubType() == ElementSubTypes::Component) + retval.push_back(m_Elements[idx]->m_Path); + } + return retval; + } + + virtual TEditorStrList GetComponentSlides(const TEditorStr &inComponent) + { + SAppElement *elem = FindElementByPath(inComponent); + if (elem != NULL && elem->GetSubType() == ElementSubTypes::Component) + return static_cast<SAppComponent *>(elem)->m_Slides; + return TEditorStrList(); + } + + virtual TEditorStrList GetBehaviors() + { + TEditorStrList retval; + for (size_t idx = 0, end = m_Elements.size(); idx < end; ++idx) { + if (m_Elements[idx]->GetSubType() == ElementSubTypes::Behavior) + retval.push_back(m_Elements[idx]->m_Path); + } + return retval; + } + + virtual Q3DStudio::THandlerList GetHandlers(const TEditorStr &inBehavior) + { + SAppElement *elem = FindElementByPath(inBehavior); + if (elem != NULL && elem->GetSubType() == ElementSubTypes::Behavior) + return static_cast<SAppBehavior *>(elem)->m_Handlers; + return Q3DStudio::THandlerList(); + } + + virtual Q3DStudio::TVisualEventList GetVisualEvents(const TEditorStr &inElement) + { + SAppElement *elem = FindElementByPath(inElement); + if (elem) + return elem->m_VisualEvents; + return Q3DStudio::TVisualEventList(); + } + + virtual TEditorStrList GetElements() + { + TEditorStrList retval; + for (size_t idx = 0, end = m_Elements.size(); idx < end; ++idx) + retval.push_back(m_Elements[idx]->m_Path); + return retval; + } + + virtual Q3DStudio::TAttOrArgList GetElementAttributes(const TEditorStr &inElement) + { + SAppElement *elem = FindElementByPath(inElement); + if (elem) + return elem->m_Attributes; + return Q3DStudio::TAttOrArgList(); + } + + TEditorPtr CreateSCXMLEditor(const TEditorStr &inPath, const TEditorStr &inId) + { + TEditorPtr newEditor = m_StateMachineEditorManager.GetOrCreateEditor(inPath, 0); + if (newEditor) { + SEditorEntry theEntry; + theEntry.m_Editor = newEditor.mPtr; + theEntry.m_FilePath = inPath; + theEntry.m_Id = inId; + m_Editors.push_back(theEntry); + } + return newEditor.mPtr; + } + + virtual TEditorPtr GetOrCreateEditor(const TEditorStr &inFullPath, bool *outLoadStatus) + { + (void)outLoadStatus; + TEditorStr normalizedPath(inFullPath); + CFileTools::NormalizePath(normalizedPath); + for (size_t idx = 0, end = m_Editors.size(); idx < end; ++idx) { + if (m_Editors[idx].m_FilePath == normalizedPath) + return m_Editors[idx].m_Editor; + } + + // Is file full path under our application directory; + TEditorStr appDir(m_FilePath); + CFileTools::GetDirectory(appDir); + TEditorStr editorDir(inFullPath); + CFileTools::GetDirectory(editorDir); + + // For scxml files outside our app dir, let the user create an editor manually. + // We will have nothing to do with it. + if (editorDir.find(appDir) == eastl::string::npos) + return TEditorPtr(); + + // Get an ID for the editor + IDOMReader::Scope __appScope(*m_UIADocument.second); + if (!m_UIADocument.second->MoveToFirstChild("assets")) { + m_UIADocument.first->Begin("assets", GetNamespace()); + } + + { + IDOMReader::Scope __assetsScope(*m_UIADocument.second); + for (bool success = m_UIADocument.second->MoveToFirstChild("statemachine"); success; + success = m_UIADocument.second->MoveToNextSibling("statemachine")) { + const char8_t *srcPath; + const char8_t *id; + m_UIADocument.second->UnregisteredAtt("src", srcPath); + m_UIADocument.second->UnregisteredAtt("id", id); + TEditorStr docPath; + CFileTools::CombineBaseAndRelative(appDir.c_str(), srcPath, docPath); + CFileTools::NormalizePath(docPath); + if (docPath == normalizedPath) { + return CreateSCXMLEditor(normalizedPath, nonNull(id)); + } + } + } + + eastl::string dirname, fname, extension; + CFileTools::Split(normalizedPath.c_str(), dirname, fname, extension); + m_UIADocument.first->Begin("statemachine", GetNamespace()); + m_UIADocument.first->Att("id", fname.c_str(), GetNamespace()); + eastl::string relativePath; + CFileTools::GetRelativeFromBase(appDir, normalizedPath, relativePath); + m_UIADocument.first->Att("src", relativePath.c_str()); + m_Dirty = true; + return CreateSCXMLEditor(normalizedPath, nonNull(fname.c_str())); + } + + static bool CouldHaveVisualStateExecutableContent(TObjPtr inObject) + { + return inObject->GetExecutableContentTypes().size() > 0; + } + template <typename TEditorType> + TEditorType &CreateEditor(TObjPtr inParent) + { + TPropertyAccessorList accessors; + TEditorType::CreateProperties(accessors, *this); + return *QT3DS_NEW(m_Foundation->getAllocator(), TEditorType)(*this, inParent, accessors); + } + + SVSEntry *GetEntryForPair(TEntryExitPair &inPair, InterpreterEventTypes::Enum inType) + { + SVSEntry *theEntry; + if (inType == InterpreterEventTypes::StateEnter + || inType == InterpreterEventTypes::Transition) { + if (inPair.first == NULL) + inPair.first = new SVSEntry(inType); + theEntry = inPair.first.mPtr; + } else { + if (inPair.second == NULL) + inPair.second = new SVSEntry(inType); + theEntry = inPair.second.mPtr; + } + return theEntry; + } + + void ParseExecutableContent(IDOMReader &inReader, const TEditorStr &inId, + TIdEntryExitMap &ioMap, InterpreterEventTypes::Enum inType, + TObjPtr parentPtr) + { + IDOMReader::Scope __contentScope(inReader); + { + IDOMReader::Scope __testScope(inReader); + // See if there is any executable content to begin with. + if (inReader.MoveToFirstChild() == false) + return; + } + + TEntryExitPair &thePair = + ioMap.insert(eastl::make_pair(inId, TEntryExitPair())).first->second; + SVSEntry *theEntry = GetEntryForPair(thePair, inType); + QT3DS_ASSERT(theEntry->m_EventType == inType); + for (bool success = inReader.MoveToFirstChild(); success; + success = inReader.MoveToNextSibling()) { + IDOMReader::Scope __elemScope(inReader); + const char8_t *elemName(inReader.GetElementName()); + if (AreEqual(elemName, SGotoSlideEditor::ElementName())) { + const char8_t *component, *slide, *rel, *wrap, *direction, *state, *mode, + *playthroughto, *rate, *time; + inReader.UnregisteredAtt("element", component); + if (inReader.UnregisteredAtt("slide", slide) == false) + inReader.UnregisteredAtt("rel", rel); + else + rel = ""; + inReader.UnregisteredAtt("wrap", wrap); + inReader.UnregisteredAtt("direction", direction); + inReader.UnregisteredAtt("state", state); + inReader.UnregisteredAtt("mode", mode); + inReader.UnregisteredAtt("playthroughto", playthroughto); + inReader.UnregisteredAtt("rate", rate); + inReader.UnregisteredAtt("time", time); + SGotoSlideEditor &theEditor = CreateEditor<SGotoSlideEditor>(parentPtr); + theEditor.m_Component.assign(nonNull(component)); + theEditor.m_Slide.assign(nonNull(slide)); + theEditor.m_Rel.assign(nonNull(rel)); + theEditor.m_Wrap.assign(nonNull(wrap)); + theEditor.m_Direction.assign(nonNull(direction)); + theEditor.m_State.assign(nonNull(state)); + theEditor.m_Mode.assign(nonNull(mode)); + theEditor.m_PlayThroughTo.assign(nonNull(playthroughto)); + theEditor.m_Rate.assign(nonNull(rate)); + theEditor.m_Time.assign(nonNull(time)); + theEntry->m_Editors.push_back(theEditor); + } else if (AreEqual(elemName, SSetAttributeEditor::ElementName())) { + const char8_t *element, *att, *val; + inReader.UnregisteredAtt("element", element); + inReader.UnregisteredAtt("attribute", att); + inReader.UnregisteredAtt("value", val); + SSetAttributeEditor &theEditor = CreateEditor<SSetAttributeEditor>(parentPtr); + theEditor.m_Element.assign(nonNull(element)); + theEditor.m_Attribute.assign(nonNull(att)); + theEditor.m_Value.assign(nonNull(val)); + theEntry->m_Editors.push_back(theEditor); + } else if (AreEqual(elemName, SFireEventEditor::ElementName())) { + const char8_t *element, *evt; + inReader.UnregisteredAtt("element", element); + inReader.UnregisteredAtt("event", evt); + SFireEventEditor &theEditor = CreateEditor<SFireEventEditor>(parentPtr); + theEditor.m_Element.assign(nonNull(element)); + theEditor.m_Event.assign(nonNull(evt)); + theEntry->m_Editors.push_back(theEditor); + } else if (AreEqual(elemName, SRunHandlerEditor::ElementName())) { + const char8_t *element, *handler, *args; + inReader.UnregisteredAtt("element", element); + inReader.UnregisteredAtt("handler", handler); + inReader.UnregisteredAtt("arguments", args); + SRunHandlerEditor &theEditor = CreateEditor<SRunHandlerEditor>(parentPtr); + theEditor.m_Behavior.assign(nonNull(element)); + theEditor.m_Handler.assign(nonNull(handler)); + theEditor.m_ArgumentStr.assign(nonNull(args)); + theEntry->m_Editors.push_back(theEditor); + } else if (AreEqual(elemName, SSetPresentationEditor::ElementName())) { + const char8_t *ref, *attribute, *value; + inReader.UnregisteredAtt("ref", ref); + inReader.UnregisteredAtt("attribute", attribute); + inReader.UnregisteredAtt("value", value); + SSetPresentationEditor &theEditor = CreateEditor<SSetPresentationEditor>(parentPtr); + theEditor.m_Ref.assign(nonNull(ref)); + theEditor.m_Attribute.assign(nonNull(attribute)); + theEditor.m_Value.assign(nonNull(value)); + theEntry->m_Editors.push_back(theEditor); + } else if (AreEqual(elemName, SPlaySoundEditor::ElementName())) { + const char8_t *file; + inReader.UnregisteredAtt("file", file); + SPlaySoundEditor &theEditor = CreateEditor<SPlaySoundEditor>(parentPtr); + theEditor.m_FilePath.assign(nonNull(file)); + theEntry->m_Editors.push_back(theEditor); + } + } + } + + void ParseVisualStateEntry(IDOMReader &inReader, TEditorPtr inEditor, TIdEntryExitMap &retval, + const char8_t *stateId) + { + TObjPtr parentPtr = inEditor->GetObjectById(stateId); + if (parentPtr) { + if (AreEqual(inReader.GetElementName().c_str(), "state")) { + for (bool entryExitSucces = inReader.MoveToFirstChild(); entryExitSucces; + entryExitSucces = inReader.MoveToNextSibling()) { + const char8_t *signalName(inReader.GetElementName()); + if (AreEqual(signalName, "enter")) { + ParseExecutableContent(inReader, stateId, retval, + InterpreterEventTypes::StateEnter, parentPtr); + } else if (AreEqual(signalName, "exit")) { + ParseExecutableContent(inReader, stateId, retval, + InterpreterEventTypes::StateExit, parentPtr); + } else { + QT3DS_ASSERT(false); + } + } + } else if (AreEqual(inReader.GetElementName().c_str(), "transition")) { + ParseExecutableContent(inReader, stateId, retval, InterpreterEventTypes::Transition, + parentPtr); + } else { + QT3DS_ASSERT(false); + } + } + } + + TIdEntryExitMap &ParseStateMachine(const TEditorStr &inId, TEditorPtr inEditor) + { + TIdEntryExitMap &retval = + m_IdToStateMaps.insert(eastl::make_pair(inId, TIdEntryExitMap())).first->second; + if (m_UIADocument.second->MoveToFirstChild("visual-states")) { + for (bool stateSuccess = m_UIADocument.second->MoveToFirstChild(); stateSuccess; + stateSuccess = m_UIADocument.second->MoveToNextSibling()) { + IDOMReader::Scope __stateScope(*m_UIADocument.second); + const char8_t *stateId; + m_UIADocument.second->UnregisteredAtt("ref", stateId); + if (!isTrivial(stateId)) { + if (stateId[0] == '#') + ++stateId; + + ParseVisualStateEntry(*m_UIADocument.second, inEditor, retval, stateId); + } + } + } + return retval; // found a statemachine with the right id. + } + + TIdEntryExitMap &GetOrCreateStateMap(const TEditorStr &inId, TEditorPtr inEditor) + { + TIdEntryExitMap &retval = + m_IdToStateMaps.insert(eastl::make_pair(inId, TIdEntryExitMap())).first->second; + + if (retval.empty()) { + IDOMReader::Scope __appScope(*m_UIADocument.second); + for (bool success = m_UIADocument.second->MoveToFirstChild("statemachine"); + success && retval.empty(); + success = m_UIADocument.second->MoveToNextSibling("statemachine")) { + IDOMReader::Scope __machineScope(*m_UIADocument.second); + const char8_t *idref; + m_UIADocument.second->UnregisteredAtt("ref", idref); + if (!isTrivial(idref)) { + if (idref[0] == '#') + ++idref; + if (inId.compare(idref) == 0) { + return ParseStateMachine(inId, inEditor); + } + } + } // for (statemachines) + } + + return retval; + } + + TIdEntryExitMap *GetStateMapForObject(TObjPtr inObject) + { + if (!CouldHaveVisualStateExecutableContent(inObject)) + return NULL; + + TEditorPtr theEditor = inObject->GetEditor(); + // Find the id for the editor + TIdEntryExitMap *stateMap(NULL); + + for (size_t idx = 0, end = m_Editors.size(); idx < end && stateMap == NULL; ++idx) { + if (m_Editors[idx].m_Editor == theEditor) + return &GetOrCreateStateMap(m_Editors[idx].m_Id, m_Editors[idx].m_Editor); + } + return NULL; + } + + // The editor obj has a remove from graph function that really is delete + virtual TObjList GetVisualStateExecutableContent(TObjPtr inObject, + InterpreterEventTypes::Enum inEventType) + { + TIdEntryExitMap *stateMap = GetStateMapForObject(inObject); + if (stateMap == NULL) + return TObjList(); + + TEditorStr objId(inObject->GetId()); + TIdEntryExitMap::iterator iter = stateMap->find(objId); + for (TIdEntryExitMap::iterator temp = stateMap->begin(), end = stateMap->end(); temp != end; + ++temp) { + objId = temp->first; + objId.clear(); + } + if (iter != stateMap->end()) { + TEntryExitPair thePair = iter->second; + if (thePair.first && thePair.first->m_EventType == inEventType) + return thePair.first->m_Editors; + else if (thePair.second && thePair.second->m_EventType == inEventType) + return thePair.second->m_Editors; + } + + return TObjList(); + } + + // Type name is the element name, so set-attribute, goto-slide, or fire-event + virtual TObjPtr AppendVisualStateExecutableContent(TObjPtr inObject, + InterpreterEventTypes::Enum inEventType, + const char8_t *inElementName) + { + TIdEntryExitMap *stateMap = GetStateMapForObject(inObject); + if (stateMap == NULL) + return TObjPtr(); + + NVConstDataRef<InterpreterEventTypes::Enum> supportedTypes = + inObject->GetExecutableContentTypes(); + bool foundEventType = false; + for (size_t idx = 0, end = supportedTypes.size(); idx < end && foundEventType == false; + ++idx) + if (inEventType == supportedTypes[idx]) + foundEventType = true; + + if (foundEventType == false) { + QT3DS_ASSERT(false); + return TObjPtr(); + } + + TEntryExitPair &thePair = + stateMap->insert(eastl::make_pair(inObject->GetId(), TEntryExitPair())).first->second; + SVSEntry *entry = GetEntryForPair(thePair, inEventType); + if (!entry) { + QT3DS_ASSERT(false); + return TObjPtr(); + } + + if (AreEqual(inElementName, SGotoSlideEditor::ElementName())) { + entry->m_Editors.push_back(CreateEditor<SGotoSlideEditor>(inObject)); + } else if (AreEqual(inElementName, SRunHandlerEditor::ElementName())) { + entry->m_Editors.push_back(CreateEditor<SRunHandlerEditor>(inObject)); + } else if (AreEqual(inElementName, SSetAttributeEditor::ElementName())) { + entry->m_Editors.push_back(CreateEditor<SSetAttributeEditor>(inObject)); + } else if (AreEqual(inElementName, SFireEventEditor::ElementName())) { + entry->m_Editors.push_back(CreateEditor<SFireEventEditor>(inObject)); + } else if (AreEqual(inElementName, SSetPresentationEditor::ElementName())) { + entry->m_Editors.push_back(CreateEditor<SSetPresentationEditor>(inObject)); + } else if (AreEqual(inElementName, SPlaySoundEditor::ElementName())) { + entry->m_Editors.push_back(CreateEditor<SPlaySoundEditor>(inObject)); + } else { + QT3DS_ASSERT(false); + return TObjPtr(); + } + TObjPtr retval = entry->m_Editors.back(); + STransaction *theTrans = GetOpenTransactionImpl(); + if (theTrans) { + SVSEntryListChange *theChange = + new SVSEntryListChange(TObjPtr(), retval, *entry, true, inObject); + theTrans->m_Changes.push_back(theChange); + } + return retval; + } + + virtual TObjPtr ChangeVisualStateExecutableContentName(TObjPtr inContent, + const char8_t *inElementName) + { + TObjPtr theParent = inContent->Parent(); + if (!CouldHaveVisualStateExecutableContent(theParent)) { + QT3DS_ASSERT(false); + return TObjPtr(); + } + TIdEntryExitMap *stateMap = GetStateMapForObject(theParent); + if (stateMap == NULL) { + QT3DS_ASSERT(false); + return TObjPtr(); + } + + TIdEntryExitMap::iterator iter = stateMap->find(theParent->GetId()); + if (iter == stateMap->end()) { + QT3DS_ASSERT(false); + return TObjPtr(); + } + TEntryExitPair thePair = iter->second; + NVScopedRefCounted<SVSEntry> theEntryFound; + if (!theEntryFound) { + NVScopedRefCounted<SVSEntry> theEntry = thePair.first; + TObjPtr theObj = inContent; + if (theEntry) { + TObjList::iterator iter = + eastl::find(theEntry->m_Editors.begin(), theEntry->m_Editors.end(), theObj); + if (iter != theEntry->m_Editors.end()) { + theEntryFound = theEntry; + } + } + } + if (!theEntryFound) { + NVScopedRefCounted<SVSEntry> theEntry = thePair.second; + TObjPtr theObj = inContent; + if (theEntry) { + TObjList::iterator iter = + eastl::find(theEntry->m_Editors.begin(), theEntry->m_Editors.end(), theObj); + if (iter != theEntry->m_Editors.end()) { + theEntryFound = theEntry; + } + } + } + if (!theEntryFound) + return TObjPtr(); + + TObjPtr theRetval; + if (AreEqual(inElementName, SGotoSlideEditor::ElementName())) { + theRetval = CreateEditor<SGotoSlideEditor>(theParent); + } else if (AreEqual(inElementName, SRunHandlerEditor::ElementName())) { + theRetval = CreateEditor<SRunHandlerEditor>(theParent); + } else if (AreEqual(inElementName, SSetAttributeEditor::ElementName())) { + theRetval = CreateEditor<SSetAttributeEditor>(theParent); + } else if (AreEqual(inElementName, SFireEventEditor::ElementName())) { + theRetval = CreateEditor<SFireEventEditor>(theParent); + } else if (AreEqual(inElementName, SSetPresentationEditor::ElementName())) { + theRetval = CreateEditor<SSetPresentationEditor>(theParent); + } else if (AreEqual(inElementName, SPlaySoundEditor::ElementName())) { + theRetval = CreateEditor<SPlaySoundEditor>(theParent); + } else { + QT3DS_ASSERT(false); + return TObjPtr(); + } + + NVScopedRefCounted<SVSEntryListChange> theOldChange = + new SVSEntryListChange(inContent, inContent, *theEntryFound, false, theParent); + NVScopedRefCounted<SVSEntryListChange> theNewChange = + new SVSEntryListChange(theRetval, inContent, *theEntryFound, true, theParent); + theOldChange->Do(); + theNewChange->Do(); + if (GetOpenTransactionImpl()) { + GetOpenTransactionImpl()->m_Changes.push_back(theOldChange.mPtr); + GetOpenTransactionImpl()->m_Changes.push_back(theNewChange.mPtr); + } + + return theRetval; + } + + // Called when the source uia changes. + virtual void RefreshFile() { LoadUIADatabase(); } + + virtual void RefreshFromStream(qt3ds::foundation::IInStream &inStream) + { + LoadUIADatabaseFromStream(inStream); + } + + // Returns the path that was passed in on create. + virtual TEditorStr GetFilePath() { return m_FilePath; } + + void CopyDOM(IDOMReader &inReader, IDOMWriter &inWriter) + { + IDOMReader::Scope __itemScope(inReader); + const SDOMElement &theElement(*inReader.GetElement()); + IDOMWriter::Scope __writeScope(inWriter, theElement.m_Name, theElement.m_Namespace); + if (theElement.m_Attributes.empty() && theElement.m_Children.empty()) { + if (!isTrivial(theElement.m_Value)) + inWriter.Value(theElement.m_Value); + } else { + for (TAttributeList::iterator iter = theElement.m_Attributes.begin(), + end = theElement.m_Attributes.end(); + iter != end; ++iter) { + inWriter.Att(iter->m_Name, iter->m_Value, iter->m_Namespace); + } + + for (bool success = inReader.MoveToFirstChild(); success; + success = inReader.MoveToNextSibling()) { + IDOMReader::Scope __loopScope(inReader); + CopyDOM(inReader, inWriter); + } + } + } + void ReplaceDOMNamespace(SDOMElement &inDom, + qt3ds::foundation::CRegisteredString &inNamespaceToReplace, + qt3ds::foundation::CRegisteredString &inNewNamespace) + { + if (!inNamespaceToReplace.IsValid() || !inNewNamespace.IsValid()) + return; + if (inDom.m_Namespace == inNamespaceToReplace) + inDom.m_Namespace = inNewNamespace; + // Set attributes namespace + for (qt3ds::foundation::TAttributeList::iterator theIter = inDom.m_Attributes.begin(), + theEnd = inDom.m_Attributes.end(); + theIter != theEnd; ++theIter) { + if (theIter->m_Namespace == inNamespaceToReplace) + theIter->m_Namespace = inNewNamespace; + } + // Recursive + for (qt3ds::foundation::SDOMElement::TElementChildList::iterator + theIter = inDom.m_Children.begin(), + theEnd = inDom.m_Children.end(); + theIter != theEnd; ++theIter) { + ReplaceDOMNamespace(*theIter, inNamespaceToReplace, inNewNamespace); + } + } + struct SEditorIDFinder + { + TEditorStr m_Id; + SEditorIDFinder(const TEditorStr &id) + : m_Id(id) + { + } + bool operator()(const SEditorEntry &entry) const { return m_Id == entry.m_Id; } + }; + + void WriteExecutableContent(IDOMWriter &inWriter, NVScopedRefCounted<SVSEntry> inEntry) + { + if (inEntry == NULL) + return; + const char *elemName = ""; + switch (inEntry->m_EventType) { + case InterpreterEventTypes::Transition: + break; + case InterpreterEventTypes::StateEnter: + elemName = "enter"; + break; + case InterpreterEventTypes::StateExit: + elemName = "exit"; + break; + default: + QT3DS_ASSERT(false); + break; + } + if (!isTrivial(elemName)) + inWriter.Begin(elemName, GetNamespace()); + + eastl::vector<SPropertyDeclaration> properties; + for (size_t idx = 0, end = inEntry->m_Editors.size(); idx < end; ++idx) { + TObjPtr editor(inEntry->m_Editors[idx]); + IDOMWriter::Scope __contentScope(inWriter, editor->TypeName(), GetNamespace()); + editor->GetProperties(properties); + bool theNoProperty = true; + for (size_t idx = 0, end = properties.size(); idx < end; ++idx) { + TEditorStr theProp = + editor->GetPropertyValue(properties[idx].m_Name)->getData<TEditorStr>(); + if (theProp.empty() == false) { + inWriter.Att(properties[idx].m_Name.c_str(), theProp.c_str(), + GetNamespace()); + theNoProperty = false; + } + } + if (theNoProperty && properties.size() > 0) + inWriter.Att(properties[0].m_Name.c_str(), "", GetNamespace()); + } + + if (!isTrivial(elemName)) + inWriter.End(); + } + + void WriteStateEntry(TIdEntryExitMap::const_iterator stateIter, IDOMWriter &writer, + const char *typeName, const char *id) + { + const char8_t *stateName = "state"; + if (AreEqual(typeName, "transition")) + stateName = "transition"; + eastl::string tempAtt; + IDOMWriter::Scope __stateScope(writer, stateName, GetNamespace()); + tempAtt.assign("#"); + tempAtt.append(id); + writer.Att("ref", tempAtt.c_str()); + WriteExecutableContent(writer, stateIter->second.first); + WriteExecutableContent(writer, stateIter->second.second); + } + + // Returns false if unable to save, ask users to check the file out. + bool SaveInner(qt3ds::foundation::IOutStream &inStream) + { + IDOMReader::Scope __saveScope(m_UIADocument.second); + NVScopedRefCounted<IDOMFactory> theFactory( + IDOMFactory::CreateDOMFactory(m_Foundation->getAllocator(), m_StringTable)); + NVScopedRefCounted<IDOMWriter> outgoingDoc = + IDOMWriter::CreateDOMWriter(m_Foundation->getAllocator(), "application", m_StringTable, + GetNamespace()) + .first; + { + m_UIADocument.second->MoveToFirstChild("application"); + for (SDOMAttribute *theAtt = m_UIADocument.second->GetFirstAttribute(); theAtt; + theAtt = m_UIADocument.second->GetNextAttribute()) { + outgoingDoc->Att(theAtt->m_Name, theAtt->m_Value, theAtt->m_Namespace); + } + } + + { + IDOMReader::Scope __appScope(m_UIADocument.second); + if (m_UIADocument.second->MoveToFirstChild("assets")) { + CopyDOM(*m_UIADocument.second, *outgoingDoc); + } + } + { + eastl::string tempAtt; + for (TIdStateMapMap::const_iterator iter = m_IdToStateMaps.begin(), + end = m_IdToStateMaps.end(); + iter != end; ++iter) { + nvvector<SEditorEntry>::iterator editorEntry = eastl::find_if( + m_Editors.begin(), m_Editors.end(), SEditorIDFinder(iter->first)); + if (editorEntry == m_Editors.end()) { + QT3DS_ASSERT(false); + return false; + } + IDOMWriter::Scope __machineScope(*outgoingDoc, "statemachine", GetNamespace()); + tempAtt.assign("#"); + tempAtt.append(iter->first); + outgoingDoc->Att("ref", tempAtt.c_str()); + IDOMWriter::Scope __vsScope(*outgoingDoc, "visual-states", GetNamespace()); + const TIdEntryExitMap &itemMap = iter->second; + for (TIdEntryExitMap::const_iterator stateIter = itemMap.begin(), + stateEnd = itemMap.end(); + stateIter != stateEnd; ++stateIter) { + TObjPtr editorObj = + editorEntry->m_Editor->GetObjectById(stateIter->first.c_str()); + if (!editorObj) { + QT3DS_ASSERT(false); + continue; + } + WriteStateEntry(stateIter, *outgoingDoc, editorObj->TypeName(), + stateIter->first.c_str()); + } + } + } + + SDOMElement *topElem = outgoingDoc->GetTopElement(); + CDOMSerializer::WriteXMLHeader(inStream); + CDOMSerializer::Write(m_Foundation->getAllocator(), *topElem, inStream, *m_StringTable, + m_UIANamespaces); + m_Dirty = false; + return true; + } + + virtual bool Save() + { + CFileSeekableIOStream theStream(m_FilePath.c_str(), FileWriteFlags()); + if (!theStream.IsOpen()) + return false; + return SaveInner(theStream); + } + + virtual bool Save(qt3ds::foundation::IOutStream &inStream) { return SaveInner(inStream); } + + virtual eastl::vector<SPresentation> GetPresentations() { return m_Presentations; } + virtual eastl::string GetElementType(SAppElement &elem) { return elem.m_Type; } + + virtual eastl::string GetElementId(SAppElement &elem) { return elem.m_Id; } + virtual bool IsComponent(SAppElement &elem) + { + return elem.GetSubType() == ElementSubTypes::Component; + } + virtual Q3DStudio::TAttOrArgList GetElementAttributes(SAppElement &elem) + { + return elem.m_Attributes; + } + virtual Q3DStudio::TAttOrArgList GetSlideAttributes() { return m_SlideProperties; } + virtual eastl::vector<SDatamodelValue> GetElementAttributeInitialValues(SAppElement &elem) + { + return elem.m_InitialValues; + } + virtual eastl::vector<SAppElement *> GetElementChildren(SAppElement &elem) + { + eastl::vector<SAppElement *> retval; + retval.resize(elem.m_Children.size()); + for (size_t idx = 0, end = elem.m_Children.size(); idx < end; ++idx) + retval[idx] = elem.m_Children[idx].mPtr; + return retval; + } + virtual eastl::string GetLastLoadingErrorString() { return m_LoadingErrorString; } + // The file may either exist or not. You can pass in a uip file as well as a uia file. + + virtual NVFoundationBase &GetFoundation() { return m_Foundation->getFoundation(); } + + virtual IStringTable &GetStringTable() { return *m_StringTable; } + + // ITransactionManager + // Undo/redo is supported via a transparent transaction system. + // calls are reentrant but last call will close the transaction object. + // Any changes to any editor objects will go through this transaction when they happen. + virtual TSignalConnectionPtr AddChangeListener(IEditorChangeListener &inListener) + { + return m_TransactionManager->AddChangeListener(inListener); + } + virtual TTransactionPtr BeginTransaction(const TEditorStr &inName) + { + return m_TransactionManager->BeginTransaction(inName); + } + + virtual TTransactionPtr GetOpenTransaction() + { + return m_TransactionManager->GetOpenTransaction(); + } + + virtual void RollbackTransaction() { m_TransactionManager->RollbackTransaction(); } + virtual void EndTransaction() { m_TransactionManager->EndTransaction(); } + + virtual void OnObjectCreated(TObjPtr) {} + + virtual void OnObjectDeleted(TObjPtr inObject) + { + // don't care. + if (!CouldHaveVisualStateExecutableContent(inObject)) + return; + + TIdEntryExitMap *stateMap = GetStateMapForObject(inObject); + if (stateMap == NULL) + return; + + TIdEntryExitMap::iterator iter = stateMap->find(inObject->GetId()); + if (iter == stateMap->end()) + return; + NVScopedRefCounted<SIdEntryExitDeleteChange> theChange = + new SIdEntryExitDeleteChange(*stateMap, inObject->GetId(), inObject); + theChange->Do(); + if (GetOpenTransactionImpl()) + GetOpenTransactionImpl()->m_Changes.push_back(theChange.mPtr); + } + bool RemoveObjectFromEntry(NVScopedRefCounted<SVSEntry> inEntry, SVSEditorObject &inObj) + { + if (inEntry) { + TObjList::iterator iter = + eastl::find(inEntry->m_Editors.begin(), inEntry->m_Editors.end(), TObjPtr(inObj)); + if (iter != inEntry->m_Editors.end()) { + NVScopedRefCounted<SVSEntryListChange> theChange = new SVSEntryListChange( + TObjPtr(inObj), TObjPtr(inObj), *inEntry, false, inObj.m_ParentObject); + theChange->Do(); + if (GetOpenTransactionImpl()) + GetOpenTransactionImpl()->m_Changes.push_back(theChange.mPtr); + return true; + } + } + return false; + } + + virtual void RemoveObjectFromGraph(SVSEditorObject &inObj) + { + TObjPtr parentPtr = inObj.m_ParentObject; + if (!CouldHaveVisualStateExecutableContent(parentPtr)) { + QT3DS_ASSERT(false); + return; + } + TIdEntryExitMap *stateMap = GetStateMapForObject(parentPtr); + if (stateMap == NULL) { + QT3DS_ASSERT(false); + return; + } + + TIdEntryExitMap::iterator iter = stateMap->find(parentPtr->GetId()); + if (iter == stateMap->end()) { + QT3DS_ASSERT(false); + return; + } + TEntryExitPair thePair = iter->second; + if (!RemoveObjectFromEntry(thePair.first, inObj)) { + RemoveObjectFromEntry(thePair.second, inObj); + } + } + + struct SEditorFinder + { + TEditorPtr m_Editor; + SEditorFinder(TEditorPtr editor) + : m_Editor(editor) + { + } + bool operator()(const SEditorEntry &entry) const { return entry.m_Editor == m_Editor; } + }; + + void CopyStateNode(SStateNode &inNode, TIdEntryExitMap &idMap, IDOMWriter &writer) + { + TIdEntryExitMap::iterator iter = idMap.find(inNode.m_Id.c_str()); + if (iter != idMap.end()) { + const char *typeName = "state"; + if (inNode.m_Type == StateNodeTypes::Transition) + typeName = "transition"; + WriteStateEntry(iter, writer, typeName, inNode.m_Id.c_str()); + } + TStateNodeList *childList = inNode.GetChildren(); + if (childList) { + for (TStateNodeList::iterator iter = childList->begin(), end = childList->end(); + iter != end; ++iter) { + CopyStateNode(*iter, idMap, writer); + } + } + } + + virtual void OnCopy(TEditorPtr inEditor, eastl::vector<SStateNode *> &ioCopiedRoots, + IDOMWriter &ioWriter, eastl::vector<SNamespacePair> &ioNamespaces) + { + ioNamespaces.push_back(SNamespacePair(m_StringTable->RegisterStr(GetNamespace()), + m_StringTable->RegisterStr("uia"))); + eastl::vector<SEditorEntry>::iterator entry = + eastl::find_if(m_Editors.begin(), m_Editors.end(), SEditorFinder(inEditor)); + if (entry == m_Editors.end()) + return; + TIdStateMapMap::iterator mapEntry = m_IdToStateMaps.find(entry->m_Id); + if (mapEntry == m_IdToStateMaps.end()) + return; + IDOMWriter::Scope __modelScope(ioWriter, "datamodel_fragment", GetNamespace()); + for (size_t idx = 0, end = ioCopiedRoots.size(); idx < end; ++idx) { + CopyStateNode(*ioCopiedRoots[idx], mapEntry->second, ioWriter); + } + } + + virtual void OnPaste(TEditorPtr inEditor, IDOMReader &ioReader, + CXMLIO::TIdRemapMap &inStateIdRemapMap) + { + eastl::vector<SEditorEntry>::iterator entry = + eastl::find_if(m_Editors.begin(), m_Editors.end(), SEditorFinder(inEditor)); + if (entry == m_Editors.end()) + return; + + IDOMReader::Scope __fragmentScope(ioReader); + if (ioReader.MoveToFirstChild("datamodel_fragment")) { + TIdEntryExitMap &stateMap = + m_IdToStateMaps.insert(eastl::make_pair(entry->m_Id, TIdEntryExitMap())) + .first->second; + for (bool success = ioReader.MoveToFirstChild(); success; + success = ioReader.MoveToNextSibling()) { + IDOMReader::Scope __childScope(ioReader); + const char8_t *idRef; + ioReader.UnregisteredAtt("ref", idRef); + if (isTrivial(idRef)) + continue; + if (idRef[0] == '#') + ++idRef; + + CXMLIO::TIdRemapMap::iterator finder = inStateIdRemapMap.find(idRef); + if (finder != inStateIdRemapMap.end()) + idRef = finder->second.c_str(); + + TEditorStr idStr(idRef); + TObjPtr parentObj = inEditor->GetObjectById(idRef); + if (parentObj) { + ParseVisualStateEntry(ioReader, inEditor, stateMap, idRef); + if (stateMap.find(idStr) != stateMap.end() + && m_TransactionManager->GetOpenTransactionImpl()) { + m_TransactionManager->GetOpenTransactionImpl()->m_Changes.push_back( + new SIdEntryExitDeleteChange(stateMap, idStr, parentObj, false)); + } + } + } + } + } + + virtual void OnIDChange(TEditorPtr inEditor, SStateNode &inNode, const char8_t *inOldId) + { + eastl::vector<SEditorEntry>::iterator entry = + eastl::find_if(m_Editors.begin(), m_Editors.end(), SEditorFinder(inEditor)); + if (entry == m_Editors.end()) + return; + + TIdEntryExitMap &stateMap = + m_IdToStateMaps.insert(eastl::make_pair(entry->m_Id, TIdEntryExitMap())).first->second; + TEditorStr oldIdStr(inOldId); + TEditorStr newIdStr(inNode.m_Id.c_str()); + TIdEntryExitMap::iterator iter = stateMap.find(oldIdStr); + if (iter != stateMap.end()) { + TEntryExitPair thePair(iter->second); + stateMap.erase(iter); + stateMap.insert(eastl::make_pair(newIdStr, thePair)); + } + } + virtual bool OnDeleteState(TObjPtr inObject) + { + // don't care. + if (!CouldHaveVisualStateExecutableContent(inObject)) + return false; + + TIdEntryExitMap *stateMap = GetStateMapForObject(inObject); + if (stateMap == NULL) + return false; + + TIdEntryExitMap::iterator iter = stateMap->find(inObject->GetId()); + if (iter == stateMap->end()) + return false; + NVScopedRefCounted<SIdEntryExitDeleteChange> theChange = + new SIdEntryExitDeleteChange(*stateMap, inObject->GetId(), inObject); + theChange->Do(); + if (GetOpenTransactionImpl()) + GetOpenTransactionImpl()->m_Changes.push_back(theChange.mPtr); + return true; + } + virtual bool OnReloadStateMachine(TEditorPtr inStateMachineEditor) + { + bool theRetval = false; + TEditorPtr theEditor = inStateMachineEditor; + + for (size_t idx = 0, end = m_Editors.size(); idx < end; ++idx) { + if (m_Editors[idx].m_Editor == theEditor) { + TIdStateMapMap::iterator theFind = m_IdToStateMaps.find(m_Editors[idx].m_Id); + if (theFind != m_IdToStateMaps.end()) { + TIdEntryExitMap &theStateMap = theFind->second; + for (TIdEntryExitMap::iterator theIter = theStateMap.begin(), + theEnd = theStateMap.end(); + theIter != theEnd;) { + TEditorStr theStateId = theIter->first; + TObjPtr theState = inStateMachineEditor->GetObjectById(theStateId.c_str()); + if (theState) { + { + NVScopedRefCounted<SVSEntry> theEntry = theIter->second.first; + if (theEntry) { + for (TObjList::iterator theIter = theEntry->m_Editors.begin(), + theEnd = theEntry->m_Editors.end(); + theIter != theEnd; ++theIter) { + SVSEditorObject *theExecutableContent = + static_cast<SVSEditorObject *>(theIter->mPtr); + if (theExecutableContent) { + theExecutableContent->m_ParentObject = theState; + } + } + } + } + { + NVScopedRefCounted<SVSEntry> theEntry = theIter->second.second; + if (theEntry) { + for (TObjList::iterator theIter = theEntry->m_Editors.begin(), + theEnd = theEntry->m_Editors.end(); + theIter != theEnd; ++theIter) { + SVSEditorObject *theExecutableContent = + static_cast<SVSEditorObject *>(theIter->mPtr); + if (theExecutableContent) { + theExecutableContent->m_ParentObject = theState; + } + } + } + } + ++theIter; + } else { + theStateMap.erase(theIter++); + theRetval = true; + } + } + } + break; + } + } + return theRetval; + } + virtual void RegisterChangeListener(IStateMachineChangeListener &) {} + virtual void UnregisterChangeListener(IStateMachineChangeListener &) {} +}; +} + +IDatamodel &IDatamodel::Create(qt3ds::state::editor::TFoundationPtr inFoundation, + const TEditorStr &inPath, const TEditorStr &inAppDir, + IStateMachineEditorManager &inStateMachineEditorManager, + IInStream *inStream) +{ + return IDatamodel::Create(inFoundation, inPath, inAppDir, inStateMachineEditorManager, + IStringTable::CreateStringTable(inFoundation->getAllocator()), + inStream); +} + +IDatamodel &IDatamodel::Create(qt3ds::state::editor::TFoundationPtr inFoundation, + const TEditorStr &inPath, const TEditorStr &inAppDir, + IStateMachineEditorManager &inStateMachineEditorManager, + IStringTable &inStringTable, IInStream *inStream) +{ + DatamodelImpl &theDatamodel = *QT3DS_NEW(inFoundation->getAllocator(), DatamodelImpl)( + inFoundation, inPath, inAppDir, inStateMachineEditorManager, inStringTable); + if (inStream) + theDatamodel.LoadUIADatabaseFromStream(*inStream); + else + theDatamodel.LoadUIADatabase(); + return theDatamodel; +} diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodel.h b/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodel.h new file mode 100644 index 00000000..e064529a --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodel.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef UIA_DATAMODEL_H +#define UIA_DATAMODEL_H +#include "Qt3DSState.h" +#include "Qt3DSStateEditor.h" +#include "Qt3DSMetadata.h" +#include "Qt3DSStateInterpreter.h" +#include "Qt3DSStateEditorFoundation.h" + +namespace qt3ds { +namespace app { + using namespace qt3ds::state; + using namespace qt3ds::state::editor; + + typedef eastl::pair<Q3DStudio::ERuntimeDataModelDataType, + Q3DStudio::ERuntimeAdditionalMetaDataType> + TDataType; + + struct SDatamodelValue; + + struct SAppElement; + + struct SPresentation + { + eastl::string m_Id; + eastl::string m_SrcPath; + eastl::string m_Author; + eastl::string m_Company; + QT3DSU32 m_Width; + QT3DSU32 m_Height; + SAppElement *m_Scene; + SPresentation() + : m_Width(800) + , m_Height(480) + , m_Scene(NULL) + { + } + }; + + class IStateMachineChangeListener + { + public: + virtual ~IStateMachineChangeListener() {} + virtual bool OnDeleteState(TObjPtr inObject) = 0; + virtual bool OnReloadStateMachine(TEditorPtr inStateMachineEditor) = 0; + }; + + class IStateMachineEditorManager + { + public: + virtual ~IStateMachineEditorManager() {} + virtual TEditorPtr GetOrCreateEditor(const TEditorStr &inFullPath, bool *outLoadStatus) = 0; + virtual void RegisterChangeListener(IStateMachineChangeListener &inListener) = 0; + virtual void UnregisterChangeListener(IStateMachineChangeListener &inListener) = 0; + }; + + class IDatamodel : public ITransactionManager, + public IStateMachineEditorManager, + public IStateMachineChangeListener + { + protected: + virtual ~IDatamodel() {} + public: + // Returns true if we had to create the file. Users should always keep track of the + // transaction stack also + // this does not include that. + virtual bool IsDirty() const = 0; + // General queries of the dataset defined by the uia file and all uip files and scxml files + // it includes. + + virtual TEditorStrList GetComponents() = 0; + virtual TEditorStrList GetComponentSlides(const TEditorStr &inComponent) = 0; + + virtual TEditorStrList GetBehaviors() = 0; + + virtual Q3DStudio::THandlerList GetHandlers(const TEditorStr &inBehavior) = 0; + + virtual Q3DStudio::TVisualEventList GetVisualEvents(const TEditorStr &inElement) = 0; + + virtual TEditorStrList GetElements() = 0; + + virtual Q3DStudio::TAttOrArgList GetElementAttributes(const TEditorStr &inElement) = 0; + + // Necessary to share transaction info and trigger deletes of related information but not + // going to get + // done right now. + // virtual TEditorPtr GetOrCreateEditor( const TEditorStr& inFullPath ) = 0; + + // The editor obj has a remove from graph function that really is delete + virtual TObjList + GetVisualStateExecutableContent(TObjPtr inObject, + InterpreterEventTypes::Enum inEventType) = 0; + // Type name is the element name, so set-attribute, goto-slide, or fire-event + virtual TObjPtr AppendVisualStateExecutableContent(TObjPtr inObject, + InterpreterEventTypes::Enum inEventType, + const char8_t *inElementName) = 0; + virtual TObjPtr ChangeVisualStateExecutableContentName(TObjPtr inContent, + const char8_t *inElementName) = 0; + + // Called when the source uia changes. + virtual void RefreshFile() = 0; + virtual void RefreshFromStream(qt3ds::foundation::IInStream &inStream) = 0; + // Returns the path that was passed in on create. + virtual TEditorStr GetFilePath() = 0; + + // Returns false if unable to save, ask users to check the file out. + virtual bool Save() = 0; + virtual bool Save(qt3ds::foundation::IOutStream &inStream) = 0; + + // The section below allows someone to build an initial scene graph. + // Note that components have additional properties + // Get a list of presentations found while parsing uia file. + virtual eastl::vector<SPresentation> GetPresentations() = 0; + virtual eastl::string GetElementType(SAppElement &elem) = 0; + virtual eastl::string GetElementId(SAppElement &elem) = 0; + virtual bool IsComponent(SAppElement &elem) = 0; + virtual Q3DStudio::TAttOrArgList GetElementAttributes(SAppElement &elem) = 0; + // These are found either on the slide or on the component depending on if you are working + // in uip space or runtime space. + virtual Q3DStudio::TAttOrArgList GetSlideAttributes() = 0; + virtual eastl::vector<SDatamodelValue> + GetElementAttributeInitialValues(SAppElement &elem) = 0; + virtual eastl::vector<SAppElement *> GetElementChildren(SAppElement &elem) = 0; + + virtual eastl::string GetLastLoadingErrorString() = 0; + + virtual NVFoundationBase &GetFoundation() = 0; + virtual IStringTable &GetStringTable() = 0; + + // inPath may either exist or not. You can pass in a uip file as well as a uia file. + // application dir is so we can find the meta data. + // inStream provides a way to load from memory, it will be used if it's not NULL. + static IDatamodel &Create(qt3ds::state::editor::TFoundationPtr inFoundation, + const TEditorStr &inPath, const TEditorStr &inApplicationDir, + IStateMachineEditorManager &inStateMachineEditorManager, + IInStream *inStream = 0); + static IDatamodel &Create(qt3ds::state::editor::TFoundationPtr inFoundation, + const TEditorStr &inPath, const TEditorStr &inApplicationDir, + IStateMachineEditorManager &inStateMachineEditorManager, + IStringTable &inStringTable, IInStream *inStream = 0); + }; + + typedef NVScopedRefCounted<IDatamodel> TDatamodelPtr; +} +} +#endif diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodelValue.h b/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodelValue.h new file mode 100644 index 00000000..7698809c --- /dev/null +++ b/src/Runtime/Source/stateapplication/editor/Qt3DSUIADatamodelValue.h @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef UIA_DATAMODEL_VALUE_H +#define UIA_DATAMODEL_VALUE_H +#include "foundation/Qt3DSDiscriminatedUnion.h" +#include "Qt3DSUIADatamodel.h" + +namespace qt3ds { +namespace app { + + using namespace Q3DStudio; + + template <typename TDatatype> + struct SDatamodelValueTypeMap + { + }; + +#define QT3DS_UIA_DATAMODEL_TYPE_MAP(type, enumname) \ + template <> \ + struct SDatamodelValueTypeMap<type> \ + { \ + static Q3DStudio::ERuntimeDataModelDataType GetType() { return Q3DStudio::enumname; } \ + }; + + struct SGuid + { + long m_Data[4]; + }; + + struct SObjectRef + { + CRegisteredString m_Presentation; + CRegisteredString m_Id; + }; + + class CStringOrInt + { + bool m_IsString; + CRegisteredString m_String; + int m_IntValue; + + public: + CStringOrInt(CRegisteredString val = CRegisteredString()) + : m_IsString(true) + , m_String(val) + , m_IntValue(0) + { + } + CStringOrInt(int val) + : m_IsString(false) + , m_IntValue(val) + { + } + CStringOrInt(const CStringOrInt &other) + : m_IsString(other.m_IsString) + , m_String(other.m_String) + , m_IntValue(other.m_IntValue) + { + } + CStringOrInt &operator=(const CStringOrInt &other) + { + m_IsString = other.m_IsString; + m_String = other.m_String; + m_IntValue = other.m_IntValue; + return *this; + } + bool IsString() const { return m_IsString; } + CRegisteredString StringValue() const + { + QT3DS_ASSERT(m_IsString); + return m_String; + } + int IntValue() const + { + QT3DS_ASSERT(m_IsString == false); + return m_IntValue; + } + }; + + QT3DS_UIA_DATAMODEL_TYPE_MAP(float, ERuntimeDataModelDataTypeFloat); + QT3DS_UIA_DATAMODEL_TYPE_MAP(QT3DSVec2, ERuntimeDataModelDataTypeFloat2); + QT3DS_UIA_DATAMODEL_TYPE_MAP(QT3DSVec3, ERuntimeDataModelDataTypeFloat3); + QT3DS_UIA_DATAMODEL_TYPE_MAP(QT3DSI32, ERuntimeDataModelDataTypeLong); + QT3DS_UIA_DATAMODEL_TYPE_MAP(eastl::string, ERuntimeDataModelDataTypeString); + QT3DS_UIA_DATAMODEL_TYPE_MAP(bool, ERuntimeDataModelDataTypeBool); + QT3DS_UIA_DATAMODEL_TYPE_MAP(SGuid, ERuntimeDataModelDataTypeLong4); + QT3DS_UIA_DATAMODEL_TYPE_MAP(CRegisteredString, ERuntimeDataModelDataTypeStringRef); + QT3DS_UIA_DATAMODEL_TYPE_MAP(SObjectRef, ERuntimeDataModelDataTypeObjectRef); + QT3DS_UIA_DATAMODEL_TYPE_MAP(CStringOrInt, ERuntimeDataModelDataTypeStringOrInt); + + struct SDatamodelValueUnionTraits + { + typedef ERuntimeDataModelDataType TIdType; + enum { + TBufferSize = sizeof(eastl::string), + }; + + static TIdType getNoDataId() { return ERuntimeDataModelDataTypeNone; } + + template <typename TDataType> + static TIdType getType() + { + return SDatamodelValueTypeMap<TDataType>().GetType(); + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case ERuntimeDataModelDataTypeFloat: + return inVisitor(*NVUnionCast<float *>(inData)); + case ERuntimeDataModelDataTypeFloat2: + return inVisitor(*NVUnionCast<QT3DSVec2 *>(inData)); + case ERuntimeDataModelDataTypeFloat3: + return inVisitor(*NVUnionCast<QT3DSVec3 *>(inData)); + case ERuntimeDataModelDataTypeLong: + return inVisitor(*NVUnionCast<QT3DSI32 *>(inData)); + case ERuntimeDataModelDataTypeString: + return inVisitor(*NVUnionCast<eastl::string *>(inData)); + case ERuntimeDataModelDataTypeBool: + return inVisitor(*NVUnionCast<bool *>(inData)); + case ERuntimeDataModelDataTypeLong4: + return inVisitor(*NVUnionCast<SGuid *>(inData)); + case ERuntimeDataModelDataTypeStringRef: + return inVisitor(*NVUnionCast<CRegisteredString *>(inData)); + case ERuntimeDataModelDataTypeObjectRef: + return inVisitor(*NVUnionCast<SObjectRef *>(inData)); + case ERuntimeDataModelDataTypeStringOrInt: + return inVisitor(*NVUnionCast<CStringOrInt *>(inData)); + default: + QT3DS_ASSERT(false); + case ERuntimeDataModelDataTypeNone: + return inVisitor(); + } + } + + template <typename TRetType, typename TVisitorType> + static TRetType visit(const char *inData, TIdType inType, TVisitorType inVisitor) + { + switch (inType) { + case ERuntimeDataModelDataTypeFloat: + return inVisitor(*NVUnionCast<const float *>(inData)); + case ERuntimeDataModelDataTypeFloat2: + return inVisitor(*NVUnionCast<const QT3DSVec2 *>(inData)); + case ERuntimeDataModelDataTypeFloat3: + return inVisitor(*NVUnionCast<const QT3DSVec3 *>(inData)); + case ERuntimeDataModelDataTypeLong: + return inVisitor(*NVUnionCast<const QT3DSI32 *>(inData)); + case ERuntimeDataModelDataTypeString: + return inVisitor(*NVUnionCast<const eastl::string *>(inData)); + case ERuntimeDataModelDataTypeBool: + return inVisitor(*NVUnionCast<const bool *>(inData)); + case ERuntimeDataModelDataTypeLong4: + return inVisitor(*NVUnionCast<const SGuid *>(inData)); + case ERuntimeDataModelDataTypeStringRef: + return inVisitor(*NVUnionCast<const CRegisteredString *>(inData)); + case ERuntimeDataModelDataTypeObjectRef: + return inVisitor(*NVUnionCast<const SObjectRef *>(inData)); + case ERuntimeDataModelDataTypeStringOrInt: + return inVisitor(*NVUnionCast<const CStringOrInt *>(inData)); + default: + QT3DS_ASSERT(false); + case ERuntimeDataModelDataTypeNone: + return inVisitor(); + } + } + }; +} +} + +// need some specializations in the original nv foundation namespace +namespace qt3ds { +namespace foundation { + + template <> + struct DestructTraits<qt3ds::app::SGuid> + { + void destruct(qt3ds::app::SGuid &) {} + }; + template <> + struct DestructTraits<qt3ds::app::SObjectRef> + { + void destruct(qt3ds::app::SObjectRef &) {} + }; + template <> + struct DestructTraits<qt3ds::app::CStringOrInt> + { + void destruct(qt3ds::app::CStringOrInt &) {} + }; + template <> + struct DestructTraits<CRegisteredString> + { + void destruct(CRegisteredString &) {} + }; +} +} + +namespace qt3ds { +namespace app { + + typedef qt3ds::foundation:: + DiscriminatedUnion<qt3ds::foundation:: + DiscriminatedUnionGenericBase<SDatamodelValueUnionTraits, + SDatamodelValueUnionTraits:: + TBufferSize>, + SDatamodelValueUnionTraits::TBufferSize> + TDatamodelUnionType; + + struct SDatamodelValue : public TDatamodelUnionType + { + SDatamodelValue() {} + SDatamodelValue(const SDatamodelValue &other) + : TDatamodelUnionType(static_cast<const TDatamodelUnionType &>(other)) + { + } + SDatamodelValue(float other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(QT3DSVec2 other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(QT3DSVec3 other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(QT3DSI32 other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(const eastl::string &other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(bool other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(SGuid other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(CRegisteredString other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(SObjectRef other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue(CStringOrInt other) + : TDatamodelUnionType(other) + { + } + SDatamodelValue &operator=(const SDatamodelValue &other) + { + TDatamodelUnionType::operator=(other); + return *this; + } + }; +} +} +#endif |