This repository has been archived on 2025-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs
Unity Technologies c2664dfb7f com.unity.netcode.gameobjects@1.11.0
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.11.0] - 2024-08-20

### Added

- Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3005)

### Fixed

- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3011)
- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3005)
- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3005)
- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#2999)
- Fixed issue where `FixedStringSerializer<T>` was using `NetworkVariableSerialization<byte>.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#2992)

### Changed

- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)
- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)
2024-08-20 00:00:00 +00:00

270 lines
11 KiB
C#

using System;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// A variable that can be synchronized over the network.
/// </summary>
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
[Serializable]
[GenerateSerializationForGenericParameter(0)]
public class NetworkVariable<T> : NetworkVariableBase
{
/// <summary>
/// Delegate type for value changed event
/// </summary>
/// <param name="previousValue">The value before the change</param>
/// <param name="newValue">The new value</param>
public delegate void OnValueChangedDelegate(T previousValue, T newValue);
/// <summary>
/// The callback to be invoked when the value gets changed
/// </summary>
public OnValueChangedDelegate OnValueChanged;
public delegate bool CheckExceedsDirtinessThresholdDelegate(in T previousValue, in T newValue);
public CheckExceedsDirtinessThresholdDelegate CheckExceedsDirtinessThreshold;
public override bool ExceedsDirtinessThreshold()
{
if (CheckExceedsDirtinessThreshold != null && m_HasPreviousValue)
{
return CheckExceedsDirtinessThreshold(m_PreviousValue, m_InternalValue);
}
return true;
}
public override void OnInitialize()
{
base.OnInitialize();
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
}
/// <summary>
/// Constructor for <see cref="NetworkVariable{T}"/>
/// </summary>
/// <param name="value">initial value set that is of type T</param>
/// <param name="readPerm">the <see cref="NetworkVariableReadPermission"/> for this <see cref="NetworkVariable{T}"/></param>
/// <param name="writePerm">the <see cref="NetworkVariableWritePermission"/> for this <see cref="NetworkVariable{T}"/></param>
public NetworkVariable(T value = default,
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
{
m_InternalValue = value;
// Since we start with IsDirty = true, this doesn't need to be duplicated
// right away. It won't get read until after ResetDirty() is called, and
// the duplicate will be made there. Avoiding calling
// NetworkVariableSerialization<T>.Duplicate() is important because calling
// it in the constructor might not give users enough time to set the
// DuplicateValue callback if they're using UserNetworkVariableSerialization
m_PreviousValue = default;
}
/// <summary>
/// The internal value of the NetworkVariable
/// </summary>
[SerializeField]
private protected T m_InternalValue;
private protected T m_PreviousValue;
private bool m_HasPreviousValue;
private bool m_IsDisposed;
/// <summary>
/// The value of the NetworkVariable container
/// </summary>
/// <remarks>
/// When assigning collections to <see cref="Value"/>, unless it is a completely new collection this will not
/// detect any deltas with most managed collection classes since assignment of one collection value to another
/// is actually just a reference to the collection itself. <br />
/// To detect deltas in a collection, you should invoke <see cref="CheckDirtyState"/> after making modifications to the collection.
/// </remarks>
public virtual T Value
{
get => m_InternalValue;
set
{
if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId))
{
LogWritePermissionError();
return;
}
// Compare the Value being applied to the current value
if (!NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
{
T previousValue = m_InternalValue;
m_InternalValue = value;
SetDirty(true);
m_IsDisposed = false;
OnValueChanged?.Invoke(previousValue, m_InternalValue);
}
}
}
/// <summary>
/// Invoke this method to check if a collection's items are dirty.
/// The default behavior is to exit early if the <see cref="NetworkVariable{T}"/> is already dirty.
/// </summary>
/// <param name="forceCheck"> when true, this check will force a full item collection check even if the NetworkVariable is already dirty</param>
/// <remarks>
/// This is to be used as a way to check if a <see cref="NetworkVariable{T}"/> containing a managed collection has any changees to the collection items.<br />
/// If you invoked this when a collection is dirty, it will not trigger the <see cref="OnValueChanged"/> unless you set <param name="forceCheck"/> to true. <br />
/// </remarks>
public bool CheckDirtyState(bool forceCheck = false)
{
var isDirty = base.IsDirty();
// Compare the previous with the current if not dirty or forcing a check.
if ((!isDirty || forceCheck) && !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue))
{
SetDirty(true);
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
m_IsDisposed = false;
isDirty = true;
}
return isDirty;
}
internal ref T RefValue()
{
return ref m_InternalValue;
}
public override void Dispose()
{
if (m_IsDisposed)
{
return;
}
m_IsDisposed = true;
if (m_InternalValue is IDisposable internalValueDisposable)
{
internalValueDisposable.Dispose();
}
m_InternalValue = default;
if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable)
{
m_HasPreviousValue = false;
previousValueDisposable.Dispose();
}
m_PreviousValue = default;
}
~NetworkVariable()
{
Dispose();
}
/// <summary>
/// Gets Whether or not the container is dirty
/// </summary>
/// <returns>Whether or not the container is dirty</returns>
public override bool IsDirty()
{
// For most cases we can use the dirty flag.
// This doesn't work for cases where we're wrapping more complex types
// like INetworkSerializable, NativeList, NativeArray, etc.
// Changes to the values in those types don't call the Value.set method,
// so we can't catch those changes and need to compare the current value
// against the previous one.
if (base.IsDirty())
{
return true;
}
// Cache the dirty value so we don't perform this again if we already know we're dirty
// Unfortunately we can't cache the NOT dirty state, because that might change
// in between to checks... but the DIRTY state won't change until ResetDirty()
// is called.
var dirty = !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue);
SetDirty(dirty);
return dirty;
}
/// <summary>
/// Resets the dirty state and marks the variable as synced / clean
/// </summary>
public override void ResetDirty()
{
// Resetting the dirty value declares that the current value is not dirty
// Therefore, we set the m_PreviousValue field to a duplicate of the current
// field, so that our next dirty check is made against the current "not dirty"
// value.
if (IsDirty())
{
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
}
base.ResetDirty();
}
/// <summary>
/// Sets the <see cref="Value"/>, marks the <see cref="NetworkVariable{T}"/> dirty, and invokes the <see cref="OnValueChanged"/> callback
/// if there are subscribers to that event.
/// </summary>
/// <param name="value">the new value of type `T` to be set/></param>
private protected void Set(T value)
{
SetDirty(true);
m_InternalValue = value;
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
}
/// <summary>
/// Writes the variable to the writer
/// </summary>
/// <param name="writer">The stream to write the value to</param>
public override void WriteDelta(FastBufferWriter writer)
{
NetworkVariableSerialization<T>.WriteDelta(writer, ref m_InternalValue, ref m_PreviousValue);
}
/// <summary>
/// Reads value from the reader and applies it
/// </summary>
/// <param name="reader">The stream to read the value from</param>
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
{
// In order to get managed collections to properly have a previous and current value, we have to
// duplicate the collection at this point before making any modifications to the current.
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
NetworkVariableSerialization<T>.ReadDelta(reader, ref m_InternalValue);
// todo:
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
// would be stored in different fields
if (keepDirtyDelta)
{
SetDirty(true);
}
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
}
/// <inheritdoc />
public override void ReadField(FastBufferReader reader)
{
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
}
/// <inheritdoc />
public override void WriteField(FastBufferWriter writer)
{
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
}
}
}