From ea77e1e69f46b16f2aac44b73fa32fc9aa34f737 Mon Sep 17 00:00:00 2001 From: Unity Technologies <@unity> Date: Wed, 17 Jul 2024 00:00:00 +0000 Subject: [PATCH] com.unity.netcode@1.3.0-pre.4 ## [1.3.0-pre.4] - 2024-07-17 ### Added * Optional UUID5GhostType property to the GhostCreation.Config struct, which allows you to provide your own unique UUID5 identifier when creating a ghost prefab at runtime, instead of relying on the auto-generated one (which uses the SHA1 of the ghost name). * NetworkStreamDriver.ResetDriverStore to properly reset the NetworkDriverStore ### Changed * All Simulate component enable states are reset to using a job instead of doing that synchronously on the main thread. Reason for the change is the fact this was inducing a big stall at the end of the Prediction loop. However, the benefits is only visible when there are a large number of jobified workload. * Corrected incorrect/missing CHANGELOG entries across many previous versions. * Updated Burst dependency to version 1.8.16 * Unified Multiplayer Project settings. * Moved menu items to a collective place to improve workflows. This removes the Multiplayer menu and integrates into common places Window, Assets/Create, right-click menus, etc. * The dependency on Unity Transport has been updated to version 2.2.1 * Re-exposed `TryFindAutoConnectEndPoint` and `HasDefaultAddressAndPortSet`, with small documentation updates. * ConcurrentDriverStore and NetworkDriverStore.Concurrent are now public and you can use the NetworkDriverStore.Concurrent in your jobs to send/receive data. --- CHANGELOG.md | 44 ++++++----- Documentation~/entities-list.md | 2 +- Documentation~/ghost-snapshots.md | 4 +- Documentation~/metrics.md | 2 +- Documentation~/time-synchronization.md | 2 +- Editor/CodeGenMenu.cs | 9 +-- Editor/MultiplayerPlayModeWindow.cs | 4 +- Editor/NetcodeConfigEditor.cs | 6 +- Editor/SourceGeneratorSettings.cs | 2 +- .../Authoring/Hybrid/NetCodeServerSettings.cs | 4 + .../ClientServerBootstrap.cs | 60 ++++++++------ Runtime/Connection/NetworkDriverStore.cs | 48 ++++++++---- Runtime/Connection/NetworkStreamDriver.cs | 63 ++++++++++++++- .../Connection/NetworkStreamReceiveSystem.cs | 10 +-- Runtime/NetCodeConfig.cs | 2 +- .../Snapshot/GhostPredictionSystemGroup.cs | 78 +++++++++++++++---- Runtime/Snapshot/GhostPrefabCreation.cs | 58 +++++++++++--- .../Source~/Documentation/templates-spec.md | 38 ++++----- Runtime/Stats/GhostStatsSystem.cs | 2 +- Runtime/Stats/netdbg.html | 2 +- Tests/Editor/BootstrapTests.cs | 62 +++++++++++++++ Tests/Editor/GhostPrefabCreationTests.cs | 51 ++++++++++++ ValidationExceptions.json | 4 +- package.json | 12 +-- 24 files changed, 427 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a42b4e6..b8e706b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,30 @@ uid: changelog # Changelog +## [1.3.0-pre.4] - 2024-07-17 + +### Added + +* Optional UUID5GhostType property to the GhostCreation.Config struct, which allows you to provide your own unique UUID5 identifier when creating a ghost prefab at runtime, instead of relying on the auto-generated one (which uses the SHA1 of the ghost name). +* NetworkStreamDriver.ResetDriverStore to properly reset the NetworkDriverStore + +### Changed + +* All Simulate component enable states are reset to using a job instead of doing that synchronously on the main thread. Reason for the change is the fact this was inducing a big stall at the end of the Prediction loop. However, the benefits is only visible when there are a large number of jobified workload. +* Corrected incorrect/missing CHANGELOG entries across many previous versions. +* Updated Burst dependency to version 1.8.16 +* Unified Multiplayer Project settings. +* Moved menu items to a collective place to improve workflows. This removes the Multiplayer menu and integrates into common places Window, Assets/Create, right-click menus, etc. +* The dependency on Unity Transport has been updated to version 2.2.1 +* Re-exposed `TryFindAutoConnectEndPoint` and `HasDefaultAddressAndPortSet`, with small documentation updates. +* ConcurrentDriverStore and NetworkDriverStore.Concurrent are now public and you can use the NetworkDriverStore.Concurrent in your jobs to send/receive data. + + ## [1.3.0-exp.1] - 2024-06-11 ### Added * The Multiplayer PlayMode Tools Window now calls synchronous `Connect` and `Disconnect` methods, and now shows the `Handshake` connection step. Handshake occurs when the client connection has been accepted by the server, but said client is awaiting a `NetworkId` assignment RPC from said server. -* Possibility to optimise the ghost serialization and pre-serialization by registering a custom chunk serialization function pointer that will let users reason on a per-archetype and write the serialization code without requiring virtual methods (function pointer call indirection) and optimised for the use case. * Further clarifications, minor improvements, and fixes to the PlayMode Tools Window. * `DefaultRelevancyQuery` to specify general rules for relevancy without specifying it ghost by ghost. * Tooltips and additional info for the NetCodeConfig, supporting `ClientServerTickRate`, `ClientTickRate`, and `GhostSendSystemData`. @@ -47,7 +65,6 @@ uid: changelog * Issue where disconnecting your own client (via a direct `Disconnect` call) would fail to recycle the `NetworkId` component, and fail to dispose of the Entity. * We now also correctly report and clean-up stale connections. I.e. Connections that are entered into invalid states by user-code. * Issue where the `CommandSendSystem` was attempting to send RPCs with stale connections. -* some slow path in the normal ghost serialization that was causing many re-serialization of the same chunk, in case the chunk data was not fitting inside the temporary stream buffer. That was almost the norm in many cases, when the serialised entities are large enough (either because of the number of components or because of the size of them). * NetworkStreamConnection now holds an accurate connection state right after the call to driver's Connect, instead of having to wait a frame to get it updated. * Minor documentation issues. * InvalidOperationException: cases where EntityManager is part of an exclusive transaction we skip gathering analytics for its world. @@ -115,6 +132,8 @@ uid: changelog * BatchScaleImportanceDelegate, a new version of the importance scaling function that work in batches. It is not required to set both the ScaleImportance and the BatchScaleImportance function pointers. If the BatchScaleImportance is set, it is the preferred. * TempStreamInitialSize, a new parameter in the GhostSendSystemData for tuning the initial size of the temporary buffer used by server to serialise ghosts. By default now the size is 8KB. * AlwaysRelevantQuery to specify general rules for relevancy without specifying it ghost by ghost. +* Added support for `NetCodeConnectionEvents` (accessed via singleton `NetworkStreamDriver`, `ConnectionEventsForFrame` property), allowing users an alternative to the `ConnectionState` component for tracking client connection and disconnection events. +* When single-stepping the Unity Editor, you'll see `NetCodeConnectionEvent`s in our Multiplayer PlayMode Tools Window. ### Changed @@ -158,7 +177,7 @@ uid: changelog * QoL issue where `GhostAuthoringInspectionComponent` was not always modifiable in areas of the Editor where it is valid to modify them. * Issue where `GhostAuthoringComponent` was disallowed in nested prefab setups (where the root prefab is NOT a ghost). * Log verbiage when creating a driver in DefaultDriverConstructor read like a 'call to action'. It's not. - +* Internal driver clobbering error when calling `NetworkDriverStore.Disconnect` leading to rare exceptions in esoteric situations. ## [1.2.0-exp.3] - 2023-11-09 @@ -174,7 +193,6 @@ uid: changelog * mostly for maintenance, code-generation for the component and buffer serialiser, using helper methods living all inside the package. No user visible changes * Updated Transport dependency to version 2.1.0. * The minimum supported editor version is now 2022.3.11f1 -* all Simulate component enable states are reset to using a job instead of doing that synchronously on the main thread. Reason for the change is the fact this was inducing a big stall at the end of the Prediction loop. However, the benefits is only visible when there are a large number of jobified workload. * components, command, buffers and rpc are now replicated also if they are private or internal ### Removed @@ -193,6 +211,8 @@ uid: changelog * `IndexOutOfRangeException` in the `GhostCollectionSystem` when ghost hash mismatches are present (a common error during dev). * An issue accessing the m_PredictionSwitchingSmoothingLookup buffer when multiple ghosts change their owner and they need to switch prediction mode. * GhostPrefabCreation.ConvertToGhostPrefab api that incorrectly replicated and assign to child entity components the root entity variant. +* Possibility to optimise the ghost serialization and pre-serialization by registering a custom chunk serialization function pointer that will let users reason on a per-archetype and write the serialization code without requiring virtual methods (function pointer call indirection) and optimised for the use case. +* some slow path in the normal ghost serialization that was causing many re-serialization of the same chunk, in case the chunk data was not fitting inside the temporary stream buffer. That was almost the norm in many cases, when the serialised entities are large enough (either because of the number of components or because of the size of them). ## [1.1.0-pre.3] - 2023-10-17 @@ -468,11 +488,6 @@ MetricsMonitorComponent: MetricsMonitor, * It is not necessary anymore to define a custom `DefaultGhostVariant` system if a `LocalTransform` (`Rotation` or `Position` for V1) or `PhysicsVelocity` variants are added to project (since a `default` selection is already provided by the package). * Updated `com.unity.transport` dependency to 2.0.0-pre.2 - -### Deprecated - -* `ProjectSettings / NetCodeClientTarget` was not actually saved to the ProjectSettings. Instead, it was saved to `EditorPrefs`, breaking build determinism across machines. Now that this has been fixed, your EditorPref has been clobbered, and `ClientSettings.NetCodeClientTarget` has been deprecated (in favour of `NetCodeClientSettings.instance.ClientTarget`). - ### Removed * Removing dependencies on `com.unity.jobs` package. @@ -497,18 +512,8 @@ MetricsMonitorComponent: MetricsMonitor, * Removed CSS warning in package. * A problem with baking and additional ghost entities that was removing `LocalTransform`, `WorldTransform` and `LocalToWorld` matrix. * Mismatched ClientServerTickRate.SimulationTickRate and PredictedFixedStepSimulationSystemGroup.RateManager.Timestep will throw an error and will set the values to match each other. -* An issue with pre-spawned ghost baking when the baked entity has not LocalTransform (position/rotation for transform v1) component. -* "Ghost Distance Importance Scaling" is now working again. Ensure you read the updated documentation. -* Missing field write in `NetworkStreamListenSystem.OnCreate`, fixing Relay servers. -* Code-Generated Burst-compiled Serializer methods will now only compile inside worlds with `WorldFlag.GameClient` and `WorldFlag.GameServer` WorldFlags. This improves exit play-mode speeds (when Domain Reload is enabled), baking (in all cases), and recompilation speeds. -* Fixed an issue where multiple ghost types with the same archetype but difference data could sometime trigger errors about ghosts changing type. * Improvements to the `GhostAuthoringInspectionComponent`, including removing the freeze when a baker creates lots of "Additional" entities, better display of Inputs, and fixed bug where the EntityGuid was not being saved (so modifying additional Entities is now supported). We now also detect (but don't destroy) broken ComponentOverrides, making it easier to switch from TRANSFORMS_V1 (for example). -* Fix a mistake where the relay sample will create a client driver rather than a server driver -* Fix logic for relay set up on the client. Making sure when calling DefaultDriverConstructor.RegisterClientDriver with relay settings that we skip this unless, requested playtype is client or clientandserver (if no server is found), the simulator is enabled, or on a client only build. -* Fixed `ArgumentException: ArchetypeChunk.GetDynamicComponentDataArrayReinterpret cannot be called on zero-sized IComponentData` in `GhostPredictionHistorySystem.PredictionBackupJob`. Added comprehensive test coverage for the `GhostPredictionHistorySystem` (via adding a predicted ghost version of the `GhostSerializationTestsForEnableableBits` tests). * Fixed serialization of components on child entities in the case where `SentForChildEntities = true`. This fix may introduce a small performance regression in baking and netcode world initialization. -* `GhostUpdateSystem` now supports Change Filtering, so components on the client will now only be marked as changed _when they actually are changed_. We strongly recommend implementing change filtering when reading components containing `[GhostField]`s and `[GhostEnabledBit]`s on the client. -* Fixed input component codegen issue when the type is nested in a parent class * Exposed NetworkTick value to Entity Inspector. * Fixed code-gen error where `ICommandData.Tick` was not being replicated. * Fixed code-gen GhostField error handling when dealing with properties on Buffers, Commands, and Components. @@ -534,7 +539,6 @@ MetricsMonitorComponent: MetricsMonitor, * added some sanity check to prevent updating invalid ghosts * Added a new method, `GhostPrefabCreation.ConvertToGhostPrefab` which can be used to create ghost prefabs from code without having an asset for them. * Added a support for creating multiple network drivers. It is now possible to have a server that listen to the same port using different network interfaces (ex: IPC, Socket, WebSocket at the same time). -* Hybrid assemblies will not be included in DOTS Runtime builds. * code generation documentation * RegisterPredictedPhysicsRuntimeSystemReadWrite and RegisterPredictedPhysicsRuntimeSystemReadOnly extension methods, for tracking dependencies when using predicted networked physics systems. * Support for runtime editing the number of ThinClients. diff --git a/Documentation~/entities-list.md b/Documentation~/entities-list.md index e4e8a1c..719f124 100644 --- a/Documentation~/entities-list.md +++ b/Documentation~/entities-list.md @@ -211,7 +211,7 @@ This singleton is a special kind of ghost without a prefab asset. ### GhostStats | Component | Description | |-----------------------------------------|---------------------------------------------------------------------| -| __GhostStats__ | State if the NetDbg tools is connected or not. | +| __GhostStats__ | State if the Network Debugger tools is connected or not. | | __GhostStatsCollectionCommand__ | Internal stats data for commands. | | __GhostStatsCollectionSnapshot__ | Internal stats data used to track sent/received snapshot data. | | __GhostStatsCollectionPredictionError__ | Record the prediction stats for various ghost/component types pair. | diff --git a/Documentation~/ghost-snapshots.md b/Documentation~/ghost-snapshots.md index 2a164cb..a07771d 100644 --- a/Documentation~/ghost-snapshots.md +++ b/Documentation~/ghost-snapshots.md @@ -405,11 +405,11 @@ to be added to all ghost types, sent for all ghost types, and serialized using t ## Snapshot visualization tool -To understand what is being put on the wire in the Netcode, you can use the snapshot visualization tool, __NetDbg__ tool. +To understand what is being put on the wire in the Netcode, you can use the snapshot visualization tool, __Network Debugger__ tool. net debug tool -To open the tool, go to menu: __Multiplayer > Open NetDbg__, and the tool opens in a browser window. It displays a vertical bar for each received snapshot, with a breakdown of the snapshot’s ghost types, size etc. +To open the tool, go to menu: __Window > Multiplayer > Network Debugger__, and the tool opens in a browser window. It displays a vertical bar for each received snapshot, with a breakdown of the snapshot’s ghost types, size etc. To see more detailed information about the snapshot, click on one of the bars. diff --git a/Documentation~/metrics.md b/Documentation~/metrics.md index 18e1015..910e460 100644 --- a/Documentation~/metrics.md +++ b/Documentation~/metrics.md @@ -1,6 +1,6 @@ # Metrics -There are 2 ways of gathering metrics about the netcode simulation. The simplest and most straight forward way is to use the NetDbg from the Multiplayer Menu in the Editor. This will provide you with a simple web interface to view the metrics. +There are 2 ways of gathering metrics about the netcode simulation. The simplest and most straight forward way is to use the Network Debugger from the Multiplayer Menu in the Editor. This will provide you with a simple web interface to view the metrics. The second way is to create a Singleton of type [MetricsMonitorComponent](https://docs.unity3d.com/Packages/com.unity.netcode@latest/index.html?subfolder=/api/Unity.NetCode.MetricsMonitor.html) and populate it with the data points you want to monitor. diff --git a/Documentation~/time-synchronization.md b/Documentation~/time-synchronization.md index 6240d94..eb20820 100644 --- a/Documentation~/time-synchronization.md +++ b/Documentation~/time-synchronization.md @@ -19,7 +19,7 @@ The tick the client estimates the server will apply the commands on is called th For interpolated objects, the client should present them in a state it has received data for. This time is called **interpolation tick**. The `interpolation tick` is calculated as an offset in respect the `predicted tick`. That time offset is called **prediction delay**.
The `interpolation delay` is calculated by taking into account round trip time, jitter and packet arrival rate, all data that is generally available on the client. -We also add some additional time, based on the network tick rate, to make sure we can handle some packets being lost. You can visualize the time offsets and scales in the snapshot visualization tool, [NetDbg](ghost-snapshots#Snapshot-visualization-tool). +We also add some additional time, based on the network tick rate, to make sure we can handle some packets being lost. You can visualize the time offsets and scales in the snapshot visualization tool, [Network Debugger](ghost-snapshots#Snapshot-visualization-tool). The `NetworkTimeSystem` slowly adjusts both `prediction tick` and `interpolation delay` in small increments to keep them advancing at a smooth rate and ensure that neither the interpolation tick nor the prediction tick goes back in time. diff --git a/Editor/CodeGenMenu.cs b/Editor/CodeGenMenu.cs index cf089fb..f244761 100644 --- a/Editor/CodeGenMenu.cs +++ b/Editor/CodeGenMenu.cs @@ -1,13 +1,12 @@ using UnityEditor; using UnityEngine; -using UnityEditorInternal; namespace Unity.NetCode.Editor { class CodeGenMenu { - [MenuItem("Multiplayer/Force Code Generation", priority = 10)] - static private void ForceRunCodeGen() + [MenuItem("Assets/Multiplayer/Force Code Generation", priority = 1000)] + private static void ForceRunCodeGen() { EditorApplication.delayCall += () => { @@ -29,8 +28,8 @@ static private void ForceRunCodeGen() }; } - [MenuItem("Multiplayer/Open Source Generated Folder", priority = 12)] - static private void OpenSourceGeneratedFolder() + [MenuItem("Assets/Multiplayer/Open Source Generated Folder", priority = 1000)] + private static void OpenSourceGeneratedFolder() { if (!System.IO.File.Exists("Temp/NetCodeGenerated")) { diff --git a/Editor/MultiplayerPlayModeWindow.cs b/Editor/MultiplayerPlayModeWindow.cs index 4c6556c..eb5c97b 100644 --- a/Editor/MultiplayerPlayModeWindow.cs +++ b/Editor/MultiplayerPlayModeWindow.cs @@ -141,7 +141,7 @@ internal class MultiplayerPlayModeWindow : EditorWindow, IHasCustomMenu static GUILayoutOption s_RightButtonWidth = GUILayout.Width(120); private int m_PreviousFrameCount; - [MenuItem("Multiplayer/Window: PlayMode Tools", priority = 50)] + [MenuItem("Window/Multiplayer/PlayMode Tools", priority = 3007)] private static void ShowWindow() { GetWindow(false, k_Title, true); @@ -227,7 +227,7 @@ void PlayModeUpdate() } } - [MenuItem("Multiplayer/Toggle Lag Spike Simulation _F12", priority = 51)] + [MenuItem("Window/Multiplayer/Toggle Lag Spike Simulation _F12", priority = 3007)] static void ToggleLagSpikeSimulatorShortcut() { if (ClientServerBootstrap.ClientWorld != null) diff --git a/Editor/NetcodeConfigEditor.cs b/Editor/NetcodeConfigEditor.cs index e22e0a4..a1936dc 100644 --- a/Editor/NetcodeConfigEditor.cs +++ b/Editor/NetcodeConfigEditor.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Unity.Collections; using Unity.NetCode.Hybrid; @@ -39,7 +38,6 @@ private static NetCodeConfig SavedConfig } } - [MenuItem("Multiplayer/Create NetcodeConfig Asset", priority = 100)] internal static void CreateNetcodeSettingsAsset() { var assetPath = AssetDatabase.GenerateUniqueAssetPath("Assets/NetcodeConfig.asset"); @@ -84,10 +82,10 @@ public static SettingsProvider CreateNetcodeConfigSettingsProvider() { // First parameter is the path in the Settings window. // Second parameter is the scope of this setting: it only appears in the Project Settings window. - var provider = new SettingsProvider("Project/NetCodeConfig.asset", SettingsScope.Project) + var provider = new SettingsProvider("Project/Multiplayer", SettingsScope.Project) { // By default the last token of the path is used as display name if no label is provided. - label = "NetCode for Entities", + label = "Multiplayer", // Create the SettingsProvider and initialize its drawing (IMGUI) function in place: guiHandler = (searchContext) => { diff --git a/Editor/SourceGeneratorSettings.cs b/Editor/SourceGeneratorSettings.cs index efdb6bb..06866fd 100644 --- a/Editor/SourceGeneratorSettings.cs +++ b/Editor/SourceGeneratorSettings.cs @@ -11,7 +11,7 @@ static class SourceGeneratorSettings /// Create the Default.globalconfig file in the Assets folder root. /// /// - [MenuItem("Multiplayer/Create SourceGenerator AnalyzerConfig", priority = 101)] + [MenuItem("Assets/Create/Multiplayer/SourceGenerator AnalyzerConfig", priority = 1)] static void CreateGlobalConfig() { var assetPath = Path.Combine(Application.dataPath, "Default.globalconfig"); diff --git a/Runtime/Authoring/Hybrid/NetCodeServerSettings.cs b/Runtime/Authoring/Hybrid/NetCodeServerSettings.cs index 1a3c1cb..afac4c2 100644 --- a/Runtime/Authoring/Hybrid/NetCodeServerSettings.cs +++ b/Runtime/Authoring/Hybrid/NetCodeServerSettings.cs @@ -123,6 +123,8 @@ internal class ServerSettings : DotsPlayerSettingsProvider { VisualElement m_BuildSettingsContainer; + public override string ProviderPath => "Project/Multiplayer/Build"; + public override int Importance { get { return 1; } @@ -140,6 +142,8 @@ protected override IEntitiesPlayerSettings DoGetSettingAsset() public override void OnActivate(DotsGlobalSettings.PlayerType type, VisualElement rootElement) { + DotsGlobalSettings.Instance.ServerProvider.ProviderPath = "Project/Multiplayer/Build"; + rootElement.RegisterCallback(OnAttachToPanel); rootElement.RegisterCallback(OnDetachFromPanel); diff --git a/Runtime/ClientServerWorld/ClientServerBootstrap.cs b/Runtime/ClientServerWorld/ClientServerBootstrap.cs index 9f706a7..2570b97 100644 --- a/Runtime/ClientServerWorld/ClientServerBootstrap.cs +++ b/Runtime/ClientServerWorld/ClientServerBootstrap.cs @@ -273,7 +273,14 @@ public static World CreateClientWorld(string name) #endif } - internal static bool TryFindAutoConnectEndPoint(out NetworkEndpoint autoConnectEp) + /// + /// Optional client bootstrap helper method, so your custom bootstrap flows can copy this subset of auto-connect logic. + /// Reads , and checks for default AutoConnect arguments if valid. + /// + /// A valid endpoint for auto-connection. + /// True if the auto-connect Endpoint is specified for this given . + /// Thrown if the RequestedPlayType enum has an unknown value. + public static bool TryFindAutoConnectEndPoint(out NetworkEndpoint autoConnectEp) { autoConnectEp = default; @@ -319,11 +326,11 @@ internal static bool TryFindAutoConnectEndPoint(out NetworkEndpoint autoConnectE } /// - /// Returns true if user code has specified both an and . + /// Returns true if user code has specified both an and set. /// /// The resulting combined . /// True if user code has specified both an and . - internal static bool HasDefaultAddressAndPortSet(out NetworkEndpoint autoConnectEp) + public static bool HasDefaultAddressAndPortSet(out NetworkEndpoint autoConnectEp) { if (AutoConnectPort != 0 && DefaultConnectAddress != NetworkEndpoint.AnyIpv4) { @@ -384,7 +391,7 @@ public static World CreateServerWorld(string name) /// public static NetworkEndpoint DefaultListenAddress = NetworkEndpoint.AnyIpv4; /// - /// Check if the server should start listening for incoming connection automatically after the world has been created. + /// Denotes if the server should start listening for incoming connection automatically after the world has been created. /// /// If the is set, the server should start listening for connection using the /// and . @@ -392,7 +399,8 @@ public static World CreateServerWorld(string name) /// public static bool WillServerAutoListen => AutoConnectPort != 0; /// - /// The current modality + /// The current modality. + /// . /// public enum PlayType { @@ -415,30 +423,38 @@ public enum PlayType /// Server = 2 } -#if UNITY_EDITOR + /// /// The current play mode, used to configure drivers and worlds. - /// - public static PlayType RequestedPlayType => MultiplayerPlayModePreferences.RequestedPlayType; - /// - /// The number of thin clients to create. Only available in the Editor. - /// - public static int RequestedNumThinClients => MultiplayerPlayModePreferences.RequestedNumThinClients; + ///
- In editor, this is determined by the PlayMode tools window. + ///
- In builds, this is determined by the platform (and thus UNITY_SERVER and UNITY_CLIENT defines), + /// which in turn are controlled by the Project Settings. + /// + /// + /// In builds, use this flag to determine whether your build supports running as a client, + /// a server, or both. + /// + public static PlayType RequestedPlayType + { + get + { +#if UNITY_EDITOR + return MultiplayerPlayModePreferences.RequestedPlayType; #elif UNITY_SERVER - /// - /// The current play mode, used to configure drivers and worlds. - /// - public static PlayType RequestedPlayType => PlayType.Server; + return PlayType.Server; #elif UNITY_CLIENT - /// - /// The current play mode, used to configure drivers and worlds. - /// - public static PlayType RequestedPlayType => PlayType.Client; + return PlayType.Client; #else + return PlayType.ClientAndServer; +#endif + } + } + +#if UNITY_EDITOR /// - /// The current play mode, used to configure drivers and worlds. + /// The number of thin clients to create. Only available in the Editor. /// - public static PlayType RequestedPlayType => PlayType.ClientAndServer; + public static int RequestedNumThinClients => MultiplayerPlayModePreferences.RequestedNumThinClients; #endif //Burst compatible counters that be used in job or ISystem to check when clients or server worlds are present internal struct ServerClientCount diff --git a/Runtime/Connection/NetworkDriverStore.cs b/Runtime/Connection/NetworkDriverStore.cs index d5c32f3..5b56eb9 100644 --- a/Runtime/Connection/NetworkDriverStore.cs +++ b/Runtime/Connection/NetworkDriverStore.cs @@ -82,7 +82,7 @@ internal void StopListening() /// Struct that contains a the version of the /// and relative pipelines. /// - internal struct Concurrent + public struct Concurrent { /// /// The version of the network driver. @@ -120,6 +120,7 @@ public void Dispose() internal NetworkDriverData m_Driver1; internal NetworkDriverData m_Driver2; private int m_numDrivers; + private int m_Finalized; /// /// The fixed capacity of the driver container. @@ -174,6 +175,27 @@ public bool IsAnyUsingSimulator } } + /// + /// Return true if there is at least one driver listening for incoming connections. + /// + public bool HasListeningInterfaces + { + get + { + for (var i = FirstDriver; i <= LastDriver; ++i) + { + ref readonly var driverInstance = ref GetDriverInstanceRO(i); + if (driverInstance.driver.IsCreated && driverInstance.driver.Listening) + return true; + } + return false; + } + } + + /// + /// Denote if the store has at least one driver registered + /// + public bool IsCreated => m_numDrivers > 0 && m_Driver0.IsCreated; /// /// Add a new driver to the store. Throw exception if all drivers slot are already occupied or the driver is not created/valid @@ -188,7 +210,8 @@ public int RegisterDriver(TransportType driverType, in NetworkDriverInstance dri throw new InvalidOperationException("Cannot register non valid driver (IsCreated == false)"); if (m_numDrivers == Capacity) throw new InvalidOperationException("Cannot register more driver. All slot are already used"); - + if(m_Finalized != 0) + throw new InvalidOperationException("It is invalid to register a NetworkDriver instance to an already finalized NetworkDriverStore.\nIn order to register a new driver, you need to create a new NetworkDriverStore or invoke the RegisterNetworkDriver before the store instance is assigned to NetworkStreamDriver."); int nextDriverId = FirstDriverId + m_numDrivers; ++m_numDrivers; ref var driverRef = ref GetDriverDataRW(nextDriverId); @@ -199,23 +222,15 @@ public int RegisterDriver(TransportType driverType, in NetworkDriverInstance dri return nextDriverId; } - /// - /// Reset the current state of the store and must be called before registering the drivers. - /// - internal void BeginDriverRegistration() - { - m_numDrivers = 0; - m_Driver0.Dispose(); - m_Driver1.Dispose(); - m_Driver2.Dispose(); - } /// /// Finalize the registration phase by initializing all missing driver instances with a NullNetworkInterface. /// This final step is necessary to make the job safety system able to track all the safety handles. /// - internal void EndDriverRegistration() + internal void FinalizeDriverStore() { + if (m_Finalized != 0) + throw new InvalidOperationException("FinalizeDriverStore is called on already finalized NetworkDriverStore instance."); //The ifdef is to prevent allocating driver internal data when not necessary. //Allocating all drivers is necessary only in case safety handles are enabled. #if ENABLE_UNITY_COLLECTIONS_CHECKS @@ -396,6 +411,7 @@ internal unsafe ref NetworkDriverData GetDriverDataRW(int driverId) /// Invoke the delegate on all registered drivers. /// /// + [Obsolete("The ForEachDriver has been deprecated. Please always iterate over the driver using a for loop, using the FirstDriver and LastDriver ids instead.")] public void ForEachDriver(DriverVisitor visitor) { if (m_numDrivers == 0) @@ -472,7 +488,7 @@ public void Dispose() { } /// /// The concurrent version of the DriverStore. Contains the concurrent copy of the drivers and relative pipelines. /// - internal struct ConcurrentDriverStore + public struct ConcurrentDriverStore { internal NetworkDriverStore.Concurrent m_Concurrent0; internal NetworkDriverStore.Concurrent m_Concurrent1; @@ -482,8 +498,8 @@ internal struct ConcurrentDriverStore /// Get the concurrent driver with the given driver id /// /// the id of the driver. Must always greater or equals - /// - /// + /// the concurrent version of the NetworkdDriverStore + /// Throws if driverId is out of range. public NetworkDriverStore.Concurrent GetConcurrentDriver(int driverId) { switch (driverId) diff --git a/Runtime/Connection/NetworkStreamDriver.cs b/Runtime/Connection/NetworkStreamDriver.cs index 08df085..cfd5e36 100644 --- a/Runtime/Connection/NetworkStreamDriver.cs +++ b/Runtime/Connection/NetworkStreamDriver.cs @@ -45,7 +45,12 @@ internal NetworkStreamDriver(void* driverStore, NativeReference numIds, Nat /// Copying such a large struct is expensive, prefer ref var driverStore = ref networkStreamDriver.RefRW.DriverStore; syntax. /// public ref NetworkDriverStore DriverStore => ref UnsafeUtility.AsRef(m_DriverPointer).DriverStore; - internal ref ConcurrentDriverStore ConcurrentDriverStore => ref UnsafeUtility.AsRef(m_DriverPointer).ConcurrentDriverStore; + + /// + /// A reference to the concurrent version of the (), used for send/receiving + /// messages in jobs. + /// + public ref ConcurrentDriverStore ConcurrentDriverStore => ref UnsafeUtility.AsRef(m_DriverPointer).ConcurrentDriverStore; /// /// Convenience. Records the DriverStore used in the latest call to or . @@ -167,6 +172,10 @@ private NetworkEndpoint SanitizeConnectAddress(in NetworkEndpoint endpoint, int /// public bool Listen(NetworkEndpoint endpoint) { + //Check that at least the first driver have been created. This is a sufficient condition. + if (!DriverStore.m_Driver0.IsCreated) + throw new InvalidOperationException($"You cannot call Listen on a NetworkStreamDriver for which the DriverStore have been not created. Please ensure the NetworkDriverStore is setup before calling the Listen method."); + // Switching to server mode. Start listening all the driver interfaces var errors = new FixedList32Bytes(); //It is possible to listen on a specific address/port. However, for IPC drivers there is a restriction: @@ -208,6 +217,9 @@ public bool Listen(NetworkEndpoint endpoint) /// Throw an exception if the driver is not created or if multiple drivers are register public Entity Connect(EntityManager entityManager, NetworkEndpoint endpoint, Entity ent = default) { + if (!DriverStore.m_Driver0.IsCreated) + throw new InvalidOperationException($"You cannot call Connect on a NetworkStreamDriver for which the DriverStore have been not created. Please ensure the NetworkDriverStore is setup before calling the Connect method."); + var netDebugQuery = entityManager.CreateEntityQuery(new EntityQueryBuilder(Allocator.Temp).WithAll()); var netDebug = netDebugQuery.GetSingleton(); @@ -355,5 +367,54 @@ internal DriverMigrationSystem.DriverStoreState StoreMigrationState() DriverState = NetworkStreamReceiveSystem.DriverState.Migrating; return driverStoreState; } + + /// + /// Reset the current by disposing the current instance and its associated + /// . + /// This method can be used to re-create and re-configure the driver after world has been created and before either + /// or has been called. + /// + /// + /// + /// var driverStore = new NetworkDriverStore(); + /// var constructor = NetworkStreamReceiveSystem.DriverConstructor; + /// constructor.CreateServerDriver(serverWorld, ref driverStore, netDebug); + /// var driver = EntityManager.CreateEntityQuery(typeof(NetworkStreamDriver)).GetSingleton<NetworkStreamDriver>(); + /// driver.ResetDriverStore(driverStore); + /// var listenEndPoint = NetworkEndpoint.AnyIpv4.WithPort(MyPort); + /// driver.Listen(listenEndPoint); + /// + /// + /// The world the NetworkStreamDriver singleton is part of. + /// The new driver store to use. + public void ResetDriverStore(WorldUnmanaged world, ref NetworkDriverStore driverStore) + { + if (UnsafeUtility.AddressOf(ref driverStore) == UnsafeUtility.AddressOf(ref DriverStore)) + { + //Try to self assign the same instance. Skip. I would say this is an error. Unfortunately, we can't catch the + //case where the NetworkDriverStore is copied on the stack and assigned. + return; + } + if (world.IsClient() && DriverStore.DriversCount > 1) + throw new InvalidOperationException($"Cannot assign the NetworkDriverStore to the NetworkStreamDriver for world {world.Name}. Client must configure the driver store to use ONLY ONE network driver, but the {nameof(driverStore)} instance passed as argument has been configured to use {driverStore.DriversCount} network drivers."); + + //If the driver is not the "default" (no registered driver and the first interface is not created) it is valid to dispose the current driver. + //For example: the server can dispose the driver to stop listening (it is actually the only way to stop listening). + //In all cases, it is not valid to dispose a driver if there are connections. + if (DriverStore.IsCreated) + { + using var connectionQuery = world.EntityManager.CreateEntityQuery(typeof(NetworkStreamConnection)); + if (!connectionQuery.IsEmpty) + throw new InvalidOperationException($"Cannot assign the NetworkDriverStore to the NetworkStreamDriver for world {world.Name} because there are NetworkStreamConnection entities.\nPlease ensure you are setting up the drivers after you disconnected all the connections and have them properly cleanup by the NetworkStreamReceiveSystem. This will usually require at least one world update (because NetworkStreamConnection are cleanup component)."); + } + + //reset the current driver store any any case. This is a no-op if the current instance is already destroyed. + DriverStore.Dispose(); + //finalize the driver store by adding any empty drivers. Calling Begin is not required, it is just finalizing the driver creation. + //Modify an existing driver store is also prohibited. Like calling RegisterDriver after having the driver finalized. + driverStore.FinalizeDriverStore(); + DriverStore = driverStore; + ConcurrentDriverStore = driverStore.ToConcurrent(); + } } } diff --git a/Runtime/Connection/NetworkStreamReceiveSystem.cs b/Runtime/Connection/NetworkStreamReceiveSystem.cs index 24244ee..f230b05 100644 --- a/Runtime/Connection/NetworkStreamReceiveSystem.cs +++ b/Runtime/Connection/NetworkStreamReceiveSystem.cs @@ -335,23 +335,19 @@ public void OnCreate(ref SystemState state) else { driverStore = new NetworkDriverStore(); - driverStore.BeginDriverRegistration(); if (state.World.IsServer()) DriverConstructor.CreateServerDriver(state.World, ref driverStore, SystemAPI.GetSingleton()); else DriverConstructor.CreateClientDriver(state.World, ref driverStore, SystemAPI.GetSingleton()); - driverStore.EndDriverRegistration(); } m_DriverPointers = (IntPtr)UnsafeUtility.Malloc(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), Allocator.Persistent); - - ref var store = ref UnsafeUtility.AsRef((void*)m_DriverPointers); - store.DriverStore = driverStore; - store.ConcurrentDriverStore = driverStore.ToConcurrent(); - + UnsafeUtility.MemClear((void*)m_DriverPointers, UnsafeUtility.SizeOf()); var networkStreamEntity = state.EntityManager.CreateEntity(ComponentType.ReadWrite()); state.EntityManager.SetName(networkStreamEntity, "NetworkStreamDriver"); SystemAPI.SetSingleton(new NetworkStreamDriver((void*)m_DriverPointers, m_NumNetworkIds, m_FreeNetworkIds, lastEp, m_ConnectionEvents, m_ConnectionEvents.AsReadOnly())); + SystemAPI.GetSingleton().ResetDriverStore(state.WorldUnmanaged, ref driverStore); + state.RequireForUpdate(); state.RequireForUpdate(); state.RequireForUpdate(); diff --git a/Runtime/NetCodeConfig.cs b/Runtime/NetCodeConfig.cs index f157a95..9ac0d04 100644 --- a/Runtime/NetCodeConfig.cs +++ b/Runtime/NetCodeConfig.cs @@ -8,7 +8,7 @@ namespace Unity.NetCode /// Config file, allowing the package user to tweak netcode variables without having to write code. /// Create as many instances as you like. /// - [CreateAssetMenu(menuName = "NetCode/NetCodeConfig Asset", fileName = "NetCodeConfig")] + [CreateAssetMenu(menuName = "Multiplayer/NetCodeConfig Asset", fileName = "NetCodeConfig", order = 1)] public class NetCodeConfig : ScriptableObject, IComparable { /// diff --git a/Runtime/Snapshot/GhostPredictionSystemGroup.cs b/Runtime/Snapshot/GhostPredictionSystemGroup.cs index 623ae60..20b8dcc 100644 --- a/Runtime/Snapshot/GhostPredictionSystemGroup.cs +++ b/Runtime/Snapshot/GhostPredictionSystemGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using Unity.Assertions; using Unity.Collections; using Unity.Core; @@ -7,6 +8,8 @@ using Unity.Mathematics; using Unity.Burst; using Unity.Burst.Intrinsics; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; namespace Unity.NetCode { @@ -52,7 +55,7 @@ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useE Assert.IsFalse(useEnabledMask); var predicted = chunk.GetNativeArray(ref predictedHandle); - + var enabledMask = chunk.GetEnabledMask(ref simulateHandle); if (chunk.Has(ref linkedEntityGroupHandle)) { var linkedEntityGroupArray = chunk.GetBufferAccessor(ref linkedEntityGroupHandle); @@ -60,9 +63,10 @@ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useE for(int i = 0, chunkEntityCount = chunk.Count; i < chunkEntityCount; i++) { var shouldPredict = predicted[i].ShouldPredict(tick); - if (chunk.IsComponentEnabled(ref simulateHandle, i) != shouldPredict) + var isPredicting = enabledMask.GetBit(i); + enabledMask[i] = shouldPredict; + if (isPredicting != shouldPredict) { - chunk.SetComponentEnabled(ref simulateHandle, i, shouldPredict); var linkedEntityGroup = linkedEntityGroupArray[i]; for (int child = 1; child < linkedEntityGroup.Length; ++child) { @@ -76,9 +80,7 @@ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useE else { for(int i = 0, chunkEntityCount = chunk.Count; i < chunkEntityCount; i++) - { - chunk.SetComponentEnabled(ref simulateHandle, i, predicted[i].ShouldPredict(tick)); - } + enabledMask[i] = predicted[i].ShouldPredict(tick); } } } @@ -104,6 +106,50 @@ public void OnUpdate(ref SystemState state) } } + [RequireMatchingQueriesForUpdate] + [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)] + [UpdateInGroup(typeof(PredictedSimulationSystemGroup), OrderLast=true)] + [BurstCompile] + internal partial struct GhostPredictionEnableSimulateSystem : ISystem + { + ComponentTypeHandle m_SimulateHandle; + private EntityQuery m_GhostQuery; + + [BurstCompile] + public void OnCreate(ref SystemState state) + { + m_SimulateHandle = state.GetComponentTypeHandle(); + var builder = new EntityQueryBuilder(Allocator.Temp) + .WithDisabled() + .WithAny(); + m_GhostQuery = state.GetEntityQuery(builder); + } + [BurstCompile] + struct EnableAllPredictedGhostSimulate : IJobChunk + { + public ComponentTypeHandle simulateHandle; + public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) + { + var enabledMask = chunk.GetEnabledMask(ref simulateHandle); + for(int i=0;i(); + if (netTime.IsFinalPredictionTick) + { + m_SimulateHandle.Update(ref state); + state.Dependency = new EnableAllPredictedGhostSimulate() + { + simulateHandle = m_SimulateHandle, + }.ScheduleParallel(m_GhostQuery, state.Dependency); + } + } + } + class NetcodeServerPredictionRateManager : IRateManager { private EntityQuery m_NetworkTimeQuery; @@ -154,7 +200,7 @@ unsafe class NetcodeClientPredictionRateManager : IRateManager private EntityQuery m_UniqueInputTicksQuery; private EntityQuery m_GhostQuery; - private EntityQuery m_GhostChildQuery; + //private EntityQuery m_GhostChildQuery; private NetworkTick m_LastFullPredictionTick; @@ -199,16 +245,17 @@ internal NetcodeClientPredictionRateManager(ComponentSystemGroup group) var builder = new EntityQueryDesc { - All = new[]{ComponentType.ReadWrite(), ComponentType.ReadOnly()}, + All = new[]{ComponentType.ReadWrite()}, + Any = new []{ComponentType.ReadOnly(), ComponentType.ReadOnly()}, Options = EntityQueryOptions.IgnoreComponentEnabledState }; m_GhostQuery = group.World.EntityManager.CreateEntityQuery(builder); - builder = new EntityQueryDesc - { - All = new[]{ComponentType.ReadWrite(), ComponentType.ReadOnly()}, - Options = EntityQueryOptions.IgnoreComponentEnabledState - }; - m_GhostChildQuery = group.World.EntityManager.CreateEntityQuery(builder); + // builder = new EntityQueryDesc + // { + // All = new[]{ComponentType.ReadWrite(), ComponentType.ReadOnly()}, + // Options = EntityQueryOptions.IgnoreComponentEnabledState + // }; + //m_GhostChildQuery = group.World.EntityManager.CreateEntityQuery(builder); } public bool ShouldGroupUpdate(ComponentSystemGroup group) { @@ -300,7 +347,6 @@ public bool ShouldGroupUpdate(ComponentSystemGroup group) networkTime.Flags &= ~(NetworkTimeFlags.IsFinalPredictionTick|NetworkTimeFlags.IsFinalFullPredictionTick|NetworkTimeFlags.IsFirstTimeFullyPredictingTick); group.World.EntityManager.SetComponentEnabled(m_GhostQuery, false); - group.World.EntityManager.SetComponentEnabled(m_GhostChildQuery, false); m_ClientTickRateQuery.TryGetSingleton(out var clientTickRate); if (clientTickRate.MaxPredictionStepBatchSizeRepeatedTick < 1) @@ -378,8 +424,6 @@ public bool ShouldGroupUpdate(ComponentSystemGroup group) networkTime.PredictedTickIndex++; return true; } - group.World.EntityManager.SetComponentEnabled(m_GhostQuery, true); - group.World.EntityManager.SetComponentEnabled(m_GhostChildQuery, true); #if UNITY_EDITOR || NETCODE_DEBUG if (!networkTime.IsFinalPredictionTick) throw new InvalidOperationException("IsFinalPredictionTick should not be set before executing the final prediction tick"); diff --git a/Runtime/Snapshot/GhostPrefabCreation.cs b/Runtime/Snapshot/GhostPrefabCreation.cs index ea8d6e3..fa42b93 100644 --- a/Runtime/Snapshot/GhostPrefabCreation.cs +++ b/Runtime/Snapshot/GhostPrefabCreation.cs @@ -121,6 +121,12 @@ public struct Config /// public FixedString64Bytes Name; /// + /// Optional UUID5 identifier used to unique determine the ghost type. By default that ghost type of the generated prefab + /// is calculated using the SHA1 hash of the mandatory property (combined with some unique GUID prefix). + /// If user provide a non-default ghost unique UUID5 guid the ghost will use that instead. + /// + public Hash128 UUID5GhostType; + /// /// Higher importance means the ghost will be sent more frequently if there is not enough bandwidth to send everything. /// public int Importance; @@ -250,6 +256,29 @@ public int Compare(ComponentType x, ComponentType y) return 0; } } + + /// + /// Convert a general to a proper UUID5 hash format by enforcing bits and version + /// to be set. + /// + /// the hash to convert to UUID5 format + /// a new hash with the appropriate bytes set as specified by the RFC 4122 + public static Hash128 ConvertHash128ToUUID5(Hash128 hash128) + { + return new Hash128( + hash128.Value.x, + (hash128.Value.y & (~0xf000u)) | 0x5000u, // Set version to 5 + (hash128.Value.z & (0x3fffffffu)) | 0x80000000u, // Set upper bits to 1 and 0 + hash128.Value.w); + } + + private static bool ValidateIsUUID5(this ref GhostType ghostType) + { + //verify version is 5 and upper bits are 10 + return (ghostType.guid1 & 0xf000u) == 0x5000 && + (ghostType.guid2 & 0xC0000000u) == 0x80000000; + } + internal unsafe struct SHA1 { private void UpdateABCDE(int i, ref uint a, ref uint b, ref uint c, ref uint d, ref uint e, uint f, uint k) @@ -374,16 +403,10 @@ public SHA1(in FixedString128Bytes str) } } - public GhostType ToGhostType() + public Hash128 ToHash128() { // Construct a guid, store it in the GhostType - return new GhostType - { - guid0 = h0, - guid1 = (h1 & (~0xf000u)) | 0x5000u, // Set version to 5 - guid2 = (h2 & (0x3fffffffu)) | 0x80000000u, // Set upper bits to 1 and 0 - guid3 = h3 - }; + return new Hash128(h0, h1, h2, h3); } private fixed uint words[80]; @@ -905,10 +928,21 @@ public static void ConvertToGhostPrefab(EntityManager entityManager, Entity pref NetcodeConversionTarget target = (entityManager.World.IsServer()) ? NetcodeConversionTarget.Server : NetcodeConversionTarget.Client; // Calculate a uuid v5 using the guid of this .cs file as namespace and the prefab name as name. See rfc 4122 for more info on uuid5 // TODO: should probably be the raw bytes from the namespace guid + name - var uuid5 = new SHA1($"f17641b8-279a-94b1-1b84-487e72d49ab5{config.Name}"); - // I need an unique identifier and should not clash with any loaded prefab, use uuid5 with a namespace + ghost name - var ghostType = uuid5.ToGhostType(); - + GhostType ghostType; + if (config.UUID5GhostType != default) + { + ghostType = GhostType.FromHash128(config.UUID5GhostType); +#if NETCODE_DEBUG + if (!ghostType.ValidateIsUUID5()) + throw new InvalidOperationException($"The custom UUID5 ghost type {config.UUID5GhostType} is not a valid UUID5 compliant unique identifier. Please refer to https://datatracker.ietf.org/doc/html/rfc4122 for more details"); +#endif + } + else + { + var uuid5 = new SHA1($"f17641b8-279a-94b1-1b84-487e72d49ab5{config.Name}"); + // I need an unique identifier and should not clash with any loaded prefab, use uuid5 with a namespace + ghost name + ghostType = GhostType.FromHash128(ConvertHash128ToUUID5(uuid5.ToHash128())); + } //This should be present only for prefabs. FinalizePrefabComponents is also called for not prefab entities so it should not //be added there. if(target != NetcodeConversionTarget.Server && config.SupportedGhostModes != GhostModeMask.Interpolated) diff --git a/Runtime/SourceGenerators/Source~/Documentation/templates-spec.md b/Runtime/SourceGenerators/Source~/Documentation/templates-spec.md index 3bf5693..8564587 100644 --- a/Runtime/SourceGenerators/Source~/Documentation/templates-spec.md +++ b/Runtime/SourceGenerators/Source~/Documentation/templates-spec.md @@ -60,22 +60,22 @@ The following variables and regions can be used by the user when writing custom |GHOST_USING|the using statement list| |GHOST_MASK_INDEX|the current bit index in the change mask. From 0 to 31| ### REGIONS -||| -|------|------| -|GHOST_FIELD| fields that will be added the snapshot for this type | -|GHOST_IMPORTS| user defined using statement that should be included in the serializer code| -|GHOST_RESTORE_FROM_BACKUP|| -|GHOST_PREDICT| code that calculate the predicted value for the current field | -|GHOST_READ| code that deserialise the field value for the byte stream | -|GHOST_WRITE| code that serialize the field value to the byte stream | -|GHOST_COPY_TO_SNAPSHOT| contains the code to copy component field data to the snapshot struct | -|COPY_FROM_SNAPSHOT_SETUP| placeholder region that will contains the code for setting the serialization based if the struct is a buffer or component| -|GHOST_COPY_FROM_SNAPSHOT|copied into the COPY_FROM_SNAPSHOT_SETUP| -|GHOST_COPY_FROM_BUFFER | copied into the COPY_FROM_SNAPSHOT_SETUP| -|GHOST_COPY_FROM_SNAPSHOT_INTERPOLATE_SETUP|optional region, can contains initialization code used by the GHOST_COPY_FROM_SNAPSHOT_INTERPOLATE region| -|GHOST_COPY_FROM_SNAPSHOT_INTERPOLATE_DISTSQ|calculate the current field's square distance in between the Before and After snapshots | -|GHOST_COPY_FROM_SNAPSHOT_INTERPOLATE|code for interpolate the field value in between the Before and After snapshot values| -|GHOST_CALCULATE_CHANGE_MASK|contains the code to check and update the change mask bits| -|GHOST_CALCULATE_CHANGE_MASK_ZERO|contains the code to check and set the change mask bits. Used when the mask is reset or after 32 mask bits has been written| -|GHOST_REPORT_PREDICTION_ERROR|the code tha calculate the error (as distance usually) in between the current and predicted value for the current field| -|GHOST_GET_PREDICTION_ERROR_NAME|the error name, will be used in the NetDbgTool or for debugging purpose| +|| | +|------|-----------------------------------------------------------------------------------------------------------------------------| +|GHOST_FIELD| fields that will be added the snapshot for this type | +|GHOST_IMPORTS| user defined using statement that should be included in the serializer code | +|GHOST_RESTORE_FROM_BACKUP| | +|GHOST_PREDICT| code that calculate the predicted value for the current field | +|GHOST_READ| code that deserialise the field value for the byte stream | +|GHOST_WRITE| code that serialize the field value to the byte stream | +|GHOST_COPY_TO_SNAPSHOT| contains the code to copy component field data to the snapshot struct | +|COPY_FROM_SNAPSHOT_SETUP| placeholder region that will contains the code for setting the serialization based if the struct is a buffer or component | +|GHOST_COPY_FROM_SNAPSHOT| copied into the COPY_FROM_SNAPSHOT_SETUP | +|GHOST_COPY_FROM_BUFFER | copied into the COPY_FROM_SNAPSHOT_SETUP | +|GHOST_COPY_FROM_SNAPSHOT_INTERPOLATE_SETUP| optional region, can contains initialization code used by the GHOST_COPY_FROM_SNAPSHOT_INTERPOLATE region | +|GHOST_COPY_FROM_SNAPSHOT_INTERPOLATE_DISTSQ| calculate the current field's square distance in between the Before and After snapshots | +|GHOST_COPY_FROM_SNAPSHOT_INTERPOLATE| code for interpolate the field value in between the Before and After snapshot values | +|GHOST_CALCULATE_CHANGE_MASK| contains the code to check and update the change mask bits | +|GHOST_CALCULATE_CHANGE_MASK_ZERO| contains the code to check and set the change mask bits. Used when the mask is reset or after 32 mask bits has been written | +|GHOST_REPORT_PREDICTION_ERROR| the code tha calculate the error (as distance usually) in between the current and predicted value for the current field | +|GHOST_GET_PREDICTION_ERROR_NAME| the error name, will be used in the Network Debugger or for debugging purpose | diff --git a/Runtime/Stats/GhostStatsSystem.cs b/Runtime/Stats/GhostStatsSystem.cs index 82b7eee..4332f01 100644 --- a/Runtime/Stats/GhostStatsSystem.cs +++ b/Runtime/Stats/GhostStatsSystem.cs @@ -20,7 +20,7 @@ namespace Unity.NetCode class GhostStatsConnection : IDisposable { #if UNITY_EDITOR - [MenuItem("Multiplayer/Window: NetDbg (Browser)", priority = 75)] + [MenuItem("Window/Multiplayer/Network Debugger (Browser)", priority = 3007)] public static void OpenDebugger() { System.Diagnostics.Process.Start(Path.GetFullPath("Packages/com.unity.netcode/Runtime/Stats/netdbg.html")); diff --git a/Runtime/Stats/netdbg.html b/Runtime/Stats/netdbg.html index cab3080..67d0371 100644 --- a/Runtime/Stats/netdbg.html +++ b/Runtime/Stats/netdbg.html @@ -1,7 +1,7 @@ -Unity NetDbg +Unity Network Debugger