Skip to content

gui/mod-manager improvements #1481

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

Merged
merged 21 commits into from
Jul 26, 2025
Merged

Conversation

ong-yinggao98
Copy link
Contributor

@ong-yinggao98 ong-yinggao98 commented Jul 12, 2025

Requires DFHack/dfhack#5500 be merged for arena mode views

The manager also notifies users when a version of a vanilla module in their preset has been changed. This way they should know to update the preset to avoid the popup in future. Since mod manager no longer gives a hoot about the vanilla module versions, an entirely vanilla preset for 51.13 should theoretically also be backwards compatible with an older version.

image

TODO

@ab9rf
Copy link
Member

ab9rf commented Jul 12, 2025

I suspect the reason why fields.src_dir[i].value:startswith('data/vanilla') does not work is because fields.src_dir[i].value is a C++ string (being accessed via proxy), not a Lua string, and so the Lua method startswith (which is defined for Lua strings but not for C++ strings) doesn't work.

@ong-yinggao98
Copy link
Contributor Author

ong-yinggao98 commented Jul 12, 2025

I suspect the reason why fields.src_dir[i].value:startswith('data/vanilla') does not work is because fields.src_dir[i].value is a C++ string (being accessed via proxy), not a Lua string, and so the Lua method startswith (which is defined for Lua strings but not for C++ strings) doesn't work.

Interesting, but in that case why does it work in script-manager.lua::get_active_mods()? It seems to call startswith just fine and the target seems to also be a C++ string.

-- local ol = df.global.world.object_loader
        local path = ol.object_load_order_src_dir[idx].value
        table.insert(mods, {
            id=ol.object_load_order_id[idx].value,
            name=ol.object_load_order_name[idx].value,
            version=ol.object_load_order_displayed_version[idx].value,
            numeric_version=ol.object_load_order_numeric_version[idx],
            path=path,
            vanilla=path:startswith('data/vanilla/'), -- windows also uses forward slashes
        })

@SilasD
Copy link
Contributor

SilasD commented Jul 12, 2025

maybe forcing it into a Lua string would work? untested code.

tostring(fields.src_dir[i].value):startswith('data/vanilla')

Edit: Your comment in gui/mod-manager.lua shows this code:
from_fields.src_dir[i].startswith('data/vanilla')
That doesn't work the same as this edited code:
from_fields.src_dir[i]:startswith('data/vanilla')
The . was changed to :, to comply with Lua's object-oriented style string functions.

@ab9rf
Copy link
Member

ab9rf commented Jul 13, 2025

Interesting, but in that case why does it work in script-manager.lua::get_active_mods()? It seems to call startswith just fine and the target seems to also be a C++ string.

-- local ol = df.global.world.object_loader
        local path = ol.object_load_order_src_dir[idx].value
        table.insert(mods, {
            id=ol.object_load_order_id[idx].value,
            name=ol.object_load_order_name[idx].value,
            version=ol.object_load_order_displayed_version[idx].value,
            numeric_version=ol.object_load_order_numeric_version[idx],
            path=path,
            vanilla=path:startswith('data/vanilla/'), -- windows also uses forward slashes
        })

Because local path = ol.object_load_order_src_dir[idx].value results in it being converted from a C++ string to a Lua string, and so path:startswith is looking for that named method on a Lua string.

This is an artifact of how our C++ object proxying into Lua works. When you directly "read" from a C++ string in order to use its value as a Lua value, the proxy handler automatically converts it into a Lua string. However, when you call a method directly on an object, this doesn't happen: instead, the metatable of the C++ proxy object is consulted, and as there is no startswith method in the C++ string proxy object metatable, the method call fails.

It might be an idea to "chain" the Lua string metatable onto the C++ string proxy metatable, but I'm not at all sure how to go about doing this. Will require considerable thought.

Copying the proxy object into a local variable before calling the method should provide a workaround since the C++ proxy object will be converted to a Lua object at that time.

@ong-yinggao98
Copy link
Contributor Author

I haven't tried SilasD's suggestion, but ab9rf's works like a charm.

@ong-yinggao98 ong-yinggao98 changed the title Edits to the mod manager gui/mod-manager version compatibility Jul 13, 2025
@ong-yinggao98 ong-yinggao98 changed the title gui/mod-manager version compatibility gui/mod-manager improvements Jul 13, 2025
@ong-yinggao98 ong-yinggao98 marked this pull request as ready for review July 13, 2025 15:13
@ab9rf
Copy link
Member

ab9rf commented Jul 25, 2025

I just tested this locally with 52.02 and the arena mode functionality does not work. It's possible that the viewscreen name has changed in 52.xx, but that needs to be addressed before I can consider merging this.

@SilasD
Copy link
Contributor

SilasD commented Jul 25, 2025

As a minor note, Lua supports multiple return values.
Lua 5.3 Reference Manual, section 3.3.4

Functions can return more than one value, (... specific syntax ...)

I'm going to go through and flag these, with suggested code.

@lethosor
Copy link
Member

lethosor commented Jul 25, 2025

@ab9rf

I suspect the reason why fields.src_dir[i].value:startswith('data/vanilla') does not work is because fields.src_dir[i].value is a C++ string (being accessed via proxy), not a Lua string, and so the Lua method startswith (which is defined for Lua strings but not for C++ strings) doesn't work.

This doesn't really make sense to me. C++ strings returned through the Lua wrapper are Lua strings.

string* is different - that is exposed as a pointer with a .value field, which is a string. (Maybe this is what you meant by "proxy object"?)

I see this in the PR:

local function vanilla(dir)
    dir = dir.value
    return dir:startswith('data/vanilla')
end

which appears to only work on string*s, because it uses .value but I see no reason why startswith() wouldn't work once the string has been dereferenced.

mod_index = i
break
end
end

if mod_index == nil then
return false
return { success= false, version= nil }
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider
return false, nil

Copy link
Member

Choose a reason for hiding this comment

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

I think either is fine. And I think there is a decent argument for using "named" return values like this instead of unnamed ones (multiple returns)

@ab9rf
Copy link
Member

ab9rf commented Jul 25, 2025

This doesn't really make sense to me. C++ strings returned through the Lua wrapper are Lua strings.

Only if they pass through stl_string_identity::lua_write. My best guess, based on the observed behavior, is that this isn't happening when the object is being pulled out of a "container" object; instead of ultimately pushing a Lua string onto the stack, it's pushing the Lua userdata object that proxies the C++ string without using the lua_write specialization that converts C++ strings into Lua strings. It has to retain the proxy object so that if it gets assigned to the assignment uses the STL string assignment method. Otherwise you wouldn't be able to do object.field = "string" and get a useful result.

@lethosor
Copy link
Member

Only if they pass through stl_string_identity::lua_write. My best guess, based on the observed behavior, is that this isn't happening when the object is being pulled out of a "container" object; instead of ultimately pushing a Lua string onto the stack, it's pushing the Lua userdata object that proxies the C++ string without using the lua_write specialization that converts C++ strings into Lua strings. It has to retain the proxy object so that if it gets assigned to the assignment uses the STL string assignment method. Otherwise you wouldn't be able to do object.field = "string" and get a useful result.

Ok, so what I'm trying to say is that there are two cases:

  • Native Lua strings, equivalent to "foo" (also returned by reading std::string fields from C++), where startswith() works
  • Proxy objects, with a .value field, defined by DFHack and returned when reading std::string* - startswith() does not work on these natively, but does work on the .value field

And there isn't a third case that looks like case 1 but where startswith() doesn't work. (i.e. this is unlike Java/JavaScript where there is a distinction between string literals and new String())

@ong-yinggao98
Copy link
Contributor Author

ong-yinggao98 commented Jul 25, 2025

I just tested this locally with 52.02 and the arena mode functionality does not work. It's possible that the viewscreen name has changed in 52.xx, but that needs to be addressed before I can consider merging this.

Correct, please do not merge it yet. I had mentioned that I need the the related PR merged but I'll move it to the top of the description in case just for more prominence.

Requires DFHack/dfhack#5500

@ab9rf
Copy link
Member

ab9rf commented Jul 25, 2025

I just tested this locally with 52.02 and the arena mode functionality does not work. It's possible that the viewscreen name has changed in 52.xx, but that needs to be addressed before I can consider merging this.

Correct, please do not merge it yet. I mentioned that I need the the related PR merged but I'll move it to the top of the description in bold.

Requires DFHack/dfhack#5500

Gotcha, I will go look at that PR now.

@ong-yinggao98
Copy link
Contributor Author

I'm going to go through and flag these, with suggested code.

In hindsight unpacked return types should have been obvious to me, thanks a ton for the comments and suggestions.

@ab9rf
Copy link
Member

ab9rf commented Jul 25, 2025

I just tested this locally, after merging DFHack/dfhack#5500, and while the panel appears in arena mode and I can save profiles I cannot load a profile.

I suspect that this needs updates to reflect the type changes with 52.02; if you look at the changes made for 52.02 compatibility you will note that I had to make adjustments to code in script-manager.lua and I suspect parallel changes need to be made here.

E:/Kelly/DF/df_52_02_steam/hack/scripts/gui/mod-manager.lua:17: attempt to index a nil value (local 'dir')
stack traceback:
        E:/Kelly/DF/df_52_02_steam/hack/scripts/gui/mod-manager.lua:17: in upvalue 'vanilla'
        E:/Kelly/DF/df_52_02_steam/hack/scripts/gui/mod-manager.lua:89: in function <E:/Kelly/DF/df_52_02_steam/hack/scripts/gui/mod-manager.lua:78>
        (...tail calls...)
        E:/Kelly/DF/df_52_02_steam/hack/scripts/gui/mod-manager.lua:144: in upvalue 'swap_modlist'
        E:/Kelly/DF/df_52_02_steam/hack/scripts/gui/mod-manager.lua:248: in upvalue 'load_preset'
        E:/Kelly/DF/df_52_02_steam/hack/scripts/gui/mod-manager.lua:345: in local 'cb'
        E:\Kelly\DF\df_52_02_steam\hack\lua\gui\widgets\list.lua:323: in function 'gui.widgets.list.double_click'
        E:\Kelly\DF\df_52_02_steam\hack\lua\gui\widgets\list.lua:345: in function 'gui.widgets.list.onInput'
        E:\Kelly\DF\df_52_02_steam\hack\lua\gui.lua:869: in function <E:\Kelly\DF\df_52_02_steam\hack\lua\gui.lua:856>
        (...tail calls...)
        ...df_52_02_steam\hack\lua\gui\widgets\containers\panel.lua:306: in function 'gui.widgets.containers.panel.onInput'
        E:\Kelly\DF\df_52_02_steam\hack\lua\gui.lua:869: in function <E:\Kelly\DF\df_52_02_steam\hack\lua\gui.lua:856>
        (...tail calls...)
        E:\Kelly\DF\df_52_02_steam\hack\lua\gui.lua:1119: in function <E:\Kelly\DF\df_52_02_steam\hack\lua\gui.lua:1103>
        [C]: in ?

@ab9rf
Copy link
Member

ab9rf commented Jul 25, 2025

Only if they pass through stl_string_identity::lua_write. My best guess, based on the observed behavior, is that this isn't happening when the object is being pulled out of a "container" object; instead of ultimately pushing a Lua string onto the stack, it's pushing the Lua userdata object that proxies the C++ string without using the lua_write specialization that converts C++ strings into Lua strings. It has to retain the proxy object so that if it gets assigned to the assignment uses the STL string assignment method. Otherwise you wouldn't be able to do object.field = "string" and get a useful result.

Ok, so what I'm trying to say is that there are two cases:

  • Native Lua strings, equivalent to "foo" (also returned by reading std::string fields from C++), where startswith() works
  • Proxy objects, with a .value field, defined by DFHack and returned when reading std::string* - startswith() does not work on these natively, but does work on the .value field

And there isn't a third case that looks like case 1 but where startswith() doesn't work. (i.e. this is unlike Java/JavaScript where there is a distinction between string literals and new String())

I understand what you're saying now. I'm still not fully up to speed with all the metamethods on some of the proxy objects.

@ong-yinggao98
Copy link
Contributor Author

I suspect that this needs updates to reflect the type changes with 52.02; if you look at the changes made for 52.02 compatibility you will note that I had to make adjustments to code in script-manager.lua and I suspect parallel changes need to be made here.

Yep, we no longer need to pass .value anymore. I'll do a final pass tomorrow to make sure none of the changes have affected script behaviour.

@ab9rf
Copy link
Member

ab9rf commented Jul 25, 2025

This is now working for me in testing in arena mode.

@ab9rf
Copy link
Member

ab9rf commented Jul 25, 2025

this looks ready to merge to me, but i'll wait if you want to give it one more pass first

i want to get a release out soon for the fix to mod manager but i'd like to slide this in with that release if i can

@ong-yinggao98
Copy link
Contributor Author

this looks ready to merge to me, but i'll wait if you want to give it one more pass first

i want to get a release out soon for the fix to mod manager but i'd like to slide this in with that release if i can

I've just finished testing, thanks!

@ab9rf ab9rf merged commit ca3ab76 into DFHack:master Jul 26, 2025
10 checks passed
@ong-yinggao98 ong-yinggao98 deleted the modmanager-vanilla branch July 26, 2025 16:44
ab9rf added a commit that referenced this pull request Jul 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Mod Manager does not automatically update vanilla mods gui/mod-manager should be able to automatically apply mods to the Object Testing Arena
4 participants