Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow game to specify first and last mod in mod loading order. #14177

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

sfence
Copy link
Contributor

@sfence sfence commented Dec 28, 2023

Allow games to do something at the beginning and end of the mod loading process.

To do

This PR is Ready for Review.

  • Get feedback
  • Apply feedback
  • Add documentation to lua_doc.md

How to test

Switch to this branch and load the devtest game.
In devtest game.conf lines first_mod and last_mod are added. There are added checks in every devtest mod and in last_mod mod to verify if the loading order is right.

@Zughy Zughy added Feature ✨ PRs that add or enhance a feature @ Script API Roadmap: Needs approval The change is not part of the current roadmap and needs to be approved by coredevs beforehand. labels Dec 28, 2023
Copy link
Contributor

@appgurueu appgurueu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Unofficial review, not a core dev.)

Allowing games to do something here makes sense.

I'm not a fan of the repeated

if minetest.global_exists("first_mod") then
	table.insert(first_mod.mods,minetest.get_current_modname())
else
	error("ERROR: Mod "..minetest.get_current_modname().." loaded before first_mod.")
end

boilerplate in every devtest mod. Could you perhaps test this differently, for example by means of C++ unit tests?

src/content/mod_configuration.cpp Show resolved Hide resolved
@lhofhansl
Copy link
Contributor

Alternatively we can add some hooks before_mod_load, after_mod_load (or something, not good at naming) that are executed at those stages.
Games can then implement these hooks in one or more of their mods. Also that way each mod can early and late things.

@sfence
Copy link
Contributor Author

sfence commented Dec 28, 2023

Alternatively we can add some hooks before_mod_load, after_mod_load (or something, not good at naming) that are executed at those stages. Games can then implement these hooks in one or more of their mods. Also that way each mod can early and late things.

It will be complicated I think. Because every mod will have to be loaded twice time. At first time check if there are some hooks registration and the second time register nodes etc. So it will result in breaking backward compatibility, I am afraid.

But it will be a more flexible solution to do something like it.

@appgurueu
Copy link
Contributor

Also that way each mod can early and late things.

The problem with this is that it doesn't make much sense: There can't be multiple mods that load first or last. And this has practical implications: Mods that want to load first usually want to do so to, say, override some function. If you have two mods attempting to do this, you can easily get a conflict.

@sfence
Copy link
Contributor Author

sfence commented Dec 28, 2023

@appgurueu

Thanks for the feedback.

Should be testable in C++ unittests. Lua solutions take less effort to create.
But C++ unittest will be probably better for it. I will look at it if I get in right mod for it.

@wsor4035
Copy link
Contributor

just some a thought, not 100%

relating to the keys in game.conf, would it make sense to have them be something like load_first_mod = modname and load_last_mod = modname taking the syntax from world.mt

@sfence
Copy link
Contributor Author

sfence commented Dec 29, 2023

just some a thought, not 100%

relating to the keys in game.conf, would it make sense to have them be something like load_first_mod = modname and load_last_mod = modname taking the syntax from world.mt

Not a problem to change key names.
But still waiting for roadmap approval or disapproval. So it does not make sense to continue work on this now.

@lhofhansl
Copy link
Contributor

The problem with this is that it doesn't make much sense: There can't be multiple mods that load first or last. And this has practical implications: Mods that want to load first usually want to do so to, say, override some function. If you have two mods attempting to do this, you can easily get a conflict.

Fair enough. I thoughts since games control their mods, they can control the sets of mods that do that.
If this is a better approach let's do this then.

@sfence
Copy link
Contributor Author

sfence commented Dec 30, 2023

Fair enough. I thoughts since games control their mods, they can control the sets of mods that do that.
If this is a better approach let's do this then.

I think, that before and after hooks can be implemented with this as well. It only requires the first loaded mod to go through mod lists and in every mod path looks for file before_hook.lua for example. after hook can be done also through callback registration. So it will be on the game developer to allow/disallow it.

@FreeLikeGNU
Copy link
Contributor

FreeLikeGNU commented Jan 19, 2024

Each world has a list of enabled mods. Why not set the order with that list with enabled mods at the top of the list with the user able to change that order to needs?

Slightly off-topic: It would be nice to have mod settings in that list as well instead of a just a boolean it could be something like:

just use the mod's default or game.conf settings:
load_mod_some_mod = mods/some_mod

this world uses this mod in a particular way:
load_mod_some_mod = { path= "mods/some_mod", settings = {good = true, bad = 11, ugly = "welcome" } }

@sfence
Copy link
Contributor Author

sfence commented Jan 19, 2024

Each world has a list of enabled mods. Why not set the order with that list with enabled mods at the top of the list with the user able to change that order to needs?

Slightly off-topic: It would be nice to have mod settings in that list as well instead of a just a boolean it could be something like:

just use the mod's default or game.conf settings: load_mod_some_mod = mods/some_mod

this world uses this mod in a particular way: load_mod_some_mod = { path= "mods/some_mod", settings = {good = true, bad = 11, ugly = "welcome" } }

I am afraid, it will be extremely complicated for the user to determine the correct loading order to satisfy all dependencies in a manually configured loading order list. In addition, this feature works with an enabled mods list. It changes only the logic of ordering that list by dependencies.
What is the expected advantage of changing which mod will be loaded as 9th in order for example?

@appgurueu appgurueu added Concept approved Approved by a core dev: PRs welcomed! and removed Roadmap: Needs approval The change is not part of the current roadmap and needs to be approved by coredevs beforehand. labels Feb 9, 2024
Copy link
Member

@rubenwardy rubenwardy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should update src/unittest/test_servermodmanager.cpp to add a test that first_mod and last_mod are in the correct places. If you do this, you could maybe remove all the checks from mods inside devtest

games/devtest/mods/last_mod/init.lua Outdated Show resolved Hide resolved
src/content/mod_configuration.cpp Outdated Show resolved Hide resolved
src/content/mod_configuration.cpp Outdated Show resolved Hide resolved
src/content/mod_configuration.cpp Outdated Show resolved Hide resolved
src/content/mod_configuration.cpp Outdated Show resolved Hide resolved
@rubenwardy rubenwardy added this to the 5.9.0 milestone Mar 24, 2024
@rubenwardy
Copy link
Member

Adding to 5.9.0 as it reduces the impact of #14486

@sfence sfence force-pushed the define_first_last_loaded_mods branch from b304f62 to 83959b2 Compare March 27, 2024 04:48
@sfence
Copy link
Contributor Author

sfence commented Apr 9, 2024

I have compiled Minetest on my MacBook M2 and run ./bin/minetest --run-unittests command and I do not see the problem from the GitHub Macos test.
So hopefully, it is not a real problem.

Will be nice if somebody checks it also on a MacBook with an Intel CPU.

@grorp
Copy link
Member

grorp commented Apr 28, 2024

@fluxionary why 👎?

So hopefully, it is not a real problem.

I pressed the "run again" button, who knows. But if that doesn't work, you'll have to fix CI either way.

@appgurueu appgurueu force-pushed the define_first_last_loaded_mods branch from 6d7feb1 to 7002f21 Compare April 28, 2024 18:06
Copy link
Contributor

@appgurueu appgurueu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your patience.

*/
void checkConflictsAndDeps();

private:
std::optional<std::string> m_first_mod;
std::optional<std::string> m_last_mod;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw I think it's okay to use an empty string as an implicit null, avoid possible confusion between "" (often not valid) and std::nullopt

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like @appgurueu should answer this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could have left this as it was tbh, both are fine. Though since I already converted some to optionals, it improves consistency to also use optionals here. Strings are just used as mod "IDs" / "names" here. When I see a std::string in the context of a name / ID, I don't know if an empty string is allowed (a comment could help communicate this, but only by convention), and I can easily forget to handle the empty string properly.

std::optional<std::string> makes this pretty clear IMO if you know that the std::string is conceptually a name (and hence not supposed to be empty), though something like std::optional<NonEmptyString> would be better.

If we were to make this even cleaner, we could do std::optional<ModName> and using ModName = std::string;, and if we were to make this completely idiot-proof we could even have ModName be a type that enforces a valid, non-empty mod name (this would be overkill, though).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::optional<std::string> makes this pretty clear IMO if you know that the std::string is conceptually a name (and hence not supposed to be empty),

I would have interpreted that exactly opposite: someone went to the effort of adding std:optional<>, so there must be something special about "" that makes it different from a null value. maybe it's valid too?

@appgurueu appgurueu force-pushed the define_first_last_loaded_mods branch from 8cc3b40 to 3280fba Compare April 30, 2024 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Concept approved Approved by a core dev: PRs welcomed! Feature ✨ PRs that add or enhance a feature One approval ✅ ◻️ @ Script API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for mod that is loaded before any other mod
9 participants