Евгений Обрезков "behind the terminal"
TRANSCRIPT
Behind the terminalHow to waste half a year of your time on something
nobody cares about
or
Who am I?
• Developer Advocate at Onix-Systems (open-source, community and so on);
• NodeJS developer with 4+ years of experience;
• Active contributor to Sails ecosystem (in the past);
• Currently working with microcontrollers;
What will I talk about?
• Controlling the cursor in terminal;
• Rendering using our controlled cursor;
• Oops, we need to optimize it;
• Demos;
Controlling the cursor• Move cursor: <ESC>[<ROW>;<COLUMN>f
• Change display mode: <ESC>[<MODE>m
• Change foreground: <ESC>[38;2;<R>;<G>;<B>m
• Change background: <ESC>[48;2;<R>;<G>;<B>m
• Clear the screen: <ESC>[2J
• etc…
How can it be used for implementing canvas?
• cursor.moveTo(x, y) -> process.stdout.write(`\e[${y};${x}f`);
• cursor.background(r, g, b) ->process.stdout.write(`\e[48;2;${r};${g};${b}m`);
• cursor.write(text) ->process.stdout.write(text);
• and so on…
We have implemented mapping API calls to
control sequences, great!
Simple render engine
Move to (10, 10) -> change foreground color to red -> write “Hello, World” -> move to (0, 0).
cursor .moveTo(10, 10) // <ESC>[10;10f .foreground(255, 0, 0) // <ESC>[38;2;255;0;0m .write(‘Hello, World’) // Hello, World .moveTo(0, 0); // <ESC>[0;0f
It works great for static rendering, when you show
the frame only once
But, we want to render 25\30\60\120 times a
second…
Oops, we have a problem
<ESC>[10;10f<ESC>[38;2;255;0;0mHello, World<ESC>[0;0f<ESC>[2J<ESC>[10;10f<ESC>[38;2;255;0;0mHello,World<ESC>[0;0f<ESC>[2J<ESC>[10;10f<ESC>[38;2;255;0;0mHello,World<ESC>[0;0f<ESC>[2J<ESC>[10;10f<ESC>[38;2;255;0;0mHello,World<ESC>[0;0f<ESC>[2J<ESC>[10;10f<ESC>[38;2;255;0;0mHello,World<ESC>[0;0f<ESC>[2J<ESC>[10;10f<ESC>[38;2;255;0;0mHello,World<ESC>[0;0f<ESC>[2J…
If frame is rich enough, you will get spamming a lot of text 25
times per second - that’s not good
Let’s optimize it
Our assumptions• It’s logical to assume, that we need to track which
“pixels” are changed and which not;
• Our current structure is not applicable to building difference map - we have only one big line of control sequences and we know nothing about current state of cursor, “pixels”, etc;
• Solution can be to wrap each cell of the terminal in independent control sequence, so we can control them independently from others;
Wrapping the cellsEach cell must know how to render itself independently. Let’s wrap the cell in all the required sequences for that:
• Position;
• Background\Foreground;
• Display modes;
• Character to write in this cell;
• Reset all settings to default (so it doesn’t collide with other styles);
Wrapped cell
<ESC>[<Y>;<X>f # Position <ESC>[48;2;<R>;<G>;<B>m # Background <ESC>[38;2;<R>;<G>;<B>m # Foreground<ESC>[<DISPLAY_MODE>m # Bold, dim, whatever<CHAR_IN_CELL> # A single character to print<ESC>[0m # Reset all display settings
We got independent cells, think of them as “pixels”
Introducing the “markers”
Cell markers
We have 2 states of the cell: modified and unmodified.
Let’s say, we change background for some cell:
cell[y * <TERMINAL_WIDTH> + x].setBackground();
It changes the cell state to modified and set new background color for our cell, which will rebuild the control sequence for that cell on the next flushing.
Markers allow to not build the difference between cells - we already know that it was not
modified
What if we change background to the same
color?
That’s when difference comes into play
If control sequence in a modified cell is the same as the old control sequence - ignore flushing - we
already flushed it before
Checklist• We have a kind of “virtual terminal” in our render engine
which is represented as an array of wrappers around real cells;
• We removed the control sequence which resets the terminal state. We can clear it via writing cell with whitespace and no styling;
• We have hybrid system with markers and differences between frames;
• Finally, we have 120 FPS in the terminal - amazing!
It’s written entirely in JavaScript
Everybody loves demos!
Thanks
ghaiklor ghaiklor ghaiklor