Rust: macro to keep same frame rate does not work - rust

To keep a certain frame rate, i've been using std::thread::sleep() to wait until enough time has passed. Calculating how much it sleeps messes up the code a bit, so I tried making a macro for it. This is it now, but it does not work:
macro_rules! fps30 {
($($body: expr;);*) => {
let time = std::time::Instant::now()
$($body)*
let time_elapsed = time.elapsed().as_micros();
if FRAME_TIME_30FPS_IN_MICROS > time_elapsed {
let time_to_sleep = FRAME_TIME_30FPS_IN_MICROS - time_elapsed;
std::thread::sleep(std::time::Duration::from_micros(time_to_sleep as u64));
}
};
}
And I want to use it like this:
loop {
fps30!{
// do everything I want to do in the loop
}
}
When I don't implement it as a macro (by pasting the code in the loop directly) it works, and retains a nice 29 frames per second (I guess not 30 because of the small overhead the sleep calculations give). The error it gives during compilation states: no rules expected the token 'p', where p is an object/struct instance I use in the macro.
Any suggestions?

The problem is with the handling of the ; in:
$($($body: expr;);*)
When you want to accept a ; separated list of expressions you should either write $($($body: expr;)*) or $($($body: expr);*). The former means a list of ;-terminated expressions, while the latter is a list of ;-separated expressions.
The difference is subtle but important. If you add both then you would need to write two ;; to separate every expression.
It would be better if you accepted a list of ; terminated expressions, so do:
$($($body: expr;)*)
Then you have a couple of errors in the body of the macro, also related to ;. You are missing the ; before the expansion of the $body:
let time = std::time::Instant::now();
And you are missing the ; in the expansion of $body itself, because the ; is not part of the captured expr:
$($body;)*
With these changed it works, except if you try:
fps30!{
if a {
}
if a {
}
}
Because there is no ; after the if expressions!!! You could try switching to:
$($($body: expr);*)
But it will not work either, now because there is no ; between the expressiones!
You could accept a single $body: block but then you will be required to write an additional couple of {}. Not ideal...
If you really want to accept any kind of block of code, I recommend to accept a list of token trees (tt). And when expanding them, enclose them in a {}, in case it does not end with a ;.
macro_rules! fps30 {
($($body: tt)*) => {
let time = std::time::Instant::now();
{ $($body)* }
let time_elapsed = time.elapsed().as_micros();
//...
};
}
Now your macro will accept any kind of syntax, and will expand it dumbly inside macro.
You could even add the possibility of $body having a type and a value, and make fps30 evaluate to that value`:
let res = { $($body)* };
//...
res
As an additional bonus, if you write a syntax error, it will fail when compiling the code, not when expanding the macro, which is much easier to debug.

Related

What are some good ways to implement a read line or a sleep timer in Rust

I've finished my CLI but it exits too quickly for people to use it, Anyone know how would I go towards implementing code in my main.rs without breaking the compiler lol.
I was thinking of maybe a for loop that prints , read and execute and then start again. Or maybe a read line function so it stays up long enough to output the display.
Where would you guys implement that ? Thanks!
use structopt::StructOpt;
mod cli;
mod task;
use cli::{Action::*, CommandLineArgs};
use task::Task;
fn main() {
// Get the command-line arguments.
let CommandLineArgs {
action,
todo_file,
} = CommandLineArgs::from_args();
// Unpack the todo file.
let todo_file = todo_file.expect("Failed to find todo file");
// Perform the action.
match action {
Add { text } => task::add_task(todo_file,
Task::new(text)),
List => task::list_tasks(todo_file),
Done { position } =>
task::complete_task(todo_file, position),
}
.expect("Failed to perform action")
}
From the example, it seems you're getting the arguments from the command line. If you instead wanted the program to wait for a user to enter some text, interpret that text as a command and run it, and then wait for input again, until the user exits the program, then you'll probably want https://doc.rust-lang.org/std/io/struct.Stdin.html or possibly a higher level crate such as https://docs.rs/rustyline/8.0.0/rustyline/
If you were using stdin directly, you can call io::stdin().read_line() which will wait until the user has entered a line of text and pressed enter before the function will return. You can then parse that string to get the action to perform. The input/parsing/action code can be surrounded in a loop {} and one of the actions can be an exit command that will exit the loop.

Make msvc C4706 go away without pragmas

Following code in MSVC generates warning about assignment in conditional expression.
https://godbolt.org/z/i_rwY9
int main()
{
int a;
if ((a = 5)) {
return 1;
}
return a;
}
Note that I tried to use the double () around if since that makes the warning go away with g++, but I do not know how to make it go away in msvc without extracting the assignment from condition.
Is there a way to nudge msvc to figure out that this assignment is intentional?
I know I can use pragmas to disable this warning, but pattern is very common so I would like to get a solution without pragmas if one exists.
The MSVC compiler will give this warning unless you can convince it that you really do know what you're doing. Adding at least one 'real' logical test will achieve this:
int main()
{
int a;
if ((a = 5) != 0) {
return 1;
}
return a;
}
Note that the constant 5 can readily be replaced with any variable or valid expression: adding the explicit != 0 test does nothing to actually change the outcome of the code (and it is unlikely to change the generated assembly).

Swift: How to interrupt a sort routine

I want to give users the ability to stop a sorting routine if it takes too long.
I tried to use DispatchWorkItem.cancel. However, this does not actually stop processes that have already started.
let myArray = [String]() // potentially 230M elements!
...
workItem = DispatchWorkItem {
let result = myArray.sorted()
DispatchQueue.main.async {
print ("done")
}
}
// if process is too long, user clicks "Cancel" =>
workItem.cancel() // does not stop sort
How can I kill the workItem's thread?
I don't have access to the sorted routine, therefore I cannot insert tests to check if current thread is in "cancelled" status...
As you have deduced, one simply cannot kill a work item without periodically checking isCancelled and manually performing an early exit if it is set.
There are two options:
You can used sorted(by:), test for isCancelled there, throwing an error if it is canceled. That achieves the desired early exit.
That might look like:
func cancellableSort() -> DispatchWorkItem {
var item: DispatchWorkItem!
item = DispatchWorkItem() {
let unsortedArray = (0..<10_000_000).shuffled()
let sortedArray = try? unsortedArray.sorted { (obj1, obj2) -> Bool in
if item.isCancelled {
throw SortError.cancelled
}
return obj1 < obj2
}
// do something with your sorted array
item = nil
}
DispatchQueue.global().async(execute: item)
return item
}
Where
enum SortError: Error {
case cancelled
}
Be forewarned that even in release builds, this can have a dramatic impact on performance. So you might want to benchmark this.
You could just write your own sort routine, inserting your own test of isCancelled inside the algorithm. This gives you more control over where precisely you perform the test (i.e., you might not want to do it for every comparison, but rather at some higher level loop within the algorithm, thereby minimizing the performance impact). And given the number of records, this gives you a chance to pick an algorithm best suited for your data set.
Obviously, when benchmarking these alternatives, make sure you test optimized/release builds so that your results are not skewed by your build settings.
As an aside, you might considering using Operation, too, as its handling of cancelation is more elegant, IMHO. Plus you can have a dedicated object for the sort operation, which is cleaner.

Dynamic variables and promises

It seems that dynamic variables don't always survive subroutine calls in threads:
sub foo($x, &y = &infix:<+>) {
my &*z = &y;
bar($x);
}
sub bar ($x) {
say &*z($x,$x);
my $promise = start { bar($x-1) if $x > 0 }
await $promise;
# bar($x-1) if $x > 0 # <-- provides the expected result: 6, 4, 2, 0
}
foo(3); # 6, 4, Dynamic variable &*z not found
Using a more globally scoped variable also works, so it's not that all variables are lost — it seems specific to dynamics:
our &b;
sub foo($a, &c = &infix:<+>) {
&b = &c;
bar($a);
}
sub bar ($a) {
say &b($a,$a);
my $promise = start { bar($a-1) if $a > 0 }
await $promise;
}
foo(3); # 6, 4, 2, 0
Once the variable is set in foo(), it is read without problem in bar(). But when bar() is called from inside the promise, the value for &*z disappears not on the first layer of recursion but the second.
I'm sensing a bug but maybe I'm doing something weird with the between the recursion/dynamic variables/threading that's messing things up.
Under current semantics, start will capture the context it was invoked in. If dynamic variable lookup fails on the stack of the thread that the start executes on (one of those from the thread pool), then it will fall back to looking at the dynamic scope captured when the start block was scheduled.
When a start block is created during the execution of another start block, the same thing happens. However, there is no relationship between the two, meaning that the context captured by the "outer" start block will not be searched also. While one could argue for that to happen, it seems potentially problematic to do so. Consider this example:
sub tick($n = 1 --> Nil) {
start {
await Promise.in(1);
say $n;
tick($n + 1);
}
}
tick();
sleep;
This is a (not entirely idiomatic) way to produce a tick every second. Were the inner start to retain a reference back to the state of the outer one, for the purpose of dynamic variable lookup, then this program would build up a chain of ever increasing length in memory, which seems like an undesirable behavior.

Access to modified closure - ref int

int count = itemsToValidate.Count;
foreach(var item in itemsToValidate)
{
item.ValidateAsync += (x, y) => this.HandleValidate(ref count);
}
private void HandleValidate(ref int x)
{
--x;
if (x == 0)
{
// All items are validated.
}
}
For the above code resharper complained "Access to Modified Closure". Doesn't do that if I change that to type of object. Why is this a closure, even though I am passing by ref ?
This happens all the time
ReSharper is warning you that count is implicitly captured by the lambdas that you are assigning as "validation complete" event handlers, and that its value may well change between the time the lambda is created (i.e. when you assign the event handler) and the time when it is invoked. If this happens, the lambda will not see the value one would intuitively expect.
An example:
int count = itemsToValidate.Count;
foreach(var item in itemsToValidate)
{
item.ValidateAsync += (x, y) => this.HandleValidate(ref count);
}
// afterwards, at some point before the handlers get invoked:
count = 0;
In this instance the handlers will read the value of count as 0 instead of itemsToValidate.Count -- which might be called "obvious", but is surprising and counter-intuitive to many developers not familiar with the mechanics of lambdas.
And we usually solve it like this
The usual solution to "shut R# up" is to move the captured variable in an inner scope, where it is much less accessible and R# can be prove that it cannot be modified until the lambda is evaluated:
int count = itemsToValidate.Count;
foreach(var item in itemsToValidate)
{
int inner = count; // this makes inner impossible to modify
item.ValidateAsync += (x, y) => this.HandleValidate(ref inner);
}
// now this will of course not affect what the lambdas do
count = 0;
But your case is special
Your particular case is a comparatively rare one where you specifically want this behavior, and using the above trick would actually make the program behave incorrectly (you need the captured references to point to the same count).
The correct solution: disable this warning using the special line comments that R# recognizes.

Resources