diff --git a/Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.cpp b/Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.cpp similarity index 82% rename from Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.cpp rename to Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.cpp index c6008deb34..51674bb32c 100644 --- a/Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.cpp +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.cpp @@ -6,16 +6,17 @@ * */ -#include "CommunicatorTracePrinter.h" +#include "ProcessCommunicatorTracePrinter.h" -CommunicatorTracePrinter::CommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window) : + +ProcessCommunicatorTracePrinter::ProcessCommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window) : m_communicator(communicator), m_window(window) { m_stringBeingConcatenated.reserve(1024); } -CommunicatorTracePrinter::~CommunicatorTracePrinter() +ProcessCommunicatorTracePrinter::~ProcessCommunicatorTracePrinter() { // flush stdout WriteCurrentString(false); @@ -24,7 +25,7 @@ CommunicatorTracePrinter::~CommunicatorTracePrinter() WriteCurrentString(true); } -void CommunicatorTracePrinter::Pump() +void ProcessCommunicatorTracePrinter::Pump() { if (m_communicator->IsValid()) { @@ -42,7 +43,7 @@ void CommunicatorTracePrinter::Pump() } } -void CommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr) +void ProcessCommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr) { if (readSize > AZ_ARRAY_SIZE(m_streamBuffer)) { @@ -67,7 +68,7 @@ void CommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdE } } -void CommunicatorTracePrinter::WriteCurrentString(bool isFromStdErr) +void ProcessCommunicatorTracePrinter::WriteCurrentString(bool isFromStdErr) { AZStd::string& bufferToUse = isFromStdErr ? m_errorStringBeingConcatenated : m_stringBeingConcatenated; diff --git a/Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.h b/Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.h similarity index 54% rename from Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.h rename to Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.h index c2e4da32af..5e14fa290d 100644 --- a/Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.h +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.h @@ -10,20 +10,21 @@ #include -//! CommunicatorTracePrinter listens to stderr and stdout of a running process and writes its output to the AZ_Trace system +//! ProcessCommunicatorTracePrinter listens to stderr and stdout of a running process and writes its output to the AZ_Trace system //! Importantly, it does not do any blocking operations. -class CommunicatorTracePrinter +class ProcessCommunicatorTracePrinter { public: - CommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window); - ~CommunicatorTracePrinter(); + ProcessCommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window); + ~ProcessCommunicatorTracePrinter(); - // call this periodically to drain the buffers and write them. + //! Call this periodically to drain the buffers and write them. void Pump(); - // drains the buffer into the string thats being built, then traces the string when it hits a newline. + //! Drains the buffer into the string that's being built, then traces the string when it hits a newline. void ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr); + //! Prints the current buffer to AZ_Error or AZ_TracePrintf so that it can be picked up by AZ::Debug::Trace void WriteCurrentString(bool isFromStdError); private: diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index b8d428ab82..aac7bd8f14 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -277,6 +277,8 @@ set(FILES Process/ProcessWatcher.cpp Process/ProcessWatcher.h Process/ProcessCommon_fwd.h + Process/ProcessCommunicatorTracePrinter.cpp + Process/ProcessCommunicatorTracePrinter.h ProjectManager/ProjectManager.h ProjectManager/ProjectManager.cpp Render/GameIntersectorComponent.h diff --git a/Code/Legacy/CryCommon/ICmdLine.h b/Code/Legacy/CryCommon/ICmdLine.h index 6071671b02..80561fd85c 100644 --- a/Code/Legacy/CryCommon/ICmdLine.h +++ b/Code/Legacy/CryCommon/ICmdLine.h @@ -71,6 +71,14 @@ public: // The value of the argument as integer number. virtual const int GetIValue() const = 0; // + + // Description: + // Retrieve the value of the argument. + // Arguments: + // cmdLineValue. The cmdline value will be filled out if a valid boolean is found. + // Return Value: + // Returns true if the cmdline arg is actually a boolean string matching "true" or "false"; otherwise return false. + virtual const bool GetBoolValue(bool& cmdLineValue) const = 0; }; // Command line interface diff --git a/Code/Legacy/CrySystem/AZCoreLogSink.h b/Code/Legacy/CrySystem/AZCoreLogSink.h index 1b09c3198d..9d732b3dd4 100644 --- a/Code/Legacy/CrySystem/AZCoreLogSink.h +++ b/Code/Legacy/CrySystem/AZCoreLogSink.h @@ -36,9 +36,10 @@ public: Disconnect(); } - inline static void Connect() + inline static void Connect(bool suppressSystemOutput) { GetInstance().m_ignoredAsserts = new IgnoredAssertMap(); + GetInstance().m_suppressSystemOutput = suppressSystemOutput; GetInstance().BusConnect(); } @@ -126,7 +127,7 @@ public: CryLogAlways("%s", message); } - return true; // suppress default AzCore behavior. + return m_suppressSystemOutput; #else AZ_UNUSED(fileName); AZ_UNUSED(line); @@ -146,7 +147,7 @@ public: return false; // allow AZCore to do its default behavior. } gEnv->pLog->LogError("(%s) - %s", window, message); - return true; // suppress default AzCore behavior. + return m_suppressSystemOutput; } bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override @@ -161,7 +162,7 @@ public: } CryWarning(VALIDATOR_MODULE_UNKNOWN, VALIDATOR_WARNING, "(%s) - %s", window, message); - return true; // suppress default AzCore behavior. + return m_suppressSystemOutput; } bool OnOutput(const char* window, const char* message) override @@ -179,12 +180,13 @@ public: { CryLog("(%s) - %s", window, message); } - - return true; // suppress default AzCore behavior. + + return m_suppressSystemOutput; } private: using IgnoredAssertMap = AZStd::unordered_map, AZStd::equal_to, AZ::OSStdAllocator>; IgnoredAssertMap* m_ignoredAsserts; + bool m_suppressSystemOutput = true; }; diff --git a/Code/Legacy/CrySystem/CmdLineArg.cpp b/Code/Legacy/CrySystem/CmdLineArg.cpp index 79d23ddae7..554e734055 100644 --- a/Code/Legacy/CrySystem/CmdLineArg.cpp +++ b/Code/Legacy/CrySystem/CmdLineArg.cpp @@ -42,5 +42,22 @@ const int CCmdLineArg::GetIValue() const { return atoi(m_value.c_str()); } +const bool CCmdLineArg::GetBoolValue(bool& cmdLineValue) const +{ + AZStd::string lowercaseValue(m_value); + AZStd::to_lower(lowercaseValue.begin(), lowercaseValue.end()); + if (lowercaseValue == "true") + { + cmdLineValue = true; + return true; + } + if (lowercaseValue == "false") + { + cmdLineValue = false; + return true; + } + + return false; +} diff --git a/Code/Legacy/CrySystem/CmdLineArg.h b/Code/Legacy/CrySystem/CmdLineArg.h index 5e3a629e7c..66d56be132 100644 --- a/Code/Legacy/CrySystem/CmdLineArg.h +++ b/Code/Legacy/CrySystem/CmdLineArg.h @@ -30,6 +30,7 @@ public: const ECmdLineArgType GetType() const; const float GetFValue() const; const int GetIValue() const; + const bool GetBoolValue(bool& cmdLineValue) const; private: diff --git a/Code/Legacy/CrySystem/SystemInit.cpp b/Code/Legacy/CrySystem/SystemInit.cpp index ad29ea102f..e0986d3ec9 100644 --- a/Code/Legacy/CrySystem/SystemInit.cpp +++ b/Code/Legacy/CrySystem/SystemInit.cpp @@ -737,7 +737,17 @@ bool CSystem::Init(const SSystemInitParams& startupParams) m_pCmdLine = new CCmdLine(startupParams.szSystemCmdLine); - AZCoreLogSink::Connect(); + // Init AZCoreLogSink. Don't suppress system output if we're running as an editor-server + bool suppressSystemOutput = true; + if (const ICmdLineArg* isEditorServerArg = m_pCmdLine->FindArg(eCLAT_Pre, "editorsv_isDedicated")) + { + bool editorsv_isDedicated = false; + if (isEditorServerArg->GetBoolValue(editorsv_isDedicated) && editorsv_isDedicated) + { + suppressSystemOutput = false; + } + } + AZCoreLogSink::Connect(suppressSystemOutput); // Registers all AZ Console Variables functors specified within CrySystem if (auto azConsole = AZ::Interface::Get(); azConsole) diff --git a/Code/Tools/AssetProcessor/assetprocessor_static_files.cmake b/Code/Tools/AssetProcessor/assetprocessor_static_files.cmake index aa1d85d433..400a3f7ba5 100644 --- a/Code/Tools/AssetProcessor/assetprocessor_static_files.cmake +++ b/Code/Tools/AssetProcessor/assetprocessor_static_files.cmake @@ -92,8 +92,6 @@ set(FILES native/utilities/BuilderManager.inl native/utilities/ByteArrayStream.cpp native/utilities/ByteArrayStream.h - native/utilities/CommunicatorTracePrinter.cpp - native/utilities/CommunicatorTracePrinter.h native/utilities/IniConfiguration.cpp native/utilities/IniConfiguration.h native/utilities/JobDiagnosticTracker.cpp diff --git a/Code/Tools/AssetProcessor/native/resourcecompiler/RCBuilder.cpp b/Code/Tools/AssetProcessor/native/resourcecompiler/RCBuilder.cpp index 27210685fc..c463f2320e 100644 --- a/Code/Tools/AssetProcessor/native/resourcecompiler/RCBuilder.cpp +++ b/Code/Tools/AssetProcessor/native/resourcecompiler/RCBuilder.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include @@ -31,7 +30,6 @@ #include "native/utilities/assetUtils.h" #include "native/utilities/AssetBuilderInfo.h" -#include "native/utilities/CommunicatorTracePrinter.h" #include diff --git a/Code/Tools/AssetProcessor/native/utilities/ApplicationManagerBase.cpp b/Code/Tools/AssetProcessor/native/utilities/ApplicationManagerBase.cpp index eb230ce336..499e5fe4f7 100644 --- a/Code/Tools/AssetProcessor/native/utilities/ApplicationManagerBase.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/ApplicationManagerBase.cpp @@ -1482,7 +1482,7 @@ bool ApplicationManagerBase::WaitForBuilderExit(AzFramework::ProcessWatcher* pro AZ::u32 exitCode = 0; bool finishedOK = false; QElapsedTimer ticker; - CommunicatorTracePrinter tracer(processWatcher->GetCommunicator(), "AssetBuilder"); + ProcessCommunicatorTracePrinter tracer(processWatcher->GetCommunicator(), "AssetBuilder"); ticker.start(); diff --git a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.cpp b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.cpp index 751afc1a5d..aa462f7590 100644 --- a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.cpp @@ -164,7 +164,7 @@ namespace AssetProcessor return false; } - m_tracePrinter = AZStd::make_unique(m_processWatcher->GetCommunicator(), "AssetBuilder"); + m_tracePrinter = AZStd::make_unique(m_processWatcher->GetCommunicator(), "AssetBuilder"); return WaitForConnection(); } diff --git a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.h b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.h index 657e4c7788..440f0b3709 100644 --- a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.h +++ b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.h @@ -10,11 +10,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include // used in the inl file. @@ -127,7 +127,7 @@ namespace AssetProcessor AZStd::unique_ptr m_processWatcher = nullptr; //! Optional communicator, only available if we have a process watcher - AZStd::unique_ptr m_tracePrinter = nullptr; + AZStd::unique_ptr m_tracePrinter = nullptr; const AssetUtilities::QuitListener& m_quitListener; }; diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 8f11985cba..ec783808a2 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -14,11 +14,8 @@ #include #include #include -#include -#include #include -#include #include #include #include @@ -133,6 +130,7 @@ namespace Multiplayer AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); AzFramework::GameEntityContextEventBus::Handler::BusDisconnect(); MultiplayerEditorServerRequestBus::Handler::BusDisconnect(); + AZ::TickBus::Handler::BusDisconnect(); } void MultiplayerEditorSystemComponent::NotifyRegisterViews() @@ -157,12 +155,20 @@ namespace Multiplayer [[fallthrough]]; case eNotify_OnEndGameMode: // Kill the configured server if it's active - if (m_serverProcess) + AZ::TickBus::Handler::BusDisconnect(); + if (m_serverProcessWatcher) { - m_serverProcess->TerminateProcess(0); - m_serverProcess = nullptr; + m_serverProcessWatcher->TerminateProcess(0); + if (m_serverProcessTracePrinter) + { + m_serverProcessTracePrinter->Pump(); + m_serverProcessTracePrinter->WriteCurrentString(true); + m_serverProcessTracePrinter->WriteCurrentString(false); + } + m_serverProcessWatcher = nullptr; + m_serverProcessTracePrinter = nullptr; } - + if (INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName))) { editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient); @@ -181,7 +187,7 @@ namespace Multiplayer } } - AzFramework::ProcessWatcher* LaunchEditorServer() + void MultiplayerEditorSystemComponent::LaunchEditorServer() { // Assemble the server's path AZ::CVarFixedString serverProcess = editorsv_process; @@ -207,12 +213,22 @@ namespace Multiplayer { server_rhi = static_cast(editorsv_rhi_override); } + + const auto console = AZ::Interface::Get(); + AZ::CVarFixedString sv_defaultPlayerSpawnAsset; + + if (console->GetCvarValue("sv_defaultPlayerSpawnAsset", sv_defaultPlayerSpawnAsset) != AZ::GetValueResult::Success) + { + AZ_Assert( false, + "MultiplayerEditorSystemComponent::LaunchEditorServer failed! Could not find the sv_defaultPlayerSpawnAsset cvar; the editor-server " + "will fall back to using some other default player! Please update this code to use a valid cvar!") + } processLaunchInfo.m_commandlineParameters = AZStd::string::format( R"("%s" --project-path "%s" --editorsv_isDedicated true --sv_defaultPlayerSpawnAsset "%s" --rhi "%s")", serverPath.c_str(), AZ::Utils::GetProjectPath().c_str(), - static_cast(sv_defaultPlayerSpawnAsset).c_str(), + sv_defaultPlayerSpawnAsset.c_str(), server_rhi.GetCStr() ); processLaunchInfo.m_showWindow = true; @@ -220,13 +236,21 @@ namespace Multiplayer // Launch the Server AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess( - processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT); AZ_Error( "MultiplayerEditor", processLaunchInfo.m_launchResult != AzFramework::ProcessLauncher::ProcessLaunchResult::PLR_MissingFile, "LaunchEditorServer failed! The ServerLauncher binary is missing! (%s) Please build server launcher.", serverPath.c_str()) - return outProcess; + // Stop the previous server if one exists + if (m_serverProcessWatcher) + { + AZ::TickBus::Handler::BusDisconnect(); + m_serverProcessWatcher->TerminateProcess(0); + } + m_serverProcessWatcher.reset(outProcess); + m_serverProcessTracePrinter = AZStd::make_unique(m_serverProcessWatcher->GetCommunicator(), "EditorServer"); + AZ::TickBus::Handler::BusConnect(); } void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() @@ -286,7 +310,7 @@ namespace Multiplayer editorNetworkInterface->Listen(editorsv_port); // Launch the editor-server - m_serverProcess = LaunchEditorServer(); + LaunchEditorServer(); } else { @@ -393,4 +417,20 @@ namespace Multiplayer { return PyIsInGameMode(); } + + void MultiplayerEditorSystemComponent::OnTick(float, AZ::ScriptTimePoint) + { + if (m_serverProcessTracePrinter) + { + m_serverProcessTracePrinter->Pump(); + } + else + { + AZ::TickBus::Handler::BusDisconnect(); + AZ_Warning( + "MultiplayerEditorSystemComponent", false, + "The server process trace printer is NULL so we won't be able to pipe server logs to the editor. Please update the code to call AZ::TickBus::Handler::BusDisconnect whenever the editor-server is terminated.") + } + } + } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 77b41a5dc4..5e49f8f08d 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -13,14 +13,12 @@ #include #include -#include - #include #include #include -#include #include #include +#include #include namespace AzNetworking @@ -52,6 +50,7 @@ namespace Multiplayer , private AzToolsFramework::EditorEvents::Bus::Handler , private IEditorNotifyListener , private MultiplayerEditorServerRequestBus::Handler + , private AZ::TickBus::Handler { public: AZ_COMPONENT(MultiplayerEditorSystemComponent, "{9F335CC0-5574-4AD3-A2D8-2FAEF356946C}"); @@ -84,7 +83,9 @@ namespace Multiplayer bool IsInGameMode() override; //! @} - private: + private: + void LaunchEditorServer(); + //! EditorEvents::Handler overrides //! @{ void OnEditorNotifyEvent(EEditorNotifyEvent event) override; @@ -101,8 +102,14 @@ namespace Multiplayer void SendEditorServerLevelDataPacket(AzNetworking::IConnection* connection) override; //! @} + //! AZ::TickBus::Handler + //! @{ + void OnTick(float, AZ::ScriptTimePoint) override; + //! @} + IEditor* m_editor = nullptr; - AzFramework::ProcessWatcher* m_serverProcess = nullptr; + AZStd::unique_ptr m_serverProcessWatcher = nullptr; + AZStd::unique_ptr m_serverProcessTracePrinter = nullptr; AzNetworking::ConnectionId m_editorConnId; ServerAcceptanceReceivedEvent::Handler m_serverAcceptanceReceivedHandler; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index d839407fb0..dd2e7956ca 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -37,8 +37,6 @@ namespace AzNetworking namespace Multiplayer { - AZ_CVAR_EXTERNED(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset); - //! Multiplayer system component wraps the bridging logic between the game and transport layer. class MultiplayerSystemComponent final : public AZ::Component