SARJ
September 5, 2023, 1:14pm
#1
There is event subscription using an anonymous function:
this.entity.on('eventName', () => {}, this);
this.entity.on('eventName', function () {}, this);
In this case, how can you unsubscribe from all entity subscriptions for the given event?
Will it work, and is this method acceptable?
const self = this;
this.on('destroy', function () {
self.entity.off('eventName');
});
Should I use ‘once’ for the ‘destroy’ event, or is ‘on’ sufficient?
const self = this;
this.once('destroy', function () {
self.entity.off('eventName');
});
Hi @SARJ ,
You need to keep a reference of your anonymous function:
const myMethod = () => {};
this.entity.on('eventName', myMethod);
// later remove the event just for this method
this.entity.off('eventName', myMethod);
Ofcourse you can still call the off() without any argument, and it will handle both this method and any other attached with this eventName. It depends on what you are trying to do.
SARJ
September 5, 2023, 4:17pm
#3
Can you call it like this, without specifying functions, to unsubscribe from any events of the entity?
this.entity.off('eventName');
1 Like
That will unsubscribe any events named eventName
for this entity.
If you want to unsubscribe from any events, whatever their name is, use:
this.entity.off();
Be careful with this last one, there may be internal events the PlayCanvas engine uses that you may be turning off.
LeXXik
September 6, 2023, 12:06am
#5
With the next engine release, you will be able to do it like this:
const event = this.entity.on('eventName', myMethod);
// then later
event.off();
More details:
playcanvas:main
← Maksims:event-handle
opened 03:36PM - 14 Jul 23 UTC
This PR introduces `EventHandle` for easier events management.
# Problem:
Ev… ent management with limited lifetime of objects - can be cumbersome, especially as there is no easy way to detach events unless you have all the references to arguments that this event was created with.
Also, anonymous functions as a callback on event that needs to be removed after - is a "no no!" with a current implementation.
So when you need to subscribe to an event in a script, and manage that subscription, you need to add very similar lines to 3 places: initialize, destroy and swap. And when you have many events, it looks like copy-pasted code all around with small modifications.
Another major problem is that current EventHandler implementation returns `this` when creating an event using `on` and `once`, which historically was called "chaining", but it is not chaining as it does not offer any logic of chaining events, their order of execution and rules of execution.
So implementing EventHandle requires breaking change, which PlayCanvas avoids. As chaining has not been seen used in practice, this PR implements it in a breaking manner, to mitigate issues if someone (extremely unlikely) used chaining, this implementation makes a `on` and `once` method for backwards compatibility. So this code will still work:
````js
app.on('evtOne', () => {
// one
}).on('evtTwo', () => {
// two
});
````
### Notes:
Engine does not have a single use of "chaining" in its codebase or examples.
I've tested this PR on dozens of existing projects (old and new, small and big), and have not found a single use of "chaining".
This also been discussed on forums and on github: https://github.com/playcanvas/engine/pull/5341 https://github.com/playcanvas/engine/issues/4910
During tests, engine uses somewhere references for event handle (old object) after it has been removed.
# Solution:
Proposed implementation changes the behavior of `on` and `once` on `EventHandler`, by returning `EventHandle`. Which developer can use to detach event by simply calling `handle.off()`.
# New APIs:
```js
// pc.EventHandler
let evt = obj.on('event', () => { }); // returns EventHandle
// pc.EventHandle
evt.removed; // true if this event has been removed
evt.off(); // function to simply remove an event
```
# Examples:
ScriptType remove event when script is destroyed or swapped:
```js
Script.prototype.initialize = function () {
this.evt = this.app.on('event', this._onEvent, this);
this.once('destroy', () => {
this.evt.off();
});
};
Script.prototype.swap = function(old) {
old.evt.off();
this.evt = this.app.on('event', this._onEvent, this);
};
Script.prototype._onEvent = function() {
// event
};
```
ScriptType with many events:
```js
Script.prototype.initialize = function () {
this.bindEvents();
this.once('destroy', this.detachEvents); // make sure we detach events on destroy
};
// bind all events
Script.prototype.bindEvents = function() {
this.detachEvents();
if (!this.events) this.events = [];
this.events.push(this.app.on('eventA', this._onEvent, this));
this.events.push(this.app.on('eventB', this._onEvent, this));
this.events.push(this.app.on('eventC', this._onEvent, this));
this.events.push(this.app.on('eventD', this._onEvent, this));
};
// detach all linked events
Script.prototype.detachEvents = function() {
if (!this.events?.length) return;
for(let i = 0; i < this.events.length; i++) {
this.events[i].off();
}
this.events.length = 0;
};
Script.prototype.swap = function(old) {
old.detachEvents(); // detach events from old script instance
this.bindEvents();
};
Script.prototype._onEvent = function() {
// event
};
```
I confirm I have read the [contributing guidelines](https://github.com/playcanvas/engine/blob/master/.github/CONTRIBUTING.md) and signed the [Contributor License Agreement](https://docs.google.com/a/playcanvas.com/forms/d/1Ih69zQfJG-QDLIEpHr6CsaAs6fPORNOVnMv5nuo0cjk/viewform).
3 Likes
SARJ
September 6, 2023, 6:22am
#6
@Leonidas I want to unsubscribe all events called ‘eventName’ from the entity, so I think this is a better:
this.entity.off('eventName');
Indeed, do not need to unsubscribe everything.
1 Like
SARJ
September 6, 2023, 6:26am
#8
So I can call the following construct in the future?
const event = this.entity.on('eventName', myMethod);
this.once('destroy', function () {
event.off();
});
SARJ
September 6, 2023, 6:27am
#9
Should I use ‘once’ for the ‘destroy’ event, or is ‘on’ sufficient?
I would gather it’s the same, given the destroy
event will fire once in the lifetime of the entity. But I haven’t fully tested it and most examples use on
.
1 Like