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

Splitting CheckInvPaste() into smaller functions #6984

Merged

Conversation

matheusgomes28
Copy link
Contributor

Moved functions that were getting previous items into smaller ones with better names. At least you can see straight-away what each of these blocks of code do!

This has been sitting on my github for a few weeks, just got back to this today and realise the timedemo test is failing. Will fix that, but creating this to get some feedback anyway.

@matheusgomes28
Copy link
Contributor Author

I am struggling a little bit to debug the timedemo_test, as I get the following errors when running it locally:

ERROR: Asset not found: lua\user.lua
Demo file not found
...

I can't find user.lua anywhere, am I missing something?

@StephenCWills
Copy link
Member

StephenCWills commented Feb 23, 2024

Are you debugging with Visual Studio?

EDIT: If so, try this...

image

image

image


Here's the text for that second image.

{
  "version": "0.2.1",
  "defaults": {},
  "configurations": [
    {
      "type": "default",
      "project": "CMakeLists.txt",
      "projectTarget": "devilutionx.exe",
      "name": "devilutionx.exe",
      "args": []
    },
    {
        "type": "default",
        "project": "CMakeLists.txt",
        "projectTarget": "devilutionx.exe",
        "name": "timedemo",
        "args": [
            "--config-dir",
            "${cmake.buildRoot}\\test\\fixtures\\timedemo\\WarriorLevel1to2",
            "--save-dir",
            "${cmake.buildRoot}\\test\\fixtures\\timedemo\\WarriorLevel1to2",
            "--diablo",
            "--spawn",
            "--lang",
            "en",
            "--demo",
            "0",
            "--timedemo"
        ]
    }
  ]
}

And you may have to select Build > Build All from the toolbar to make sure the test fixtures get copied to the build directory. That's probably why you're seeing Demo file not found. FYI, user.lua has nothing to do with it.

@matheusgomes28
Copy link
Contributor Author

matheusgomes28 commented Feb 24, 2024

Thanks for the reply, @StephenCWills .

I am running on VsCode with the MSVC compilers on Windows, and the clang compilers on Ubuntu. I figured what the issue I have is: it's simply that we get the prefPath in paths.cpp from the SDL call to SDL_GetBasePath() which get's where the binary is located. Given my following launch.json in VScode:

        {
            "name": "Timedemo Debug",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "${workspaceFolder}\\build\\vs2022-deb\\Debug\\timedemo_test.exe",
            "cwd": "${workspaceFolder}\\build\\vs2022-deb\\",
            "args": [
                "--config-dir",
                "C:\\Users\\mathe\\development\\devilutionX\\build\\vs2022-deb\\test\\fixtures\\timedemo\\WarriorLevel1to2",
                "--save-dir",
                "C:\\Users\\mathe\\development\\devilutionX\\build\\vs2022-deb\\test\\fixtures\\timedemo\\WarriorLevel1to2",
                "--diablo",
                "--spawn",
                "--lang",
                "en",
                "--demo",
                "0",
                "--timedemo"
            ],
            "stopAtEntry": false,
            "environment": [],
            "console": "integratedTerminal"
        }

SDL_GetBasePath() will return ${workspaceFolder}\\build\\vs2022-deb\\Debug as the base path, and we later add on the paths text/fixture/... to that in order to create the path to demo_0.dmo. This works fine when you have ctest integration in your IDE (like visual studio, clion, or simply running ctest) because CMake seems to copy the bin as if it was called from the directory where add_test(...) was called from.

In my case, I endup with the demo_0.dmo path as C:\Users\mathe\development\devilutionX\build\vs2022-deb\Debug\/test/fixtures/timedemo/WarriorLevel1to2\demo_0.dmo

I do wonder: should we not use the value of the current working directory as the base path, instead of the path to the binary? Either way, I haven't fixed it yet, and the solution seems to just run the test through the test explorer and let ctest do the magic.

@StephenCWills
Copy link
Member

SDL_GetBasePath() will return ${workspaceFolder}\\build\\vs2022-deb\\Debug as the base path, and we later add on the paths text/fixture/... to that in order to create the path to demo_0.dmo. This works fine when you have ctest integration in your IDE (like visual studio, clion, or simply running ctest) because CMake seems to copy the bin as if it was called from the directory where add_test(...) was called from.

Since we don't use add_test(), at least not directly, then we'll have to go by the documentation for gtest_discover_tests() instead.

add_executable(${test_target} "${test_target}.cpp")
gtest_discover_tests(${test_target})

That said, it seems the WORKING_DIRECTORY property of that call works the same.

https://cmake.org/cmake/help/latest/module/GoogleTest.html#command:gtest_discover_tests

Specifies the directory in which to run the discovered test cases. If this option is not provided, the current binary directory is used.


I do wonder: should we not use the value of the current working directory as the base path, instead of the path to the binary? Either way, I haven't fixed it yet, and the solution seems to just run the test through the test explorer and let ctest do the magic.

We do use CMAKE_CURRENT_BINARY_DIR by default as the base path to deploy the test fixtures.

set(dst "${DEVILUTIONX_TEST_FIXTURES_OUTPUT_DIRECTORY}/${fixture}")

if(NOT DEFINED DEVILUTIONX_TEST_FIXTURES_OUTPUT_DIRECTORY)
set(DEVILUTIONX_TEST_FIXTURES_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/fixtures")
endif()

That path should match the one gtest_discover_tests() is using as its WORKING_DIRECTORY. Since the executable is deployed to CMAKE_RUNTIME_OUTPUT_DIRECTORY, I do agree that it would make more sense if we used a relative path in the test exectuable instead.

However, I'm not really that familiar with ctest. It looks to me like CMake is generating a bunch of files in the format *_test[1]_tests.cmake that lists the WORKING_DIRECTORY property. This is presumably the working directory that CMake uses to execute the test. I'm not sure what Visual Studio, CLion, or ctest would use. Or does the bootstrapper within the test executable itself set up its own working directory before executing the test? 🤷


Anyway, if you want to give it a shot, here's where we set pref path up to use relative paths in pfile_write_hero.

paths::SetPrefPath(".");

@matheusgomes28 matheusgomes28 force-pushed the refactor-check-inventory-paste-func branch from 8bd1af1 to 128c288 Compare February 25, 2024 09:19
@matheusgomes28
Copy link
Contributor Author

@StephenCWills

Since we don't use add_test(), at least not directly, then we'll have to go by the documentation for gtest_discover_tests() instead.

If you look at the source code for gtest_discover_tests(), seems to be pretty much a wrapper around add_test() with some extra google test specific stuff, hence the same behaviour.

I kind of gave up trying to fix the prefix paths outside the ctest integration and just hardcoded the correct pathPrefix(...) in the test so I can debug. I'm pretty sure ctest changes the binary path itself by either copying the binary to the WORKING_DIR or some other way, then the SDL calls get the binary path dir, not the current working dir as the base directory.

Either way, I think I fixed the tests... turns out I removed one too many if checks on item locations.

Moved functions that were getting previous items into
smaller ones with better names. At least you can see
straight-away what each of these blocks of code do!
@matheusgomes28 matheusgomes28 force-pushed the refactor-check-inventory-paste-func branch from 128c288 to 107ec7d Compare February 25, 2024 09:25
@AJenbo
Copy link
Member

AJenbo commented Mar 2, 2024

Looks good over all, this is a truly massive function so hard to tackle. It's fine if you do it in steps and just extract some of the obvious bits first.

@matheusgomes28 matheusgomes28 marked this pull request as ready for review March 4, 2024 18:14
@matheusgomes28
Copy link
Contributor Author

Thanks @AJenbo . I'll removed some of the comments I just realised I left, then you are free to merge this.

Moved functions that were getting previous items into
smaller ones with better names. At least you can see
straight-away what each of these blocks of code do!
…eusgomes28/devilutionX into refactor-check-inventory-paste-func
Source/inv.cpp Outdated

std::optional<int8_t> GetPrevItemId(int slot, const Player &player, const Size &itemSize)
{
int8_t item_something = 0;
Copy link
Collaborator

@kphoenix137 kphoenix137 Mar 12, 2024

Choose a reason for hiding this comment

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

Perhaps a better variable name? This is quite ambiguous

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, hence why this feedback request! I guess it gets the item ID from the inv grid? I was not too sure on this one.

Copy link
Member

Choose a reason for hiding this comment

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

Looks like prevItemId would have been a good name since that reflects the getter function name and this is the returned value.

Source/inv.cpp Outdated
return;
}

auto const maybe_it = (il == ILOC_UNEQUIPABLE) ? GetPrevItemId(slot, player, itemSize) : 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also a quite ambiguous variable name

@AJenbo
Copy link
Member

AJenbo commented Mar 15, 2024

Nice this is a good enough clean up that the function is now manageable to read 👍

I'll go over it in detail soon.

Source/inv.cpp Outdated
Comment on lines 609 to 610
break;
}
Copy link
Member

Choose a reason for hiding this comment

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

would be nice to use the same break style now that the switch isn't so bloated.

Source/inv.cpp Outdated Show resolved Hide resolved
Source/inv.cpp Outdated Show resolved Hide resolved
Source/inv.cpp Outdated Show resolved Hide resolved
Source/inv.cpp Outdated
player.HoldItem = previouslyEquippedItem;
}
case ILOC_ARMOR:
ChangeBodyEquipment(slot, player, il);
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice if these new functions for changing equipment took there arguments in the same order: Player, Slot, Other, rather then being a bit random about it.

Source/inv.cpp Outdated
return it;
}

std::optional<int8_t> GetPrevItemId(int slot, const Player &player, const Size &itemSize)
Copy link
Member

@AJenbo AJenbo Apr 20, 2024

Choose a reason for hiding this comment

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

I don't really have anything against std::optional, but only positive values are valid returns from this so we can use -1 to indicate that nothing was found which allows a bit more direct use with out the need to check before passing the value on to the next user.

Some thing more relevant for this function and refactoring is that if you flip the if statements you can get rid of the else cases and instead make use of early returns. This takes a lot fewer lines and avoids the pyramid structure of the nested conditions.

This also reveals something odd in that CheckOverlappingItems() is always feed a 0 as the 4th argument as this value is never set by anything before it's called.

int8_t GetPrevItemId(int slot, const Player &player, const Size &itemSize)
{
	if (player.HoldItem._itype != ItemType::Gold)
		return CheckOverlappingItems(slot, player, itemSize, 0);
	int8_t item_cell_begin = player.InvGrid[slot - SLOTXY_INV_FIRST];
	if (item_cell_begin == 0)
		return 0;
	if (item_cell_begin <= 0)
		return -item_cell_begin;
	if (player.InvList[item_cell_begin - 1]._itype != ItemType::Gold)
		return item_cell_begin;
	return 0;
}

Copy link
Member

@AJenbo AJenbo left a comment

Choose a reason for hiding this comment

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

When running this PR I'm unable to place items in the belt using the mouse. Could you please look in to what is causing this?

It seems to be swallowed by this bit of code

	if ((il != ILOC_UNEQUIPABLE) && (desired_loc != il))
		return;

Source/inv.cpp Outdated
Comment on lines 559 to 561
}

if ((il != ILOC_UNEQUIPABLE) && (desired_loc != il)) {
Copy link
Member

Choose a reason for hiding this comment

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

Previously there was and else in between these, with out it it is now not possible to place items on the belt as desired_loc is never ILOC_BELT.

Suggested change
}
if ((il != ILOC_UNEQUIPABLE) && (desired_loc != il)) {
} else if ((il != ILOC_UNEQUIPABLE) && (desired_loc != il)) {

Copy link
Member

@AJenbo AJenbo left a comment

Choose a reason for hiding this comment

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

I applied a few changes and did a bit further cleanups, hope you are ok with me doing so. Overall a good clean up, thanks for putting time an effort in to it.

@AJenbo AJenbo merged commit 7848ab7 into diasurgical:master Apr 20, 2024
22 checks passed
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.

4 participants