Merge editor-server race condition fix; will test on Jenkins
Signed-off-by: Gene Walters <genewalt@amazon.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
class MultiplayerEditorServerRequests : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
|
||||
virtual void SendEditorServerInitPacket(AzNetworking::IConnection* connection) = 0;
|
||||
};
|
||||
using MultiplayerEditorServerRequestBus = AZ::EBus<MultiplayerEditorServerRequests>;
|
||||
} // namespace Multiplayer
|
||||
@@ -4,7 +4,9 @@
|
||||
<Include File="AzNetworking/AutoGen/CorePackets.AutoPackets.h" />
|
||||
<Include File="Multiplayer/MultiplayerTypes.h" />
|
||||
<Include File="Multiplayer/NetworkTime/INetworkTime.h" />
|
||||
|
||||
|
||||
<Packet Name="EditorServerReadyForInit" Desc="A packet the local server will send on startup once it's ready for the EditorServerInit packet."/>
|
||||
|
||||
<Packet Name="EditorServerInit" Desc="A packet that initializes a local server launched from the editor">
|
||||
<Member Type="bool" Name="lastUpdate" Init="false"/>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
#include <Multiplayer/INetworkSpawnableLibrary.h>
|
||||
#include <Multiplayer/MultiplayerConstants.h>
|
||||
#include <Multiplayer/MultiplayerEditorServerBus.h>
|
||||
#include <Editor/MultiplayerEditorConnection.h>
|
||||
#include <Source/AutoGen/AutoComponentTypes.h>
|
||||
|
||||
@@ -35,13 +36,25 @@ namespace Multiplayer
|
||||
m_networkEditorInterface->SetTimeoutMs(AZ::TimeMs{ 0 }); // Disable timeouts on this network interface
|
||||
if (editorsv_isDedicated)
|
||||
{
|
||||
uint16_t editorServerPort = DefaultServerEditorPort;
|
||||
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console)
|
||||
uint16_t editorsv_port = DefaultServerEditorPort;
|
||||
if (const auto console = AZ::Interface<AZ::IConsole>::Get())
|
||||
{
|
||||
console->GetCvarValue("editorsv_port", editorServerPort);
|
||||
console->GetCvarValue("editorsv_port", editorsv_port);
|
||||
}
|
||||
AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.")
|
||||
|
||||
// Check if there's already an Editor out there waiting to connect
|
||||
ConnectionId editorServerToEditorConnectionId = m_networkEditorInterface->Connect(IpAddress(LocalHost.data(), editorsv_port, ProtocolType::Tcp));
|
||||
|
||||
// If there wasn't an Editor waiting for this server to start, then assume this is an editor-server launched by hand... listen and wait for the editor to request a connection
|
||||
if (editorServerToEditorConnectionId == AzNetworking::InvalidConnectionId)
|
||||
{
|
||||
m_networkEditorInterface->Listen(editorsv_port);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_networkEditorInterface->SendReliablePacket(editorServerToEditorConnectionId, MultiplayerEditorPackets::EditorServerReadyForInit());
|
||||
}
|
||||
AZ_Assert(m_networkEditorInterface, "MP Editor Network Interface was unregistered before Editor Server could start listening.");
|
||||
m_networkEditorInterface->Listen(editorServerPort);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +138,15 @@ namespace Multiplayer
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MultiplayerEditorConnection::HandleRequest(
|
||||
[[maybe_unused]] AzNetworking::IConnection* connection,
|
||||
[[maybe_unused]] const AzNetworking::IPacketHeader& packetHeader,
|
||||
[[maybe_unused]] MultiplayerEditorPackets::EditorServerReadyForInit& packet)
|
||||
{
|
||||
MultiplayerEditorServerRequestBus::Broadcast(&MultiplayerEditorServerRequestBus::Events::SendEditorServerInitPacket, connection);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MultiplayerEditorConnection::HandleRequest
|
||||
(
|
||||
[[maybe_unused]] AzNetworking::IConnection* connection,
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Multiplayer
|
||||
MultiplayerEditorConnection();
|
||||
~MultiplayerEditorConnection() = default;
|
||||
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReadyForInit& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet);
|
||||
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet);
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ namespace Multiplayer
|
||||
{
|
||||
AzFramework::GameEntityContextEventBus::Handler::BusConnect();
|
||||
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
|
||||
MultiplayerEditorServerRequestBus::Handler::BusConnect();
|
||||
AZ::Interface<IMultiplayer>::Get()->AddServerAcceptanceReceivedHandler(m_serverAcceptanceReceivedHandler);
|
||||
}
|
||||
|
||||
@@ -125,6 +126,7 @@ namespace Multiplayer
|
||||
{
|
||||
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
|
||||
AzFramework::GameEntityContextEventBus::Handler::BusDisconnect();
|
||||
MultiplayerEditorServerRequestBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void MultiplayerEditorSystemComponent::NotifyRegisterViews()
|
||||
@@ -154,8 +156,8 @@ namespace Multiplayer
|
||||
m_serverProcess->TerminateProcess(0);
|
||||
m_serverProcess = nullptr;
|
||||
}
|
||||
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
|
||||
if (editorNetworkInterface)
|
||||
|
||||
if (INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)))
|
||||
{
|
||||
editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient);
|
||||
}
|
||||
@@ -252,62 +254,48 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
const AZ::CVarFixedString remoteAddress = editorsv_serveraddr;
|
||||
if (editorsv_launch && LocalHost == remoteAddress)
|
||||
if (editorsv_launch)
|
||||
{
|
||||
if (LocalHost != remoteAddress)
|
||||
{
|
||||
AZ_Warning(
|
||||
"MultiplayerEditor", false,
|
||||
"Launching EditorServer skipped because incompatible cvars. editorsv_launch=true, meaning you want to launch an editor-server on this machine, but the editorsv_serveraddr is %s instead of the local address (127.0.0.1)."
|
||||
"Please either set editorsv_launch=false and keep the remote editor-server, or set editorsv_launch=true and editorsv_serveraddr=127.0.0.1.",
|
||||
remoteAddress.c_str())
|
||||
return;
|
||||
}
|
||||
|
||||
// Begin listening so we know when the editor-server being ready for us
|
||||
INetworkInterface* editorNetworkInterface =
|
||||
AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
|
||||
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.");
|
||||
editorNetworkInterface->Listen(editorsv_port);
|
||||
|
||||
// Launch the editor-server
|
||||
m_serverProcess = LaunchEditorServer();
|
||||
|
||||
//AZ_Warning(
|
||||
// "MultiplayerEditor", false,
|
||||
// "Just launched LaunchEditorServer. About to sleep!")
|
||||
|
||||
//AZStd::this_thread::sleep_for(AZStd::chrono::seconds(10));
|
||||
|
||||
//AZ_Warning("MultiplayerEditor", false, "Just launched LaunchEditorServer. Done sleeping!")
|
||||
|
||||
}
|
||||
|
||||
// Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets
|
||||
AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
|
||||
|
||||
// Now that the server has launched, attempt to connect the NetworkInterface
|
||||
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
|
||||
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.");
|
||||
m_editorConnId = editorNetworkInterface->Connect(
|
||||
AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp));
|
||||
|
||||
if (m_editorConnId == AzNetworking::InvalidConnectionId)
|
||||
else
|
||||
{
|
||||
AZ_Warning(
|
||||
"MultiplayerEditor", false,
|
||||
"Could not connect to server targeted by Editor. If using a local server, check that it's built and editorsv_launch is true.");
|
||||
return;
|
||||
}
|
||||
// Editorsv_launch=false, so we're expecting an editor-server already exists.
|
||||
// Connect to the editor-server and then send the EditorServerInit packet.
|
||||
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName));
|
||||
AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.")
|
||||
|
||||
m_editorConnId = editorNetworkInterface->Connect(AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp));
|
||||
|
||||
// Read the buffer into EditorServerInit packets until we've flushed the whole thing
|
||||
byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
|
||||
|
||||
while (byteStream.GetCurPos() < byteStream.GetLength())
|
||||
{
|
||||
MultiplayerEditorPackets::EditorServerInit packet;
|
||||
auto& outBuffer = packet.ModifyAssetData();
|
||||
|
||||
// Size the packet's buffer appropriately
|
||||
size_t readSize = outBuffer.GetCapacity();
|
||||
size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos();
|
||||
if (byteStreamSize < readSize)
|
||||
if (m_editorConnId == AzNetworking::InvalidConnectionId)
|
||||
{
|
||||
readSize = byteStreamSize;
|
||||
AZ_Warning(
|
||||
"MultiplayerEditor", false,
|
||||
"Editor game-mode multiplayer failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i)."
|
||||
"Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.",
|
||||
remoteAddress.c_str(),
|
||||
static_cast < uint16_t>(editorsv_port))
|
||||
return;
|
||||
}
|
||||
|
||||
outBuffer.Resize(readSize);
|
||||
byteStream.Read(readSize, outBuffer.GetBuffer());
|
||||
|
||||
// If we've run out of buffer, mark that we're done
|
||||
if (byteStream.GetCurPos() == byteStream.GetLength())
|
||||
{
|
||||
packet.SetLastUpdate(true);
|
||||
}
|
||||
editorNetworkInterface->SendReliablePacket(m_editorConnId, packet);
|
||||
SendEditorServerInitPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,6 +312,65 @@ namespace Multiplayer
|
||||
AZ::Interface<IMultiplayer>::Get()->SendReadyForEntityUpdates(true);
|
||||
}
|
||||
|
||||
void MultiplayerEditorSystemComponent::SendEditorServerInitPacket(AzNetworking::IConnection* connection)
|
||||
{
|
||||
const auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
|
||||
if (!prefabEditorEntityOwnershipInterface)
|
||||
{
|
||||
AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable")
|
||||
return;
|
||||
}
|
||||
|
||||
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
|
||||
|
||||
AZStd::vector<uint8_t> buffer;
|
||||
AZ::IO::ByteContainerStream byteStream(&buffer);
|
||||
|
||||
// Serialize Asset information and AssetData into a potentially large buffer
|
||||
for (const auto& asset : assetData)
|
||||
{
|
||||
AZ::Data::AssetId assetId = asset.GetId();
|
||||
AZStd::string assetHint = asset.GetHint();
|
||||
auto hintSize = aznumeric_cast<uint32_t>(assetHint.size());
|
||||
|
||||
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
|
||||
byteStream.Write(sizeof(uint32_t), reinterpret_cast<void*>(&hintSize));
|
||||
byteStream.Write(assetHint.size(), assetHint.data());
|
||||
AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
|
||||
}
|
||||
|
||||
// Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets
|
||||
AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
|
||||
|
||||
// Read the buffer into EditorServerInit packets until we've flushed the whole thing
|
||||
byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
|
||||
|
||||
while (byteStream.GetCurPos() < byteStream.GetLength())
|
||||
{
|
||||
MultiplayerEditorPackets::EditorServerInit editorServerInitPacket;
|
||||
auto& outBuffer = editorServerInitPacket.ModifyAssetData();
|
||||
|
||||
// Size the packet's buffer appropriately
|
||||
size_t readSize = outBuffer.GetCapacity();
|
||||
const size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos();
|
||||
if (byteStreamSize < readSize)
|
||||
{
|
||||
readSize = byteStreamSize;
|
||||
}
|
||||
|
||||
outBuffer.Resize(readSize);
|
||||
byteStream.Read(readSize, outBuffer.GetBuffer());
|
||||
|
||||
// If we've run out of buffer, mark that we're done
|
||||
if (byteStream.GetCurPos() == byteStream.GetLength())
|
||||
{
|
||||
editorServerInitPacket.SetLastUpdate(true);
|
||||
}
|
||||
|
||||
connection->SendReliablePacket(editorServerInitPacket);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerEditorSystemComponent::EnterGameMode()
|
||||
{
|
||||
PyEnterGameMode();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
#include <Multiplayer/MultiplayerEditorServerBus.h>
|
||||
#include <Multiplayer/Editor/MultiplayerPythonEditorEventsBus.h>
|
||||
#include <IEditor.h>
|
||||
|
||||
@@ -50,6 +51,7 @@ namespace Multiplayer
|
||||
, private AzFramework::GameEntityContextEventBus::Handler
|
||||
, private AzToolsFramework::EditorEvents::Bus::Handler
|
||||
, private IEditorNotifyListener
|
||||
, private MultiplayerEditorServerRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(MultiplayerEditorSystemComponent, "{9F335CC0-5574-4AD3-A2D8-2FAEF356946C}");
|
||||
@@ -94,6 +96,11 @@ namespace Multiplayer
|
||||
void OnGameEntitiesReset() override;
|
||||
//! @}
|
||||
|
||||
//! MultiplayerEditorServerRequestBus::Handler
|
||||
//! @{
|
||||
void SendEditorServerInitPacket(AzNetworking::IConnection* connection) override;
|
||||
//! @}
|
||||
|
||||
IEditor* m_editor = nullptr;
|
||||
AzFramework::ProcessWatcher* m_serverProcess = nullptr;
|
||||
AzNetworking::ConnectionId m_editorConnId;
|
||||
|
||||
@@ -13,6 +13,7 @@ set(FILES
|
||||
Include/Multiplayer/MultiplayerConstants.h
|
||||
Include/Multiplayer/MultiplayerStats.h
|
||||
Include/Multiplayer/MultiplayerTypes.h
|
||||
Include/Multiplayer/MultiplayerEditorServerBus.h
|
||||
Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h
|
||||
Include/Multiplayer/Components/MultiplayerComponent.h
|
||||
Include/Multiplayer/Components/MultiplayerComponentRegistry.h
|
||||
|
||||
Reference in New Issue
Block a user