beyond-all-reason / spring

A powerful free cross-platform RTS game engine
https://beyond-all-reason.github.io/spring/
Other
219 stars 102 forks source link

[moonshot FR] Basecontent wupget handlers: generate ordering via dependency list #937

Open sprunk opened 1 year ago

sprunk commented 1 year ago

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:

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:

before = { "Some widget whom I'm poisoning" },
after = { "Some API I'm using", "Some widget who just runs before me by design" },

The benefits are:

WatchTheFort commented 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.

MasterBel2 commented 1 year ago
sprunk commented 1 year ago

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.

sprunk commented 1 year ago

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.

WatchTheFort commented 1 year ago

@sprunk What you described sounds like a package manager. Is that what we actually need?

MasterBel2 commented 1 year ago

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?

sprunk commented 1 year ago

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.