Skip to main content

Session

Struct Session 

Source
pub struct Session { /* private fields */ }
Expand description

A diagram being edited, the steps behind and ahead of it, and a position among them.

Nothing here changes a session, and the type system says so rather than a rule this file keeps. No method takes &mut self: every one of them is asked over &self and hands a new Session back, so a caller that forgets to keep what it was given changes nothing, and a caller that keeps it still cannot have changed this one. The guarantee §4 of the model makes — the diagram a session owns is changed through the session and nowhere else — is what the absence of a &mut self anywhere in this surface means, and it is a stronger answer than the one a private field was: nothing in this crate can break it, because there is no borrow that would let it. The price is that a caller has to keep what it is given: discarding the result of perform changes nothing at all, which is a warning rather than a failure. The field below is written once on top of that: a command changes the diagram by way of the steps, never by way of the field.

The field holds the diagram the session was created over, and the steps hold every state after it. A step is the diagram a command left, so the state before the first command is the one state no step holds, and it is the field — which is why nothing writes the field again, and why the diagram in hand is either that field or a step and never anything else. One diagram per step and one to start with, which is the model’s “memory proportional to the diagram times the number of steps”, and each of them held once.

What a session is made of is shareable, and the sharing is what pays for the copies. Two sessions standing at different positions in one history hold the same VectorSync<Step> and the same Diagrams, and a new one costs a new position and at most a new leaf. Nothing in the layer needs two sessions at once today — the demonstration walks one — but this is the property a caller gets for free the first time one does, and it is why the steps are a persistent sequence and not a Vec.

Giving a step back moves the position and changes nothing else. undo and redo are one expression each for that reason, and neither removes a step or rebuilds a diagram: the state that comes back is the state that was there, held since the command that made it. Nothing is inverted, and every step stays where it is while the history walks over them, which is what makes the walk go both ways.

The history is a line, and position is where in it the session stands. It is how many steps are behind rather than an index into a pair of stacks, so it cannot disagree with either of them: there is something behind exactly when position is above zero, and something ahead exactly when it is below steps.len(). A command carried out from behind the end discards what was ahead, which is the only end a line can discard from without changing what giving a step back means.

A command that changes nothing records nothing and discards nothing (#285): the session comes back as it was, so a branch is only ever cut by a command that moved the drawing.

The steps are held in the same persistent sequence the diagram holds its figures in, so a session standing at one place in a history shares every step with one standing at another rather than copying them — and a step is a whole diagram beside a command, which is what makes the difference between sharing and copying the difference between 949 B and 40 122 B per step. That sequence has no truncation, so what was ahead is dropped by a walk of drop_last_mut rather than in one step: each of those is a trie descent where a Vec was one, and it is paid only on the branch that carries a command out from behind the end. See kept_first, which is where that walk is written and tested.

§The absence, shown rather than described

The rule that nothing here changes a session is held by nothing in this file, so it is worth one documentation example that says it out loud: everything up to the last line compiles, and the last line is a method this crate does not have.

ⓘ
use monospace_core::{Buffer, Pos, Size, Window};
use monospace_diagram::Diagram;
use monospace_editing::Session;

let session = Session::new(Diagram::new());
session.diagram().draw(&mut Buffer::new(Window {
    origin: Pos { x: 0, y: 0 },
    size: Size { width: 1, height: 1 },
}));

session.diagram_mut();

Implementations§

Source§

impl Session

Source

pub fn new(initial_diagram: Diagram) -> Session

A session over diagram, holding it and no history behind it.

The diagram is taken by value, so the caller has handed it over rather than lent it, and a copy the caller kept is a diagram of its own that this session never sees. Where it came from is not asked and not refused: a diagram is a value and any of them can be opened, which §8 of the model lists as deliberately unresolved rather than as missing.

Source

pub fn diagram(&self) -> &Diagram

The diagram this session holds, borrowed.

The step behind the position, and the diagram this session was created over when nothing is behind it — read rather than rebuilt, and whole. Nothing is copied and nothing can be changed through it, which is what makes it the answer for a caller that repaints on every event. A caller that wants to keep one copies it — Diagram is Clone — and the copy is a diagram like any other: changeable, free of the session and unconnected to it.

Source

pub fn can_undo(&self) -> bool

Whether there is a step behind the position, and so whether undo would change anything.

Answerable without carrying it out, which is what lets a caller grey out a control rather than guess — the same answer the diagram gives a reference to a figure that is not there.

Source

pub fn can_redo(&self) -> bool

Whether there is a step ahead of the position, and so whether redo would change anything.

Source

pub fn affected_by_undo(&self) -> Option<ShapeId>

The figure the step behind the position touched, or None when there is nothing behind.

Answerable without walking, like can_undo, which is why the walk keeps its signature (§4 of the application model asks before it gives the step back). A caller that has to know where to leave the selection asks this first and keeps the answer across the one call that moves the position.

The answer is the step’s and not the command’s. For every command that names a figure it is that id, and for Command::Add it is the identity the diagram assigned — the one case the command alone cannot name, which is why the step records it (§5 of the editing model).

Source

pub fn affected_by_redo(&self) -> Option<ShapeId>

The figure the step ahead of the position touched, or None when there is nothing ahead.

The other direction of affected_by_undo, asked the same way and recorded the same way.

Source

pub fn undo(&self) -> Self

Gives the step behind the position back: what the session this returns holds is one step earlier than what this one holds.

Nothing here is changed, and the same is true of redo and perform. What comes back is a session standing one place earlier over the same steps: a new position rather than a new history, every step a node this one was already holding, and no diagram copied to produce it. At the oldest position there is nothing behind, and what comes back is this session unchanged — no error, no report and no panic, which is the answer the diagram gives a figure it does not hold and the reason can_undo is asked rather than a result waited for.

Source

pub fn redo(&self) -> Self

Carries the step ahead of the position out again: the diagram it holds is what the session this returns holds again.

This session is not changed, for the reason undo gives. At the newest position there is nothing ahead, and what comes back is this session unchanged — the same silence in the other direction, and asked about by can_redo rather than waited for.

Source

pub fn perform(&self, command: Command) -> Self

Carries command out against the diagram this session holds, and hands back the session standing over what it left: records the diagram the command leaves as a step, and discards whatever was ahead of the position.

This session is not changed, and discarding what comes back changes nothing. That is what keeping a session out of reach of a &mut buys, and it is why this is the one method here whose result has to be kept rather than merely read.

The step is the diagram the command left, and that order is what fixes what the field holds. The state the first command found is the one state no step holds, and it is the field — which is why nothing writes the field again, and why the diagram in hand is either that field or a step and never a third thing. The other order would work too, and it would make the field hold the newest state rather than the first: a field whose contents change with every command, and a newest state kept outside the history, which is what makes the answer at the end of the history a case of its own instead of a step like any other.

The diagram in hand is the one the step is handed, so no diagram is copied to record it. apply_to builds the state and hands it back, and that value is both what the step holds and what the next call to diagram reads — a whole diagram on the write path, which is what the other order would have cost, since a step written before the command it belongs to has to be handed a copy of what that command went on to replace.

What the command becomes is carried out here and nowhere else. The mapping from a meaning to one of the diagram’s seven operations is the one in Command, and the session is what carries it out because only what was asked knows whether what it was asked to do was one gesture or several: a figure dragged across a screen is one thing to the person dragging it and many displacements to the diagram. Nothing reads that answer yet — §8 of the model says what is left to write — and the field it will be read from is the step’s command, which is recorded beside the diagram from this first version.

Nothing is inverted, and the steps are what make that unnecessary. Diagram::remove freezes every reference naming the figure it takes and states that nothing re-hangs them, so an inverse of that command cannot be derived — it could only be reconstructed from what the caller happened to capture beforehand. Every state the history passes through is kept instead, and giving a change back is one of them becoming the diagram again.

What was ahead of the position is discarded, which is what makes the history a line rather than a tree. Branching earns its keep when a state somebody discarded is worth returning to, and nothing here says one is: a caller that moves on from a state it gave back has said the state was not worth keeping. What would reopen it is the first caller that wants one back, and §6 of the model says so. It is kept_first rather than a Vec::truncate, because the sequence holding the steps is not one that can be truncated in place; the walk is where the cost of that choice is written down rather than paid silently here.

A command that changed nothing records no step (#285). The diagram’s operations cannot fail, so a command naming a figure it does not hold changes nothing, and neither does one whose change gives the figure back as it was — a box at its smallest shrunk again, a line at its shortest shrunk again, a connector resized, a figure brought forward or to the front when it is already at the front, or backward or to the back when it is already at the back. What comes back is this session: the same steps, the same position and the same diagram, so what was ahead of the position stays ahead and a no-op discards no branch. The command knows which figure it touched and answers whether that figure changed; nothing here compares two diagrams, which a diagram has no way of doing.

Source

pub fn add(&self, shape: Shape) -> (Self, ShapeId)

Adds shape to the diagram this session holds, assigns it a ShapeId, records the resulting diagram as a step, and hands back the new session beside the assigned identity.

This session is not changed, for the reason every method here gives: a new session is returned holding the step ahead and the original is left intact.

The step records Command::Add { shape } and the diagram add produced, so undo gives the diagram back without the added figure and redo restores it.

What was ahead of the position is discarded, exactly as in perform: a branch is cut when a new figure is added from behind the end of the history.

Trait Implementations§

Source§

impl Clone for Session

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.