Wwise-UE中的回调设计

Design of Callback Mechanism in Wwise-UE Integration

Posted by 李AA on December 25, 2021

前言

之前写过一篇Wwise-UE中的回调设计01。本文想要继续讨论一些使用与实现上的细节, 同时对自定义扩展进行一些讨论。

Object

AkComponentCallbackManager

  • 全局单例,负责Package的生成/清理,PackagePlayingID关系的维护

AkCallbackInfoPool

  • 负责UAkCallbackInfo实例的生成与维护。这个Pool的存在保证了每种类型CallbackInfo实例只有一个,可以反复利用

AkBankManager

  • 全局单例, 针对bank加载/卸载回调的管理,也维护一个已加载bank的list

创建

  • 三个实例都在初始化时实例完成,值得注意的是CallbackManager初始化的失败会导致AkAudio的初始失败
1
2
3
4
5
6
7
8
9
10
11
12
13
/** AkAudioDevice.cpp **/
bool FAkAudioDevice::EnsureInitialized()
{
    ......

    AkBankManager = new FAkBankManager;

	CallbackInfoPool = new AkCallbackInfoPool;

    CallbackManager = new FAkComponentCallbackManager();
	
    return CallbackManager != nullptr;
}

Package

Event

  • Event的回调被设计为Package的形式主要原因是需要支持回调的动态控制,可以runtime的注册与取消

1. IAkUserEventCallbackPackage

  • Package基类,其中有两个最重要属性
1
2
3
4
    /** 回调的Flag, 参考EAkCallbackType */
	uint32 uUserFlags;
    /** 这个是Package的哈希标识符,用来取消Callback与删除Package用的 */
	uint32 KeyHash;

2. FAkFunctionPtrEventCallbackPackage

  • Package子类, 增加了回调函数与Pakcage缓存, 因为回调接口类没有进行UE Wrap,所以建议作为AkAudio模块内的回调Package
1
2
3
4
5
    /** 回调函数 */
	AkCallbackFunc pfnUserCallback;

	/** Package缓存 */
	void* pUserCookie;

3. FAkBlueprintDelegateEventCallbackPackage

  • Package子类, 增加了动态多播代理,可以绑定模块外和蓝图层的回调函数
1
2
3
4
5
6
    /** 多播代理 */
    FOnAkPostEventCallback BlueprintCallback

    /** 代理原型 */
    DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnAkPostEventCallback, EAkCallbackType, CallbackType, UAkCallbackInfo*, CallbackInfo);

4. FAkLatentActionEventCallbackPackage

  • package子类,增加了FWaitEndOfEventAction代理, 用于蓝图中PostAndWaitEndOfEvent异步接口
1
2
    /** 蓝图异步调用事件 */
	FWaitEndOfEventAction* EndOfEventLatentAction;

5. Hash

  • Package的哈希方式,确保唯一性就好了
1
2
3
4
5
6
7
8
9
10
11
/** AkComponentCallbackManager.cpp **/

    uint32 FAkComponentCallbackManager::GetKeyHash(void* Key)
    {
    	return GetTypeHash(Key);
    }
    
    uint32 FAkComponentCallbackManager::GetKeyHash(const    FOnAkPostEventCallback& Key)
    {
    	return HashCombine(GetTypeHash(Key.GetUObject()), GetTypeHash(Key.GetFunctionName()));
    }

Bank

  • bank的回调不需要动态控制的功能,所以保持为简单的CallbackInfo

1. IAkBankCallbackInfo

  • CallbackInfo基类, 里面有关联bank的指针
1
2
    /** 此回调关联Bank */
    class UAkAudioBank* Bank;

2. FAkBankFunctionPtrCallbackInfo

  • CallbackInfo子类, 增加了回调函数cookie缓存, 因为回调接口类没有进行UE Wrap,所以建议作为AkAudio模块内的回调callbackinfo
1
2
3
4
    /** 回调函数 */
    AkBankCallbackFunc CallbackFunc;
    /** 用户自定义cookie */
	void* UserCookie;

3. FAkBankBlueprintDelegateCallbackInfo

  • Callbackinfo子类, 增加了动态多播代理,可以绑定模块外和蓝图层的回调函数
1
2
3
4
5
    /** 多播代理 */
    FOnAkBankCallback BankBlueprintCallback;

    /** 代理原型 */
    DECLARE_DYNAMIC_DELEGATE_OneParam(FOnAkBankCallback, EAkResult, Result);

4. FAkBankLatentActionCallbackInfo

  • callbackinfo子类,增加了FWaitEndOfEventAction代理, 用于蓝图中LoadBank异步接口
1
2
    /** 蓝图异步调用事件 */
	FOnAkBankCallback BankBlueprintCallback;

Flow

1. Trigger Task

Event

  • 三种不同的PostEvent接口创建不同的Package类型
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
/** AkAudioDevice.cpp **/

/** FAkFunctionPtrEventCallbackPackage类型 */
AkPlayingID FAkAudioDevice::PostEvent(
	const FString& in_EventName,
	UAkComponent* in_pComponent,
	AkUInt32 in_uFlags,
	AkCallbackFunc in_pfnCallback,
	void * in_pCookie,
	const TArray<AkExternalSourceInfo>& in_ExternalSources)

/** FAkBlueprintDelegateEventCallbackPackage类型 */
AkPlayingID FAkAudioDevice::PostEvent(
	const FString& in_EventName,
	UAkComponent* in_pComponent,
	const FOnAkPostEventCallback& PostEventCallback,
	AkUInt32 in_uFlags,
	const TArray<AkExternalSourceInfo>& in_ExternalSources)

/** FAkLatentActionEventCallbackPackage类型 */
AkPlayingID FAkAudioDevice::PostEventLatentAction(
	const FString& in_EventName,
	UAkComponent* in_pComponent,
	FWaitEndOfEventAction* LatentAction,
	const TArray<AkExternalSourceInfo>& in_ExternalSources)
  • 这里面要注意下, 上层PostEvent接口最后都生成了一个只有AkGameObjectID的匿名函数对象, 然后传递给最下层的直接和SoundEngine对话的那个PostEvent,集成中为数不多比较骚的地方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
	/** FAkFunctionPtrEventCallbackPackage类型 */
	return PostEvent(in_EventName, in_pComponent, in_ExternalSources, [in_pfnCallback, in_pCookie, in_uFlags, this](AkGameObjectID gameObjID) {
		return CallbackManager->CreateCallbackPackage(in_pfnCallback, in_pCookie, in_uFlags, gameObjID);
	});

	/** FAkBlueprintDelegateEventCallbackPackage类型 */
	return PostEvent(in_EventName, in_pComponent, in_ExternalSources, [PostEventCallback, in_uFlags, this](AkGameObjectID gameObjID) {
		return CallbackManager->CreateCallbackPackage(PostEventCallback, in_uFlags, gameObjID);
	});

	/** FAkLatentActionEventCallbackPackage类型 */
		return PostEvent(in_EventName, in_pComponent, in_ExternalSources, [LatentAction, this](AkGameObjectID gameObjID) {
		return CallbackManager->CreateCallbackPackage(LatentAction, gameObjID);
	});

Bank

  • 三种不同的LoadBank接口创建不同的CallbackInfo类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** FAkBankFunctionPtrCallbackInfo类型 */
AKRESULT FAkAudioDevice::LoadBank(
	class UAkAudioBank *     in_Bank,
	AkBankCallbackFunc  in_pfnBankCallback,
	void *              in_pCookie,
	AkMemPoolId         in_memPoolId,
	AkBankID &          out_bankID
)

/** FAkBankBlueprintDelegateCallbackInfo类型 */
AKRESULT FAkAudioDevice::LoadBankAsync(
	class UAkAudioBank *     in_Bank,
	const FOnAkBankCallback& BankLoadedCallback,
	AkMemPoolId         in_memPoolId,
	AkBankID &          out_bankID
)

/** FAkBankLatentActionCallbackInfo类型 */
AKRESULT FAkAudioDevice::LoadBank(
	class UAkAudioBank *     in_Bank,
	FWaitEndBankAction* LoadBankLatentAction
)
  • UnloadBankLoadBank没有区别,三个接口对应三种CallbackInfo

2. Make Package

Event

  • CallbackManager中为不同Package重载了CreateCallbackPackage接口
1
2
3
4
5
6
7
8
9
10
11
/** AkComponentCallbackManager.h **/

	/** FAkFunctionPtrEventCallbackPackage类型 */
	IAkUserEventCallbackPackage* CreateCallbackPackage(AkCallbackFunc in_cbFunc, void* in_Cookie, uint32 in_Flags, AkGameObjectID in_gameObjID);

	/** FAkBlueprintDelegateEventCallbackPackage类型 */
	IAkUserEventCallbackPackage* CreateCallbackPackage(FOnAkPostEventCallback BlueprintCallback, uint32 in_Flags, AkGameObjectID in_gameObjID);

	/** FAkLatentActionEventCallbackPackage类型 */
	IAkUserEventCallbackPackage* CreateCallbackPackage(FWaitEndOfEventAction* LatentAction, AkGameObjectID in_gameObjID);

  • 上述的PostEvent接口内都调用了CallbackManager::CreateCallbackPackage, 基本就是简单粗暴的new了一个package出来,然后放到两个map中后续进行管理,这两个map下面会讨论
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
/** FAkFunctionPtrEventCallbackPackage类型 */
IAkUserEventCallbackPackage* FAkComponentCallbackManager::CreateCallbackPackage(AkCallbackFunc in_cbFunc, void* in_Cookie, uint32 in_Flags, AkGameObjectID in_gameObjID)
{
	/** 哈希Cookie用来创建Package标识 */
	uint32 KeyHash = GetKeyHash(in_Cookie);
	auto pPackage = new FAkFunctionPtrEventCallbackPackage(in_cbFunc, in_Cookie, in_Flags, KeyHash);
	if (pPackage)
	{
		FScopeLock Lock(&CriticalSection);
		/** 添加到GameObject-Packages map */
		GameObjectToPackagesMap.FindOrAdd(in_gameObjID).Add(pPackage);
		/** 添加到hash-package map */
		UserCookieHashToPackageMap.Add(KeyHash, pPackage);
	}

	return pPackage;
}

/** FAkBlueprintDelegateEventCallbackPackage类型 */
IAkUserEventCallbackPackage* FAkComponentCallbackManager::CreateCallbackPackage(FOnAkPostEventCallback BlueprintCallback, uint32 in_Flags, AkGameObjectID in_gameObjID)
{
	/** 哈希代理的地址来创建Package标识 */
	uint32 KeyHash = GetKeyHash(BlueprintCallback);
	auto pPackage = new FAkBlueprintDelegateEventCallbackPackage(BlueprintCallback, in_Flags, KeyHash);
	if (pPackage)
	{
		FScopeLock Lock(&CriticalSection);
		/** 添加到GameObject-Packages map */
		GameObjectToPackagesMap.FindOrAdd(in_gameObjID).Add(pPackage);
		/** 添加到hash-package map */
		UserCookieHashToPackageMap.Add(KeyHash, pPackage);
	}

	return pPackage;
}

/** FAkLatentActionEventCallbackPackage类型 */
IAkUserEventCallbackPackage* FAkComponentCallbackManager::CreateCallbackPackage(FWaitEndOfEventAction* LatentAction, AkGameObjectID in_gameObjID)
{
	/** LatentAction类型的Package不支持动态取消回调,所以不需要哈希 */
	auto pPackage = new FAkLatentActionEventCallbackPackage(LatentAction, 0);
	if (pPackage)
	{
		FScopeLock Lock(&CriticalSection);
		/** 添加到GameObject-Packages map */
		GameObjectToPackagesMap.FindOrAdd(in_gameObjID).Add(pPackage);
	}

	return pPackage;
}

bank

  • bank因为没用用Package的形式, 只是简单的CallbackInfo所以BankManager中没有实现Create接口, 而是在触发接口中简单粗暴的new了对应的类型
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
/** AkAudioDevice.cpp **/


/** FAkBankFunctionPtrCallbackInfo类型 */
AKRESULT FAkAudioDevice::LoadBank(
	class UAkAudioBank *     in_Bank,
	AkBankCallbackFunc  in_pfnBankCallback,
	void *              in_pCookie,
	AkMemPoolId         in_memPoolId,
	AkBankID &          out_bankID
)
{
	if (EnsureInitialized() && in_Bank)
	{
		if (AkBankManager != NULL)
		{
			/** 直接new */
			IAkBankCallbackInfo* cbInfo = new FAkBankFunctionPtrCallbackInfo(in_pfnBankCallback, in_Bank, in_pCookie);
			if (cbInfo)
			{
				/** 直接传给SoundEngine */
				return AK::SoundEngine::LoadBank(TCHAR_TO_AK(*(in_Bank->GetName())), FAkBankManager::BankLoadCallback, cbInfo, in_memPoolId, out_bankID);
			}
		}
		else
		{
			return AK::SoundEngine::LoadBank(TCHAR_TO_AK(*(in_Bank->GetName())), in_pfnBankCallback, in_pCookie, in_memPoolId, out_bankID);
		}
	}
	return AK_Fail;
}

/** FAkBankBlueprintDelegateCallbackInfo类型 */
AKRESULT FAkAudioDevice::LoadBankAsync(
	class UAkAudioBank *     in_Bank,
	const FOnAkBankCallback& BankLoadedCallback,
	AkMemPoolId         in_memPoolId,
	AkBankID &          out_bankID
)
{
	if (EnsureInitialized() && AkBankManager != NULL && in_Bank) // ensure audiolib is initialized
	{
		/** 直接new */
		IAkBankCallbackInfo* cbInfo = new FAkBankBlueprintDelegateCallbackInfo(in_Bank, BankLoadedCallback);

		// Need to hijack the callback, so we can add the bank to the loaded banks list when successful.
		if (cbInfo)
		{
			/** 直接传给SoundEngine */
			return AK::SoundEngine::LoadBank(TCHAR_TO_AK(*(in_Bank->GetName())), FAkBankManager::BankLoadCallback, cbInfo, in_memPoolId, out_bankID);
		}
	}
	return AK_Fail;
}

/** FAkBankLatentActionCallbackInfo类型 */
AKRESULT FAkAudioDevice::LoadBank(
	class UAkAudioBank *     in_Bank,
	FWaitEndBankAction* LoadBankLatentAction
)
{
	if (EnsureInitialized() && AkBankManager != NULL && in_Bank) // ensure audiolib is initialized
	{
		/** 直接new */
		IAkBankCallbackInfo* cbInfo = new FAkBankLatentActionCallbackInfo(in_Bank, LoadBankLatentAction);

		// Need to hijack the callback, so we can add the bank to the loaded banks list when successful.
		if (cbInfo)
		{
			AkBankID BankId;
			/** 直接传给SoundEngine */
			return AK::SoundEngine::LoadBank(TCHAR_TO_AK(*(in_Bank->GetName())), FAkBankManager::BankLoadCallback, cbInfo, AK_DEFAULT_POOL_ID, BankId);
		}
	}
	return AK_Fail;
}

GameObjectToPackagesMap & UserCookieHashToPackageMap

  • GameObjectToPackagesMap中储存了所有在SoundEngine注册过的GameObject与它们此时还没有处理完的Package的映射关系
1
2
3
4
5
6
7
/** AkComponentCallbackManager.h **/

	/** Package自定义Set */
	typedef TSet<IAkUserEventCallbackPackage*> PackageSet;

	/** 所有注册给SoundEngine的GameObject都会在这里保存一个ID,之后维护与其PackageSet关系 */
	TMap<AkGameObjectID, PackageSet, FDefaultSetAllocator, PackageSetGameObjectIDKeyFuncs> GameObjectToPackagesMap;
  • GameObjectToPackagesMap中的数据用处主要是判断Post的Event是否还在Active状态,说白了就是有没有播完。其实维护这个Map的代价挺高的, 如果只是为了判断事件状态,我觉得应该从SoundEngine内去实现这个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/** AkComponentCallbackManager.cpp **/
bool FAkComponentCallbackManager::HasActiveEvents(AkGameObjectID in_gameObjID)
{
	FScopeLock Lock(&CriticalSection);
	/** 通过判断GameObject此时是否有未处理的Package,来判断事件是否还是Active状态 */
	auto pPackageSet = GameObjectToPackagesMap.Find(in_gameObjID);
	return pPackageSet && pPackageSet->Num() > 0;
}

/** AkComponet.cpp **/

bool UAkComponent::HasActiveEvents() const
{
	auto CallbackManager = FAkComponentCallbackManager::GetInstance();
	return (CallbackManager != nullptr) && CallbackManager->HasActiveEvents(GetAkGameObjectID());
}

/** Tick级别的Map查询, 用来实现AkComponent的AutoDestroy功能 */
void UAkComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
	......
	if (!HasActiveEvents() && bAutoDestroy && bStarted)
		DestroyComponent();
}
  • UserCookieHashToPackageMap中储存了cookie的哈希值与cookie所在package的映射关系
1
2
3
4
/** AkComponentCallbackManager.h **/

	/** cookie哈希值与包含其的Package的map */
	TMultiMap<uint32, IAkUserEventCallbackPackage*> UserCookieHashToPackageMap;
  • UserCookieHashToPackageMap中的数据主要作用是可以动态的取消回调,可以理解为CreatePackage以后但是还没有HandleAction期间,都可以把Package清理掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/** AkComponentCallbackManager.cpp **/

/** 取消回调接口 */
void FAkComponentCallbackManager::CancelEventCallback(const FOnAkPostEventCallback& in_Delegate)
{
	CancelKeyHash(GetKeyHash(in_Delegate));
}

/** 通过cookie哈希值找到关联Packages,执行CancelCallback */
void FAkComponentCallbackManager::CancelKeyHash(uint32 HashToCancel)
{
	FScopeLock AutoLock(&CriticalSection);

	TArray<IAkUserEventCallbackPackage*> PackagesToCancel;
	UserCookieHashToPackageMap.MultiFind(HashToCancel, PackagesToCancel);

	for (auto iter = PackagesToCancel.CreateConstIterator(); iter; ++iter)
	{
		if (*iter)
		{
			(*iter)->CancelCallback();
		}
	}
}

3. Send To SoundEngine

静态回调函数接口

  • Event与Bank的CallbakcManager中都有一个静态的回调接口,这个接口也是传给SoundEngine进行回调的公共接口, SoundEngine回调后集成相关的所有回调处理都从这里开始
1
2
3
4
5
6
7
8
9
10
11
12
13
/** AkComponentCallbackManager.h **/

	static void AkComponentCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo);

/** AkBankManager.h */

	static void BankLoadCallback(
		AkUInt32		in_bankID,
		const void *	in_pInMemoryBankPtr,
		AKRESULT		in_eLoadResult,
		AkMemPoolId		in_memPoolId,
		void *			in_pCookie
	);
  • Package最终在这个模板函数中生成,作为cookie发送给SoundEngine
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
template<typename FCreateCallbackPackage>
AkPlayingID FAkAudioDevice::PostEvent(
	const FString& in_EventName,
	const AkGameObjectID in_gameObjectID,
	const TArray<AkExternalSourceInfo>& in_ExternalSources,
	FCreateCallbackPackage CreateCallbackPackage
)
{
	AkPlayingID playingID = AK_INVALID_PLAYING_ID;

	if (m_bSoundEngineInitialized && CallbackManager)
	{
		/** 最终的CreateCallbackPackage, 只有需要一个参数 */
		auto pPackage = CreateCallbackPackage(in_gameObjectID);
		if (pPackage)
		{
			playingID = AK::SoundEngine::PostEvent(
				  TCHAR_TO_AK(*in_EventName)
				, in_gameObjectID
				, pPackage->uUserFlags | AK_EndOfEvent
				/** 静态回调函数接口 */
				, &FAkComponentCallbackManager::AkComponentCallback
				/** 可以看到Package作为cookie参数传给了SoundEngine */
				, pPackage
				, in_ExternalSources.Num()
				, const_cast<AkExternalSourceInfo*>(in_ExternalSources.GetData())
			);
			if (playingID == AK_INVALID_PLAYING_ID)
			{
				CallbackManager->RemoveCallbackPackage(pPackage, in_gameObjectID);
			}
		}
	}

	return playingID;
}
  • bank的回调则是在LoadBank接口内直接传给SondEngine,可以看其中一个示例
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
AKRESULT FAkAudioDevice::LoadBank(
	class UAkAudioBank *     in_Bank,
	AkBankCallbackFunc  in_pfnBankCallback,
	void *              in_pCookie,
	AkMemPoolId         in_memPoolId,
	AkBankID &          out_bankID
)
{
	if (EnsureInitialized() && in_Bank) // ensure audiolib is initialized
	{
		if (AkBankManager != NULL)
		{
			/** 直接new一个CallbackInfo */
			IAkBankCallbackInfo* cbInfo = new FAkBankFunctionPtrCallbackInfo(in_pfnBankCallback, in_Bank, in_pCookie);

			if (cbInfo)
			{
				/** CallbackInfo直接传入SioundEngine */
				return AK::SoundEngine::LoadBank(TCHAR_TO_AK(*(in_Bank->GetName())), 
												/** 静态回调函数接口 */
												FAkBankManager::BankLoadCallback, 
												/** CallbackInfo作为cookie传入 */
												cbInfo, 
												in_memPoolId, 
												out_bankID);
			}
		}
		else
		{
			return AK::SoundEngine::LoadBank(TCHAR_TO_AK(*(in_Bank->GetName())), in_pfnBankCallback, in_pCookie, in_memPoolId, out_bankID);
		}
	}
	return AK_Fail;
}

4. Handle Package

  • SoundEngine回调的第一站就是静态回调函数接口
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
/** AkComponentCallbackManager.cpp **/

void FAkComponentCallbackManager::AkComponentCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
	/** Cookie转为Package基类 */
	auto pPackage = (IAkUserEventCallbackPackage*)in_pCallbackInfo->pCookie;

	if (Instance && pPackage)
	{	
		/** 获取package中GameObjectID */
		const auto& gameObjID = in_pCallbackInfo->gameObjID;
		bool deletePackage = false;

		{
			FScopeLock Lock(&Instance->CriticalSection);
			/** 找到GameObjectID关联的PackageSet */
			auto pPackageSet = Instance->GameObjectToPackagesMap.Find(gameObjID);
			/** 如果是事件结束回调就从PackageSet里面清理掉这个Package */
			if (pPackageSet && in_eType == AK_EndOfEvent)
			{
				Instance->RemovePackageFromSet(pPackageSet, pPackage, gameObjID);
				/** 标记这个package可以清理 */
				deletePackage = true;
			}
		}

		
		if ((pPackage->uUserFlags & in_eType) != 0)
		{	
			/** 执行Package的HandleAction来处理Package */
			pPackage->HandleAction(in_eType, in_pCallbackInfo);
		}

		if (deletePackage)
		{	
			/** 很重要!这里不清理就内存泄露了, 佩服用裸指针的人 */
			delete pPackage;
		}
	}
}
  • BankManager的静态回调函数接口
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
/** AkBankManager.cpp **/

void FAkBankManager::BankLoadCallback(
	AkUInt32		in_bankID,
	const void *	in_pInMemoryBankPtr,
	AKRESULT		in_eLoadResult,
	AkMemPoolId		in_memPoolId,
	void *			in_pCookie
)
{
	if (in_pCookie)
	{
		/** 把cookie转为IAkBankCallbackInfo类 */
		IAkBankCallbackInfo* BankCbInfo = (IAkBankCallbackInfo*)in_pCookie;
		if (in_eLoadResult == AK_Success)
		{
			FScopeLock Lock(&GetInstance()->m_BankManagerCriticalSection);
			/** 这里是维护了一个成功加载的Bank List, 引用计数用的 */
			GetInstance()->AddLoadedBank(BankCbInfo->Bank);
		}

		/** 调用不同类型CallbackInfo的HandleAction接口 */
		BankCbInfo->HandleAction(in_bankID, in_pInMemoryBankPtr, in_eLoadResult, in_memPoolId);

		/** 重要!清理CallbackInfo */
		delete BankCbInfo;
	}
}
  • 每种Package类型都需要实现自己的HandleAction接口,来处理自定义的回调流程
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
/** AkComponentCallbackManager.cpp **/

/** FAkFunctionPtrEventCallbackPackage类型 */
void FAkFunctionPtrEventCallbackPackage::HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
	if (pfnUserCallback)
	{
		/** 把Package中的cookie填充进这个Callbackinfo中 */
		in_pCallbackInfo->pCookie = pUserCookie;
		/** 调用Package中的回调函数 */
		pfnUserCallback(in_eType, in_pCallbackInfo);
		/** 把CallbackInfo的cookie指向这个package本身,可能和SoundEngine内对CallbackInfo的回收有关 */
		in_pCallbackInfo->pCookie = (void*)this;
	}
}

/** FAkBlueprintDelegateEventCallbackPackage类型 */
void FAkBlueprintDelegateEventCallbackPackage::HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
	if (BlueprintCallback.IsBound())
	{
		/** 直接按回调信息类型,申请新内存把AkCallbackInfo拷过来 */
		AkCallbackInfo* cbInfoCopy = AkCallbackTypeHelpers::CopyWwiseCallbackInfo(in_eType, in_pCallbackInfo);
		/** 把AkCallbackType类型转换为EAkCallbackType */
		EAkCallbackType BlueprintCallbackType = AkCallbackTypeHelpers::GetBlueprintCallbackTypeFromAkCallbackType(in_eType);
		auto CachedBlueprintCallback = BlueprintCallback;
		/** 游戏线程上开异步,把CallbackInfo,CallbackType和回调函数传过去 */
		AsyncTask(ENamedThreads::GameThread, [cbInfoCopy, BlueprintCallbackType, CachedBlueprintCallback]
		{
			UAkCallbackInfo* BlueprintAkCallbackInfo = nullptr;
			if (cbInfoCopy)
			{
				/** 根据EAkCallbackType把AkCallbackInfo转为UAkCakkbackInfo, 这里涉及到AkCallbackInfoPool下面会讨论 */
				BlueprintAkCallbackInfo = AkCallbackTypeHelpers::GetBlueprintableCallbackInfo(BlueprintCallbackType, cbInfoCopy);
				/** 很重要!始放之前拷贝AkCallbackinfo时分配的内存 */
				FMemory::Free(cbInfoCopy);
			}

			/** 广播了!!! */
			CachedBlueprintCallback.ExecuteIfBound(BlueprintCallbackType, BlueprintAkCallbackInfo);

			if (auto AudioDevice = FAkAudioDevice::Get())
			{
				if (auto CallbackInfoPool = AudioDevice->GetAkCallbackInfoPool())
				{
					/** AkCallbackInfoPool中这个EAkCallbackType类型的UAkCallbackInfo用完了, 要重置后返回pool中,重复利用*/
					CallbackInfoPool->Release(BlueprintAkCallbackInfo);
				}
			}
		});
	}
}

/** FAkLatentActionEventCallbackPackage类型 */
void FAkLatentActionEventCallbackPackage::HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
	if (EndOfEventLatentAction)
	{
		/** LatentAction由父类统一执行,只需要标记就行 */
		EndOfEventLatentAction->EventFinished = true;
	}
}
  • 每种Bank的CallbackInfo中也要实现HandleAction接口
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
/** AkBankManager.cpp **/

/** FAkBankFunctionPtrCallbackInfo类型 */
void FAkBankFunctionPtrCallbackInfo::HandleAction(AkUInt32 BankID, const void * InMemoryBankPtr, AKRESULT ActionResult, AkMemPoolId MemPoolId)
{
	if (CallbackFunc != nullptr)
	{	
		/** 直接调用传入的回调函数 */
		CallbackFunc(BankID, InMemoryBankPtr, ActionResult, MemPoolId, UserCookie);
	}
}

/** FAkBankLatentActionCallbackInfo类型 */
void FAkBankLatentActionCallbackInfo::HandleAction(AkUInt32 BankID, const void * InMemoryBankPtr, AKRESULT ActionResult, AkMemPoolId MemPoolId)
{
	if (BankLatentAction != nullptr)
	{
		/** LatentAction由父类统一执行,只需要标记就行 */
		BankLatentAction->ActionDone = true;
	}
}

/** FAkBankBlueprintDelegateCallbackInfo类型 */
void FAkBankBlueprintDelegateCallbackInfo::HandleAction(AkUInt32 BankID, const void * InMemoryBankPtr, AKRESULT ActionResult, AkMemPoolId MemPoolId)
{
	if (BankBlueprintCallback.IsBound())
	{
		auto CachedBlueprintCallback = BankBlueprintCallback;
		/** 游戏线程直接异步执行广播 */
		AsyncTask(ENamedThreads::GameThread, [ActionResult, CachedBlueprintCallback]()
		{
			CachedBlueprintCallback.ExecuteIfBound((EAkResult)ActionResult);
		});
	}
}

AkCallbackInfoPool的作用

  • AkCallbackInfoPool作为一个对象池, 主要是维护了一个EAkCallbackTypeUAkCallbackInfo的对象池,目的是保证每个EAkCallbackType类型的UAkCallbackInfo对象只有一个,每次广播用完后都重置可以重复利用
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
/** AkCallbackInfoPool.h**/

class AkCallbackInfoPool final
{
public:
	/** 每次由AkCallbackInfo需要转为UAkCakkbackInfo时,就按EAkCallbackType来池子中取对象,没用的话就创建个新的 */
	template<typename CallbackType>
	CallbackType* Acquire()
	{
		return static_cast<CallbackType*>(internalAcquire(CallbackType::StaticClass()));
	}

	/** 广播后需要清理这个UAkCallbackInfo等下次取用 */
	void Release(UAkCallbackInfo* instance);

private:
	UAkCallbackInfo* internalAcquire(UClass* type);

private:
	/** 对象池,每个UAkCallbackType对应一个UAkCallbackInfo对象 */
	TMap<UClass*, TArray<UAkCallbackInfo*>> Pool;
	/** 这个是防止UE的GC把所有对象都强引用起来 */
	TArray<TStrongObjectPtr<UAkCallbackInfo>> gcStorage;
};

5. Clear Package

  • Package是New出来的,没有引用计数,所以合理清理掉是很重要的
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
/** AkComponentCallbackManager.cpp **/

/** 如果是EndOfEvent的回调,直接在静态回调接口里面就清理了 */
void FAkComponentCallbackManager::AkComponentCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
	......
	
	auto pPackageSet = Instance->GameObjectToPackagesMap.Find(gameObjID);
	if (pPackageSet && in_eType == AK_EndOfEvent)
	{
		/**GameObjectToPackagesMap中找出PackageSet,PackageSet中删除这个Package*/
		Instance->RemovePackageFromSet(pPackageSet, pPackage, gameObjID);
		/** delete标记 */
		deletePackage = true;
	}

	......

	if (deletePackage)
	{
		/** 清理! */
		delete pPackage;
	}
}

/** GameObject注销的时候也要检查清理一遍 */
void FAkComponentCallbackManager::UnregisterGameObject(AkGameObjectID in_gameObjID)
{
	AK::SoundEngine::CancelEventCallbackGameObject(in_gameObjID);

	/** 直接用GameObjectID找到关联PackageSet,整个清理掉 */
	FScopeLock Lock(&CriticalSection);
	auto pPackageSet = GameObjectToPackagesMap.Find(in_gameObjID);
	if (pPackageSet)
	{
		for (auto pPackage : *pPackageSet)
		{
			UserCookieHashToPackageMap.Remove(pPackage->KeyHash, pPackage);
			/** 清理! */
			delete pPackage;
		}

		GameObjectToPackagesMap.Remove(in_gameObjID);
	}
}

/** 最后的保底,CallbackManager析构的时候把所有package都清理掉 */
FAkComponentCallbackManager::~FAkComponentCallbackManager()
{
	for (auto& Item : GameObjectToPackagesMap)
	{
		for (auto pPackage : Item.Value)
		{	
			/** 清理! */
			delete pPackage;
		}
	}

	Instance = nullptr;
}

自定义CallbackPackage

  • 知道了Package的作用后,如果现有的Package类型不符合需求,我们就可以自定义Package了

1. 声名代理

  • 按需求声明一个自定义多播代理,也可以用已经声明好的FOnAkPostEventCallback
1
DECLARE_DYNAMIC_DELEGATE_FourParams(FCustomPostEventCallback, EAkCallbackType, CallbackType, UAkCallbackInfo*, CallbackInfo, FString, CustomStr, int, CustomIndex);

2. 创建Package子类

  • 创建IAkUserEventCallbackPackage子类Pakcage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class FCustomCallbackPackage : public IAkUserEventCallbackPackage
{
public:
	FCustomCallbackPackage(FCustomPostEventCallback PostEventCallback, uint32 Flags, uint32 in_Hash, FString in_CustomStr, int in_CustomIndex)
		: IAkUserEventCallbackPackage(Flags, in_Hash)
		, MyCallback(PostEventCallback)
		,CustomStr(in_CustomStr)
		,CustomIndex(CustomIndex)
	{}

	virtual void HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) override;
	virtual void CancelCallback() override;

private:
	FCustomPostEventCallback MyCallback;

	/** 这里可以添加各种自定义的Package成员了 */
	FString CustomStr;
	int CustonIndex;
	......
};

3. 重载CreatePackage

  • AkCallbackManager中重载CreateCallbackPackage接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** AkComponentCallbackManager.cpp **/

IAkUserEventCallbackPackage* FAkComponentCallbackManager::CreateCallbackPackage(FCustomPostEventCallback MyCallback, uint32 in_Flags, AkGameObjectID in_gameObjID)
{
	/** 需要把代理哈希后用来生成Package */
	uint32 KeyHash = GetKeyHash(MyCallback);
	/** 创建自定义Pakcage, 传入自定义变量 */
	auto pPackage = new FCustomCallbackPackage(BlueprintCallback, in_Flags, KeyHash, "Hello", 888);
	if (pPackage)
	{
		FScopeLock Lock(&CriticalSection);
		/** 一定要加到两个Map中进行维护 */
		GameObjectToPackagesMap.FindOrAdd(in_gameObjID).Add(pPackage);
		UserCookieHashToPackageMap.Add(KeyHash, pPackage);
	}

	/** 返回Package */
	return pPackage;
}

4. 实现HandleAction与CancelCalback

  • 代理类型的回调需要在HandleAction中进行GamePlay线程的异步处理
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
void FCustomCallbackPackage::HandleAction(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo)
{
	if (CustomCallback.IsBound())
	{
		/** 因为in_pCallbackInfo之后要被回收,先拷贝出来给异步处理用 */
		AkCallbackInfo* cbInfoCopy = AkCallbackTypeHelpers::CopyWwiseCallbackInfo(in_eType, in_pCallbackInfo);
		/** AkCallbackType转EAkCallbackType */
		EAkCallbackType CustomCallbackType = AkCallbackTypeHelpers::GetBlueprintCallbackTypeFromAkCallbackType(in_eType);
		/** 代理也拷贝出来给异步用 */
		auto CachedMyCallback = MyCallback;
		/** 自定义数据也拷贝出来 */
		FString Async_Str = CustomStr;
		int Async_Index = CusotomIndex;

		AsyncTask(ENamedThreads::GameThread, [cbInfoCopy, CustomCallbackType, CachedMyCallback, Async_Str, CustomIndex]
		{
			UAkCallbackInfo* CustomAkCallbackInfo = nullptr;
			if (cbInfoCopy)
			{
				/** AkCallbackInfo转UAkCallbackInfo */
				CustomAkCallbackInfo = AkCallbackTypeHelpers::GetBlueprintableCallbackInfo(CustomCallbackType, cbInfoCopy);
				/** 记得释放cookie内存 */
				FMemory::Free(cbInfoCopy);
			}
			/** 广播了!!! */
			CachedMyCallback.ExecuteIfBound(CustomCallbackType, CustomAkCallbackInfo, Async_Str, Async_Index);

			if (auto AudioDevice = FAkAudioDevice::Get())
			{
				if (auto CallbackInfoPool = AudioDevice->GetAkCallbackInfoPool())
				{
					/** UAkCallbackInfo用完要记得释放,下次才能从对象池中重复使用 */
					CallbackInfoPool->Release(CustomAkCallbackInfo);
				}
			}
		});
	}
  • 代理类型的CancelCallback把代理清理了就好了
1
2
3
4
5
void FCustomCallbackPackage::CancelCallback()
{
	CustomCallback.Clear();
	uUserFlags = 0;
}

5. 适配PostEvent接口

  • 需要新的PostEvent接口可以接纳自定义的回调代理与自定义数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AkPlayingID FAkAudioDevice::PostEvent(
	const FString& in_EventName,
	UAkComponent* in_pComponent,
	const FCustomPostEventCallback& MyCallback,
	FString in_CustomStr,
	int in_CustomIndex,
	AkUInt32 in_uFlags,
	const TArray<AkExternalSourceInfo>& in_ExternalSources,
)
{
	/** 函数对象直接由匿名函数表示, 匿名函数中创建我们的自定义Package */
	return PostEvent(in_EventName, in_pComponent, in_ExternalSources, [MyCallback, in_uFlags, in_CustomStr, in_CustomIndex, this](AkGameObjectID gameObjID) {
		return CallbackManager->CreateCallbackPackage(MyCallback, in_uFlags, gameObjID, in_CustomStr, in_CustomIndex);
	});
}

总结

  • Wwise的回调设计扩展性还是很强的,虽然有很多裸指针让人很不放心。恰当的进行自定义Package的设计可以辅助开发一些意想不到的功能,不过也要根据实际情况控制好Package处理的时效性。不需要Package的地方也可以参考Bank的CallbackInfo就可以得到不错的效果。