You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Tools/LuaIDE/Source/LUA/LUAEditorContext.cpp

2479 lines
103 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/PlatformIncl.h>
#include "LUAEditorContext.h"
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptAsset.h>
#include <AzCore/Script/ScriptContextAttributes.h>
#include <AzCore/Script/ScriptSystemBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/string/regex.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/UI/UICore/SaveChangesDialog.hxx>
#include <AzToolsFramework/UI/LegacyFramework/UIFrameworkAPI.h>
#include <AzToolsFramework/UI/Logging/LogPanel_Panel.h>
#include <AzToolsFramework/UI/UICore/QTreeViewStateSaver.hxx>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <Source/LUA/LUALocalsTrackerMessages.h>
#include <QMessageBox>
#include <regex>
#include "LUAEditorContextInterface.h"
#include "LUAEditorDebuggerMessages.h"
#include "LUAWatchesDebuggerMessages.h"
#include "LUAEditorViewMessages.h"
#include "LUAContextControlMessages.h"
namespace LUAEditor
{
const char* LUAEditorDebugName = "Lua Debug";
const char* LUAEditorInfoName = "Lua Editor";
LUAEditor::Context* s_pLUAEditorScriptPtr = nullptr; // for script access
class BreakpointSavedState
: public AZ::UserSettings
{
public:
AZ_RTTI(BreakpointSavedState, "{EB3E0061-75AC-41F7-8631-6072F6C018EB}", AZ::UserSettings);
AZ_CLASS_ALLOCATOR(BreakpointSavedState, AZ::SystemAllocator, 0);
BreakpointSavedState() {}
BreakpointMap m_Breakpoints;
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<BreakpointSavedState>()
->Field("m_Breakpoints", &BreakpointSavedState::m_Breakpoints)
->Version(2);
}
}
};
AZ::Uuid ContextID("37BA0B6A-CFCF-42CA-91A5-E794BB17AD6D");
//////////////////////////////////////////////////////////////////////////
//Context
Context::Context()
: m_pLUAEditorMainWindow(nullptr)
, m_connectedState(false)
, m_fileIO(nullptr)
{
m_referenceModel = new QStandardItemModel();
m_numOutstandingOperations = 0;
s_pLUAEditorScriptPtr = this;
m_bProcessingActivate = false;
m_bReloadCheckQueued = false;
mostRecentlyOpenedDocumentView.clear();
m_queuedOpenRecent = false;
m_bShuttingDown = false;
AddDefaultLUAKeywords();
AddDefaultLUALibraryFunctions();
}
Context::~Context()
{
s_pLUAEditorScriptPtr = nullptr;
delete m_referenceModel;
for (auto errorData : m_errorData)
{
delete errorData;
}
m_errorData.clear();
AZ_Assert(m_numOutstandingOperations == 0, "Save still pending when shut down.");
AZ_Assert(!m_pLUAEditorMainWindow, "You must deactivate this Context");
}
Context::Context(const Context&)
{
AZ_Assert(false, "You can't copy this type!");
}
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Context::Init()
{
m_fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(m_fileIO, "FileIO system is not present, make sure a FileIO instance is set by the application.");
}
void Context::Activate()
{
ResetTargetContexts();
LegacyFramework::CoreMessageBus::Handler::BusConnect();
ContextInterface::Handler::BusConnect(ContextID);
Context_DocumentManagement::Handler::BusConnect();
Context_DebuggerManagement::Handler::BusConnect();
LUABreakpointRequestMessages::Handler::BusConnect();
LUAStackRequestMessages::Handler::BusConnect();
AzFramework::TargetManagerClient::Bus::Handler::BusConnect();
LUAWatchesRequestMessages::Handler::BusConnect();
LUATargetContextRequestMessages::Handler::BusConnect();
HighlightedWords::Handler::BusConnect();
AzFramework::AssetSystemInfoBus::Handler::BusConnect();
// Connect to source control
using SCConnectionBus = AzToolsFramework::SourceControlConnectionRequestBus;
SCConnectionBus::Broadcast(&SCConnectionBus::Events::EnableSourceControl, true);
//AzToolsFramework::RegisterAssetType(AzToolsFramework::RegisteredAssetType(ContextID, AZ::ScriptAsset::StaticAssetType(), "Script", ".lua", true, 0));
m_pBreakpointSavedState = AZ::UserSettings::CreateFind<BreakpointSavedState>(AZ_CRC("BreakpointSavedState", 0xbb65be3a), AZ::UserSettings::CT_LOCAL);
AzToolsFramework::MainWindowDescription desc;
desc.name = "LUA Editor";
desc.ContextID = ContextID;
desc.hotkeyDesc = AzToolsFramework::HotkeyDescription(AZ_CRC("LUAOpenEditor", 0x5870cf6d), "Ctrl+Shift+L", "Open LUA Editor", "General", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW);
EBUS_EVENT(AzToolsFramework::FrameworkMessages::Bus, AddComponentInfo, desc);
EBUS_EVENT_RESULT(m_ipcOpenFilesHandle, LegacyFramework::IPCCommandBus, RegisterIPCHandler, "open_files", AZStd::bind(&Context::OnIPCOpenFiles, this, AZStd::placeholders::_1));
bool connectedToAssetProcessor = false;
// When the AssetProcessor is already launched it should take less than a second to perform a connection
// but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize
// and able to negotiate a connection when running a debug build
// and to negotiate a connection
AzFramework::AssetSystem::ConnectionSettings connectionSettings;
AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
connectionSettings.m_connectionIdentifier = desc.name;
AzFramework::AssetSystemRequestBus::BroadcastResult(connectedToAssetProcessor,
&AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
if (!connectedToAssetProcessor)
{
AZ_TracePrintf(desc.name.c_str(), "%s was not able to connect to the Asset Processor. Please ensure that the Asset Processor is running.", desc.name.c_str());
}
}
void Context::Deactivate()
{
EBUS_EVENT(LegacyFramework::IPCCommandBus, UnregisterIPCHandler, m_ipcOpenFilesHandle);
//AzToolsFramework::UnRegisterAssetType(AZ::ScriptAsset::StaticAssetType());
LUATargetContextRequestMessages::Handler::BusDisconnect();
LUAWatchesRequestMessages::Handler::BusDisconnect();
LegacyFramework::CoreMessageBus::Handler::BusDisconnect();
//AzToolsFramework::AssetManagementMessages::Handler::BusDisconnect(ContextID);
ContextInterface::Handler::BusDisconnect(ContextID);
Context_DocumentManagement::Handler::BusDisconnect();
Context_DebuggerManagement::Handler::BusDisconnect();
LUAStackRequestMessages::Handler::BusDisconnect();
LUABreakpointRequestMessages::Handler::BusDisconnect();
AzFramework::TargetManagerClient::Bus::Handler::BusDisconnect();
HighlightedWords::Handler::BusDisconnect();
AzFramework::AssetSystemInfoBus::Handler::BusDisconnect();
}
void Context::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("AssetProcessorToolsConnection", 0x734669bc));
}
void Context::ApplicationDeactivated()
{
}
void Context::ApplicationActivated()
{
if (m_bShuttingDown)
{
return;
}
if (m_bProcessingActivate)
{
return;
}
RefreshAllDocumentPerforceStat();
// Open any files we specified in the command line.
if (!m_filesToOpen.empty())
{
for (const auto& file : m_filesToOpen)
{
OpenAssetByPhysicalPath(file.c_str());
}
m_filesToOpen.clear();
}
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->SetupLuaFilesPanel();
}
}
bool Context::OnIPCOpenFiles(const AZStd::string& parameters)
{
if (parameters.empty())
{
return false;
}
AZStd::vector<AZStd::string> files;
AZ::StringFunc::Tokenize(parameters.c_str(), files, ";");
if (!files.empty())
{
for (const auto& file : files)
{
OpenAssetByPhysicalPath(file.c_str());
}
if (m_pLUAEditorMainWindow)
{
if (m_pLUAEditorMainWindow->isMinimized())
{
m_pLUAEditorMainWindow->showNormal();
}
else
{
m_pLUAEditorMainWindow->show();
}
m_pLUAEditorMainWindow->raise();
m_pLUAEditorMainWindow->activateWindow();
m_pLUAEditorMainWindow->setFocus();
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointsUpdate, m_pBreakpointSavedState->m_Breakpoints);
}
return true;
}
return false;
}
void Context::ApplicationShow(AZ::Uuid id)
{
if (ContextID == id)
{
ProvisionalShowAndFocus(true);
}
}
void Context::ApplicationHide(AZ::Uuid id)
{
if (ContextID == id)
{
bool allowed = OnGetPermissionToShutDown(); // everyone must agree to hide before we can
if (allowed)
{
m_pLUAEditorMainWindow->hide();
auto newState = AZ::UserSettings::CreateFind<LUAEditorContextSavedState>(AZ_CRC("LUA EDITOR CONTEXT STATE", 0xc20c7427), AZ::UserSettings::CT_LOCAL);
newState->m_MainEditorWindowIsVisible = false;
}
}
}
void Context::ApplicationCensus()
{
auto newState = AZ::UserSettings::CreateFind<LUAEditorContextSavedState>(AZ_CRC("LUA EDITOR CONTEXT STATE", 0xc20c7427), AZ::UserSettings::CT_LOCAL);
EBUS_EVENT(AzToolsFramework::FrameworkMessages::Bus, ApplicationCensusReply, newState->m_MainEditorWindowIsVisible);
}
void Context::AddDefaultLUAKeywords()
{
AZStd::string keywords[] = {
{"and"}, {"false"}, {"local"}, {"then"}, {"break"}, {"for"}, {"nil"}, {"true"}, {"do"},
{"function"}, {"not"}, {"until"}, {"else"}, {"goto"}, {"or"}, {"while"}, {"elseif"}, {"if"}, {"repeat"},
{"end"}, {"in"}, {"return"}
};
m_LUAKeywords.insert(std::begin(keywords), std::end(keywords));
}
void Context::AddDefaultLUALibraryFunctions()
{
AZStd::string librarys[] = {
{"assert"}, {"collectgarbage"}, {"next"}, {"pairs"}, {"pcall"}, {"rawequal"}, {"rawget"}, {"rawlen"}, {"rawset"},
{"select"}, {"setmetatable"}, {"tonumber"}, {"tostring"}, {"type"}, {"_VERSION"}, {"xpcall"}, {"coroutine.create"}, {"coroutine.resume"}, {"coroutine.running"},
{"coroutine.status"}, {"coroutine.wrap"}, {"coroutine.yield"}, {"string.byte"}, {"string.char"}, {"string.dump"}, {"string.find"}, {"string.format"},
{"string.gmatch"}, {"string.gsub"}, {"string.len"}, {"string.lower"}, {"string.match"}, {"string.rep"}, {"string.reverse"}, {"string.sub"}, {"string.upper"},
{"table.concat"}, {"table.insert"}, {"table.pack"}, {"table.remove"}, {"table.sort"}, {"table.unpack"}, {"math.abs"}, {"math.acos"}, {"math.asin"},
{"math.atan"}, {"math.atan2"}, {"math.ceil"}, {"math.cos"}, {"math.cosh"}, {"math.deg"}, {"math.exp"}, {"math.floor"}, {"math.fmod"}, {"math.frexp"},
{"math.huge"}, {"math.ldexp"}, {"math.log"}, {"math.max"}, {"math.min"}, {"math.modf"}, {"math.pi"}, {"math.pow"}, {"math.rad"}, {"math.random"}, {"math.randomseed"},
{"math.sin"}, {"math.sinh"}, {"math.sqrt"}, {"math.tan"}, {"math.tanh"}, {"bit32.arshift"}, {"bit32.band"}, {"bit32.bnot"}, {"bit32.bor"}, {"bit32.btest"},
{"bit32.bxor"}, {"bit32.extract"}, {"bit32.replace"}, {"bit32.lrotate"}, {"bit32.lshift"}, {"bit32.rrotate"}, {"bit32.rshift"}
};
m_LUALibraryFunctions.insert(std::begin(librarys), std::end(librarys));
}
void Context::RefreshAllDocumentPerforceStat()
{
m_bProcessingActivate = true;
AZ_TracePrintf(LUAEditorDebugName, "Entry refreshalldocumentperforceStat()\n");
for (DocumentInfoMap::iterator it = m_documentInfoMap.begin(); it != m_documentInfoMap.end(); ++it)
{
DocumentInfo& info = it->second;
{
AZ_TracePrintf(LUAEditorDebugName, "Refreshing Perforce for assetId '%s' '%s'\n", info.m_assetId.c_str(), info.m_assetName.c_str());
}
if (m_fileIO && !info.m_bUntitledDocument)
{
// check for updated modtime:
if (!m_fileIO->Exists(info.m_assetId.c_str()))
{
AZ_TracePrintf(LUAEditorDebugName, "During Refresh, a document appears to have been removed from disk: \"%s\"\n", info.m_assetName.c_str());
// this can happen if they mark something for delete...
info.m_bIsModified = true;
info.m_bSourceControl_CanWrite = true;
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->OnDocumentInfoUpdated(info);
}
}
else
{
uint64_t lastKnownModTime = (AZ::u64)info.m_lastKnownModTime.dwHighDateTime << 32 | info.m_lastKnownModTime.dwLowDateTime;
uint64_t modTime = m_fileIO->ModificationTime(info.m_assetId.c_str());
if (lastKnownModTime != modTime)
{
// ruh oh! The file time of the asset changed - someone reverted, modified, etc. What do we do?
// do we have unsaved changes?
info.m_lastKnownModTime.dwHighDateTime = static_cast<DWORD>(modTime >> 32);
info.m_lastKnownModTime.dwLowDateTime = static_cast<DWORD>(modTime);
{
AZ_TracePrintf(LUAEditorDebugName, "Document modtime has changed, queueing reload of '%s' '%s'\n", info.m_assetId.c_str(), info.m_assetName.c_str());
}
// async crossover to test files being written against asset changes
if (info.m_bDataIsWritten)
{
m_reloadCheckDocuments.insert(info.m_assetId);
if (!m_bReloadCheckQueued)
{
m_bReloadCheckQueued = true;
EBUS_QUEUE_FUNCTION(AZ::SystemTickBus, &Context::ProcessReloadCheck, this);
}
}
}
if (!info.m_bSourceControl_BusyGettingStats)
{
// it's OK to skip getting fresh file info from Perforce here
// because we've already given the go-ahead to exit the Application
if (!m_bShuttingDown)
{
{
AZ_TracePrintf(LUAEditorDebugName, "Queuing P4 Refresh of '%s' '%s'\n", info.m_assetId.c_str(), info.m_assetName.c_str());
}
info.m_bSourceControl_BusyGettingStats = true;
// while we're reading it, fetch the perforce information for it:
//AZ_TracePrintf("Debug"," ++m_numOutstandingOperations %d\n",__LINE__);
++m_numOutstandingOperations;
using SCCommandBus = AzToolsFramework::SourceControlCommandBus;
SCCommandBus::Broadcast(&SCCommandBus::Events::GetFileInfo,
info.m_assetId.c_str(),
AZStd::bind(&Context::PerforceStatResponseCallback,
this,
AZStd::placeholders::_1,
AZStd::placeholders::_2,
info.m_assetId)
);
// check for updated modtime, too...
}
}
}
}
}
m_bProcessingActivate = false;
AZ_TracePrintf(LUAEditorDebugName, "Finished refreshalldocumentperforceStat()\n");
}
void Context::ProcessReloadCheck()
{
AZ_TracePrintf(LUAEditorDebugName, "ProcessReloadCheck ProcessReloadCheck()\n");
m_bReloadCheckQueued = false;
for (auto currentDoc = m_reloadCheckDocuments.begin(); currentDoc != m_reloadCheckDocuments.end(); ++currentDoc)
{
DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(*currentDoc);
if (docInfoIter == m_documentInfoMap.end())
{
continue;
}
DocumentInfo& info = docInfoIter->second;
{
AZ_TracePrintf(LUAEditorDebugName, "ProcessReloadCheck inspecting assetId '%s' '%s'\n", info.m_assetId.c_str(), info.m_assetName.c_str());
}
auto newState = AZ::UserSettings::CreateFind<LUAEditorMainWindowSavedState>(AZ_CRC("LUA EDITOR MAIN WINDOW STATE", 0xa181bc4a), AZ::UserSettings::CT_LOCAL);
// Check to see if it is unmodified and the setting is set to auto-reload unmodified files
bool shouldAutoReload = newState->m_bAutoReloadUnmodifiedFiles && !info.m_bIsModified;
bool shouldReload = false;
if (!shouldAutoReload)
{
// we may have unsaved changes:
QMessageBox msgBox(this->m_pLUAEditorMainWindow);
msgBox.setText("A file has been modified by an outside program. Would you like to reload it from disk? If you do, you will lose any unsaved changes.");
msgBox.setInformativeText(info.m_assetName.c_str());
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setButtonText(QMessageBox::Yes, "Reload From Disk");
msgBox.setButtonText(QMessageBox::No, "Don't reload");
msgBox.setDefaultButton(QMessageBox::No);
msgBox.setIcon(QMessageBox::Question);
shouldReload = (msgBox.exec() == QMessageBox::Yes);
}
if (shouldAutoReload || shouldReload)
{
// queue document reopen!
{
AZ_TracePrintf(LUAEditorDebugName, "ProcessReloadCheck user queueing reload for assetId '%s' '%s'\n", info.m_assetId.c_str(), info.m_assetName.c_str());
}
EBUS_QUEUE_FUNCTION(AZ::SystemTickBus, &Context::OnReloadDocument, this, info.m_assetId);
}
else
{
// document remains open and modified, we don't overwrite or reload!
// but also don't prompt them -update the modtime.
if (!info.m_assetId.empty())
{
if (m_fileIO)
{
uint64_t modTime = m_fileIO->ModificationTime(info.m_assetId.c_str());
info.m_lastKnownModTime.dwHighDateTime = static_cast<DWORD>(modTime >> 32);
info.m_lastKnownModTime.dwLowDateTime = static_cast<DWORD>(modTime);
}
}
}
}
AZ_TracePrintf(LUAEditorDebugName, "Exit ProcessReloadCheck()\n");
}
void Context::DesiredTargetConnected(bool connected)
{
AZ_TracePrintf(LUAEditorDebugName, "Context::DesiredTargetConnected( %d )\n", connected);
if (connected)
{
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, EnumerateContexts);
EBUS_EVENT(LUAEditorMainWindowMessages::Bus, OnConnectedToTarget);
EBUS_EVENT(LUAEditor::Context_ControlManagement::Bus, OnTargetConnected);
m_connectedState = true;
}
else
{
EBUS_EVENT(LUAEditorMainWindowMessages::Bus, OnDisconnectedFromTarget);
EBUS_EVENT(LUAEditor::Context_ControlManagement::Bus, OnTargetDisconnected);
EBUS_EVENT(LUAEditor::Context_DebuggerManagement::Bus, OnDebuggerDetached);
m_connectedState = false;
}
}
void Context::DesiredTargetChanged(AZ::u32 newTargetID, AZ::u32 oldTargetID)
{
(void)oldTargetID;
(void)newTargetID;
AZ_TracePrintf(LUAEditorDebugName, "Context::RemoteTargetChanged()\n");
RequestDetachDebugger();
}
//////////////////////////////////////////////////////////////////////////
//BreakpointTracker Request Messages
const BreakpointMap* Context::RequestBreakpoints()
{
return &m_pBreakpointSavedState->m_Breakpoints;
}
//////////////////////////////////////////////////////////////////////////
//StackTracker Request Messages
void Context::RequestStackClicked(const AZStd::string& stackString, int lineNumber)
{
// incoming display string looks like this:
/*
"[Lua] gameinfo\script\player\playercharacter_strider (587) : PreStateTick(37BA18D8, 0.033333)"
*/
// outgoing string for the asset name should look like this:
/*
"gameinfo\script\player\playercharacter_strider"
*/
QString script = stackString.c_str();
script = script.right(script.length() - 6);
script = script.left(script.indexOf(" "));
RequestEditorFocus(script.toUtf8().data(), lineNumber);
}
//////////////////////////////////////////////////////////////////////////
//TargetContextTracker Request Messages
const AZStd::vector<AZStd::string> Context::RequestTargetContexts()
{
return m_targetContexts;
}
const AZStd::string Context::RequestCurrentTargetContext()
{
return m_currentTargetContext;
}
void Context::RequestEditorFocus(const AZStd::string& relativePath, int lineNumber)
{
AZStd::to_lower(const_cast<AZStd::string&>(relativePath).begin(), const_cast<AZStd::string&>(relativePath).end());
bool foundAbsolutePath = false;
AZStd::string absolutePath;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundAbsolutePath,
&AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, absolutePath);
bool fileFound = false;
if (foundAbsolutePath)
{
AZStd::to_lower(const_cast<AZStd::string&>(absolutePath).begin(), const_cast<AZStd::string&>(absolutePath).end());
auto docInfoIter = m_documentInfoMap.find(absolutePath);
fileFound = docInfoIter != m_documentInfoMap.end();
}
if (!fileFound)
{
// Check if we have a relative path, we may still be able to open the file (this may happen when double clicking on a stack panel entry)
for (const auto& doc : m_documentInfoMap)
{
const char* result = strstr(doc.first.c_str(), relativePath.c_str());
if (result)
{
absolutePath = doc.second.m_assetId;
fileFound = true;
break;
}
}
if (!fileFound)
{
// the document was probably closed.
if (foundAbsolutePath)
{
AssetOpenRequested(absolutePath, true);
}
else
{
AssetOpenRequested(relativePath, true);
}
return;
}
}
ProvisionalShowAndFocus();
// tell the view that it needs to focus that document!
m_pLUAEditorMainWindow->OnRequestFocusView(absolutePath);
m_pLUAEditorMainWindow->MoveEditCursor(absolutePath, lineNumber, true);
}
void Context::RequestDeleteBreakpoint(const AZStd::string& assetIdString, int lineNumber)
{
for (BreakpointMap::iterator it = m_pBreakpointSavedState->m_Breakpoints.begin(); it != m_pBreakpointSavedState->m_Breakpoints.end(); ++it)
{
const Breakpoint& bp = it->second;
if (bp.m_assetName == assetIdString)
{
if (bp.m_documentLine == lineNumber - 1) // -1 offset is counter to the +1 line numbering scheme used by the LUA editor
{
DeleteBreakpoint(bp.m_breakpointId);
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointsUpdate, m_pBreakpointSavedState->m_Breakpoints);
break;
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// AzToolsFramework CoreMessages and support
void Context::OnRestoreState()
{
const AZStd::string k_launchString = "launch";
const AZStd::string k_luaEditorString = "lua";
const AZStd::string k_luaScriptFileString = "files";
// the world editor considers itself a default window, so it always makes one
typedef AzToolsFramework::FrameworkMessages::Bus HotkeyBus;
// register our hotkeys so that they exist in the preferences panel even if we're not open:
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAFind", 0xc62d8078), "Ctrl+F", "Find", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAQuickFindLocal", 0x115cbcda), "Ctrl+F3", "Quick Find Local", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAQuickFindLocalReverse", 0xdd8a0c22), "Ctrl+Shift+F3", "Quick Find Local (Reverse)", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAFindInFiles", 0xdaebdfdd), "Ctrl+Shift+F", "Find In Files", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAReplace", 0x1fd5510c), "Ctrl+R", "Replace", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAReplaceInFiles", 0x38b609e0), "Ctrl+Shift+R", "Replace In Files", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAGoToLine", 0xb6603f27), "Ctrl+G", "Go to line number...", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAFold", 0xf0969e48), "Alt+0", "Fold Source Functions", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAUnfold", 0x36934ecd), "Alt+Shift+0", "Unfold Source Functions", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUACloseAllExceptCurrent", 0x0076409a), "Ctrl+Alt+F4", "Close All Windows Except Current", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUACloseAll", 0xf732678f), "Ctrl+Shift+F4", "Close All Windows", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAComment", 0x873c2725), "Ctrl+K", "Comment Selected Block", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAUncomment", 0x9190cf18), "Ctrl+Shift+K", "Uncomment Selected Block", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUALinesUpTranspose", 0xafc899ef), "Ctrl+Shift+Up", "Transpose Lines Up", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUALinesDnTranspose", 0xf9d733bf), "Ctrl+Shift+Down", "Transpose Lines Down", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
EBUS_EVENT(HotkeyBus, RegisterHotkey, AzToolsFramework::HotkeyDescription(AZ_CRC("LUAResetZoom", 0xbe0787ad), "Ctrl+0", "Reset Default Zoom", "LUA Editor", 1, AzToolsFramework::HotkeyDescription::SCOPE_WINDOW));
bool GUIMode = true;
EBUS_EVENT_RESULT(GUIMode, LegacyFramework::FrameworkApplicationMessages::Bus, IsRunningInGUIMode);
if (!GUIMode)
{
// do not auto create lua editor main window in batch mode.
return;
}
const AZ::CommandLine* commandLine = nullptr;
AZ::ComponentApplicationBus::Broadcast([&commandLine](AZ::ComponentApplicationRequests* requests)
{
commandLine = requests->GetAzCommandLine();
});
bool forceShow = false;
bool forceHide = false;
if (commandLine->HasSwitch(k_launchString))
{
forceHide = true;
size_t numSwitchValues = commandLine->GetNumSwitchValues(k_launchString);
for (size_t i = 0; i < numSwitchValues; ++i)
{
AZStd::string inputValue = commandLine->GetSwitchValue(k_launchString, i);
if (inputValue.compare(k_luaEditorString) == 0)
{
forceShow = true;
forceHide = false;
}
}
}
size_t numSwitchValues = commandLine->GetNumSwitchValues(k_luaScriptFileString);
if (numSwitchValues >= 1)
{
m_filesToOpen.clear();
for (size_t i = 0; i < numSwitchValues; ++i)
{
AZStd::string inputValue = commandLine->GetSwitchValue(k_luaScriptFileString, i);
// Open the file(s)
AZStd::to_lower(const_cast<AZStd::string&>(inputValue).begin(), const_cast<AZStd::string&>(inputValue).end());
// Cache the files we want to open, we will open them when we activate the main window.
m_filesToOpen.push_back(inputValue);
}
}
ProvisionalShowAndFocus(forceShow, forceHide);
}
bool Context::OnGetPermissionToShutDown() // until everyone returns true, we can't shut down.
{
AZ_TracePrintf(LUAEditorDebugName, "Context::OnGetPermissionToShutDown()\n");
if (m_pLUAEditorMainWindow)
{
if (!m_pLUAEditorMainWindow->OnGetPermissionToShutDown())
{
return false;
}
}
m_bShuttingDown = true;
return true;
}
// until everyone returns true, we can't shut down.
bool Context::CheckOkayToShutDown()
{
if (m_pLUAEditorMainWindow)
{
// confirmation that we're quitting.
if (m_pLUAEditorMainWindow->isVisible())
{
m_pLUAEditorMainWindow->setEnabled(false);
m_pLUAEditorMainWindow->hide();
}
}
if (m_numOutstandingOperations > 0)
{
AZ_TracePrintf(LUAEditorDebugName, "CheckOkayToShutDown() return FALSE with (%d) OutstandingOperations\n", static_cast<int>(m_numOutstandingOperations));
return false;
}
return true;
}
void Context::OnSaveState()
{
// notify main view to persist?
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->SaveWindowState();
}
}
// Script interface:
void Context::LoadLayout()
{
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->RestoreWindowState();
}
}
void Context::SaveLayout()
{
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->SaveWindowState();
}
}
void Context::OnDestroyState()
{
m_documentInfoMap.clear();
if (m_pLUAEditorMainWindow)
{
delete m_pLUAEditorMainWindow;
}
m_pLUAEditorMainWindow = nullptr;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//ContextInterface
//////////////////////////////////////////////////////////////////////////
// Utility
void Context::ProvisionalShowAndFocus(bool forcedShow, bool forcedHide)
{
// main view will auto persist (load)
auto newState = AZ::UserSettings::CreateFind<LUAEditorContextSavedState>(AZ_CRC("LUA EDITOR CONTEXT STATE", 0xc20c7427), AZ::UserSettings::CT_LOCAL);
if (forcedShow)
{
newState->m_MainEditorWindowIsOpen = true;
newState->m_MainEditorWindowIsVisible = true;
}
else if (forcedHide)
{
newState->m_MainEditorWindowIsOpen = false;
newState->m_MainEditorWindowIsVisible = false;
}
if (newState->m_MainEditorWindowIsOpen)
{
if (newState->m_MainEditorWindowIsVisible)
{
if (!m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow = aznew LUAEditorMainWindow(m_referenceModel, m_connectedState);
}
m_pLUAEditorMainWindow->show();
m_pLUAEditorMainWindow->raise();
m_pLUAEditorMainWindow->activateWindow();
m_pLUAEditorMainWindow->setFocus();
}
else
{
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->hide();
}
}
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointsUpdate, m_pBreakpointSavedState->m_Breakpoints);
}
}
//////////////////////////////////////////////////////////////////////////
// Context_DocumentManagement Messages
void Context::OnNewDocument(const AZStd::string& assetId)
{
ShowLUAEditorView();
AZStd::string normalizedAssetId = assetId;
AZStd::to_lower(normalizedAssetId.begin(), normalizedAssetId.end());
// make sure we have a name that is not already tracked
DocumentInfoMap::pair_iter_bool infoEntry = m_documentInfoMap.insert_key(normalizedAssetId);
DocumentInfo& info = infoEntry.first->second;
info.m_assetId = normalizedAssetId;
info.m_assetName = assetId;
AZ::StringFunc::Path::GetFullFileName(assetId.c_str(), info.m_displayName);
info.m_bSourceControl_Ready = true;
info.m_bSourceControl_CanWrite = true;
info.m_bUntitledDocument = false;
info.m_bIsBeingSaved = false;
info.m_scriptAsset = "";
// now open a view that will end up with its info (the view will have a progress bar on it as it loads)
m_pLUAEditorMainWindow->OnOpenLUAView(info);
// now since there is no actual loading we just tell its done, since the document is untitled it wont try
// retrieve the document data in the call
info.m_bDataIsLoaded = true;
m_pLUAEditorMainWindow->OnDocumentInfoUpdated(info);
}
void Context::OnLoadDocument(const AZStd::string& assetId, bool errorOnNotFound)
{
AssetOpenRequested(assetId, errorOnNotFound);
}
void Context::OnCloseDocument(const AZStd::string& id)
{
AZStd::string assetId = id; // as we might delete the reference
AZ_TracePrintf(LUAEditorDebugName, AZStd::string::format("LUAEditor OnCloseDocument" "%s\n", assetId.c_str()).c_str());
DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(assetId);
AZ_Assert(docInfoIter != m_documentInfoMap.end(), "LUAEditor OnCloseDocument() : Cant find Document Info.");
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->OnCloseView(assetId);
}
m_documentInfoMap.erase(docInfoIter);
docInfoIter = m_documentInfoMap.find(assetId);
if (docInfoIter != m_documentInfoMap.end())
{
//AZ_Assert(docInfoIter != m_documentInfoMap.end(), "LUAEditor OnCloseDocument() : Cant find Document Data.");
m_documentInfoMap.erase(docInfoIter);
}
CleanUpBreakpoints();
}
void Context::OnSaveDocument(const AZStd::string& assetId, bool bCloseAfterSave, bool bSaveAs)
{
AZ_TracePrintf(LUAEditorDebugName, AZStd::string::format("LUAEditor OnSaveDocument" "%s\n", assetId.c_str()).c_str());
if (!m_pLUAEditorMainWindow)
{
return;
}
// Make a copy because it may be modified behind our backs by later bus calls
AZStd::string originalAssetId = assetId;
auto docInfoIter = m_documentInfoMap.find(originalAssetId);
if (docInfoIter == m_documentInfoMap.end())
{
AZ_TracePrintf(LUAEditorDebugName, "Context::OnSaveDocument - Document with ID is already closed - ignoring.\n");
return;
}
AZStd::string newAssetName(docInfoIter->second.m_assetName);
bool newFileCreated = false;
if (docInfoIter->second.m_bIsBeingSaved)
{
return;
}
bool trySaveAs = docInfoIter->second.m_bUntitledDocument || bSaveAs;
while (trySaveAs)
{
if (!m_pLUAEditorMainWindow->OnFileSaveDialog(docInfoIter->second.m_assetName, newAssetName))
{
return;
}
// the file dialog lets us do silly things like choose the same name as the original
// in which case we should treat it just like a regular save
if (newAssetName == docInfoIter->second.m_assetName)
{
docInfoIter->second.m_bUntitledDocument = false;
break;
}
// do not allow SaveAs onto an existing asset, even if it could be checked out and modified "safely."
// end user must check out and modify contents directly if they want this
if (AZ::StringFunc::Find(newAssetName.c_str(), ".lua") == AZStd::string::npos)
{
newAssetName += ".lua";
}
AZ::Data::AssetId catalogAssetId;
EBUS_EVENT_RESULT(catalogAssetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, newAssetName.c_str(), AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid(), false);
if (catalogAssetId.IsValid() || m_fileIO->Exists(newAssetName.c_str()))
{
QMessageBox::warning(m_pLUAEditorMainWindow, "Warning", "You Cannot SaveAs Over An Existing Asset\nPlease Check And Try A New Filename");
continue;
}
trySaveAs = false;
docInfoIter->second.m_bUntitledDocument = false;
AZ::StringFunc::Path::GetFullFileName(newAssetName.c_str(), docInfoIter->second.m_displayName);
// when you 'save as' you can write to it, even if it started out not that way.
docInfoIter->second.m_bSourceControl_Ready = true;
docInfoIter->second.m_bSourceControl_CanWrite = true;
newFileCreated = true;
}
if (!docInfoIter->second.m_bSourceControl_CanWrite)
{
AZ_Warning("LUA Editor Error", false, "<div severity=\"warning\">Unable to save document - the document is read-only.</div>");
}
docInfoIter->second.m_bDataIsWritten = false;
docInfoIter->second.m_bCloseAfterSave = bCloseAfterSave;
docInfoIter->second.m_bIsBeingSaved = true;
//AZ_TracePrintf("Debug"," ++m_numOutstandingOperations %d\n",__LINE__);
++m_numOutstandingOperations;
//////////////////////////////////////////////////////////////////////////
// Blocking test for now (use the streamer later)
// insert with the proper ID (saved file)
bool isSaved = false;
AZ::IO::SystemFile luaFile;
if (luaFile.Open(newAssetName.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY))
{
luaFile.Write(docInfoIter->second.m_scriptAsset.c_str(), docInfoIter->second.m_scriptAsset.size());
isSaved = true;
luaFile.Close();
}
// Open the newly saved file
if (isSaved)
{
if (newFileCreated)
{
docInfoIter->second.m_bCloseAfterSave = true;
AZStd::string normalizedAssetId = newAssetName;
AZStd::to_lower(normalizedAssetId.begin(), normalizedAssetId.end());
EBUS_EVENT(Context_DocumentManagement::Bus, OnLoadDocument, normalizedAssetId, true);
DocumentCheckOutRequested(normalizedAssetId);
}
}
DataSaveDoneCallback(isSaved, originalAssetId);
//////////////////////////////////////////////////////////////////////////
m_pLUAEditorMainWindow->OnDocumentInfoUpdated(docInfoIter->second);
}
bool Context::OnSaveDocumentAs(const AZStd::string& assetId, bool bCloseAfterSave)
{
AZ_TracePrintf(LUAEditorDebugName, AZStd::string::format("LUAEditor OnSaveDocumentAs" "%s\n", assetId.c_str()).c_str());
[[maybe_unused]] DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(assetId);
AZ_Assert(docInfoIter != m_documentInfoMap.end(), "LUAEditor OnSaveDocumentAs() : Cant find Document Info.");
OnSaveDocument(assetId, bCloseAfterSave, true);
return true;
}
void Context::DocumentCheckOutRequested(const AZStd::string& assetId)
{
DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(assetId);
AZ_Assert(docInfoIter != m_documentInfoMap.end(), "Invalid document lookup.");
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIO, "FileIO system is not present.");
if (fileIO && !fileIO->Exists(assetId.c_str()))
{
AZ_Warning(LUAEditorInfoName, false, "%s : Unable to check out file from source control, it may need to be saved first.", assetId.c_str());
return;
}
DocumentInfo& docInfo = docInfoIter->second;
AZ_TracePrintf(LUAEditorDebugName, "LUAEditor DocumentCheckOutRequested: %s\n", docInfo.m_assetName.c_str());
docInfo.m_bSourceControl_BusyRequestingEdit = true;
AZStd::string sourceFile;
bool fileFound = false;
using AssetSysReqBus = AzToolsFramework::AssetSystemRequestBus;
AssetSysReqBus::BroadcastResult(fileFound, &AssetSysReqBus::Events::GetFullSourcePathFromRelativeProductPath, assetId.c_str(), sourceFile);
if (!fileFound)
{
// This warning can be tripped if the LuaIDE loses connection with the asset processor.
AZ_Warning(LUAEditorInfoName, false, "The Lua IDE source control integration requires an active connection to the Asset Processor. Make sure Asset Processor is running.");
// Reset BusyRequestingEdit or we'll be stuck with the "checking out" loading bar for forever
docInfo.m_bSourceControl_BusyRequestingEdit = false;
return;
}
////AZ_TracePrintf("Debug"," ++m_numOutstandingOperations %d\n",__LINE__);
++m_numOutstandingOperations;
using SCCommandBus = AzToolsFramework::SourceControlCommandBus;
SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit,
sourceFile.c_str(),
true,
AZStd::bind(&Context::PerforceRequestEditCallback,
this,
AZStd::placeholders::_1,
AZStd::placeholders::_2,
assetId)
);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//AssetManagementMessages
//Our callback that tell us when the asset open request finishes
void Context::DataLoadDoneCallback(bool success, const AZStd::string& assetId)
{
//AZ_TracePrintf("Debug"," --m_numOutstandingOperations %d\n",__LINE__);
--m_numOutstandingOperations;
{
AZ_TracePrintf("Debug", "DataLoadDoneCallback() ENTRY: loaded data for assetId %s\n", assetId.c_str());
}
// update data?
if (success)
{
DocumentInfoMap::iterator actualDocument = m_documentInfoMap.find(assetId);
AZ_Assert(actualDocument != m_documentInfoMap.end(), "Invalid document lookup.");
{
AZ_TracePrintf(LUAEditorDebugName, "DataLoadDoneCallback() sending OnDocumentInfoUpdated data for assetId '%s' '%s'\n", assetId.c_str(), actualDocument->second.m_assetName.c_str());
}
actualDocument->second.m_bDataIsLoaded = true;
actualDocument->second.m_bIsModified = false;
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->OnDocumentInfoUpdated(actualDocument->second);
}
}
AZ_TracePrintf(LUAEditorDebugName, "DataLoadDoneCallback() EXIT\n");
}
void Context::DataSaveDoneCallback(bool success, const AZStd::string& assetId)
{
//AZ_TracePrintf("Debug"," --m_numOutstandingOperations %d\n",__LINE__);
--m_numOutstandingOperations;
{
// forensic logging
AZ_TracePrintf(LUAEditorDebugName, "DataSaveDoneCallback() ENTRY: data save returned for assetId %s (%s)\n", assetId.c_str(), success ? "TRUE" : "FALSE");
}
DocumentInfoMap::iterator actualDocument = m_documentInfoMap.find(assetId);
AZ_Assert(actualDocument != m_documentInfoMap.end(), "Invalid assetId lookup.");
if (actualDocument == m_documentInfoMap.end())
{
return;
}
actualDocument->second.m_bIsBeingSaved = false; // we are no longer saving - regardless of whether we succeeded or not!
// update data:
if (success)
{
actualDocument->second.m_bDataIsWritten = true;
//update the mod time in the document info
if (m_fileIO)
{
uint64_t modTime = m_fileIO->ModificationTime(assetId.c_str());
actualDocument->second.m_lastKnownModTime.dwHighDateTime = static_cast<DWORD>(modTime >> 32);
actualDocument->second.m_lastKnownModTime.dwLowDateTime = static_cast<DWORD>(modTime);
actualDocument->second.m_bDataIsLoaded = true;
actualDocument->second.m_bIsModified = false;
}
// refresh source info:
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->OnDocumentInfoUpdated(actualDocument->second);
}
if (actualDocument->second.m_bCloseAfterSave)
{
EBUS_EVENT(Context_DocumentManagement::Bus, OnCloseDocument, assetId);
}
}
}
void Context::NotifyDocumentModified(const AZStd::string& assetId, bool modified)
{
// the document was modified, note this down
DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(assetId);
AZ_Assert(docInfoIter != m_documentInfoMap.end(), "Invalid document lookup.");
docInfoIter->second.m_bIsModified = modified;
}
void Context::UpdateDocumentData(const AZStd::string& assetId, const char* dataPtr, const AZStd::size_t dataLength)
{
DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(assetId);
AZ_Assert(docInfoIter != m_documentInfoMap.end(), "Invalid document lookup.");
DocumentInfo& docInfo = docInfoIter->second;
AZ_Assert(docInfo.m_bDataIsLoaded, "You may not retrieve data until it is loaded.");
//docInfo.m_scriptAsset.GetAssetData()->SetData(dataPtr, dataLength);
docInfo.m_scriptAsset = AZStd::string(dataPtr, dataLength);
}
void Context::GetDocumentData(const AZStd::string& assetId, const char** dataPtr, AZStd::size_t& dataLength)
{
DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(assetId);
AZ_Assert(docInfoIter != m_documentInfoMap.end(), "Invalid document lookup.");
DocumentInfo& docInfo = docInfoIter->second;
AZ_Assert(docInfo.m_bDataIsLoaded, "You may not retrieve data until it is loaded.");
*dataPtr = docInfo.m_scriptAsset.data();
dataLength = docInfo.m_scriptAsset.size();
}
void Context::PerforceStatResponseCallback(bool success, const AzToolsFramework::SourceControlFileInfo& fileInfo, const AZStd::string& assetId)
{
{
AZ_TracePrintf("Debug", "PerforceStatResponseCallback() ENTRY: loaded assetId %s\n", assetId.c_str());
}
//AZ_TracePrintf("Debug"," --m_numOutstandingOperations %d\n",__LINE__);
--m_numOutstandingOperations;
// you got a callback from the perforce API, this is guaranteed to be on the main thread.
DocumentInfoMap::iterator actualDocument = m_documentInfoMap.find(assetId);
// the document may have already been closed. this is fine.
if (actualDocument == m_documentInfoMap.end())
{
{
AZ_TracePrintf("Debug", "PerforceStatResponseCallback() EXIT: no such assetId %s\n", assetId.c_str());
}
return;
}
DocumentInfo& doc = actualDocument->second;
//only means stats has been retrieved at least once
doc.m_bSourceControl_Ready = true;
//this operation is now considered done
doc.m_bSourceControl_BusyGettingStats = false;
//check file info flags to see if we can write
doc.m_bSourceControl_CanWrite = (fileInfo.m_flags & AzToolsFramework::SCF_Writeable) != 0;
doc.m_sourceControlInfo = fileInfo;
//if we can check out is slightly a little more complicated
//if the stat operation failed then we cant check out
//if the stat operation succeeded then we need to make sure that it is currently checked in and its not out of date
if (success == false)
{
doc.m_bSourceControl_CanCheckOut = false;
}
else
{
doc.m_bSourceControl_CanCheckOut = (fileInfo.IsManaged() && !(fileInfo.m_flags & AzToolsFramework::SCF_OutOfDate));
doc.m_bSourceControl_CanCheckOut = fileInfo.m_flags & AzToolsFramework::SCF_MultiCheckOut || doc.m_bSourceControl_CanCheckOut;
}
AZ_TracePrintf(LUAEditorDebugName, "PerforceStatResponseCallback() sending OnDocumentInfoUpdated\n");
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->OnDocumentInfoUpdated(doc);
}
{
AZ_TracePrintf("Debug", "PerforceStatResponseCallback() EXIT: OK %s\n", assetId.c_str());
}
}
void Context::PerforceRequestEditCallback(bool success, const AzToolsFramework::SourceControlFileInfo& fileInfo, const AZStd::string& assetId)
{
//AZ_TracePrintf("Debug"," --m_numOutstandingOperations %d\n",__LINE__);
--m_numOutstandingOperations;
// you got a callback from the perforce API, this is guaranteed to be on the main thread.
DocumentInfoMap::iterator actualDocument = m_documentInfoMap.find(assetId);
if (actualDocument == m_documentInfoMap.end())
{
return;
}
DocumentInfo& doc = actualDocument->second;
//this operation is considered done
doc.m_bSourceControl_BusyRequestingEdit = false;
//check file info flags to see if we can write
doc.m_bSourceControl_CanWrite = !fileInfo.IsReadOnly();
doc.m_sourceControlInfo = fileInfo;
//if we can check out is slightly a little more complicated
//if the stat operation failed then we cant check out
//if the stat operation succeeded then we need to make sure that it is currently checked in and its not out of date
if (success == false)
{
doc.m_bSourceControl_CanCheckOut = false;
}
else
{
doc.m_bSourceControl_CanCheckOut = fileInfo.IsManaged() && !fileInfo.HasFlag(AzToolsFramework::SCF_OutOfDate);
doc.m_bSourceControl_CanCheckOut = fileInfo.HasFlag(AzToolsFramework::SCF_MultiCheckOut) || doc.m_bSourceControl_CanCheckOut;
}
if (!doc.m_bSourceControl_Ready)
{
QMessageBox::warning(m_pLUAEditorMainWindow, "Warning", "Perforce shows that it's not ready.");
}
if (!doc.m_bSourceControl_CanWrite)
{
if (!doc.m_sourceControlInfo.HasFlag(AzToolsFramework::SCF_OpenByUser))
{
QMessageBox::warning(m_pLUAEditorMainWindow, "Warning", "This file is ReadOnly you cannot write to this file.");
}
}
else if (!doc.m_bSourceControl_CanCheckOut)
{
if (doc.m_sourceControlInfo.m_status == AzToolsFramework::SCS_ProviderIsDown)
{
QMessageBox::warning(m_pLUAEditorMainWindow, "Warning", "Perforce Is Down.\nFile will be saved.\nYou must reconcile with Perforce later!");
}
else if (doc.m_sourceControlInfo.m_status == AzToolsFramework::SCS_ProviderError)
{
QMessageBox::warning(m_pLUAEditorMainWindow, "Warning", "Perforce encountered an error.\nFile will be saved.\nYou must reconcile with Perforce later!");
}
else if (doc.m_sourceControlInfo.m_status == AzToolsFramework::SCS_CertificateInvalid)
{
QMessageBox::warning(m_pLUAEditorMainWindow, "Warning", "Perforce Connection is not trusted.\nFile will be saved.\nYou must reconcile with Perforce later!");
}
else if (!doc.m_sourceControlInfo.HasFlag(AzToolsFramework::SCF_OpenByUser))
{
QMessageBox::warning(m_pLUAEditorMainWindow, "Warning", "Perforce says that you cannot write to this file.");
}
}
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->OnDocumentInfoUpdated(doc);
}
}
void Context::OnReloadDocument(const AZStd::string assetId)
{
DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(assetId);
{
AZ_TracePrintf(LUAEditorDebugName, "OnReloadDocument() ENTRY user queing reload for assetId '%s'\n", assetId.c_str());
}
AZ_Assert(docInfoIter != m_documentInfoMap.end(), "Invalid document lookup.");
docInfoIter->second.m_bDataIsLoaded = false;
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->OnDocumentInfoUpdated(docInfoIter->second);
}
AZ_TracePrintf(LUAEditorDebugName, "OnReloadDocument() Beginning asset load.\n");
docInfoIter->second.m_assetId = assetId;
// while we're reading it, fetch the perforce information for it:
// AZ_TracePrintf("Debug"," ++m_numOutstandingOperations %d\n",__LINE__);
++m_numOutstandingOperations;
using SCCommandBus = AzToolsFramework::SourceControlCommandBus;
SCCommandBus::Broadcast(&SCCommandBus::Events::GetFileInfo,
assetId.c_str(),
AZStd::bind(&Context::PerforceStatResponseCallback,
this,
AZStd::placeholders::_1,
AZStd::placeholders::_2,
assetId)
);
AZ_TracePrintf(LUAEditorDebugName, "OnReloadDocument() Queuing bind bus relay.\n");
++m_numOutstandingOperations;
// Load the document
bool isLoaded = false;
AZ::IO::SystemFile luaFile;
if (luaFile.Open(assetId.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY))
{
docInfoIter->second.m_scriptAsset.resize(luaFile.Length());
luaFile.Read(docInfoIter->second.m_scriptAsset.size(), docInfoIter->second.m_scriptAsset.data());
isLoaded = true;
luaFile.Close();
}
Context::DataLoadDoneCallback(isLoaded, docInfoIter->second.m_assetId);
}
void Context::OpenMostRecentDocumentView()
{
m_queuedOpenRecent = false;
if (mostRecentlyOpenedDocumentView.empty())
{
return;
}
if (m_pLUAEditorMainWindow)
{
m_bProcessingActivate = true;
m_pLUAEditorMainWindow->IgnoreFocusEvents(false);
m_pLUAEditorMainWindow->setAnimated(false);
ProvisionalShowAndFocus();
m_pLUAEditorMainWindow->OnRequestFocusView(mostRecentlyOpenedDocumentView);
m_pLUAEditorMainWindow->setAnimated(true);
m_bProcessingActivate = false;
}
}
void Context::OpenAssetByPhysicalPath(const char* physicalPath)
{
//error check input
if (!physicalPath)
{
AZ_Warning("LUAEditor::Context", false, AZStd::string::format("<span severity=\"err\">Path is null: '%s'</span>", physicalPath).c_str());
return;
}
if (!strlen(physicalPath))
{
AZ_Warning("LUAEditor::Context", false, AZStd::string::format("<span severity=\"err\">Path is empty: '%s'</span>", physicalPath).c_str());
return;
}
if (!m_fileIO->Exists(physicalPath))
{
AZ_Warning(LUAEditorInfoName, false, AZStd::string::format("<span severity=\"err\">Could not open the file, file not found: '%s'</span>", physicalPath).c_str());
QMessageBox msgBox(m_pLUAEditorMainWindow);
msgBox.setModal(true);
msgBox.setText("File not found");
msgBox.setInformativeText(physicalPath);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Critical);
msgBox.exec();
CleanUpBreakpoints();
return;
}
AssetOpenRequested(physicalPath, true);
}
void Context::AssetOpenRequested(const AZStd::string& assetId, bool errorOnNotFound)
{
if (!m_fileIO)
{
return;
}
AZStd::string assetIdLower(assetId);
AZStd::to_lower(assetIdLower.begin(), assetIdLower.end());
ShowLUAEditorView();
// asset browser requests opening of a particular assets.
// we need to do a whole bunch of things
// * we need to start tracking and validate the document that is about to be opened. It might already be open, for example.
// - documents may belong to another Context (?) like for example if entities have embedded blobs of lua. In that case, the
// interface may be different in that the asset is treated as a different type and the other Context manages the docs.
// * we need to create a new LUA panel for it
// * we need to load that lua panel with the document's data, initializing it.
// are we already tracking it?
auto it = m_documentInfoMap.find(assetIdLower);
if (it != m_documentInfoMap.end())
{
// tell the view that it needs to focus that document!
mostRecentlyOpenedDocumentView = assetIdLower;
if (m_queuedOpenRecent)
{
return;
}
EBUS_QUEUE_FUNCTION(AZ::SystemTickBus, &Context::OpenMostRecentDocumentView, this);
return;
}
if (!m_fileIO->Exists(assetId.c_str()))
{
if (errorOnNotFound)
{
AZ_Warning(LUAEditorInfoName, false, AZStd::string::format("<span severity=\"err\">Could not open the file, file not found: '%s'</span>", assetId.c_str()).c_str());
QMessageBox msgBox(m_pLUAEditorMainWindow);
msgBox.setModal(true);
msgBox.setText("File not found");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Critical);
msgBox.exec();
}
CleanUpBreakpoints();
return;
}
// Register the script into the asset catalog
AZ::Data::AssetType assetType = AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid();
AZ::Data::AssetId catalogAssetId;
EBUS_EVENT_RESULT(catalogAssetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, assetIdLower.c_str(), assetType, true);
uint64_t modTime = m_fileIO->ModificationTime(assetId.c_str());
DocumentInfo info;
info.m_assetName = assetIdLower;
AZ::StringFunc::Path::GetFullFileName(assetId.c_str(), info.m_displayName);
info.m_assetId = assetIdLower;
info.m_bSourceControl_BusyGettingStats = true;
info.m_bSourceControl_BusyGettingStats = false;
info.m_bSourceControl_CanWrite = true;
info.m_lastKnownModTime.dwHighDateTime = static_cast<DWORD>(modTime >> 32);
info.m_lastKnownModTime.dwLowDateTime = static_cast<DWORD>(modTime);
info.m_bIsModified = false;
// load the script source
auto infoIter = m_documentInfoMap.insert(AZStd::make_pair(info.m_assetId, info)).first;
// now open a view that will end up with its info (the view will have a progress bar on it as it loads)
m_pLUAEditorMainWindow->OnOpenLUAView(info);
{
// while we're reading it, fetch the perforce information for it:
//AZ_TracePrintf("Debug"," ++m_numOutstandingOperations %d\n",__LINE__);
++m_numOutstandingOperations;
using SCCommandBus = AzToolsFramework::SourceControlCommandBus;
SCCommandBus::Broadcast(&SCCommandBus::Events::GetFileInfo,
assetId.c_str(),
AZStd::bind(&Context::PerforceStatResponseCallback,
this,
AZStd::placeholders::_1,
AZStd::placeholders::_2,
assetId)
);
}
//AZ_TracePrintf("Debug"," ++m_numOutstandingOperations %d\n",__LINE__);
++m_numOutstandingOperations;
//////////////////////////////////////////////////////////////////////////
// Load in places
bool isLoaded = false;
AZ::IO::SystemFile luaFile;
if (luaFile.Open(assetId.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY))
{
infoIter->second.m_scriptAsset.resize(luaFile.Length());
luaFile.Read(infoIter->second.m_scriptAsset.size(), infoIter->second.m_scriptAsset.data());
isLoaded = true;
luaFile.Close();
}
DataLoadDoneCallback(isLoaded, assetIdLower);
//////////////////////////////////////////////////////////////////////////
if (m_queuedOpenRecent)
{
return;
}
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->IgnoreFocusEvents(false);
}
mostRecentlyOpenedDocumentView = assetIdLower;
EBUS_QUEUE_FUNCTION(AZ::SystemTickBus, &Context::OpenMostRecentDocumentView, this);
}
void Context::RunAsAnotherInstance()
{
const AZStd::string k_luaScriptFileString = "files";
const AzFramework::CommandLine* commandLine = nullptr;
AZ::ComponentApplicationBus::Broadcast([&commandLine](AZ::ComponentApplicationRequests* requests)
{
commandLine = requests->GetAzCommandLine();
});
AZStd::string parameters = "";
size_t numSwitchValues = commandLine->GetNumSwitchValues(k_luaScriptFileString);
if (numSwitchValues >= 1)
{
for (size_t i = 0; i < numSwitchValues; ++i)
{
AZStd::string inputValue = commandLine->GetSwitchValue(k_luaScriptFileString, i);
AZStd::to_lower(const_cast<AZStd::string&>(inputValue).begin(), const_cast<AZStd::string&>(inputValue).end());
// Cache the files we want to open, we will open them when we activate the main window.
parameters.append(inputValue);
parameters.append(";");
}
}
// Send the list of files to open to the running instance.
EBUS_EVENT(LegacyFramework::IPCCommandBus, SendIPCCommand, "open_files", parameters.c_str());
}
void Context::ShowLUAEditorView()
{
ProvisionalShowAndFocus(true);
}
//////////////////////////////////////////////////////////////////////////
// Context_DebuggerManagement Messages
//////////////////////////////////////////////////////////////////////////
// ExecuteScriptBlob - execute a script blob.
void Context::ExecuteScriptBlob(const AZStd::string& fromAssetId, bool executeLocally)
{
DocumentInfoMap::iterator docInfoIter = m_documentInfoMap.find(fromAssetId);
AZ_Assert(docInfoIter != m_documentInfoMap.end(), "Could not find data");
if (docInfoIter->second.m_scriptAsset.empty())
{
AZ_Warning(LUAEditorDebugName, false, "Could not execute empty script document.");
return;
}
const char* scriptData = docInfoIter->second.m_scriptAsset.c_str();
size_t scriptDataSize = docInfoIter->second.m_scriptAsset.size();
EBUS_EVENT(LUAEditor::LUAStackTrackerMessages::Bus, StackClear);
DocumentInfoMap::iterator actualDocument = m_documentInfoMap.find(fromAssetId);
AZ_Assert(actualDocument != m_documentInfoMap.end(), "Invalid document lookup.");
SynchronizeBreakpoints();
// the debug name is simply the name of the document.
// if its unnamed, it's synthesized
AZStd::string debugName = actualDocument->second.m_assetName;
// if we're executing it locally, we'll just execute it locally - do not involve the debugger.
if (executeLocally)
{
AZ::ScriptContext* sc = nullptr;
EBUS_EVENT_RESULT(sc, AZ::ScriptSystemRequestBus, GetContext, AZ::ScriptContextIds::DefaultScriptContextId);
if (sc)
{
// we might want to bracket this with some sort of error or warning protection, to collect
// the error / warning results in a neat way to show user.
sc->Execute(scriptData, debugName.c_str());
}
return;
}
// otherwise we've been told to execute it on the debugger remotely:
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, ExecuteScript, debugName, scriptData, scriptDataSize);
}
void Context::SynchronizeBreakpoints()
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::SynchronizeBreakpoints()\n");
for (BreakpointMap::iterator it = m_pBreakpointSavedState->m_Breakpoints.begin(); it != m_pBreakpointSavedState->m_Breakpoints.end(); ++it)
{
Breakpoint& bp = it->second;
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, CreateBreakpoint, bp.m_assetName, bp.m_documentLine);
}
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointsUpdate, m_pBreakpointSavedState->m_Breakpoints);
}
void Context::CreateBreakpoint(const AZStd::string& fromAssetId, int lineNumber)
{
DocumentInfoMap::iterator actualDocument = m_documentInfoMap.find(fromAssetId);
AZ_Assert(actualDocument != m_documentInfoMap.end(), "Invalid document lookup.");
DocumentInfo& doc = actualDocument->second;
AZ::Uuid breakpointUID = AZ::Uuid::CreateRandom();
// when a breakpoint is created, we need to find out what the most recent blob is that was applied
// to patch over that line number in that document, and apply it to that blob.
// first, let's find if we've patched or run any blobs. By default, the doc name will be the asset name.
AZStd::string debugName = doc.m_assetName;
AZ_TracePrintf(LUAEditorDebugName, "Context::CreateBreakpoint( %s )\n", debugName.c_str());
BreakpointMap::pair_iter_bool newInsertion = m_pBreakpointSavedState->m_Breakpoints.insert(breakpointUID);
AZ_Assert(newInsertion.second, "Breakpoint already exists!");
Breakpoint& newBreakpoint = newInsertion.first->second;
newBreakpoint.m_assetName = debugName;
newBreakpoint.m_breakpointId = breakpointUID;
newBreakpoint.m_assetId = fromAssetId;
newBreakpoint.m_documentLine = lineNumber;
// we now know the 'debug name' (a string) that was submitted to the piece of code that this breakpoint is for, and we know the line number
// inside that blob that the breakpoint wants to be set on.
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, CreateBreakpoint, debugName, lineNumber);
//AZ_TracePrintf(LUAEditorDebugName, "Setting breakpoint in '%s' on line %i (In document %s line %i)\n", debugName.c_str(), lineNumber, doc.m_displayName.c_str(), lineNumber);
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointsUpdate, m_pBreakpointSavedState->m_Breakpoints);
}
void Context::DeleteBreakpoint(const AZ::Uuid& breakpointUID)
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::DeleteBreakpoint()\n");
BreakpointMap::iterator finder = m_pBreakpointSavedState->m_Breakpoints.find(breakpointUID);
//AZ_Assert(finder != m_breakpoints.end(), "Breakpoint referred to, but not found!");
if (finder != m_pBreakpointSavedState->m_Breakpoints.end())
{
Breakpoint& bp = finder->second;
AZ_TracePrintf(LUAEditorDebugName, " - Removed breakpoint in '%s' on line '%i'\n", bp.m_assetName.c_str(), bp.m_documentLine);
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, RemoveBreakpoint, bp.m_assetName, bp.m_documentLine);
m_pBreakpointSavedState->m_Breakpoints.erase(finder);
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointsUpdate, m_pBreakpointSavedState->m_Breakpoints);
}
}
// Find any breakpoints that no longer have any attachment and remove them
void Context::CleanUpBreakpoints()
{
// Build a list of orphaned breakpoints
std::vector<AZ::Uuid> invalidBreakpoints;
for (auto const &breakpoint : m_pBreakpointSavedState->m_Breakpoints)
{
if (!m_fileIO->Exists(breakpoint.second.m_assetName.c_str()))
{
invalidBreakpoints.emplace_back(breakpoint.second.m_breakpointId);
}
}
for (auto const &id : invalidBreakpoints)
{
DeleteBreakpoint(id);
}
// submit the updated list
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointsUpdate, m_pBreakpointSavedState->m_Breakpoints);
}
//TODO: add a valid-invalid state to local breakpoints
// default new breaks to INVALID
// use the response here to set matching breaks to VALID
void Context::OnBreakpointAdded(const AZStd::string& /*assetIdString*/, int /*lineNumber*/)
{
//AZ_TracePrintf(LUAEditorDebugName, "Debug agent added breakpoint at %s, line %d.\n", assetIdString.c_str(), lineNumber + 1);
}
void Context::OnBreakpointRemoved(const AZStd::string& /*assetIdString*/, int /*lineNumber*/)
{
//AZ_TracePrintf(LUAEditorDebugName, "Debug agent removed breakpoint at %s, line %d.\n", assetIdString.c_str(), lineNumber + 1);
}
void Context::MoveBreakpoint(const AZ::Uuid& breakpointUID, int lineNumber)
{
// moving a breakpoint will cause it to update where it is in the document in question
// however, we don't actually re-transmit the breakpoint to the gridmate core, because we haven't re-run the script
// this is just housekeeping so that when gridmate says a certain breakpoint came in at a certain place,
// we know what they're talking about.
if (lineNumber >= 0)
{
BreakpointMap::iterator finder = m_pBreakpointSavedState->m_Breakpoints.find(breakpointUID);
if (finder != m_pBreakpointSavedState->m_Breakpoints.end())
{
Breakpoint& bp = finder->second;
bp.m_documentLine = lineNumber;
AZ_TracePrintf(LUAEditorDebugName, "Breakpoint '%s' updated to point at document line '%i'\n", bp.m_assetName.c_str(), bp.m_documentLine);
}
// send out the update
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointsUpdate, m_pBreakpointSavedState->m_Breakpoints);
}
}
void Context::OnDebuggerAttached()
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::OnDebuggerAttached()\n");
EBUS_EVENT(Context_ControlManagement::Bus, OnDebuggerAttached);
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, EnumRegisteredClasses, m_currentTargetContext.c_str());
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, EnumRegisteredEBuses, m_currentTargetContext.c_str());
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, EnumRegisteredGlobals, m_currentTargetContext.c_str());
EBUS_EVENT(LUAEditorMainWindowMessages::Bus, OnConnectedToDebugger);
EBUS_EVENT(LUAWatchesDebuggerMessages::Bus, OnDebuggerAttached);
SynchronizeBreakpoints();
}
void Context::OnDebuggerRefused()
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::OnDebuggerRefused()\n");
EBUS_EVENT(LUAEditorMainWindowMessages::Bus, OnDisconnectedFromDebugger);
EBUS_EVENT(Context_ControlManagement::Bus, OnDebuggerDetached);
EBUS_EVENT(LUAWatchesDebuggerMessages::Bus, OnDebuggerDetached);
}
void Context::OnDebuggerDetached()
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::OnDebuggerDetached()\n");
EBUS_EVENT(LUAEditorMainWindowMessages::Bus, OnDisconnectedFromDebugger);
EBUS_EVENT(Context_ControlManagement::Bus, OnDebuggerDetached);
EBUS_EVENT(LUAWatchesDebuggerMessages::Bus, OnDebuggerDetached);
}
// this happens when a breakpoint is hit:
void Context::OnBreakpointHit(const AZStd::string& relativePath, int lineNumber)
{
// Convert from debug path (relative) to absolute path (how the Lua IDE stores files)
AZStd::string absolutePath = relativePath.substr(1);
EBUS_EVENT(AzToolsFramework::AssetSystemRequestBus, GetFullSourcePathFromRelativeProductPath, absolutePath, absolutePath);
//AZ_TracePrintf(LUAEditorDebugName, "Breakpoint '%s' was hit on line %i\n", assetIdString.c_str(), lineNumber);
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, GetCallstack);
EBUS_EVENT(LUAEditor::LUABreakpointRequestMessages::Bus, RequestEditorFocus, absolutePath, lineNumber);
AZStd::string assetId = absolutePath;
// let's see if we can find an open document
DocumentInfoMap::iterator actualDocument = m_documentInfoMap.find(assetId.c_str());
if (actualDocument == m_documentInfoMap.end())
{
// the document might have been closed
AssetOpenRequested(assetId, true);
// let's see if we can find an open document
DocumentInfoMap::iterator actualDocumentIterator = m_documentInfoMap.find(assetId.c_str());
if (actualDocumentIterator != m_documentInfoMap.end())
{
actualDocumentIterator->second.m_PresetLineAtOpen = lineNumber;
}
// early out after requesting a background data load
return;
}
int actualDocumentLineNumber = lineNumber;
// we now know what document that the breakpoint is talking about.
// we could just assume that the document has not changed since we saw the breakpoint applied, but its possible that is has in fact shifted.
// do we have any breakpoints established for that particular blob?
{
BreakpointMap::iterator it = m_pBreakpointSavedState->m_Breakpoints.begin();
for (; it != m_pBreakpointSavedState->m_Breakpoints.end(); ++it)
{
const Breakpoint& bp = it->second;
if (bp.m_assetId == assetId)
{
if (bp.m_documentLine == lineNumber)
{
// this is that breakpoint!
actualDocumentLineNumber = bp.m_documentLine; // bump it if its shifted:
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointHit, bp);
break;
}
}
}
if (it == m_pBreakpointSavedState->m_Breakpoints.end())
{
// it's an imaginary break resulting from one of the STEP calls
// dummy up a bp and send that as the message
Breakpoint dbp;
dbp.m_breakpointId = AZ::Uuid::CreateNull();
dbp.m_assetId = "";
dbp.m_documentLine = lineNumber;
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointHit, dbp);
}
}
//AZ_TracePrintf(LUAEditorDebugName, "That translates to line number %i in document '%s'\n", actualDocumentLineNumber, doc.m_displayName.c_str());
// what do we actually do?
// we need to
// 1. FOCUS that window
// 2. INDICATE that we are 'stopped'
// 3. Update any watched variables
// 4. Show a program cursor on that line!
// 5. Enable the step over and so on, the debugging controls basically.
// focus the window:
ProvisionalShowAndFocus(true);
// are we already tracking it?
// tell the view that it needs to focus that document!
m_pLUAEditorMainWindow->OnRequestFocusView(assetId);
m_pLUAEditorMainWindow->MoveProgramCursor(assetId, actualDocumentLineNumber);
}
void Context::OnReceivedAvailableContexts(const AZStd::vector<AZStd::string>& contexts)
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::OnReceivedAvailableContexts()\n");
m_targetContexts = contexts;
size_t i;
for (i = 0; i < m_targetContexts.size(); ++i)
{
if (m_currentTargetContext == m_targetContexts[i])
{
break;
}
}
if (i >= m_targetContexts.size())
{
m_targetContexts.clear();
m_currentTargetContext = "Default";
}
EBUS_EVENT(LUAEditor::Context_ControlManagement::Bus, OnTargetContextPrepared, m_currentTargetContext);
RequestAttachDebugger();
}
void Context::OnReceivedRegisteredClasses(const AzFramework::ScriptUserClassList& classes)
{
AZ_TracePrintf(LUAEditorDebugName, "Context::OnReceivedRegisteredClasses()\n");
// Reset the class reference for the current target
ContextReference& reference = m_reference[m_currentTargetContext];
reference.m_classes = classes;
UpdateReferenceWindow();
}
void Context::OnReceivedRegisteredEBuses(const AzFramework::ScriptUserEBusList& ebuses)
{
AZ_TracePrintf(LUAEditorDebugName, "Context::OnReceivedRegisteredEBuses()\n");
ContextReference& reference = m_reference[m_currentTargetContext];
reference.m_buses = ebuses;
UpdateReferenceWindow();
}
void Context::OnReceivedRegisteredGlobals(const AzFramework::ScriptUserMethodList& methods, const AzFramework::ScriptUserPropertyList& properties)
{
AZ_TracePrintf(LUAEditorDebugName, "Context::OnReceivedRegisteredGlobals()\n");
ContextReference& reference = m_reference[m_currentTargetContext];
reference.m_globals.m_methods = methods;
reference.m_globals.m_properties = properties;
UpdateReferenceWindow();
}
AZStd::string GetTooltip(const AzFramework::ScriptUserPropertyInfo& propInfo)
{
static const char* lut[2][2] =
{
{ "Locked", "WO" },
{ "RO", "R/W" }
};
AZStd::string rw = lut[propInfo.m_isRead][propInfo.m_isWrite];
return propInfo.m_name + "[" + rw + "]";
}
AZStd::string GetTooltip(const AzFramework::ScriptUserMethodInfo& methodInfo)
{
return methodInfo.m_name + "(" + methodInfo.m_dbgParamInfo + ")";
}
AZStd::string GetTooltip(const AzFramework::ScriptUserClassInfo& classInfo)
{
return classInfo.m_name + "()";
}
AZStd::string GetTooltip(const AzFramework::ScriptUserEBusInfo& ebusInfo)
{
return ebusInfo.m_name;
}
void Context::UpdateReferenceWindow()
{
const ContextReference& reference = m_reference[m_currentTargetContext];
m_LUAKeywords.clear();
AddDefaultLUAKeywords();
m_LUALibraryFunctions.clear();
AddDefaultLUALibraryFunctions();
m_referenceModel->clear();
// Globals
ReferenceItem* global = aznew ReferenceItem("Globals", AZ::u64(0));
global->setToolTip("Globals");
global->setWhatsThis("Global Methods and Variables");
m_referenceModel->appendRow(global);
for (const auto& methodInfo : reference.m_globals.m_methods)
{
ReferenceItem* method = aznew ReferenceItem(methodInfo.m_name.c_str(), AZ::u64(0));
method->setToolTip(QString::fromUtf8(GetTooltip(methodInfo).c_str()));
method->setWhatsThis(QString::fromUtf8(methodInfo.m_name.c_str()));
global->appendRow(method);
m_LUALibraryFunctions.insert(methodInfo.m_name);
}
for (const auto& propInfo : reference.m_globals.m_properties)
{
ReferenceItem* variable = aznew ReferenceItem(propInfo.m_name.c_str(), AZ::u64(0));
variable->setToolTip(QString::fromUtf8(GetTooltip(propInfo).c_str()));
variable->setWhatsThis(QString::fromUtf8(propInfo.m_name.c_str()));
global->appendRow(variable);
m_LUAKeywords.insert(propInfo.m_name);
}
// Classes
ReferenceItem* classes = aznew ReferenceItem("Classes", AZ::u64(0));
classes->setToolTip("Classes");
classes->setWhatsThis("Classes");
m_referenceModel->appendRow(classes);
for (const auto& classInfo : reference.m_classes)
{
ReferenceItem* classItem = aznew ReferenceItem(QString::fromUtf8(classInfo.m_name.c_str()), 0);
classItem->setToolTip(QString::fromUtf8(classInfo.m_name.c_str()));
classItem->setWhatsThis(QString::fromUtf8(classInfo.m_name.c_str()));
classes->appendRow(classItem);
for (const auto& methodInfo : classInfo.m_methods)
{
ReferenceItem* methodItem = aznew ReferenceItem(QString::fromUtf8((methodInfo.m_name + "( " + methodInfo.m_dbgParamInfo + " )").c_str()), 0);
methodItem->setToolTip(QString::fromUtf8(GetTooltip(methodInfo).c_str()));
methodItem->setWhatsThis(QString::fromUtf8(methodInfo.m_name.c_str()));
classItem->appendRow(methodItem);
m_LUALibraryFunctions.insert(classInfo.m_name + "." + methodInfo.m_name);
}
for (const auto& propInfo : classInfo.m_properties)
{
ReferenceItem* propItem = aznew ReferenceItem(propInfo.m_name.c_str(), 0);
propItem->setToolTip(QString::fromUtf8(GetTooltip(propInfo).c_str()));
propItem->setWhatsThis(QString::fromUtf8(propInfo.m_name.c_str()));
classItem->appendRow(propItem);
m_LUALibraryFunctions.insert(classInfo.m_name + "." + propInfo.m_name);
}
}
// Buses
ReferenceItem* buses = aznew ReferenceItem("EBuses", AZ::u64(0));
buses->setToolTip("EBuses");
buses->setWhatsThis("EBuses");
m_referenceModel->appendRow(buses);
for (auto const &ebusInfo : reference.m_buses)
{
// Make a reference item from the info-block for displaying in the class hierarchy and add it to the reference table.
ReferenceItem* ebus = aznew ReferenceItem(QString::fromUtf8(ebusInfo.m_name.c_str()), 0);
ebus->setToolTip(QString::fromUtf8(GetTooltip(ebusInfo).c_str()));
ebus->setWhatsThis(QString::fromUtf8(ebusInfo.m_name.c_str()));
buses->appendRow(ebus);
if (!ebusInfo.m_events.empty())
{
ReferenceItem* eventRoot = aznew ReferenceItem("Event", 0);
ReferenceItem* broadcastRoot = (ebusInfo.m_canBroadcast) ? aznew ReferenceItem("Broadcast", 0) : nullptr;
ReferenceItem* notificationsRoot = (ebusInfo.m_hasHandler) ? aznew ReferenceItem("Notifications", 0) : nullptr;
for (auto const &eventInfo : ebusInfo.m_events)
{
// Construct the visual element for displaying in the reference pane.
ReferenceItem* eventItem = aznew ReferenceItem(QString::fromUtf8(GetTooltip(eventInfo).c_str()), 0);
eventItem->setToolTip(QString::fromUtf8(GetTooltip(eventInfo).c_str()));
eventItem->setWhatsThis(QString::fromUtf8(eventInfo.m_name.c_str()));
if (eventInfo.m_category == "Event")
{
eventRoot->appendRow(eventItem);
m_LUALibraryFunctions.insert(ebusInfo.m_name + ".Event." + eventInfo.m_name);
}
else if (eventInfo.m_category == "Broadcast" && broadcastRoot)
{
broadcastRoot->appendRow(eventItem);
m_LUALibraryFunctions.insert(ebusInfo.m_name + ".Broadcast." + eventInfo.m_name);
}
else if (eventInfo.m_category == "Notification" && notificationsRoot)
{
notificationsRoot->appendRow(eventItem);
}
else
{
// This should not happen, but in the case that we somehow have a handler or broadcast
// and nowhere to attach it, let's at least not leak it
delete eventItem;
}
}
// Add the root nodes that have children to the bus tree, delete empty roots
for (ReferenceItem* rootNode : { eventRoot, broadcastRoot, notificationsRoot })
{
if (rootNode && rootNode->rowCount() > 0)
{
ebus->appendRow(rootNode);
}
else if (rootNode)
{
delete rootNode;
}
}
}
}
if (m_pLUAEditorMainWindow)
{
Q_EMIT m_pLUAEditorMainWindow->OnReferenceDataChanged();
}
EBUS_EVENT(HighlightedWordNotifications::Bus, LUALibraryFunctionsUpdated);
}
void Context::OnReceivedLocalVariables(const AZStd::vector<AZStd::string>& vars)
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::OnReceivedLocalVariables()\n");
EBUS_EVENT(LUALocalsTrackerMessages::Bus, LocalsUpdate, vars);
for (size_t i = 0; i < vars.size(); ++i)
{
//AZ_TracePrintf(LUAEditorDebugName, "\t%s\n", vars[i].c_str());
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, GetValue, vars[i]);
}
}
void Context::OnReceivedCallstack(const AZStd::vector<AZStd::string>& callstack)
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::OnReceivedCallstack()\n");
LUAEditor::StackList sl;
for (size_t i = 0; i < callstack.size(); ++i)
{
//AZ_TracePrintf(LUAEditorDebugName, "stack[%d] %s\n", i, callstack[i].c_str());
const char* stackLine = callstack[i].c_str();
// strings starting with a pointer address aren't useful and break the format
if (stackLine)
{
const size_t tempSize = 4096;
if (!isdigit(stackLine[0]))
{
if (stackLine[0] == '[')
{
// LUA format
LUAEditor::StackEntry s;
const char* fb = strchr(stackLine, '@');
if (fb)
{
++fb;
const char* fe = strchr(fb, '(');
if (fe)
{
char temp[tempSize];
--fe;
memcpy(temp, fb, fe - fb);
temp[ fe - fb ] = 0;
s.m_blob = temp;
int line = 0;
const char* ns = strchr(stackLine, '(');
if (ns)
{
++ns;
line = atoi(ns) - 1; // -1 offset to bridge editor vs display
}
s.m_blobLine = line;
sl.push_back(s);
}
}
}
else
{
// standard VS format
LUAEditor::StackEntry s;
const char* fb = stackLine;
const char* fe = strchr(fb, '(');
if (fe)
{
char temp[tempSize];
--fe;
ptrdiff_t pdt = fe - fb;
if (pdt < (tempSize - 1))
{
memcpy(temp, fb, pdt);
temp[ pdt ] = 0;
s.m_blob = stackLine;
}
else
{
pdt = tempSize - 5;
memcpy(temp, fb, pdt);
temp[ pdt++ ] = '.';
temp[ pdt++ ] = '.';
temp[ pdt++ ] = '.';
temp[ pdt ] = 0;
s.m_blob = stackLine;
}
int line = 0;
const char* ns = strchr(stackLine, '(');
if (ns)
{
++ns;
line = atoi(ns) - 1; // -1 offset to bridge editor vs display
}
s.m_blobLine = line;
sl.push_back(s);
}
}
}
else
{
// function pointers
LUAEditor::StackEntry s;
s.m_blobLine = 0;
s.m_blob = stackLine;
sl.push_back(s);
}
}
}
EBUS_EVENT(LUAEditor::LUAStackTrackerMessages::Bus, StackUpdate, sl);
}
void Context::RequestWatchedVariable(const AZStd::string& varName)
{
//AZ_TracePrintf(LUAEditorDebugName, "RequestWatchedVariable: name=%s\n", varName.c_str());
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, GetValue, varName);
}
void Context::OnReceivedValueState(const AZ::ScriptContextDebug::DebugValue& value)
{
//AZ_TracePrintf(LUAEditorDebugName, "Received LUA var: name=%s, val=%s, type=0x%x, flags=0x%x\n", value.m_name.c_str(), value.m_value.c_str(), (int)value.m_assetType, (int)value.m_flags);
EBUS_EVENT(LUAEditor::LUAWatchesDebuggerMessages::Bus, WatchesUpdate, value);
}
void Context::OnSetValueResult(const AZStd::string& /*name*/, bool /*success*/)
{
//AZ_TracePrintf(LUAEditorDebugName, "SetValue(%s) %s.\n", name.c_str(), success ? "succeeded" : "failed");
}
void Context::OnExecutionResumed()
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::OnExecutionResumed()\n");
if (m_pLUAEditorMainWindow)
{
m_pLUAEditorMainWindow->MoveProgramCursor("", -1);
}
EBUS_EVENT(LUAEditor::LUAStackTrackerMessages::Bus, StackClear);
EBUS_EVENT(LUABreakpointTrackerMessages::Bus, BreakpointResume);
}
void Context::OnExecuteScriptResult(bool success)
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::OnExecutionScriptResult( %d )\n", success);
EBUS_EVENT(LUAEditorMainWindowMessages::Bus, OnExecuteScriptResult, success);
}
void Context::ResetTargetContexts()
{
m_targetContexts.clear();
m_currentTargetContext = "Default";
EBUS_EVENT(LUAEditor::Context_ControlManagement::Bus, OnTargetContextPrepared, m_currentTargetContext);
}
void Context::SetCurrentTargetContext(AZStd::string& contextName)
{
//AZ_TracePrintf(LUAEditorDebugName, "Context::SetCurrentTargetContext()\n");
RequestDetachDebugger();
// is this a valid context, in our existing list from the target?
size_t i;
for (i = 0; i < m_targetContexts.size(); ++i)
{
if (contextName == m_targetContexts[i])
{
m_currentTargetContext = contextName;
break;
}
}
if (i >= m_targetContexts.size())
{
ResetTargetContexts();
}
EBUS_EVENT(LUAEditor::Context_ControlManagement::Bus, OnTargetContextPrepared, m_currentTargetContext);
UpdateReferenceWindow();
RequestAttachDebugger();
}
//////////////////////////////////////////////////////////////////////////
//Debug Request messages
//////////////////////////////////////////////////////////////////////////
void Context::RequestDetachDebugger()
{
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, DetachDebugger);
}
void Context::RequestAttachDebugger()
{
EBUS_EVENT(LUAEditorDebuggerMessages::Bus, AttachDebugger, m_currentTargetContext.c_str());
}
ReferenceItem::ReferenceItem(const QIcon& icon, const QString& text, size_t id)
: QStandardItem(icon, text)
, m_Id(id)
{
}
ReferenceItem::ReferenceItem(const QString& text, size_t id)
: QStandardItem(text)
, m_Id(id)
{
}
void LUAEditorContextSavedState::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<LUAEditorContextSavedState, AZ::UserSettings >()
->Version(1)
->Field("m_MainEditorWindowIsVisible", &LUAEditorContextSavedState::m_MainEditorWindowIsVisible)
->Field("m_MainEditorWindowIsOpen", &LUAEditorContextSavedState::m_MainEditorWindowIsOpen);
}
}
//////////////////////////////////////////////////////////////////////////
// ContextFactory
//////////////////////////////////////////////////////////////////////////
void Context::Reflect(AZ::ReflectContext* reflection)
{
AzToolsFramework::LogPanel::BaseLogPanel::Reflect(reflection);
AzToolsFramework::QTreeViewWithStateSaving::Reflect(reflection);
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
Breakpoint::Reflect(reflection);
BreakpointSavedState::Reflect(reflection);
LUAEditorMainWindowSavedState::Reflect(reflection);
LUAEditorContextSavedState::Reflect(reflection);
SyntaxStyleSettings::Reflect(reflection);
serializeContext->Class<Context>()
->Version(10)
;
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection);
if (behaviorContext)
{
behaviorContext->Class<Context>("LUAEditor")->
Method("SaveLayout", &Context::SaveLayout)->
Method("LoadLayout", &Context::LoadLayout);
behaviorContext->Property("luaEditor", BehaviorValueGetter(&s_pLUAEditorScriptPtr), nullptr);
}
}
void Context::AssetCompilationSuccess(const AZStd::string& assetPath)
{
if (IsLuaAsset(assetPath))
{
AZ_TracePrintf(LUAEditorInfoName, "Compilation Successful - %s\n", assetPath.c_str());
}
}
void Context::AssetCompilationFailed(const AZStd::string& assetPath)
{
if (IsLuaAsset(assetPath))
{
AZStd::lock_guard<AZStd::mutex> lock(m_failedAssetMessagesMutex);
m_failedAssets.push(assetPath);
EBUS_QUEUE_FUNCTION(AZ::SystemTickBus, &Context::ProcessFailedAssetMessages, this);
}
}
void Context::ProcessFailedAssetMessages()
{
AZStd::string currentAsset;
bool foundAsset = false;
do
{
{
AZStd::lock_guard<AZStd::mutex> lock(m_failedAssetMessagesMutex);
if (m_failedAssets.empty())
{
foundAsset = false;
}
else
{
foundAsset = true;
currentAsset = AZStd::move(m_failedAssets.front());
m_failedAssets.pop();
}
}
if (foundAsset)
{
AZStd::string msg = AZStd::string::format("Compilation Failed! (%s)\n", currentAsset.c_str());
AZ_Warning(LUAEditorInfoName, false, msg.c_str());
AZ::Outcome<AzToolsFramework::AssetSystem::JobInfoContainer> jobInfoResult = AZ::Failure();
AzToolsFramework::AssetSystemJobRequestBus::BroadcastResult(jobInfoResult, &AzToolsFramework::AssetSystemJobRequestBus::Events::GetAssetJobsInfo, currentAsset, false);
if (jobInfoResult.IsSuccess())
{
const AzToolsFramework::AssetSystem::JobInfo &jobInfo = jobInfoResult.GetValue()[0];
AZ::Outcome<AZStd::string> logResult = AZ::Failure();
AzToolsFramework::AssetSystemJobRequestBus::BroadcastResult(logResult, &AzToolsFramework::AssetSystemJobRequestBus::Events::GetJobLog, jobInfo.m_jobRunKey);
if (logResult.IsSuccess())
{
// Errors should come in the form of <timestamp> filename.lua:####: errormsg
std::regex errorRegex(".+\\.lua:(\\d+):(.*)");
AzToolsFramework::Logging::LogLine::ParseLog(logResult.GetValue().c_str(), logResult.GetValue().size(),
[this, &currentAsset, &errorRegex](AzToolsFramework::Logging::LogLine& logLine)
{
if ((logLine.GetLogType() == AzToolsFramework::Logging::LogLine::TYPE_WARNING) || (logLine.GetLogType() == AzToolsFramework::Logging::LogLine::TYPE_ERROR))
{
if (m_pLUAEditorMainWindow)
{
m_errorData.push_back(new CompilationErrorData());
auto errorData = m_errorData.back();
// Get the full path from the currentAsset
bool pathFound = false;
AZStd::string fullPath;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(pathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, currentAsset, errorData->m_filename);
// Lower this so that it matches the asset_id used by the rest of the lua id when referring to open files
AZStd::to_lower(errorData->m_filename.begin(), errorData->m_filename.end());
// Errors should come in the form of <timestamp> filename.lua:####: errormsg
AZStd::string logString = logLine.ToString();
// Default the final message to the entire line in case it can't be parsed for line number and actual error
AZStd::string finalMessage = logString;
// Try to extract the line number here
std::smatch match;
std::string stdLogString = logString.c_str();
bool matchFound = std::regex_search(stdLogString, match, errorRegex);
if (matchFound)
{
int lineNumber = 0;
if (AZ::StringFunc::LooksLikeInt(match[1].str().c_str(), &lineNumber))
{
errorData->m_lineNumber = lineNumber;
finalMessage = match[2].str().c_str();
}
}
m_pLUAEditorMainWindow->AddMessageToLog(logLine.GetLogType(), LUAEditorInfoName, finalMessage.c_str(), errorData);
}
}
});
}
}
}
} while (foundAsset); // keep doing this as long as there's failed assets in the queue
}
bool Context::IsLuaAsset(const AZStd::string& assetPath)
{
return AZ::StringFunc::Path::IsExtension(assetPath.c_str(), ".lua");
}
}