Add Asset serialization for Ctrl+G and related net interfaces

This commit is contained in:
puvvadar
2021-05-06 16:18:13 -07:00
parent 2f4120cdfb
commit aa51233536
8 changed files with 132 additions and 13 deletions
@@ -116,8 +116,8 @@ namespace AzNetworking
int32_t TcpSocket::Receive(uint8_t* outData, uint32_t size) const
{
AZ_Assert(size > 0, "Invalid data size for send");
AZ_Assert(outData != nullptr, "NULL data pointer passed to send");
AZ_Assert(size > 0, "Invalid data size for receive");
AZ_Assert(outData != nullptr, "NULL data pointer passed to receive");
if (!IsOpen())
{
return SocketOpResultErrorNotOpen;
@@ -57,4 +57,10 @@
<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>
@@ -10,23 +10,32 @@
*
*/
#include <Include/IMultiplayer.h>
#include <Include/IMultiplayerTools.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Source/MultiplayerSystemComponent.h>
#include <Source/Editor/MultiplayerEditorSystemComponent.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Utils/Utils.h>
#include <AzNetworking/Framework/INetworking.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
namespace Multiplayer
{
static const AZStd::string_view s_networkInterfaceName("MultiplayerEditorServerInterface");
using namespace AzNetworking;
AZ_CVAR(bool, editorsv_enabled, false, 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");
void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context)
{
@@ -61,6 +70,16 @@ 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()
@@ -97,25 +116,52 @@ namespace Multiplayer
m_serverProcess->TerminateProcess(0);
m_serverProcess = nullptr;
}
if (m_editorNetworkInterface)
{
// Disconnect the interface, connection management will clean it up
m_editorNetworkInterface->Disconnect(m_editorConnId, AzNetworking::DisconnectReason::TerminatedByUser);
m_editorConnId = AzNetworking::InvalidConnectionId;
}
break;
}
}
void MultiplayerEditorSystemComponent::OnGameEntitiesStarted()
{
auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
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();
auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable");
}
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
if (assetData.size() > 0)
{
// Assemble the server's path
@@ -154,6 +200,39 @@ namespace Multiplayer
processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE);
}
}
// 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));
// 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)
{
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())
{
packet.SetLastUpdate(true);
}
m_editorNetworkInterface->SendReliablePacket(m_editorConnId, packet);
}
}
void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
@@ -81,5 +81,7 @@ namespace Multiplayer
IEditor* m_editor = nullptr;
AzFramework::ProcessWatcher* m_serverProcess = nullptr;
AzNetworking::ConnectionId m_editorConnId;
AzNetworking::INetworkInterface* m_editorNetworkInterface = nullptr;
};
}
@@ -57,7 +57,9 @@ namespace Multiplayer
using namespace AzNetworking;
static const AZStd::string_view s_networkInterfaceName("MultiplayerNetworkInterface");
static const AZStd::string_view s_networkEditorInterfaceName("MultiplayerEditorNetworkInterface");
static constexpr uint16_t DefaultServerPort = 30090;
static constexpr uint16_t DefaultServerEditorPort = 30091;
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");
@@ -397,6 +399,19 @@ 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,
@@ -492,6 +507,12 @@ 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));
@@ -72,7 +72,8 @@ 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);
//! IConnectionListener interface
//! @{
AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override;
@@ -109,6 +110,7 @@ namespace Multiplayer
AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session");
AzNetworking::INetworkInterface* m_networkInterface = nullptr;
AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr;
AZ::ConsoleCommandInvokedEvent::Handler m_consoleCommandHandler;
AZ::ThreadSafeDeque<AZStd::string> m_cvarCommands;
@@ -24,6 +24,16 @@ namespace Multiplayer
NetworkPrefabProcessor::Reflect(context);
}
void MultiplayerToolsSystemComponent::Activate()
{
AZ::Interface<IMultiplayerTools>::Register(this);
}
void MultiplayerToolsSystemComponent::Deactivate()
{
AZ::Interface<IMultiplayerTools>::Unregister(this);
}
bool MultiplayerToolsSystemComponent::DidProcessNetworkPrefabs()
{
return m_didProcessNetPrefabs;
@@ -31,9 +31,8 @@ namespace Multiplayer
~MultiplayerToolsSystemComponent() override = default;
/// AZ::Component overrides.
void Activate() override {};
void Deactivate() override {};
void Activate() override;
void Deactivate() override;
bool DidProcessNetworkPrefabs() override;