GameSubSystem简单实现
一种简单的维护 SubSystem
的解决方案,参考 UE
自带的 FSubsystemCollection
实现。
在维护各个业务时,经常需要将一个上层的 Manager
,下面再拆出若干个子系统,需要一种快捷的方法快速扩展出一套 SUbSystem
系统。
基本结构
classDiagram
Manager..*FGameSubSystemCollection
class Manager {
SubSystemCollections : FGameSubSystemCollection~USubSystemBase~
}
class FGameSubSystemCollection {
FGameSubSystemCollection()
}
FGameSubSystemCollectionBase<|--FGameSubSystemCollection
class FGameSubSystemCollectionBase {
Outer : TWeakObjectPtr~UObject~
SubSystemMap : TMap~UClass*,TStrongObjectPtr[UGameSubSystemBase]~
BaseType : UClass*
Init()
Uninit()
Tick()
FGameSubSystemCollectionBase(UClass* InBaseType)
AddSubSystemByClass(UClass* SubSystemClass)
RemoveSubSystemByClass(UClass* SubSystemClass)
}
FGameSubSystemCollectionBase..>UGameSubSystemBase
class UGameSubSystemBase {
LastTickTime : float
+ Init()
+ Uninit()
+ Tick()
# OnInit()
# OnUnInit()
# OnTick(float DeltaTime)
# GetTickInternal()
# GetTimeNow()
}
UGameSubSystemBase<|--UGameSubSystem
class UGameSubSystem {
# OnInit()
# OnUnInit()
# OnTick(float DeltaTime)
# GetTickInternal()
}
使用方法
由 Manager
持有对应类型的 SubSystemCollection
,主动调用 Init
、Uninit
、Tick
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { public: template<class T> typename TEnableIf<TIsDerivedFrom<T, UGameSubSystem>::Value, T*>::Type GetSubSystem() { return SubSystemCollections.GetSubSystem<T>(); } private: FGameSubSystemCollection <UGameSubSystem> SubSystemCollections; }
SubSystemCollections.Init() SubSystemCollections.Uninit() SubSystemCollections.Tick()
|
SubSystemCollection
SubSystemCollection
负责收集与管理所有的 SubSystem
。
在 SubSystemCollectionBase
中,提供一个 BaseClass
,这个 BaseClass
由对应的 SubSystemCollection
在初始化的时候传入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| template<typename TBaseType> struct FGameSubSystemCollection : FGameSubSystemCollectionBase { static_assert(TIsDerivedFrom<TBaseType, UGameSubSystemBase>::Value, "TBaseType must inherit from UGameSubSystemBase"); FGameSubSystemCollection() : FGameSubSystemCollectionBase(TBaseType::StaticClass()) { } }
FGameSubSystemCollectionBase::FGameSubSystemCollectionBase(UClass* InBaseType) : BaseType(InBaseType) { check(BaseType); }
|
在 Init
的时候,根据 BaseClass
,找到所有继承于该类的 Class
(GetDerivedClasses(BaseType, SubSystemClasses, true)
),然后进行 AddSubSystemByClass
。
用一个 TMap < UClass*, TStrongObjectPtr<UGameSubSystemBase> > SubSystemMap
将所有的 SubSystem
实例保存下来,这里由于我们期望 FGameSubSystemCollection
可以在编译期决定类型,所以使用了 template<typename TBaseType>
,导致无法走 UHT
的反射,挂上UPROPERTY()
来保证生命周期。所以这里需要用 TStrongObjectPtr
来保证这个 Collection
内部的 SubSystem
不会被 GC
掉,同时保证被 Manager
持有时生命周期正确。
同时我们需要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
struct FGameSubSystemCollectionBase { public: void Init(UObject* InOuter, bool bCreatedDerivedClasses = true); void Init(UObject* InOuter, const TArray<UClass*>& InSubSystemClasses); void Uninit(); void Tick();
protected: FGameSubSystemCollectionBase(UClass* InBaseType); public: TArray<UGameSubSystemBase*> AddSubSystemByClasses(const TArray<UClass*>& SubSystemClasses); UGameSubSystemBase* AddSubSystemByClass(UClass* SubSystemClass); void RemoveSubSystemByClass(UClass* SubSystemClass);
protected: UGameSubSystemBase* GetSubSystemInternal(UClass* SubSystemClass) const; TArray<UGameSubSystemBase*> GetSubSystemsInternal() const;
private: TWeakObjectPtr <UObject> Outer = nullptr; TMap < UClass*, TStrongObjectPtr<UGameSubSystemBase> > SubSystemMap; UClass* BaseType = nullptr; };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
FGameSubSystemCollectionBase::FGameSubSystemCollectionBase(UClass* InBaseType) : BaseType(InBaseType) { check(BaseType); }
void FGameSubSystemCollectionBase::Init(UObject* InOuter, bool bCreatedDerivedClasses) { if (Outer.IsValid()) return; if (InOuter == nullptr) return;
Outer = InOuter;
if (bCreatedDerivedClasses) { TArray<UClass*> SubSystemClasses; GetDerivedClasses(BaseType, SubSystemClasses, true); AddSubSystemByClasses( SubSystemClasses ); } }
void FGameSubSystemCollectionBase::Init(UObject* InOuter, const TArray<UClass*>& InSubSystemClasses) { if (Outer.IsValid()) return; if (InOuter == nullptr) return; Outer = InOuter; AddSubSystemByClasses(InSubSystemClasses); }
void FGameSubSystemCollectionBase::Tick() { for (auto& Pair : SubSystemMap) { auto SubSystem = Pair.Value; if (SubSystem.IsValid()) { SubSystem->Tick(); } } }
void FGameSubSystemCollectionBase::Uninit() { TArray <UClass*> SubSystemClasses; SubSystemMap.GetKeys(SubSystemClasses); for (auto SubSystemClass : SubSystemClasses) { RemoveSubSystemByClass(SubSystemClass); } }
TArray<UGameSubSystemBase*> FGameSubSystemCollectionBase::AddSubSystemByClasses(const TArray<UClass*>& SubSystemClasses) { TArray<UGameSubSystemBase*> SubSystems;
for (auto SubSystemClass : SubSystemClasses) { auto SubSystem = AddSubSystemByClass( SubSystemClass ); if (IsValid(SubSystem)) { SubSystems.Add( SubSystem ); } }
return SubSystems; }
UGameSubSystemBase* FGameSubSystemCollectionBase::AddSubSystemByClass(UClass* SubSystemClass) { if (SubSystemClass == nullptr || !Outer.IsValid()) return nullptr; if (SubSystemClass->HasAnyClassFlags(CLASS_Abstract)) return nullptr; if (!SubSystemClass->IsChildOf(BaseType)) return nullptr; if (SubSystemMap.Contains( SubSystemClass )) { return SubSystemMap.FindRef(SubSystemClass).Get(); }
UGameSubSystemBase* SubSystem = NewObject<UGameSubSystemBase>(Outer.Get(), SubSystemClass); SubSystemMap.Add( SubSystemClass, TStrongObjectPtr(SubSystem) ); SubSystem->Init(); return SubSystem; }
void FGameSubSystemCollectionBase::RemoveSubSystemByClass(UClass* SubSystemClass) { if (SubSystemClass == nullptr || !Outer.IsValid()) return; if (!SubSystemMap.Contains( SubSystemClass )) return; auto SubSystem = SubSystemMap.FindAndRemoveChecked(SubSystemClass); if (!SubSystem.IsValid()) return; SubSystem->Uninit(); }
UGameSubSystemBase* FGameSubSystemCollectionBase::GetSubSystemInternal(UClass* SubSystemClass) const { if (SubSystemClass == nullptr) return nullptr; if (auto SubSystem = SubSystemMap.FindRef( SubSystemClass ); SubSystem.IsValid()) { return SubSystem.Get(); } return nullptr; }
TArray<UGameSubSystemBase*> FGameSubSystemCollectionBase::GetSubSystemsInternal() const { TArray <UGameSubSystemBase*> OutArray; for (auto [Class, SubSystem] : SubSystemMap) { if (!SubSystem.IsValid()) continue; OutArray.Add( SubSystem.Get() ); } return OutArray; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
template<typename TBaseType> struct FGameSubSystemCollection : FGameSubSystemCollectionBase { static_assert(TIsDerivedFrom<TBaseType, UGameSubSystemBase>::Value, "TBaseType must inherit from UGameSubSystemBase"); FGameSubSystemCollection() : FGameSubSystemCollectionBase(TBaseType::StaticClass()) { } public: template<class TSubSystemClass = TBaseType> typename TEnableIf<TIsDerivedFrom<TSubSystemClass, TBaseType>::Value, TSubSystemClass*>::Type GetSubSystem() const { return static_cast<TSubSystemClass*>(GetSubSystemInternal(TSubSystemClass::StaticClass())); } template<class TSubSystemClass = TBaseType> typename TEnableIf<TIsDerivedFrom<TSubSystemClass, TBaseType>::Value, TArray<TSubSystemClass*>>::Type GetSubSystems() const { const TArray<UGameSubSystemBase*>& Array = GetSubSystemsInternal(); const TArray<TSubSystemClass*>* SpecificArray = reinterpret_cast<const TArray<TSubSystemClass*>*>(&Array); return *SpecificArray; } };
|
特别地,注意这里的 FGameSubSystemCollection::GetSubSystems
,由于 GetSubSystemsInternal
返回的都是 UGameSubSystemBase
指针,内存大小和 TSubSystemClass
指针一样,所以可以使用 reinterpret_cast
直接将整个数组的类型转化,节省一步 O(n)
来 cast
的开销。
SubSystemBase
我们需要一个 SubSystemBase
,负责管理给 SubSystemCollection
持有,同时给各自定义的 SubSystem
继承。
提供一些基础的方法,Init
、Uninit
、Tick
给 SubSystemCollection
调用。
同时子类只需要关心:OnInit
、OnUninit
、OnTick
。
对于各自的业务,大家各自继承自定义的 SubSystemBase
,然后再自定义各自的 SubSytem
继承于这个业务扩展出来的 SubSystemBase
即可。
SubSystemBase->GetOuter()
就可以拿到对应的 Manager
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| UCLASS(Abstract) class UGameSubSystemBase : public UObject { GENERATED_BODY() public: void Init(); void Tick(); void Uninit(); protected: virtual void OnInit(); virtual void OnUninit(); virtual void OnTick(float DeltaTime);
protected: virtual float GetTickInternal() { return -1.0f; } virtual float GetTimeNow();
private: float LastTickTime; };
|
对于 Tick
,维护一个 LastTickTime
,用于计算 DeltaTime
。
1 2 3 4 5 6 7 8 9 10 11 12
| void UGameSubSystemBase::Tick() { float TickInternal = GetTickInternal(); if (TickInternal < 0) return;
float CurrentTickTime = GetTimeNow(); if (CurrentTickTime - LastTickTime > TickInternal) { OnTick(CurrentTickTime - LastTickTime); LastTickTime = CurrentTickTime; } }
|