Organize the event mechanism of FairyGUI

Organize the event mechanism of FairyGUI

Analyze how the components respond to events and the scheduling structure between them
Here is my perspective to analyze the process of the event mechanism

According to the FGUI structure sharing last time, most components inherit GObject class or DisplayObject class
GObject and DisplayObject are even the most basic object classes

The task of sub item components in a UI system is to carry the display and trigger the response
The underlying component needs to respond to events
Here we introduce the first class EventDispatcher event related to today's event mechanism - dispatcher
GObject/DisplayObject inherits EventDispatcher and provides event mechanism for components
EventDispatcher class is the implementation of IEventDispatcher interface
IEventDispatcher constrains several methods required for event scheduling

	// IEventDispatcher
	void AddEventListener(string strType, EventCallback0 callback);
	void AddEventListener(string strType, EventCallback1 callback);
	void RemoveEventListener(string strType, EventCallback0 callback);
	void RemoveEventListener(string strType, EventCallback1 callback);
	bool DispatchEvent(EventContext context);

These method names add / remove EventListener()
In the parameter, you can see two kinds of delegate callbacks defined in Fgui

	public delegate void EventCallback0(); 
	public delegate void EventCallback1(EventContext context);

Here we see the EventContext events that we often deal with - the context content will not expand the EventContext for the time being
However, when you see the EventContext, you will think of the setting operations of various component events in the normal code
Like thebtn onClick:Add(...)/ Set(...)

Let's take a look at the type of onClick attribute that we often deal with in the GObject class

		// GObject
		public EventListener onClick
		{
			get { return _onClick ?? (_onClick = new EventListener(this, "onClick")); }
		}

EventListener event - listener comes into our view

		// EventListener
		EventBridge _bridge;
		string _type;

		public EventListener(EventDispatcher owner, string type)
		{
			_bridge = owner.GetEventBridge(type);
			_type = type;
		}

In EventListener
There is an event of EventBridge event link type_ bridge object and a string to identify the listening event type
Now let's continue to see how common Add/Set operations are done

		// EventListener
		public void Add(EventCallback0 callback)
		{
			_bridge.Add(callback);
		}

		public void Set(EventCallback1 callback)
		{
			_bridge.Clear();
			if (callback != null)
				_bridge.Add(callback);
		}

It's all about_ Operation of bridge
Continue to the EventBridge to see which properties and methods are available

		// EventBridge
		public EventDispatcher owner;

		EventCallback0 _callback0;
		EventCallback1 _callback1;
		EventCallback1 _captureCallback;
		internal bool _dispatching;

		public EventBridge(EventDispatcher owner)
		{
			this.owner = owner;
		}

		public void Add(EventCallback1 callback)
		{
			_callback1 -= callback;
			_callback1 += callback;
		}

		public void Remove(EventCallback1 callback)
		{
			_callback1 -= callback;
		}
		...

We can see that the callback object is quietly triggered here

Take a look at the trigger function in response to the event

	 	// EventBridge
		public void CallInternal(EventContext context)
		{
			_dispatching = true;
			context.sender = owner;
			try
			{
				if (_callback1 != null)
					_callback1(context);
				if (_callback0 != null)
					_callback0();
			}
			finally
			{
				_dispatching = false;
			}
		}

When I get here, I think about how these events are triggered and responded to
Before looking at how to be triggered, let's go back to the creation process of bridge in Listener

		// EventListener
		public EventListener(EventDispatcher owner, string type)
		{
			_bridge = owner.GetEventBridge(type);
			_type = type;
		}

In its construction, the dispatcher's acquisition method is called. At this time, the dispatcher is actually the polymorphic component object currently set
Go to EventDispatcher and take a look at GetEventBridge

		// EventDispatcher
		internal EventBridge GetEventBridge(string strType)
		{
			if (_dic == null)
				_dic = new Dictionary<string, EventBridge>();

			EventBridge bridge = null;
			if (!_dic.TryGetValue(strType, out bridge))
			{
				bridge = new EventBridge(this);
				_dic[strType] = bridge;
			}
			return bridge;
		}

It is found that each EventDispatcher has a string and eventbridge key value pair dictionary
The implementation of EventDispatcher also maintains the dictionary for the add remove operation in the previous IEventDispatcher interface
Here comes the end of EventListener EventBridge

Next, let's continue to see how the event is triggered

Both the touch determination and response trigger occur in Stage HandleEvents()
If you are interested, you can go to the Stage class and have a look

			// Stage
			void HandleEvents()
			{
				GetHitTarget();

				if (Input.GetKeyUp(KeyCode.LeftShift) || Input.GetKeyUp(KeyCode.RightShift))
					InputEvent.shiftDown = false;
				else if (Input.GetKeyDown(KeyCode.LeftShift) || Input.GetKeyDown(KeyCode.RightShift))
					InputEvent.shiftDown = true;

				UpdateTouchPosition();

				if (_customInput)
				{
					HandleCustomInput();
					_customInput = false;
				}
				else if (touchScreen)
					HandleTouchEvents();
				else
					HandleMouseEvents();

				if (_focused is InputTextField)
					HandleTextInput();
			}

In HandleEvents, the current touch logic will be used
To trigger the corresponding event of the target in TouchInfo obtained when GetHitTarget()
After that, it will call the BubbleEvent(string strType, object data) of the focus component

			// Stage
			DisplayObject clickTarget = touch.ClickTest();
			if (clickTarget != null)
			{
				touch.UpdateEvent();

				if (Input.GetMouseButtonUp(1) || Input.GetMouseButtonUp(2))
					clickTarget.BubbleEvent("onRightClick", touch.evt);
				else
					clickTarget.BubbleEvent("onClick", touch.evt);
			}

BubbleEvent is an internal method of the EventDispatcher object to trigger events
The bubbling event will trigger the same operation event for the parent component of the triggering component until the end of the stage
(calling context.StopPropagation() can stop the bubbling from advancing to the parent component)
Through GetChainBridges(strType, bubbleChain, true) method
To get the bubblechain to be triggered (list < eventbridge >)

		// EventDispatcher
		internal bool BubbleEvent(string strType, object data, List<EventBridge> addChain)
		{
			EventContext context = EventContext.Get();
			context.initiator = this;

			context.type = strType;
			context.data = data;
			if (data is InputEvent)
				sCurrentInputEvent = (InputEvent)data;
			context.inputEvent = sCurrentInputEvent;
			List<EventBridge> bubbleChain = context.callChain;
			bubbleChain.Clear();

			GetChainBridges(strType, bubbleChain, true);

			int length = bubbleChain.Count;
			for (int i = length - 1; i >= 0; i--)
			{
				bubbleChain[i].CallCaptureInternal(context);
				if (context._touchCapture)
				{
					context._touchCapture = false;
					if (strType == "onTouchBegin")
						Stage.inst.AddTouchMonitor(context.inputEvent.touchId, bubbleChain[i].owner);
				}
			}

			if (!context._stopsPropagation)
			{
				for (int i = 0; i < length; ++i)
				{
					bubbleChain[i].CallInternal(context);

					if (context._touchCapture)
					{
						context._touchCapture = false;
						if (strType == "onTouchBegin")
							Stage.inst.AddTouchMonitor(context.inputEvent.touchId, bubbleChain[i].owner);
					}

					if (context._stopsPropagation)
						break;
				}

				if (addChain != null)
				{
					length = addChain.Count;
					for (int i = 0; i < length; ++i)
					{
						EventBridge bridge = addChain[i];
						if (bubbleChain.IndexOf(bridge) == -1)
						{
							bridge.CallCaptureInternal(context);
							bridge.CallInternal(context);
						}
					}
				}
			}

			EventContext.Return(context);
			context.initiator = null;
			context.sender = null;
			context.data = null;
			return context._defaultPrevented;
		}

GetChainBridges

		// EventDispatcher
		internal void GetChainBridges(string strType, List<EventBridge> chain, bool bubble)
		{
			EventBridge bridge = TryGetEventBridge(strType);
			if (bridge != null && !bridge.isEmpty)
				chain.Add(bridge);

			if ((this is DisplayObject) && ((DisplayObject)this).gOwner != null)
			{
				bridge = ((DisplayObject)this).gOwner.TryGetEventBridge(strType);
				if (bridge != null && !bridge.isEmpty)
					chain.Add(bridge);
			}

			if (!bubble)
				return;

			if (this is DisplayObject)
			{
				DisplayObject element = (DisplayObject)this;
				while ((element = element.parent) != null)
				{
					bridge = element.TryGetEventBridge(strType);
					if (bridge != null && !bridge.isEmpty)
						chain.Add(bridge);

					if (element.gOwner != null)
					{
						bridge = element.gOwner.TryGetEventBridge(strType);
						if (bridge != null && !bridge.isEmpty)
							chain.Add(bridge);
					}
				}
			}
			else if (this is GObject)
			{
				GObject element = (GObject)this;
				while ((element = element.parent) != null)
				{
					bridge = element.TryGetEventBridge(strType);
					if (bridge != null && !bridge.isEmpty)
						chain.Add(bridge);
				}
			}
		}

At this stage, we found the life cycle of Context: acquisition, delivery and recycling
Then let's see what's in the EventContext

There is a static stack in the EventContext as the Context object pool
Each event is taken out of the pool (Get())
After initialization, assign the current event attribute on the
Empty the return pool after responding to the event (Return(EventContext value))

Let's take another look at the properties in the context object EventContext that plays the role of transmission

sender object of type EventDispatcher
   the distribution object that records the event trigger is assigned in the CallInternal() of the EventDispatcher
initiator object of type object
  it records that the initiator of the event trigger assigns a value before the bridge calls CallInternal()
  the distributor and initiator of general events are the same, but they may be different if the event has bubbled
Type object of type string
  record event type identification
inputEvent object of type inputEvent
  if the event is a keyboard / touch / mouse event, the relevant data of such event can be obtained by accessing the inputEvent object
data object of type object
   event data provides user-defined data transfer objects with assignment before bridge calls CallInternal()

There are two situations in use
Context is used when obtaining the data of the button snder. data
When obtaining the list item data, you need to use context data. data
Why

Because context is generated in different ways

The context obtained when the button event is triggered Sender is the distribution object of this event. This button itself
Because it assigns itself to the sender object when calling internal() of the bridge
Therefore, the data value of sender is the data value of the button to be obtained
At this time, the data of the button is the touch passed in in in response to the event evt
clickTarget.BubbleEvent("onClick", touch.evt);
context.sender = owner;

The list calls onclickItem logic, and the processing of sub items is different
It is found in the GList class that clicking events will be registered for the sub item component by default when adding a list sub item

	//GList
	override public GObject AddChildAt(GObject child, int index)
    {
        base.AddChildAt(child, index);
        if (child is GButton)
        {
            GButton button = (GButton)child;
            button.selected = false;
            button.changeStateOnClick = false;
        }

        child.onClick.Add(_itemClickDelegate);
        child.onRightClick.Add(_itemClickDelegate);

        return child;
    }

_itemClickDelegate = __clickItem;

    void __clickItem(EventContext context)
    {
        GObject item = context.sender as GObject;
        if ((item is GButton) && selectionMode != ListSelectionMode.None)
            SetSelectionOnEvent(item, context.inputEvent);

        if (scrollPane != null && scrollItemToViewOnClick)
            scrollPane.ScrollToView(item, true);

        DispatchItemEvent(item, context);
    }

    virtual protected void DispatchItemEvent(GObject item, EventContext context)
    {
        if (context.type == item.onRightClick.type)
            DispatchEvent("onRightClickItem", item);
        else
            DispatchEvent("onClickItem", item);
    }

When the sub item click event is triggered, the internal response event will be triggered, that is, the DispatchEvent in the IEventDispatcher interface
Because it is a logical point, you only need to trigger its ontology, so you don't need a BubbleEvent
You can see that the data passed in at this time is item
Then, for the context obtained in the pool Data assignment
It is the incoming item, so it is context when taking the data value data. data

	// EventDispatcher
	public bool DispatchEvent(string strType, object data)
    {
        return InternalDispatchEvent(strType, null, data, null);
    }

    public bool DispatchEvent(string strType, object data, object initiator)
    {
        return InternalDispatchEvent(strType, null, data, initiator);
    }

    static InputEvent sCurrentInputEvent = new InputEvent();

    internal bool InternalDispatchEvent(string strType, EventBridge bridge, object data, object initiator)
    {
        if (bridge == null)
            bridge = TryGetEventBridge(strType);

        EventBridge gBridge = null;
        if ((this is DisplayObject) && ((DisplayObject)this).gOwner != null)
            gBridge = ((DisplayObject)this).gOwner.TryGetEventBridge(strType);

        bool b1 = bridge != null && !bridge.isEmpty;
        bool b2 = gBridge != null && !gBridge.isEmpty;
        if (b1 || b2)
        {
            EventContext context = EventContext.Get();
            context.initiator = initiator != null ? initiator : this;
            context.type = strType;
            context.data = data;
            if (data is InputEvent)
                sCurrentInputEvent = (InputEvent)data;
            context.inputEvent = sCurrentInputEvent;

            if (b1)
            {
                bridge.CallCaptureInternal(context);
                bridge.CallInternal(context);
            }

            if (b2)
            {
                gBridge.CallCaptureInternal(context);
                gBridge.CallInternal(context);
            }

            EventContext.Return(context);
            context.initiator = null;
            context.sender = null;
            context.data = null;

            return context._defaultPrevented;
        }
        else
            return false;
    }

So summarize the whole event mechanism structure of FGUI

EventDispatcher distributes and manages event types
EventListener event listener object
The EventBridge event bus performs callback collection, management and response
EventContext the event context content is responsible for transmitting information

Finally, a class diagram is attached

Posted by KC_Geek on Mon, 23 May 2022 03:40:13 +0300