Ticket: #5020: Allocate fewer ncurses color pairs#5066
Ticket: #5020: Allocate fewer ncurses color pairs#5066egmontkob merged 4 commits intoMidnightCommander:masterfrom
Conversation
lib/tty/color-ncurses.c
Outdated
| // ncurses color pair 0 always refers to the default colors; add it to our hash | ||
| default_color_pair_id = g_strdup ("-1.-1"); | ||
| mc_ncurses_color_pair = g_try_new (int, 1); | ||
| *mc_ncurses_color_pair = 0; | ||
| g_hash_table_insert (mc_tty_ncurses_color_pairs, default_color_pair_id, mc_ncurses_color_pair); | ||
| mc_tty_ncurses_next_color_pair = 1; |
lib/tty/color-ncurses.c
Outdated
| color_pair_str = g_strdup_printf ("%d.%d", ifg, ibg); | ||
|
|
||
| mc_ncurses_color_pair = | ||
| (int *) g_hash_table_lookup (mc_tty_ncurses_color_pairs, (gpointer) color_pair_str); | ||
|
|
||
| if (mc_ncurses_color_pair == NULL) | ||
| { | ||
| mc_ncurses_color_pair = g_try_new0 (int, 1); | ||
| *mc_ncurses_color_pair = mc_tty_ncurses_next_color_pair++; |
There was a problem hiding this comment.
And this code (*) - can't it be somehow extracted in a function like "remember color pair if not there"?
There was a problem hiding this comment.
Done, curious if you like the new version.
There was a problem hiding this comment.
I do like it much better, thank you 🙏
| (void) y; | ||
| (void) x; | ||
| (void) rows; | ||
| (void) cols; | ||
| (void) color; |
There was a problem hiding this comment.
I know you just moved this code, but could you please use MC_UNUSED instead of this hacky ugliness for the stuff we touch? Or maybe even at the header, something like:
#ifdef ENABLE_SHADOWS
tty_colorize_area (int y, int x, int rows, int cols, int color)
#else
tty_colorize_area (MC_UNUSED int, MC_UNUSED int, MC_UNUSED int, MC_UNUSED int, MC_UNUSED int)
#endif(if this works)
There was a problem hiding this comment.
Currently mc uses MC_UNUSED ~10 times and (void) variable_name; ~350 times.
Due to the #ifdef branching nature of things, your proposed variant doesn't look cleaner to me than the current code.
If you don't mind, I'd rather keep this untouched – leave it to you to change if you wish to.
(Your other feedback I agree with and will address.)
There was a problem hiding this comment.
Well, shall we (or I) perform a mass search and replace then? The other items accumulated over time before I eventually introduced MC_UNUSED to solve the problem properly because not all warnings can be silenced with the void hack. Only our approach with Andrew so far was to avoid code-base-wide changes after non-essential changes in policy...
There was a problem hiding this comment.
Can't we just leave (void) here?
It works here. Whether at other places a similar hack works or not is irrelevant.
By the way, the refactoring I just did (as per your other request) happened to introduce another such (void) trick where MC_UNUSED would be incorrect: the value may or may not be used. What to do then?
You have a very hard time convincing me that I have to switch to the new way which is used at 10 places, the old way used at 350 places isn't good anymore.
I'd also like to firmly reject on principle the idea that if I move a method to a different file (where it obviously should've belonged to from the beginning) then I'd definitely need to update the body of the method to our new standards. It's a fair request that newly written code should use the new way, you may or may not mass-update the old syntax, but moving a method to a different location shouldn't count as new code and shouldn't be subjected to the new guidelines.
In this very case, I'm also reluctant to go with this change because it introduces more duplication that the current one, it's easier to get it wrong in one of the cases (which I guess our CI doesn't test, and I wouldn't want to manually test the no-shadow case either beacuse that's not what this change is about); it would also need to go consistently in the header vs. impl file which adds another ugly ifdef-branching; overall I just find it uglier, more fragile than the current code, and would need additional testing. I'd much rather spend my time on things that actually do matter – there's a lot to do.
(Note: Moving the method is a prerequisite for the main commit of the PR; see the "ouch" comment and the compiler warning in the first two drafts attached to the bug.)
There was a problem hiding this comment.
By the way, the refactoring I just did (as per your other request) happened to introduce another such
(void)trick whereMC_UNUSEDwould be incorrect: the value may or may not be used. What to do then?
In C++, they have recently adopted maybe_unused 😢, but I think that in C, there is no solution other than accepting the silencer if you cannot change other code (like moving it inside of the disappearing construct).
In my hierarchy of values, I would then accept the silencer as it is less evil than non-generic (duplicating) logic code.
You have a very hard time convincing me that I have to switch to the new way which is used at 10 places, the old way used at 350 places isn't good anymore.
It's unfortunate that I should have a very hard time doing so, because you seem to be otherwise thinking rationally and have quite an experience with C.
The problem with unused variable silencers is that they... well, silence the warnings. However, they are not dedicated unambiguous syntax constructs to do so, but at best, some hopefully otherwise harmless construct, which is accepted to be abused for the purpose.
As such, these statements then cannot be easily identified by means other than semantic analysis and don’t directly convey the meaning to the reader.
A proper way to silence warnings if needed is to use dedicated attributes (which is what the new macro is using). It resolves into a directive to the compiler that doesn't and cannot affect anything else, and can be easily found in the code. Also, its meaning is directly obvious.
Moreover, there are places where the void trick just doesn't work (like labels) and you must use attributes if you need to silence a warning there.
There are several classes of cases when the use of silencers is warranted:
- Function parameters
- Function returns
- Internal variables
The general approach should be to avoid the situation if possible; if not, use it.
The first type of usage I find particularly problematic and want to replace it as a priority
The second is used absolutely inconsistently in our code, and in my opinion should be just thrown away. I don't think that the codebase is ready for strict return checking anyway, and refactoring this properly would be a major project... which might not even make sense in the C world, in terms of what you gain for the amount you invest, unless you're working in some kind of regulated environment. In which case, you might as well reconsider your choice of language.
I can see that in the last case, the macro in its current form can only mark the variable at the declaration site, which doesn't make it much better than the silencer.
Maybe we can improve on that by giving it an optional parameter, so that it would expand only if the parameter evaluates to true. I'm not sure that this is doable within the C macro system and with an amount of effort invested that is worth it.
I'd also like to firmly reject on principle the idea that if I move a method to a different file (where it obviously should've belonged to from the beginning) then I'd definitely need to update the body of the method to our new standards. It's a fair request that newly written code should use the new way, you may or may not mass-update the old syntax, but moving a method to a different location shouldn't count as new code and shouldn't be subjected to the new guidelines.
I don't think that I can be convinced that there is a problem on principle with updating the code as it is touched. In my experience, this is actually the only pragmatic and practical way of evolving huge old code bases in the right direction.
The sweeping changes mostly require an inordinate amount of work and just never get done, and on top of that introduce risks, like mass-upgrades to new types (size_t and friends) or removal of dead casts. You've recently witnessed the effects of that sort yourself.
The approach of doing something to the code that is touched when it is touched, even if it has nothing to do with the change per se (obviously in a separate commit), makes the upgrades possible in the first place and then limits the risks, as the change is more likely to be correct due to its limited nature and more stringent review.
Nevertheless, what is certainly true is that it's highly problematic if we don't agree on the nature of the change and if you have some issues "on principle" with the approach itself. I expected neither of those as I made my suggestion, otherwise I wouldn't have made it.
Now, of course, I'm not going to block anything on these grounds. Let's leave it the way you want for this PR.
There was a problem hiding this comment.
The approach of doing something to the code that is touched when it is touched, even if it has nothing to do with the change per se (obviously in a separate commit)
As long as it's a trivial improvement, why not.
In this case it's a combination of two major things:
-
I do not really consider moving an entire method to a different file – but otherwise leaving it unchanged – as "touching it".
-
The requested change hardly has any precedence in the source, IMO is absolutely not obvious that it makes the code cleaner (maybe it makes it semantically better, but visually uglier), and if I wanted to be really sure that I didn't make a silly mistake then it would take a bit of additional testing as well.
I hope you don't mind that I just decided to draw the line before this step now. :)
a3519d8 to
18e816e
Compare
|
Note: In the test file, the last row's almost last columns "base lightgray" and "base white" appear incorrectly. I'll investigate why. It might be related to the recent removal of the "base" keyword; it might easily be that the test is outdated and the code is fine. |
18e816e to
64d902f
Compare
Fixed. @horkykuba Could you please review the commit "Fix shady trick..."?
Also (I haven't tested this, just looked bad in the code) you explicitly exclude the value 0 from the "shady trick of applying a tint of red", thereby letting ncurses change |
zyv
left a comment
There was a problem hiding this comment.
I don't think I have anything else to wish for now 👍
| mc_tty_ncurses_next_color_pair = 0; | ||
| default_color_pair_id = get_ncurses_color_pair (-1, -1); | ||
| g_assert (default_color_pair_id == 0); | ||
| (void) default_color_pair_id; // unused if g_assert is eliminated |
There was a problem hiding this comment.
Out of curiosity, do you hit the warning when you compile in some configuration, or is this theory?
I'm asking because I've started eliminating these hacks in a separate branch. In the worst case, I'd have to accept that some of those must remain.
There was a problem hiding this comment.
Out of curiosity, do you hit the warning when you compile in some configuration, or is this theory?
It's experience (probably mostly from VTE/gnome-terminal).
Look at glib/gtestutils.h: under an #ifdef G_DISABLE_ASSERT these g_assert() and friends are eliminated and don't evaluate (don't even look at) their parameter.
I have also shot myself in the foot with code like g_assert (important_function_call_to_make() == 0); – works great until you disable asserts.
Now, maybe there's no set of configure option to pass to mc which would set this macro, but still, it's bad practice to rely on it.
lib/tty/color-ncurses.c
Outdated
| init_pair (mc_color_pair->pair_index, ifg, ibg); | ||
| #endif | ||
| mc_tty_color_save_attr (mc_color_pair->pair_index, attr); | ||
| ncurses_color_pair = get_ncurses_color_pair (ifg, ibg); |
There was a problem hiding this comment.
I'd ask to do const int ... here to limit the scope and mutability, and then Andrew will ask exactly the opposite, because he likes to see all variables at the top of the function like in the old, good Pascal days :-(
Maybe you can at least constify it at the top, though?
There was a problem hiding this comment.
he likes to see all variables at the top of the function
At the begin of block.
like in the old, good Pascal days :-(
like in the old, good C days .
There was a problem hiding this comment.
I'd ask to do const int ... here to limit the scope and mutability
Done – primarily because the line underneath already did the same, so it's now more consistent.
Maybe you can at least constify it at the top, though?
I'm surely misunderstanding you here... declare as const at the top and assign the value later? That's not how const works :-D
| (void) y; | ||
| (void) x; | ||
| (void) rows; | ||
| (void) cols; | ||
| (void) color; |
There was a problem hiding this comment.
By the way, the refactoring I just did (as per your other request) happened to introduce another such
(void)trick whereMC_UNUSEDwould be incorrect: the value may or may not be used. What to do then?
In C++, they have recently adopted maybe_unused 😢, but I think that in C, there is no solution other than accepting the silencer if you cannot change other code (like moving it inside of the disappearing construct).
In my hierarchy of values, I would then accept the silencer as it is less evil than non-generic (duplicating) logic code.
You have a very hard time convincing me that I have to switch to the new way which is used at 10 places, the old way used at 350 places isn't good anymore.
It's unfortunate that I should have a very hard time doing so, because you seem to be otherwise thinking rationally and have quite an experience with C.
The problem with unused variable silencers is that they... well, silence the warnings. However, they are not dedicated unambiguous syntax constructs to do so, but at best, some hopefully otherwise harmless construct, which is accepted to be abused for the purpose.
As such, these statements then cannot be easily identified by means other than semantic analysis and don’t directly convey the meaning to the reader.
A proper way to silence warnings if needed is to use dedicated attributes (which is what the new macro is using). It resolves into a directive to the compiler that doesn't and cannot affect anything else, and can be easily found in the code. Also, its meaning is directly obvious.
Moreover, there are places where the void trick just doesn't work (like labels) and you must use attributes if you need to silence a warning there.
There are several classes of cases when the use of silencers is warranted:
- Function parameters
- Function returns
- Internal variables
The general approach should be to avoid the situation if possible; if not, use it.
The first type of usage I find particularly problematic and want to replace it as a priority
The second is used absolutely inconsistently in our code, and in my opinion should be just thrown away. I don't think that the codebase is ready for strict return checking anyway, and refactoring this properly would be a major project... which might not even make sense in the C world, in terms of what you gain for the amount you invest, unless you're working in some kind of regulated environment. In which case, you might as well reconsider your choice of language.
I can see that in the last case, the macro in its current form can only mark the variable at the declaration site, which doesn't make it much better than the silencer.
Maybe we can improve on that by giving it an optional parameter, so that it would expand only if the parameter evaluates to true. I'm not sure that this is doable within the C macro system and with an amount of effort invested that is worth it.
I'd also like to firmly reject on principle the idea that if I move a method to a different file (where it obviously should've belonged to from the beginning) then I'd definitely need to update the body of the method to our new standards. It's a fair request that newly written code should use the new way, you may or may not mass-update the old syntax, but moving a method to a different location shouldn't count as new code and shouldn't be subjected to the new guidelines.
I don't think that I can be convinced that there is a problem on principle with updating the code as it is touched. In my experience, this is actually the only pragmatic and practical way of evolving huge old code bases in the right direction.
The sweeping changes mostly require an inordinate amount of work and just never get done, and on top of that introduce risks, like mass-upgrades to new types (size_t and friends) or removal of dead casts. You've recently witnessed the effects of that sort yourself.
The approach of doing something to the code that is touched when it is touched, even if it has nothing to do with the change per se (obviously in a separate commit), makes the upgrades possible in the first place and then limits the risks, as the change is more likely to be correct due to its limited nature and more stringent review.
Nevertheless, what is certainly true is that it's highly problematic if we don't agree on the nature of the change and if you have some issues "on principle" with the approach itself. I expected neither of those as I made my suggestion, otherwise I wouldn't have made it.
Now, of course, I'm not going to block anything on these grounds. Let's leave it the way you want for this PR.
64d902f to
fb15bdf
Compare
|
Changed Will wait for a few days for @horkykuba to possibly chime in, before merging. |
Don't modify the value of -1, representing the terminal's default colors. Do add a touch of red to #000000 too since palette index 0 is not necessarily pitch black. Signed-off-by: Egmont Koblinger <egmont@gmail.com>
Remove an unneded tty_setcolor(), fix the array size. Signed-off-by: Egmont Koblinger <egmont@gmail.com>
Signed-off-by: Egmont Koblinger <egmont@gmail.com>
Allocating a new ncurses color pair for every new (fg, bg, attrs) tuple might result in running out of available color pairs too soon. Instead, allocate a new ncurses color pair only if really needed, i.e. if (fg, bg) is a new combination. Apply the attributes separately. Signed-off-by: Egmont Koblinger <egmont@gmail.com>
fb15bdf to
95f2560
Compare
Proposed changes
Reuse ncurses color pairs if possible
Allocating a new ncurses color pair for every new (fg, bg, attrs) tuple might result in running out of available color pairs too soon.
Instead, allocate a new ncurses color pair only if really needed, i.e. if (fg, bg) is a new combination. Apply the attributes separately.
Plus some minor underlying changes.
Checklist
👉 Our coding style can be found here: https://midnight-commander.org/coding-style/ 👈
git commit --amend -smake indent && make check)