Policy设计理念
Policy是设计模式中“策略模式”的一种应用。在程序中,使用组合代替继承是一种更好的方法,假设一个类需要实现特别多的功能,将所有的功能在其中实现会导致大量的编程心智负荷,并且其中一个功能发生改变的时候,不可避免的需要修改整个类的设计。
如果将此类的所有功能拆分成多个部分,每一部分由一个类来实现,然后再将所有的类组合起来,不但使类的设计更加清晰,并且可以根据需要灵活地替换掉其中一部分功能。这就是组合带来的好处。
如果使用多重继承来实现组合,并不利于替换其中的某个功能,修改类的继承方式是一件麻烦的事。在C++中,模板非常合适用来实现组合功能,因为模板在编译期生成代码,可以通过不同的特化实现不同的组合方式。
举一个例子,假设现在有一个类,它的其中一个功能就是通过Create函数创造自身对象(因为各种原因,我们希望可以控制这个类的构建行为,比如管理它的内存分配方式)。可以这样设计:
template<typename T>
class CNewCreator
{
public:
static T* Create() { return new T; }
};
template<typename T>
class CMallocCreator
{
public:
static T* Create()
{
void* pBuff = malloc(sizeof(T));
if (pBuff)
{
return static_cast<T*>(pBuff);
}
return nullptr;
}
};
template<typename T>
class CNotCreator
{
public:
static T* Create()
{
return nullptr;
}
};
template<typename T, template<typename> class Creator>
class Test: public Creator<T>
{
public:
void Create()
{
value = Creator<T>::Create();
}
private:
T* value;
}
接下来就可以根据需要选择特化哪种类型的Creator。
int main()
{
Test<AngClass, CNewCreator> newTest;
Test<AngClass, CMallocCreator> mallocTest;
}
newTest和mallocTest都是Test类型,但是它们使用不同的创建方式创建AngClass对象。
EBusAddressPolicy
用来定义EBus是否使用Address,并且确定Address是否可以排序。
enum class EBusAddressPolicy
{
/**
* (Default) The EBus has a single address.
*/
Single,
/**
* The EBus has multiple addresses; the order in which addresses
* receive events is undefined.
* Events that are addressed to an ID are received by handlers
* that are connected to that ID.
* Events that are broadcast without an ID are received by
* handlers at all addresses.
*/
ById,
/**
* The EBus has multiple addresses; the order in which addresses
* receive events is defined.
* Events that are addressed to an ID are received by handlers
* that are connected to that ID.
* Events that are broadcast without an ID are received by
* handlers at all addresses.
* The order in which addresses receive events is defined by
* AZ::EBusTraits::BusIdOrderCompare.
*/
ByIdAndOrdered,
};
EBusHandlerPolicy
决定可以连接到EBus的Handler数量。
/**
* Defines how many handlers can connect to an address on the EBus
* and the order in which handlers at each address receive events.
*/
enum class EBusHandlerPolicy
{
/**
* The EBus supports one handler for each address.
*/
Single,
/**
* (Default) Allows any number of handlers for each address;
* handlers at an address receive events in the order
* in which the handlers are connected to the EBus.
*/
Multiple,
/**
* Allows any number of handlers for each address; the order
* in which each address receives an event is defined
* by AZ::EBusTraits::BusHandlerOrderCompare.
*/
MultipleAndOrdered,
};
EBusConnectionPolicy
提供用户使用的connection policies,可以使用其自定义EBus连接时触发的动作。
template <class Bus>
struct EBusConnectionPolicy
{
// 一些必要的数据,如BusPtr BusIdType
...
/**
* Connects a handler to an EBus address.
* @param ptr[out] A pointer that will be bound to the EBus address that
* the handler will be connected to.
* @param context Global data for the EBus.
* @param handler The handler to connect to the EBus address.
* @param id The ID of the EBus address that the handler will be connected to.
*/
static void Connect(BusPtr& ptr, Context& context, HandlerNode& handler, ConnectLockGuard& contextLock, const BusIdType& id = 0);
/**
* Disconnects a handler from an EBus address.
* @param context Global data for the EBus.
* @param handler The handler to disconnect from the EBus address.
* @param ptr A pointer to a specific address on the EBus.
*/
static void Disconnect(Context& context, HandlerNode& handler, BusPtr& ptr);
};
////////////////////////////////////////////////////////////
// Implementations
////////////////////////////////////////////////////////////
template <class Bus>
void EBusConnectionPolicy<Bus>::Connect(BusPtr&, Context&, HandlerNode&, ConnectLockGuard&, const BusIdType&)
{
}
template <class Bus>
void EBusConnectionPolicy<Bus>::Disconnect(Context&, HandlerNode&, BusPtr&)
{
}
用户可以自定义EBusConnectionPolicy<Bus>::Connect和Disconnect,默认它们什么都不做。
StoragePolicy
EBusGlobalStoragePolicy
定义EBus的数据储存的位置,默认每一个dll储存它们自己的EBus实例数据。这里使用了静态局部变量。
/**
* A choice of AZ::EBusTraits::StoragePolicy that specifies
* that EBus data is stored in a global static variable.
* With this policy, each module (DLL) has its own instance of the EBus.
* @tparam Context A class that contains EBus data.
*/
template <class Context>
struct EBusGlobalStoragePolicy
{
/**
* Returns the EBus data.
* @return A pointer to the EBus data.
*/
static Context* Get()
{
// Because the context in this policy lives in static memory space, and
// doesn't need to be allocated, there is no reason to defer creation.
return &GetOrCreate();
}
/**
* Returns the EBus data.
* @return A reference to the EBus data.
*/
static Context& GetOrCreate()
{
static Context s_context;
return s_context;
}
};
EBusThreadLocalStoragePolicy
定义EBus的数据储存的位置,每个线程储存它自己的EBus实例数据。
/**
* A choice of AZ::EBusTraits::StoragePolicy that specifies
* that EBus data is stored in a thread_local static variable.
* With this policy, each thread has its own instance of the EBus.
* @tparam Context A class that contains EBus data.
*/
template <class Context>
struct EBusThreadLocalStoragePolicy
{
/**
* Returns the EBus data.
* @return A pointer to the EBus data.
*/
static Context* Get()
{
// Because the context in this policy lives in static memory space, and
// doesn't need to be allocated, there is no reason to defer creation.
return &GetOrCreate();
}
/**
* Returns the EBus data.
* @return A reference to the EBus data.
*/
static Context& GetOrCreate()
{
thread_local static Context s_context;
return s_context;
}
};
EBusQueuePolicy
用来顺序执行EBus指定的function。
默认没有任何实现,
template <bool IsEnabled, class Bus, class MutexType>
struct EBusQueuePolicy
{
typedef AZ::Internal::NullBusMessageCall BusMessageCall;
void Execute() {};
void Clear() {};
void SetActive(bool /*isActive*/) {};
bool IsActive() { return false; }
size_t Count() const { return 0; }
};
当IsEnabled为true时,特化版本才有实现,
template <class Bus, class MutexType>
struct EBusQueuePolicy<true, Bus, MutexType>
{
typedef AZStd::function<void()> BusMessageCall; // 可以执行的函数
typedef AZStd::deque<BusMessageCall, typename Bus::AllocatorType> DequeType;
typedef AZStd::queue<BusMessageCall, DequeType > MessageQueueType;
EBusQueuePolicy() = default;
bool m_isActive = Bus::Traits::EventQueueingActiveByDefault;
MessageQueueType m_messages;
MutexType m_messagesMutex; ///< Used to control access to the m_messages. Make sure you never interlock with the EBus mutex. Otherwise, a deadlock can occur.
// 顺序执行m_messages中的所有函数
void Execute()
{
AZ_Warning("System", m_isActive, "You are calling execute queued functions on a bus which has not activated its function queuing! Call YourBus::AllowFunctionQueuing(true)!");
MessageQueueType localMessages;
// Swap the current list of queue functions with a local instance
{
AZStd::scoped_lock lock(m_messagesMutex);
AZStd::swap(localMessages, m_messages);
}
// Execute the queue functions safely now that are owned by the function
while (!localMessages.empty())
{
const BusMessageCall& localMessage = localMessages.front();
localMessage();
localMessages.pop();
}
}
void Clear()
{
AZStd::lock_guard<MutexType> lock(m_messagesMutex);
m_messages = {};
}
void SetActive(bool isActive)
{
AZStd::lock_guard<MutexType> lock(m_messagesMutex);
m_isActive = isActive;
if (!m_isActive)
{
m_messages = {};
}
};
bool IsActive()
{
return m_isActive;
}
size_t Count()
{
AZStd::lock_guard<MutexType> lock(m_messagesMutex);
return m_messages.size();
}
};
非常简单的功能,管理一个队列的function,并且可以控制是否active,Executes顺序执行所有function,并且此队列保证是线程安全的。
Router
Router功能用来控制在EBus分发事件的行为,在EBusContainer中就使用过它,见 Evnt Bus Internal 关于EBUS_DO_ROUTING 章节。
EBusRouterNode
封装一个Interface,使其可以成为intrusive_multiset_node,也就是这个对象可以作为一个节点放到intrusive_multiset中。
template <class Interface>
struct EBusRouterNode
: public AZStd::intrusive_multiset_node<EBusRouterNode<Interface>>
{
Interface* m_handler = nullptr;
int m_order = 0;
EBusRouterNode& operator=(Interface* handler);
Interface* operator->() const;
operator Interface*() const;
bool operator<(const EBusRouterNode& rhs) const;
};
template <class Interface>
inline EBusRouterNode<Interface>& EBusRouterNode<Interface>::operator=(Interface* handler)
{
m_handler = handler;
return *this;
}
//////////////////////////////////////////////////////////////////////////
template <class Interface>
inline Interface* EBusRouterNode<Interface>::operator->() const
{
return m_handler;
}
//////////////////////////////////////////////////////////////////////////
template <class Interface>
inline EBusRouterNode<Interface>::operator Interface*() const
{
return m_handler;
}
//////////////////////////////////////////////////////////////////////////
template <class Interface>
bool EBusRouterNode<Interface>::operator<(const EBusRouterNode& rhs) const
{
return m_order < rhs.m_order;
}
在EBus中,Interface表示可以收到事件通知的对象,也就是Handler,所以EBusRouterNode的功能与Handler相似,都是用来接收事件通知并触发某个函数的。
EBusRouterPolicy
包含多个EBusRouterNode,可以执行这些EBusRouterNode的某个函数。
template <class Bus>
struct EBusRouterPolicy
{
using RouterNode = EBusRouterNode<typename Bus::InterfaceType>;
typedef AZStd::intrusive_multiset<RouterNode, AZStd::intrusive_multiset_base_hook<RouterNode>> Container;
// We have to share this with a general processing event if we want to support stopping messages in progress.
enum class EventProcessingState : int
{
ContinueProcess, ///< Continue with the event process as usual (default).
SkipListeners, ///< Skip all listeners but notify all other routers.
SkipListenersAndRouters, ///< Skip everybody. Nobody should receive the event after this.
};
template <class Function, class... InputArgs>
inline bool RouteEvent(const typename Bus::BusIdType* busIdPtr, bool isQueued, bool isReverse, Function&& func, InputArgs&&... args);
Container m_routers;
};
这里定义了EventProcessingState,有3个,分别是:
- ContinueProcess:默认设置,继续处理事件。
- SkipListeners:跳过所有监听者(Handler)但是通知Routers.
- SkipListenersAndRouters:跳过所有人,也就是此事件在此次处理后就结束。
在EBus中对Router功能有这样一段解释:
/**
* Controls the flow of EBus events.
* Enables an event to be forwarded, and possibly stopped, before reaching
* the normal event handlers.
* Use cases for routing include tracing, debugging, and versioning an %EBus.
* The default `EBusRouterPolicy` forwards the event to each connected
* `EBusRouterNode` before sending the event to the normal handlers. Each
* node can stop the event or let it continue.
*/
使用Router可以控制EBus事件的传播流,比如在Handler收到一个事件前,将此事件转发或停止,用来追踪或Debug事件。默认情况下,EBus将事件发送至Handler前,会先将此事件发送至EBusRouterNode,而EBusRouterNode可以控制事件是否停止。
所以上面的3个EventProcessingState就是用来控制Router的设置。
在EBusContainer的实现中,进行Broadcas或Event触发事件时,都会先执行 EBUS_DO_ROUTING,它根据RouteEvent函数的返回结果来决定是否继续执行事件分发任务,所以Router功能的核心就在于它的RouteEvent函数,下面来看它的实现:
template <class Bus>
template <class Function, class... InputArgs>
inline bool EBusRouterPolicy<Bus>::RouteEvent(const typename Bus::BusIdType* busIdPtr, bool isQueued, bool isReverse, Function&& func, InputArgs&&... args)
{
auto rtLast = m_routers.end();
// 这里使用m_routers的begin迭代器来创建一个RouterCallstackEntry对象
typename Bus::RouterCallstackEntry rtCurrent(m_routers.begin(), busIdPtr, isQueued, isReverse);
// 遍历m_routers中的所有EBusRouterNode,并触发它们的func函数
while (rtCurrent.m_iterator != rtLast)
{
AZStd::invoke(func, (*rtCurrent.m_iterator++), args...);
// SkipListenersAndRouters,此事件在发送给当前EBusRouterNode后就结束,这里立即返回true
if (rtCurrent.m_processingState == EventProcessingState::SkipListenersAndRouters)
{
return true;
}
}
// ContinueProcess,所有EBusRouterNode收到事件后,还要将事件发送给Handler
if (rtCurrent.m_processingState != EventProcessingState::ContinueProcess)
{
return true;
}
// SkipListeners 所有EBusRouterNode收到事件后,不再将事件发送给Handler
return false;
}
这里的实现有Bug?在EBUS_DO_ROUTING中是RouteEvent返回true事件就停止,这里的ContinueProcess和SkipListeners的处理写反了。
在此函数中,使用到了EBus中的对象Bus::RouterCallstackEntry,它的定义如下:
struct RouterCallstackEntry
: public CallstackEntry
{
typedef typename RouterPolicy::Container::iterator Iterator;
RouterCallstackEntry(Iterator it, const BusIdType* busId, bool isQueued, bool isReverse);
~RouterCallstackEntry() override = default;
void SetRouterProcessingState(RouterProcessingState state) override;
bool IsRoutingQueuedEvent() const override;
bool IsRoutingReverseEvent() const override;
Iterator m_iterator;
RouterProcessingState m_processingState;
bool m_isQueued;
bool m_isReverse;
};
//=========================================================================
template<class Interface, class Traits>
EBus<Interface, Traits>::RouterCallstackEntry::RouterCallstackEntry(Iterator it, const BusIdType* busId, bool isQueued, bool isReverse)
: CallstackEntry(EBus::GetContext(), busId)
, m_iterator(it)
, m_processingState(RouterPolicy::EventProcessingState::ContinueProcess)
, m_isQueued(isQueued)
, m_isReverse(isReverse)
{
}
//=========================================================================
template<class Interface, class Traits>
void EBus<Interface, Traits>::RouterCallstackEntry::SetRouterProcessingState(RouterProcessingState state)
{
m_processingState = state;
}
//=========================================================================
template<class Interface, class Traits>
bool EBus<Interface, Traits>::RouterCallstackEntry::IsRoutingQueuedEvent() const
{
return m_isQueued;
}
//=========================================================================
template<class Interface, class Traits>
bool EBus<Interface, Traits>::RouterCallstackEntry::IsRoutingReverseEvent() const
{
return m_isReverse;
}
首先,它是一个CallstackEntry,这种对象的特点是当创建对象时它自动加入EBus的m_context→s_callstack中,它是一个链表,并且每个线程有一个自己的s_callstack。当一个Handler断开时连接,它的OnRemoveHandler和OnPostRemoveHandler函数会被调用(这里没有使用这个特性)。而当一个CallstackEntry析构时,它自动从s_callstack删除。
借用CallstackEntry自动加入s_callstack和从其中删除的特性,在RouteEvent函数调用期间,EBus可以很方便地询问Router的一些状态,比如上面的IsRoutingQueuedEvent和IsRoutingReverseEvent。
EBusEventProcessingPolicy
最后的EBusEventProcessingPolicy用来实现EBus的函数调用操作,在EBusContainer中,Event和Broadcast函数就是使用它来调用handler的函数的,
EBusEventProcessingPolicy具体的实现如下:
struct EBusEventProcessingPolicy
{
template<class Results, class Function, class Interface, class... InputArgs>
static void CallResult(Results& results, Function&& func, Interface&& iface, InputArgs&&... args)
{
results = AZStd::invoke(AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
}
template<class Function, class Interface, class... InputArgs>
static void Call(Function&& func, Interface&& iface, InputArgs&&... args)
{
AZStd::invoke(AZStd::forward<Function>(func), AZStd::forward<Interface>(iface), AZStd::forward<InputArgs>(args)...);
}
};
有CallResult和Call两个版本,其中都是使用std::invoke来实现的(C++17加入的新特性)。
总结
按照程序数据与行为分离的实现思想,EBusContainer保存了EBus所使用的数据,而Policy模块则提供了EBus各种行为的实现,包括EBus本身Address和Handler的控制,Router功能和EventProcessing调用可执行函数,