New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generalize asynchronous events #6092
base: minor-next
Are you sure you want to change the base?
Conversation
code came from #6015 and will be subject to probable changes
An asynchronous event is one that allows the addition of promises to be resolved before being completed. This implementation integrates priority levels, allowing you to wait for all promises added to one level before moving on to the next. This is made possible by separating the event call logic into several functions, which can then be integrated into AsyncEventTrait::callAsync()
If the event is async and the handler return Promise, it will be handle as an async event. However, if the event is async and the handler return smth different than Promise, it will be handle synchronously. An async handler can specify if it wishes to be called with no concurrent handlers with the tag @noConcurrentCall
New behaviourAbove all, the logic of synchronous events remains the same, even for asynchronous events, as long as they are called synchronously. When a listener of an asynchronous event returns a
In this way, each listener can add only one promise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tldr AsyncEventDelegate, will look again when PR is ready
)); | ||
} | ||
switch(strtolower($tags[ListenerMethodTags::NO_CONCURRENT_CALL])){ | ||
case "true": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we have multiple of these, what about extracting to a function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not directly related to this PR, it should be done externally.
break; | ||
default: | ||
throw new PluginException("Event handler " . Utils::getNiceClosureName($handlerClosure) . "() declares invalid @" . ListenerMethodTags::NO_CONCURRENT_CALL . " value \"" . $tags[ListenerMethodTags::NO_CONCURRENT_CALL] . "\""); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to this PR, but both the API and this handler would be much nicer if we use attributes instead:
#[\pocketmine\event\AsyncHandler]
public function handler(XxxEvent $event) : Promise { ... }
#[\pocketmine\event\AsyncHandler(concurrent: true)]
public function handler(XxxEvent $event) : Promise { ... }
Co-authored-by: Javier León <58715544+JavierLeon9966@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me like a lot of complexity went into making async events and their handlers use the same frameworks that sync events do. The net effect of this is that async events are callable either synchronously or asynchronously, which seems to me like a design flaw.
We can prohibit the synchronous call of a specified asynchronous event, but exiting the event will require a BC break every time we want to implement it in an event. As I said here synchronous and asynchronous calling is a subject for discussion, however, it's still necessary to keep synchronous handlers in the middle of synchronous ones, as not everyone needs to return a promise every time. |
… AsyncEventDelegate
}); | ||
$listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $asyncListeners); | ||
} | ||
$listenersByPriority = array_merge_recursive($listeners, $asyncListeners, $exclusiveAsyncListeners); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't exclusive async listeners supposed to be run before normal async listeners?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the debate going on in my neurons. I don't have any pros or cons, really.
|
New behavior (again)As async event is BC breaking by default, I detached the async event from the synchronous ones. Now, an async event should be handle with an async handler : must return a Promise. It can have a tag to specify if he need to be an exclusive handler or not. |
Introduction
This PR would be more of a foretaste for #6015 in that it provides a means of generalizing asynchronous event calling.
An asynchronous event in this context is one whose action can be deferred by plugins specifying promises to be resolved.
The integrity of event priorities is guaranteed by calling them one by one. Before moving on to the next priority, we wait until all the promises of the current one have been fulfilled.
Only the state of the server and player context can't be guaranteed. However, the state of the event is unaffected, whether it is called asynchronous or synchronous.
Behaviour
When a listener of an asynchronous event returns a
Promise
, the event is marked as asynchronous and placed at the end of the call stack for its priority.When a callAsync is called, the call flow is as follows:
In this way, each listener can add only one promise.
If, on the other hand, the handler is sensitive to changes in the event, it is preferable to add the @exclusiveCall tag, which specifies that it must be the only one being called.
Event transformation
If we wanted to implement the asynchronous chat event, this would look like this:
When calling:
Changes
API changes
AsyncEvent
classPromise::all()
Behavioural changes
Backwards compatibility
Not for the moment
Follow-up
n/a
Tests