Potential Feature: Being able to modify the order in which add-ons are loaded

For example, a user with two add-ons that both use the hook on_deck_browser_will_render_content and add to content.stats might want to change the order that they appear. (E.g making review heatmap appear above the extra stats the following image).

I was thinking of achieving this by loading add-ons in the order that they appear within the QListWidget here, and letting the order be changed by dragging and dropping the items.

I’d be happy to work on this feature if it was wanted by others / would be merged into the codebase. And if anyone has any opinions or a better method of achieving this then please let me know :slight_smile:

1 Like

I think they are loaded in alphabetical order of the directory where the add-on is stored, so (even though it’s not very practical), it’s currently possible to change the order of loading of the add-ons. This is definitively not a better method, but at least a currently working one :slight_smile:

I’ve resisted this in the past out of concern that add-on authors would use it to “solve” conflicts with other add-ons, but being able to change the order things are shown on the screen does seem like a reasonable use case. It may not be trivial to implement though - how do you plan to store the order, and keep it stable in the face of extra add-ons being added? It also presumably mean the list could no longer be alphabetically sorted by default, which would be a downside for the bulk of users who wouldn’t use this feature.

3 Likes

Fair enough. Perhaps one way to prevent that would to not allow add-on authors to have a choice on the order that they load, leaving it down to each user.

I could store the order as a list of folder names, that is saved whenever the add-on dialog is closed. When add-ons are loaded, select from the list, if a corresponding folder doesn’t exist (that is, a valid add-on folder) remove from the list. If there are any folders that didn’t appear in the list, then add them to the rear (so that they are loaded last).

I think the list could still be alphabetically sorted by default, as long as there is a setting - e.g in preferences ‘Allow Add-on order to be modified?’. I do understand if you don’t want to add complexity through more user settings as you’ve mentioned before.

You could also make it alphabetically sorted by default by inserting a new add-on into the right order in the ordered list. This might be a little confusing for people using the feature though.

However, I do worry that this could uncover some previously undiscovered bugs between conflicting add-ons.

What do you think?

1 Like

The first, easy way to handle this would simply be to allow renaming of add-ons, and keep alphabetical order, like with Decks. Not very elegant, but quite straightforward.

An other option would be to have a number for each add-on, provided by the developer, which represents the priority of the add-on. For obvious backward compatibility reasons, if a developer does not provide one, it could default to something like 1000. This would allow the important feature of providing a “default way to interact with other add-ons” feature, that is, with the example of the two add-ons used by the OP, the developer of the add-on that provides statistics could choose a default priority higher / lower than the one of the heatmap add-on, allowing it to have a better default integration with it (because it wouldn’t require the user to reorder them by hand at each installation). The downside of this method, besides being a little bit harder than the precedent one, is that it’s harder to also provide a drag-and-drop feature. Instead, users would have to change the priority, which might be less practical. The upside is that add-on developers can provide a default place, with respect to other add-ons, of their own add-ons, which would very probably cover a lot of use cases.

Yet another option would be to completely separate the shown order with the order of importing. It could be done like this: when you open the add-on list, you have (at least) two fields to sort them. By their name, or by their order of importing (which would be shown in an extra column), similarly to the browser. If the users clicked on Reposition (or something similar that hasn’t already been used to avoid confusion), a pop-up opens, the same list is shown but this time the add-ons are sorted by import order, you can’t choose by what they are sorted and they are drag-and-dropable. This is probably the most complex solution, and it doesn’t allow (at least I don’t see how) the developers to give a default relative order with another add-on. On the other hand, that’s probably how most users would expect to manage their add-ons.

I feel like this may be the best option for now, as it bypasses most of the implementation complexity. Users can easily rename an add-on folder, but someone will need to check that automatic updates still work when the folder is changed from an ID to something else.

Diligent add-on authors can define a ANKIREVADDONS env var to reverse the default order for testing.

Perhaps as a first step we could allow add-ons to request a specific subscriber execution priority when subscribing to hooks, i.e. something along the lines of:

import bisect
from typing import Callable, List, Tuple


class SortedHook:
    _hooks: List[Tuple[int, Callable[[], None]]] = []

    def append(self, callback: Callable[[], None], priority: int = 0) -> None:
        bisect.insort(self._hooks, (priority, callback))

    def remove(self, callback: Callable[[], None]) -> None:
        for index, item in enumerate(self._hooks):
            if item[1] == callback:
                del self._hooks[index]
                break

    def count(self) -> int:
        return len(self._hooks)

    def __call__(self) -> None:
        for priority, callback in self._hooks:
            try:
                callback()
            except:
                # if the hook fails, remove it
                self._hooks.remove((priority, callback))
                raise

> anki_will_do_something = SortedHook()
> subscriber_that_wants_to_run_early = lambda: print("early")
> subscriber_that_wants_to_run_late = lambda: print("late")
> anki_will_do_something.append(subscriber_that_wants_to_run_late, priority=1)
> anki_will_do_something.append(subscriber_that_wants_to_run_early, priority=-1)
> anki_will_do_something()
early
late

I think that, in general, add-on import order is always a headache to reason about, and having things be predictable is important. However, given that add-ons already make things quite unpredictable by frequently employing tricks like deferring hook subscription or using low-sorted package names, perhaps a canonical API like this would be the lesser evil and actually prevent breakages caused by authors feeling that they need to use hacks (e.g. in case of on_deck_browser_will_render_content trying to inject HTML between the native stats and other add-ons for add-ons with late import time).

As far as user customizability goes, add-ons that want to support arbitrary execution order, e.g. to influence order of appearance in composite web views, could then expose an option in their settings that allows users to customize the execution priority (I’d imagine not in those terms, but more so e.g. “Show more towards the top/bottom of the screen”).

This would mean that add-on authors would still have full fine-grained control over whether or not the execution order of specific subscribers can be modified, which will reduce the number of cases to test against.

To put it in other terms:

  • If we allow users to rename add-on folders (or rather, more easily than they currently can / encourage it as part of the UI), then add-on authors have to account for both variance in the execution order of other add-ons (which they can’t control), and variance in the execution order of their own add-on across all entry points into Anki
  • If we instead go with the approach above, then while still exposed to the uncontrollability of other add-ons, authors would have much more control over the execution order of their own code, while also being able to provide some level of fine-grained customizability to their users, if they so desire.
    • Edit: To limit the combinations to think through, we could also approach this step-by-step, and only make some hooks sorted, in particular those involved in composing UIs.

I would say that ideally, though, it would be nice to move to a system like VS Code’s view containers for areas like Anki’s deck browser at some point (or e.g. the editor buttons for that matter), which would allow users to just drag-and-drop UI elements around.

I like the idea of handling the order of execution at the hook level, because why should an add-on always be executed either after or before an other add-on? However, this makes it impossible for the user to customize that (I mean, try figuring out a simple UI that allows the user to customize such a thing…), which means that if two add-ons are not properly setup, the user basically can’t do anything. On the other hand, since it’s also the current situation, this doesn’t seem too much of a problem.

This sounds good to me, it seems like it gives the right level of control to Anki developers (restricting which hooks are sorted), add-on authors (changing order and decide whether users are allowed to) and users (can change the order if allowed). The only thing I’d add is you might want to fix the range that priority can take, e.g 1-1000 to make it more readable.

I think the best way to do this would be to let the addon dev declare that his addon should load before the addons with the ids [00000001, 00000002] etc.

Now, if two addons declare the same. Let’s say that both want to load before the Image Occlusion addon. Well, then they fall back to alphabetical order between those two.

The problem with this approach is that it doesn’t scale well. Assume you have an add-on that you know must be loaded after all other add-ons (except exceptions…), for example because it’s an add-on that is supposed to check the integrity of something after all other stuff has been applied, and suppose it’s a brand new add-on I just released. Either I wait for every other add-on developer to realize their add-on should be loaded before mine (which might never happen, for unmaintained add-ons for instance), or I add every other add-on as to be loaded before mine, which is a huge work.

Perhaps I misread, but I thought that the addons already load in alphabetical order. My proposal was simply an addendum to this system.

If they’d load in alphabetical order with the option to define loading order preferences as I explained before. I think it should work no? Perhaps I’m missing something. I’m still a noob here :smiley:

I’m not sure a numerical ordering would work well with add-on authors. I fear it would lead to an arms race where authors compete to be the first/last to load to avoid compat issues with their own add-on, and it breaks down when multiple add-ons have used the first/last order. I still think manual renaming by users may be the best - it’s one of the simplest proposed solutions, is likely only going to happen when users want to control content order (reducing the likelihood of a combinatorial explosion of orders that need testing by add-on authors), and leaves the user in control.

Hi there!

Is this something that’s on the roadmap to be implemented?

Sorry, it’s not a priority at the moment.