Files
UnrealEngine/Engine/Source/Runtime/StateStream/Public/GenericStateStream.h
2025-05-18 13:04:45 +08:00

673 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "HAL/CriticalSection.h"
#include "Misc/Optional.h"
#include "Misc/ScopeRWLock.h"
#include "StateStream.h"
#include "StateStreamDebugRenderer.h"
#include "StateStreamDefinitions.h"
#include "StateStreamStore.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Settings struct - Can be inherited to set settings on statestream
template<typename TInterfaceType, typename TUserDataType = void>
struct TStateStreamSettings
{
using InterfaceType = TInterfaceType;
using UserDataType = TUserDataType;
enum { Id = InterfaceType::Id };
static inline constexpr const TCHAR* DebugName = InterfaceType::Handle::DebugName;
static inline constexpr bool SkipCreatingDeletes = false;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// TStateStream is a generic implementation of IStateStream that contains all the boiler plate code
// related to ticks, interpolation, etc. Should be the default goto implementation.
// Inherit this class for each type of state stream
// Provide a subclass of TStateStreamSettings as template parameter
template<typename Settings>
class TStateStream : public IStateStream, public Settings::InterfaceType, public IStateStreamHandleOwner
{
public:
using InterfaceType = typename Settings::InterfaceType;
using FHandle = typename InterfaceType::Handle;
using FStaticState = typename InterfaceType::StaticState;
using FDynamicState = typename InterfaceType::DynamicState;
using FUserDataType = typename Settings::UserDataType;
enum { Id = Settings::Id };
// InterfaceType
virtual FHandle Game_CreateInstance(const FStaticState& Ss, const FDynamicState& Ds) override final;
// IStateStreamHandleOwner (used by state stream handles on game side)
virtual void Game_AddRef(uint32 HandleId) override final;
virtual void Game_Release(uint32 HandleId) override final;
virtual void Game_Update(uint32 HandleId, const void* Ds, double TimeFactor, uint64 UserData) override;
virtual void* Game_Edit(uint32 HandleId, double TimeFactor, uint64 UserData) override;
virtual void* Render_GetUserData(uint32 HandleId) override final;
// IStateStream (used by StateStreamManagerImpl)
virtual void Game_BeginTick() override;
virtual void Game_EndTick(StateStreamTime AbsoluteTime) override;
virtual void Game_Exit() override;
virtual void* Game_GetVoidPointer() override;
virtual uint32 GetId() override;
virtual void Render_Update(StateStreamTime AbsoluteTime) override;
virtual void Render_PostUpdate() override;
virtual void Render_Exit() override;
virtual void Render_GarbageCollect() override;
virtual const TCHAR* GetDebugName() override;
virtual void DebugRender(IStateStreamDebugRenderer& Renderer) override;
FUserDataType*& Render_GetUserData(const FHandle& Handle);
const FDynamicState& Render_GetDynamicState(const FHandle& Handle);
// Specialize to do custom things
virtual void Render_OnCreate(const FStaticState& Ss, const FDynamicState& Ds, FUserDataType*& UserData, bool IsDestroyedInSameFrame) {}
virtual void Render_OnUpdate(const FStaticState& Ss, const FDynamicState& Ds, FUserDataType*& UserData) {}
virtual void Render_OnDestroy(const FStaticState& Ss, const FDynamicState& Ds, FUserDataType*& UserData) {}
// ... or use template specialization to avoid virtual calls
void Render_OnCreateInline(const FStaticState& Ss, const FDynamicState& Ds, FUserDataType*& UserData, bool IsDestroyedInSameFrame) { Render_OnCreate(Ss, Ds, UserData, IsDestroyedInSameFrame); }
void Render_OnUpdateInline(const FStaticState& Ss, const FDynamicState& Ds, FUserDataType*& UserData) { Render_OnUpdate(Ss, Ds, UserData); }
void Render_OnDestroyInline(const FStaticState& Ss, const FDynamicState& Ds, FUserDataType*& UserData) { Render_OnDestroy(Ss, Ds, UserData); }
// For unit tests
uint32 GetUsedInstancesCount() const { return Instances.GetUsedCount(); }
uint32 GetUsedDynamicstatesCount() const { return DynamicStates.GetUsedCount(); }
TStateStream() = default;
~TStateStream() = default;
protected:
FDynamicState& Edit(uint32 HandleId, double TimeFactor);
template<typename InDynamicState>
void Update(uint32 HandleId, const InDynamicState& Ds, double TimeFactor);
private:
struct FTick;
void ApplyChanges(const FTick& Tick, StateStreamTime Time, uint32 PrevTickIndex, StateStreamTime PrevTime, const TBitArray<>& ModifiedInstances);
template<typename T>
void MakeInternal(T& State);
// Information about instance.
struct FInstance
{
FInstance() = default;
FInstance(const FStaticState& Ss, uint32 Rc, uint32 Ct) : StaticState(Ss), RefCount(Rc), CreateTick(Ct) {}
FStaticState StaticState;
uint32 RefCount = 0;
uint32 CreateTick = 0;
uint32 DeleteTick = ~0u;
TOptional<FDynamicState> RendDynamicState;
FUserDataType* UserData = nullptr;
};
TStateStreamStore<FInstance> Instances;
// Tick produced by game side.
struct FTick
{
TArray<uint32> DynamicStates; // Contains index of all instances existing in tick (some might have been destroyed but index not reused)
TBitArray<> ModifiedInstances; // Contains bits saying which of the instances that have been modified in this tick.
FTick* PrevTick = nullptr; // Tick with earlier time
FTick* NextTick = nullptr; // Tick with newer time
StateStreamTime Time = 0; // Time Tick finished
uint32 Index = 0; // Index of tick (created by TickCounter)
};
FRWLock CurrentTickLock;
FTick* CurrentTick = nullptr; // Tick being worked on in game
FTick* OldestAvailableTick = nullptr; // Newest finished tick available to rendering
FTick* NewestAvailableTick = nullptr; // Newest finished tick available to rendering
TStateStreamStore<FDynamicState> DynamicStates; // Store for dynamic states.
uint32 TickCounter = 1;
FTick* RendTick = nullptr; // Last used tick for rendering.
StateStreamTime RendTime = 0; // Last used time for rendering
TArray<FInstance*> DeferredDestroys;
TStateStream(const TStateStream&) = delete;
TStateStream& operator=(const TStateStream&) = delete;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation
template<typename Settings>
typename TStateStream<Settings>::FHandle TStateStream<Settings>::Game_CreateInstance(const FStaticState& Ss, const FDynamicState& Ds)
{
check(CurrentTick);
uint32 InstanceIndex = Instances.Emplace(Ss, 1, CurrentTick->Index);
uint32 DynamicStateIndex = DynamicStates.Add(Ds);
CurrentTickLock.WriteLock();
if (uint32(CurrentTick->DynamicStates.Num()) <= InstanceIndex)
{
CurrentTick->DynamicStates.SetNum(InstanceIndex + 1);
CurrentTick->ModifiedInstances.SetNum(InstanceIndex + 1, false);
}
CurrentTick->ModifiedInstances[InstanceIndex] = true;
CurrentTick->DynamicStates[InstanceIndex] = DynamicStateIndex;
CurrentTickLock.WriteUnlock();
return { *this, InstanceIndex + 1 };
}
template<typename Settings>
void TStateStream<Settings>::Game_AddRef(uint32 HandleId)
{
check(CurrentTick);
check(HandleId != 0);
uint32 InstanceIndex = HandleId - 1;
FInstance& Instance = Instances[InstanceIndex];
check(Instance.RefCount);
++Instance.RefCount;
}
template<typename Settings>
void TStateStream<Settings>::Game_Release(uint32 HandleId)
{
check(CurrentTick);
check(HandleId != 0);
uint32 InstanceIndex = HandleId - 1;
FInstance& Instance = Instances[InstanceIndex];
check(Instance.RefCount);
if (--Instance.RefCount)
{
return;
}
Instance.DeleteTick = CurrentTick->Index;
MakeInternal(Instance.StaticState);
CurrentTickLock.ReadLock();
CurrentTick->ModifiedInstances[InstanceIndex] = true;
uint32 DsIndex = CurrentTick->DynamicStates[InstanceIndex];
CurrentTickLock.ReadUnlock();
MakeInternal(DynamicStates[DsIndex]);
}
template<typename Settings>
void TStateStream<Settings>::Game_Update(uint32 HandleId, const void* Ds, double TimeFactor, uint64 UserData)
{
FDynamicState& DsState = Edit(HandleId, TimeFactor);
FStateStreamCopyContext Context;
Context.IsInternal = 0;
DsState.Apply(Context, *static_cast<const FDynamicState*>(Ds));
}
template<typename Settings>
void* TStateStream<Settings>::Game_Edit(uint32 HandleId, double TimeFactor, uint64 UserData)
{
return &Edit(HandleId, TimeFactor);
}
template<typename Settings>
typename TStateStream<Settings>::FDynamicState& TStateStream<Settings>::Edit(uint32 HandleId, double TimeFactor)
{
check(HandleId != 0);
uint32 InstanceIndex = HandleId - 1;
check(CurrentTick);
check(InstanceIndex < uint32(CurrentTick->DynamicStates.Num()));
CurrentTickLock.ReadLock();
FDynamicState* DsState;
if (CurrentTick->ModifiedInstances[InstanceIndex])
{
uint32 DsIndex = CurrentTick->DynamicStates[InstanceIndex];
CurrentTickLock.ReadUnlock();
DsState = &DynamicStates[DsIndex];
}
else
{
uint32& IndexRef = CurrentTick->DynamicStates[InstanceIndex];
uint32 OldIndex = IndexRef;
CurrentTick->ModifiedInstances[InstanceIndex] = true;
uint32 DsIndex;
void* DsPtr = DynamicStates.AddUninitialized(DsIndex);
IndexRef = DsIndex;
CurrentTickLock.ReadUnlock();
FDynamicState& OldDs = DynamicStates[OldIndex];
DsState = new (DsPtr) FDynamicState(OldDs);
MakeInternal(OldDs);
}
return *DsState;
}
template<typename Settings>
template<typename InDynamicState>
void TStateStream<Settings>::Update(uint32 HandleId, const InDynamicState& Ds, double TimeFactor)
{
/*
check(HandleId != 0);
uint32 InstanceIndex = HandleId - 1;
check(CurrentTick);
check(InstanceIndex < uint32(CurrentTick->DynamicStates.Num()));
CurrentTickLock.ReadLock();
uint32 DsIndex = CurrentTick->DynamicStates[InstanceIndex];
auto& CurrentState = *(decltype(InDynamicState)*)DynamicStates[DsIndex];
if (!CurrentState.Diff(InDs)) // TODO
{
return;
}
*/
FStateStreamCopyContext Context;
Context.IsInternal = 0;
InDynamicState& DsState = Edit(HandleId, TimeFactor);
DsState.Apply(Context, Ds);
}
template<typename Settings>
void* TStateStream<Settings>::Render_GetUserData(uint32 HandleId)
{
check(HandleId);
uint32 InstanceIndex = HandleId - 1;
return Instances[InstanceIndex].UserData;
}
template<typename Settings>
void TStateStream<Settings>::Game_BeginTick()
{
check(!CurrentTick);
CurrentTick = new FTick();
CurrentTick->Index = TickCounter++;
if (NewestAvailableTick)
{
CurrentTick->DynamicStates = NewestAvailableTick->DynamicStates;
CurrentTick->ModifiedInstances.SetNum(CurrentTick->DynamicStates.Num(), false);
}
}
template<typename Settings>
void TStateStream<Settings>::Game_EndTick(StateStreamTime AbsoluteTime)
{
check(CurrentTick);
CurrentTick->Time = AbsoluteTime;
CurrentTick->PrevTick = NewestAvailableTick;
if (NewestAvailableTick)
{
check(NewestAvailableTick->Time <= AbsoluteTime);
NewestAvailableTick->NextTick = CurrentTick;
}
else
{
OldestAvailableTick = CurrentTick;
}
NewestAvailableTick = CurrentTick;
CurrentTick = nullptr;
}
template<typename Settings>
void TStateStream<Settings>::Game_Exit()
{
check(!CurrentTick);
/*
for (TConstSetBitIterator<> It(NewestAvailableTick->Modif); It; ++It)
{
}
*/
}
template<typename Settings>
void* TStateStream<Settings>::Game_GetVoidPointer()
{
return static_cast<InterfaceType*>(this);
}
template<typename Settings>
uint32 TStateStream<Settings>::GetId()
{
return Id;
}
template<typename Settings>
void TStateStream<Settings>::Render_Update(StateStreamTime AbsoluteTime)
{
if (!NewestAvailableTick || AbsoluteTime == RendTime)
return;
check(AbsoluteTime > RendTime); // Only play forward for now
uint32 PrevTick = 0;
bool IsFirstTick = false;
if (RendTick)
{
PrevTick = RendTick->Index;
}
else
{
RendTick = OldestAvailableTick;
IsFirstTick = true;
}
StateStreamTime PrevTime = RendTime;
RendTime = AbsoluteTime;
// We are still inside the same tick
if (RendTime <= RendTick->Time)
{
// Just interpolate or apply RendTick
ApplyChanges(*RendTick, RendTime, PrevTick, PrevTime, RendTick->ModifiedInstances);
return;
}
// We were at exact end of last handled tick.. move into next
if (PrevTime == RendTick->Time)
{
FTick* NewRendTick = RendTick->NextTick;
if (!NewRendTick) // We've caught up with game.. set RendTime back to PrevTime and return
{
RendTime = PrevTime;
return;
}
if (!IsFirstTick)
RendTick = NewRendTick;
// We don't need to include RendTick Modifications.
if (RendTime <= RendTick->Time)
{
// Just interpolate or apply RendTick
ApplyChanges(*RendTick, RendTime, PrevTick, PrevTime, RendTick->ModifiedInstances);
return;
}
}
// We are overlapping between two or more ticks
TBitArray<> ModifiedInstances(RendTick->ModifiedInstances);
while (RendTick->Time < RendTime)
{
if (!RendTick->NextTick)
{
RendTime = RendTick->Time;
break;
}
RendTick = RendTick->NextTick;
ModifiedInstances.CombineWithBitwiseOR(RendTick->ModifiedInstances, EBitwiseOperatorFlags::MaxSize);
}
ApplyChanges(*RendTick, RendTime, PrevTick, PrevTime, ModifiedInstances);
}
template<typename Settings>
void TStateStream<Settings>::Render_PostUpdate()
{
for (FInstance* Instance : DeferredDestroys)
{
Render_OnDestroyInline(Instance->StaticState, *Instance->RendDynamicState, Instance->UserData);
Instance->RendDynamicState.Reset();
}
DeferredDestroys.SetNum(0);
}
template<typename Settings>
void TStateStream<Settings>::Render_Exit()
{
if (NewestAvailableTick)
{
StateStreamTime maxTime = (StateStreamTime)std::numeric_limits<StateStreamTime>::max();
Render_Update(maxTime);
//Render_GarbageCollect();
}
}
template<typename Settings>
void TStateStream<Settings>::Render_GarbageCollect()
{
if (!OldestAvailableTick)
{
return;
}
FTick* RendTickUsed = RendTick;
if (RendTick && RendTick->Time != RendTime && RendTick->PrevTick)
RendTickUsed = RendTick->PrevTick;
FTick* Tick = OldestAvailableTick;
while (Tick != RendTickUsed)
{
FTick* Next = Tick->NextTick;
check (Next);
// We can remove all DynamicStates that are different in Next tick since we know this is the last tick using the state
for (TConstSetBitIterator<> It(Next->ModifiedInstances); It; ++It)
{
uint32 InstanceIndex = It.GetIndex();
if (InstanceIndex >= uint32(Tick->DynamicStates.Num()))
{
continue;
}
// If instance was created in next tick we ignore this. If instance is deleted in tick we handle it further down
FInstance& Instance = Instances[InstanceIndex];
if (Instance.DeleteTick == Tick->Index || Instance.CreateTick == Next->Index)
{
continue;
}
uint32 DynamicStateIndex = Tick->DynamicStates[InstanceIndex];
if (DynamicStateIndex == Next->DynamicStates[InstanceIndex])
{
continue;
}
DynamicStates.Remove(DynamicStateIndex);
}
// Remove all instances who was deleted in Tick
for (TConstSetBitIterator<> It(Tick->ModifiedInstances); It; ++It)
{
uint32 InstanceIndex = It.GetIndex();
FInstance& Instance = Instances[InstanceIndex];
if (Instance.DeleteTick != Tick->Index)
{
continue;
}
DynamicStates.Remove(Tick->DynamicStates[InstanceIndex]);
Instance.RendDynamicState.Reset();
Instances.Remove(InstanceIndex);
}
delete Tick;
Tick = Next;
}
OldestAvailableTick = Tick;
}
template<typename Settings>
const TCHAR* TStateStream<Settings>::GetDebugName()
{
return Settings::DebugName;
}
template<typename Settings>
void TStateStream<Settings>::DebugRender(IStateStreamDebugRenderer& Renderer)
{
TStringBuilder<1024> DebugLine;
uint32 ModifiedCount = 0;
if (FTick* Tick = NewestAvailableTick)
{
ModifiedCount = Tick->ModifiedInstances.CountSetBits();
}
DebugLine.Appendf(TEXT("%s Num: %u Changed: %u"), GetDebugName(), Instances.GetUsedCount(), ModifiedCount);
Renderer.DrawText(*DebugLine);
}
template<typename Settings>
typename TStateStream<Settings>::FUserDataType*& TStateStream<Settings>::Render_GetUserData(const FHandle& Handle)
{
uint32 Id = Handle.GetId();
check(Id);
uint32 InstanceIndex = Id - 1;
return Instances[InstanceIndex].UserData;
}
template<typename Settings>
const typename TStateStream<Settings>::FDynamicState& TStateStream<Settings>::Render_GetDynamicState(const FHandle& Handle)
{
uint32 Id = Handle.GetId();
check(Id);
uint32 InstanceIndex = Id - 1;
return *Instances[InstanceIndex].RendDynamicState;
}
template<typename Settings>
void TStateStream<Settings>::ApplyChanges(const FTick& Tick, StateStreamTime Time, uint32 PrevTickIndex, StateStreamTime PrevTime, const TBitArray<>& ModifiedInstances)
{
// TODO: This will do lots of allocations
struct FCreateInfo { FInstance* Instance; bool IsDestroyInSameFrame; };
TArray<FCreateInfo> Creates;
TArray<FInstance*> Updates;
TArray<FInstance*> Destroys;
for (TConstSetBitIterator<> It(ModifiedInstances); It; ++It)
{
uint32 InstanceIndex = It.GetIndex();
FInstance& Instance = Instances[InstanceIndex];
if (Instance.DeleteTick <= PrevTickIndex) // Already deleted
{
continue;
}
FStaticState& Ss = Instance.StaticState;
bool IsCreate = Instance.CreateTick > PrevTickIndex && Instance.CreateTick <= Tick.Index;
bool IsDestroy = Instance.DeleteTick > PrevTickIndex && Instance.DeleteTick <= Tick.Index;
if (IsCreate)
{
if (Settings::SkipCreatingDeletes && IsDestroy)
{
continue;
}
check(!Instance.RendDynamicState.IsSet());
uint32 DynamicStateIndex = Tick.DynamicStates[InstanceIndex];
FStateStreamCopyContext Context;
Instance.RendDynamicState.Emplace(Context, DynamicStates[DynamicStateIndex]);
}
else if (Instance.CreateTick == Tick.Index) // Still in the tick it was created, no interpolation possible
{
continue;
}
FDynamicState& Ds = *Instance.RendDynamicState;
if (Tick.Time == Time || !Tick.PrevTick)
{
if (!IsCreate)
{
FStateStreamCopyContext Context;
Ds.Apply(Context, DynamicStates[Tick.DynamicStates[InstanceIndex]]);
}
}
else
{
const FTick& PrevTick = *Tick.PrevTick;
uint32 FromIndex = PrevTick.DynamicStates[InstanceIndex];
uint32 ToIndex = Tick.DynamicStates[InstanceIndex];
StateStreamTime DeltaTime = Tick.Time - PrevTick.Time;
StateStreamTime TimeInTo = Time - PrevTick.Time;
double Factor = double(TimeInTo) / DeltaTime;
FDynamicState& From = DynamicStates[FromIndex];
FDynamicState& To = DynamicStates[ToIndex];
FStateStreamInterpolateContext Context;
Context.Factor = Factor;
Ds.Interpolate(Context, From, To);
}
if (IsCreate)
{
Creates.Add({&Instance, IsDestroy});
}
if (IsDestroy)
{
if (IsCreate)
{
DeferredDestroys.Add(&Instance);
}
else
{
Destroys.Add(&Instance);
}
continue;
}
if (!IsCreate)
{
Updates.Add(&Instance);
}
}
for (FCreateInfo& Info : Creates)
{
FInstance& Instance = *Info.Instance;
Render_OnCreateInline(Instance.StaticState, *Instance.RendDynamicState, Instance.UserData, Info.IsDestroyInSameFrame);
}
for (FInstance* Instance : Updates)
{
Render_OnUpdateInline(Instance->StaticState, *Instance->RendDynamicState, Instance->UserData);
}
for (FInstance* Instance : Destroys)
{
Render_OnDestroyInline(Instance->StaticState, *Instance->RendDynamicState, Instance->UserData);
Instance->RendDynamicState.Reset();
}
}
template<typename Settings>
template<typename T>
void TStateStream<Settings>::MakeInternal(T& State)
{
FStateStreamHandle* Deps[256];
for (uint32 I=0, E=State.GetDependencies(Deps, UE_ARRAY_COUNT(Deps)); I!=E; ++I)
{
Deps[I]->MakeInternal();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////