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

Stateful Formatters #420

Merged
merged 1 commit into from Jul 8, 2022
Merged

Stateful Formatters #420

merged 1 commit into from Jul 8, 2022

Conversation

1Dragoon
Copy link

@1Dragoon 1Dragoon commented Mar 25, 2022

Very much a janky WIP, and some of the objects I introduced could use better naming though I avoided doing so in the interest of retaining compatibility with existing code. Haven't tested this against older rustc, but as of my current rustc version (latest stable) there are no lints from either regular check or clippy. But let me know if this seems like an ok direction and I'll revise it further.

Somewhen I'll modify that example to show what a more bumpy file transfer looks like, and the advantage of averaging it by the method I used. done

@1Dragoon
Copy link
Author

Note I moved the estimator to it's own module. It had to be public for this to work, and I'm not sure whether you guys would want to make the state module public, and it didn't really seem to fit anywhere else.

@1Dragoon
Copy link
Author

1Dragoon commented Mar 26, 2022

The more I think about this, the more I wonder what other use cases somebody might have for needing this level of access to the API, and I might be over-engineering it as a result. This might be better done with allowing the user to specify whether estimations are done per tick, as the default estimator does, or over n seconds, milliseconds, etc. (milliseconds hypothetically in a scientific use case? I personally don't have a use case for sub-second sampling) I think my example merely demonstrates a case where the default estimator simply doesn't work at all (basically the numbers are completely meaningless to the user) but at the same time they'd still like and benefit from having an estimation provided.

I think the efficiency improvements I've made in my alternative estimator might even make it more efficient than the existing one, as it does hardly anything at all until it's time to produce a new sample, which itself isn't all that costly over that interval, but at the same time it probably shouldn't outright replace the existing one as I could see many people preferring that per-tick estimation that is currently used, and per-tick should probably remain the default as it is much more precise.

Let me know what your thoughts are, or you can outright tell me that I'm totally bonkers and I'll accept that too :) I'll hold off on any further work on the estimator itself until I hear from one of the maintainers, though I have a few other formatting improvements in mind that I might (or might not) push to this same repo because it's working on solving a very similar if not the same problem, but I'm still trying to figure out how they'll work.

@1Dragoon
Copy link
Author

The last push I did gets everything working the way I need it to. Please see the rate_smoothing example for the overall effect of what it is I'm trying to do, with example real world use cases mentioned in the comments.

I'm also tempted to implement a lock-free incrementing function, but I need to evaluate whether that will offer much benefit in what I'm working on.

@1Dragoon 1Dragoon marked this pull request as ready for review March 30, 2022 03:04
@1Dragoon
Copy link
Author

That's odd, I compiled against rustc 1.53 and there were no lints, and all tests passed. I even added some very aggressive clippy lints and they were all clear. I also ran the cargo format check exactly as those checks do. All of that was done before being pushed. I'll look at it again to see if something slipped by somehow.

@djc
Copy link
Collaborator

djc commented Mar 30, 2022

It's very possible that clippy has gotten more strict since 1.53, especially if you enable all those extra strict stuff. 1.53 is pretty old at this point, maybe upgrade to current stable?

@1Dragoon
Copy link
Author

Oh I thought those tests were specifically against 1.53? Sure, I'll use the latest stable, I used rustup to override to 1.53 so that I could guarantee compatibility with 6 months prior as you had asked in my other PR.

@djc
Copy link
Collaborator

djc commented Mar 30, 2022

We run the tests against 1.53 to ensure the library works well, but all the linking happens on stable.

@djc
Copy link
Collaborator

djc commented Apr 4, 2022

For this to be mergable, I'd like to:

  • Have the map be a std::collections::HashMap, not a DashMap
  • Move the map back into ProgressStyle
  • Keep the Estimator as is for now
  • If the Default parameter doesn't work, we can add a default() method to the trait that yields a trait object

As I've tried to explain a few times, this should be a minimal change.

Also, it would make my life easier if discussion of the relevant changes happened in this PR rather than in the issue.

@1Dragoon
Copy link
Author

1Dragoon commented Apr 4, 2022

Not sure what you mean move it 'back'? The format_map is still in progressstate. That particular map I made into a dashmap had to be that way because it originally did live in progressstate, because it provides internal mutability (it was Arc, hence why those variables were named 'adm'.) It doesn't need to be dashmap if it's in progressstate though.

While I can put some sort of default in there, I'm not sure where you are wanting it to be called from.

The only other thing I can do is require that statefulformatter is immutable with internal mutability, which complicates things a lot from the user side as it will probably need atomics even if they're not really appropriate for the use case, or it forces them into using locks. I also don't really know how to meet my own use case while falling within those constraints. I'm looking it over to see what can be done, but right now I don't know.

@djc
Copy link
Collaborator

djc commented Apr 4, 2022

Not sure what you mean move it 'back'? The format_map is still in progressstate.

Sorry, I meant we should not add a map to BarState and only replace the format_map with something more capable.

While I can put some sort of default in there, I'm not sure where you are wanting it to be called from.

From the impl for ProgressStyle::clone().

The only other thing I can do is require that statefulformatter is immutable with internal mutability

Why? BarState::tick() has mutable access to the ProgressStyle, right?

@1Dragoon
Copy link
Author

1Dragoon commented Apr 4, 2022

Why? BarState::tick() has mutable access to the ProgressStyle, right?

Right but in order to live inside of progressstyle it has to be Clone. I don't know how to do that while also making it Send + Sync by adding Arc, which removes mutability. Unless progressstyle isn't meant to be cloned across threads?

@djc
Copy link
Collaborator

djc commented Apr 4, 2022

You can remove the derived Clone impl in favor of manual Clone impl, that gives you the flexibility to do something different for the format_map values.

@1Dragoon
Copy link
Author

1Dragoon commented Apr 4, 2022

Ah I see. I'll chew on this for a bit.

@1Dragoon
Copy link
Author

1Dragoon commented Apr 4, 2022

I'm still not really understanding the use case for cloning ProgressStyle. When ProgressStyle is cloned, how do we resume the formatter state from the existing instance? How do we keep the new one syncronized? Unless calling default is really supposed to be just another way of calling clone? But then I don't understand how the clone stays syncronized.

Or is it that when people do clone it, they already intend on resetting something?

@1Dragoon
Copy link
Author

1Dragoon commented Apr 5, 2022

Well whatever the use case is, it doesn't seem to impact mine at all. Still a draft, there are a few things I need to clean up, but overall let me know if this works.

@1Dragoon 1Dragoon changed the title Estimator as a trait Stateful Formatters Apr 5, 2022
@1Dragoon
Copy link
Author

1Dragoon commented Apr 5, 2022

I think I'm mostly satisfied with what I have there. I'd really like the ability to remove or at the very least suppress the built-in estimator as it will be running (I think? unless the compiler sees it as dead code somehow and removes it anyways) even though my use case doesn't need it at all. Also I still don't understand the need for cloning ProgressStyle, but it doesn't interfere with anything I'm doing in any way so no problem there.

If you don't count my example code, or the comments, or the extra lines mandated by cargo fmt, this pull request is somewhere under 80 lines.

For quick reference, here's the trait signature:

pub trait StatefulFormatter: Display + Send {
    fn default(&self) -> Box<dyn StatefulFormatter>;
    fn tick(&mut self, state: &mut ProgressState, now: Instant);
    fn reset(&mut self, state: &mut ProgressState, now: Instant);
}

The reason I added progressstate to reset is so that if there is only a partial reset as opposed to a full reset, we can figure that out and adjust accordingly.

.rusty-hook.toml Outdated Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
@@ -0,0 +1,218 @@
#![warn(clippy::pedantic)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer not to add an example for this right now.

Copy link
Author

Choose a reason for hiding this comment

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

this will be removed last, let's just make sure everything else is good first

Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's remove this, you can just keep it in your local working dir for testing.

Copy link
Author

Choose a reason for hiding this comment

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

I'm going to keep this until everything else looks good. The reason why is because I want to continually evaluate whether the syntax I'm able to use looks correct and idiomatic. It acts as a sanity check, and when I refactor anything crate side, it auto-refactors on my side as well

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes: which is why I suggested you keep this in your local clone without committing it into your Git commits. This should support your refactoring work without me having to look at the changes, and, since I am close to merging this, have to do an extra round trip to ask you to remove it.

This comment was marked as outdated.

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/state.rs Outdated Show resolved Hide resolved
src/state.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Show resolved Hide resolved
@1Dragoon
Copy link
Author

1Dragoon commented Apr 5, 2022

The hook is entirely for my own convenience because I kept pushing changes that were failing tests, it will go once we've got everything else sorted out.

To a lesser extent, the example is as well. It mainly works to ensure that my use case remains valid without needing me to screw with my other repos in the meantime, and ultimately will provide a working model after.

Just let me know once we're down to these two and I'll take them out last

@djc
Copy link
Collaborator

djc commented Apr 5, 2022

You could just keep the example in your local workdir uncommitted, similar for the rusty-hook config (though that should really just be a Git pre-push hook probably). Putting stuff in your PR that is not slated to be merged makes reviewing harder.

@1Dragoon
Copy link
Author

1Dragoon commented Apr 5, 2022

Git always complains when I do that, there's probably more to its usage that I need to figure out. That, and I'm actually working on this from two different computers, so this also functions as my way to keep things synchronized.

@djc
Copy link
Collaborator

djc commented May 11, 2022

@1Dragoon do you still want to finish this? Would be nice; I think we're going to push out another rc soon and hopefully release 0.17.0 soon after.

@1Dragoon
Copy link
Author

1Dragoon commented May 11, 2022 via email

@djc
Copy link
Collaborator

djc commented May 12, 2022

Great, thanks!

@1Dragoon
Copy link
Author

1Dragoon commented May 30, 2022

One thing I'm having difficulty making time for is that making ProgressTracker a single type rather than having the variant type is that I need to refactor all of the example code, but other than that what I have locally works

EDIT: Actually maybe there's a different way

@1Dragoon
Copy link
Author

I'm a bit stuck. I put a TODO comment in style.rs showing where the problem is. Basically in order to fall within your requirement of having clone and having both stateful and stateless trackers as a single trait.

It is nice being able to toss either a simple closure or a more complex tracker type as the same parameter, but I'm not certain how to pull that off given the need for clone to be implemented. Little help anybody?

@djc
Copy link
Collaborator

djc commented May 31, 2022

Sorry, did you mean on line 727? I didn't see an actual TODO anywhere. I think it's fine to ditch the closure option for now and require an instantce of a ProgressTracker-implementing type.

@1Dragoon
Copy link
Author

1Dragoon commented May 31, 2022

Oh sorry I accidentally committed before adding the todo comment. It's line 25 of style.rs. What I put there is basically just a filler. I just pushed the corrected one with the comment added.

EDIT: One other thing I should mention, I had to add an explicit to_string() (name to be bikeshedded) method to the trait as I couldn't work out how to require Display as a type constraint on style.rs line 26 (or if such a thing should even be done at all as it negates the simplicity,) so this was a compromise

@1Dragoon
Copy link
Author

1Dragoon commented Jun 1, 2022

Merged in the latest upstream commits and resolved the conflict.

One thing though -- I don't really like the default() method name because it's a bit misleading, and it may conflict if the user had a struct that they actually wanted to implement the real default trait for. Just feels a bit needlessly broken to keep it named as such. Not sure what else to call it though, maybe respawn()?

Copy link
Collaborator

@djc djc left a comment

Choose a reason for hiding this comment

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

This seems to be in a pretty good state, I'm almost ready to merge!

src/state.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/progress_bar.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
@1Dragoon
Copy link
Author

@djc Have you had a chance to review my comments yet?

@djc
Copy link
Collaborator

djc commented Jun 20, 2022

Uh, I think I already left a round of comments here? I was waiting for you.

@1Dragoon

This comment was marked as outdated.

@1Dragoon 1Dragoon requested a review from djc June 21, 2022 01:01
src/progress_bar.rs Outdated Show resolved Hide resolved
src/progress_bar.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
src/style.rs Outdated Show resolved Hide resolved
@@ -0,0 +1,218 @@
#![warn(clippy::pedantic)]

This comment was marked as outdated.

src/style.rs Outdated Show resolved Hide resolved
@1Dragoon
Copy link
Author

Oh I figured it out, have to go to the file changes page and then click the review button. They really ought to add some kind of UI hint that you need to do that before anybody can even see your comments, otherwise there's nothing at all to indicate that you have to do so.

src/style.rs Outdated Show resolved Hide resolved
src/style.rs Show resolved Hide resolved
@1Dragoon
Copy link
Author

BTW I'd still like to be able to tell the built in estimator to noop if we're not using it.

Copy link
Author

@1Dragoon 1Dragoon left a comment

Choose a reason for hiding this comment

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

Other than my concern about the built-in estimator, I'm satisfied with this.

@1Dragoon
Copy link
Author

1Dragoon commented Jul 3, 2022

@djc any further concerns with this?

Copy link
Collaborator

@djc djc left a comment

Choose a reason for hiding this comment

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

No, can you rebase it one more time? Feel free to just squash all the changes in one commit (or I can do that when merging).

src/style.rs Outdated Show resolved Hide resolved
@chris-laplante
Copy link
Collaborator

@1Dragoon want me to take a crack at fixing the conflicts? (since they were caused by my most recent commits). I think I can make the changes right in this PR.

@1Dragoon
Copy link
Author

1Dragoon commented Jul 6, 2022

Sure, sorry I haven't gotten to this yet because I've been sick. I just needed to do that and get rid of the where clause, but git was kind of being a PITA and I couldn't sit up long enough to deal with it.

@1Dragoon
Copy link
Author

1Dragoon commented Jul 6, 2022

I'm feeling a bit better this afternoon so I went ahead and squashed my repo and started to resolve the merge confilcts but this one has me a bit confused:

                    if let Some(formatter) = self.format_map.get(key.as_str()) {
                        formatter.push_str(&formatter(state).replace('\t', &" ".repeat(self.tab_width)));
                    }

I think this might need to be moved to the default impl, still looking at it

@1Dragoon
Copy link
Author

1Dragoon commented Jul 6, 2022

I'm not sure that I like what I had to do with line 246, but it works though

@djc please review when you can

@chris-laplante
Copy link
Collaborator

I'm not sure that I like what I had to do with line 246, but it works though

@djc please review when you can

It looks OK to me.

src/style.rs Outdated
buf.push_str(&formatter(state).replace('\t', &" ".repeat(self.tab_width)));
if let Some(tracker) = self.format_map.get(key.as_str()) {
tracker.write(state, &mut buf);
buf = buf.replace('\t', &" ".repeat(self.tab_width));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should do a struct TabRewriter(&mut dyn fmt::Write); with an impl fmt::Write that rewrites the tabs before writing them to the inner fmt::Write instead.

Does that seem manageable?

Copy link
Author

@1Dragoon 1Dragoon Jul 7, 2022

Choose a reason for hiding this comment

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

Not sure I follow. Where would we pass tab_width to that? Or are you suggesting to just use the global default?

@1Dragoon
Copy link
Author

1Dragoon commented Jul 8, 2022

@djc See the latest commit. I think that's what you're looking for?

Also should probably mark this to resolve #391 and #394, not sure how that works though.

@djc djc merged commit 1d47e1d into console-rs:main Jul 8, 2022
@djc
Copy link
Collaborator

djc commented Jul 8, 2022

Great work, thanks for sticking with it!

@1Dragoon
Copy link
Author

1Dragoon commented Jul 9, 2022

Sure, no problem.

Also not a huge deal, but will it ever show me as a contributor here? Reason I ask is because I am thinking that at some point I might show potential future employers what kind of work I've done, and I'm not sure how I'll be able to do that once I've deleted my forked copy (for cleanliness sake.)

@djc
Copy link
Collaborator

djc commented Jul 9, 2022

Yes, you should show up as a contributor for this project (or you can send the URL to this PR).

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.

None yet

3 participants