onivim / oni

Oni: Modern Modal Editing - powered by Neovim
https://www.onivim.io
MIT License
11.35k stars 299 forks source link

Configuration.ts leaking listeners #585

Closed cyansprite closed 7 years ago

cyansprite commented 7 years ago

We create a lot of these the code path is pretty long Configuration -> Oni -> Plugin -> ... Basically, would it be more beneficial to switch this to a singleton? I'm doing this with the Config ( currently working on keybindings and abstracting it ) So I wonder if we could do the same for this?

bryphe commented 7 years ago

Oh ya, thanks for logging this issue!

I think one quick way to fix this would be, in the browser/src/Plugins/Api/Configuration.ts:

    public get onConfigurationChangedEvent(): IEvent<void> {
        return this._onConfigurationChangedEvent
    }

    private _onConfigurationChangedEvent: Event<void> = new Event<void>("oni_configuration_changed")

    constructor() {
        Config.instance().registerListener(() => {
            this._onConfigurationChangedEvent.dispatch(null)
        })
    }

    public getValue<T>(configValue: string, defaultValue?: T): T {
        return Config.instance().getValue(<any>configValue) || defaultValue
    }

This object gets created for every plugin, and it always starts listening to configuration changes... even if there is no listener registered.

I think it might be good instead to only start listening the first time we get the _onConfigurationChangedEvent (lazy init).

I don't think this particular object makes sense to be a singleton, because we basically pass it to the plugin via the Oni instance in the activate method.

One thing I'd like to move away from though, is using the Node EventEmitter class - and use the typed Event/IEvent objects in the code. You get better completion with those, and it's easier to document as opposed to a bunch of strings.

cyansprite commented 7 years ago

One thing I'd like to move away from though, is using the Node EventEmitter class - and use the typed Event/IEvent objects in the code. You get better completion with those, and it's easier to document as opposed to a bunch of strings. :+1:

I don't think this particular object makes sense to be a singleton, because we basically pass it to the plugin via the Oni instance in the activate method.

So each plugin has their own configuration?

bryphe commented 7 years ago

At least right now, there is an Oni object created for each plugin (and then each of those Oni objects creates that Configuration object, which listens to Config.ts).

Some of it is over-complicated, because previously, all the plugins were hosted in completely isolated contexts. I moved away from that now and I'd like to move towards having a common Oni object that is shared between the plugins - this would allow a model where plugins could define shared or common functionality (kind of like the Emacs plugin model). Just haven't had a chance to clean it up completely!

The side effect though is today, a new Oni object is created for each plugin, and passed to the plugin on activation. You can sort of see the lifecycle if you open the dev tools, put a breakpoint in the constructor in browser/src/Plugin/Api/Configuration.ts, and reload Oni - if you look up the callstack you'll see the flow of how it is created.

But I think the short term fix is to do the lazy initialization - and longer term, we wouldn't create new instances of this object for each plugin, we' d just have a common one (which would also help solve this problem, since we'd only register the listener once).

cyansprite commented 7 years ago

At least right now, there is an Oni object created for each plugin (and then each of those Oni objects creates that Configuration object, which listens to Config.ts).

Right I looked through the code to see the lifecycle :P I just didn't quite understand why.

Some of it is over-complicated, because previously, all the plugins were hosted in completely isolated contexts. I moved away from that now and I'd like to move towards having a common Oni object that is shared between the plugins - this would allow a model where plugins could define shared or common functionality (kind of like the Emacs plugin model). Just haven't had a chance to clean it up completely!

I think this is the best and yeah I understand, only so much you can do.

bryphe commented 7 years ago

Looks like this was addressed as part of #692