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

Add support for pop syntax #171

Closed
3 tasks done
ziyaointl opened this issue May 28, 2020 · 2 comments · Fixed by #250
Closed
3 tasks done

Add support for pop syntax #171

ziyaointl opened this issue May 28, 2020 · 2 comments · Fixed by #250

Comments

@ziyaointl
Copy link
Member

ziyaointl commented May 28, 2020

Currently, push_matrix is implemented as a context-manager that automatically pops the transformation matrix at the end of a with block. While this is very pythonic and introduces great readability in most cases, this could lead to code that’s too far indented to the right when multiple with blocks are nested. Furthermore, this introduces additional work when the user attempts to adapt their code from the Processing Language to p5py. Therefore, I propose retaining push_matrix's ability of being a context manager while making it usable as a normal function, much like python’s built-in open. In addition, I will implement a pop_matrix function corresponding to the popMatrix function in Processing.

A similar argument exists for push_style and pop_style.

Implementation

The general approach is explicitly storing the state in a global stack created in p5.py instead of using the stack of a context manager.

Here's a snippet of example code for push_matrix and pop_matrix. I used print statements as placeholders to better visualize the effect.

from contextlib import AbstractContextManager

class _MatrixContext(AbstractContextManager):
    def __exit__(self, exc_type, exc_value, traceback):
        pop_matrix()

def push_matrix():
    print("push")
    return _MatrixContext()
    
def pop_matrix():
    print("pop")

Example of the code in action:

>>> push_matrix()
push
<__main__._MatrixContext object at 0x7fa5d3f162e8>

>>> pop_matrix()
pop

>>> with push_matrix():
...     pass
push
pop

push_style and pop_style can be implemented using the same idea. However, there is a large number of style components to be recorded. Instead of creating a stack for each style component, I plan to write a Style class and two helper functions to encode and decode (+restore) it.

Todo

  • pushMatrix & popMatrix
  • pushStyle & popStyle
  • Document changes
@jeremydouglass
Copy link
Contributor

jeremydouglass commented May 28, 2020

I agree that explicit-pop support would be a great option to have -- especially given that most processing materials are written in other dialects and it would aid translation.

Explicit pop actions were recently removed because they were not implemented, and that caused buggy behavior. Changing to context-managers-only fixed that bug: 13b6a2d

One subtlety to think about with both context manager push and explicit push-pop support is to make sure there is correct behavior if a sketch mixes using both.

Another thing to think about is how explicit-push interacts with draw. A weird thing about explicit push in Processing (Java mode) is that a push state can be open over multiple frames, but its contents (the current matrix) are reset when draw loops. Here is an example. You might expect this to rotate the rectangle some amount between 0.2 and 1.0:

int stack = 0;
void setup() {
  size(200,200);
}
void draw() {
  background(128);
  if(stack<5 && random(1)>.9) {
    stack++;
    pushMatrix();
    rotate(0.2);
  }
  rect(50,0,150,30);
  println(stack);
  if(stack>1 && random(1)>0.9) {
    stack--;
    popMatrix();
  }
}

...but even though the stack depth may increase to 5 in a random walk, the rotation is always either 0 or 0.2, because the contents of open stack frames are always wiped.


Re:

this could lead to code that’s too far indented to the right

One way around heavy indenting of pushes (as with heavy indent in Python in general) is breaking up deep indent chains with functions calls.

@ziyaointl
Copy link
Member Author

Hi Jeremy,

Those are good points! Here are my thoughts:

One subtlety to think about with both context manager push and explicit push-pop support is to make sure there is correct behavior if a sketch mixes using both.

The Processing Language throws a RuntimeException when more pop_matrix calls are made than push_matrix calls. I think we can follow the same behavior and allow pop_matrix to be used within a push_matrix context with the limitation that the stack should not be empty (i.e. the user has to compensate by making more push_matrix calls) when the push_matrix context is exiting. Otherwise, we raise a RuntimeError.

This is the option that requires no extra code (except checking for whether the stack is empty). There may exist other options that are more intuitive but I have yet to come up with one.

Another thing to think about is how explicit-push interacts with draw. A weird thing about explicit push in Processing (Java mode) is that a push state can be open over multiple frames, but its contents (the current matrix) are reset when draw loops. Here is an example. You might expect this to rotate the rectangle some amount between 0.2 and 1.0:

int stack = 0;
void setup() {
  size(200,200);
}
void draw() {
  background(128);
  if(stack<5 && random(1)>.9) {
    stack++;
    pushMatrix();
    rotate(0.2);
  }
  rect(50,0,150,30);
  println(stack);
  if(stack>1 && random(1)>0.9) {
    stack--;
    popMatrix();
  }
}

...but even though the stack depth may increase to 5 in a random walk, the rotation is always either 0 or 0.2, because the contents of open stack frames are always wiped.

This is very interesting, especially the fact that the program won't error even if a popMatrix call was made without a corresponding pushMatrix in the same draw call. My guess is that before draw is called each time, the currently effective matrix is reset to the default but the stack that stores pushed matrices is retained. If my guess is correct, then it wouldn't be too difficult to replicate the same behavior in p5py.

One way around heavy indenting of pushes (as with heavy indent in Python in general) is breaking up deep indent chains with functions calls.

This is a technique I did not know before in explicit form. Thanks for sharing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants