Open sprunk opened 1 year ago
What happens if there is a circular relationship specified?
Would it be better to remove all ordering logic entirely from wupget files, and have it delegated to a dedicated loader file? The file would list out every wupget, in the order they should be loaded.
What happens if there is a circular relationship specified?
Dunno, probably you get some sort of feedback (error message? also crash if done in synced?) and none of those load. But this could be also left for devs to specify, see the next post.
Would it be better to remove all ordering logic entirely from wupget files, and have it delegated to a dedicated loader file? The file would list out every wupget, in the order they should be loaded.
No, because custom wupgets exist, you can't list them in a gameside file but they still have ordering needs. This precludes use in basecontent.
Games that don't care about modding or custom widgets could do that. But it would likely still be a bad idea because you can't tell if A is in front of B because it needs to be due to some meaningful relationship, or because it just arbitrarily ended up that way on the list because it's linear so somebody just has to be in front of the other. You can try to add comments but these will stop reflecting reality sooner or later and need maintenance on any change. It's also not tool-friendly. In general some dependencies are necessitated as implementation details (just happen to use some function exposed by a different widget) and it feels inelegant to be forced to touch a master list if these details change.
The question about load failure reminds me of a broader issue. Layers currently affect all callins the same, i.e. if A receives UnitCreated
before B then it also receives UnitDestroyed
before B. In particular, sometimes you set a layer just to get a later Initialize
, for some API to be exposed. Perhaps some way to specify loading/enabling relationship as separate from general callins would also be good?
Like here's a set of relationships that you could specify, it's a bit bloated but it's just an example for exposition.
after/before = { ... }
, controls ordering in callins except Initialize
. Circularity causes an error and disables all involved wupgets. Wupgets disabled but present in the repo contribute to the graph (rationale: transitive is convenient) but do not disable us (unless also present in requires
, see below). Missing dependency does not cause an error and is ignored (rationale: custom widgets specifying their relationship to other known custom widgets that may not be installed).requires/poisons = { ... }
, controls ordering in Initialize
. We're disabled if any requirement is disabled, but see below.enabled = "maybe"
, behaves as if disabled, but becomes enabled if required via the above. Use case: you can make a bunch of facilities for widget makers or modders, set enabled = maybe
so they don't incur perf costs by default, but can still be relied upon without modders having to modify any file other than their own wupget. Perhaps it's great for things in basecontent.replaces = { ... }
, disables everything in this set, but treats them as enabled for the purpose of "requires" etc. If we're set to enabled = maybe
we are set to enabled if anything in the set was enabled, ttherwise if all in the set are disabled, we're also disabled. If a wupget is replaced by multiple non-transitively, or if there is circularity, no replacement happens. Use case: custom widget disables a built-in one without having to do anything. Version N+1 of a thing can be copy-pasted without having to remove (or in any way deal with) version N, but you still get version N "back" if N+1 is disabled.conflicts = { ... }
, resolves the case such that at most one (possibly zero) of the conflicting widgets is enabled. Use case: automatically make sure only one wupget handles a thing. Affects toggling things later on (makes the set work like radiobuttons where it disables the others when you enable one of them).implements = { ... }
, a version of conflicts
that works on tags instead of wupget names, such that custom wupgets don't need to be aware for each other. At most one wupget implementing a tag is left enabled. For example various playerlists would have implements = { "playerlist" }
.@sprunk What you described sounds like a package manager. Is that what we actually need?
I really like most of those ideas. required = maybe
sounds especially cool for API-style widgets that implement nothing on their own, especially if they're designed such that disabling the temporarily disables the widgets that require it.
Conflicts management is interesting, but it seems to be very ugly fast. Something I'm concerned about is handling the transition between two conflicting widgets - e.g. BAR Hotkeys to my keybind editor, which migrates the preset you're using to a uikeys.txt.
I don't really like the idea of implements
, it seems much more likely to cause issues due to two widget creators not being aware of each others' widgets, and especially since just coz two things implement the same thing doesn't mean they're incompatible and don't conflict.
I think it best that to only enforce relationships we know (like require/conflict), and provide mitigations to ensure players feel free to mess around in the grey area (e.g. config/data backups). implements
violates that, but I think the rest are okay. (not sure about replaces
though, maybe best to leave as a special case of conflicts
?
package manager. Is that what we actually need?
I don't think it would be an efficient use of mana, but people work on what they want and sometimes pick up random overly ambitious projects, or pick something that has a high quality ceiling but settle for a crappy least-effort implementation that railroads things in the future. So I threw out a maximally feature-rich set of ideas just as a sort of fairly high bar of what is possible in case having these potential use cases enumerated is useful, and because it seems like a natural extension of the core proposal of before/after. It isn't needed per se, and it isn't anything resembling a final design, but having something in that vein as a feature would be useful. Note that this is all about engine handlers in basecontent, but BAR in specific might want some sort of custom widget management system anyway so perhaps some design will spill over in either direction.
The original proposal (simple before/after as a replacement for layer, without managing what is enabled) should be fairly simple to do, and there is a time component because the longer it takes the more widgets are written that (ab)use layer. I still wouldn't say it's "needed" in any sort of immediate urgency terms, but the earlier the better, and I think it would be a reasonable use of mana.
Conflicts management is interesting, but it seems to be very ugly fast
Yes, the package managing part is mostly spitballing. Most of those tags would likely need more design thought put into it, but maybe are a decent starting point. Fortunately most of these can be done separately so if somebody were to take up the task they can just skip some.
I don't really like the idea of implements, it seems much more likely to cause issues due to two widget creators not being aware of each others' widgets
The idea is that a game would have a set of well-known tags like "playerlist", "resourcebars" or "music". Widgets would then only try to use those broad categories. But sure, leave that out.
Current state of affairs
Wupgets (both widgets and gadgets) specify a numerical layer in the
wupget:GetInfo()
call. The wupget handler then sorts by this number to get the order in which wupgets receive events. If multiple wupgets have the same layer, their order is unspecified.This has multiple problems:
layer = 1337
that they can't explain, which also leaves their wupget broken in some rare edge case when the layer matters.layer = 1339
but leave everybody else (especially the newbies who will take that and copy-paste it) none the wiser since it looks the same as before.layer = 1339 -- after wupget XYZ which has layer 1337
, and possibly also add-- before wupget ABC which has layer 1339
to the other widget. This is very fragile and requires effort to maintain.Proposal
The core problem that layers try to solve is ordering, so why not do it directly? The proposal is to make it possible to construct trees like above explicitly. There would be a handful of tags to allow this, like:
The benefits are:
after = { "foo" }
and that widget layer 123, then the system automatically solves it and gives you layer 124 or layer 9999 or whatever, as long as the constraints are not broken. You can migrate to the new system gradually if need be.