optimizing network stuff
This commit is contained in:
@@ -21,7 +21,7 @@ namespace RebootKit.Editor.Background {
|
|||||||
EditorUtility.InstanceIDToObject(createEvent.instanceId) as GameObject;
|
EditorUtility.InstanceIDToObject(createEvent.instanceId) as GameObject;
|
||||||
|
|
||||||
if (gameObjectChanged != null && gameObjectChanged.TryGetComponent(out Actor actor)) {
|
if (gameObjectChanged != null && gameObjectChanged.TryGetComponent(out Actor actor)) {
|
||||||
actor.ActorID = UniqueID.NewULongFromGuid();
|
actor.ActorStaticID = UniqueID.NewULongFromGuid();
|
||||||
EditorUtility.SetDirty(actor);
|
EditorUtility.SetDirty(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using UnityEngine;
|
using UnityEditor;
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Build;
|
using UnityEditor.Build;
|
||||||
using UnityEditor.Build.Reporting;
|
using UnityEditor.Build.Reporting;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace RebootKitEditor.Build {
|
namespace RebootKit.Editor.Build {
|
||||||
class BuildVersionBumper : IPostprocessBuildWithReport {
|
class BuildVersionBumper : IPostprocessBuildWithReport {
|
||||||
public int callbackOrder => 0;
|
public int callbackOrder => 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using RebootKitEditor.Build;
|
using RebootKit.Editor.Build;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace RebootKitEditor {
|
namespace RebootKit.Editor {
|
||||||
static class CommonEditorActions {
|
static class CommonEditorActions {
|
||||||
[MenuItem(REditorConsts.k_EditorMenu + "Bump minor version", false, 0)]
|
[MenuItem(REditorConsts.k_EditorMenu + "Bump minor version", false, 0)]
|
||||||
static void BumpMinorVersion() {
|
static void BumpMinorVersion() {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using RebootKit.Engine.Foundation;
|
using RebootKit.Editor.Utils;
|
||||||
using RebootKitEditor.Utils;
|
using RebootKit.Engine.Foundation;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace RebootKitEditor.PropertyDrawers {
|
namespace RebootKit.Editor.PropertyDrawers {
|
||||||
[CustomPropertyDrawer(typeof(ConfigVar))]
|
[CustomPropertyDrawer(typeof(ConfigVar))]
|
||||||
public class CVarDrawer : PropertyDrawer {
|
public class CVarDrawer : PropertyDrawer {
|
||||||
bool m_Expand;
|
bool m_Expand;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace RebootKitEditor.PropertyDrawers {
|
namespace RebootKit.Editor.PropertyDrawers {
|
||||||
[CustomPropertyDrawer(typeof(ConstsProperty<>))]
|
[CustomPropertyDrawer(typeof(ConstsProperty<>))]
|
||||||
public class ConstsPropertyDrawer : PropertyDrawer {
|
public class ConstsPropertyDrawer : PropertyDrawer {
|
||||||
const string k_InlineValue = "m_InlineValue";
|
const string k_InlineValue = "m_InlineValue";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using RebootKit.Engine.Foundation;
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace RebootKitEditor.PropertyDrawers {
|
namespace RebootKit.Editor.PropertyDrawers {
|
||||||
[CustomPropertyDrawer(typeof(SerializableGuid))]
|
[CustomPropertyDrawer(typeof(SerializableGuid))]
|
||||||
public class SerializableGuidDrawer : PropertyDrawer {
|
public class SerializableGuidDrawer : PropertyDrawer {
|
||||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace RebootKitEditor.PropertyDrawers {
|
namespace RebootKit.Editor.PropertyDrawers {
|
||||||
[CustomPropertyDrawer(typeof(FloatRange))]
|
[CustomPropertyDrawer(typeof(FloatRange))]
|
||||||
public class ValueRangeDrawer : PropertyDrawer {
|
public class ValueRangeDrawer : PropertyDrawer {
|
||||||
const string k_minPropertyName = "min";
|
const string k_minPropertyName = "min";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace RebootKitEditor {
|
namespace RebootKit.Editor {
|
||||||
static class REditorConsts {
|
static class REditorConsts {
|
||||||
internal const string k_EditorMenu = "Reboot Reality/";
|
internal const string k_EditorMenu = "Reboot Reality/";
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using RebootKit.Engine.UI;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
namespace RebootKitEditor.RebootWindow {
|
namespace RebootKit.Editor.RebootWindow {
|
||||||
public class ConfigVarsView : IView {
|
public class ConfigVarsView : IView {
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using RebootKit.Engine.Console;
|
using RebootKit.Engine.Console;
|
||||||
using RebootKit.Engine.Main;
|
|
||||||
using RebootKit.Engine.UI;
|
using RebootKit.Engine.UI;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
namespace RebootKitEditor.RebootWindow {
|
namespace RebootKit.Editor.RebootWindow {
|
||||||
public class HomeView : IView {
|
public class HomeView : IView {
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
using RebootKit.Editor.RebootWindow;
|
using RebootKit.Engine;
|
||||||
using RebootKit.Engine;
|
|
||||||
using RebootKit.Engine.Foundation;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||||
using TabView = RebootKit.Editor.RebootWindow.TabView;
|
|
||||||
|
|
||||||
namespace RebootKitEditor.RebootWindow {
|
namespace RebootKit.Editor.RebootWindow {
|
||||||
static class RTheme {
|
static class RTheme {
|
||||||
public static readonly Color s_FirstColor = ColorFromHex("#B9B8B9");
|
public static readonly Color s_FirstColor = ColorFromHex("#B9B8B9");
|
||||||
public static readonly Color s_SecondColor = ColorFromHex("#6B6B6B");
|
public static readonly Color s_SecondColor = ColorFromHex("#6B6B6B");
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using RebootKit.Engine.UI;
|
using RebootKit.Engine.UI;
|
||||||
using RebootKitEditor.RebootWindow;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Assertions;
|
using UnityEngine.Assertions;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
using RebootKit.Engine;
|
using RebootKit.Editor.Utils;
|
||||||
|
using RebootKit.Engine;
|
||||||
using RebootKit.Engine.Simulation;
|
using RebootKit.Engine.Simulation;
|
||||||
using RebootKit.Engine.UI;
|
using RebootKit.Engine.UI;
|
||||||
using RebootKitEditor.Utils;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.SceneManagement;
|
using UnityEditor.SceneManagement;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
namespace RebootKitEditor.RebootWindow {
|
namespace RebootKit.Editor.RebootWindow {
|
||||||
public class WorldsView : IView {
|
public class WorldsView : IView {
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace RebootKitEditor.Utils {
|
namespace RebootKit.Editor.Utils {
|
||||||
public static class AssetDatabaseEx {
|
public static class AssetDatabaseEx {
|
||||||
public static T[] LoadAllAssets<T>() where T : Object {
|
public static T[] LoadAllAssets<T>() where T : Object {
|
||||||
string[] guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
|
string[] guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
|
||||||
namespace RebootKitEditor.Utils {
|
namespace RebootKit.Editor.Utils {
|
||||||
public struct CVarSerializedProperties {
|
public struct CVarSerializedProperties {
|
||||||
public SerializedProperty flags;
|
public SerializedProperty flags;
|
||||||
public SerializedProperty name;
|
public SerializedProperty name;
|
||||||
|
|||||||
17
Editor/Utils/DomainReloader.cs
Normal file
17
Editor/Utils/DomainReloader.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace RebootKit.Editor.Utils {
|
||||||
|
public static class DomainReloader {
|
||||||
|
[MenuItem(REditorConsts.k_EditorMenu + "Reload Domain", priority = 1000)]
|
||||||
|
public static void ReloadDomain() {
|
||||||
|
if (EditorApplication.isCompiling) {
|
||||||
|
Debug.LogError("Cannot reload domain while compiling.");
|
||||||
|
EditorUtility.DisplayDialog("Reload Domain", "Cannot reload domain while compiling.", "OK");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorUtility.RequestScriptReload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Editor/Utils/DomainReloader.cs.meta
Normal file
3
Editor/Utils/DomainReloader.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 38260f05c1a7436cbe1813733e82ef56
|
||||||
|
timeCreated: 1753762882
|
||||||
@@ -3,7 +3,7 @@ using UnityEditor.UIElements;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
namespace SzafaKitEditor.VisualElements {
|
namespace RebootKit.Editor.VisualElements {
|
||||||
public class CVarPropertyField : VisualElement {
|
public class CVarPropertyField : VisualElement {
|
||||||
readonly SerializedProperty _cvarProperty;
|
readonly SerializedProperty _cvarProperty;
|
||||||
readonly SerializedProperty _defaultValueKindProperty;
|
readonly SerializedProperty _defaultValueKindProperty;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
using RebootKit.Engine.Main;
|
using RebootKit.Engine.Main;
|
||||||
using RebootKit.Engine.Network;
|
using RebootKit.Engine.Network;
|
||||||
using RebootKit.Engine.Simulation;
|
using RebootKit.Engine.Simulation;
|
||||||
@@ -55,7 +56,7 @@ namespace RebootKit.Engine.Development {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AppendNetworkStateInfo() {
|
void AppendNetworkStateInfo() {
|
||||||
NetworkSystem network = RR.NetworkSystemInstance;
|
NetworkSystem network = RR.Network;
|
||||||
|
|
||||||
if (network == null) {
|
if (network == null) {
|
||||||
m_StringBuilder.AppendLine("NetworkSystem not initialized");
|
m_StringBuilder.AppendLine("NetworkSystem not initialized");
|
||||||
@@ -74,7 +75,6 @@ namespace RebootKit.Engine.Development {
|
|||||||
if (network.TryGetClientState(network.LocalClientID, out NetworkClientState clientState)) {
|
if (network.TryGetClientState(network.LocalClientID, out NetworkClientState clientState)) {
|
||||||
m_StringBuilder.Append($"LocalClientID: {clientState.ClientID.ToString()}");
|
m_StringBuilder.Append($"LocalClientID: {clientState.ClientID.ToString()}");
|
||||||
m_StringBuilder.Append($" | SyncState: {clientState.SyncState.ToString()}");
|
m_StringBuilder.Append($" | SyncState: {clientState.SyncState.ToString()}");
|
||||||
m_StringBuilder.Append($" | ActorsSyncPacketsLeft: {clientState.ActorsSyncPacketsLeft.ToString()}");
|
|
||||||
m_StringBuilder.Append($" | IsReady: {clientState.IsReady.ToString()}");
|
m_StringBuilder.Append($" | IsReady: {clientState.IsReady.ToString()}");
|
||||||
} else {
|
} else {
|
||||||
m_StringBuilder.Append("ClientState not found for LocalClientID: ");
|
m_StringBuilder.Append("ClientState not found for LocalClientID: ");
|
||||||
@@ -82,10 +82,35 @@ namespace RebootKit.Engine.Development {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_StringBuilder.AppendLine();
|
m_StringBuilder.AppendLine();
|
||||||
|
|
||||||
|
if (network.Manager.Stats != null) {
|
||||||
|
m_StringBuilder.Append("Stats: ");
|
||||||
|
m_StringBuilder.Append($"Send Reliable: {FormatToLargestUnit(network.Manager.Stats.ReliableBytesSentPerSecond)}/s");
|
||||||
|
m_StringBuilder.Append($" | Send Unreliable: {FormatToLargestUnit(network.Manager.Stats.UnreliableBytesSentPerSecond)}/s");
|
||||||
|
m_StringBuilder.Append($" | Receive: {FormatToLargestUnit(network.Manager.Stats.BytesReceivedPerSecond)}/s");
|
||||||
|
m_StringBuilder.Append($" | Ping: {network.Manager.Stats.Ping.ToString()}ms");
|
||||||
|
m_StringBuilder.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string FormatToLargestUnit(double value) {
|
||||||
|
if (value < 1024) {
|
||||||
|
return $"{value:F2} B";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 1024 * 1024) {
|
||||||
|
return $"{(value / 1024):F2} KB";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 1024 * 1024 * 1024) {
|
||||||
|
return $"{(value / (1024 * 1024)):F2} MB";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{(value / (1024 * 1024 * 1024)):F2} GB";
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppendActorsStateInfo() {
|
void AppendActorsStateInfo() {
|
||||||
NetworkSystem network = RR.NetworkSystemInstance;
|
NetworkSystem network = RR.Network;
|
||||||
if (network == null) {
|
if (network == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using RebootKit.Engine.Foundation;
|
using RebootKit.Engine.Foundation;
|
||||||
using Unity.Multiplayer.Tools.NetStatsMonitor;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
|
|
||||||
@@ -11,15 +10,11 @@ namespace RebootKit.Engine.Development {
|
|||||||
|
|
||||||
[ConfigVar("debug.game_version", 1, "Controls game version overlay visibility. 0 - hidden, 1 - visible")]
|
[ConfigVar("debug.game_version", 1, "Controls game version overlay visibility. 0 - hidden, 1 - visible")]
|
||||||
public static ConfigVar ShowGameVersion;
|
public static ConfigVar ShowGameVersion;
|
||||||
|
|
||||||
[ConfigVar("debug.network_stats", 1, "Controls network stats overlay visibility. 0 - hidden, 1 - visible")]
|
|
||||||
public static ConfigVar ShowNetworkStats;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DevToolsService : ServiceMonoBehaviour {
|
public class DevToolsService : ServiceMonoBehaviour {
|
||||||
[SerializeField] DebugOverlayView m_DebugOverlayView;
|
[SerializeField] DebugOverlayView m_DebugOverlayView;
|
||||||
[SerializeField] GameVersionOverlay m_GameVersionOverlay;
|
[SerializeField] GameVersionOverlay m_GameVersionOverlay;
|
||||||
[SerializeField] RuntimeNetStatsMonitor m_NetworkStatsOverlay;
|
|
||||||
|
|
||||||
IDisposable m_CVarChangedListener;
|
IDisposable m_CVarChangedListener;
|
||||||
|
|
||||||
@@ -27,7 +22,6 @@ namespace RebootKit.Engine.Development {
|
|||||||
ConfigVar.StateChanged += OnCVarChanged;
|
ConfigVar.StateChanged += OnCVarChanged;
|
||||||
OnCVarChanged(DebugCVars.OverlayMode);
|
OnCVarChanged(DebugCVars.OverlayMode);
|
||||||
OnCVarChanged(DebugCVars.ShowGameVersion);
|
OnCVarChanged(DebugCVars.ShowGameVersion);
|
||||||
OnCVarChanged(DebugCVars.ShowNetworkStats);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnDisable() {
|
void OnDisable() {
|
||||||
@@ -42,10 +36,6 @@ namespace RebootKit.Engine.Development {
|
|||||||
if (InputSystem.GetDevice<Keyboard>().f3Key.wasReleasedThisFrame) {
|
if (InputSystem.GetDevice<Keyboard>().f3Key.wasReleasedThisFrame) {
|
||||||
DebugCVars.OverlayMode.Set(DebugCVars.OverlayMode.IndexValue == 1 ? 0 : 1);
|
DebugCVars.OverlayMode.Set(DebugCVars.OverlayMode.IndexValue == 1 ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (InputSystem.GetDevice<Keyboard>().f4Key.wasReleasedThisFrame) {
|
|
||||||
DebugCVars.ShowNetworkStats.Set(DebugCVars.ShowNetworkStats.IndexValue == 1 ? 0 : 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnOverlayModeChanged(int mode) {
|
void OnOverlayModeChanged(int mode) {
|
||||||
@@ -61,8 +51,6 @@ namespace RebootKit.Engine.Development {
|
|||||||
OnOverlayModeChanged(cvar.IndexValue);
|
OnOverlayModeChanged(cvar.IndexValue);
|
||||||
} else if (cvar == DebugCVars.ShowGameVersion) {
|
} else if (cvar == DebugCVars.ShowGameVersion) {
|
||||||
m_GameVersionOverlay.gameObject.SetActive(cvar.IndexValue > 0);
|
m_GameVersionOverlay.gameObject.SetActive(cvar.IndexValue > 0);
|
||||||
} else if (cvar == DebugCVars.ShowNetworkStats) {
|
|
||||||
m_NetworkStatsOverlay.Visible = cvar.IndexValue > 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,5 +17,8 @@ namespace RebootKit.Engine {
|
|||||||
// @NOTE: Spacewar, change as needed
|
// @NOTE: Spacewar, change as needed
|
||||||
[Header("Steam")]
|
[Header("Steam")]
|
||||||
public uint steamAppID = 480;
|
public uint steamAppID = 480;
|
||||||
|
|
||||||
|
[Header("Network")]
|
||||||
|
public byte protocolVersion = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,14 @@ namespace RebootKit.Engine.Extensions {
|
|||||||
}
|
}
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ToHexString(this NativeSlice<byte> slice) {
|
||||||
|
StringBuilder sb = new StringBuilder(slice.Length * 3);
|
||||||
|
for (int i = 0; i < slice.Length; i++) {
|
||||||
|
sb.AppendFormat("{0:X2} ", slice[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
25
Runtime/Engine/Code/Foundation/QuantizationUtility.cs
Normal file
25
Runtime/Engine/Code/Foundation/QuantizationUtility.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace RebootKit.Engine.Foundation {
|
||||||
|
public static class QuantizationUtility {
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ushort FloatToUShort(float value, float min, float max) {
|
||||||
|
return (ushort)((value - min) / (max - min) * ushort.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static float UShortToFloat(ushort value, float min, float max) {
|
||||||
|
return min + (value / (float)ushort.MaxValue) * (max - min);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static byte FloatToByte(float value, float min, float max) {
|
||||||
|
return (byte)((value - min) / (max - min) * byte.MaxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static float ByteToFloat(byte value, float min, float max) {
|
||||||
|
return min + (value / (float)byte.MaxValue) * (max - min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a1c1f40f52a64f4ab55436cc00e2ef20
|
||||||
|
timeCreated: 1753473209
|
||||||
@@ -1,47 +1,46 @@
|
|||||||
using System;
|
using System;
|
||||||
using Cysharp.Threading.Tasks;
|
using RebootKit.Engine.Network;
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Netcode;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||||
|
|
||||||
namespace RebootKit.Engine.Main {
|
namespace RebootKit.Engine.Main {
|
||||||
public abstract class Game : NetworkBehaviour {
|
public abstract class Game : MonoBehaviour {
|
||||||
static readonly Logger s_GameLogger = new Logger(nameof(Game));
|
static readonly Logger s_GameLogger = new Logger(nameof(Game));
|
||||||
|
|
||||||
|
[SerializeField] NetworkPlayerController m_PlayerControllerPrefab;
|
||||||
|
|
||||||
|
public NetworkPlayerController LocalPlayerController { get; internal set; }
|
||||||
|
|
||||||
|
protected virtual void Awake() {
|
||||||
|
LocalPlayerController = Instantiate(m_PlayerControllerPrefab, transform);
|
||||||
|
}
|
||||||
|
|
||||||
// Server only callbacks
|
//
|
||||||
|
// @MARK: Server
|
||||||
|
//
|
||||||
protected virtual void OnPlayerBecameReady(ulong clientID) {
|
protected virtual void OnPlayerBecameReady(ulong clientID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event callbacks
|
public virtual void OnClientConnected(ulong clientID) {
|
||||||
|
s_GameLogger.Info($"Client {clientID} connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnClientDisconnected(ulong clientID) {
|
||||||
|
s_GameLogger.Info($"Client {clientID} disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: Chat
|
||||||
|
//
|
||||||
protected virtual void OnChatMessage(string message) {
|
protected virtual void OnChatMessage(string message) {
|
||||||
s_GameLogger.Info($"Chat: {message}");
|
s_GameLogger.Info($"Chat: {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// NGO callbacks
|
//
|
||||||
public override void OnNetworkSpawn() {
|
// @MARK: Player Ready State
|
||||||
base.OnNetworkSpawn();
|
//
|
||||||
RR.GameInstance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnNetworkDespawn() {
|
|
||||||
base.OnNetworkDespawn();
|
|
||||||
RR.GameInstance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chat
|
|
||||||
[Rpc(SendTo.Server)]
|
|
||||||
public void SendChatMessageRpc(string message) {
|
|
||||||
PrintChatMessageClientRpc(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ClientRpc(Delivery = RpcDelivery.Reliable)]
|
|
||||||
void PrintChatMessageClientRpc(string message) {
|
|
||||||
OnChatMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void PlayerBecameReady(ulong clientID) {
|
internal void PlayerBecameReady(ulong clientID) {
|
||||||
if (!IsServer) {
|
if (!RR.IsServer()) {
|
||||||
s_GameLogger.Error("PlayerBecameReady called on client, but this should only be called on the server.");
|
s_GameLogger.Error("PlayerBecameReady called on client, but this should only be called on the server.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ using RebootKit.Engine.Foundation;
|
|||||||
using RebootKit.Engine.Input;
|
using RebootKit.Engine.Input;
|
||||||
using RebootKit.Engine.Network;
|
using RebootKit.Engine.Network;
|
||||||
using RebootKit.Engine.Simulation;
|
using RebootKit.Engine.Simulation;
|
||||||
|
#if RR_STEAM
|
||||||
using RebootKit.Engine.Steam;
|
using RebootKit.Engine.Steam;
|
||||||
using Unity.Netcode;
|
#endif
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.AddressableAssets;
|
using UnityEngine.AddressableAssets;
|
||||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||||
@@ -28,7 +29,7 @@ namespace RebootKit.Engine.Main {
|
|||||||
|
|
||||||
[ConfigVar("con.write_log", 1, "Enables writing game log to console output")]
|
[ConfigVar("con.write_log", 1, "Enables writing game log to console output")]
|
||||||
static ConfigVar s_WriteLogToConsole;
|
static ConfigVar s_WriteLogToConsole;
|
||||||
|
|
||||||
internal static EngineConfigAsset EngineConfig;
|
internal static EngineConfigAsset EngineConfig;
|
||||||
|
|
||||||
static DisposableBag s_DisposableBag;
|
static DisposableBag s_DisposableBag;
|
||||||
@@ -36,8 +37,13 @@ namespace RebootKit.Engine.Main {
|
|||||||
|
|
||||||
static AsyncOperationHandle<SceneInstance> s_MainMenuSceneHandle;
|
static AsyncOperationHandle<SceneInstance> s_MainMenuSceneHandle;
|
||||||
|
|
||||||
static NetworkSystem s_NetworkSystemPrefab;
|
internal static NetworkSystem Network;
|
||||||
internal static NetworkSystem NetworkSystemInstance;
|
internal static byte NetworkProtocolVersion {
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get {
|
||||||
|
return EngineConfig.protocolVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static ConsoleService Console { get; private set; }
|
internal static ConsoleService Console { get; private set; }
|
||||||
public static InputService Input { get; private set; }
|
public static InputService Input { get; private set; }
|
||||||
@@ -65,30 +71,18 @@ namespace RebootKit.Engine.Main {
|
|||||||
s_ServicesBag.Add(Input);
|
s_ServicesBag.Add(Input);
|
||||||
World = CreateService<WorldService>();
|
World = CreateService<WorldService>();
|
||||||
|
|
||||||
await InitializeAssetsAsync(cancellationToken);
|
|
||||||
|
|
||||||
#if RR_STEAM
|
#if RR_STEAM
|
||||||
await SteamManager.InitializeAsync(cancellationToken);
|
await SteamManager.InitializeAsync(cancellationToken);
|
||||||
|
Network = new NetworkSystem(new SteamNetworkManager());
|
||||||
|
#else
|
||||||
|
Network = new NetworkSystem(new UnityNetworkManager());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
await InitializeAssetsAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @NOTE: This method is called after the main scene is loaded.
|
// @NOTE: This method is called after the main scene is loaded.
|
||||||
internal static async UniTask RunAsync(CancellationToken cancellationToken) {
|
internal static async UniTask RunAsync(CancellationToken cancellationToken) {
|
||||||
s_NetworkSystemPrefab =
|
|
||||||
Resources.Load<NetworkSystem>(RConsts.k_CoreNetworkGameSystemsResourcesPath);
|
|
||||||
|
|
||||||
NetworkManager.Singleton.OnConnectionEvent += OnConnectionEvent;
|
|
||||||
NetworkManager.Singleton.OnServerStarted += OnServerStarted;
|
|
||||||
NetworkManager.Singleton.OnServerStopped += OnServerStopped;
|
|
||||||
|
|
||||||
#if RR_STEAM
|
|
||||||
if (NetworkManager.Singleton.TryGetComponent(out FacepunchTransport facepunchTransport)) {
|
|
||||||
NetworkManager.Singleton.NetworkConfig.NetworkTransport = facepunchTransport;
|
|
||||||
} else {
|
|
||||||
s_Logger.Error("Steam integration is enabled but FacepunchTransport is not found in NetworkManager.");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Observable.EveryUpdate()
|
Observable.EveryUpdate()
|
||||||
.Subscribe(_ => Tick())
|
.Subscribe(_ => Tick())
|
||||||
.AddTo(ref s_DisposableBag);
|
.AddTo(ref s_DisposableBag);
|
||||||
@@ -112,16 +106,12 @@ namespace RebootKit.Engine.Main {
|
|||||||
s_Logger.Info("Shutting down");
|
s_Logger.Info("Shutting down");
|
||||||
|
|
||||||
if (GameInstance is not null) {
|
if (GameInstance is not null) {
|
||||||
GameInstance.NetworkObject.Despawn();
|
|
||||||
Object.Destroy(GameInstance);
|
Object.Destroy(GameInstance);
|
||||||
GameInstance = null;
|
GameInstance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetworkManager.Singleton is not null) {
|
Network.Dispose();
|
||||||
NetworkManager.Singleton.OnConnectionEvent -= OnConnectionEvent;
|
Network = null;
|
||||||
NetworkManager.Singleton.OnServerStarted -= OnServerStarted;
|
|
||||||
NetworkManager.Singleton.OnServerStopped -= OnServerStopped;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if RR_STEAM
|
#if RR_STEAM
|
||||||
SteamManager.Shutdown();
|
SteamManager.Shutdown();
|
||||||
@@ -159,8 +149,10 @@ namespace RebootKit.Engine.Main {
|
|||||||
return worldConfig;
|
return worldConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Game API
|
//
|
||||||
public static async UniTask OpenMainMenuAsync(CancellationToken cancellationToken) {
|
// @MARK: Game
|
||||||
|
//
|
||||||
|
internal static async UniTask OpenMainMenuAsync(CancellationToken cancellationToken) {
|
||||||
s_Logger.Info("Opening main menu");
|
s_Logger.Info("Opening main menu");
|
||||||
|
|
||||||
World.Unload();
|
World.Unload();
|
||||||
@@ -201,7 +193,7 @@ namespace RebootKit.Engine.Main {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkSystemInstance.SetCurrentWorld(worldID);
|
Network.SetCurrentWorld(worldID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Actor SpawnActor(AssetReferenceGameObject assetReference,
|
public static Actor SpawnActor(AssetReferenceGameObject assetReference,
|
||||||
@@ -212,7 +204,7 @@ namespace RebootKit.Engine.Main {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetworkSystemInstance is null) {
|
if (Network is null) {
|
||||||
s_Logger.Error("NetworkSystemInstance is not initialized. Cannot spawn actor.");
|
s_Logger.Error("NetworkSystemInstance is not initialized. Cannot spawn actor.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -223,16 +215,16 @@ namespace RebootKit.Engine.Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s_Logger.Info($"Spawning actor from asset reference: {assetReference.RuntimeKey}");
|
s_Logger.Info($"Spawning actor from asset reference: {assetReference.RuntimeKey}");
|
||||||
return NetworkSystemInstance.Actors.SpawnActor(assetReference, position, rotation);
|
return Network.Actors.SpawnActor(assetReference, position, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Actor FindSpawnedActor(ulong actorID) {
|
public static Actor FindSpawnedActor(ushort actorID) {
|
||||||
if (NetworkSystemInstance is null) {
|
if (Network is null) {
|
||||||
s_Logger.Error("NetworkSystemInstance is not initialized. Cannot find actor.");
|
s_Logger.Error("NetworkSystemInstance is not initialized. Cannot find actor.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Actor actor = NetworkSystemInstance.Actors.FindActorByID(actorID);
|
Actor actor = Network.Actors.FindActorByID(actorID);
|
||||||
if (actor is null) {
|
if (actor is null) {
|
||||||
s_Logger.Error($"Actor with ID {actorID} not found");
|
s_Logger.Error($"Actor with ID {actorID} not found");
|
||||||
}
|
}
|
||||||
@@ -240,7 +232,24 @@ namespace RebootKit.Engine.Main {
|
|||||||
return actor;
|
return actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service API
|
public static void PossessActor(ulong clientID, ushort actorID) {
|
||||||
|
if (!IsServer()) {
|
||||||
|
s_Logger.Error("Only server can possess actors for clients.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Network == null) {
|
||||||
|
s_Logger.Error("Network is not initialized. Cannot possess actor.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Network.SendPossessedActor(clientID, actorID);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: Service API
|
||||||
|
// Services seems to be useless in the current architecture. Consider removing this API in the future.
|
||||||
|
//
|
||||||
public static TService CreateService<TService>(ServiceAsset<TService> asset) where TService : class, IService {
|
public static TService CreateService<TService>(ServiceAsset<TService> asset) where TService : class, IService {
|
||||||
if (asset is null) {
|
if (asset is null) {
|
||||||
throw new ArgumentNullException($"Null asset of type {typeof(TService)}");
|
throw new ArgumentNullException($"Null asset of type {typeof(TService)}");
|
||||||
@@ -257,7 +266,9 @@ namespace RebootKit.Engine.Main {
|
|||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logging API
|
//
|
||||||
|
// @MARK: Logging API
|
||||||
|
//
|
||||||
public static void Log(string message) {
|
public static void Log(string message) {
|
||||||
Debug.Log(message);
|
Debug.Log(message);
|
||||||
Console?.WriteToOutput(message);
|
Console?.WriteToOutput(message);
|
||||||
@@ -277,7 +288,9 @@ namespace RebootKit.Engine.Main {
|
|||||||
Console?.WriteToOutput(message);
|
Console?.WriteToOutput(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CVar API
|
//
|
||||||
|
// @MARK: Config Variables
|
||||||
|
//
|
||||||
public static ConfigVar CVarIndex(string name, int defaultValue = -1) {
|
public static ConfigVar CVarIndex(string name, int defaultValue = -1) {
|
||||||
ConfigVar cvar = ConfigVarsContainer.Get(name);
|
ConfigVar cvar = ConfigVarsContainer.Get(name);
|
||||||
if (cvar != null) {
|
if (cvar != null) {
|
||||||
@@ -311,49 +324,57 @@ namespace RebootKit.Engine.Main {
|
|||||||
return cvar;
|
return cvar;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network API
|
//
|
||||||
|
// @MARK: Network API
|
||||||
|
//
|
||||||
public static bool IsServer() {
|
public static bool IsServer() {
|
||||||
return NetworkManager.Singleton.IsServer;
|
return Network != null && Network.Manager.IsServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsClient() {
|
public static bool IsClient() {
|
||||||
return NetworkManager.Singleton.IsClient;
|
return Network != null && Network.Manager.IsClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void StartHost() {
|
public static void StartHost() {
|
||||||
if (NetworkManager.Singleton.IsHost) {
|
if (IsServer() || IsClient()) {
|
||||||
s_Logger.Error("Already hosting a server");
|
s_Logger.Error("Already hosting a server or connected as a client");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @TODO: Handle failures
|
||||||
|
|
||||||
s_Logger.Info("Starting host");
|
s_Logger.Info("Starting host");
|
||||||
NetworkManager.Singleton.StartHost();
|
if (!Network.Manager.StartHost()) {
|
||||||
|
s_Logger.Error("Failed to start host.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void StopServer() { }
|
public static void StopHost() {
|
||||||
|
Network.Manager.StopHost();
|
||||||
|
}
|
||||||
|
|
||||||
public static void Connect() {
|
public static void Connect() {
|
||||||
if (NetworkManager.Singleton.IsClient) {
|
if (IsClient()) {
|
||||||
s_Logger.Error("Already connected to a server");
|
s_Logger.Error("Already connected to a server");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_Logger.Info($"Connecting to server.");
|
s_Logger.Info("Connecting to server.");
|
||||||
NetworkManager.Singleton.StartClient();
|
Network.Manager.StartClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ConnectWithSteamID(ulong steamId) {
|
public static void ConnectWithSteamID(ulong steamId) {
|
||||||
#if RR_STEAM
|
#if RR_STEAM
|
||||||
|
if (IsClient()) {
|
||||||
if (NetworkManager.Singleton.IsClient) {
|
|
||||||
s_Logger.Error("Already connected to a server");
|
s_Logger.Error("Already connected to a server");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_Logger.Info($"Connecting to server with Steam ID: {steamId}");
|
s_Logger.Info($"Connecting to server with Steam ID: {steamId}");
|
||||||
if (NetworkManager.Singleton.NetworkConfig.NetworkTransport is FacepunchTransport facepunchTransport) {
|
if (Network.Manager is SteamNetworkManager steamNetworkManager) {
|
||||||
facepunchTransport.targetSteamId = steamId;
|
steamNetworkManager.TargetSteamID = steamId;
|
||||||
NetworkManager.Singleton.StartClient();
|
Network.Manager.StartClient();
|
||||||
} else {
|
} else {
|
||||||
s_Logger.Error("Network transport is not FacepunchTransport. Cannot connect with Steam ID.");
|
s_Logger.Error("Network transport is not FacepunchTransport. Cannot connect with Steam ID.");
|
||||||
}
|
}
|
||||||
@@ -362,7 +383,9 @@ namespace RebootKit.Engine.Main {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Disconnect() { }
|
public static void Disconnect() {
|
||||||
|
Network.Manager.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
public static void SendChatMessage(string message) {
|
public static void SendChatMessage(string message) {
|
||||||
if (!IsClient()) {
|
if (!IsClient()) {
|
||||||
@@ -374,40 +397,48 @@ namespace RebootKit.Engine.Main {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameInstance.SendChatMessageRpc(message);
|
throw new NotSupportedException("Cannot send chat message. Not connected to a server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Tick() {
|
static void Tick() {
|
||||||
|
float deltaTime = Time.deltaTime;
|
||||||
|
Network.Tick(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnConnectionEvent(NetworkManager network, ConnectionEventData data) {
|
internal static void OnServerStarted() {
|
||||||
s_Logger.Info("Connection event: " + data.EventType);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void OnServerStarted() {
|
|
||||||
s_Logger.Info("Server started");
|
s_Logger.Info("Server started");
|
||||||
|
|
||||||
GameInstance = Object.Instantiate(EngineConfig.gamePrefab);
|
GameInstance = Object.Instantiate(EngineConfig.gamePrefab);
|
||||||
GameInstance.NetworkObject.Spawn();
|
|
||||||
|
|
||||||
NetworkSystemInstance = Object.Instantiate(s_NetworkSystemPrefab);
|
|
||||||
NetworkSystemInstance.NetworkObject.Spawn();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OnServerStopped(bool obj) {
|
internal static void OnServerStopped() {
|
||||||
s_Logger.Info("Server stopped");
|
s_Logger.Info("Server stopped");
|
||||||
|
|
||||||
if (GameInstance is not null) {
|
if (GameInstance is not null) {
|
||||||
GameInstance.NetworkObject.Despawn();
|
Object.Destroy(GameInstance.gameObject);
|
||||||
GameInstance = null;
|
GameInstance = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (NetworkSystemInstance is not null) {
|
internal static void OnClientStarted() {
|
||||||
if (NetworkSystemInstance.NetworkObject is not null && NetworkSystemInstance.NetworkObject.IsSpawned) {
|
if (IsServer()) {
|
||||||
NetworkSystemInstance.NetworkObject.Despawn();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkSystemInstance = null;
|
GameInstance = Object.Instantiate(EngineConfig.gamePrefab);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OnClientStopped() {
|
||||||
|
if (IsServer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
World.Unload();
|
||||||
|
OpenMainMenuAsync(CancellationToken.None).Forget();
|
||||||
|
|
||||||
|
if (GameInstance is not null) {
|
||||||
|
Object.Destroy(GameInstance.gameObject);
|
||||||
|
GameInstance = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System;
|
using Unity.Collections;
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Collections.LowLevel.Unsafe;
|
|
||||||
using Unity.Netcode;
|
|
||||||
using UnityEngine.Assertions;
|
using UnityEngine.Assertions;
|
||||||
|
|
||||||
namespace RebootKit.Engine.Network {
|
namespace RebootKit.Engine.Network {
|
||||||
@@ -29,7 +26,7 @@ namespace RebootKit.Engine.Network {
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Deserialize<TEntity>(NativeArray<byte> data, ref TEntity entity)
|
public static void Deserialize<TEntity>(NativeSlice<byte> data, ref TEntity entity)
|
||||||
where TEntity : ISerializableEntity {
|
where TEntity : ISerializableEntity {
|
||||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
||||||
if (reader.HasNext(data.Length)) {
|
if (reader.HasNext(data.Length)) {
|
||||||
|
|||||||
62
Runtime/Engine/Code/Network/INetworkManager.cs
Normal file
62
Runtime/Engine/Code/Network/INetworkManager.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Collections;
|
||||||
|
|
||||||
|
namespace RebootKit.Engine.Network {
|
||||||
|
public enum SendMode {
|
||||||
|
Reliable,
|
||||||
|
Unreliable
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface INetworkManagerDelegate {
|
||||||
|
void OnServerStarted();
|
||||||
|
void OnServerStopped();
|
||||||
|
void OnClientStarted();
|
||||||
|
void OnClientStopped();
|
||||||
|
void OnClientConnected(ulong clientID);
|
||||||
|
void OnClientDisconnected(ulong clientID);
|
||||||
|
void OnMessageReceived(ulong senderID, NativeArray<byte> data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NetworkManagerStats {
|
||||||
|
public ulong ReliableBytesSent { get; set; }
|
||||||
|
public ulong UnreliableBytesSent { get; set; }
|
||||||
|
public ulong BytesReceived { get; set; }
|
||||||
|
|
||||||
|
public double ReliableBytesSentPerSecond { get; set; }
|
||||||
|
public double UnreliableBytesSentPerSecond { get; set; }
|
||||||
|
public double BytesReceivedPerSecond { get; set; }
|
||||||
|
|
||||||
|
public int Ping { get; set; }
|
||||||
|
|
||||||
|
public void Reset() {
|
||||||
|
ReliableBytesSent = 0;
|
||||||
|
UnreliableBytesSent = 0;
|
||||||
|
BytesReceived = 0;
|
||||||
|
ReliableBytesSentPerSecond = 0.0;
|
||||||
|
UnreliableBytesSentPerSecond = 0.0;
|
||||||
|
BytesReceivedPerSecond = 0.0;
|
||||||
|
Ping = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface INetworkManager : IDisposable {
|
||||||
|
INetworkManagerDelegate Delegate { get; set; }
|
||||||
|
|
||||||
|
ulong LocalClientID { get; }
|
||||||
|
|
||||||
|
NetworkManagerStats Stats { get; }
|
||||||
|
|
||||||
|
bool IsServer();
|
||||||
|
bool StartHost();
|
||||||
|
void StopHost();
|
||||||
|
|
||||||
|
bool IsClient();
|
||||||
|
bool StartClient();
|
||||||
|
void Disconnect();
|
||||||
|
|
||||||
|
void Send(ulong clientID, NativeSlice<byte> data, SendMode mode);
|
||||||
|
unsafe void Send(ulong clientID, byte* data, int length, SendMode mode);
|
||||||
|
|
||||||
|
void Tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Engine/Code/Network/INetworkManager.cs.meta
Normal file
3
Runtime/Engine/Code/Network/INetworkManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 26612cc58cd249cf8d567501e253eab1
|
||||||
|
timeCreated: 1753138601
|
||||||
@@ -7,7 +7,7 @@ using UnityEngine.Pool;
|
|||||||
namespace RebootKit.Engine.Network {
|
namespace RebootKit.Engine.Network {
|
||||||
public struct NetworkBufferReader : IDisposable {
|
public struct NetworkBufferReader : IDisposable {
|
||||||
class ReaderHandle {
|
class ReaderHandle {
|
||||||
public NativeArray<byte> Data;
|
public NativeSlice<byte> Data;
|
||||||
public int Position;
|
public int Position;
|
||||||
public bool IsBigEndian;
|
public bool IsBigEndian;
|
||||||
}
|
}
|
||||||
@@ -27,8 +27,7 @@ namespace RebootKit.Engine.Network {
|
|||||||
|
|
||||||
ReaderHandle m_Handle;
|
ReaderHandle m_Handle;
|
||||||
|
|
||||||
public NetworkBufferReader(NativeArray<byte> data, int position = 0) {
|
public NetworkBufferReader(NativeSlice<byte> data, int position = 0) {
|
||||||
Assert.IsTrue(data.IsCreated, "Trying to create a NetworkBufferReader with uncreated data.");
|
|
||||||
Assert.IsTrue(position >= 0 && position <= data.Length,
|
Assert.IsTrue(position >= 0 && position <= data.Length,
|
||||||
"Position must be within the bounds of the data array.");
|
"Position must be within the bounds of the data array.");
|
||||||
|
|
||||||
@@ -53,6 +52,11 @@ namespace RebootKit.Engine.Network {
|
|||||||
Assert.IsTrue(HasNext(size),
|
Assert.IsTrue(HasNext(size),
|
||||||
$"Not enough data to read the requested size. Requested: {size}, Available: {m_Handle.Data.Length - m_Handle.Position}");
|
$"Not enough data to read the requested size. Requested: {size}, Available: {m_Handle.Data.Length - m_Handle.Position}");
|
||||||
|
|
||||||
|
if (size <= 0) {
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
value = new NativeArray<byte>(size, allocator);
|
value = new NativeArray<byte>(size, allocator);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
value[i] = m_Handle.Data[m_Handle.Position++];
|
value[i] = m_Handle.Data[m_Handle.Position++];
|
||||||
@@ -61,6 +65,17 @@ namespace RebootKit.Engine.Network {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Read(out NativeSlice<byte> value, int size) {
|
||||||
|
if (!HasNext(size)) {
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = m_Handle.Data.Slice(m_Handle.Position, size);
|
||||||
|
m_Handle.Position += size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Read(out byte value) {
|
public bool Read(out byte value) {
|
||||||
if (!HasNext(1)) {
|
if (!HasNext(1)) {
|
||||||
value = 0;
|
value = 0;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Assertions;
|
using UnityEngine.Assertions;
|
||||||
@@ -179,7 +180,7 @@ namespace RebootKit.Engine.Network {
|
|||||||
|
|
||||||
public void Write(ulong value) {
|
public void Write(ulong value) {
|
||||||
Assert.IsTrue(sizeof(ulong) == 8, "Size of ulong must be 8 bytes.");
|
Assert.IsTrue(sizeof(ulong) == 8, "Size of ulong must be 8 bytes.");
|
||||||
Assert.IsTrue(WillFit(sizeof(ulong)), "Buffer overflow: Cannot write beyond capacity.");
|
Assert.IsTrue(WillFit(sizeof(ulong)), $"Buffer overflow: Cannot write beyond capacity. Current position: {m_Handle.Position}, Capacity: {m_Handle.Capacity}");
|
||||||
|
|
||||||
if (BitConverter.IsLittleEndian) {
|
if (BitConverter.IsLittleEndian) {
|
||||||
Write((byte) (value & 0xFF));
|
Write((byte) (value & 0xFF));
|
||||||
@@ -312,5 +313,43 @@ namespace RebootKit.Engine.Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @NOTE: Writes ascii characters as bytes, and prepends the length as a ushort.
|
||||||
|
public static int GetStringWriteLength(string value) {
|
||||||
|
if (value.Length > ushort.MaxValue) {
|
||||||
|
return sizeof(ushort);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(value)) {
|
||||||
|
return sizeof(ushort);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length prefix
|
||||||
|
int length = sizeof(ushort);
|
||||||
|
|
||||||
|
for (int i = 0; i < value.Length; i++) {
|
||||||
|
length += sizeof(char);
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(string value) {
|
||||||
|
if (value.Length > ushort.MaxValue) {
|
||||||
|
Write((ushort) 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(value)) {
|
||||||
|
Write((ushort) 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Write((ushort)value.Length);
|
||||||
|
|
||||||
|
for (int i = 0; i < value.Length; i++) {
|
||||||
|
Write((byte)value[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
109
Runtime/Engine/Code/Network/NetworkMessageBuffer.cs
Normal file
109
Runtime/Engine/Code/Network/NetworkMessageBuffer.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
using System;
|
||||||
|
using RebootKit.Engine;
|
||||||
|
using RebootKit.Engine.Network;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
|
|
||||||
|
namespace RebootKit.Engine.Network {
|
||||||
|
enum NetworkMessageType : byte {
|
||||||
|
None = 0x00,
|
||||||
|
// @MARK: Server to client messages
|
||||||
|
SpawnActor = 0x01,
|
||||||
|
PossessActor = 0x02,
|
||||||
|
ActorsEventsList = 0x03,
|
||||||
|
ActorsCoreStatesUpdateList = 0x04,
|
||||||
|
ActorsStatesUpdateList = 0x05,
|
||||||
|
ActorsTransformUpdateList = 0x06,
|
||||||
|
SynchronizeGameState = 0x07,
|
||||||
|
|
||||||
|
// @MARK: Client to server messages
|
||||||
|
ClientSynchronizedGameState = 0x80,
|
||||||
|
ActorsCommandsList = 0x81,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NetworkMessageHeader : ISerializableEntity {
|
||||||
|
public ushort MagicNumber;
|
||||||
|
public byte Version;
|
||||||
|
public NetworkMessageType MessageType;
|
||||||
|
|
||||||
|
public const int k_HeaderSize = sizeof(ushort) + sizeof(byte) + sizeof(byte);
|
||||||
|
|
||||||
|
public int GetMaxBytes() {
|
||||||
|
return k_HeaderSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Serialize(NetworkBufferWriter writer) {
|
||||||
|
writer.Write(MagicNumber);
|
||||||
|
writer.Write(Version);
|
||||||
|
writer.Write((byte) MessageType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deserialize(NetworkBufferReader reader) {
|
||||||
|
reader.Read(out MagicNumber);
|
||||||
|
reader.Read(out Version);
|
||||||
|
reader.Read(out byte type);
|
||||||
|
MessageType = (NetworkMessageType) type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkMessageBuffer : IDisposable {
|
||||||
|
NativeArray<byte> m_Data;
|
||||||
|
public NetworkBufferWriter Writer;
|
||||||
|
|
||||||
|
public NetworkMessageBuffer(Allocator allocator) {
|
||||||
|
m_Data = new NativeArray<byte>(RConsts.k_NetworkMessageMaxSize, allocator);
|
||||||
|
Writer = new NetworkBufferWriter(m_Data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
Writer.Dispose();
|
||||||
|
m_Data.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset() {
|
||||||
|
Writer.Position = 0;
|
||||||
|
|
||||||
|
if (m_Data.IsCreated) {
|
||||||
|
unsafe {
|
||||||
|
UnsafeUtility.MemClear(m_Data.GetUnsafePtr(), m_Data.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeSlice<byte> GetDataSlice() {
|
||||||
|
return m_Data.Slice(0, Writer.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteHeader(NetworkMessageType type, byte version) {
|
||||||
|
NetworkMessageBufferUtility.WriteHeader(Writer, type, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeArray<byte> GetDataCopy(Allocator allocator) {
|
||||||
|
NativeSlice<byte> dataSlice = GetDataSlice();
|
||||||
|
NativeArray<byte> dataCopy = new NativeArray<byte>(dataSlice.Length, allocator);
|
||||||
|
dataSlice.CopyTo(dataCopy);
|
||||||
|
return dataCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetMessageContentSize() {
|
||||||
|
Assert.IsTrue(Writer.Position >= NetworkMessageHeader.k_HeaderSize,
|
||||||
|
"Writer position must be greater than or equal to header size.");
|
||||||
|
return Writer.Position - NetworkMessageHeader.k_HeaderSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class NetworkMessageBufferUtility {
|
||||||
|
public static void WriteHeader(NetworkBufferWriter writer,
|
||||||
|
NetworkMessageType type,
|
||||||
|
byte version) {
|
||||||
|
NetworkMessageHeader header = new NetworkMessageHeader {
|
||||||
|
MagicNumber = RConsts.k_NetworkMessageMagic,
|
||||||
|
Version = version,
|
||||||
|
MessageType = type,
|
||||||
|
};
|
||||||
|
|
||||||
|
header.Serialize(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Engine/Code/Network/NetworkMessageBuffer.cs.meta
Normal file
3
Runtime/Engine/Code/Network/NetworkMessageBuffer.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e9d2fa991d31446db7fe1a4fef441a1d
|
||||||
|
timeCreated: 1753152667
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using RebootKit.Engine.Foundation;
|
|
||||||
using RebootKit.Engine.Simulation;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Collections.LowLevel.Unsafe;
|
|
||||||
using UnityEngine.Assertions;
|
|
||||||
using UnityEngine.Pool;
|
|
||||||
|
|
||||||
namespace RebootKit.Engine.Network {
|
|
||||||
struct NetworkPacketHeader : ISerializableEntity {
|
|
||||||
public int MagicNumber;
|
|
||||||
public ushort Version;
|
|
||||||
public ushort EntityCount;
|
|
||||||
|
|
||||||
public static int GetEntityCountOffset() {
|
|
||||||
return sizeof(int) + sizeof(ushort);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize(NetworkBufferWriter writer) {
|
|
||||||
writer.Write(MagicNumber);
|
|
||||||
writer.Write(Version);
|
|
||||||
writer.Write(EntityCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Deserialize(NetworkBufferReader reader) {
|
|
||||||
reader.Read(out MagicNumber);
|
|
||||||
reader.Read(out Version);
|
|
||||||
reader.Read(out EntityCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetMaxBytes() {
|
|
||||||
return sizeof(int) + sizeof(ushort) * 2; // MagicNumber, Version, EntityCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NetworkPacket : IDisposable {
|
|
||||||
public static readonly IObjectPool<NetworkPacket> Pool = new ObjectPool<NetworkPacket>(
|
|
||||||
() => {
|
|
||||||
NetworkPacket packet = new NetworkPacket();
|
|
||||||
packet.Data = default;
|
|
||||||
packet.Writer = default;
|
|
||||||
return packet;
|
|
||||||
},
|
|
||||||
packet => {
|
|
||||||
// Packet is initialized after being retrieved from the pool
|
|
||||||
},
|
|
||||||
packet => {
|
|
||||||
packet.Dispose();
|
|
||||||
},
|
|
||||||
packet => {
|
|
||||||
packet.Dispose();
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
16
|
|
||||||
);
|
|
||||||
|
|
||||||
public NativeArray<byte> Data;
|
|
||||||
public NetworkBufferWriter Writer;
|
|
||||||
|
|
||||||
public ushort EntityCount { get; private set; }
|
|
||||||
|
|
||||||
public void IncrementEntityCount() {
|
|
||||||
int originalPosition = Writer.Position;
|
|
||||||
|
|
||||||
EntityCount += 1;
|
|
||||||
|
|
||||||
Writer.Position = NetworkPacketHeader.GetEntityCountOffset(); // Reset position to write the entity count
|
|
||||||
Writer.Write(EntityCount);
|
|
||||||
Writer.Position = originalPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
Data.Dispose();
|
|
||||||
Writer.Dispose();
|
|
||||||
|
|
||||||
EntityCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NetworkDataType : byte {
|
|
||||||
None = 0x00,
|
|
||||||
ActorCoreState = 0x01,
|
|
||||||
ActorTransformSync = 0x02,
|
|
||||||
ActorState = 0x03,
|
|
||||||
ActorEvent = 0x04,
|
|
||||||
ActorCommand = 0x05,
|
|
||||||
SynchronizeActor = 0x07,
|
|
||||||
SpawnActor = 0x08,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NetworkDataHeader : ISerializableEntity {
|
|
||||||
public NetworkDataType Type;
|
|
||||||
public ulong ActorID;
|
|
||||||
public int DataSize;
|
|
||||||
|
|
||||||
public int GetMaxBytes() {
|
|
||||||
return sizeof(ulong) + sizeof(byte) + sizeof(int);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize(NetworkBufferWriter writer) {
|
|
||||||
writer.Write((byte) Type);
|
|
||||||
writer.Write(ActorID);
|
|
||||||
writer.Write(DataSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Deserialize(NetworkBufferReader reader) {
|
|
||||||
reader.Read(out byte typeByte);
|
|
||||||
Type = (NetworkDataType) typeByte;
|
|
||||||
reader.Read(out ActorID);
|
|
||||||
reader.Read(out DataSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NetworkPacketQueue : IDisposable {
|
|
||||||
static readonly Logger s_Logger = new Logger(nameof(NetworkPacketQueue));
|
|
||||||
|
|
||||||
readonly int m_PacketMaxSize;
|
|
||||||
readonly ushort m_Version;
|
|
||||||
|
|
||||||
internal readonly List<NetworkPacket> NetworkPackets = new List<NetworkPacket>();
|
|
||||||
|
|
||||||
public NetworkPacketQueue(int packetMaxSize, ushort version = 1) {
|
|
||||||
m_PacketMaxSize = packetMaxSize;
|
|
||||||
m_Version = version;
|
|
||||||
Assert.IsTrue(m_PacketMaxSize > 0, "Packet maximum size must be greater than zero.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
foreach (NetworkPacket packet in NetworkPackets) {
|
|
||||||
packet.Data.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPackets.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear() {
|
|
||||||
foreach (NetworkPacket packet in NetworkPackets) {
|
|
||||||
packet.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPackets.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteActorState(ulong actorID, IActorData entity) {
|
|
||||||
Assert.IsTrue(entity.GetMaxBytes() <= m_PacketMaxSize,
|
|
||||||
$"Entity size {entity.GetMaxBytes()} exceeds packet max size {m_PacketMaxSize}.");
|
|
||||||
|
|
||||||
NetworkDataHeader header = new NetworkDataHeader {
|
|
||||||
Type = NetworkDataType.ActorState,
|
|
||||||
ActorID = actorID,
|
|
||||||
DataSize = entity.GetMaxBytes()
|
|
||||||
};
|
|
||||||
|
|
||||||
int bytesToWrite = header.GetMaxBytes() + entity.GetMaxBytes();
|
|
||||||
|
|
||||||
NetworkPacket packet = GetPacketToWriteTo(bytesToWrite);
|
|
||||||
header.Serialize(packet.Writer);
|
|
||||||
entity.Serialize(packet.Writer);
|
|
||||||
packet.IncrementEntityCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteActorTransformState(ulong actorID, ActorTransformSyncData transformData) {
|
|
||||||
NetworkDataHeader header = new NetworkDataHeader {
|
|
||||||
Type = NetworkDataType.ActorTransformSync,
|
|
||||||
ActorID = actorID,
|
|
||||||
DataSize = transformData.GetMaxBytes()
|
|
||||||
};
|
|
||||||
|
|
||||||
int bytesToWrite = header.GetMaxBytes() + transformData.GetMaxBytes();
|
|
||||||
|
|
||||||
NetworkPacket packet = GetPacketToWriteTo(bytesToWrite);
|
|
||||||
header.Serialize(packet.Writer);
|
|
||||||
transformData.Serialize(packet.Writer);
|
|
||||||
packet.IncrementEntityCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteActorCoreState(ulong actorID, ActorCoreStateSnapshot coreState) {
|
|
||||||
NetworkDataHeader header = new NetworkDataHeader {
|
|
||||||
Type = NetworkDataType.ActorCoreState,
|
|
||||||
ActorID = actorID,
|
|
||||||
DataSize = coreState.GetMaxBytes()
|
|
||||||
};
|
|
||||||
|
|
||||||
int bytesToWrite = header.GetMaxBytes() + coreState.GetMaxBytes();
|
|
||||||
|
|
||||||
NetworkPacket packet = GetPacketToWriteTo(bytesToWrite);
|
|
||||||
header.Serialize(packet.Writer);
|
|
||||||
coreState.Serialize(packet.Writer);
|
|
||||||
packet.IncrementEntityCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteSpawnActor(FixedString64Bytes assetGUID,
|
|
||||||
ulong actorID,
|
|
||||||
ActorCoreStateSnapshot coreState,
|
|
||||||
IActorData actorData) {
|
|
||||||
NetworkDataHeader header = new NetworkDataHeader {
|
|
||||||
Type = NetworkDataType.SpawnActor,
|
|
||||||
ActorID = actorID,
|
|
||||||
DataSize = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
header.DataSize += sizeof(byte) * 64; // assetGUID
|
|
||||||
header.DataSize += coreState.GetMaxBytes();
|
|
||||||
header.DataSize += sizeof(ushort);
|
|
||||||
header.DataSize += actorData.GetMaxBytes();
|
|
||||||
|
|
||||||
NetworkPacket packet = GetPacketToWriteTo(header.GetMaxBytes() + header.DataSize);
|
|
||||||
header.Serialize(packet.Writer);
|
|
||||||
|
|
||||||
packet.Writer.Write(assetGUID);
|
|
||||||
coreState.Serialize(packet.Writer);
|
|
||||||
|
|
||||||
packet.Writer.Write((ushort) actorData.GetMaxBytes());
|
|
||||||
actorData.Serialize(packet.Writer);
|
|
||||||
|
|
||||||
packet.IncrementEntityCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteActorSynchronize(ulong actorID,
|
|
||||||
ActorCoreStateSnapshot coreState,
|
|
||||||
IActorData actorData) {
|
|
||||||
NetworkDataHeader header = new NetworkDataHeader {
|
|
||||||
Type = NetworkDataType.SynchronizeActor,
|
|
||||||
ActorID = actorID,
|
|
||||||
DataSize = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
header.DataSize += coreState.GetMaxBytes();
|
|
||||||
header.DataSize += sizeof(ushort);
|
|
||||||
header.DataSize += actorData.GetMaxBytes();
|
|
||||||
|
|
||||||
NetworkPacket packet = GetPacketToWriteTo(header.GetMaxBytes() + header.DataSize);
|
|
||||||
header.Serialize(packet.Writer);
|
|
||||||
|
|
||||||
coreState.Serialize(packet.Writer);
|
|
||||||
|
|
||||||
packet.Writer.Write((ushort) actorData.GetMaxBytes());
|
|
||||||
actorData.Serialize(packet.Writer);
|
|
||||||
|
|
||||||
packet.IncrementEntityCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteActorEvent(ActorEvent actorEvent) {
|
|
||||||
NetworkDataHeader header = new NetworkDataHeader {
|
|
||||||
Type = NetworkDataType.ActorEvent,
|
|
||||||
ActorID = actorEvent.ActorID,
|
|
||||||
DataSize = actorEvent.GetMaxBytes()
|
|
||||||
};
|
|
||||||
|
|
||||||
NetworkPacket packet = GetPacketToWriteTo(header.GetMaxBytes() + header.DataSize);
|
|
||||||
header.Serialize(packet.Writer);
|
|
||||||
actorEvent.Serialize(packet.Writer);
|
|
||||||
|
|
||||||
packet.IncrementEntityCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteActorCommand(ActorCommand actorCommand) {
|
|
||||||
NetworkDataHeader header = new NetworkDataHeader {
|
|
||||||
Type = NetworkDataType.ActorCommand,
|
|
||||||
ActorID = actorCommand.ActorID,
|
|
||||||
DataSize = actorCommand.GetMaxBytes()
|
|
||||||
};
|
|
||||||
|
|
||||||
NetworkPacket packet = GetPacketToWriteTo(header.GetMaxBytes() + header.DataSize);
|
|
||||||
header.Serialize(packet.Writer);
|
|
||||||
actorCommand.Serialize(packet.Writer);
|
|
||||||
|
|
||||||
packet.IncrementEntityCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPacket GetPacketToWriteTo(int bytesToWrite) {
|
|
||||||
foreach (NetworkPacket networkPacket in NetworkPackets) {
|
|
||||||
if (networkPacket.Writer.WillFit(bytesToWrite)) {
|
|
||||||
return networkPacket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.IsTrue(bytesToWrite < m_PacketMaxSize,
|
|
||||||
$"Packet size {bytesToWrite} exceeds maximum allowed size {m_PacketMaxSize}.");
|
|
||||||
|
|
||||||
NetworkPacket packet = NetworkPacket.Pool.Get();
|
|
||||||
packet.Data = new NativeArray<byte>(m_PacketMaxSize, Allocator.Persistent);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
void* ptr = packet.Data.GetUnsafePtr();
|
|
||||||
UnsafeUtility.MemClear(ptr, sizeof(byte) * packet.Data.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.Writer = new NetworkBufferWriter(packet.Data, 0);
|
|
||||||
|
|
||||||
NetworkPacketHeader header = new NetworkPacketHeader {
|
|
||||||
MagicNumber = RConsts.k_NetworkPacketMagicNumber,
|
|
||||||
Version = m_Version,
|
|
||||||
EntityCount = 0 // Will be updated later
|
|
||||||
};
|
|
||||||
|
|
||||||
header.Serialize(packet.Writer);
|
|
||||||
NetworkPackets.Add(packet);
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 8a122573c79c4b3e9ef3bc2da3b09faa
|
|
||||||
timeCreated: 1752855419
|
|
||||||
@@ -1,42 +1,39 @@
|
|||||||
using System;
|
using System.Threading;
|
||||||
using System.Threading;
|
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
using RebootKit.Engine.Foundation;
|
|
||||||
using RebootKit.Engine.Main;
|
using RebootKit.Engine.Main;
|
||||||
using RebootKit.Engine.Simulation;
|
using RebootKit.Engine.Simulation;
|
||||||
using Unity.Netcode;
|
using UnityEngine;
|
||||||
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||||
|
|
||||||
namespace RebootKit.Engine.Network {
|
namespace RebootKit.Engine.Network {
|
||||||
public abstract class NetworkPlayerController : NetworkBehaviour {
|
public abstract class NetworkPlayerController : MonoBehaviour {
|
||||||
static readonly Logger s_Logger = new Logger(nameof(NetworkPlayerController));
|
static readonly Logger s_Logger = new Logger(nameof(NetworkPlayerController));
|
||||||
|
|
||||||
ulong m_ActorIDToPossess;
|
ushort m_ActorIDToPossess;
|
||||||
|
CancellationTokenSource m_PossessionCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
public Actor PossessedActor { get; private set; }
|
public Actor PossessedActor { get; private set; }
|
||||||
|
|
||||||
public void PossessActor(Actor actor) {
|
internal void SetPossessedActor(ushort actorID) {
|
||||||
if (!IsServer) {
|
s_Logger.Info("Setting possessed actor to " + actorID);
|
||||||
s_Logger.Error("PossessActor can only be called on the server.");
|
|
||||||
return;
|
if (actorID == 0) {
|
||||||
}
|
m_ActorIDToPossess = 0;
|
||||||
|
|
||||||
if (actor == null) {
|
if (PossessedActor != null) {
|
||||||
s_Logger.Error("Cannot possess a null actor.");
|
PossessedActor = null;
|
||||||
return;
|
OnUnpossessActor(PossessedActor);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m_PossessionCancellationTokenSource.Cancel();
|
||||||
|
m_PossessionCancellationTokenSource.Dispose();
|
||||||
|
m_PossessionCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
PossessActorRpc(actor.ActorID, RpcTarget.Everyone);
|
WaitForActorToSpawnThenPossessAsync(actorID, m_PossessionCancellationTokenSource.Token).Forget();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Rpc(SendTo.SpecifiedInParams)]
|
async UniTask WaitForActorToSpawnThenPossessAsync(ushort actorID, CancellationToken cancellationToken) {
|
||||||
void PossessActorRpc(ulong actorID, RpcParams rpcParams) {
|
|
||||||
if (PossessedActor is not null) {
|
|
||||||
OnUnpossessActor(PossessedActor);
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForActorToSpawnThenPossessAsync(actorID, destroyCancellationToken).Forget();
|
|
||||||
}
|
|
||||||
|
|
||||||
async UniTask WaitForActorToSpawnThenPossessAsync(ulong actorID, CancellationToken cancellationToken) {
|
|
||||||
Actor actor = null;
|
Actor actor = null;
|
||||||
while (actor == null) {
|
while (actor == null) {
|
||||||
actor = RR.FindSpawnedActor(actorID);
|
actor = RR.FindSpawnedActor(actorID);
|
||||||
@@ -51,27 +48,6 @@ namespace RebootKit.Engine.Network {
|
|||||||
OnPossessActor(actor);
|
OnPossessActor(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnPossessActor() {
|
|
||||||
if (!IsServer) {
|
|
||||||
s_Logger.Error("UnPossessActor can only be called on the server.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PossessedActor == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UnPossessActorRpc(RpcTarget.Everyone);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Rpc(SendTo.SpecifiedInParams)]
|
|
||||||
void UnPossessActorRpc(RpcParams rpcParams) {
|
|
||||||
if (PossessedActor is not null) {
|
|
||||||
OnUnpossessActor(PossessedActor);
|
|
||||||
PossessedActor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnPossessActor(Actor actor) {
|
protected virtual void OnPossessActor(Actor actor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
|||||||
using Unity.Netcode;
|
|
||||||
|
|
||||||
namespace RebootKit.Engine.Network {
|
|
||||||
public abstract class NetworkWorldController : NetworkBehaviour {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: edd521a69a6f4e43b97ec258adf499a6
|
|
||||||
timeCreated: 1751377120
|
|
||||||
291
Runtime/Engine/Code/Network/UnityNetworkManager.cs
Normal file
291
Runtime/Engine/Code/Network/UnityNetworkManager.cs
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using R3;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
|
using Unity.Networking.Transport;
|
||||||
|
using UnityEngine;
|
||||||
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||||
|
|
||||||
|
namespace RebootKit.Engine.Network {
|
||||||
|
// @NOTE: This won't probably be suited for production use.
|
||||||
|
public class UnityNetworkManager : INetworkManager {
|
||||||
|
static readonly Logger s_Logger = new Logger(nameof(UnityNetworkManager));
|
||||||
|
|
||||||
|
const ulong k_ServerClientID = 0;
|
||||||
|
|
||||||
|
NetworkDriver m_Driver;
|
||||||
|
|
||||||
|
// @MARK: Server specific stuff
|
||||||
|
bool m_IsServer;
|
||||||
|
readonly Dictionary<ulong, NetworkConnection> m_ServerConnections = new Dictionary<ulong, NetworkConnection>();
|
||||||
|
ulong m_ClientIDCounter = 1;
|
||||||
|
|
||||||
|
bool m_IsClient;
|
||||||
|
NetworkConnection m_ClientConnection;
|
||||||
|
|
||||||
|
public INetworkManagerDelegate Delegate { get; set; }
|
||||||
|
|
||||||
|
public ulong LocalClientID { get; private set; }
|
||||||
|
|
||||||
|
public NetworkManagerStats Stats { get; } = new NetworkManagerStats();
|
||||||
|
|
||||||
|
public ushort Port = 7777;
|
||||||
|
|
||||||
|
public UnityNetworkManager() {
|
||||||
|
m_Driver = NetworkDriver.Create();
|
||||||
|
if (!m_Driver.IsCreated) {
|
||||||
|
s_Logger.Error("Failed to create network driver");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_IsServer = false;
|
||||||
|
m_IsClient = false;
|
||||||
|
|
||||||
|
LocalClientID = 0; // This should be set to a unique ID for the local client
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
if (m_Driver.IsCreated) {
|
||||||
|
m_Driver.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ServerConnections.Clear();
|
||||||
|
m_ClientConnection = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
float m_StatsTimer = 0.0f;
|
||||||
|
|
||||||
|
public void Tick() {
|
||||||
|
m_Driver.ScheduleUpdate().Complete();
|
||||||
|
|
||||||
|
if (IsServer()) {
|
||||||
|
ServerTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsClient()) {
|
||||||
|
ClientTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsServer() || IsClient()) {
|
||||||
|
m_StatsTimer -= Time.deltaTime;
|
||||||
|
if (m_StatsTimer <= 0.0f) {
|
||||||
|
m_StatsTimer = 1.0f;
|
||||||
|
|
||||||
|
Stats.ReliableBytesSentPerSecond = Stats.ReliableBytesSent;
|
||||||
|
Stats.UnreliableBytesSentPerSecond = Stats.UnreliableBytesSent;
|
||||||
|
Stats.BytesReceivedPerSecond = Stats.BytesReceived;
|
||||||
|
|
||||||
|
Stats.ReliableBytesSent = 0;
|
||||||
|
Stats.UnreliableBytesSent = 0;
|
||||||
|
Stats.BytesReceived = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerTick() {
|
||||||
|
using NativeList<ulong> clientIDsToRemove = new NativeList<ulong>(Allocator.Temp);
|
||||||
|
foreach ((ulong clientID, NetworkConnection connection) in m_ServerConnections) {
|
||||||
|
if (!connection.IsCreated) {
|
||||||
|
clientIDsToRemove.Add(clientID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ulong clientID in clientIDsToRemove) {
|
||||||
|
m_ServerConnections.Remove(clientID);
|
||||||
|
Delegate?.OnClientDisconnected(clientID);
|
||||||
|
s_Logger.Info($"Client {clientID} disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkConnection incomingConnection;
|
||||||
|
while ((incomingConnection = m_Driver.Accept()) != default) {
|
||||||
|
ulong newClientID = m_ClientIDCounter++;
|
||||||
|
m_ServerConnections.Add(newClientID, incomingConnection);
|
||||||
|
|
||||||
|
s_Logger.Info("Connection accepted: " + newClientID);
|
||||||
|
Delegate?.OnClientConnected(newClientID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @NOTE: Handle incoming messages from clients
|
||||||
|
clientIDsToRemove.Clear();
|
||||||
|
foreach ((ulong clientID, NetworkConnection connection) in m_ServerConnections) {
|
||||||
|
DataStreamReader stream;
|
||||||
|
NetworkEvent.Type cmd;
|
||||||
|
while ((cmd = m_Driver.PopEventForConnection(connection, out stream)) !=
|
||||||
|
NetworkEvent.Type.Empty) {
|
||||||
|
if (cmd == NetworkEvent.Type.Data) {
|
||||||
|
NativeArray<byte> data = new NativeArray<byte>(stream.Length, Allocator.Temp);
|
||||||
|
stream.ReadBytes(data);
|
||||||
|
|
||||||
|
Stats.BytesReceived += (ulong) stream.Length;
|
||||||
|
Delegate?.OnMessageReceived(clientID, data);
|
||||||
|
} else if (cmd == NetworkEvent.Type.Disconnect) {
|
||||||
|
clientIDsToRemove.Add(clientID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO: Code duplication
|
||||||
|
foreach (ulong clientID in clientIDsToRemove) {
|
||||||
|
m_ServerConnections.Remove(clientID);
|
||||||
|
Delegate?.OnClientDisconnected(clientID);
|
||||||
|
s_Logger.Info($"Client {clientID} disconnected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientTick() {
|
||||||
|
DataStreamReader stream;
|
||||||
|
NetworkEvent.Type cmd;
|
||||||
|
while ((cmd = m_ClientConnection.PopEvent(m_Driver, out stream)) != NetworkEvent.Type.Empty) {
|
||||||
|
if (cmd == NetworkEvent.Type.Data) {
|
||||||
|
NativeArray<byte> data = new NativeArray<byte>(stream.Length, Allocator.Temp);
|
||||||
|
stream.ReadBytes(data);
|
||||||
|
Stats.BytesReceived += (ulong) stream.Length;
|
||||||
|
Delegate?.OnMessageReceived(k_ServerClientID, data);
|
||||||
|
} else if (cmd == NetworkEvent.Type.Disconnect) {
|
||||||
|
m_IsClient = false;
|
||||||
|
m_ClientConnection = default;
|
||||||
|
Delegate?.OnClientDisconnected(LocalClientID);
|
||||||
|
s_Logger.Info("Disconnected from server");
|
||||||
|
} else if (cmd == NetworkEvent.Type.Connect) {
|
||||||
|
s_Logger.Info("Client connected");
|
||||||
|
Delegate?.OnClientConnected(LocalClientID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: INetworkManager
|
||||||
|
//
|
||||||
|
public bool IsServer() {
|
||||||
|
return m_IsServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool StartHost() {
|
||||||
|
if (IsServer()) {
|
||||||
|
s_Logger.Error("Server already started");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats.Reset();
|
||||||
|
|
||||||
|
NetworkEndpoint endpoint = NetworkEndpoint.AnyIpv4.WithPort(7777);
|
||||||
|
|
||||||
|
if (m_Driver.Bind(endpoint) != 0) {
|
||||||
|
s_Logger.Error($"Failed to bind to port {Port}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Driver.Listen();
|
||||||
|
|
||||||
|
m_ClientConnection = m_Driver.Connect(NetworkEndpoint.LoopbackIpv4.WithPort(Port));
|
||||||
|
|
||||||
|
if (!m_ClientConnection.IsCreated) {
|
||||||
|
s_Logger.Error($"Failed to create client connection on port {Port}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalClientID = k_ServerClientID;
|
||||||
|
m_ServerConnections.Add(LocalClientID, m_ClientConnection);
|
||||||
|
|
||||||
|
m_IsServer = true;
|
||||||
|
m_IsClient = true;
|
||||||
|
|
||||||
|
Delegate?.OnServerStarted();
|
||||||
|
Delegate?.OnClientConnected(LocalClientID);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopHost() { throw new NotImplementedException(); }
|
||||||
|
|
||||||
|
public bool IsClient() {
|
||||||
|
return m_IsClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool StartClient() {
|
||||||
|
if (IsClient()) {
|
||||||
|
s_Logger.Error("Client already started");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats.Reset();
|
||||||
|
|
||||||
|
NetworkEndpoint endpoint = NetworkEndpoint.LoopbackIpv4.WithPort(Port);
|
||||||
|
m_ClientConnection = m_Driver.Connect(endpoint);
|
||||||
|
|
||||||
|
if (!m_ClientConnection.IsCreated) {
|
||||||
|
s_Logger.Error($"Failed to connect to server on port {Port}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_IsClient = true;
|
||||||
|
Delegate?.OnClientStarted();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect() { throw new NotImplementedException(); }
|
||||||
|
|
||||||
|
public void Send(ulong clientID, NativeSlice<byte> data, SendMode mode) {
|
||||||
|
unsafe {
|
||||||
|
void* ptr = data.GetUnsafePtr();
|
||||||
|
Send(clientID, (byte*)ptr, data.Length, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Send(ulong clientID, byte* data, int length, SendMode mode) {
|
||||||
|
if (IsServer()) {
|
||||||
|
if (clientID == LocalClientID) {
|
||||||
|
NativeArray<byte> messageData = new NativeArray<byte>(length, Allocator.Temp);
|
||||||
|
UnsafeUtility.MemCpy(messageData.GetUnsafePtr(), data, length);
|
||||||
|
Delegate?.OnMessageReceived(clientID, messageData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_ServerConnections.TryGetValue(clientID, out NetworkConnection value)) {
|
||||||
|
if (!value.IsCreated) {
|
||||||
|
s_Logger.Error($"Client {clientID} connection is not created. Cannot send message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Driver.BeginSend(value, out DataStreamWriter writer);
|
||||||
|
writer.WriteBytesUnsafe(data, length);
|
||||||
|
m_Driver.EndSend(writer);
|
||||||
|
|
||||||
|
if (mode == SendMode.Reliable) {
|
||||||
|
Stats.ReliableBytesSent += (ulong) length;
|
||||||
|
} else if (mode == SendMode.Unreliable){
|
||||||
|
Stats.UnreliableBytesSent += (ulong) length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s_Logger.Error($"Client {clientID} not found. Cannot send message.");
|
||||||
|
}
|
||||||
|
} else if (IsClient()) {
|
||||||
|
if (clientID != k_ServerClientID) {
|
||||||
|
s_Logger.Error("Client ID mismatch. Cannot send message to another client from client context.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_ClientConnection.IsCreated) {
|
||||||
|
s_Logger.Error("Client connection is not created. Cannot send message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Driver.BeginSend(m_ClientConnection, out DataStreamWriter writer, length) == 0) {
|
||||||
|
writer.WriteBytesUnsafe(data, length);
|
||||||
|
|
||||||
|
if (m_Driver.EndSend(writer) < 0) {
|
||||||
|
s_Logger.Error("Failed to send message to server.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == SendMode.Reliable) {
|
||||||
|
Stats.ReliableBytesSent += (ulong) length;
|
||||||
|
} else if (mode == SendMode.Unreliable){
|
||||||
|
Stats.UnreliableBytesSent += (ulong) length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s_Logger.Error($"Failed to begin sending message to server with length {length}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Engine/Code/Network/UnityNetworkManager.cs.meta
Normal file
3
Runtime/Engine/Code/Network/UnityNetworkManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f2b23e503c2946ef9b2d631cef89facb
|
||||||
|
timeCreated: 1753326254
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
internal const string k_BuildFlagDebug = "RR_DEBUG";
|
internal const string k_BuildFlagDebug = "RR_DEBUG";
|
||||||
internal const string k_BuildFlagSteam = "RR_STEAM";
|
internal const string k_BuildFlagSteam = "RR_STEAM";
|
||||||
|
|
||||||
internal const int k_NetworkPacketMagicNumber = 0x52455245; // "RERE" in ASCII
|
internal const ushort k_NetworkMessageMagic = 0xBEBE;
|
||||||
|
internal const int k_NetworkMessageMaxSize = 1024;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using RebootKit.Engine.Foundation;
|
using RebootKit.Engine.Foundation;
|
||||||
@@ -6,8 +7,9 @@ using RebootKit.Engine.Main;
|
|||||||
using RebootKit.Engine.Network;
|
using RebootKit.Engine.Network;
|
||||||
using TriInspector;
|
using TriInspector;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Netcode;
|
using Unity.Mathematics;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||||
|
|
||||||
namespace RebootKit.Engine.Simulation {
|
namespace RebootKit.Engine.Simulation {
|
||||||
@@ -24,59 +26,54 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct ActorCommand : ISerializableEntity {
|
public struct ActorCommand : ISerializableEntity {
|
||||||
public ulong ActorID;
|
public ushort ActorID;
|
||||||
public ulong ClientID;
|
public byte CommandID;
|
||||||
public ushort CommandID;
|
|
||||||
public NativeArray<byte> Data;
|
public NativeArray<byte> Data;
|
||||||
|
|
||||||
public int GetMaxBytes() {
|
public int GetMaxBytes() {
|
||||||
return sizeof(ulong) + // ActorID
|
return sizeof(ushort) + // ActorID
|
||||||
sizeof(ulong) + // ClientID
|
sizeof(byte) + // CommandID
|
||||||
sizeof(ushort) + // CommandID
|
sizeof(byte) + // Data length
|
||||||
sizeof(ushort) + // Data length
|
sizeof(byte) * Data.Length; // Data
|
||||||
sizeof(byte) * Data.Length; // Data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Serialize(NetworkBufferWriter writer) {
|
public void Serialize(NetworkBufferWriter writer) {
|
||||||
writer.Write(ActorID);
|
writer.Write(ActorID);
|
||||||
writer.Write(ClientID);
|
|
||||||
writer.Write(CommandID);
|
writer.Write(CommandID);
|
||||||
writer.Write((ushort) Data.Length);
|
writer.Write((byte) Data.Length);
|
||||||
if (Data.IsCreated) {
|
if (Data.IsCreated) {
|
||||||
writer.Write(Data);
|
writer.Write(Data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deserialize(NetworkBufferReader reader) {
|
public void Deserialize(NetworkBufferReader reader) {
|
||||||
reader.Read(out ActorID);
|
reader.Read(out ActorID);
|
||||||
reader.Read(out ClientID);
|
|
||||||
reader.Read(out CommandID);
|
reader.Read(out CommandID);
|
||||||
reader.Read(out ushort dataLength);
|
reader.Read(out byte dataLength);
|
||||||
if (dataLength > 0) {
|
if (dataLength > 0) {
|
||||||
reader.Read(out Data, dataLength, Allocator.Temp);
|
reader.Read(out Data, dataLength, Allocator.Temp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @NOTE: ActorEvent is used to send events from the server to clients and only clients.
|
|
||||||
// Server should not receive ActorEvents.
|
|
||||||
public struct ActorEvent : ISerializableEntity {
|
public struct ActorEvent : ISerializableEntity {
|
||||||
public ulong ActorID;
|
public ushort ActorID;
|
||||||
public ushort EventID;
|
public byte EventID;
|
||||||
public NativeArray<byte> Data;
|
public NativeArray<byte> Data;
|
||||||
|
|
||||||
public int GetMaxBytes() {
|
public int GetMaxBytes() {
|
||||||
return sizeof(ulong) + // ActorID
|
return sizeof(ushort) + // ActorID
|
||||||
sizeof(ushort) + // EventID
|
sizeof(byte) + // EventID
|
||||||
sizeof(ushort) + // Data length
|
sizeof(byte) + // Data length
|
||||||
sizeof(byte) * Data.Length; // Data
|
sizeof(byte) * Data.Length; // Data
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Serialize(NetworkBufferWriter writer) {
|
public void Serialize(NetworkBufferWriter writer) {
|
||||||
writer.Write(ActorID);
|
writer.Write(ActorID);
|
||||||
writer.Write(EventID);
|
writer.Write(EventID);
|
||||||
|
Assert.IsTrue(Data.Length < byte.MaxValue, "Data of ActorEvent is too large to fit in a byte.");
|
||||||
|
|
||||||
writer.Write((ushort) Data.Length);
|
writer.Write((byte) Data.Length);
|
||||||
if (Data.IsCreated) {
|
if (Data.IsCreated) {
|
||||||
writer.Write(Data);
|
writer.Write(Data);
|
||||||
}
|
}
|
||||||
@@ -85,7 +82,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
public void Deserialize(NetworkBufferReader reader) {
|
public void Deserialize(NetworkBufferReader reader) {
|
||||||
reader.Read(out ActorID);
|
reader.Read(out ActorID);
|
||||||
reader.Read(out EventID);
|
reader.Read(out EventID);
|
||||||
reader.Read(out ushort dataLength);
|
reader.Read(out byte dataLength);
|
||||||
|
|
||||||
if (dataLength > 0) {
|
if (dataLength > 0) {
|
||||||
reader.Read(out Data, dataLength, Allocator.Temp);
|
reader.Read(out Data, dataLength, Allocator.Temp);
|
||||||
@@ -94,13 +91,14 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
enum ActorPhysicsFlags : byte {
|
enum ActorFlags : byte {
|
||||||
None = 0,
|
None = 0,
|
||||||
IsKinematic = 1 << 0,
|
Hidden = 1 << 0,
|
||||||
DisableColliders = 1 << 1,
|
DisableColliders = 1 << 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ActorCoreStateSnapshot : ISerializableEntity {
|
struct ActorCoreStateSnapshot : ISerializableEntity {
|
||||||
|
public ushort ActorID;
|
||||||
public DateTime Timestamp;
|
public DateTime Timestamp;
|
||||||
|
|
||||||
// @NOTE: Position, Rotation, and Scale are in local space.
|
// @NOTE: Position, Rotation, and Scale are in local space.
|
||||||
@@ -108,102 +106,65 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
public Quaternion Rotation;
|
public Quaternion Rotation;
|
||||||
public Vector3 Scale;
|
public Vector3 Scale;
|
||||||
|
|
||||||
public bool IsHidden;
|
public ActorFlags Flags;
|
||||||
public ActorPhysicsFlags Flags;
|
|
||||||
|
|
||||||
public ulong MasterActorID;
|
public ushort MasterActorID;
|
||||||
public FixedString32Bytes MasterSocketName;
|
public FixedString32Bytes MasterSocketName;
|
||||||
|
|
||||||
public void Serialize(NetworkBufferWriter writer) {
|
public void Serialize(NetworkBufferWriter writer) {
|
||||||
|
writer.Write(ActorID);
|
||||||
writer.Write(Timestamp.Ticks);
|
writer.Write(Timestamp.Ticks);
|
||||||
writer.Write(Position);
|
writer.Write(Position);
|
||||||
writer.Write(Rotation);
|
writer.Write(Rotation);
|
||||||
writer.Write(Scale);
|
writer.Write(Scale);
|
||||||
writer.Write(IsHidden);
|
|
||||||
writer.Write((byte) Flags);
|
writer.Write((byte) Flags);
|
||||||
writer.Write(MasterActorID);
|
writer.Write(MasterActorID);
|
||||||
writer.Write(MasterSocketName);
|
writer.Write(MasterSocketName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deserialize(NetworkBufferReader reader) {
|
public void Deserialize(NetworkBufferReader reader) {
|
||||||
|
reader.Read(out ActorID);
|
||||||
reader.Read(out long ticks);
|
reader.Read(out long ticks);
|
||||||
Timestamp = new DateTime(ticks, DateTimeKind.Utc);
|
Timestamp = new DateTime(ticks, DateTimeKind.Utc);
|
||||||
reader.Read(out Position);
|
reader.Read(out Position);
|
||||||
reader.Read(out Rotation);
|
reader.Read(out Rotation);
|
||||||
reader.Read(out Scale);
|
reader.Read(out Scale);
|
||||||
reader.Read(out IsHidden);
|
|
||||||
reader.Read(out byte flagsByte);
|
reader.Read(out byte flagsByte);
|
||||||
Flags = (ActorPhysicsFlags) flagsByte;
|
Flags = (ActorFlags) flagsByte;
|
||||||
reader.Read(out MasterActorID);
|
reader.Read(out MasterActorID);
|
||||||
reader.Read(out MasterSocketName);
|
reader.Read(out MasterSocketName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetMaxBytes() {
|
public int GetMaxBytes() {
|
||||||
return sizeof(long) + // Timestamp
|
return sizeof(ushort) + // ActorID
|
||||||
|
sizeof(long) + // Timestamp
|
||||||
sizeof(float) * 3 + // Position
|
sizeof(float) * 3 + // Position
|
||||||
sizeof(float) * 4 + // Rotation (Quaternion)
|
sizeof(float) * 4 + // Rotation (Quaternion)
|
||||||
sizeof(float) * 3 + // Scale
|
sizeof(float) * 3 + // Scale
|
||||||
sizeof(bool) + // IsHidden
|
|
||||||
sizeof(byte) + // Flags
|
sizeof(byte) + // Flags
|
||||||
sizeof(ulong) + // MasterActorID
|
sizeof(ushort) + // MasterActorID
|
||||||
sizeof(byte) * 32; // MasterSocketName
|
sizeof(byte) * 32; // MasterSocketName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Represents the synchronization mode for actor transforms (and rigidbody).
|
|
||||||
/// @TODO: Might be a good idea to keep client-side actors rigidbody as kinematic and simulate physics only on the server.
|
|
||||||
/// IMPORTANT:
|
|
||||||
/// - Position, Rotation, and Scale are in local space.
|
|
||||||
/// - Velocity and AngularVelocity are only used if UsingRigidbody is set.
|
|
||||||
/// - When Actor is mounted to another actor, sync won't happen.
|
|
||||||
///
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum ActorTransformSyncMode : byte {
|
public enum ActorTransformSyncMode : byte {
|
||||||
None = 0,
|
None = 0,
|
||||||
Position = 1 << 0,
|
Position = 1 << 0,
|
||||||
Rotation = 1 << 1,
|
Rotation = 1 << 1,
|
||||||
Scale = 1 << 2,
|
Scale = 1 << 2
|
||||||
// @NOTE: If this is set, Position and Rotation will be synced using Rigidbody's position and rotation.
|
|
||||||
UsingRigidbody = 1 << 3,
|
|
||||||
Velocity = 1 << 4, // @NOTE: Velocity is only used if UsingRigidbody is set.
|
|
||||||
AngularVelocity = 1 << 5 // @NOTE: AngularVelocity is only used if UsingRigidbody is set.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ActorTransformSyncData : ISerializableEntity {
|
public struct ActorTransformSyncData : ISerializableEntity {
|
||||||
|
public ushort ActorID;
|
||||||
public ActorTransformSyncMode SyncMode;
|
public ActorTransformSyncMode SyncMode;
|
||||||
|
|
||||||
public Vector3 Position;
|
public Vector3 Position;
|
||||||
public Quaternion Rotation;
|
public Vector3 Rotation;
|
||||||
public Vector3 Scale;
|
public Vector3 Scale;
|
||||||
public Vector3 Velocity;
|
|
||||||
public Vector3 AngularVelocity;
|
|
||||||
|
|
||||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
|
|
||||||
serializer.SerializeValue(ref SyncMode);
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
|
||||||
serializer.SerializeValue(ref Position);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
|
||||||
serializer.SerializeValue(ref Rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
|
||||||
serializer.SerializeValue(ref Scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
||||||
serializer.SerializeValue(ref Velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
||||||
serializer.SerializeValue(ref AngularVelocity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize(NetworkBufferWriter writer) {
|
public void Serialize(NetworkBufferWriter writer) {
|
||||||
|
writer.Write(ActorID);
|
||||||
writer.Write((byte) SyncMode);
|
writer.Write((byte) SyncMode);
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||||
@@ -211,23 +172,26 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||||
writer.Write(Rotation);
|
Rotation.x = Mathf.Repeat(Rotation.x, 360.0f);
|
||||||
|
Rotation.y = Mathf.Repeat(Rotation.y, 360.0f);
|
||||||
|
Rotation.z = Mathf.Repeat(Rotation.z, 360.0f);
|
||||||
|
|
||||||
|
ushort rotX = QuantizationUtility.FloatToUShort(Rotation.x, 0.0f, 360.0f);
|
||||||
|
ushort rotY = QuantizationUtility.FloatToUShort(Rotation.y, 0.0f, 360.0f);
|
||||||
|
ushort rotZ = QuantizationUtility.FloatToUShort(Rotation.z, 0.0f, 360.0f);
|
||||||
|
|
||||||
|
writer.Write(rotX);
|
||||||
|
writer.Write(rotY);
|
||||||
|
writer.Write(rotZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||||
writer.Write(Scale);
|
writer.Write(Scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
||||||
writer.Write(Velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
||||||
writer.Write(AngularVelocity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deserialize(NetworkBufferReader reader) {
|
public void Deserialize(NetworkBufferReader reader) {
|
||||||
|
reader.Read(out ActorID);
|
||||||
reader.Read(out byte syncModeByte);
|
reader.Read(out byte syncModeByte);
|
||||||
SyncMode = (ActorTransformSyncMode) syncModeByte;
|
SyncMode = (ActorTransformSyncMode) syncModeByte;
|
||||||
|
|
||||||
@@ -236,19 +200,17 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||||
reader.Read(out Rotation);
|
reader.Read(out ushort rotX);
|
||||||
|
reader.Read(out ushort rotY);
|
||||||
|
reader.Read(out ushort rotZ);
|
||||||
|
|
||||||
|
Rotation.x = QuantizationUtility.UShortToFloat(rotX, 0.0f, 360.0f);
|
||||||
|
Rotation.y = QuantizationUtility.UShortToFloat(rotY, 0.0f, 360.0f);
|
||||||
|
Rotation.z = QuantizationUtility.UShortToFloat(rotZ, 0.0f, 360.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||||
reader.Read(out Scale);
|
reader.Read(out Vector3 scale);
|
||||||
}
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
||||||
reader.Read(out Velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
||||||
reader.Read(out AngularVelocity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,21 +222,13 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||||
size += sizeof(float) * 4; // Quaternion
|
size += sizeof(ushort) * 3; // Vector3 - Euler angles
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||||
size += sizeof(float) * 3; // Vector3
|
size += sizeof(float) * 3; // Vector3
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
||||||
size += sizeof(float) * 3; // Vector3
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
||||||
size += sizeof(float) * 3; // Vector3
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +237,8 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
static readonly Logger s_ActorLogger = new Logger(nameof(Actor));
|
static readonly Logger s_ActorLogger = new Logger(nameof(Actor));
|
||||||
|
|
||||||
[field: SerializeField, TriInspector.ReadOnly] public string SourceActorPath { get; internal set; } = "";
|
[field: SerializeField, TriInspector.ReadOnly] public string SourceActorPath { get; internal set; } = "";
|
||||||
[field: SerializeField, Unity.Collections.ReadOnly] public ulong ActorID { get; internal set; }
|
[field: SerializeField, TriInspector.ReadOnly] public ulong ActorStaticID { get; internal set; }
|
||||||
|
[field: SerializeField, TriInspector.ReadOnly] public ushort ActorID { get; internal set; }
|
||||||
|
|
||||||
[NonSerialized] internal IActorData Data;
|
[NonSerialized] internal IActorData Data;
|
||||||
|
|
||||||
@@ -302,11 +257,22 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
[SerializeField] bool m_SetKinematicOnMount = true;
|
[SerializeField] bool m_SetKinematicOnMount = true;
|
||||||
[SerializeField] bool m_DisableCollidersOnMount = true;
|
[SerializeField] bool m_DisableCollidersOnMount = true;
|
||||||
|
|
||||||
internal ActorPhysicsFlags PhysicsFlagsBeforeMount = ActorPhysicsFlags.None;
|
internal ActorFlags Flags = ActorFlags.None;
|
||||||
internal ActorPhysicsFlags PhysicsFlags = ActorPhysicsFlags.None;
|
|
||||||
|
|
||||||
// @NOTE: Sync won't happen if actor is mounted to another actor.
|
// @NOTE: Sync won't happen if actor is mounted to another actor.
|
||||||
[SerializeField] internal ActorTransformSyncMode transformSyncMode = ActorTransformSyncMode.None;
|
[SerializeField] internal bool syncTransform = true;
|
||||||
|
[SerializeField] internal bool syncPosition = true;
|
||||||
|
[SerializeField] internal bool syncRotation = true;
|
||||||
|
[SerializeField] internal bool syncScale = false;
|
||||||
|
|
||||||
|
class ActorClientState {
|
||||||
|
public ulong LastSyncTick;
|
||||||
|
public Vector3 Position;
|
||||||
|
public Quaternion Rotation;
|
||||||
|
public Vector3 Scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly Dictionary<ulong, ActorClientState> m_ClientsStates = new Dictionary<ulong, ActorClientState>();
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public struct AttachmentSocket {
|
public struct AttachmentSocket {
|
||||||
@@ -334,8 +300,8 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
// @MARK: Unity callbacks
|
// @MARK: Unity callbacks
|
||||||
//
|
//
|
||||||
void OnValidate() {
|
void OnValidate() {
|
||||||
if (ActorID == 0) {
|
if (ActorStaticID == 0) {
|
||||||
ActorID = UniqueID.NewULongFromGuid();
|
ActorStaticID = UniqueID.NewULongFromGuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +313,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
|
|
||||||
// @MARK: Server side
|
// @MARK: Server side
|
||||||
public virtual void OnServerTick(float deltaTime) { }
|
public virtual void OnServerTick(float deltaTime) { }
|
||||||
protected virtual void OnActorCommandServer(ActorCommand actorCommand) { }
|
protected virtual void OnActorCommandServer(ulong senderID, ActorCommand actorCommand) { }
|
||||||
|
|
||||||
// Override this method to implement client-side logic
|
// Override this method to implement client-side logic
|
||||||
public virtual void OnClientTick(float deltaTime) { }
|
public virtual void OnClientTick(float deltaTime) { }
|
||||||
@@ -364,12 +330,18 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
|
|
||||||
bool shouldBeActive = !hidden;
|
bool shouldBeActive = !hidden;
|
||||||
if (gameObject.activeSelf == shouldBeActive) {
|
if (gameObject.activeSelf == shouldBeActive) {
|
||||||
s_ActorLogger
|
s_ActorLogger.Warning($"Actor {name} (ID: {ActorID}) is already in the desired visibility state: {shouldBeActive.ToString()}");
|
||||||
.Warning($"Actor {name} (ID: {ActorID}) is already in the desired visibility state: {shouldBeActive.ToString()}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gameObject.SetActive(shouldBeActive);
|
gameObject.SetActive(shouldBeActive);
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
Flags |= ActorFlags.Hidden;
|
||||||
|
} else {
|
||||||
|
Flags &= ~ActorFlags.Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
IsCoreStateDirty = true;
|
IsCoreStateDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,16 +355,15 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
MasterActor = actor;
|
MasterActor = actor;
|
||||||
MasterSocketName = new FixedString32Bytes(slotName);
|
MasterSocketName = new FixedString32Bytes(slotName);
|
||||||
|
|
||||||
PhysicsFlagsBeforeMount = PhysicsFlags;
|
|
||||||
if (m_SetKinematicOnMount) {
|
|
||||||
PhysicsFlags |= ActorPhysicsFlags.IsKinematic;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_DisableCollidersOnMount) {
|
if (m_DisableCollidersOnMount) {
|
||||||
PhysicsFlags |= ActorPhysicsFlags.DisableColliders;
|
Flags |= ActorFlags.DisableColliders;
|
||||||
|
UpdateLocalCollidersState(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_SetKinematicOnMount) {
|
||||||
|
actorRigidbody.isKinematic = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateLocalPhysicsState(PhysicsFlags);
|
|
||||||
UpdateMountedTransform();
|
UpdateMountedTransform();
|
||||||
IsCoreStateDirty = true;
|
IsCoreStateDirty = true;
|
||||||
}
|
}
|
||||||
@@ -413,51 +384,15 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
MasterSocketName = default;
|
MasterSocketName = default;
|
||||||
UpdateMountedTransform();
|
UpdateMountedTransform();
|
||||||
|
|
||||||
PhysicsFlags = PhysicsFlagsBeforeMount;
|
if (m_DisableCollidersOnMount) {
|
||||||
UpdateLocalPhysicsState(PhysicsFlags);
|
UpdateLocalCollidersState(true);
|
||||||
|
Flags &= ~ActorFlags.DisableColliders;
|
||||||
IsCoreStateDirty = true;
|
}
|
||||||
}
|
|
||||||
|
if (m_SetKinematicOnMount) {
|
||||||
public void SetCollidersEnabled(bool enableColliders) {
|
actorRigidbody.isKinematic = false;
|
||||||
if (!RR.IsServer()) {
|
|
||||||
s_ActorLogger.Error($"Only the server can enable/disable colliders. Actor: {name} (ID: {ActorID})");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actorRigidbody is null) {
|
|
||||||
s_ActorLogger.Error($"Actor {name} (ID: {ActorID}) has no Rigidbody to set colliders on.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableColliders) {
|
|
||||||
PhysicsFlags &= ~ActorPhysicsFlags.DisableColliders;
|
|
||||||
} else {
|
|
||||||
PhysicsFlags |= ActorPhysicsFlags.DisableColliders;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateLocalCollidersState(enableColliders);
|
|
||||||
IsCoreStateDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetKinematic(bool isKinematic) {
|
|
||||||
if (!RR.IsServer()) {
|
|
||||||
s_ActorLogger.Error($"Only the server can set kinematic state. Actor: {name} (ID: {ActorID})");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actorRigidbody is null) {
|
|
||||||
s_ActorLogger.Error($"Actor {name} (ID: {ActorID}) has no Rigidbody to set kinematic state on.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isKinematic) {
|
|
||||||
PhysicsFlags |= ActorPhysicsFlags.IsKinematic;
|
|
||||||
} else {
|
|
||||||
PhysicsFlags &= ~ActorPhysicsFlags.IsKinematic;
|
|
||||||
}
|
|
||||||
|
|
||||||
actorRigidbody.isKinematic = isKinematic;
|
|
||||||
IsCoreStateDirty = true;
|
IsCoreStateDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,14 +403,14 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
return !gameObject.activeSelf;
|
return !gameObject.activeSelf;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SendActorCommand<TCmdData>(ushort commandID, ref TCmdData commandData)
|
protected void SendActorCommand<TCmdData>(byte commandID, ref TCmdData commandData)
|
||||||
where TCmdData : struct, ISerializableEntity {
|
where TCmdData : struct, ISerializableEntity {
|
||||||
NativeArray<byte> data = DataSerializationUtils.Serialize(commandData);
|
NativeArray<byte> data = DataSerializationUtils.Serialize(commandData);
|
||||||
|
|
||||||
SendActorCommand(commandID, data);
|
SendActorCommand(commandID, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SendActorCommand(ushort commandID, NativeArray<byte> data = default) {
|
protected void SendActorCommand(byte commandID, NativeArray<byte> data = default) {
|
||||||
if (Manager is null) {
|
if (Manager is null) {
|
||||||
s_ActorLogger.Error($"Cannot send command because Manager is null for actor {name} (ID: {ActorID})");
|
s_ActorLogger.Error($"Cannot send command because Manager is null for actor {name} (ID: {ActorID})");
|
||||||
return;
|
return;
|
||||||
@@ -483,7 +418,6 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
|
|
||||||
ActorCommand command = new ActorCommand {
|
ActorCommand command = new ActorCommand {
|
||||||
ActorID = ActorID,
|
ActorID = ActorID,
|
||||||
ClientID = NetworkManager.Singleton.LocalClientId,
|
|
||||||
CommandID = commandID,
|
CommandID = commandID,
|
||||||
Data = data
|
Data = data
|
||||||
};
|
};
|
||||||
@@ -491,19 +425,19 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
Manager.SendActorCommand(command);
|
Manager.SendActorCommand(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SendActorEvent<TEventData>(ushort eventID, ref TEventData eventData)
|
protected void SendActorEvent<TEventData>(byte eventID, ref TEventData eventData)
|
||||||
where TEventData : struct, ISerializableEntity {
|
where TEventData : struct, ISerializableEntity {
|
||||||
NativeArray<byte> data = DataSerializationUtils.Serialize(eventData);
|
NativeArray<byte> data = DataSerializationUtils.Serialize(eventData);
|
||||||
SendActorEvent(eventID, data);
|
SendActorEvent(eventID, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SendActorEvent(ushort eventID, NativeArray<byte> data = default) {
|
protected void SendActorEvent(byte eventID, NativeArray<byte> data = default) {
|
||||||
if (!RR.IsServer()) {
|
if (!RR.IsServer()) {
|
||||||
s_ActorLogger.Error($"Only the server can send actor events. Actor: {name} (ID: {ActorID})");
|
s_ActorLogger.Error($"Only the server can send actor events. Actor: {name} (ID: {ActorID})");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Manager is null) {
|
if (Manager == null) {
|
||||||
s_ActorLogger.Error($"Cannot send event because Manager is null for actor {name} (ID: {ActorID})");
|
s_ActorLogger.Error($"Cannot send event because Manager is null for actor {name} (ID: {ActorID})");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -540,17 +474,22 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
//
|
//
|
||||||
// @MARK: Internal
|
// @MARK: Internal
|
||||||
//
|
//
|
||||||
|
internal void InitializeOnClient() {
|
||||||
|
if (actorRigidbody != null) {
|
||||||
|
actorRigidbody.isKinematic = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal ActorCoreStateSnapshot GetCoreStateSnapshot() {
|
internal ActorCoreStateSnapshot GetCoreStateSnapshot() {
|
||||||
ActorCoreStateSnapshot snapshot = new ActorCoreStateSnapshot();
|
ActorCoreStateSnapshot snapshot = new ActorCoreStateSnapshot();
|
||||||
|
snapshot.ActorID = ActorID;
|
||||||
snapshot.Timestamp = DateTime.UtcNow;
|
snapshot.Timestamp = DateTime.UtcNow;
|
||||||
snapshot.Position = transform.localPosition;
|
snapshot.Position = transform.localPosition;
|
||||||
snapshot.Rotation = transform.localRotation;
|
snapshot.Rotation = transform.localRotation;
|
||||||
snapshot.Scale = transform.localScale;
|
snapshot.Scale = transform.localScale;
|
||||||
|
snapshot.Flags = Flags;
|
||||||
|
|
||||||
snapshot.IsHidden = !gameObject.activeSelf;
|
snapshot.MasterActorID = MasterActor != null ? MasterActor.ActorID : (ushort)0;
|
||||||
snapshot.Flags = PhysicsFlags;
|
|
||||||
|
|
||||||
snapshot.MasterActorID = MasterActor != null ? MasterActor.ActorID : 0;
|
|
||||||
if (snapshot.MasterActorID != 0) {
|
if (snapshot.MasterActorID != 0) {
|
||||||
snapshot.MasterSocketName = MasterSocketName;
|
snapshot.MasterSocketName = MasterSocketName;
|
||||||
} else {
|
} else {
|
||||||
@@ -567,7 +506,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LastCoreStateSyncTime = snapshot.Timestamp;
|
LastCoreStateSyncTime = snapshot.Timestamp;
|
||||||
PhysicsFlags = snapshot.Flags;
|
Flags = snapshot.Flags;
|
||||||
|
|
||||||
if (snapshot.MasterActorID != 0) {
|
if (snapshot.MasterActorID != 0) {
|
||||||
MasterActor = RR.FindSpawnedActor(snapshot.MasterActorID);
|
MasterActor = RR.FindSpawnedActor(snapshot.MasterActorID);
|
||||||
@@ -583,69 +522,78 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
transform.localScale = snapshot.Scale;
|
transform.localScale = snapshot.Scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.IsHidden) {
|
if ((snapshot.Flags & ActorFlags.Hidden) != 0) {
|
||||||
gameObject.SetActive(false);
|
gameObject.SetActive(false);
|
||||||
} else {
|
} else {
|
||||||
gameObject.SetActive(true);
|
gameObject.SetActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateLocalPhysicsState(PhysicsFlags);
|
bool enableColliders = (Flags & ActorFlags.DisableColliders) == 0;
|
||||||
|
UpdateLocalCollidersState(enableColliders);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ActorTransformSyncData GetTransformSyncData() {
|
ActorClientState GetActorClientState(ulong clientID) {
|
||||||
ActorTransformSyncData data = new ActorTransformSyncData {
|
if (m_ClientsStates.TryGetValue(clientID, out ActorClientState clientState)) {
|
||||||
SyncMode = transformSyncMode
|
return clientState;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientState = new ActorClientState {
|
||||||
|
LastSyncTick = 0,
|
||||||
|
Position = transform.localPosition,
|
||||||
|
Rotation = transform.localRotation,
|
||||||
|
Scale = transform.localScale
|
||||||
};
|
};
|
||||||
|
m_ClientsStates[clientID] = clientState;
|
||||||
|
|
||||||
bool useRigidbody = (data.SyncMode & ActorTransformSyncMode.UsingRigidbody) != 0;
|
return clientState;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ulong GetLastSyncTick(ulong clientID) {
|
||||||
|
ActorClientState actorClientState = GetActorClientState(clientID);
|
||||||
|
return actorClientState.LastSyncTick;
|
||||||
|
}
|
||||||
|
|
||||||
if (useRigidbody && actorRigidbody == null) {
|
internal void UpdateClientState(ulong clientID, ulong serverTick) {
|
||||||
s_ActorLogger
|
ActorClientState actorClientState = GetActorClientState(clientID);
|
||||||
.Error($"Actor {name} (ID: {ActorID.ToString()}) has no Rigidbody to sync transform. Ignoring transform sync.");
|
actorClientState.LastSyncTick = serverTick;
|
||||||
data.SyncMode = ActorTransformSyncMode.None;
|
actorClientState.Position = transform.localPosition;
|
||||||
|
actorClientState.Rotation = transform.localRotation;
|
||||||
|
actorClientState.Scale = transform.localScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ActorTransformSyncData GetTransformSyncDataForClient(ulong clientID) {
|
||||||
|
ActorTransformSyncData data = new ActorTransformSyncData {
|
||||||
|
ActorID = ActorID,
|
||||||
|
SyncMode = ActorTransformSyncMode.None
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!syncTransform || MasterActor != null) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.Position = transform.localPosition;
|
||||||
|
data.Rotation = transform.localRotation.eulerAngles;
|
||||||
|
data.Scale = transform.localScale;
|
||||||
|
|
||||||
if ((data.SyncMode & ActorTransformSyncMode.Position) != 0) {
|
ActorClientState actorClientState = GetActorClientState(clientID);
|
||||||
if (useRigidbody) {
|
|
||||||
data.Position = actorRigidbody.position;
|
if (syncPosition && math.distancesq(actorClientState.Position, transform.localPosition) > 0.01f) {
|
||||||
} else {
|
data.SyncMode |= ActorTransformSyncMode.Position;
|
||||||
data.Position = transform.localPosition;
|
}
|
||||||
}
|
|
||||||
|
if (syncRotation && Quaternion.Angle(actorClientState.Rotation, transform.localRotation) > 0.01f) {
|
||||||
|
data.SyncMode |= ActorTransformSyncMode.Rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((data.SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
if (syncScale && math.distancesq(actorClientState.Scale, transform.localScale) > 0.01f) {
|
||||||
if (useRigidbody) {
|
data.SyncMode |= ActorTransformSyncMode.Scale;
|
||||||
data.Rotation = actorRigidbody.rotation;
|
|
||||||
} else {
|
|
||||||
data.Rotation = transform.localRotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((data.SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
|
||||||
data.Scale = transform.localScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useRigidbody && actorRigidbody != null) {
|
|
||||||
if ((data.SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
||||||
data.Velocity = actorRigidbody.linearVelocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((data.SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
||||||
data.AngularVelocity = actorRigidbody.angularVelocity;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RestoreTransformState(ActorTransformSyncData data) {
|
internal void RestoreTransformState(ActorTransformSyncData data) {
|
||||||
bool useRigidbody = (data.SyncMode & ActorTransformSyncMode.UsingRigidbody) != 0;
|
bool useRigidbody = actorRigidbody != null;
|
||||||
if (useRigidbody && actorRigidbody == null) {
|
|
||||||
s_ActorLogger
|
|
||||||
.Error($"Actor {name} (ID: {ActorID.ToString()}) has no Rigidbody to restore transform state. Ignoring transform sync.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((data.SyncMode & ActorTransformSyncMode.Position) != 0) {
|
if ((data.SyncMode & ActorTransformSyncMode.Position) != 0) {
|
||||||
if (useRigidbody) {
|
if (useRigidbody) {
|
||||||
@@ -657,38 +605,15 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
|
|
||||||
if ((data.SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
if ((data.SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
||||||
if (useRigidbody) {
|
if (useRigidbody) {
|
||||||
actorRigidbody.rotation = data.Rotation;
|
actorRigidbody.rotation = Quaternion.Euler(data.Rotation);
|
||||||
} else {
|
} else {
|
||||||
transform.localRotation = data.Rotation;
|
transform.localRotation = Quaternion.Euler(data.Rotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((data.SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
if ((data.SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
||||||
transform.localScale = data.Scale;
|
transform.localScale = data.Scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useRigidbody && (data.SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
||||||
actorRigidbody.linearVelocity = data.Velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useRigidbody && (data.SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
||||||
actorRigidbody.angularVelocity = data.AngularVelocity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateLocalPhysicsState(ActorPhysicsFlags flags) {
|
|
||||||
if (actorRigidbody == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((flags & ActorPhysicsFlags.IsKinematic) != 0) {
|
|
||||||
actorRigidbody.isKinematic = true;
|
|
||||||
} else {
|
|
||||||
actorRigidbody.isKinematic = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool enableColliders = (flags & ActorPhysicsFlags.DisableColliders) == 0;
|
|
||||||
UpdateLocalCollidersState(enableColliders);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateLocalCollidersState(bool enable) {
|
void UpdateLocalCollidersState(bool enable) {
|
||||||
@@ -720,7 +645,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
return CreateActorData();
|
return CreateActorData();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void HandleActorCommand(ActorCommand actorCommand) {
|
internal void HandleActorCommand(ulong senderID, ActorCommand actorCommand) {
|
||||||
if (!RR.IsServer()) {
|
if (!RR.IsServer()) {
|
||||||
s_ActorLogger.Error($"Only the server can handle actor commands. Actor: {name} (ID: {ActorID})");
|
s_ActorLogger.Error($"Only the server can handle actor commands. Actor: {name} (ID: {ActorID})");
|
||||||
return;
|
return;
|
||||||
@@ -737,7 +662,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnActorCommandServer(actorCommand);
|
OnActorCommandServer(senderID, actorCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void HandleActorEvent(ActorEvent actorEvent) {
|
internal void HandleActorEvent(ActorEvent actorEvent) {
|
||||||
|
|||||||
@@ -1,52 +1,51 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
|
||||||
using RebootKit.Engine.Extensions;
|
using RebootKit.Engine.Extensions;
|
||||||
using RebootKit.Engine.Foundation;
|
using RebootKit.Engine.Foundation;
|
||||||
using RebootKit.Engine.Main;
|
using RebootKit.Engine.Main;
|
||||||
using RebootKit.Engine.Network;
|
using RebootKit.Engine.Network;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Netcode;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.AddressableAssets;
|
using UnityEngine.AddressableAssets;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace RebootKit.Engine.Simulation {
|
namespace RebootKit.Engine.Simulation {
|
||||||
// @TODO:
|
// @TODO:
|
||||||
// - Actors States might be packed into chunks to reduce the number of RPCs sent.
|
// - Actors States might be packed into chunks to reduce the number of RPCs sent.
|
||||||
// - Release addressables when they are no longer needed.
|
// - Release addressables when they are no longer needed.
|
||||||
public class ActorsManager : NetworkBehaviour {
|
public class ActorsManager : IDisposable {
|
||||||
static readonly Logger s_Logger = new Logger(nameof(ActorsManager));
|
static readonly Logger s_Logger = new Logger(nameof(ActorsManager));
|
||||||
|
|
||||||
|
readonly NetworkSystem m_Network;
|
||||||
|
|
||||||
readonly List<Actor> m_InSceneActors = new List<Actor>();
|
readonly List<Actor> m_InSceneActors = new List<Actor>();
|
||||||
readonly List<Actor> m_SpawnedActors = new List<Actor>();
|
readonly List<Actor> m_SpawnedActors = new List<Actor>();
|
||||||
|
|
||||||
public int InSceneActorsCount { get { return m_InSceneActors.Count; } }
|
ushort m_ActorIDCounter = 0;
|
||||||
public int SpawnedActorsCount { get { return m_SpawnedActors.Count; } }
|
|
||||||
public int TotalActorsCount { get { return m_InSceneActors.Count + m_SpawnedActors.Count; } }
|
|
||||||
|
|
||||||
//
|
public ushort InSceneActorsCount { get { return (ushort) m_InSceneActors.Count; } }
|
||||||
// @MARK: NetworkBehaviour callbacks
|
public ushort SpawnedActorsCount { get { return (ushort) m_SpawnedActors.Count; } }
|
||||||
//
|
public int TotalActorsCount { get { return InSceneActorsCount + SpawnedActorsCount; } }
|
||||||
public override void OnNetworkSpawn() {
|
|
||||||
base.OnNetworkSpawn();
|
public ActorsManager(NetworkSystem networkSystem) {
|
||||||
|
m_Network = networkSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNetworkDespawn() {
|
public void Dispose() { }
|
||||||
base.OnNetworkDespawn();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// @MARK: Unity callbacks
|
// @MARK: Update
|
||||||
//
|
//
|
||||||
void Update() {
|
public void Tick(float deltaTime) {
|
||||||
foreach (Actor actor in m_InSceneActors) {
|
foreach (Actor actor in m_InSceneActors) {
|
||||||
actor.OnClientTick(Time.deltaTime);
|
actor.OnClientTick(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Actor actor in m_SpawnedActors) {
|
foreach (Actor actor in m_SpawnedActors) {
|
||||||
actor.OnClientTick(Time.deltaTime);
|
actor.OnClientTick(deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +53,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
// @MARK: Server-side logic
|
// @MARK: Server-side logic
|
||||||
//
|
//
|
||||||
public void ServerTick(float dt) {
|
public void ServerTick(float dt) {
|
||||||
if (!IsServer) {
|
if (!RR.IsServer()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,23 +69,50 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
actor.IsDataDirty = false;
|
actor.IsDataDirty = false;
|
||||||
|
|
||||||
if (actor.Data.GetMaxBytes() > 0) {
|
if (actor.Data.GetMaxBytes() > 0) {
|
||||||
RR.NetworkSystemInstance.WriteActorState(NetworkPacketTarget.AllClients(), actor.ActorID, actor.Data);
|
foreach (NetworkClientState client in m_Network.Clients.Values) {
|
||||||
|
if (client.IsServer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Network.WriteActorState(client.ClientID, actor.ActorID, actor.Data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor.IsCoreStateDirty) {
|
if (actor.IsCoreStateDirty) {
|
||||||
actor.IsCoreStateDirty = false;
|
actor.IsCoreStateDirty = false;
|
||||||
|
|
||||||
RR.NetworkSystemInstance.WriteActorCoreState(NetworkPacketTarget.AllClients(),
|
foreach (NetworkClientState client in m_Network.Clients.Values) {
|
||||||
actor.ActorID,
|
if (client.IsServer) {
|
||||||
actor.GetCoreStateSnapshot());
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Network.WriteActorCoreState(client.ClientID, actor.GetCoreStateSnapshot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (NetworkClientState client in m_Network.Clients.Values) {
|
||||||
|
if (client.IsServer) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor.transformSyncMode != ActorTransformSyncMode.None && actor.MasterActor == null) {
|
foreach (Actor actor in actors.OrderBy(t => t.GetLastSyncTick(client.ClientID))) {
|
||||||
ActorTransformSyncData syncData = actor.GetTransformSyncData();
|
if (!actor.syncTransform || actor.MasterActor != null) {
|
||||||
RR.NetworkSystemInstance.WriteActorTransformState(NetworkPacketTarget.AllClients(),
|
continue;
|
||||||
actor.ActorID,
|
}
|
||||||
syncData);
|
|
||||||
|
ActorTransformSyncData syncData = actor.GetTransformSyncDataForClient(client.ClientID);
|
||||||
|
if (syncData.SyncMode == ActorTransformSyncMode.None) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Network.WriteActorTransformState(client.ClientID, syncData)) {
|
||||||
|
actor.UpdateClientState(client.ClientID, RR.Network.TickCount);
|
||||||
|
} else {
|
||||||
|
// @NOTE: We ran out of space in the packet
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,7 +121,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
// @MARK: Server API
|
// @MARK: Server API
|
||||||
//
|
//
|
||||||
public Actor SpawnActor(AssetReferenceGameObject assetReference, Vector3 position, Quaternion rotation) {
|
public Actor SpawnActor(AssetReferenceGameObject assetReference, Vector3 position, Quaternion rotation) {
|
||||||
if (!IsServer) {
|
if (!RR.IsServer()) {
|
||||||
s_Logger.Error("Only the server can spawn actors.");
|
s_Logger.Error("Only the server can spawn actors.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -105,13 +131,16 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong actorID = UniqueID.NewULongFromGuid();
|
if (!TryGenerateNextActorID(out ushort actorID)) {
|
||||||
|
s_Logger.Error("Cannot spawn actor: Failed to generate next actor ID.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
GameObject actorObject = assetReference.InstantiateAsync(position, rotation).WaitForCompletion();
|
GameObject actorObject = assetReference.InstantiateAsync(position, rotation).WaitForCompletion();
|
||||||
Actor actor = actorObject.GetComponent<Actor>();
|
Actor actor = actorObject.GetComponent<Actor>();
|
||||||
if (actor is null) {
|
if (actor is null) {
|
||||||
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
||||||
Destroy(actorObject);
|
Object.Destroy(actorObject);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,36 +148,44 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
actor.SourceActorPath = assetReference.AssetGUID;
|
actor.SourceActorPath = assetReference.AssetGUID;
|
||||||
actor.ActorID = actorID;
|
actor.ActorID = actorID;
|
||||||
actor.Data = actor.InternalCreateActorData();
|
actor.Data = actor.InternalCreateActorData();
|
||||||
|
|
||||||
m_SpawnedActors.Add(actor);
|
m_SpawnedActors.Add(actor);
|
||||||
|
|
||||||
RR.NetworkSystemInstance.WriteSpawnActor(NetworkPacketTarget.AllClients(),
|
foreach (NetworkClientState client in m_Network.Clients.Values) {
|
||||||
assetReference.AssetGUID,
|
if (client.IsServer) {
|
||||||
actor.ActorID,
|
continue;
|
||||||
actor.GetCoreStateSnapshot(),
|
}
|
||||||
actor.Data);
|
|
||||||
|
m_Network.SendSpawnActor(client.ClientID,
|
||||||
|
assetReference.AssetGUID,
|
||||||
|
actor.GetCoreStateSnapshot(),
|
||||||
|
actor.Data);
|
||||||
|
}
|
||||||
|
|
||||||
return actor;
|
return actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CleanUp() {
|
bool TryGenerateNextActorID(out ushort actorID) {
|
||||||
if (IsServer) {
|
m_ActorIDCounter += 1;
|
||||||
CleanUpRpc();
|
actorID = m_ActorIDCounter;
|
||||||
}
|
return true;
|
||||||
|
|
||||||
m_InSceneActors.Clear();
|
|
||||||
|
|
||||||
foreach (Actor actor in m_SpawnedActors) {
|
|
||||||
if (actor.OrNull() != null) {
|
|
||||||
Destroy(actor.gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_SpawnedActors.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Rpc(SendTo.NotMe)]
|
internal void AssignActorsIDs() {
|
||||||
void CleanUpRpc() {
|
if (!RR.IsServer()) {
|
||||||
CleanUp();
|
s_Logger.Info("Only the server can assign actors IDs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ActorIDCounter = 0;
|
||||||
|
|
||||||
|
foreach (Actor actor in m_InSceneActors) {
|
||||||
|
if (!TryGenerateNextActorID(out ushort actorID)) {
|
||||||
|
s_Logger.Error("Failed to generate actor ID. Probably reached the limit of 65535 actors.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.ActorID = actorID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -164,7 +201,17 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
m_InSceneActors.Add(actor);
|
m_InSceneActors.Add(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Actor FindActorByID(ulong actorID) {
|
Actor FindInSceneActorWithStaticID(ulong staticID) {
|
||||||
|
foreach (Actor actor in m_InSceneActors) {
|
||||||
|
if (actor.ActorStaticID == staticID) {
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Actor FindActorByID(ushort actorID) {
|
||||||
foreach (Actor actor in m_InSceneActors) {
|
foreach (Actor actor in m_InSceneActors) {
|
||||||
if (actor.ActorID == actorID) {
|
if (actor.ActorID == actorID) {
|
||||||
return actor;
|
return actor;
|
||||||
@@ -180,186 +227,112 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
public void CleanUp() {
|
||||||
// @MARK: Initial synchronization
|
m_InSceneActors.Clear();
|
||||||
//
|
|
||||||
internal void InitializeActorsForClient(ulong clientID) {
|
|
||||||
if (RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
|
||||||
clientState.SyncState = NetworkClientSyncState.PreparingForActorsSync;
|
|
||||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
|
||||||
PrepareClientForActorsSyncRpc(RpcTarget.Single(clientState.ClientID, RpcTargetUse.Temp));
|
|
||||||
} else {
|
|
||||||
s_Logger.Error($"Client state for {clientID} not found. Cannot synchronize actors.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Rpc(SendTo.SpecifiedInParams, Delivery = RpcDelivery.Reliable)]
|
foreach (Actor actor in m_SpawnedActors) {
|
||||||
void PrepareClientForActorsSyncRpc(RpcParams rpcParams) {
|
if (actor.OrNull() != null) {
|
||||||
foreach (Actor spawnedActor in m_SpawnedActors) {
|
Object.Destroy(actor.gameObject);
|
||||||
Destroy(spawnedActor.gameObject);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_SpawnedActors.Clear();
|
m_SpawnedActors.Clear();
|
||||||
|
|
||||||
ClientIsReadyForActorsSyncRpc();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Rpc(SendTo.Server)]
|
|
||||||
void ClientIsReadyForActorsSyncRpc(RpcParams rpcParams = default) {
|
|
||||||
ulong clientID = rpcParams.Receive.SenderClientId;
|
|
||||||
if (!RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
|
||||||
s_Logger.Error($"Client state for {clientID} not found. Cannot mark client as ready for actors sync.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clientState.SyncState = NetworkClientSyncState.SyncingActors;
|
|
||||||
clientState.ActorsSyncPacketsLeft = m_InSceneActors.Count;
|
|
||||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
|
||||||
|
|
||||||
s_Logger.Info($"Starting actor synchronization for client {clientID}.\n" +
|
|
||||||
$"InScene Actors to sync: {m_InSceneActors.Count}\n" +
|
|
||||||
$"Actors to spawn: {m_SpawnedActors.Count}");
|
|
||||||
|
|
||||||
foreach (Actor actor in m_InSceneActors) {
|
|
||||||
RR.NetworkSystemInstance.WriteActorSynchronize(NetworkPacketTarget.Single(clientID),
|
|
||||||
actor.ActorID,
|
|
||||||
actor.GetCoreStateSnapshot(),
|
|
||||||
actor.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Actor actor in m_SpawnedActors) {
|
|
||||||
s_Logger.Info("Spawning actor for client synchronization: " + actor.SourceActorPath);
|
|
||||||
RR.NetworkSystemInstance.WriteSpawnActor(NetworkPacketTarget.Single(clientID),
|
|
||||||
actor.SourceActorPath,
|
|
||||||
actor.ActorID,
|
|
||||||
actor.GetCoreStateSnapshot(),
|
|
||||||
actor.Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Rpc(SendTo.Server, Delivery = RpcDelivery.Reliable)]
|
|
||||||
void ClientSynchronizedActorRpc(RpcParams rpcParams = default) {
|
|
||||||
ulong clientID = rpcParams.Receive.SenderClientId;
|
|
||||||
|
|
||||||
if (!RR.NetworkSystemInstance.TryGetClientState(clientID, out NetworkClientState clientState)) {
|
|
||||||
s_Logger.Error($"Client state for {clientID} not found. Cannot mark client as synchronized.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clientState.ActorsSyncPacketsLeft--;
|
|
||||||
s_Logger.Info($"Synchronized actor for client {clientID}. Packets left: {clientState.ActorsSyncPacketsLeft}");
|
|
||||||
RR.NetworkSystemInstance.UpdateClientState(clientState);
|
|
||||||
|
|
||||||
if (clientState.ActorsSyncPacketsLeft == 0) {
|
|
||||||
RR.NetworkSystemInstance.ClientSynchronizedActors(clientID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// @MARK: Network Data Handling
|
/// @MARK: Network Data Handling
|
||||||
///
|
///
|
||||||
internal void OnReceivedEntity(NetworkDataHeader header, NativeArray<byte> data) {
|
internal void ProcessActorEvent(in ActorEvent actorEvent) {
|
||||||
if (header.Type == NetworkDataType.ActorCoreState) {
|
Actor actor = FindActorByID(actorEvent.ActorID);
|
||||||
Actor actor = FindActorByID(header.ActorID);
|
if (actor == null) {
|
||||||
if (actor == null) {
|
s_Logger.Error($"Failed to find actor with ID {actorEvent.ActorID} for event handling.");
|
||||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for core state update.");
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
|
||||||
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
|
|
||||||
coreState.Deserialize(reader);
|
|
||||||
actor.RestoreCoreState(coreState);
|
|
||||||
} else if (header.Type == NetworkDataType.ActorTransformSync) {
|
|
||||||
Actor actor = FindActorByID(header.ActorID);
|
|
||||||
if (actor == null) {
|
|
||||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for transform state update.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
|
||||||
ActorTransformSyncData transformSyncData = new ActorTransformSyncData();
|
|
||||||
transformSyncData.Deserialize(reader);
|
|
||||||
actor.RestoreTransformState(transformSyncData);
|
|
||||||
} else if (header.Type == NetworkDataType.ActorState) {
|
|
||||||
Actor actor = FindActorByID(header.ActorID);
|
|
||||||
if (actor == null) {
|
|
||||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for state update.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataSerializationUtils.Deserialize(data, ref actor.Data);
|
|
||||||
} else if (header.Type == NetworkDataType.ActorEvent) {
|
|
||||||
Actor actor = FindActorByID(header.ActorID);
|
|
||||||
if (actor == null) {
|
|
||||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for event handling.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
|
||||||
ActorEvent actorEvent = new ActorEvent();
|
|
||||||
actorEvent.Deserialize(reader);
|
|
||||||
|
|
||||||
actor.HandleActorEvent(actorEvent);
|
|
||||||
} else if (header.Type == NetworkDataType.ActorCommand) {
|
|
||||||
if (!RR.IsServer()) {
|
|
||||||
s_Logger.Error($"Received ActorCommand on client, but this should only be handled on the server.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Actor actor = FindActorByID(header.ActorID);
|
|
||||||
if (actor == null) {
|
|
||||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for command handling.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
|
||||||
ActorCommand actorCommand = new ActorCommand();
|
|
||||||
actorCommand.Deserialize(reader);
|
|
||||||
|
|
||||||
actor.HandleActorCommand(actorCommand);
|
|
||||||
} else if (header.Type == NetworkDataType.SynchronizeActor) {
|
|
||||||
Actor actor = FindActorByID(header.ActorID);
|
|
||||||
if (actor == null) {
|
|
||||||
s_Logger.Error($"Failed to find actor with ID {header.ActorID} for synchronization.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
|
||||||
|
|
||||||
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
|
|
||||||
coreState.Deserialize(reader);
|
|
||||||
|
|
||||||
reader.Read(out ushort actorDataSize);
|
|
||||||
reader.Read(out NativeArray<byte> stateData, actorDataSize);
|
|
||||||
|
|
||||||
actor.RestoreCoreState(coreState);
|
|
||||||
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
|
||||||
|
|
||||||
ClientSynchronizedActorRpc();
|
|
||||||
} else if (header.Type == NetworkDataType.SpawnActor) {
|
|
||||||
using NetworkBufferReader reader = new NetworkBufferReader(data);
|
|
||||||
|
|
||||||
reader.Read(out FixedString64Bytes value);
|
|
||||||
string guid = value.ToString();
|
|
||||||
|
|
||||||
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
|
|
||||||
coreState.Deserialize(reader);
|
|
||||||
|
|
||||||
reader.Read(out ushort actorDataSize);
|
|
||||||
reader.Read(out NativeArray<byte> stateData, actorDataSize);
|
|
||||||
|
|
||||||
SpawnLocalActor(guid,
|
|
||||||
header.ActorID,
|
|
||||||
coreState,
|
|
||||||
stateData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actor.HandleActorEvent(actorEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessActorCommand(ulong senderID, in ActorCommand actorCommand) {
|
||||||
|
if (!RR.IsServer()) {
|
||||||
|
s_Logger.Error("Received ActorCommand on client, but this should only be handled on the server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor actor = FindActorByID(actorCommand.ActorID);
|
||||||
|
if (actor == null) {
|
||||||
|
s_Logger.Error($"Failed to find actor with ID {actorCommand.ActorID} for command handling.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.HandleActorCommand(senderID, actorCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessActorCoreState(in ActorCoreStateSnapshot coreState) {
|
||||||
|
if (RR.IsServer()) {
|
||||||
|
s_Logger.Error("Received ActorCoreState on server, but this should only be handled on the client.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor actor = FindActorByID(coreState.ActorID);
|
||||||
|
if (actor == null) {
|
||||||
|
s_Logger.Error($"Failed to find actor with ID {coreState.ActorID} for core state update.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.RestoreCoreState(coreState);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessActorState(ushort actorID, NativeSlice<byte> stateData) {
|
||||||
|
if (RR.IsServer()) {
|
||||||
|
s_Logger.Error("Received ActorState on server, but this should only be handled on the client.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor actor = FindActorByID(actorID);
|
||||||
|
if (actor == null) {
|
||||||
|
s_Logger.Error($"Failed to find actor with ID {actorID} for state update.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessActorTransformState(in ActorTransformSyncData transformData) {
|
||||||
|
if (RR.IsServer()) {
|
||||||
|
s_Logger.Error("Received ActorTransformSync on server, but this should only be handled on the client.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor actor = FindActorByID(transformData.ActorID);
|
||||||
|
if (actor == null) {
|
||||||
|
s_Logger.Error($"Failed to find actor with ID {transformData.ActorID} for transform state update.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.RestoreTransformState(transformData);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessSpawnActor(in FixedString64Bytes assetGUID,
|
||||||
|
in ActorCoreStateSnapshot coreState,
|
||||||
|
in NativeSlice<byte> stateData) {
|
||||||
|
if (RR.IsServer()) {
|
||||||
|
s_Logger.Error("Received SpawnActor on server, but this should only be handled on the client.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetGUID.IsEmpty) {
|
||||||
|
s_Logger.Error("Received SpawnActor with empty asset GUID, this should not happen.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpawnLocalActor(assetGUID.ToString(),
|
||||||
|
coreState,
|
||||||
|
stateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpawnLocalActor(string guid,
|
void SpawnLocalActor(string guid,
|
||||||
ulong actorID,
|
|
||||||
ActorCoreStateSnapshot coreStateSnapshot,
|
ActorCoreStateSnapshot coreStateSnapshot,
|
||||||
NativeArray<byte> stateData) {
|
NativeSlice<byte> stateData) {
|
||||||
AssetReferenceGameObject assetReference = new AssetReferenceGameObject(guid);
|
AssetReferenceGameObject assetReference = new AssetReferenceGameObject(guid);
|
||||||
if (!assetReference.RuntimeKeyIsValid()) {
|
if (!assetReference.RuntimeKeyIsValid()) {
|
||||||
s_Logger.Error($"Invalid asset reference for actor with GUID {guid}");
|
s_Logger.Error($"Invalid asset reference for actor with GUID {guid}");
|
||||||
@@ -377,17 +350,24 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
Actor actor = actorObject.GetComponent<Actor>();
|
Actor actor = actorObject.GetComponent<Actor>();
|
||||||
if (actor is null) {
|
if (actor is null) {
|
||||||
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
s_Logger.Error($"GameObject {actorObject.name} does not have an Actor component.");
|
||||||
Destroy(actorObject);
|
Object.Destroy(actorObject);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
actor.Manager = this;
|
actor.Manager = this;
|
||||||
actor.SourceActorPath = guid;
|
actor.SourceActorPath = guid;
|
||||||
actor.ActorID = actorID;
|
actor.ActorID = coreStateSnapshot.ActorID;
|
||||||
actor.Data = actor.InternalCreateActorData();
|
actor.Data = actor.InternalCreateActorData();
|
||||||
|
|
||||||
|
if (!RR.IsServer()) {
|
||||||
|
actor.InitializeOnClient();
|
||||||
|
}
|
||||||
|
|
||||||
actor.RestoreCoreState(coreStateSnapshot);
|
actor.RestoreCoreState(coreStateSnapshot);
|
||||||
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
if (stateData.Length > 0) {
|
||||||
|
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||||
|
}
|
||||||
|
|
||||||
m_SpawnedActors.Add(actor);
|
m_SpawnedActors.Add(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,12 +379,91 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
s_Logger.Error("Only the server can send actor events.");
|
s_Logger.Error("Only the server can send actor events.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RR.NetworkSystemInstance.WriteActorEvent(NetworkPacketTarget.AllClients(), actorEvent);
|
foreach (ulong clientID in m_Network.Clients.Keys) {
|
||||||
|
m_Network.WriteActorEvent(clientID, actorEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SendActorCommand(ActorCommand actorCommand) {
|
internal void SendActorCommand(ActorCommand actorCommand) {
|
||||||
RR.NetworkSystemInstance.WriteActorCommand(actorCommand);
|
m_Network.WriteActorCommand(actorCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// @MARK: Synchronization Helpers
|
||||||
|
///
|
||||||
|
|
||||||
|
// @TODO: We might want to sending this in chunks because it probably wont fit into a single packet in real game.
|
||||||
|
internal void WriteInSceneActorsStates(NetworkBufferWriter writer) {
|
||||||
|
writer.Write((ushort) InSceneActorsCount);
|
||||||
|
|
||||||
|
foreach (Actor actor in m_InSceneActors) {
|
||||||
|
writer.Write(actor.ActorStaticID);
|
||||||
|
writer.Write(actor.ActorID);
|
||||||
|
actor.GetCoreStateSnapshot().Serialize(writer);
|
||||||
|
|
||||||
|
writer.Write((byte) actor.Data.GetMaxBytes());
|
||||||
|
actor.Data.Serialize(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool ReadInSceneActorsStates(NetworkBufferReader reader) {
|
||||||
|
reader.Read(out ushort count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
reader.Read(out ulong actorStaticID);
|
||||||
|
reader.Read(out ushort actorID);
|
||||||
|
|
||||||
|
s_Logger.Info($"Reading actor with StaticID {actorStaticID} and ID {actorID} during synchronization.");
|
||||||
|
|
||||||
|
Actor actor = FindInSceneActorWithStaticID(actorStaticID);
|
||||||
|
if (actor == null) {
|
||||||
|
s_Logger.Error($"Failed to find actor with ID {actorID} during synchronization.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActorCoreStateSnapshot coreState = new ActorCoreStateSnapshot();
|
||||||
|
coreState.Deserialize(reader);
|
||||||
|
|
||||||
|
reader.Read(out byte actorDataSize);
|
||||||
|
reader.Read(out NativeSlice<byte> stateData, actorDataSize);
|
||||||
|
|
||||||
|
actor.RestoreCoreState(coreState);
|
||||||
|
if (stateData.Length > 0) {
|
||||||
|
DataSerializationUtils.Deserialize(stateData, ref actor.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_Logger.Info($"Assigning StaticID {actorStaticID} and ID {actorID}");
|
||||||
|
actor.ActorID = actorID;
|
||||||
|
s_Logger.Info("Actor id set to " + actor.ActorID);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Actor inSceneActor in m_InSceneActors) {
|
||||||
|
s_Logger.Info($"InSceneActor: StaticID={inSceneActor.ActorStaticID}, ID={inSceneActor.ActorID}, Path={inSceneActor.SourceActorPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SpawnDynamicActorsForClient(ulong clientID) {
|
||||||
|
if (!RR.IsServer()) {
|
||||||
|
s_Logger.Error("Only the server can spawn dynamic actors for clients.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Actor actor in m_SpawnedActors) {
|
||||||
|
if (actor.OrNull() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActorCoreStateSnapshot coreState = actor.GetCoreStateSnapshot();
|
||||||
|
m_Network.SendSpawnActor(clientID,
|
||||||
|
actor.SourceActorPath,
|
||||||
|
coreState,
|
||||||
|
actor.Data);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,7 @@ using System.Threading;
|
|||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
using RebootKit.Engine.Foundation;
|
using RebootKit.Engine.Foundation;
|
||||||
using RebootKit.Engine.Main;
|
using RebootKit.Engine.Main;
|
||||||
using Unity.Netcode;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.AddressableAssets;
|
|
||||||
using UnityEngine.Assertions;
|
using UnityEngine.Assertions;
|
||||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||||
using UnityEngine.ResourceManagement.ResourceProviders;
|
using UnityEngine.ResourceManagement.ResourceProviders;
|
||||||
@@ -59,7 +57,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
Unload();
|
Unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async UniTask LoadAsync(WorldConfig worldConfig, CancellationToken cancellationToken) {
|
public async UniTask LoadAsync(WorldConfig worldConfig, CancellationToken cancellationToken) {
|
||||||
await UniTask.WaitWhile(() => m_WorldState == WorldState.Loading, cancellationToken: cancellationToken);
|
await UniTask.WaitWhile(() => m_WorldState == WorldState.Loading, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
@@ -73,9 +71,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
|
|
||||||
await m_SceneInstance.Result.ActivateAsync();
|
await m_SceneInstance.Result.ActivateAsync();
|
||||||
SceneManager.SetActiveScene(m_SceneInstance.Result.Scene);
|
SceneManager.SetActiveScene(m_SceneInstance.Result.Scene);
|
||||||
|
|
||||||
// await UniTask.WaitWhile(() => RR.CoreNetworkGameSystemsInstance is null, cancellationToken: cancellationToken);
|
|
||||||
|
|
||||||
foreach (GameObject root in m_SceneInstance.Result.Scene.GetRootGameObjects()) {
|
foreach (GameObject root in m_SceneInstance.Result.Scene.GetRootGameObjects()) {
|
||||||
if (root.TryGetComponent(out IWorldContext worldContext)) {
|
if (root.TryGetComponent(out IWorldContext worldContext)) {
|
||||||
Assert.IsNull(Context,
|
Assert.IsNull(Context,
|
||||||
@@ -84,7 +80,7 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (Actor actor in root.GetComponentsInChildren<Actor>()) {
|
foreach (Actor actor in root.GetComponentsInChildren<Actor>()) {
|
||||||
RR.NetworkSystemInstance.Actors.RegisterInSceneActor(actor);
|
RR.Network.Actors.RegisterInSceneActor(actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +92,8 @@ namespace RebootKit.Engine.Simulation {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RR.NetworkSystemInstance != null) {
|
if (RR.Network != null) {
|
||||||
RR.NetworkSystemInstance.Actors.CleanUp();
|
RR.Network.Actors.CleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_SceneInstance.IsValid()) {
|
if (m_SceneInstance.IsValid()) {
|
||||||
|
|||||||
@@ -1,277 +0,0 @@
|
|||||||
// Source: https://github.com/Unity-Technologies/multiplayer-community-contributions/blob/main/Transports/com.community.netcode.transport.facepunch/Runtime/FacepunchTransport.cs
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Steamworks;
|
|
||||||
using Steamworks.Data;
|
|
||||||
using Unity.Collections;
|
|
||||||
using Unity.Collections.LowLevel.Unsafe;
|
|
||||||
using Unity.Netcode;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace RebootKit.Engine.Steam {
|
|
||||||
using SocketConnection = Connection;
|
|
||||||
|
|
||||||
public class FacepunchTransport : NetworkTransport, IConnectionManager, ISocketManager {
|
|
||||||
private ConnectionManager connectionManager;
|
|
||||||
private SocketManager socketManager;
|
|
||||||
private Dictionary<ulong, Client> connectedClients;
|
|
||||||
|
|
||||||
[Space]
|
|
||||||
[Tooltip("The Steam App ID of your game. Technically you're not allowed to use 480, but Valve doesn't do anything about it so it's fine for testing purposes.")]
|
|
||||||
[SerializeField] private uint steamAppId = 480;
|
|
||||||
|
|
||||||
[Tooltip("The Steam ID of the user targeted when joining as a client.")]
|
|
||||||
[SerializeField] public ulong targetSteamId;
|
|
||||||
|
|
||||||
[Header("Info")]
|
|
||||||
[ReadOnly]
|
|
||||||
[Tooltip("When in play mode, this will display your Steam ID.")]
|
|
||||||
[SerializeField] private ulong userSteamId;
|
|
||||||
|
|
||||||
private LogLevel LogLevel => NetworkManager.Singleton.LogLevel;
|
|
||||||
|
|
||||||
private class Client {
|
|
||||||
public SteamId steamId;
|
|
||||||
public SocketConnection connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #region MonoBehaviour Messages
|
|
||||||
//
|
|
||||||
// private void Awake() {
|
|
||||||
// try {
|
|
||||||
// SteamClient.Init(steamAppId, false);
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// if (LogLevel <= LogLevel.Error)
|
|
||||||
// Debug.LogError($"[{nameof(FacepunchTransport)}] - Caught an exeption during initialization of Steam client: {e}");
|
|
||||||
// } finally {
|
|
||||||
// StartCoroutine(InitSteamworks());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void Update() {
|
|
||||||
// SteamClient.RunCallbacks();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void OnDestroy() {
|
|
||||||
// SteamClient.Shutdown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
#region NetworkTransport Overrides
|
|
||||||
|
|
||||||
public override ulong ServerClientId => 0;
|
|
||||||
|
|
||||||
public override void DisconnectLocalClient() {
|
|
||||||
connectionManager?.Connection.Close();
|
|
||||||
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Disconnecting local client.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void DisconnectRemoteClient(ulong clientId) {
|
|
||||||
if (connectedClients.TryGetValue(clientId, out Client user)) {
|
|
||||||
// Flush any pending messages before closing the connection
|
|
||||||
user.connection.Flush();
|
|
||||||
user.connection.Close();
|
|
||||||
connectedClients.Remove(clientId);
|
|
||||||
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Disconnecting remote client with ID {clientId}.");
|
|
||||||
} else if (LogLevel <= LogLevel.Normal)
|
|
||||||
Debug.LogWarning($"[{nameof(FacepunchTransport)}] - Failed to disconnect remote client with ID {clientId}, client not connected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override unsafe ulong GetCurrentRtt(ulong clientId) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize(NetworkManager networkManager = null) {
|
|
||||||
connectedClients = new Dictionary<ulong, Client>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private SendType NetworkDeliveryToSendType(NetworkDelivery delivery) {
|
|
||||||
return delivery switch {
|
|
||||||
NetworkDelivery.Reliable => SendType.Reliable,
|
|
||||||
NetworkDelivery.ReliableFragmentedSequenced => SendType.Reliable,
|
|
||||||
NetworkDelivery.ReliableSequenced => SendType.Reliable,
|
|
||||||
NetworkDelivery.Unreliable => SendType.Unreliable,
|
|
||||||
NetworkDelivery.UnreliableSequenced => SendType.Unreliable,
|
|
||||||
_ => SendType.Reliable
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Shutdown() {
|
|
||||||
try {
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Shutting down.");
|
|
||||||
|
|
||||||
connectionManager?.Close();
|
|
||||||
socketManager?.Close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (LogLevel <= LogLevel.Error)
|
|
||||||
Debug.LogError($"[{nameof(FacepunchTransport)}] - Caught an exception while shutting down: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Send(ulong clientId, ArraySegment<byte> data, NetworkDelivery delivery) {
|
|
||||||
var sendType = NetworkDeliveryToSendType(delivery);
|
|
||||||
|
|
||||||
if (clientId == ServerClientId)
|
|
||||||
connectionManager.Connection.SendMessage(data.Array, data.Offset, data.Count, sendType);
|
|
||||||
else if (connectedClients.TryGetValue(clientId, out Client user))
|
|
||||||
user.connection.SendMessage(data.Array, data.Offset, data.Count, sendType);
|
|
||||||
else if (LogLevel <= LogLevel.Normal)
|
|
||||||
Debug.LogWarning($"[{nameof(FacepunchTransport)}] - Failed to send packet to remote client with ID {clientId}, client not connected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override NetworkEvent PollEvent(out ulong clientId,
|
|
||||||
out ArraySegment<byte> payload,
|
|
||||||
out float receiveTime) {
|
|
||||||
connectionManager?.Receive();
|
|
||||||
socketManager?.Receive();
|
|
||||||
|
|
||||||
clientId = 0;
|
|
||||||
receiveTime = Time.realtimeSinceStartup;
|
|
||||||
payload = default;
|
|
||||||
return NetworkEvent.Nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool StartClient() {
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Starting as client.");
|
|
||||||
|
|
||||||
connectionManager = SteamNetworkingSockets.ConnectRelay<ConnectionManager>(targetSteamId);
|
|
||||||
connectionManager.Interface = this;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool StartServer() {
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Starting as server.");
|
|
||||||
|
|
||||||
socketManager = SteamNetworkingSockets.CreateRelaySocket<SocketManager>();
|
|
||||||
socketManager.Interface = this;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region ConnectionManager Implementation
|
|
||||||
|
|
||||||
private byte[] payloadCache = new byte[4096];
|
|
||||||
|
|
||||||
private void EnsurePayloadCapacity(int size) {
|
|
||||||
if (payloadCache.Length >= size)
|
|
||||||
return;
|
|
||||||
|
|
||||||
payloadCache = new byte[Math.Max(payloadCache.Length * 2, size)];
|
|
||||||
}
|
|
||||||
|
|
||||||
void IConnectionManager.OnConnecting(ConnectionInfo info) {
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Connecting with Steam user {info.Identity.SteamId}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void IConnectionManager.OnConnected(ConnectionInfo info) {
|
|
||||||
InvokeOnTransportEvent(NetworkEvent.Connect, ServerClientId, default, Time.realtimeSinceStartup);
|
|
||||||
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Connected with Steam user {info.Identity.SteamId}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void IConnectionManager.OnDisconnected(ConnectionInfo info) {
|
|
||||||
InvokeOnTransportEvent(NetworkEvent.Disconnect, ServerClientId, default, Time.realtimeSinceStartup);
|
|
||||||
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Disconnected Steam user {info.Identity.SteamId}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe void IConnectionManager.OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel) {
|
|
||||||
EnsurePayloadCapacity(size);
|
|
||||||
|
|
||||||
fixed (byte* payload = payloadCache) {
|
|
||||||
UnsafeUtility.MemCpy(payload, (byte*) data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
InvokeOnTransportEvent(NetworkEvent.Data, ServerClientId, new ArraySegment<byte>(payloadCache, 0, size),
|
|
||||||
Time.realtimeSinceStartup);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region SocketManager Implementation
|
|
||||||
|
|
||||||
void ISocketManager.OnConnecting(SocketConnection connection, ConnectionInfo info) {
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Accepting connection from Steam user {info.Identity.SteamId}.");
|
|
||||||
|
|
||||||
connection.Accept();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ISocketManager.OnConnected(SocketConnection connection, ConnectionInfo info) {
|
|
||||||
if (!connectedClients.ContainsKey(connection.Id)) {
|
|
||||||
connectedClients.Add(connection.Id, new Client() {
|
|
||||||
connection = connection,
|
|
||||||
steamId = info.Identity.SteamId
|
|
||||||
});
|
|
||||||
|
|
||||||
InvokeOnTransportEvent(NetworkEvent.Connect, connection.Id, default, Time.realtimeSinceStartup);
|
|
||||||
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Connected with Steam user {info.Identity.SteamId}.");
|
|
||||||
} else if (LogLevel <= LogLevel.Normal)
|
|
||||||
Debug.LogWarning($"[{nameof(FacepunchTransport)}] - Failed to connect client with ID {connection.Id}, client already connected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ISocketManager.OnDisconnected(SocketConnection connection, ConnectionInfo info) {
|
|
||||||
if (connectedClients.Remove(connection.Id)) {
|
|
||||||
InvokeOnTransportEvent(NetworkEvent.Disconnect, connection.Id, default, Time.realtimeSinceStartup);
|
|
||||||
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Disconnected Steam user {info.Identity.SteamId}");
|
|
||||||
} else if (LogLevel <= LogLevel.Normal)
|
|
||||||
Debug.LogWarning($"[{nameof(FacepunchTransport)}] - Failed to diconnect client with ID {connection.Id}, client not connected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe void ISocketManager.OnMessage(SocketConnection connection,
|
|
||||||
NetIdentity identity,
|
|
||||||
IntPtr data,
|
|
||||||
int size,
|
|
||||||
long messageNum,
|
|
||||||
long recvTime,
|
|
||||||
int channel) {
|
|
||||||
EnsurePayloadCapacity(size);
|
|
||||||
|
|
||||||
fixed (byte* payload = payloadCache) {
|
|
||||||
UnsafeUtility.MemCpy(payload, (byte*) data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
InvokeOnTransportEvent(NetworkEvent.Data, connection.Id, new ArraySegment<byte>(payloadCache, 0, size),
|
|
||||||
Time.realtimeSinceStartup);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Utility Methods
|
|
||||||
|
|
||||||
private IEnumerator InitSteamworks() {
|
|
||||||
yield return new WaitUntil(() => SteamClient.IsValid);
|
|
||||||
|
|
||||||
SteamNetworkingUtils.InitRelayNetworkAccess();
|
|
||||||
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Initialized access to Steam Relay Network.");
|
|
||||||
|
|
||||||
userSteamId = SteamClient.SteamId;
|
|
||||||
|
|
||||||
if (LogLevel <= LogLevel.Developer)
|
|
||||||
Debug.Log($"[{nameof(FacepunchTransport)}] - Fetched user Steam ID.");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 516179f3a4a7476ea24875d52fae3042
|
|
||||||
timeCreated: 1751378412
|
|
||||||
@@ -51,6 +51,10 @@ namespace RebootKit.Engine.Steam {
|
|||||||
s_Logger.Warning("Join request key is empty. Cannot process join request.");
|
s_Logger.Warning("Join request key is empty. Cannot process join request.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ulong.TryParse(key, out ulong steamID)) {
|
||||||
|
RR.ConnectWithSteamID(steamID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,332 @@
|
|||||||
#define RR_LOCALHOST_ONLY
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using R3;
|
using RebootKit.Engine.Network;
|
||||||
using RebootKit.Engine.Foundation;
|
|
||||||
using RebootKit.Engine.Main;
|
|
||||||
using Steamworks;
|
using Steamworks;
|
||||||
using Steamworks.Data;
|
using Steamworks.Data;
|
||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||||
|
|
||||||
namespace RebootKit.Engine.Steam {
|
namespace RebootKit.Engine.Steam {
|
||||||
|
public class SteamNetworkManager : INetworkManager, ISocketManager, IConnectionManager {
|
||||||
|
static readonly Logger s_Logger = new Logger(nameof(SteamNetworkManager));
|
||||||
|
|
||||||
|
public const ulong k_ServerClientID = 0;
|
||||||
|
const int k_DefaultPort = 419;
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
public ulong ClientID;
|
||||||
|
public Connection Connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly Dictionary<ulong, Client> m_Clients = new Dictionary<ulong, Client>();
|
||||||
|
|
||||||
|
SocketManager m_SocketManager;
|
||||||
|
ConnectionManager m_ConnectionManager;
|
||||||
|
|
||||||
|
float m_StatsRefreshTimer;
|
||||||
|
|
||||||
|
public INetworkManagerDelegate Delegate { get; set; }
|
||||||
|
|
||||||
|
public ulong LocalClientID {
|
||||||
|
get {
|
||||||
|
return SteamClient.SteamId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkManagerStats Stats { get; } = new NetworkManagerStats();
|
||||||
|
|
||||||
|
// @NOTE: Set this before starting the client to specify which server to connect to.
|
||||||
|
public ulong TargetSteamID;
|
||||||
|
|
||||||
|
public SteamNetworkManager() {
|
||||||
|
SteamNetworkingUtils.InitRelayNetworkAccess();
|
||||||
|
SteamNetworkingUtils.OnDebugOutput += SteamDebugOutput;
|
||||||
|
SteamNetworkingUtils.DebugLevel = NetDebugOutput.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
SteamNetworkingUtils.OnDebugOutput -= SteamDebugOutput;
|
||||||
|
|
||||||
|
StopHost();
|
||||||
|
Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SteamDebugOutput(NetDebugOutput output, string msg) {
|
||||||
|
s_Logger.Info($"Output: {output} - {msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: Server
|
||||||
|
//
|
||||||
|
public bool IsServer() {
|
||||||
|
return m_SocketManager != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool StartHost() {
|
||||||
|
try {
|
||||||
|
TargetSteamID = SteamClient.SteamId;
|
||||||
|
|
||||||
|
m_SocketManager = SteamNetworkingSockets.CreateRelaySocket<SocketManager>(k_DefaultPort);
|
||||||
|
m_SocketManager.Interface = this;
|
||||||
|
|
||||||
|
m_ConnectionManager =
|
||||||
|
SteamNetworkingSockets.ConnectRelay<ConnectionManager>(TargetSteamID, k_DefaultPort);
|
||||||
|
m_ConnectionManager.Interface = this;
|
||||||
|
|
||||||
|
SteamFriends.SetRichPresence("connect", LocalClientID.ToString());
|
||||||
|
|
||||||
|
Stats.Reset();
|
||||||
|
|
||||||
|
Delegate?.OnServerStarted();
|
||||||
|
Delegate?.OnClientStarted();
|
||||||
|
} catch (Exception e) {
|
||||||
|
s_Logger.Error($"Failed to create server: {e.Message}");
|
||||||
|
m_SocketManager = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopHost() {
|
||||||
|
if (!IsServer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_Logger.Info("Stopping server...");
|
||||||
|
|
||||||
|
SteamFriends.SetRichPresence("connect", null);
|
||||||
|
|
||||||
|
foreach (Client client in m_Clients.Values) {
|
||||||
|
client.Connection.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Clients.Clear();
|
||||||
|
|
||||||
|
m_SocketManager.Close();
|
||||||
|
m_SocketManager = null;
|
||||||
|
|
||||||
|
Delegate?.OnClientStopped();
|
||||||
|
Delegate?.OnServerStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: Client
|
||||||
|
//
|
||||||
|
public bool IsClient() {
|
||||||
|
return m_ConnectionManager != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool StartClient() {
|
||||||
|
if (IsClient()) {
|
||||||
|
s_Logger.Error("Cannot start client while already connected.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_Logger.Info("Connecting to server with steam ID: " + TargetSteamID);
|
||||||
|
try {
|
||||||
|
m_ConnectionManager =
|
||||||
|
SteamNetworkingSockets.ConnectRelay<ConnectionManager>(TargetSteamID, k_DefaultPort);
|
||||||
|
m_ConnectionManager.Interface = this;
|
||||||
|
|
||||||
|
Stats.Reset();
|
||||||
|
|
||||||
|
Delegate?.OnClientStarted();
|
||||||
|
} catch (Exception e) {
|
||||||
|
s_Logger.Error($"Failed to connect to server with ID {TargetSteamID}: {e.Message}");
|
||||||
|
m_ConnectionManager = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect() {
|
||||||
|
if (!IsClient()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_Logger.Info("Disconnecting from server...");
|
||||||
|
|
||||||
|
m_ConnectionManager.Close();
|
||||||
|
m_ConnectionManager = null;
|
||||||
|
|
||||||
|
Delegate?.OnClientStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: Data Sending/Receiving
|
||||||
|
//
|
||||||
|
public void Send(ulong clientID, NativeSlice<byte> data, SendMode mode) {
|
||||||
|
if (data.Length == 0) {
|
||||||
|
s_Logger.Error("Cannot send empty data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
Send(clientID, (byte*) data.GetUnsafePtr(), data.Length, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Send(ulong clientID, byte* data, int length, SendMode mode) {
|
||||||
|
if (IsServer()) {
|
||||||
|
if (clientID == k_ServerClientID || clientID == LocalClientID) {
|
||||||
|
NativeArray<byte> dataArray = new NativeArray<byte>(length, Allocator.Temp);
|
||||||
|
UnsafeUtility.MemCpy(dataArray.GetUnsafePtr(), data, length);
|
||||||
|
MessageReceived(LocalClientID, dataArray);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_Clients.TryGetValue(clientID, out Client client)) {
|
||||||
|
s_Logger.Error($"Client with ID {clientID} not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result sendResult = client.Connection.SendMessage((IntPtr) data, length, GetSteamSendType(mode));
|
||||||
|
if (sendResult == Result.OK) {
|
||||||
|
if (mode == SendMode.Reliable) {
|
||||||
|
Stats.ReliableBytesSent += (ulong) length;
|
||||||
|
} else if (mode == SendMode.Unreliable) {
|
||||||
|
Stats.UnreliableBytesSent += (ulong) length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s_Logger.Error($"Failed to send message to clientID, {clientID}. Result: {sendResult}.");
|
||||||
|
}
|
||||||
|
} else if (IsClient()) {
|
||||||
|
if (clientID != k_ServerClientID) {
|
||||||
|
s_Logger.Error($"Client can only send messages to the server (clientID must be {k_ServerClientID.ToString()}).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result sendResult = m_ConnectionManager.Connection.SendMessage((IntPtr) data,
|
||||||
|
length,
|
||||||
|
GetSteamSendType(mode));
|
||||||
|
if (sendResult == Result.OK) {
|
||||||
|
if (mode == SendMode.Reliable) {
|
||||||
|
Stats.ReliableBytesSent += (ulong) length;
|
||||||
|
} else if (mode == SendMode.Unreliable) {
|
||||||
|
Stats.UnreliableBytesSent += (ulong) length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s_Logger.Error($"Failed to send message to server. Result: {sendResult}. Flushing and retrying...");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s_Logger.Error("Cannot send data, server or client is not started.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageReceived(ulong senderID, NativeArray<byte> data) {
|
||||||
|
Delegate?.OnMessageReceived(senderID, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick() {
|
||||||
|
m_SocketManager?.Receive();
|
||||||
|
m_ConnectionManager?.Receive();
|
||||||
|
|
||||||
|
m_StatsRefreshTimer -= UnityEngine.Time.deltaTime;
|
||||||
|
|
||||||
|
if (m_StatsRefreshTimer <= 0f) {
|
||||||
|
m_StatsRefreshTimer = 1.0f;
|
||||||
|
|
||||||
|
Stats.ReliableBytesSentPerSecond = Stats.ReliableBytesSent;
|
||||||
|
Stats.UnreliableBytesSentPerSecond = Stats.UnreliableBytesSent;
|
||||||
|
Stats.BytesReceivedPerSecond = Stats.BytesReceived;
|
||||||
|
|
||||||
|
Stats.ReliableBytesSent = 0;
|
||||||
|
Stats.UnreliableBytesSent = 0;
|
||||||
|
Stats.BytesReceived = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: IConnectionManager
|
||||||
|
//
|
||||||
|
void ISocketManager.OnConnecting(Connection connection, ConnectionInfo info) {
|
||||||
|
s_Logger.Info($"OnConnecting: {connection.Id} - {info.Identity}");
|
||||||
|
connection.Accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISocketManager.OnConnected(Connection connection, ConnectionInfo info) {
|
||||||
|
if (!m_Clients.ContainsKey(info.Identity.SteamId)) {
|
||||||
|
s_Logger.Info($"OnConnected: {connection.Id} - {info.Identity}");
|
||||||
|
|
||||||
|
Client client = new Client {
|
||||||
|
ClientID = info.Identity.SteamId,
|
||||||
|
Connection = connection
|
||||||
|
};
|
||||||
|
m_Clients.Add(info.Identity.SteamId, client);
|
||||||
|
|
||||||
|
Delegate?.OnClientConnected(client.ClientID);
|
||||||
|
} else {
|
||||||
|
s_Logger.Error("Failed to add client, already exists: " + info.Identity.SteamId);
|
||||||
|
connection.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISocketManager.OnDisconnected(Connection connection, ConnectionInfo info) {
|
||||||
|
s_Logger.Info($"OnDisconnected: {connection.Id} - {info.Identity}");
|
||||||
|
|
||||||
|
if (m_Clients.Remove(info.Identity.SteamId)) {
|
||||||
|
Delegate?.OnClientDisconnected(info.Identity.SteamId);
|
||||||
|
} else {
|
||||||
|
s_Logger.Error("Failed to remove client, not found: " + info.Identity.SteamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISocketManager.OnMessage(Connection connection,
|
||||||
|
NetIdentity identity,
|
||||||
|
IntPtr data,
|
||||||
|
int size,
|
||||||
|
long messageNum,
|
||||||
|
long recvTime,
|
||||||
|
int channel) {
|
||||||
|
NativeArray<byte> dataArray = new NativeArray<byte>(size, Allocator.Temp);
|
||||||
|
unsafe {
|
||||||
|
UnsafeUtility.MemCpy(dataArray.GetUnsafePtr(), (void*) data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats.BytesReceived += (ulong) size;
|
||||||
|
MessageReceived(identity.SteamId, dataArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: ISocketManager
|
||||||
|
//
|
||||||
|
void IConnectionManager.OnConnecting(ConnectionInfo info) { }
|
||||||
|
|
||||||
|
void IConnectionManager.OnConnected(ConnectionInfo info) {
|
||||||
|
Delegate?.OnClientConnected(info.Identity.SteamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IConnectionManager.OnDisconnected(ConnectionInfo info) {
|
||||||
|
Delegate?.OnClientDisconnected(info.Identity.SteamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IConnectionManager.OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel) {
|
||||||
|
NativeArray<byte> dataArray = new NativeArray<byte>(size, Allocator.Temp);
|
||||||
|
unsafe {
|
||||||
|
UnsafeUtility.MemCpy(dataArray.GetUnsafePtr(), (void*) data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats.BytesReceived += (ulong) size;
|
||||||
|
MessageReceived(k_ServerClientID, dataArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// @MARK: Utilities
|
||||||
|
//
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
static SendType GetSteamSendType(SendMode mode) {
|
||||||
|
return mode switch {
|
||||||
|
SendMode.Reliable => SendType.Reliable,
|
||||||
|
SendMode.Unreliable => SendType.Unreliable,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// class SteamNetworkManager : INetworkManager {
|
// class SteamNetworkManager : INetworkManager {
|
||||||
// static readonly Logger s_Logger = new Logger(nameof(SteamNetworkManager));
|
// static readonly Logger s_Logger = new Logger(nameof(SteamNetworkManager));
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -15,10 +15,7 @@
|
|||||||
"GUID:84651a3751eca9349aac36a66bba901b",
|
"GUID:84651a3751eca9349aac36a66bba901b",
|
||||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||||
"GUID:f2d49d9fa7e7eb3418e39723a7d3b92f",
|
"GUID:f2d49d9fa7e7eb3418e39723a7d3b92f",
|
||||||
"GUID:324caed91501a9c47a04ebfd87b68794",
|
"GUID:324caed91501a9c47a04ebfd87b68794"
|
||||||
"GUID:1491147abca9d7d4bb7105af628b223e",
|
|
||||||
"GUID:b1cd7326e664a434ab35daa802773c7f",
|
|
||||||
"GUID:2c360f3794ebc41388fc11424ddbfdd0"
|
|
||||||
],
|
],
|
||||||
"includePlatforms": [],
|
"includePlatforms": [],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 3117fc3405548ba42b44318e9dce1efd
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 2eeb6bda1b70984479edf1306d3bdc99
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
%YAML 1.1
|
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
|
||||||
--- !u!1 &1321683558189709310
|
|
||||||
GameObject:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
serializedVersion: 6
|
|
||||||
m_Component:
|
|
||||||
- component: {fileID: 1878935056881269035}
|
|
||||||
- component: {fileID: 258784011466397156}
|
|
||||||
- component: {fileID: 8437866359923088342}
|
|
||||||
- component: {fileID: 2149791309811179493}
|
|
||||||
m_Layer: 0
|
|
||||||
m_Name: core_network_game_systems
|
|
||||||
m_TagString: Untagged
|
|
||||||
m_Icon: {fileID: 0}
|
|
||||||
m_NavMeshLayer: 0
|
|
||||||
m_StaticEditorFlags: 0
|
|
||||||
m_IsActive: 1
|
|
||||||
--- !u!4 &1878935056881269035
|
|
||||||
Transform:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 1321683558189709310}
|
|
||||||
serializedVersion: 2
|
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
|
||||||
m_ConstrainProportionsScale: 0
|
|
||||||
m_Children: []
|
|
||||||
m_Father: {fileID: 0}
|
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
||||||
--- !u!114 &258784011466397156
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 1321683558189709310}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
GlobalObjectIdHash: 2588764075
|
|
||||||
InScenePlacedSourceGlobalObjectIdHash: 0
|
|
||||||
DeferredDespawnTick: 0
|
|
||||||
Ownership: 1
|
|
||||||
AlwaysReplicateAsRoot: 0
|
|
||||||
SynchronizeTransform: 0
|
|
||||||
ActiveSceneSynchronization: 0
|
|
||||||
SceneMigrationSynchronization: 0
|
|
||||||
SpawnWithObservers: 1
|
|
||||||
DontDestroyWithOwner: 0
|
|
||||||
AutoObjectParentSync: 0
|
|
||||||
SyncOwnerTransformWhenParented: 0
|
|
||||||
AllowOwnerToParent: 0
|
|
||||||
--- !u!114 &8437866359923088342
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 1321683558189709310}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: 1f967d37c17e4704b80849c305a53be9, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
<Actors>k__BackingField: {fileID: 2149791309811179493}
|
|
||||||
--- !u!114 &2149791309811179493
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 1321683558189709310}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: af0c5ff8ac4945eca2a9cab0a88268f4, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
ShowTopMostFoldoutHeaderGroup: 1
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 2cc631d24ab41194ebdeffff7faf62a5
|
|
||||||
PrefabImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -490,7 +490,7 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_Document: {fileID: 638618785}
|
m_Document: {fileID: 638618785}
|
||||||
--- !u!1 &735095186
|
--- !u!1 &1014886342
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
@@ -498,50 +498,48 @@ GameObject:
|
|||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 735095188}
|
- component: {fileID: 1014886344}
|
||||||
- component: {fileID: 735095187}
|
- component: {fileID: 1014886343}
|
||||||
m_Layer: 0
|
m_Layer: 5
|
||||||
m_Name: Runtime Network Stats Monitor
|
m_Name: watermark
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
m_StaticEditorFlags: 0
|
m_StaticEditorFlags: 0
|
||||||
m_IsActive: 1
|
m_IsActive: 1
|
||||||
--- !u!114 &735095187
|
--- !u!114 &1014886343
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 735095186}
|
m_GameObject: {fileID: 1014886342}
|
||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
m_EditorHideFlags: 0
|
m_EditorHideFlags: 0
|
||||||
m_Script: {fileID: 11500000, guid: 17737e0516da2445b9b0077ae2bd9b4f, type: 3}
|
m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_Visible: 1
|
m_PanelSettings: {fileID: 11400000, guid: 7f72ab2868182b849a37094d51821bc2, type: 2}
|
||||||
m_MaxRefreshRate: 30
|
m_ParentUI: {fileID: 0}
|
||||||
<CustomStyleSheet>k__BackingField: {fileID: 0}
|
sourceAsset: {fileID: 9197481963319205126, guid: 70f3e7058aab07444af9dc0114ecec32, type: 3}
|
||||||
<PanelSettingsOverride>k__BackingField: {fileID: 0}
|
m_SortingOrder: 9999
|
||||||
<Position>k__BackingField:
|
m_WorldSpaceSizeMode: 1
|
||||||
<OverridePosition>k__BackingField: 1
|
m_WorldSpaceWidth: 1920
|
||||||
m_PositionLeftToRight: 0
|
m_WorldSpaceHeight: 1080
|
||||||
m_PositionTopToBottom: 0
|
--- !u!4 &1014886344
|
||||||
<Configuration>k__BackingField: {fileID: 11400000, guid: 8f1b4e3792d8446399527749cfb591a2, type: 2}
|
|
||||||
--- !u!4 &735095188
|
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 735095186}
|
m_GameObject: {fileID: 1014886342}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 1493735124}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &1468139325
|
--- !u!1 &1468139325
|
||||||
GameObject:
|
GameObject:
|
||||||
@@ -621,7 +619,6 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_DebugOverlayView: {fileID: 1809244332}
|
m_DebugOverlayView: {fileID: 1809244332}
|
||||||
m_GameVersionOverlay: {fileID: 638618787}
|
m_GameVersionOverlay: {fileID: 638618787}
|
||||||
m_NetworkStatsOverlay: {fileID: 735095187}
|
|
||||||
--- !u!4 &1493735124
|
--- !u!4 &1493735124
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -637,7 +634,6 @@ Transform:
|
|||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 1809244330}
|
- {fileID: 1809244330}
|
||||||
- {fileID: 638618786}
|
- {fileID: 638618786}
|
||||||
- {fileID: 735095188}
|
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &1809244329
|
--- !u!1 &1809244329
|
||||||
@@ -713,3 +709,4 @@ SceneRoots:
|
|||||||
- {fileID: 1468139327}
|
- {fileID: 1468139327}
|
||||||
- {fileID: 1493735124}
|
- {fileID: 1493735124}
|
||||||
- {fileID: 599568090}
|
- {fileID: 599568090}
|
||||||
|
- {fileID: 1014886344}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
-unity-text-outline-color: black;
|
-unity-text-outline-color: black;
|
||||||
-unity-text-outline-width: 1px;
|
-unity-text-outline-width: 1px;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
-unity-font-definition: url("project://database/Assets/RebootKit/Runtime/Engine/core_assets/fonts/JetBrainsMono-Bold.ttf");
|
-unity-font-definition: url("project://database/Assets/RebootKit/Runtime/Engine/core_assets/fonts/JetBrainsMono-Bold.ttf");
|
||||||
text-shadow: 0 2px 16px rgba(0, 0, 0, 0.75);
|
text-shadow: 0 2px 16px rgba(0, 0, 0, 0.75);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user