Network Buffers
概述
Fusion為每個沒有StateAuthority
的NetworkObject
保留接收到的狀態快照的歷史記錄,其代表最近已知的權威狀態。對於每個本機模擬物件(無論是權威的還是預測的),都有一個額外的當前和上一個快照。
NetworkBehaviour
上的[Networked]
屬性提供了對這些快照中最新快照的直接存取(無論是在歷史記錄中還是在本機模擬狀態中)。
要獲取任何NetworkBehaviour
的兩個最新快照以及它們之間的縮放相對時間偏移(用於內插補點),請使用TryGetSnapshotsBuffers()
。
要存取快照中的單個屬性值,NetworkBehaviour
基礎類別既提供低層級資料存取,也提供類型化的PropertyReader
。
除此之外,網路行為允許使用ChangeDetector
類別以監視其網路狀態的變化。
NetworkBehaviourBuffer及PropertyReader
NetworkBehaviourBuffer
是Fusion內部資料存儲的抽象,它提供了一種有效的低成本唯讀方式來查看網路狀態快照。
對於高級用例,網路緩衝區提供了低層級的ReinterpretState<T>(offset)
方法,該方法允許將任何偏移量的緩衝區資料轉換為特定的資料類型。通常不建議這樣做,除非在效能極其敏感或需要對已連網狀態進行自訂解釋的情況下。
相反,Fusion提供了PropertyReader
類別。屬性讀取器將C#類型與命名屬性及其在緩衝區中的偏移量相關聯。屬性讀取器由Fusion內部快取,因此除非在高效能關鍵場景中,否則不需要本機快取。
要從NetworkBehaviourBuffer
讀取屬性,只需在緩衝區或讀取器上調用Read()
方法:
C#
var reader = GetPropertyReader<T>(nameof(SomeProperty));
T t = reader.Read(buffer);
// or
t = buffer.Read(reader);
對於從兩個緩衝區(通常是前一個快照和當前快照)讀取相同屬性的常見情況,Fusion提供了一個快捷方式:
C#
var reader = GetPropertyReader<T>(propertyname);
(previous,current) = reader.Read(previousBuffer, currentBuffer);
快照及內插補點
要存取NetworkBehaviourBuffer
的兩個最新快照,請調用TryGetSnapshotsBuffers()
。這將傳回兩個緩衝區以及它們的from
和to
刷新和內插補點偏移(alpha):
C#
if (TryGetSnapshotsBuffers(out var fromBuffer, out var toBuffer, out alpha))
{
var reader = GetPropertyReader<T>(nameof(State));
(from,to) = reader.Read(fromBuffer,toBuffer);
fromTick = fromBuffer.Tick;
toTick = toBuffer.Tick;
}
緩衝區可以按原樣使用,也可以使用傳回的內插補點偏移在兩者之間內插補點渲染時間精確值。
NetworkBehaviourBufferInterpolator
為了簡化內插補點,Fusion提供了NetworkBehaviourBufferInterpolator
結構。給定一個NetworkBehaviour
的結構,它將獲取快照緩衝區和屬性讀取器,並傳回命名屬性的內插補點值:
C#
var interpolator = new NetworkBehaviourBufferInterpolator(this);
Vector3 v = interpolator.Vector3(nameof(SomeVector3Property));
NetworkBehaviourBufferInterpolator
僅支援具有內插補點值的合理數學定義的類型,但可以使用C#擴展方法輕鬆新增對自訂類型的支援:
C#
public static class NBBIExtension
{
public static NameSpace.CustomType CustomType(this NetworkBehaviourBufferInterpolator interpolator, string propertyname)
{
(var from, var to) = interpolator.Behaviour.GetPropertyReader<NameSpace.CustomType>(propertyname).Read(interpolator.From, interpolator.To);
// Do custom interpolation betwen "from" and "to" using "interpolator.Alpha"
return CustomType.Interpolate(from,to,interpolator.Alpha);
}
}
ChangeDetector
Fusion提供了一種使用“ChangeDetector
追蹤網路狀態變化的方法。要獲取NetworkBehaviour
的變化檢測器,請在NetworkBehaviour
上調用GetChangeDetector(source, copy)
,並保留對傳回執行個體的參照:
C#
private ChangeDetector _changeDetector;
public override void Spawned()
{
_changeDetector = GetChangeDetector(ChangeDetector.Source.SimulationState);
}
提供的源參數決定了「新值」在時間線上的採樣位置。這可以是本機時間框架(SimulationState
),也可以是遠端時間框架(分別為SnapshotFrom
或SnapshotTo
)。
值得注意的是,SimulationState僅適用於模擬中包含的網路物件。預設下,這意味著本機同儕節點具有狀態或輸入授權的任何物件,但它也存在於已明確啟用模擬的物件中。
複製參數確定是否應在構造時複製更改檢測器的初始狀態(如果不是,則檢測更改的第一次調用將建立「之前」狀態,並始終返回一個空列舉器)。
衍生更改的「舊值」只是上次叫用檢測器時的先前值。因此,可以在任何執行點檢測到更改,無論是在FixedUpdateNetwork()
中的確切刷新邊界上,還是在Render()
甚至Update()
的中間渲染幀上。
每個建立的ChangeDetector
都保持自己的狀態,獨立於之前建立的檢測器,因此,多個變化檢測器可以在同一個NetworkBehaviour
上共存。
使用一個ChangeDetector
ChangeDetector
的主要入口點是DetectChanges()
方法,該方法傳回自上次調用該方法以來在給定時間範圍內所有已更改内容的列舉,以及之前和當前的緩衝區:
C#
foreach (var propertyname in _changeDetector.DetectChanges( this, out var previousBuffer, out var currentBuffer))
{
switch (propertyname)
{
case nameof(SomeProperty):
{
var reader = GetPropertyReader<T>(propertyname);
(previous,current) = reader.Read(previousBuffer, currentBuffer);
...
break;
}
}
}
如果不需要以前的值,則可以簡化為:
C#
foreach (var change in _changeDetector.DetectChanges(this))
{
switch (change)
{
case nameof(SomeProperty):
var current = SomeProperty;
....
break;
}
}
避免模版
NetworkBehaviourBuffer
和PropertyReader
的設計考慮了效能和完整性。它以最小的額外負荷提供了非常高的靈活性,但在行為具有許多屬性並且可能不關心單個屬性更改的情況下,它也可能導致大量的模版程式碼。
對於這些場景,在實施INetworkStruct
的結構中收集已連網屬性,並將每個這樣的結構視為單個屬性,可能是有益的。
NetworkBehaviourWithState
以下是一個基礎類別示例,它以一些靈活性換取易用性。透過將物件的整個狀態存儲在單個結構中,並提供一個方法來檢查該結構中是否有任何更改,同時傳回之前之後的值,可以將更改檢測簡化為單個方法調用。
同樣,可以一次存取所有屬性的快照,而無需顯式地處理屬性讀取器。
C#
public abstract class NetworkBehaviourWithState<T> : NetworkBehaviour where T : unmanaged, INetworkStruct
{
public abstract ref T State { get; }
private ChangeDetector _changesSimulation;
private ChangeDetector _changesFrom;
private ChangeDetector _changesTo;
protected bool TryGetStateChanges(out T previous, out T current, ChangeDetector.Source source = ChangeDetector.Source.SimulationState)
{
switch (source)
{
default:
case ChangeDetector.Source.SimulationState:
return TryGetStateChanges(source, ref _changesSimulation, out previous, out current);
case ChangeDetector.Source.SnapshotFrom:
return TryGetStateChanges(source, ref _changesFrom, out previous, out current);
case ChangeDetector.Source.SnapshotTo:
return TryGetStateChanges(source, ref _changesTo, out previous, out current);
}
}
private bool TryGetStateChanges(ChangeDetector.Source source, ref ChangeDetector changes, out T previous, out T current)
{
if(changes==null)
changes = GetChangeDetector(source);
if (changes != null)
{
foreach (var change in changes.DetectChanges(this, out var previousBuffer, out var currentBuffer))
{
switch (change)
{
case nameof(State):
var reader = GetPropertyReader<T>(change);
(previous,current) = reader.Read(previousBuffer, currentBuffer);
return true;
}
}
}
current = default;
previous = default;
return false;
}
protected bool TryGetStateSnapshots(out T from, out Tick fromTick, out T to, out Tick toTick, out float alpha)
{
if (TryGetSnapshotsBuffers(out var fromBuffer, out var toBuffer, out alpha))
{
var reader = GetPropertyReader<T>(nameof(State));
(from, to) = reader.Read(fromBuffer, toBuffer);
fromTick = fromBuffer.Tick;
toTick = toBuffer.Tick;
return true;
}
from = default;
to = default;
fromTick = default;
toTick = default;
return false;
}
}
示例用途
要使用該擴展程式,只需從中衍生,定義已連網狀態的結構並宣告抽象屬性:
C#
public class SomeBehaviour : NetworkBehaviourWithState<SomeBehaviour.NetworkState>
{
[Networked] public override ref NetworkState State => ref MakeRef<NetworkState>();
public struct NetworkState : INetworkStruct
{
public TickTimer SomeTimer;
public int SomeInt;
public Vector3 SomePoint;
}
public override void Render()
{
if ( TryGetStateChanges(out NetworkState old, out NetworkState current) )
{
// Something changed in the networked state - "old" and "current" has the before and after values.
}
if (TryGetStateSnapshots(out NetworkState from, out Tick fromTick, out NetworkState to, out Tick toTick, out float alpha))
{
// We're currently rendering the state between "from" and "to" at offset "alpha"
}
}
}