// 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 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 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 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 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 RendDynamicState; FUserDataType* UserData = nullptr; }; TStateStreamStore Instances; // Tick produced by game side. struct FTick { TArray 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 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 DeferredDestroys; TStateStream(const TStateStream&) = delete; TStateStream& operator=(const TStateStream&) = delete; }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Implementation template typename TStateStream::FHandle TStateStream::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 void TStateStream::Game_AddRef(uint32 HandleId) { check(CurrentTick); check(HandleId != 0); uint32 InstanceIndex = HandleId - 1; FInstance& Instance = Instances[InstanceIndex]; check(Instance.RefCount); ++Instance.RefCount; } template void TStateStream::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 void TStateStream::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(Ds)); } template void* TStateStream::Game_Edit(uint32 HandleId, double TimeFactor, uint64 UserData) { return &Edit(HandleId, TimeFactor); } template typename TStateStream::FDynamicState& TStateStream::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 template void TStateStream::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 void* TStateStream::Render_GetUserData(uint32 HandleId) { check(HandleId); uint32 InstanceIndex = HandleId - 1; return Instances[InstanceIndex].UserData; } template void TStateStream::Game_BeginTick() { check(!CurrentTick); CurrentTick = new FTick(); CurrentTick->Index = TickCounter++; if (NewestAvailableTick) { CurrentTick->DynamicStates = NewestAvailableTick->DynamicStates; CurrentTick->ModifiedInstances.SetNum(CurrentTick->DynamicStates.Num(), false); } } template void TStateStream::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 void TStateStream::Game_Exit() { check(!CurrentTick); /* for (TConstSetBitIterator<> It(NewestAvailableTick->Modif); It; ++It) { } */ } template void* TStateStream::Game_GetVoidPointer() { return static_cast(this); } template uint32 TStateStream::GetId() { return Id; } template void TStateStream::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 void TStateStream::Render_PostUpdate() { for (FInstance* Instance : DeferredDestroys) { Render_OnDestroyInline(Instance->StaticState, *Instance->RendDynamicState, Instance->UserData); Instance->RendDynamicState.Reset(); } DeferredDestroys.SetNum(0); } template void TStateStream::Render_Exit() { if (NewestAvailableTick) { StateStreamTime maxTime = (StateStreamTime)std::numeric_limits::max(); Render_Update(maxTime); //Render_GarbageCollect(); } } template void TStateStream::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 const TCHAR* TStateStream::GetDebugName() { return Settings::DebugName; } template void TStateStream::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 TStateStream::FUserDataType*& TStateStream::Render_GetUserData(const FHandle& Handle) { uint32 Id = Handle.GetId(); check(Id); uint32 InstanceIndex = Id - 1; return Instances[InstanceIndex].UserData; } template const typename TStateStream::FDynamicState& TStateStream::Render_GetDynamicState(const FHandle& Handle) { uint32 Id = Handle.GetId(); check(Id); uint32 InstanceIndex = Id - 1; return *Instances[InstanceIndex].RendDynamicState; } template void TStateStream::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 Creates; TArray Updates; TArray 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 template void TStateStream::MakeInternal(T& State) { FStateStreamHandle* Deps[256]; for (uint32 I=0, E=State.GetDependencies(Deps, UE_ARRAY_COUNT(Deps)); I!=E; ++I) { Deps[I]->MakeInternal(); } } ////////////////////////////////////////////////////////////////////////////////////////////////////