pe: rotating console lines and basic benchmarks
This commit is contained in:
parent
0da3395e89
commit
189aadff1c
12
README.md
12
README.md
|
@ -65,3 +65,15 @@ Future wishes:
|
|||
| Island of the Dead Kings | TODO | TODO | TODO | TODO |
|
||||
| Synchro-Donjon | TODO | TODO | TODO | TODO |
|
||||
| Flappy Bird | Yes | Yes | TODO | TODO |
|
||||
|
||||
**Basic benchmarks**
|
||||
|
||||
PythonExtra is slightly faster than the official Python app, probably due to
|
||||
optimization during compilation of the VM. The tests below show the effect on
|
||||
basic Python operations.
|
||||
|
||||
| Test | Program | Official Python (fx-CG 50) | PythonExtra (fx-CG 50) |
|
||||
| ---- | ------- | -------------------------- | ---------------------- |
|
||||
| VM speed | `for i in range(1000000): pass` | ~12 seconds | 8.9 seconds |
|
||||
| Shell output | `for i in range(100000): print(i)` | ~22 seconds | 11.3 seconds |
|
||||
| Large integers | 250! (500 repeats) | ~15 seconds | 8.6 seconds |
|
||||
|
|
|
@ -35,9 +35,7 @@ bool console_line_init(console_line_t *line, int prealloc_size)
|
|||
void console_line_deinit(console_line_t *line)
|
||||
{
|
||||
free(line->data);
|
||||
line->data = NULL;
|
||||
line->size = 0;
|
||||
line->alloc_size = 0;
|
||||
memset(line, 0, sizeof *line);
|
||||
}
|
||||
|
||||
bool console_line_alloc(console_line_t *line, int n)
|
||||
|
@ -46,7 +44,7 @@ bool console_line_alloc(console_line_t *line, int n)
|
|||
return true;
|
||||
|
||||
/* Always increase the size by at least 16 so we can insert many times in a
|
||||
row without worrying about excessive insertions. */
|
||||
row without worrying about excessive allocations. */
|
||||
int newsize = max(line->alloc_size + 16, n + 1);
|
||||
char *newdata = realloc(line->data, newsize);
|
||||
if(!newdata)
|
||||
|
@ -147,86 +145,188 @@ int console_line_render(int x, int y, console_line_t *line, int w, int dy,
|
|||
return y;
|
||||
}
|
||||
|
||||
//=== Rotating line storage ===//
|
||||
|
||||
bool linebuf_init(linebuf_t *buf, int capacity, int backlog_size)
|
||||
{
|
||||
if(capacity <= 0)
|
||||
return false;
|
||||
|
||||
buf->lines = malloc(capacity * sizeof *buf->lines);
|
||||
if(!buf->lines)
|
||||
return false;
|
||||
|
||||
buf->capacity = capacity;
|
||||
buf->start = 0;
|
||||
buf->size = 0;
|
||||
buf->absolute = 1;
|
||||
buf->backlog_size = backlog_size;
|
||||
buf->total_size_except_last = 0;
|
||||
buf->absolute_rendered = 0;
|
||||
buf->total_rendered = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void linebuf_deinit(linebuf_t *buf)
|
||||
{
|
||||
free(buf->lines);
|
||||
memset(buf, 0, sizeof *buf);
|
||||
}
|
||||
|
||||
/* Lines in the buffer are identified by their positions within the `lines`
|
||||
array, which are integers equipped with modulo arithmetic (we call them
|
||||
"indices"). We abstract away the rotation by numbering stored lines from 0
|
||||
to buf->size - 1, and we call these numbers "nths". When we want to identify
|
||||
lines independent of rotation, we use their absolute line number.
|
||||
|
||||
Such numbers refer to lines stored in the buffer if:
|
||||
(index) 0 <= linebuf_index_to_nth(buf, index) < size
|
||||
(nth) 0 <= nth < size
|
||||
(abs) 0 <= abs - buf->absolute < size */
|
||||
|
||||
GINLINE static int linebuf_nth_to_index(linebuf_t const *buf, int nth)
|
||||
{
|
||||
return (buf->start + nth) % buf->capacity;
|
||||
}
|
||||
|
||||
/* Get the nth line. */
|
||||
GINLINE static console_line_t *linebuf_get_nth_line(linebuf_t const *buf,
|
||||
int nth)
|
||||
{
|
||||
if(nth < 0 || nth >= buf->size)
|
||||
return NULL;
|
||||
|
||||
return &buf->lines[linebuf_nth_to_index(buf, nth)];
|
||||
}
|
||||
|
||||
/* Move `index` by `diff`; assumes |diff| <= buf->capacity. */
|
||||
GINLINE static int linebuf_index_add(linebuf_t const *buf, int index, int diff)
|
||||
{
|
||||
return (index + diff + buf->capacity) % buf->capacity;
|
||||
}
|
||||
|
||||
int linebuf_start(linebuf_t const *buf)
|
||||
{
|
||||
return buf->absolute;
|
||||
}
|
||||
|
||||
int linebuf_end(linebuf_t const *buf)
|
||||
{
|
||||
return buf->absolute + buf->size;
|
||||
}
|
||||
|
||||
console_line_t *linebuf_get_line(linebuf_t const *buf, int abs)
|
||||
{
|
||||
return linebuf_get_nth_line(buf, abs - buf->absolute);
|
||||
}
|
||||
|
||||
console_line_t *linebuf_get_last_line(linebuf_t const *buf)
|
||||
{
|
||||
return linebuf_get_nth_line(buf, buf->size - 1);
|
||||
}
|
||||
|
||||
console_line_t *linebuf_newline(linebuf_t *buf)
|
||||
{
|
||||
/* Make space if the buffer is full */
|
||||
linebuf_recycle_oldest_lines(buf, buf->size - buf->capacity + 1);
|
||||
|
||||
/* Freeze the current last line's size */
|
||||
if(buf->size > 0) {
|
||||
console_line_t *L = linebuf_get_nth_line(buf, buf->size - 1);
|
||||
buf->total_size_except_last += L->size;
|
||||
}
|
||||
|
||||
buf->size++;
|
||||
console_line_t *L = linebuf_get_nth_line(buf, buf->size - 1);
|
||||
console_line_init(L, 16);
|
||||
return L;
|
||||
}
|
||||
|
||||
void linebuf_recycle_oldest_lines(linebuf_t *buf, int count)
|
||||
{
|
||||
count = min(count, buf->size);
|
||||
if(count <= 0)
|
||||
return;
|
||||
|
||||
for(int nth = 0; nth < count; nth++) {
|
||||
console_line_t *L = linebuf_get_nth_line(buf, nth);
|
||||
buf->total_rendered -= L->render_lines;
|
||||
if(nth != buf->size - 1)
|
||||
buf->total_size_except_last -= L->size;
|
||||
console_line_deinit(L);
|
||||
}
|
||||
|
||||
buf->start = linebuf_index_add(buf, buf->start, count);
|
||||
buf->size -= count;
|
||||
buf->absolute += count;
|
||||
}
|
||||
|
||||
void linebuf_clean_backlog(linebuf_t *buf)
|
||||
{
|
||||
if(buf->size <= 0)
|
||||
return;
|
||||
|
||||
int remove = 0;
|
||||
int n = buf->total_size_except_last + linebuf_get_last_line(buf)->size;
|
||||
|
||||
while(remove < buf->size - 1 && n > buf->backlog_size) {
|
||||
n -= linebuf_get_nth_line(buf, remove)->size;
|
||||
remove++;
|
||||
}
|
||||
|
||||
linebuf_recycle_oldest_lines(buf, remove);
|
||||
}
|
||||
|
||||
void linebuf_update_render(linebuf_t *buf, int width, bool lazy)
|
||||
{
|
||||
int start = linebuf_start(buf);
|
||||
int end = linebuf_end(buf);
|
||||
if(lazy)
|
||||
start = max(start, buf->absolute_rendered + 1);
|
||||
|
||||
for(int abs = start; abs < end; abs++) {
|
||||
console_line_t *L = linebuf_get_nth_line(buf, abs - buf->absolute);
|
||||
buf->total_rendered -= L->render_lines;
|
||||
console_line_update_render_lines(L, width);
|
||||
buf->total_rendered += L->render_lines;
|
||||
}
|
||||
|
||||
buf->absolute_rendered = max(buf->absolute_rendered, end - 2);
|
||||
}
|
||||
|
||||
//=== Terminal emulator ===//
|
||||
|
||||
console_t *console_create(int backlog_size)
|
||||
console_t *console_create(int backlog_size, int maximum_line_count)
|
||||
{
|
||||
backlog_size = max(backlog_size, PE_CONSOLE_LINE_MAX_LENGTH);
|
||||
maximum_line_count = max(maximum_line_count, 1);
|
||||
|
||||
console_t *cons = malloc(sizeof *cons);
|
||||
cons->lines = NULL;
|
||||
cons->line_count = 0;
|
||||
|
||||
cons->absolute_lineno = 1;
|
||||
cons->backlog_size = max(backlog_size, PE_CONSOLE_LINE_MAX_LENGTH);
|
||||
if(!linebuf_init(&cons->lines, maximum_line_count, backlog_size)) {
|
||||
free(cons);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cons->cursor = -1;
|
||||
cons->render_needed = true;
|
||||
cons->render_font = NULL;
|
||||
cons->render_width = 0;
|
||||
cons->render_lines = 0;
|
||||
cons->render_total_lines = 0;
|
||||
|
||||
if(!console_new_line(cons)) {
|
||||
free(cons);
|
||||
return NULL;
|
||||
}
|
||||
console_newline(cons);
|
||||
return cons;
|
||||
}
|
||||
|
||||
bool console_new_line(console_t *cons)
|
||||
void console_newline(console_t *cons)
|
||||
{
|
||||
int newsize = (cons->line_count + 1) * sizeof *cons->lines;
|
||||
console_line_t *newlines = realloc(cons->lines, newsize);
|
||||
if(!newlines)
|
||||
return false;
|
||||
cons->lines = newlines;
|
||||
|
||||
/* If this fails we keep the extended lines buffer. The next realloc() will
|
||||
be a no-op and we don't have to track anything. */
|
||||
if(!console_line_init(&cons->lines[cons->line_count], 16))
|
||||
return false;
|
||||
cons->line_count++;
|
||||
linebuf_newline(&cons->lines);
|
||||
cons->cursor = 0;
|
||||
cons->render_needed = true;
|
||||
|
||||
/* Routinely clean the backlog every time a line is added. */
|
||||
console_clean_backlog(cons);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static console_line_t *console_get_absolute_line(console_t const *cons,
|
||||
int lineno)
|
||||
{
|
||||
int i = lineno - cons->absolute_lineno;
|
||||
return (i >= 0 && i < cons->line_count) ? &cons->lines[i] : NULL;
|
||||
} */
|
||||
|
||||
void console_clean_backlog(console_t *cons)
|
||||
{
|
||||
/* Find how many lines we can keep while using at most cons->backlog_size
|
||||
bytes to store them. The console must always have at least one line. */
|
||||
int first_kept = cons->line_count;
|
||||
int line_size = 0;
|
||||
int total_size = 0;
|
||||
|
||||
do {
|
||||
total_size += line_size;
|
||||
first_kept--;
|
||||
line_size = cons->lines[first_kept].size;
|
||||
}
|
||||
while(first_kept > 0 && total_size + line_size <= cons->backlog_size);
|
||||
|
||||
/* Remove old lines */
|
||||
for(int j = 0; j < first_kept; j++)
|
||||
console_line_deinit(&cons->lines[j]);
|
||||
|
||||
/* Don't realloc() yet, it will happen soon enough anyway */
|
||||
memmove(cons->lines, cons->lines + first_kept,
|
||||
(cons->line_count - first_kept) * sizeof cons->lines[0]);
|
||||
cons->line_count -= first_kept;
|
||||
|
||||
/* Update the absolute number of the first line */
|
||||
cons->absolute_lineno += first_kept;
|
||||
|
||||
cons->render_needed = true;
|
||||
/* This is a good time to clean up the backlog. */
|
||||
// TODO: This might actually be the performance bottleneck, because we do a
|
||||
// long loop only to find out that the total size is still reasonable!
|
||||
linebuf_clean_backlog(&cons->lines);
|
||||
}
|
||||
|
||||
void console_clear_render_flag(console_t *cons)
|
||||
|
@ -236,9 +336,7 @@ void console_clear_render_flag(console_t *cons)
|
|||
|
||||
void console_destroy(console_t *cons)
|
||||
{
|
||||
for(int i = 0; i < cons->line_count; i++)
|
||||
console_line_deinit(&cons->lines[i]);
|
||||
free(cons->lines);
|
||||
linebuf_deinit(&cons->lines);
|
||||
free(cons);
|
||||
}
|
||||
|
||||
|
@ -271,38 +369,31 @@ static void view_params(int w,
|
|||
void console_compute_view(console_t *cons, font_t const *font,
|
||||
int width, int lines)
|
||||
{
|
||||
/* If a view with the same width was previously computed, do a lazy
|
||||
update: recompute only the last lines. */
|
||||
bool lazy = (width != 0 && cons->render_width == width);
|
||||
cons->render_font = font;
|
||||
cons->render_width = width;
|
||||
cons->render_lines = lines;
|
||||
cons->render_total_lines = 0;
|
||||
|
||||
int text_w;
|
||||
view_params(width, &text_w, NULL, NULL);
|
||||
|
||||
for(int i = 0; i < cons->line_count; i++) {
|
||||
console_line_update_render_lines(&cons->lines[i], text_w);
|
||||
cons->render_total_lines += cons->lines[i].render_lines;
|
||||
}
|
||||
linebuf_update_render(&cons->lines, width, lazy);
|
||||
}
|
||||
|
||||
console_scrollpos_t console_clamp_scrollpos(console_t const *cons,
|
||||
console_scrollpos_t pos)
|
||||
{
|
||||
/* No scrolling case */
|
||||
if(cons->render_total_lines < cons->render_lines)
|
||||
if(cons->lines.total_rendered < cons->render_lines)
|
||||
return 0;
|
||||
|
||||
/* Normal clamp */
|
||||
return max(0, min(pos, cons->render_total_lines - cons->render_lines));
|
||||
return max(0, min(pos, cons->lines.total_rendered - cons->render_lines));
|
||||
}
|
||||
|
||||
void console_render(int x, int y0, console_t const *cons, int dy,
|
||||
console_scrollpos_t pos)
|
||||
{
|
||||
int total_lines = cons->render_total_lines;
|
||||
int total_lines = cons->lines.total_rendered;
|
||||
int visible_lines = cons->render_lines;
|
||||
int w = cons->render_width;
|
||||
int watermark = visible_lines - total_lines + pos;
|
||||
int y = y0;
|
||||
|
||||
int text_w, scroll_spacing, scroll_w;
|
||||
|
@ -310,15 +401,27 @@ void console_render(int x, int y0, console_t const *cons, int dy,
|
|||
|
||||
font_t const *old_font = dfont(cons->render_font);
|
||||
|
||||
/* Show only visible lines */
|
||||
for(int i = 0; i < cons->line_count; i++) {
|
||||
console_line_t *line = &cons->lines[i];
|
||||
bool show_cursor = (i == cons->line_count - 1);
|
||||
/* Show only visible lines. We want to avoid counting all the lines in the
|
||||
console, and instead start from the end. */
|
||||
int line_y = visible_lines + pos;
|
||||
int L_start = linebuf_start(&cons->lines);
|
||||
int L_end = linebuf_end(&cons->lines);
|
||||
int i = linebuf_end(&cons->lines);
|
||||
|
||||
if(watermark + line->render_lines > 0)
|
||||
y = console_line_render(x, y, line, text_w, dy, -watermark,
|
||||
visible_lines - watermark, show_cursor ? cons->cursor : -1);
|
||||
watermark += line->render_lines;
|
||||
while(i > L_start && line_y > 0)
|
||||
line_y -= linebuf_get_line(&cons->lines, --i)->render_lines;
|
||||
|
||||
/* If there isn't enough content to fill the view, start at the top. */
|
||||
line_y = min(line_y, pos);
|
||||
|
||||
while(i < L_end && line_y < visible_lines) {
|
||||
console_line_t *line = linebuf_get_line(&cons->lines, i);
|
||||
bool show_cursor = (i == L_end - 1);
|
||||
|
||||
y = console_line_render(x, y, line, text_w, dy, -line_y,
|
||||
visible_lines - line_y, show_cursor ? cons->cursor : -1);
|
||||
line_y += line->render_lines;
|
||||
i++;
|
||||
}
|
||||
|
||||
dfont(old_font);
|
||||
|
@ -342,11 +445,16 @@ void console_render(int x, int y0, console_t const *cons, int dy,
|
|||
|
||||
//=== Edition functions ===//
|
||||
|
||||
GINLINE static console_line_t *last_line(console_t *cons)
|
||||
{
|
||||
return linebuf_get_last_line(&cons->lines);
|
||||
}
|
||||
|
||||
bool console_set_cursor(console_t *cons, int pos)
|
||||
{
|
||||
console_line_t *last_line = &cons->lines[cons->line_count - 1];
|
||||
console_line_t *L = last_line(cons);
|
||||
|
||||
if(pos < last_line->prefix || pos > last_line->size)
|
||||
if(pos < L->prefix || pos > L->size)
|
||||
return false;
|
||||
|
||||
cons->cursor = pos;
|
||||
|
@ -360,37 +468,35 @@ bool console_move_cursor(console_t *cons, int cursor_movement)
|
|||
|
||||
char *console_get_line(console_t *cons, bool copy)
|
||||
{
|
||||
console_line_t *last_line = &cons->lines[cons->line_count - 1];
|
||||
char *str = last_line->data + last_line->prefix;
|
||||
|
||||
console_line_t *L = last_line(cons);
|
||||
char *str = L->data + L->prefix;
|
||||
return copy ? strdup(str) : str;
|
||||
}
|
||||
|
||||
bool console_write_block_at_cursor(console_t *cons, char const *str, int n)
|
||||
bool console_write_raw(console_t *cons, char const *str, int n)
|
||||
{
|
||||
if(!cons->line_count && !console_new_line(cons))
|
||||
return false;
|
||||
if(!cons->lines.size)
|
||||
console_newline(cons);
|
||||
|
||||
/* Split up n characters into chunks that remain within each line's storage
|
||||
capacity. */
|
||||
/* Split string into chunks smaller than each line's storage capacity. */
|
||||
while(n > 0) {
|
||||
console_line_t *last_line = &cons->lines[cons->line_count - 1];
|
||||
int capacity = console_line_capacity(last_line);
|
||||
console_line_t *L = last_line(cons);
|
||||
int capacity = console_line_capacity(L);
|
||||
int round_size = min(n, capacity);
|
||||
|
||||
if(!console_line_insert(last_line, cons->cursor, str, round_size))
|
||||
if(!console_line_insert(L, cons->cursor, str, round_size))
|
||||
return false;
|
||||
cons->cursor += round_size;
|
||||
cons->render_needed = true;
|
||||
if(round_size < n && !console_new_line(cons))
|
||||
return false;
|
||||
if(round_size < n)
|
||||
console_newline(cons);
|
||||
n -= round_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool console_write_at_cursor(console_t *cons, char const *buf, int n)
|
||||
bool console_write(console_t *cons, char const *buf, int n)
|
||||
{
|
||||
int offset = 0;
|
||||
if(n < 0)
|
||||
|
@ -403,7 +509,7 @@ bool console_write_at_cursor(console_t *cons, char const *buf, int n)
|
|||
end++;
|
||||
|
||||
int line_size = end - (buf + offset);
|
||||
if(!console_write_block_at_cursor(cons, buf + offset, line_size))
|
||||
if(!console_write_raw(cons, buf + offset, line_size))
|
||||
return false;
|
||||
|
||||
offset += line_size;
|
||||
|
@ -411,33 +517,32 @@ bool console_write_at_cursor(console_t *cons, char const *buf, int n)
|
|||
break;
|
||||
|
||||
if(buf[offset] == '\n') {
|
||||
if(!console_new_line(cons))
|
||||
return false;
|
||||
console_newline(cons);
|
||||
offset++;
|
||||
}
|
||||
else if(buf[offset] == 8) {
|
||||
offset++;
|
||||
console_line_t *last_line = &cons->lines[cons->line_count - 1];
|
||||
console_line_t *L = last_line(cons);
|
||||
if(cons->cursor > 0) {
|
||||
console_line_delete(last_line, cons->cursor-1, 1);
|
||||
console_line_delete(L, cons->cursor-1, 1);
|
||||
cons->cursor--;
|
||||
}
|
||||
}
|
||||
else if(buf[offset] == '\e') {
|
||||
console_line_t *last_line = &cons->lines[cons->line_count - 1];
|
||||
console_line_t *L = last_line(cons);
|
||||
offset++;
|
||||
|
||||
/* TODO: Handle more complex escape sequences */
|
||||
if(offset + 2 <= n && buf[offset] == '[' && buf[offset+1] == 'K') {
|
||||
console_line_delete(last_line, cons->cursor,
|
||||
last_line->size - cons->cursor);
|
||||
console_line_delete(L, cons->cursor,
|
||||
L->size - cons->cursor);
|
||||
offset += 2;
|
||||
}
|
||||
if(offset + 2 <= n && buf[offset] == 1 && buf[offset+1] == 'D') {
|
||||
cons->cursor -= (cons->cursor > 0);
|
||||
}
|
||||
if(offset + 2 <= n && buf[offset] == 1 && buf[offset+1] == 'C') {
|
||||
cons->cursor += (cons->cursor < last_line->size);
|
||||
cons->cursor += (cons->cursor < L->size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,23 +554,21 @@ bool console_write_at_cursor(console_t *cons, char const *buf, int n)
|
|||
|
||||
void console_lock_prefix(console_t *cons)
|
||||
{
|
||||
console_line_set_prefix(&cons->lines[cons->line_count - 1], cons->cursor);
|
||||
console_line_set_prefix(last_line(cons), cons->cursor);
|
||||
}
|
||||
|
||||
void console_delete_at_cursor(console_t *cons, int n)
|
||||
{
|
||||
console_line_t *last_line = &cons->lines[cons->line_count - 1];
|
||||
int real_n = console_line_delete(last_line, cons->cursor - n, n);
|
||||
int real_n = console_line_delete(last_line(cons), cons->cursor - n, n);
|
||||
cons->cursor -= real_n;
|
||||
cons->render_needed = true;
|
||||
}
|
||||
|
||||
void console_clear_current_line(console_t *cons)
|
||||
{
|
||||
console_line_t *last_line = &cons->lines[cons->line_count - 1];
|
||||
console_line_delete(last_line, last_line->prefix,
|
||||
last_line->size - last_line->prefix);
|
||||
cons->cursor = last_line->prefix;
|
||||
console_line_t *L = last_line(cons);
|
||||
console_line_delete(L, L->prefix, L->size - L->prefix);
|
||||
cons->cursor = L->prefix;
|
||||
cons->render_needed = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,9 +37,9 @@ typedef struct
|
|||
/* Line contents, NUL-terminated. The buffer might be larger. */
|
||||
char *data;
|
||||
/* Size of contents (not counting the NUL). */
|
||||
int size;
|
||||
int16_t size;
|
||||
/* Allocated size (always ≥ size+1). */
|
||||
int alloc_size;
|
||||
int16_t alloc_size;
|
||||
/* Number or render lines used (updated on-demand). */
|
||||
int16_t render_lines;
|
||||
/* Number of initial characters that can't be edited. */
|
||||
|
@ -79,33 +79,103 @@ void console_line_update_render_lines(console_line_t *line, int width);
|
|||
int console_line_render(int x, int y, console_line_t *line, int w, int dy,
|
||||
int show_from, int show_until, int cursor);
|
||||
|
||||
//=== Rotating line storage ===//
|
||||
|
||||
/* linebuf_t: A rotating array of console lines. */
|
||||
typedef struct
|
||||
{
|
||||
/* A rotating array of `capacity` lines starting at position `start` and
|
||||
holding `size` lines. The array is pre-allocated. */
|
||||
console_line_t *lines;
|
||||
|
||||
/* Invariants:
|
||||
- capacity > 0
|
||||
- 0 <= size <= capacity
|
||||
- 0 <= start < capacity
|
||||
- When size is 0, start is undefined. */
|
||||
int capacity, start, size;
|
||||
|
||||
/* To keep track of lines' identity, the rotating array includes an extra
|
||||
numbering system. Each line is assigned an *absolute* line number which
|
||||
starts at 1 and increases every time a line is added. That number is
|
||||
independent of rotation.
|
||||
|
||||
Absolute line number of the next line to be removed. This identifies
|
||||
the `start` line, unless the buffer is empty. Regardless, the interval
|
||||
[buf->absolute .. buf->absolute + buf->size) always covers exactly the
|
||||
set of lines that are held in the buffer. */
|
||||
int absolute;
|
||||
|
||||
/* To avoid memory explosion, the rotating array can be set to clean up old
|
||||
lines when the total memory consumption is too high. `backlog_size`
|
||||
specifies how many bytes of text lines are allowed to hold. */
|
||||
int backlog_size;
|
||||
/* Total size of current lines, in bytes, excluding the last line. */
|
||||
int total_size_except_last;
|
||||
|
||||
/* Last absolute line that has been both laid out for rendering and frozen
|
||||
for edition (ie. followed by another line). Lazy rendering can always
|
||||
start at `absolute_rendered+1`. */
|
||||
int absolute_rendered;
|
||||
/* Total number of rendered lines for the buffer. */
|
||||
int total_rendered;
|
||||
|
||||
} linebuf_t;
|
||||
|
||||
/* Initialize a rotating buffer by allocating `line_count` lines. The buffer
|
||||
will allow up to `backlog_size` bytes of text data and clean up lines past
|
||||
that limit. This function does not free pre-existing data in `buf`. */
|
||||
bool linebuf_init(linebuf_t *buf, int capacity, int backlog_size);
|
||||
|
||||
/* Free a rotating buffer and clean it up. */
|
||||
void linebuf_deinit(linebuf_t *buf);
|
||||
|
||||
/* Absolute line numbers of the "start" and "end" of the buffer. The set of
|
||||
lines in the buffer is always [start ... end). If the buffer is empty, the
|
||||
interval is empty and neither line number is in the buffer. */
|
||||
int linebuf_start(linebuf_t const *buf);
|
||||
int linebuf_end(linebuf_t const *buf);
|
||||
|
||||
/* Get a pointer to the line with the given absolute number. */
|
||||
console_line_t *linebuf_get_line(linebuf_t const *buf, int absolute_number);
|
||||
|
||||
/* Get a pointer to the last line in the buffer (usually the editable line). */
|
||||
console_line_t *linebuf_get_last_line(linebuf_t const *buf);
|
||||
|
||||
/* Add a new line to the buffer (recycling an old one if needed). */
|
||||
console_line_t *linebuf_new_line(linebuf_t *buf);
|
||||
|
||||
/* Recycle the `n` oldest lines from the buffer. */
|
||||
void linebuf_recycle_oldest_lines(linebuf_t *buf, int n);
|
||||
|
||||
/* Clean up lines to try and keep the memory footprint of the text under
|
||||
`backlog_size` bytes. Always keeps at least the last line. */
|
||||
void linebuf_clean_backlog(linebuf_t *buf);
|
||||
|
||||
/* Update the render width computation for all lines in the buffer. If `lazy`
|
||||
is false, all lines are re-laid out. But in the console the width often
|
||||
remains the same for many renders, and only the last line can be edited. In
|
||||
this case, `lazy` can be set to true, and only lines added or edited since
|
||||
the previous render will be re-laid out. */
|
||||
void linebuf_update_render(linebuf_t *buf, int width, bool lazy);
|
||||
|
||||
//=== Terminal emulator ===//
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* A dynamic array of console_line_t. Never empty. */
|
||||
console_line_t *lines;
|
||||
int line_count;
|
||||
/* A rotating array of console_line_t. Never empty. */
|
||||
linebuf_t lines;
|
||||
|
||||
/* Maximum (not 100% strict) amount of storage that is conserved in log.
|
||||
When this limit is exceeded, old lines are cleaned. This must be more
|
||||
than PE_CONSOLE_LINE_MAX_LENGTH. */
|
||||
int backlog_size;
|
||||
|
||||
/* Absolute number of the first line in the dynamic line array. This is
|
||||
the line number in the user-facing sense (it includes lines that were
|
||||
cleaned up to free memory). */
|
||||
int absolute_lineno;
|
||||
/* Cursor position within the last line. */
|
||||
int cursor;
|
||||
|
||||
/* Whether new data has been added and a frame should be rendered. */
|
||||
bool render_needed;
|
||||
/* View parameters from last console_compute_view() */
|
||||
|
||||
/* View geometry parameters from last console_compute_view(). */
|
||||
font_t const *render_font;
|
||||
int render_width;
|
||||
int render_lines;
|
||||
int render_total_lines;
|
||||
|
||||
} console_t;
|
||||
|
||||
|
@ -113,14 +183,11 @@ typedef struct
|
|||
typedef int console_scrollpos_t;
|
||||
|
||||
/* Create a new console with the specified backlog size. */
|
||||
console_t *console_create(int backlog_size);
|
||||
console_t *console_create(int backlog_size, int maximum_line_count);
|
||||
|
||||
/* Create a new empty line at the bottom of the console, and move the cursor
|
||||
there. Previous lines can no longer be edited. Returns false on error. */
|
||||
bool console_new_line(console_t *cons);
|
||||
|
||||
/* Clean up backlog if the total memory usage is exceeded. */
|
||||
void console_clean_backlog(console_t *cons);
|
||||
there. Previous lines can no longer be edited. */
|
||||
void console_newline(console_t *cons);
|
||||
|
||||
/* Clear the console's render flag, which is used to notify of changes. This
|
||||
function is used when handing control of the display from the console to a
|
||||
|
@ -167,11 +234,11 @@ char *console_get_line(console_t *cons, bool copy);
|
|||
|
||||
/* Write string at the cursor's position within the last line. This writes a
|
||||
raw string without interpreting escape sequences and newlines. */
|
||||
bool console_write_block_at_cursor(console_t *cons, char const *str, int n);
|
||||
bool console_write_raw(console_t *cons, char const *str, int n);
|
||||
|
||||
/* Write string at the cursor's position within the last line. This function
|
||||
interprets escape sequences and newlines. */
|
||||
bool console_write_at_cursor(console_t *cons, char const *str, int n);
|
||||
bool console_write(console_t *cons, char const *str, int n);
|
||||
|
||||
/* Set the current cursor position to mark the prefix of the current line. */
|
||||
void console_lock_prefix(console_t *cons);
|
||||
|
|
116
ports/sh/main.c
116
ports/sh/main.c
|
@ -11,7 +11,7 @@
|
|||
#include "py/mphal.h"
|
||||
#include "py/repl.h"
|
||||
#include "shared/runtime/gchelper.h"
|
||||
#include "shared/runtime/pyexec.h"
|
||||
#include "pyexec.h"
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <gint/drivers/keydev.h>
|
||||
|
@ -49,7 +49,7 @@ extern bopti_image_t const img_modifier_states;
|
|||
static ssize_t stdouterr_write(void *data, void const *buf, size_t size)
|
||||
{
|
||||
console_t *cons = data;
|
||||
console_write_at_cursor(cons, buf, size);
|
||||
console_write(cons, buf, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,10 @@ fs_descriptor_type_t stdouterr_type = {
|
|||
};
|
||||
|
||||
/* The global terminal. */
|
||||
console_t *pe_shell_console;
|
||||
console_t *pe_console = NULL;
|
||||
/* The global JustUI scene and widget shell. */
|
||||
jscene *pe_scene = NULL;
|
||||
widget_shell *pe_shell = NULL;
|
||||
|
||||
static bool strendswith(char const *str, char const *suffix)
|
||||
{
|
||||
|
@ -80,16 +83,6 @@ static bool py_file_filter(struct dirent const *ent)
|
|||
return strendswith(ent->d_name, ".py");
|
||||
}
|
||||
|
||||
static void shell_write_char(int c)
|
||||
{
|
||||
pyexec_event_repl_process_char(c);
|
||||
}
|
||||
static void shell_write_str(char const *str)
|
||||
{
|
||||
while(*str)
|
||||
pyexec_event_repl_process_char(*str++);
|
||||
}
|
||||
|
||||
static char *path_to_module(char const *path)
|
||||
{
|
||||
if(path[0] == '/')
|
||||
|
@ -145,8 +138,8 @@ static void pe_print_prompt(int which)
|
|||
else
|
||||
prompt = mp_repl_get_ps1();
|
||||
|
||||
console_write_at_cursor(pe_shell_console, prompt, -1);
|
||||
console_lock_prefix(pe_shell_console);
|
||||
console_write(pe_console, prompt, -1);
|
||||
console_lock_prefix(pe_console);
|
||||
}
|
||||
|
||||
static void pe_reset_micropython(void)
|
||||
|
@ -160,12 +153,33 @@ static void pe_reset_micropython(void)
|
|||
char const *msg = "*** SHELL INITIALIZED ***\n";
|
||||
#endif
|
||||
|
||||
console_new_line(pe_shell_console);
|
||||
console_write_at_cursor(pe_shell_console, msg, -1);
|
||||
console_newline(pe_console);
|
||||
console_write(pe_console, msg, -1);
|
||||
pyexec_event_repl_init();
|
||||
pe_print_prompt(1);
|
||||
}
|
||||
|
||||
void pe_draw(void)
|
||||
{
|
||||
dclear(C_WHITE);
|
||||
jscene_render(pe_scene);
|
||||
|
||||
/* Render shell modifiers above the scene in a convenient spot */
|
||||
int shift, alpha, layer;
|
||||
bool instant;
|
||||
widget_shell_get_modifiers(pe_shell, &shift, &alpha);
|
||||
widget_shell_modifier_info(shift, alpha, &layer, &instant);
|
||||
int icon = 2 * layer + !instant;
|
||||
#ifdef FX9860G
|
||||
dsubimage(118, 58, &img_modifier_states, 9*icon+1, 1, 8, 6,
|
||||
DIMAGE_NONE);
|
||||
#else
|
||||
dsubimage(377, 207, &img_modifier_states, 16*icon, 0, 15, 14,
|
||||
DIMAGE_NONE);
|
||||
#endif
|
||||
dupdate();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
pe_debug_init();
|
||||
|
@ -175,13 +189,13 @@ int main(int argc, char **argv)
|
|||
|
||||
keydev_set_async_filter(keydev_std(), async_filter);
|
||||
|
||||
pe_shell_console = console_create(8192);
|
||||
pe_console = console_create(8192, 200);
|
||||
|
||||
/* Set up standard streams */
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
open_generic(&stdouterr_type, pe_shell_console, STDOUT_FILENO);
|
||||
open_generic(&stdouterr_type, pe_shell_console, STDERR_FILENO);
|
||||
open_generic(&stdouterr_type, pe_console, STDOUT_FILENO);
|
||||
open_generic(&stdouterr_type, pe_console, STDERR_FILENO);
|
||||
|
||||
/* Initialize the MicroPython GC with most available memory */
|
||||
mp_stack_ctrl_init();
|
||||
|
@ -225,15 +239,15 @@ int main(int argc, char **argv)
|
|||
|
||||
//=== GUI setup ===//
|
||||
|
||||
jscene *scene = jscene_create_fullscreen(NULL);
|
||||
jlabel *title = jlabel_create("PythonExtra", scene);
|
||||
jwidget *stack = jwidget_create(scene);
|
||||
jfkeys *fkeys = jfkeys_create2(&img_fkeys_main, "/FILES;/SHELL", scene);
|
||||
pe_scene = jscene_create_fullscreen(NULL);
|
||||
jlabel *title = jlabel_create("PythonExtra", pe_scene);
|
||||
jwidget *stack = jwidget_create(pe_scene);
|
||||
jfkeys *fkeys = jfkeys_create2(&img_fkeys_main, "/FILES;/SHELL", pe_scene);
|
||||
(void)fkeys;
|
||||
|
||||
jwidget_set_stretch(title, 1, 0, false);
|
||||
|
||||
jlayout_set_vbox(scene)->spacing = _(0, 3);
|
||||
jlayout_set_vbox(pe_scene)->spacing = _(0, 3);
|
||||
jlayout_set_stack(stack);
|
||||
jwidget_set_stretch(stack, 1, 1, false);
|
||||
|
||||
|
@ -244,9 +258,9 @@ int main(int argc, char **argv)
|
|||
jwidget_set_stretch(fileselect, 1, 1, false);
|
||||
|
||||
/* Shell tab */
|
||||
widget_shell *shell = widget_shell_create(pe_shell_console, stack);
|
||||
widget_shell_set_line_spacing(shell, _(1, 3));
|
||||
jwidget_set_stretch(shell, 1, 1, false);
|
||||
pe_shell = widget_shell_create(pe_console, stack);
|
||||
widget_shell_set_line_spacing(pe_shell, _(1, 3));
|
||||
jwidget_set_stretch(pe_shell, 1, 1, false);
|
||||
|
||||
#ifdef FX9860G
|
||||
bool show_title_in_shell = false;
|
||||
|
@ -262,41 +276,24 @@ int main(int argc, char **argv)
|
|||
|
||||
/* Initial state */
|
||||
jfileselect_browse(fileselect, "/");
|
||||
jscene_show_and_focus(scene, fileselect);
|
||||
jscene_show_and_focus(pe_scene, fileselect);
|
||||
|
||||
//=== Event handling ===//
|
||||
|
||||
while(1) {
|
||||
jevent e = jscene_run(scene);
|
||||
jevent e = jscene_run(pe_scene);
|
||||
|
||||
if(e.type == JSCENE_PAINT) {
|
||||
dclear(C_WHITE);
|
||||
jscene_render(scene);
|
||||
|
||||
/* Render shell modifiers above the scene in a convenient spot */
|
||||
int shift, alpha, layer;
|
||||
bool instant;
|
||||
widget_shell_get_modifiers(shell, &shift, &alpha);
|
||||
widget_shell_modifier_info(shift, alpha, &layer, &instant);
|
||||
int icon = 2 * layer + !instant;
|
||||
#ifdef FX9860G
|
||||
dsubimage(118, 58, &img_modifier_states, 9*icon+1, 1, 8, 6,
|
||||
DIMAGE_NONE);
|
||||
#else
|
||||
dsubimage(377, 207, &img_modifier_states, 16*icon, 0, 15, 14,
|
||||
DIMAGE_NONE);
|
||||
#endif
|
||||
dupdate();
|
||||
pe_draw();
|
||||
}
|
||||
|
||||
if(e.type == WIDGET_SHELL_MOD_CHANGED)
|
||||
scene->widget.update = true;
|
||||
pe_scene->widget.update = true;
|
||||
|
||||
if(e.type == WIDGET_SHELL_INPUT) {
|
||||
char *line = (char *)e.data;
|
||||
shell_write_str(line);
|
||||
pyexec_repl_execute(line);
|
||||
free(line);
|
||||
shell_write_char('\r');
|
||||
pe_print_prompt(1);
|
||||
}
|
||||
|
||||
|
@ -304,13 +301,18 @@ int main(int argc, char **argv)
|
|||
char const *path = jfileselect_selected_file(fileselect);
|
||||
char *module = path_to_module(path);
|
||||
if(module) {
|
||||
jscene_show_and_focus(scene, shell);
|
||||
jscene_show_and_focus(pe_scene, pe_shell);
|
||||
jwidget_set_visible(title, show_title_in_shell);
|
||||
|
||||
pe_reset_micropython();
|
||||
shell_write_str("import ");
|
||||
shell_write_str(module);
|
||||
shell_write_str("\r\n");
|
||||
|
||||
char *str = malloc(8 + strlen(module) + 1);
|
||||
if(str) {
|
||||
strcpy(str, "import ");
|
||||
strcat(str, module);
|
||||
pyexec_repl_execute(str);
|
||||
free(str);
|
||||
}
|
||||
free(module);
|
||||
}
|
||||
}
|
||||
|
@ -325,11 +327,11 @@ int main(int argc, char **argv)
|
|||
pe_debug_kmalloc();
|
||||
|
||||
if(key == KEY_F1) {
|
||||
jscene_show_and_focus(scene, fileselect);
|
||||
jscene_show_and_focus(pe_scene, fileselect);
|
||||
jwidget_set_visible(title, true);
|
||||
}
|
||||
if(key == KEY_F2) {
|
||||
jscene_show_and_focus(scene, shell);
|
||||
jscene_show_and_focus(pe_scene, pe_shell);
|
||||
jwidget_set_visible(title, show_title_in_shell);
|
||||
}
|
||||
}
|
||||
|
@ -338,7 +340,7 @@ int main(int argc, char **argv)
|
|||
|
||||
gc_sweep_all();
|
||||
mp_deinit();
|
||||
console_destroy(pe_shell_console);
|
||||
console_destroy(pe_console);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ STATIC bool repl_display_debugging_info = 0;
|
|||
#define EXEC_FLAG_SOURCE_IS_VSTR (1 << 4)
|
||||
#define EXEC_FLAG_SOURCE_IS_FILENAME (1 << 5)
|
||||
#define EXEC_FLAG_SOURCE_IS_READER (1 << 6)
|
||||
#define EXEC_FLAG_SOURCE_IS_STR (1 << 7)
|
||||
|
||||
// parses, compiles and executes the code in the lexer
|
||||
// frees the lexer before returning
|
||||
|
@ -95,6 +96,9 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
|
|||
if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) {
|
||||
const vstr_t *vstr = source;
|
||||
lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0);
|
||||
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_STR) {
|
||||
lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_,
|
||||
source, strlen(source), 0);
|
||||
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) {
|
||||
lex = mp_lexer_new(MP_QSTR__lt_stdin_gt_, *(mp_reader_t *)source);
|
||||
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) {
|
||||
|
@ -184,9 +188,6 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
|
|||
|
||||
#if MICROPY_ENABLE_COMPILER
|
||||
|
||||
|
||||
STATIC int pyexec_raw_repl_process_char(int c);
|
||||
|
||||
void pyexec_event_repl_init(void) {
|
||||
MP_STATE_VM(repl_line) = vstr_new(32);
|
||||
|
||||
|
@ -225,6 +226,21 @@ int pyexec_event_repl_process_char(int c) {
|
|||
return res;
|
||||
}
|
||||
|
||||
int pyexec_repl_execute(char const *line) {
|
||||
pyexec_repl_active = 1;
|
||||
|
||||
int ret = parse_compile_execute(line,
|
||||
MP_PARSE_SINGLE_INPUT,
|
||||
EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL |
|
||||
EXEC_FLAG_SOURCE_IS_STR);
|
||||
if (ret & PYEXEC_FORCED_EXIT) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
pyexec_repl_active = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
MP_REGISTER_ROOT_POINTER(vstr_t * repl_line);
|
||||
|
||||
#endif // MICROPY_ENABLE_COMPILER
|
||||
|
|
|
@ -41,6 +41,7 @@ int pyexec_file_if_exists(const char *filename);
|
|||
int pyexec_frozen_module(const char *name);
|
||||
void pyexec_event_repl_init(void);
|
||||
int pyexec_event_repl_process_char(int c);
|
||||
int pyexec_repl_execute(char const *line);
|
||||
extern uint8_t pyexec_repl_active;
|
||||
|
||||
#if MICROPY_REPL_INFO
|
||||
|
|
|
@ -250,7 +250,7 @@ static bool widget_shell_poly_event(void *s0, jevent e)
|
|||
|
||||
if(ev.key == KEY_EXE) {
|
||||
char *line = console_get_line(s->console, true);
|
||||
console_new_line(s->console);
|
||||
console_newline(s->console);
|
||||
|
||||
jevent e = {
|
||||
.type = WIDGET_SHELL_INPUT,
|
||||
|
@ -262,7 +262,7 @@ static bool widget_shell_poly_event(void *s0, jevent e)
|
|||
|
||||
char c = keymap_translate(ev.key, ev.shift, ev.alpha);
|
||||
if(c != 0) {
|
||||
console_write_at_cursor(s->console, &c, 1);
|
||||
console_write_raw(s->console, &c, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ extern uint16_t WIDGET_SHELL_MOD_CHANGED;
|
|||
extern uint16_t WIDGET_SHELL_INPUT;
|
||||
|
||||
/* Update frequency, ie. cap on the number of shell redraws per second. */
|
||||
#define WIDGET_SHELL_FPS 30
|
||||
#define WIDGET_SHELL_FPS 10
|
||||
|
||||
/* widget_shell_create(): Create a shell widget tied to a console */
|
||||
widget_shell *widget_shell_create(console_t *console, void *parent);
|
||||
|
|
Loading…
Reference in New Issue