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

multipiles #1078

Closed
18 tasks done
dankamongmen opened this issue Oct 22, 2020 · 38 comments
Closed
18 tasks done

multipiles #1078

dankamongmen opened this issue Oct 22, 2020 · 38 comments
Assignees
Labels
documentation Improvements or additions to documentation enhancement New feature or request
Milestone

Comments

@dankamongmen
Copy link
Owner

dankamongmen commented Oct 22, 2020

It would presumably be sometimes useful to be able to scribble on an ncplane while still rendering one's other planes. Currently, one can only safely do this by stashing the scribble plane underneath all others, and ensuring that every cell gets solved above you. Introduce ncplane_decouple() or some way to create a decoupled one in ncplane_create(), and also a method by which it can come back into the stack (ncplane_move_top() etc. might be sufficient).

Note that this means all links must be severed:

  • the plane cannot be the parent of any other coupled plane
  • the plane cannot be the child of any other coupled plane
  • the plane cannot have valid above or below links

this kinda suggests that you can't have interplane structure outside the stack, or else you'd have to move everything back at once. think this through before moving on it.

  • define new internal type ncpile and replace notcurses link in ncplane
  • implement internal ncpile_destroy()
  • create doubly-linked locked circular list of ncpiles in notcurses, free them in notcurses_stop()
  • add new internal function make_ncpile()
  • add new function ncpile_create()
  • implement family detach in ncplane_reparent()
  • accept equal arguments for ncplane_reparent_family()
  • implement zaxis detach in ncplane_reparent_family()
  • update notcurses_drop_planes() to drop all piles
  • call ncpile_destroy() for emptied ncpile in ncplane_reparent_family()
  • call ncpile_destroy() for emptied ncpile in ncplane_destroy()
  • update ncplane_move_top() to use plane's pile
  • update ncplane_move_bottom() to use plane's pile
  • update ncplane_move_above() to reject cross-pile moves
  • update ncplane_move_below() to reject cross-pile moves
  • implement cross-pile moves in ncplane_reparent_family()
  • add unit test for new pile via ncpile_create()
  • add unit tests for reparenting, check that ncplane_pile() differs as expected
@dankamongmen dankamongmen added documentation Improvements or additions to documentation enhancement New feature or request labels Oct 22, 2020
@dankamongmen
Copy link
Owner Author

I don't want the ability to "swap out" an entire stack, or else we'd need ensure every stack has something fit as the standard plane. Instead, I'm thinking that you can extract an ncplane, and its bound planes come with it. Furthermore, you can create new planes bound to this extracted plane, and they will be "extracted".

To reinsert an extracted plane (and any bound planes), you'll reparent it. This handles the case where a bound plane is reinserted by itself -- the act of reparenting will decouple it from the extracted stack.

The "root" plane of an extracted stack will be bound to itself, just like the standard plane. This maintains the invariant that all planes are bound, without making a reference into the main stack. This seamlessly facilitates multiple extracted stacks.

So, extracting a plane:

  • removes it from its parent's bound list
  • binds it to itself
  • splices it out of the z-axis
  • splices all planes bound to it out of the z-axis
  • creates a new z-axis, and orders all extracted planes the same way

The one thing I don't like about this is that we lose z-axis information. Let's say we have planes 1, 2, 3, 4 from top to bottom. 2 is bound to 4; 1 and 4 are bound to 3; 3 is bound to itself. We extract 4. This gives us:

zaxis1: 1, 3
zaxis2: 2, 4

when we reparent 4, we now have 2, 4, 1, 3 (assuming reinserted planes go to the top of the z-axis). 3 is no longer between them. That's just how it goes, I suppose, when you're stuck fuck out of luck and lost in the ghetto.

@dankamongmen dankamongmen added this to the 2.1.0 milestone Oct 26, 2020
@dankamongmen dankamongmen self-assigned this Oct 26, 2020
@dankamongmen
Copy link
Owner Author

https://www.youtube.com/watch?v=3CE3fXWXwtg

probably going to go ahead and move on this

@dankamongmen
Copy link
Owner Author

what happens if you call ncplane_move_bottom() on such an extracted plane?

@dankamongmen
Copy link
Owner Author

This could just be ncplane_reparent(n, NULL).

dankamongmen added a commit that referenced this issue Nov 7, 2020
dankamongmen added a commit that referenced this issue Nov 7, 2020
dankamongmen added a commit that referenced this issue Nov 12, 2020
dankamongmen added a commit that referenced this issue Nov 12, 2020
@dankamongmen
Copy link
Owner Author

Yes, together with #1109 we will pursue a glorious new era.

There will be no standard plane, and there will be no default stack. There will be planes and stacks, created as the user deems fit. notcurses_render_stack() will be introduced. Use it to render a stack. Rasterize the render.

Have a single plane occupying a single cell, forming the entirety of your single stack. Have thousands of stacks with millions of planes. Render as you see fit.

destroy-the-old

dankamongmen added a commit that referenced this issue Nov 14, 2020
dankamongmen added a commit that referenced this issue Nov 14, 2020
@joseluis
Copy link
Collaborator

I like very much the ongoing ncplane refactor. This one specially... It will make planes a lot more fun to use.

Do you think maybe there's a simpler API for horizontal alignment that doesn't involve a union? Or do you like the current solution a lot?

@dankamongmen
Copy link
Owner Author

dankamongmen commented Nov 15, 2020

I like very much the ongoing ncplane refactor. This one specially... It will make planes a lot more fun to use.

Do you think maybe there's a simpler API for horizontal alignment that doesn't involve a union? Or do you like the current solution a lot?

i do not like it a lot.

i can't just map them onto the negatives, since you can place a place in the negatives.

i could eliminate the union aspect, and just treat it as two different "number lines" based on the flag, since ints and enums are handwave-equivalent-enough-for-this. then you're doing .x = NCALIGN_CENTER, which .... you know, i have no problem with that whatsoever, really.

i think this might require c++ to throw some static_casts into the mix, but what's c++ without a tsunami of bracketed overwrought nonsense?

https://getyarn.io/yarn-clip/3998758f-e3f0-4485-9ef4-c0b6c54ea476

@joseluis i say we take this sad old union out behind the shed and put a bullet between its eyes. as i live in a Very Free Nation, that actually means two 10-round clips of 5.56 NATO rounds pumped into the union from my two Ruger AR-556s. should you ever visit Georgia, @joseluis , stop by and we'll go shoot the shit out of something.

@dankamongmen
Copy link
Owner Author

oh btw i know i wrote "stack" a lot above, but we're actually calling them "piles" because

a) stack has a very definite CS meaning and this is not it
b) i have a masters in nuclear engineering with shit to show for it and it's fun to say pile goddamnit

if it was good enough for fermi it's certainly good enough for us

@dankamongmen
Copy link
Owner Author

for those (not) following along at home, here's what's brewing in the increasingly-inaccurately named dankamongmen/multistack branch:

* 2.0.5 (not yet released)
  * As promised, `ncplane_new()` has been marked as deprecated. It will be
    removed in 3.0. Use the strictly more powerful `ncplane_create()` instead,
    with its self-documenting `struct ncplane_options` argument. So long as
    the arguments to `ncplane_new()` do not have side-effects, calls can be
    mechanically translated to their `ncplane_create()` equivalents.
  * A single `struct notcurses` now supports multiple *planepiles* or simply
    *piles*. Planepiles do not exist as a type (any `ncplane` bound to itself
    is the root of a pile; the standard plane is always bound to itself, and
    thus there always exists a *standard pile*), but as a concept. A pile is
    made up of some root plane, and all planes recursively bound to that root.
    Multiple threads can freely operate on distinct piles, even rendering them
    concurrently (into distinct memory, obviously). A pile ceases to exist when
    all its planes are destroyed, or when its root plane is reparented into
    another pile. It is an error to call `ncplane_destroy()` on a root plane to
    which other planes are bound; `ncplane_genocide()` must be employed in this
    case. It remains impossible to reparent or destroy the standard plane.
  * A `NULL` `ncplane` can now be passed to `ncplane_create()` as its first
    argument; the created `ncplane` will root its own new pile. Similarly,
    `ncplane_reparent()` can now be provided the same `ncplane` for both
    arguments; the `ncplane` will root its own new pile (unless it already did,
    in which case this is a no-op). Planes outside the standard pile *are not
    rendered in a call to `notcurses_render()`*. An `ncplane` can be reparented
    into any existing pile.
  * `notcurses_render_pile()` has been added to render a particular pile.
    `notcurses_render()` now calls `notcurses_render_pile()` with the
    standard plane.

@dankamongmen
Copy link
Owner Author

i might not purge ncplane_new() because why break working code. I'm updating all the usages within the tree, though, and definitely marking it deprecated.

@dankamongmen
Copy link
Owner Author

The ncplane_destroy() not working on a root plane is an ugly wart, but we need that rule because otherwise, if two planes are bound directly to said root plane, what happens? The rootness is a spontaneous symmetry break.

...which suggests, of course, a root level rather than a root plane. which is not a terrible idea, except the lingering asshair that is the standard plane kinda fucks this up. can other planes be on the same root plane as the standard plane? if the set of planes bound to some plane had to be a partition, then no, but so long as they can overlap, you would have to let one...but is that such a problem?

is this stupid and overthinking in response to what's only really a rather small departure from orthogonality?

i will ponder it. all input welcome.

@dankamongmen
Copy link
Owner Author

is this stupid and overthinking in response to what's only really a rather small departure from orthogonality?

the more i think about it, this is absolutely the right thing to do. i don't like the fact that you can't reparent an entire level at once, but that's true for our current situation, also (you can't fix this with e.g. an ncplane_adopt() that takes all the bound planes of some other plane, because that breaks down for root planes). that's no big deal, though. loop over the fuckers.

oooooh what we could add is some kind of e.g. ncplane_everybody_on_the_boat() where you reparented not the specified plane, but the specified plane and its recursive boundnexts. yep, there we go. we'll want a better name.

so now if you have a root level with a single plane, and two planes are bound to it, and you do an ncplane_destroy() of the former, the latter two fall down into the root level. the boundnext pointer already serves to implement this level -- we simply relax the (now so clearly unnecessary, and until now implicit) restriction that the root plane have a NULL boundnext -- remember, the pile is not a data structure, occupies no memory, and cannot be address without aliasing some plane.

i'm glad i typed this all out, this is much, much, much better.

@dankamongmen
Copy link
Owner Author

Hrmmm actually there's a problem with this. So we've proposed decoupling a plane by reparenting against itself. This makes it a new pile. Under the proposal above, how would we extract a plane and add it to a root level of an existing pile? There's no way to do so. I don't think it particularly important, but it undermines the conceptual elegance of the entire approach--in which case the added complexity probably isn't worth it. Hrmmm.

(currently, you join a level by reparenting to the plane to which that level is bound. the planes of a root level are bound to themselves; you can only reparent underneath them).

@joseluis
Copy link
Collaborator

I think it's perfectly acceptable to have the limitation of only one plane for the root of a plane pile.

As long as we can have multiple planepiles detached from the main pile being rendered, and the flexibility of shuffling planes between piles I don't see a problem with that really.

@joseluis
Copy link
Collaborator

It would be nice to have the option of inserting a plane between two others, like adding a chain slab, on one side, and being able to insert a plane as a child and becoming a sibling of the preexisting children of the new parent, on the other...

@dankamongmen
Copy link
Owner Author

It would be nice to have the option of inserting a plane between two others, like adding a chain slab, on one side, and being able to insert a plane as a child and becoming a sibling of the preexisting children of the new parent, on the other...

to insert c between a and b, reparent b to c and c to a. but maybe you meant as one call?

@dankamongmen
Copy link
Owner Author

I think it's perfectly acceptable to have the limitation of only one plane for the root of a plane pile.

As long as we can have multiple planepiles detached from the main pile being rendered, and the flexibility of shuffling planes between piles I don't see a problem with that really.

i just really don't like that ncplane_destroy() can't be defined on root planes with these semantics. :/

@joseluis
Copy link
Collaborator

Yeah I meant in one call.

Ideally (in my mind) the notcurses object would be the one having any plane appointed to render (does it need a particular size?). You could make Nble buffering by changing that pointer to another plane.

And maybe only allow destroying either childless planes or the whole tree of descendants? You could also either reparent any plane (alongside its children) or make it an orphan.

@dankamongmen
Copy link
Owner Author

Hrmmmm, I was writing out the rules, and realized something else: the ncplane_destroy() rule is insufficient for maintaining our invariants, unless we also prohibit a pile's root plane from being reparented elsewhere within its pile. This latter is a terrible restriction. Alright, root levels it is.

The fundamental problem arises from ncplane_reparent() being specified in terms of a parent plane. ncplane_make_sibling() would complete the operation set, but the two would have a grotesque overlap in the case of C, A -> B :: A -> {B, C}. You could do this with either ncplane_reparent(C, A) or ncplane_make_sibling(C, B), which I don't like.

A far-out idea running counter to general trends (i.e. eliminating the standard plane): what if every pile was rooted by a standard plane? Reparenting something to itself would create a new pile AND a new standard plane for that pile: x -> A :: { x, S_p -> A }. Then you make something a sibling of A by reparenting to S_p. Now, I don't at all want to do this, but if we had some virtual plane representing a pile...no, I don't like it.

We could of course have a hyperspecialized ncplane_repile(A, B) where B must be a root plane, x -> A, B :: { x, { A, B } }. Were we to do this, we'd probably not have ncplane_reparent(A, A) spawn a new pile; instead, ncplane_repile(A, NULL) would accomplish this. I think that might be the cleanest thing. ncplane_reparent(A, A) would remain a no-op, as it is now.

Hrmmmm....

@dankamongmen
Copy link
Owner Author

dankamongmen commented Nov 24, 2020

If a plane is created with {y, x}=={1, 1}, and it is bound to a plane at {2, 2}, it is logically at {3, 3}.

when we reparent it (especially as a root plane), does it remain at {3, 3}? or go to {1, 1}? i think the answer is {1 + margin_t, 1 + margin_l}; that makes the most sense. the problem with this is that ncplane_y() always returns 0,0 for a root plane due to how it's defined (since the standard plane is always 0, 0).

i think we could just redefine ncplane_y() and ncplane_x() a bit more complexly:

ncplane_y(){
  return n->absy - n->boundto->absy;
}

becomes

ncplane_y(){
  if(n->boundto == n){
    return n->absy - ncplane_notcurses(n)->margin_t;
  }
  return n->absy - n->boundto->absy;
}

and when a plane becomes a root plane, throw margin_t and margin_l into it via sum.

@dankamongmen
Copy link
Owner Author

and when a plane becomes a root plane, throw margin_t and margin_l into it via sum.

yeah, that appears to work.

dankamongmen added a commit that referenced this issue Nov 24, 2020
dankamongmen added a commit that referenced this issue Nov 24, 2020
@dankamongmen
Copy link
Owner Author

Closing this as done; I've moved the rendering changes to new bug #1135.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants