Streamline MP Ctrl+G logic via MultiplayerEditorConnection

main
puvvadar 5 years ago
parent 558e5c2fb5
commit 8a39f9f1b4

@ -176,7 +176,7 @@ namespace AzNetworking
if (::bind(aznumeric_cast<int32_t>(m_socketFd), (const sockaddr*)&hints, sizeof(hints)) != 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to bind socket (%d:%s)", error, GetNetworkErrorDesc(error));
AZLOG_ERROR("Failed to bind TCP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error));
return false;
}

@ -162,7 +162,7 @@ namespace AzNetworking
const UdpReaderThread::ReceivedPackets* packets = m_readerThread.GetReceivedPackets(m_socket.get());
if (packets == nullptr)
{
AZ_Assert(false, "nullptr was retrieved for the received packet buffer, check that the socket has been registered with the reader thread");
// Socket is not yet registered with the reader thread and is likely still pending, try again later
return;
}

@ -82,7 +82,7 @@ namespace AzNetworking
if (::bind(static_cast<int32_t>(m_socketFd), (const sockaddr *)&hints, sizeof(hints)) != 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to bind socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error));
AZLOG_ERROR("Failed to bind UDP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error));
return false;
}
}

@ -77,12 +77,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
AZ::AzToolsFramework
Gem::Multiplayer.Static
)
ly_add_target(
NAME Multiplayer.Editor.Static STATIC
NAME Multiplayer.Editor GEM_MODULE
NAMESPACE Gem
FILES_CMAKE
multiplayer_editor_files.cmake
multiplayer_editor_shared_files.cmake
COMPILE_DEFINITIONS
PUBLIC
MULTIPLAYER_EDITOR
@ -94,7 +94,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
PUBLIC
Include
BUILD_DEPENDENCIES
PUBLIC
PRIVATE
Legacy::CryCommon
Legacy::Editor.Headers
AZ::AzCore
@ -102,23 +102,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
AZ::AzNetworking
AZ::AzToolsFramework
Gem::Multiplayer.Static
)
ly_add_target(
NAME Multiplayer.Editor GEM_MODULE
NAMESPACE Gem
FILES_CMAKE
multiplayer_editor_shared_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
Source
${pal_source_dir}
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Gem::Multiplayer.Editor.Static
Gem::Multiplayer.Tools
)

@ -59,10 +59,5 @@
<Member Type="Multiplayer::PrefabEntityId" Name="prefabEntityId" Init="" />
<Member Type="AzNetworking::PacketEncodingBuffer" Name="propertyUpdateData" Init="" SuppressFromInitializerList="true" />
</Packet>
<Packet Name="EditorServerInit" Desc="A packet that initializes a local server launched from the editor">
<Member Type="bool" Name="lastUpdate" Init="false"/>
<Member Type="uint16_t" Name="serializedSize" Init="0"/>
<Member Type="AzNetworking::TcpPacketEncodingBuffer" Name="assetData"/>
</Packet>
</PacketGroup>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<PacketGroup Name="MultiplayerEditorPackets" PacketStart="CorePackets::PacketType::MAX">
<Include File="AzNetworking/AutoGen/CorePackets.AutoPackets.h" />
<Include File="Include/MultiplayerTypes.h" />
<Include File="Include/INetworkTime.h" />
<Packet Name="EditorServerInit" Desc="A packet that initializes a local server launched from the editor">
<Member Type="bool" Name="lastUpdate" Init="false"/>
<Member Type="AzNetworking::TcpPacketEncodingBuffer" Name="assetData"/>
</Packet>
<Packet Name="EditorServerReady" Desc="A response packet the local server should send when ready for traffic"/>
</PacketGroup>

@ -0,0 +1,162 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Include/IMultiplayer.h>
#include <Source/Editor/MultiplayerEditorConnection.h>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/Framework/INetworking.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
namespace Multiplayer
{
using namespace AzNetworking;
static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface");
static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface");
static constexpr AZStd::string_view DefaultEditorIp = "127.0.0.1";
static constexpr uint16_t DefaultServerPort = 30090;
static constexpr uint16_t DefaultServerEditorPort = 30091;
static AZStd::vector<uint8_t> buffer;
static AZ::IO::ByteContainerStream<AZStd::vector<uint8_t>> s_byteStream(&buffer);
AZ_CVAR(bool, editorsv_isDedicated, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether to init as a server expecting data from an Editor. Do not modify unless you're sure of what you're doing.");
MultiplayerEditorConnection::MultiplayerEditorConnection()
{
m_networkEditorInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
if (editorsv_isDedicated)
{
m_networkEditorInterface->Listen(DefaultServerEditorPort);
}
}
bool MultiplayerEditorConnection::HandleRequest
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] const IPacketHeader& packetHeader,
[[maybe_unused]] MultiplayerEditorPackets::EditorServerInit& packet
)
{
// Editor Server Init is intended for non-release targets
if (!packet.GetLastUpdate())
{
// More packets are expected, flush this to the buffer
s_byteStream.Write(TcpPacketEncodingBuffer::GetCapacity(), reinterpret_cast<void*>(packet.ModifyAssetData().GetBuffer()));
}
else
{
// This is the last expected packet, flush it to the buffer
s_byteStream.Write(packet.GetAssetData().GetSize(), reinterpret_cast<void*>(packet.ModifyAssetData().GetBuffer()));
// Read all assets out of the buffer
s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> assetData;
while (s_byteStream.GetCurPos() < s_byteStream.GetLength())
{
AZ::Data::AssetLoadBehavior assetLoadBehavior;
s_byteStream.Read(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast<void*>(&assetLoadBehavior));
AZ::Data::AssetData* assetDatum = AZ::Utils::LoadObjectFromStream<AZ::Data::AssetData>(s_byteStream, nullptr);
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::Asset<AZ::Data::AssetData>(assetDatum, assetLoadBehavior);
/*
// Register Asset to AssetManager
*/
assetData.push_back(asset);
}
// Now that we've deserialized, clear the byte stream
s_byteStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
s_byteStream.Truncate();
/*
// Hand-off our resultant assets
*/
AZLOG_INFO("Editor Server completed asset receive, responding to Editor...");
if (connection->SendReliablePacket(MultiplayerEditorPackets::EditorServerReady()))
{
// Setup the normal multiplayer connection
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer);
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName));
networkInterface->Listen(DefaultServerPort);
return true;
}
else
{
return false;
}
}
return true;
}
bool MultiplayerEditorConnection::HandleRequest
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] const IPacketHeader& packetHeader,
[[maybe_unused]] MultiplayerEditorPackets::EditorServerReady& packet
)
{
if (connection->GetConnectionRole() == ConnectionRole::Connector)
{
// Receiving this packet means Editor sync is done, disconnect
connection->Disconnect(AzNetworking::DisconnectReason::TerminatedByClient, AzNetworking::TerminationEndpoint::Local);
// Connect the Editor to the local server for Multiplayer simulation
AZ::Interface<IMultiplayer>::Get()->InitializeMultiplayer(MultiplayerAgentType::Client);
INetworkInterface* networkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(s_networkInterfaceName));
const IpAddress ipAddress(DefaultEditorIp.data(), DefaultServerEditorPort, networkInterface->GetType());
networkInterface->Connect(ipAddress);
}
return true;
}
ConnectResult MultiplayerEditorConnection::ValidateConnect
(
[[maybe_unused]] const IpAddress& remoteAddress,
[[maybe_unused]] const IPacketHeader& packetHeader,
[[maybe_unused]] ISerializer& serializer
)
{
return ConnectResult::Accepted;
}
void MultiplayerEditorConnection::OnConnect([[maybe_unused]] AzNetworking::IConnection* connection)
{
;
}
bool MultiplayerEditorConnection::OnPacketReceived(AzNetworking::IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer)
{
return MultiplayerEditorPackets::DispatchPacket(connection, packetHeader, serializer, *this);
}
void MultiplayerEditorConnection::OnPacketLost([[maybe_unused]] IConnection* connection, [[maybe_unused]] PacketId packetId)
{
;
}
void MultiplayerEditorConnection::OnDisconnect([[maybe_unused]] AzNetworking::IConnection* connection, [[maybe_unused]] DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint)
{
;
}
}

@ -0,0 +1,55 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/std/string/string.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <Source/AutoGen/MultiplayerEditor.AutoPacketDispatcher.h>
namespace AzNetworking
{
class INetworkInterface;
}
namespace Multiplayer
{
//! MultiplayerEditorConnection is a connection listener to synchronize the Editor and a local server it launches
class MultiplayerEditorConnection final
: public AzNetworking::IConnectionListener
{
public:
MultiplayerEditorConnection();
~MultiplayerEditorConnection() = default;
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet);
//! IConnectionListener interface
//! @{
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
void OnConnect(AzNetworking::IConnection* connection) override;
bool OnPacketReceived(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
void OnPacketLost(AzNetworking::IConnection* connection, AzNetworking::PacketId packetId) override;
void OnDisconnect(AzNetworking::IConnection* connection, AzNetworking::DisconnectReason reason, AzNetworking::TerminationEndpoint endpoint) override;
//! @}
private:
AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr;
};
}

@ -1,21 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Source/Editor/MultiplayerEditorDispatcher.h>
namespace Multiplayer
{
MultiplayerEditorDispatcher::MultiplayerEditorDispatcher()
{
;
}
}

@ -1,36 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <IEditor.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
namespace Multiplayer
{
//! MultiplayerEditorDispatcher is responsible for dispatching delta from the Editor to an Editor launched local server
class MultiplayerEditorDispatcher final
{
public:
MultiplayerEditorDispatcher();
~MultiplayerEditorDispatcher() = default;
private:
};
}

@ -13,7 +13,7 @@
#include <Source/Multiplayer_precompiled.h>
#include <Source/MultiplayerGem.h>
#include <Source/MultiplayerSystemComponent.h>
#include <Source/MultiplayerEditorGem.h>
#include <Source/Editor/MultiplayerEditorGem.h>
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
#include <Source/Editor/MultiplayerEditorSystemComponent.h>

@ -26,16 +26,16 @@
namespace Multiplayer
{
static const AZStd::string_view s_networkInterfaceName("MultiplayerEditorServerInterface");
static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface");
using namespace AzNetworking;
AZ_CVAR(bool, editorsv_enabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
AZ_CVAR(bool, editorsv_enabled, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"Whether Editor launching a local server to connect to is supported");
AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"The server executable that should be run. Empty to use the current project's ServerLauncher");
AZ_CVAR(AZ::CVarFixedString, sv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to");
AZ_CVAR(uint16_t, sv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic");
AZ_CVAR(AZ::CVarFixedString, editorsv_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the server to connect to");
AZ_CVAR(uint16_t, editorsv_port, 30091, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that the multiplayer editor gem will bind to for traffic");
void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context)
{
@ -70,16 +70,6 @@ namespace Multiplayer
{
AzFramework::GameEntityContextEventBus::Handler::BusConnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
// Setup a network interface handled by MultiplayerSystemComponent
if (m_editorNetworkInterface == nullptr)
{
AZ::Entity* systemEntity = this->GetEntity();
MultiplayerSystemComponent* mpSysComponent = systemEntity->FindComponent<MultiplayerSystemComponent>();
m_editorNetworkInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
AZ::Name(s_networkInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *mpSysComponent);
}
}
void MultiplayerEditorSystemComponent::Deactivate()
@ -109,18 +99,16 @@ namespace Multiplayer
}
[[fallthrough]];
case eNotify_OnEndGameMode:
AZ::TickBus::Handler::BusDisconnect();
// Kill the configured server if it's active
if (m_serverProcess)
{
m_serverProcess->TerminateProcess(0);
m_serverProcess = nullptr;
}
if (m_editorNetworkInterface)
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(s_networkEditorInterfaceName));
if (editorNetworkInterface)
{
// Disconnect the interface, connection management will clean it up
m_editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByUser);
m_editorConnId = AzNetworking::InvalidConnectionId;
editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByClient);
}
break;
}
@ -133,116 +121,95 @@ namespace Multiplayer
{
AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable");
}
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 (auto asset : assetData)
{
AZ::Data::AssetId assetId = asset.GetId();
AZ::Data::AssetType assetType = asset.GetType();
const AZStd::string& assetHint = asset.GetHint();
AZ::IO::SizeType assetHintSize = assetHint.size();
AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior();
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
byteStream.Write(sizeof(AZ::Data::AssetType), reinterpret_cast<void*>(&assetType));
byteStream.Write(sizeof(assetHintSize), reinterpret_cast<void*>(&assetHintSize));
byteStream.Write(assetHint.size(), assetHint.c_str());
byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast<void*>(&assetLoadBehavior));
AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
}
// BeginGameMode and Prefab Processing have completed at this point
IMultiplayerTools* mpTools = AZ::Interface<IMultiplayerTools>::Get();
if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs())
{
AZ::TickBus::Handler::BusConnect();
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
AZStd::vector<uint8_t> buffer;
AZ::IO::ByteContainerStream byteStream(&buffer);
if (assetData.size() > 0)
// Serialize Asset information and AssetData into a potentially large buffer
for (auto asset : assetData)
{
// Assemble the server's path
AZ::CVarFixedString serverProcess = editorsv_process;
if (serverProcess.empty())
{
// If enabled but no process name is supplied, try this project's ServerLauncher
serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher";
}
AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory();
if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR))
{
// If only the process name is specified, append that as well
serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess);
}
else
{
// If any path was already specified, then simply assign
serverPath = serverProcess;
}
if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION))
{
// Add this platform's exe extension if it's not specified
serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
}
// Start the configured server if it's available
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\"", serverPath.c_str());
processLaunchInfo.m_showWindow = true;
processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL;
AZ::Data::AssetLoadBehavior assetLoadBehavior = asset.GetAutoLoadBehavior();
byteStream.Write(sizeof(AZ::Data::AssetLoadBehavior), reinterpret_cast<void*>(&assetLoadBehavior));
AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
}
m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess(
processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE);
// Assemble the server's path
AZ::CVarFixedString serverProcess = editorsv_process;
if (serverProcess.empty())
{
// If enabled but no process name is supplied, try this project's ServerLauncher
serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher";
}
}
// Now that the server has launched, attempt to connect the NetworkInterface
const AZ::CVarFixedString remoteAddress = sv_serveraddr;
m_editorConnId = m_editorNetworkInterface->Connect(
AzNetworking::IpAddress(remoteAddress.c_str(), sv_port, AzNetworking::ProtocolType::Tcp));
AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory();
if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR))
{
// If only the process name is specified, append that as well
serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess);
}
else
{
// If any path was already specified, then simply assign
serverPath = serverProcess;
}
// 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())
{
MultiplayerPackets::EditorServerInit packet;
AzNetworking::TcpPacketEncodingBuffer& outBuffer = packet.ModifyAssetData();
// Size the packet's buffer appropriately
size_t readSize = TcpPacketEncodingBuffer::GetCapacity();
size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos();
if (byteStreamSize < readSize)
if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION))
{
readSize = byteStreamSize;
// Add this platform's exe extension if it's not specified
serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
}
outBuffer.Resize(readSize);
byteStream.Read(readSize, outBuffer.GetBuffer());
// Start the configured server if it's available
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str());
processLaunchInfo.m_showWindow = true;
processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL;
// If we've run out of buffer, mark that we're done
if (byteStream.GetCurPos() == byteStream.GetLength())
// Launch the Server and give it a few seconds to boot up
m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess(
processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE);
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(15000));
// Now that the server has launched, attempt to connect the NetworkInterface
const AZ::CVarFixedString remoteAddress = editorsv_serveraddr;
INetworkInterface* editorNetworkInterface = AZ::Interface<INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(s_networkEditorInterfaceName));
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())
{
packet.SetLastUpdate(true);
}
m_editorNetworkInterface->SendReliablePacket(m_editorConnId, packet);
}
MultiplayerEditorPackets::EditorServerInit packet;
AzNetworking::TcpPacketEncodingBuffer& outBuffer = packet.ModifyAssetData();
}
// Size the packet's buffer appropriately
size_t readSize = TcpPacketEncodingBuffer::GetCapacity();
size_t byteStreamSize = byteStream.GetLength() - byteStream.GetCurPos();
if (byteStreamSize < readSize)
{
readSize = byteStreamSize;
}
void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
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);
}
}
int MultiplayerEditorSystemComponent::GetTickOrder()
{
// Tick immediately after the network system component
return AZ::TICK_PLACEMENT + 1;
}
}

@ -14,6 +14,8 @@
#include <IEditor.h>
#include <Source/Editor/MultiplayerEditorConnection.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
@ -34,7 +36,6 @@ namespace Multiplayer
//! Multiplayer system component wraps the bridging logic between the game and transport layer.
class MultiplayerEditorSystemComponent final
: public AZ::Component
, private AZ::TickBus::Handler
, private AzFramework::GameEntityContextEventBus::Handler
, private AzToolsFramework::EditorEvents::Bus::Handler
, private IEditorNotifyListener
@ -61,14 +62,7 @@ namespace Multiplayer
void NotifyRegisterViews() override;
//! @}
private:
//! AZ::TickBus::Handler overrides.
//! @{
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
//! @}
private:
//! EditorEvents::Handler overrides
//! @{
void OnEditorNotifyEvent(EEditorNotifyEvent event) override;
@ -82,6 +76,5 @@ namespace Multiplayer
IEditor* m_editor = nullptr;
AzFramework::ProcessWatcher* m_serverProcess = nullptr;
AzNetworking::ConnectionId m_editorConnId;
AzNetworking::INetworkInterface* m_editorNetworkInterface = nullptr;
};
}

@ -23,6 +23,9 @@
#include <AzCore/Interface/Interface.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
namespace AZ::ConsoleTypeHelpers
{
@ -60,6 +63,8 @@ namespace Multiplayer
static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface");
static constexpr uint16_t DefaultServerPort = 30090;
static constexpr uint16_t DefaultServerEditorPort = 30091;
//static AZStd::vector<uint8_t> buffer;
//static AZ::IO::ByteContainerStream<AZStd::vector<uint8_t>> s_byteStream(&buffer);
AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port");
AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, "127.0.0.1", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to");
@ -107,7 +112,7 @@ namespace Multiplayer
AZ::ConsoleInvokedFrom invokedFrom
) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); })
{
;
}
void MultiplayerSystemComponent::Activate()
@ -401,19 +406,6 @@ namespace Multiplayer
return false;
}
bool MultiplayerSystemComponent::HandleRequest
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] const IPacketHeader& packetHeader,
[[maybe_unused]] MultiplayerPackets::EditorServerInit& packet
)
{
#if !defined(_RELEASE)
// Support Editor Server Init for all non-release targets
#endif
return true;
}
ConnectResult MultiplayerSystemComponent::ValidateConnect
(
[[maybe_unused]] const IpAddress& remoteAddress,
@ -447,13 +439,19 @@ namespace Multiplayer
// TODO: This needs to be set to the players autonomous proxy ------------v
NetworkEntityHandle controlledEntity = GetNetworkEntityTracker()->Get(NetEntityId{ 0 });
if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so
if (controlledEntity.GetEntity() != nullptr)
{
connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity));
}
if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so
{
connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity));
}
AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<ServerToClientReplicationWindow>(controlledEntity, connection);
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window));
AZStd::unique_ptr<IReplicationWindow> window =
AZStd::make_unique<ServerToClientReplicationWindow>(controlledEntity, connection);
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())
->GetReplicationManager()
.SetReplicationWindow(AZStd::move(window));
}
}
else
{
@ -509,12 +507,6 @@ namespace Multiplayer
{
if (multiplayerType == MultiplayerAgentType::ClientServer || multiplayerType == MultiplayerAgentType::DedicatedServer)
{
#if !defined(_RELEASE)
m_networkEditorInterface = AZ::Interface<INetworking>::Get()->CreateNetworkInterface(
AZ::Name(s_networkEditorInterfaceName), ProtocolType::Tcp, TrustZone::ExternalClientToServer, *this);
m_networkEditorInterface->Listen(DefaultServerEditorPort);
#endif
m_initEvent.Signal(m_networkInterface);
const AZ::Aabb worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-16384.0f), AZ::Vector3(16384.0f));

@ -16,10 +16,12 @@
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/Threading/ThreadSafeDeque.h>
#include <AzCore/std/string/string.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <Include/IMultiplayer.h>
#include <Source/Editor/MultiplayerEditorConnection.h>
#include <Source/NetworkTime/NetworkTime.h>
#include <Source/NetworkEntity/NetworkEntityManager.h>
#include <Source/AutoGen/Multiplayer.AutoPacketDispatcher.h>
@ -72,7 +74,7 @@ namespace Multiplayer
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::NotifyClientMigration& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EntityMigration& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::ReadyForEntityUpdates& packet);
bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet);
//bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerPackets::EditorServerInit& packet);
//! IConnectionListener interface
//! @{
@ -125,5 +127,9 @@ namespace Multiplayer
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId;
#if !defined(_RELEASE)
MultiplayerEditorConnection m_editorConnectionListener;
#endif
};
}

@ -35,6 +35,10 @@ namespace Multiplayer
context.ListPrefabs([&context](AZStd::string_view prefabName, PrefabDom& prefab) {
ProcessPrefab(context, prefabName, prefab);
});
if (context.GetProcessedObjects().size() > 0)
{
mpTools->SetDidProcessNetworkPrefabs(true);
}
}
void NetworkPrefabProcessor::Reflect(AZ::ReflectContext* context)

@ -10,6 +10,4 @@
#
set(FILES
Source/Editor/MultiplayerEditorDispatcher.cpp
Source/Editor/MultiplayerEditorDispatcher.h
)

@ -12,8 +12,8 @@
set(FILES
Source/MultiplayerGem.cpp
Source/MultiplayerGem.h
Source/MultiplayerEditorGem.cpp
Source/MultiplayerEditorGem.h
Source/Editor/MultiplayerEditorGem.cpp
Source/Editor/MultiplayerEditorGem.h
Source/Editor/MultiplayerEditorSystemComponent.cpp
Source/Editor/MultiplayerEditorSystemComponent.h
)

@ -33,6 +33,7 @@ set(FILES
Source/AutoGen/AutoComponentTypes_Source.jinja
Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml
Source/AutoGen/Multiplayer.AutoPackets.xml
Source/AutoGen/MultiplayerEditor.AutoPackets.xml
Source/AutoGen/NetworkTransformComponent.AutoComponent.xml
Source/Components/LocalPredictionPlayerInputComponent.cpp
Source/Components/LocalPredictionPlayerInputComponent.h
@ -52,6 +53,8 @@ set(FILES
Source/ConnectionData/ServerToClientConnectionData.cpp
Source/ConnectionData/ServerToClientConnectionData.h
Source/ConnectionData/ServerToClientConnectionData.inl
Source/Editor/MultiplayerEditorConnection.cpp
Source/Editor/MultiplayerEditorConnection.h
Source/EntityDomains/FullOwnershipEntityDomain.cpp
Source/EntityDomains/FullOwnershipEntityDomain.h
Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp

Loading…
Cancel
Save