네트워크 버퍼
개요
Fusion은 각 NetworkObject
에 대해, 해당 오브젝트에 대한 StateAuthority
가 없는 경우, 가장 최신의 권위 있는 상태를 나타내는 수신된 상태 스냅샷의 기록을 유지합니다. 로컬에서 시뮬레이션되는 각 오브젝트(권위적이든 예측되었든 관계없이)는 추가로 현재 스냅샷과 이전 스냅샷을 가지고 있습니다.
NetworkBehaviour
의 [Networked]
속성은 이러한 스냅샷(기록에 있는 스냅샷이든 로컬 시뮬레이션 상태이든 상관없이) 중 가장 최신의 값에 직접 접근할 수 있게 해줍니다.
어떠한 NetworkBehaviour
에 대해 최근 두 개의 스냅샷과 이들 간의 보간을 위한 상대 시간 오프셋(스케일 된 값)을 얻으려면 TryGetSnapshotsBuffers()
를 사용하세요.
스냅샷 내 개별 속성 값에 접근하려면, NetworkBehaviour
기본 클래스는 저수준 데이터 접근 방식과 형식이 지정된 PropertyReader
를 모두 제공합니다.
이외에도 네트워크 행동은 ChangeDetector
클래스를 사용하여 네트워크 상태의 변화를 모니터링할 수 있게 해줍니다.
NetworkBehaviourBuffer와 PropertyReader
NetworkBehaviourBuffer
는 Fusion 내부 데이터 저장소의 추상화로, 네트워크 상태 스냅샷을 효과적으로 저비용의 읽기 전용 방식으로 들여다볼 수 있게 해줍니다.
고급 사용 사례를 위해, 네트워크 버퍼는 저수준의 ReinterpretState<T>(offset)
메서드를 제공하여 버퍼의 임의 오프셋의 데이터를 특정 데이터 타입으로 캐스팅할 수 있게 해줍니다. 이는 극히 성능에 민감하거나 네트워크 상태의 맞춤형 해석이 필요한 상황을 제외하고는 일반적으로 권장되지 않습니다.
대신 Fusion은 PropertyReader
클래스를 제공합니다. Property Reader는 C# 타입을 이름이 지정된 속성과 버퍼 내 오프셋에 연관시킵니다. Property Reader는 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
에 대해 스냅샷 버퍼와 Property Reader를 가져와, 이름이 지정된 속성의 보간 된 값을 반환합니다:
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
에 대한 Change Detector를 얻으려면, NetworkBehaviour
에서 GetChangeDetector(source, copy)
를 호출하고 반환된 인스턴스에 대한 참조를 유지하세요:
C#
private ChangeDetector _changeDetector;
public override void Spawned()
{
_changeDetector = GetChangeDetector(ChangeDetector.Source.SimulationState);
}
제공된 source 매개변수는 타임라인 상에서 "새로운 값"이 샘플링될 위치를 결정합니다. 이는 로컬 타임프레임(SimulationState
)일 수도 있고, 원격 타임프레임(또는 SnapshotFrom
또는 SnapshotTo
)일 수도 있습니다.
SimulationState는 시뮬레이션에 포함된 네트워크 오브젝트에만 존재한다는 점에 유의해야 합니다. 기본적으로 이는 로컬 피어가 상태 권한이나 입력 권한을 가진 모든 오브젝트를 의미하지만, 시뮬레이션이 명시적으로 활성화된 오브젝트에도 존재합니다.
copy 매개변수는 Change Detector의 초기 상태가 생성 시 복사되어야 하는지 여부를 결정합니다 (복사하지 않으면, 변화 감지를 위한 첫 번째 호출 시 "이전" 상태가 설정되어 항상 빈 열거자를 반환합니다).
변화가 도출되는 "이전 값"은 단순히 마지막으로 감지기가 호출되었을 때의 이전 값입니다. 따라서 변화는 FixedUpdateNetwork()
의 정확한 틱 경계, Render()
또는 심지어 Update()
와 같은 중간 렌더링 프레임에서도 감지할 수 있습니다.
생성된 각 ChangeDetector
는 이전에 생성된 감지기와 독립적인 자체 상태를 유지하므로, 동일한 NetworkBehaviour
에서 여러 Change Detector가 공존할 수 있습니다.
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"
}
}
}
Back to top