Skip to content

Instantly share code, notes, and snippets.

@Deathwing
Created December 14, 2022 22:24
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Deathwing/8c8a5c1ed68c87e6ee31b67fd8aa8db0 to your computer and use it in GitHub Desktop.
Save Deathwing/8c8a5c1ed68c87e6ee31b67fd8aa8db0 to your computer and use it in GitHub Desktop.
Initialization Code supporting Relay for Unity 2022.2.0f1 with NetCode for Entities 1.0.0-pre.15
using Unity.Assertions;
using Unity.Entities;
using Unity.NetCode;
using Unity.Networking.Transport;
using Unity.Networking.Transport.Relay;
using UnityEngine.Scripting;
namespace NetCode
{
[Preserve]
public sealed class NetCodeBootstrap : ClientServerBootstrap
{
public override bool Initialize(string defaultWorldName) => false;
public static void Prepare(PlayType requestedPlayType, RelayServerData? relayData = null)
{
EnsureRequestedPlayTypeIsSupported(requestedPlayType);
EnsureNoNetCodeWorldExists();
if (requestedPlayType != PlayType.Client)
{
EnsureCorrectDriverConstructorIsSelected(requestedPlayType, relayData, WorldFlags.GameServer);
CreateServerWorld("ServerWorld");
}
if (requestedPlayType != PlayType.Server)
{
EnsureCorrectDriverConstructorIsSelected(requestedPlayType, relayData, WorldFlags.GameClient);
CreateClientWorld("ClientWorld");
#if UNITY_EDITOR
EnsureCorrectDriverConstructorIsSelected(requestedPlayType, relayData, WorldFlags.GameThinClient);
var requestedNumThinClients = RequestedNumThinClients;
for (var i = 0; i < requestedNumThinClients; i++)
CreateThinClientWorld();
#endif
}
}
static void EnsureRequestedPlayTypeIsSupported(PlayType requestedPlayType)
{
switch (ClientServerBootstrap.RequestedPlayType)
{
case PlayType.Client:
if (requestedPlayType == PlayType.ClientAndServer || requestedPlayType == PlayType.Server)
throw new System.NotSupportedException("Cannot run a server on a client-only process");
return;
case PlayType.Server:
if (requestedPlayType == PlayType.ClientAndServer || requestedPlayType == PlayType.Client)
throw new System.NotSupportedException("Cannot run a client on a server-only process");
return;
}
}
static void EnsureNoNetCodeWorldExists()
{
for (var i = World.All.Count - 1; i >= 0; --i)
if (World.All[i].IsClient() || World.All[i].IsServer() || World.All[i].IsThinClient())
World.All[i].Dispose();
}
static void EnsureCorrectDriverConstructorIsSelected(PlayType requestedPlayType, RelayServerData? relayData, WorldFlags flags)
{
if (relayData.HasValue && (requestedPlayType != PlayType.ClientAndServer || flags == WorldFlags.GameServer))
NetworkStreamReceiveSystem.DriverConstructor = new RelayIPCAndSocketDriverConstructor(relayData.Value);
else
NetworkStreamReceiveSystem.DriverConstructor = DefaultDriverBuilder.DefaultDriverConstructor;
}
struct RelayIPCAndSocketDriverConstructor : INetworkStreamDriverConstructor
{
RelayServerData relayData;
public RelayIPCAndSocketDriverConstructor(RelayServerData relayData)
{
this.relayData = relayData;
}
public void CreateClientDriver(World world, ref NetworkDriverStore driverStore, NetDebug netDebug)
{
// TODO: Check after NetCode update
var settings = DefaultDriverBuilder.GetNetworkSettings();
settings = settings.WithRelayParameters(ref relayData);
Assert.IsTrue(ClientServerBootstrap.RequestedPlayType != ClientServerBootstrap.PlayType.Server);
Assert.IsTrue(world.IsClient());
netDebug.DebugLog("Create client relay socket network interface driver");
var driverInstance = DefaultDriverBuilder.CreateClientNetworkDriver(new UDPNetworkInterface(), settings);
driverStore.RegisterDriver(TransportType.Socket, driverInstance);
}
public void CreateServerDriver(World world, ref NetworkDriverStore driverStore, NetDebug netDebug)
{
// TODO: Check after NetCode update
{
var settings = DefaultDriverBuilder.GetNetworkServerSettings(playerCount: 0);
settings = settings.WithRelayParameters(ref relayData);
Assert.IsTrue(world.IsServer());
netDebug.DebugLog("Create server relay socket network interface driver");
var socketDriver = DefaultDriverBuilder.CreateServerNetworkDriver(new UDPNetworkInterface(), settings);
driverStore.RegisterDriver(TransportType.Socket, socketDriver);
}
{
var settings = DefaultDriverBuilder.GetNetworkServerSettings(playerCount: 0);
Assert.IsTrue(world.IsServer());
netDebug.DebugLog("Create server relay IPC network interface driver");
var ipcDriver = DefaultDriverBuilder.CreateServerNetworkDriver(new IPCNetworkInterface(), settings);
driverStore.RegisterDriver(TransportType.IPC, ipcDriver);
}
}
}
}
}
using System;
using static Unity.NetCode.ClientServerBootstrap;
using Unity.Entities;
using Unity.NetCode;
using Unity.Networking.Transport;
using Unity.Networking.Transport.Relay;
using UnityEngine;
namespace NetCode
{
public static partial class NetCodeUtility
{
public static void RequestHost(RelayServerData relayData)
{
// TODO: Check after NetCode update
var port = (ushort)(UnityEngine.Random.Range(ushort.MinValue, ushort.MaxValue) + 1);
NetCodeBootstrap.Prepare(PlayType.ClientAndServer, relayData);
RequestServerListen(NetworkEndpoint.AnyIpv4.WithPort(port));
RequestClientConnect(NetworkEndpoint.LoopbackIpv4.WithPort(port));
}
public static void RequestHost(string listenAddress, string connectAddress, ushort port, NetworkFamily family)
{
if (!NetworkEndpoint.TryParse(listenAddress, port, out var listenEndpoint, family))
throw new InvalidOperationException($"Invalid network endpoint: {family}::{listenAddress}:{port}.");
if (!NetworkEndpoint.TryParse(connectAddress, port, out var connectEndpoint, family))
throw new InvalidOperationException($"Invalid network endpoint: {family}::{connectAddress}:{port}.");
NetCodeBootstrap.Prepare(PlayType.ClientAndServer);
RequestServerListen(listenEndpoint);
RequestClientConnect(connectEndpoint);
}
public static void RequestServer(RelayServerData relayData)
{
NetCodeBootstrap.Prepare(PlayType.Server, relayData);
RequestServerListen(NetworkEndpoint.AnyIpv4);
}
public static void RequestServer(string listenAddress, ushort port, NetworkFamily family)
{
if (!NetworkEndpoint.TryParse(listenAddress, port, out var listenEndpoint, family))
throw new InvalidOperationException($"Invalid network endpoint: {family}::{listenAddress}:{port}.");
NetCodeBootstrap.Prepare(PlayType.Server);
RequestServerListen(listenEndpoint);
}
public static void RequestClient(RelayServerData relayData)
{
NetCodeBootstrap.Prepare(PlayType.Client, relayData);
RequestClientConnect(default(NetworkEndpoint));
}
public static void RequestClient(string connectAddress, ushort port, NetworkFamily family)
{
if (!NetworkEndpoint.TryParse(connectAddress, port, out var connectEndpoint, family))
throw new InvalidOperationException($"Invalid network endpoint: {family}::{connectAddress}:{port}.");
NetCodeBootstrap.Prepare(PlayType.Client);
RequestClientConnect(connectEndpoint);
}
static void RequestServerListen(NetworkEndpoint endpoint)
{
EntityManager entityManager = default;
foreach (var world in World.All)
if (world.Flags == WorldFlags.GameServer)
entityManager = world.EntityManager;
if (entityManager == default)
throw new InvalidOperationException("No server world found.");
// TODO: Check after NetCode update
var listenRequest = entityManager.CreateEntity(typeof(ServerListenRequest));
entityManager.SetComponentData(listenRequest, new ServerListenRequest { Endpoint = endpoint });
Debug.Log($"Listening on {endpoint}");
}
static void RequestClientConnect(NetworkEndpoint endpoint)
{
EntityManager entityManager = default;
foreach (var world in World.All)
if (world.Flags == WorldFlags.GameClient)
entityManager = world.EntityManager;
if (entityManager == default)
throw new InvalidOperationException("No client world found.");
// TODO: Check after NetCode update
var connectRequest = entityManager.CreateEntity(typeof(ClientConnectRequest));
entityManager.SetComponentData(connectRequest, new ClientConnectRequest { Endpoint = endpoint });
Debug.Log($"Connecting on {endpoint}");
}
struct ServerListenRequest : IComponentData
{
public NetworkEndpoint Endpoint;
}
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[CreateAfter(typeof(NetworkStreamReceiveSystem))]
partial struct ServerListenSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<ServerListenRequest>();
}
public void OnDestroy(ref SystemState state)
{
}
public void OnUpdate(ref SystemState state)
{
var listenRequest = SystemAPI.GetSingleton<ServerListenRequest>();
var entity = SystemAPI.GetSingletonEntity<ServerListenRequest>();
SystemAPI.GetSingletonRW<NetworkStreamDriver>().ValueRW.Listen(listenRequest.Endpoint);
state.EntityManager.RemoveComponent<ServerListenRequest>(entity);
}
}
struct ClientConnectRequest : IComponentData
{
public NetworkEndpoint Endpoint;
}
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)]
[CreateAfter(typeof(NetworkStreamReceiveSystem))]
partial struct ClientConnectSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<ClientConnectRequest>();
}
public void OnDestroy(ref SystemState state)
{
}
public void OnUpdate(ref SystemState state)
{
var connectRequest = SystemAPI.GetSingleton<ClientConnectRequest>();
var entity = SystemAPI.GetSingletonEntity<ClientConnectRequest>();
SystemAPI.GetSingletonRW<NetworkStreamDriver>().ValueRW.Connect(state.EntityManager, connectRequest.Endpoint);
state.EntityManager.RemoveComponent<ClientConnectRequest>(entity);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment