+ Reply to Thread
Page 1 of 4 1 2 3 4 LastLast
Results 1 to 15 of 57
Like Tree1Likes

  Click here to go to the first Rift Team post in this thread.   Thread: Event API Redesign

  1.   Click here to go to the next Rift Team post in this thread.   #1
    Rift Team
    Join Date
    Oct 2010
    Posts
    927

    Default Event API Redesign

    Over the past year, it's become clear that there's missing functionality in the event system. Removing or re-ordering events is difficult and bugprone, input events on frames are tricky to work with, and multiple handlers for frame events are near-impossible to create safely.

    Due to the original design of the event API, changing these is going to require essentially breaking the entire event system. Unfortunately, this seems like something we're going to have to do. We will, of course, do this through our standard deprecation system, including at least one full patch with both systems in place and fully functional, followed by at least one full patch with the old system available via "compatibility mode".

    Keep in mind this is solely about the method to create, modify, or remove event handlers. This has nothing to do with which events will be sent, when events will be sent, or what data will be contained within those events. Assume the events you know and love will still be sent by the same sources, in the same order, and with the same data attached.

    ----

    GLOBAL EVENTS

    Global events live in the Event.* hierarchy. These events notify addons about changes in the game state or, occasionally, in global addon state. Event hooking and modification will move to Command, while the Event hierarchy will be full of otherwise-opaque values used to indicate an event. We'll be adding three overloaded functions to Command.Event:

    Command.Event.Attach(event, callback, label)
    Command.Event.Attach(event, callback, label, priority)

    Command.Event.Detach(event, callback)
    Command.Event.Detach(event, callback, label)
    Command.Event.Detach(event, callback, label, priority)
    Command.Event.Detach(event, callback, label, priority, owner)

    Inspect.Event.List(event)

    "Attach" will add a new event handler to an event. "callback" is a function, similar (though not identical - read on) to the current event handler functions. "label" is a textual label used to identify the handler for performance and error reports. "priority" is the order of the handler - handlers with higher priority will be called first. Defaults to 0 if not provided. Note that the owner does not need to be passed in, as we can just use the current

    "Detach" will remove an existing event handler from an event. It will remove a single handler that fulfills the search parameters. Any parameter besides "event" may be "nil", which means to disregard it in a search. (No, you can't have all parameters nil. That's just silly.) I'm currently a bit divided on what it should do if it couldn't find a handler - error? Return a result code? A future version may permit "event" to be nil as well.

    "List" just lists all attached handlers. A future version may include Detach()-style search parameters.

    There's only one change to the handler itself: each handler will receive a new first parameter, called "handle". This will be used to request further information from the event system as well as to give responses, if responses are needed. Right now, this will be empty for global events - it's included for future expansion and for consistency with frame events.

    The result will look something like this:

    Code:
    Command.Event.Attach(
      Event.Quest.Complete,
      function (handle, quest)
        for k, v in pairs(quest) do
          print("Completed quest " .. k)
        end
      end,
      "Completion",
      -2) -- name the event "Completion", trigger it later than default events
    The intention of these changes is that the Attach/Detach API will make it easier to order events and remove no-longer-needed event handlers, while allowing for some backend optimizations to early-exit from an unhooked event handler extremely quickly.

    Once the deprecation process is completed, the event tables themselves will become nothing more than opaque handles that can be used in arbitrary handlers.

    ----

    FRAME INFORMATION EVENTS

    Frame info events are events that notify of a change in the frame status. This includes things like Frame.Event:Layer and Frame.Event:Move. They currently live under the myframe.Event table, but they'll be moving to Event.UI. The API will be nearly identical to the global events listed above. It will be named :EventAttach, :EventDetach, and :EventList. Macro events will be handled via another pair of functions, :EventMacroSet and :EventMacroGet. Finally, like global events, frame info events will gain a new parameter, although in this case it will be the second parameter in order to make Lua-style "member functions" work properly.

    The handler will include one function, :GetTarget(), which returns the target frame of the event. I know this sounds redundant. Bear with me.

    Code:
    local frame = MakeAFrame()
    function frame:EventMoveHandler(handle)
      print("Frame " .. self:GetName() .. " moved!")
    end
    frame:EventAttach(Event.UI.Layout.Move, frame.EventMoveHandler, "move", -2)
    
    frame:EventMacroSet(Event.UI.Input.Mouse.Left.Down, "say Hello!")
    There are several reasons for these changes. First, it allows for multiple event handlers for frame events. Second, it allows for all the easy event removal and re-ordering that the global frame handlers do. Third, the frame.Event table was a rather odd inconsistency in the addon API, and this allows that table to be removed entirely, along with the GetEventTable function. Fourth, this allows for frames to have events attached to them that the frame itself does not "know" about. Finally, this allows for frame events to have a plaintext name for performance and error reports.

    Along with these changes, we'll be loosening the guarantees for frame events, most specifically Size and Move events. These events will still be guaranteed to fire before rendering, but no further guarantee will be made. This will fix a lot of extremely circuitous code as well as fix several potential race conditions and infinite loops. Technically we never made a stronger guarantee, we're just *officially* not making a stronger guarantee now.

    This may or may not come along with a frame equivalent of Utility.Event.Create(). It's intended to be designed so that one is possible, but that may not be included in the first revision.

    ----

    FRAME INPUT EVENTS

    The API in its current state doesn't distinguish between input events and information events except conceptually. The next version will make a somewhat stronger distinction. Input events are events such as LeftClick that indicate an outside effect occurred. These exist in the same place as other frame events, and the new API will be a strict superset of the API listed for "Frame Information Events".

    Along with everything listed above, frame input events will be gaining "Dive" and "Bubble" phases. On trigger, and starting with UIParent, the appropriate (eventname)Dive event will be triggered, followed by the appropriate child, until it reaches the actual frame that received the real event. The real event will trigger, and then the process repeats in reverse, this time with (eventname)Bubble. So, assuming a hierarchy of UIParent->context->container->target, the following events will trigger, in order:

    Code:
    (User left-clicks on "target")
    UIParent:Event.UI.Input.Mouse.Left.Click.Dive
    context:Event.UI.Input.Mouse.Left.Click.Dive
    container:Event.UI.Input.Mouse.Left.Click.Dive
    target:Event.UI.Input.Mouse.Left.Click.Dive
    target:Event.UI.Input.Mouse.Left.Click
    target:Event.UI.Input.Mouse.Left.Click.Bubble
    container:Event.UI.Input.Mouse.Left.Click.Bubble
    context:Event.UI.Input.Mouse.Left.Click.Bubble
    UIParent:Event.UI.Input.Mouse.Left.Click.Bubble
    (Question: should target:Event.UI.Input.Mouse.Left.Click.Dive/Bubble trigger? I'm leaning towards "yes", but if anyone has a strong rationale either way, this is the thread for it!)

    Reparenting mid-event is undefined behavior, don't do it. ("undefined behavior" in this context means "it won't crash but I reserve the right to change it later".)

    Note that each event will have the same "handle" object mentioned above, and in this context, GetTarget() is obviously a bit more useful :) Additionally, the handle object will include functions that allow the callback to modify the flow of the event. Currently planned:

    :Catch() - May be called only during the Dive phase. Causes the main event to be called on the current frame instead, followed immediately by Bubbling up from this frame. Example:

    Code:
    (User left-clicks on "target")
    UIParent:Event.UI.Input.Mouse.Left.Click.Dive
    context:Event.UI.Input.Mouse.Left.Click.Dive (:Catch() is called here)
    context:Event.UI.Input.Mouse.Left.Click
    context:Event.UI.Input.Mouse.Left.Click.Bubble
    UIParent:Event.UI.Input.Mouse.Left.Click.Bubble
    Note that target:Event.UI.Input.Mouse.Left.Click is never called, nor do the Dive/Bubble events reach container or target.

    :Halt() - May be called during the Dive or main phases. Acts like :Catch(), but additionally, skips any remaining main-event-handler events. Example:

    Code:
    (User left-clicks on "target")
    UIParent:Event.UI.Input.Mouse.Left.Click.Dive
    context:Event.UI.Input.Mouse.Left.Click.Dive (:Halt() is called here)
    context:Event.UI.Input.Mouse.Left.Click.Bubble
    UIParent:Event.UI.Input.Mouse.Left.Click.Bubble
    Note that *no* Input.Mouse.Left.Click handler is called, and, like above, no events reach container or target.

    An example of a useful application:

    Code:
    function mywindow:EventInputMouseLeftClickDiveHandler(handle)
      if self:NotOnTop() then
        handle:Catch()
      end
    end
    
    function mywindow:EventInputMouseLeftClickHandler(handle)
      self:BringToTop()
    end
    Tada, now you have windows that bring themselves to the top when clicked, without triggering background elements.

    Built-in UI-element input handling, such as RiftButton and RiftTextfield, will attach their handlers within addon space. This means that you could suppress a click or key from being recognized by RiftButton and RiftTextfield via the :Catch() or :Halt() functions, or by simply removing the handler entirely if you wanted. However, the handlers themselves will likely be represented by sentinel values which will not be manually callable. (For now.)

    Finally, security caveats: Dive and Bubble events will not support macro handlers, and :Catch() and :Halt() will not be available when the player is in combat and either the event target or the current frame is a restricted frame.

    ----

    This has been implemented and is now available on live servers. Please report any bugs ASAP!
    Last edited by ZorbaTHut; 05-22-2013 at 05:20 PM.

  2. #2
    RIFT Community Ambassador the_real_seebs's Avatar
    Join Date
    Jan 2011
    Posts
    16,859

    Default

    I sorta dislike :Sink/:Bubble, not sure why. I think because the metaphor doesn't match mine.

    Couple thoughts:

    Events should have a .target element which is the element they intended to actually trigger on. Why? Consider the "left click brings to front" frame. What I really want is "left click brings to front unless it would have been on my close button, in which case do that". So I need to compare the target to the close button.

    Possible terminology: willLeftClick/LeftClick/didLeftClick. If you do this, you can optionally use the return from willLeftClick in some way. Also, providing more "self-aware UI" functionality: Perhaps didLeftClick()'s copy of the event should have { recipient = theLeftClickHandler, result = returnFromThat } members.

    Suggestion the second: For each event, there are two conceptual Event tables, "raw" and "cooked". The cooked table has one extra member, .raw, which points to the raw table. The cooked table is modifiable, except that the .raw member cannot be changed; the raw table is unmodifiable. Event handlers are called on the cooked table.

    Note that the second suggestion works for arbitrary events, not just UI events.
    You can play WoW in any MMO. You don't have to play WoW in RIFT. Oh, and no, RIFT is not a WoW clone. Not having fun any more? Learn to play, noob! I don't speak for Riftui, but I moderate stuff there. Just came back? Welcome back! Here's what's changed. (Updated for 2.5!)

  3. #3
    Plane Touched
    Join Date
    Feb 2012
    Posts
    228

    Default

    I really wish we were able to use that Create event function from the beginning:

    The event pattern is an useful abstraction to decouple "internal" control behavior from addon behavior, so most widgets libraries use custom events in their controls. To ease use of those custom events to other developers, they're usually implemented as similar to API events as possible, so they don't have to learn a different idiom to consume them.

    The new event model could be hard to emulate, so we might end with several different custom event models, which could be confusing and harder to learn. Moreover, once these hypothetical alternative event models had been released, they'd be hard to erradicate, even if we got the Create function.

    Having the Create function from the beginning would standardize the new event model, as we probably wouldn't try to reinvent that wheel.

  4. #4
    Sword of Telara DoomSprout's Avatar
    Join Date
    Apr 2011
    Posts
    876

    Default

    I like the sink/bubble concept.

    On the input events, I really would question the need to have multiple event handlers on a UI element. Is it not complicating the API for a feature that will probably be used incredibly rarely, if ever? hmm... unless these events are eventually available for native UI elements, in which case it would make a lot more sense, as they are more global in nature. OK, maybe there is a good reason to use the mechanism.

    And I have to say... I didn't *actually* expect this to happen!

    Edit: Just a sidenote, which of course you're aware of... this change is massive, and the removal of the old event system will kill absolutely every unmaintained addon out there. I suspect it's likely to wipe out a vast swathe of addons if/when it happens.
    Last edited by DoomSprout; 11-21-2012 at 04:04 PM.

    Gadgets: Unit Frames and Other Stuff for RIFT

  5. #5
    Telaran
    Join Date
    Apr 2011
    Posts
    50

    Default

    Hmm...any chance I could get an "explain it to me like I'm 5" executive summary of these changes? What sort of changes/tune-up would existing add-ons have to implement to maintain their current functionality? Could you point to some concrete examples or limitations of the current API that this is intending to solve?

  6. #6
    Rift Disciple
    Join Date
    Jan 2011
    Posts
    147

    Default

    I would very much like the ability to create my own frame info events. For the various custom widgets in my LibSimpleWidgets addon, I had to proxy the Event table to register my own custom events while still providing the original events of the underlying frame. A non-hacky way of doing this would be nice.
    Last edited by dOxxx; 11-21-2012 at 06:56 PM.

  7. #7
    RIFT Community Ambassador the_real_seebs's Avatar
    Join Date
    Jan 2011
    Posts
    16,859

    Default

    I think we're a ways from a solid enough plan to try to explain it in clearer terms. This would basically replace all the things that are doing frame events, and probably all the other game events too, eventually, I think.

    I agree with the notion that a general mechanism for creating events would be SUPER handy. Only worry: Namespace collisions. (I always worry about namespace collisions.) Might be nice to set rules for that, like "all user events have a U prefix" or something.

    More thoughts on bubble/sink: I like the concept, but the name is hard for me because "sink" is much more strongly associated with the sense of a "data sink" or "time sink" -- a consumer, rather than a kind of motion. But "willFoo/didFoo" are more chronology; it makes sense for a given item to get willFoo/Foo/didFoo, but only if something else entirely is making decisions based on the will* results.

    How about:
    leftClickIntercept
    leftClick
    leftClickDone

    "Intercept" doesn't tell you about the relative motion, but it at least explains what the event is for -- it's your chance to intercept a left click before someone else processes it.
    You can play WoW in any MMO. You don't have to play WoW in RIFT. Oh, and no, RIFT is not a WoW clone. Not having fun any more? Learn to play, noob! I don't speak for Riftui, but I moderate stuff there. Just came back? Welcome back! Here's what's changed. (Updated for 2.5!)

  8. #8
    Plane Touched
    Join Date
    Feb 2012
    Posts
    228

    Default

    Quote Originally Posted by Toludin View Post
    Could you point to some concrete examples or limitations of the current API that this is intending to solve?
    Imagine you wanted to write a textfield that only accepts numeric input (non negative integers for simplicity):

    How would yo do it right now? Would it work fine if the player pasted a random string from his clipboard? When undoing a non number key press, do you leave the caret and selection at their previous positions? What happens if the player deletes all the text? I personally can't come with a solution for that problem that isn't tricky and bug prone.

    With the new system it becomes easier: "In the sink phase, if the key isn't a digit nor the Delete or Backspace keys, Halt()"

  9. #9
    Telaran
    Join Date
    Apr 2011
    Posts
    50

    Default

    Quote Originally Posted by Baanano View Post
    Imagine you wanted to write a textfield that only accepts numeric input (non negative integers for simplicity):

    How would yo do it right now? Would it work fine if the player pasted a random string from his clipboard? When undoing a non number key press, do you leave the caret and selection at their previous positions? What happens if the player deletes all the text? I personally can't come with a solution for that problem that isn't tricky and bug prone.

    With the new system it becomes easier: "In the sink phase, if the key isn't a digit nor the Delete or Backspace keys, Halt()"
    Ahh, okay - thank you, that does make it somewhat more concrete. So I take it, then, that these floated changes are aimed more at expanding the functionality of the Events (and making them more robust), rather than altering how Events are thought of and used in our addons in general?

    Or, to rephrase it - if the current Events do everything I want, all I will (hopefully) need to do is update the syntaxes to the new logic of sink/bubble/catch/halt?

  10. #10
    Sword of Telara DoomSprout's Avatar
    Join Date
    Apr 2011
    Posts
    876

    Default

    Toludin; Yes. If we should be able to search and replace every instance of inserting a record into an event table with the new syntax, completely ignoring the Sink/Bubble events themselves.

    Seebs; I prefer Pre/Post to Will/Did, but I agree that Sink/Bubble doesn't quite sit comfortably.


    Edit: Removed unnecessary waffle
    Last edited by DoomSprout; 11-22-2012 at 04:35 AM.

    Gadgets: Unit Frames and Other Stuff for RIFT

  11. #11
    RIFT Community Ambassador the_real_seebs's Avatar
    Join Date
    Jan 2011
    Posts
    16,859

    Default

    Yeah, the ability to interact with keypresses is sort of missing, moreso because you can't do ANYTHING about keys that don't generate a meaningful string for what the user typed, like arrow keys.
    You can play WoW in any MMO. You don't have to play WoW in RIFT. Oh, and no, RIFT is not a WoW clone. Not having fun any more? Learn to play, noob! I don't speak for Riftui, but I moderate stuff there. Just came back? Welcome back! Here's what's changed. (Updated for 2.5!)

  12.   Click here to go to the next Rift Team post in this thread.   #12
    Rift Team
    Join Date
    Oct 2010
    Posts
    927

    Default

    Quote Originally Posted by Baanano View Post
    I really wish we were able to use that Create event function from the beginning:
    I've been kinda agonizing over this one because I have no idea how it should work. There's a few problems. First off, how do new event handlers work? Do I need to insert a whole ton of new functions into every frame whenever someone creates a new event? Second, what about parent frames? If someone attaches a new event to their Text frame that has sink/bubble capability, do I need to go and add Attach/Detach functions to its *parents* to?

    I think I've actually come up with an approach, which requires redesigning the entire event API (again), but here's a brief layout:

    Instead of frame:EventLeftClickSinkAttach(func, etc), you'd call:

    Code:
    frame:EventAttach(Event.UI.Left.Click.Sink, func, etc)
    This means that every frame has exactly three event-related functions - EventAttach, EventDetach, EventList - and the actual event lists are stored under Event.UI.

    Advantages:

    * Addon-created events just show up in the hierarchy and can be used
    * Less function bloat
    * Can pass "event IDs" around as parameters - "just attach to this event"
    * Allows easy attachment of sink/bubble events to frames that aren't aware of the root event type

    Disadvantages:

    * A little more verbose, a larger change from what we have now

    Global events could work the same way:

    Code:
    Command.Event.Attach(Event.Quest.Complete, func, etc)
    This actually solves a bunch of nasty problems. I haven't rewritten the original post to take it into account yet, though.

  13. #13
    RIFT Community Ambassador the_real_seebs's Avatar
    Join Date
    Jan 2011
    Posts
    16,859

    Default

    Event.Left.Click? Shouldn't that be either Event.LeftClick, or Event.Click.Left, or possibly Event.Mouse.LeftClick?

    If you did that, of course, it'd be easy to solve namespace: Event.User.Anything.
    You can play WoW in any MMO. You don't have to play WoW in RIFT. Oh, and no, RIFT is not a WoW clone. Not having fun any more? Learn to play, noob! I don't speak for Riftui, but I moderate stuff there. Just came back? Welcome back! Here's what's changed. (Updated for 2.5!)

  14.   Click here to go to the next Rift Team post in this thread.   #14
    Rift Team
    Join Date
    Oct 2010
    Posts
    927

    Default

    Quote Originally Posted by the_real_seebs View Post
    Event.Left.Click? Shouldn't that be either Event.LeftClick, or Event.Click.Left, or possibly Event.Mouse.LeftClick?

    If you did that, of course, it'd be easy to solve namespace: Event.User.Anything.
    I'm generally taking the approach that each capital letter is a category, and in situations where splitting up categories into subtables isn't an efficiency problem, I use subtables extensively for categories.

    And I'm planning on using something like Event.UI.(addonname).(identifier). I'm not sure offhand where Command.Event.Create() stashes events, but it'll be a similar place

  15. #15
    Plane Walker Imhothar's Avatar
    Join Date
    Feb 2012
    Posts
    439

    Default

    Edit: hit reply by accident
    Last edited by Imhothar; 11-22-2012 at 11:29 AM.
    Author of the Imhothar's Bags addon.

+ Reply to Thread
Page 1 of 4 1 2 3 4 LastLast

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts