Hi,
while trying to solve the problem how to correctly send a event + data from a gameobject/component to a single and multiple FSM's, using the default FSMEvent system some things where unexpected and could be improved.
Our main goals we wanted to achieve:
1) Able to send a fsm events similar to the unity message or UFPS event system.
2) The ability to hand over generic data to the "sendEvent" call, without directly interfering with the default Fsm.EventData member/logic.
3) Ensure that the actual event has its own copy of the event data to send, so it can be properly delayed, via default Invoke and vp_timers.
4) The ability to directly send Fsm events to a collection.
5) Ensure proper sentByInfo from script, see A)
6) Getting a warning if u try to send a unknown FSM event.
7) Make it clear to the script caller what event target will be used, so avoiding the SetEventTarget action.
Example goals:
Invoke(() => gameObject.BroadcastFsmEvent("MyEventname", ObjList, floatdata), delay);
vp_Timer.In(delay, () => gameObject.SendFsmEvent("MyEventname", targetGameobject, customDataRef));
Here is a problem we had and could not yet solve:
A) There is currently no way to set a gameobject as Event sender, even while the final usable action "Get Event Info" actually only returns a gameobject sender, instead of a Fsm.
Thats because FsmEventData only has a "public Fsm SentByFsm;" field.
Problems we run into, while trying to connect our script event system, with FSMEvents:
B) The two Fsm calls, ProcessEvent() and BroadcastEventToGameObject(), take a FsmEventData parameter, which would indicate that they also use this parameter. They both actually only use the sentbyInfo out of this structure, which caused quite some confusion on our part. So this should be better reflected, for example by better splitting the actual event data, from the sentByInfo or by making those calls private/internal.
The main thing we had to understand was: How PM handles event data and that those are global, as compared to local/per event and what this means in practice.
Here is the core extension function that achieved most of those goals:
public static void Event([NotNull] this Fsm inFsm, [NotNull] FsmEvent inFsmEvent, [CanBeNull] FsmEventData inToUseEventData = null)
{
if (inFsm == null) {
throw new ArgumentNullException("inFsm");
}
if (FsmEvent.IsNullOrEmpty(inFsmEvent)) {
throw new ArgumentNullException("inFsmEvent");
}
if (inToUseEventData == null) {
Fsm.SetEventDataSentByInfo(); // ProcessEvent will override this correctly if data is given!
}
else {
Fsm.EventData = inToUseEventData; // NOTE: needed since, ProcessEvent wont actually set the data, it only sets the sentByInfo
}
inFsm.ProcessEvent(inFsmEvent, inToUseEventData);
// TODO: copy&paste from org. whats happening here and why compared to what ProcessEvent is already doing?
if (FsmExecutionStack.ExecutingFsm != inFsm) {
FsmExecutionStack.PushFsm(inFsm);
inFsm.UpdateStateChanges();
FsmExecutionStack.PopFsm();
}
}
Here is a example gameobject extension version we now use from script:
// optimized version for collections
public static void BroadcastFsmEvent([NotNull] this GameObject inSender, [NotNull] string inEventName, [NotNull] IEnumerable<GameObject> inReceivers, [CanBeNull] System.Object inData = null)
{
if (inSender == null) {
throw new ArgumentNullException("inSender");
}
if (inEventName.IsNullOrEmpty()) {
throw new ArgumentNullException("inEventName");
}
if (inReceivers == null) {
throw new ArgumentNullException("inReceivers");
}
FsmEvent fsmEvent = PlayMakerUtils.GetFsmEventEx(inEventName);
FsmEventData eventData = PlayMakerUtils.GetAsEventData(inData);
foreach (GameObject go in inReceivers) {
foreach (PlayMakerFSM fsm in go.TryGetComponents<PlayMakerFSM>(true).Where(f => f.Fsm != null)) {
fsm.Fsm.Event(fsmEvent, eventData);
}
}
}
I hope this might get u a idea what we needed and maybe u can add/change some things around to allow better integration, by default.
bye Andy
PS: If interested i could also send the stripped down extension and utility calls we use/added?