Add dirty line segments

This commit is contained in:
Michaël Lemaire 2021-06-28 22:42:42 +02:00
parent 285550e766
commit fa889e00d9
4 changed files with 82 additions and 17 deletions

View file

@ -20,7 +20,7 @@ export class AnsiTerminalDisplay implements Display {
constructor(
private writer: Deno.Writer = Deno.stdout,
private reader: Deno.Reader = Deno.stdin,
reader: Deno.Reader = Deno.stdin,
) {
if (hasRawMode(reader)) {
this.readKeyPresses(reader); // purposefully not awaited

View file

@ -1,4 +1,4 @@
import { BufferDrawing, CharBuffer } from "./base.ts";
import { BufferDrawing, BufferLocation, CharBuffer } from "./base.ts";
import { describe, expect, it } from "./testing.ts";
describe(CharBuffer, () => {
@ -11,6 +11,26 @@ describe(CharBuffer, () => {
expect(buffer.get({ x: 0, y: 0 })).toEqual({ ch: " ", fg: 0, bg: 0 });
expect(buffer.get({ x: 1, y: 1 })).toEqual({ ch: "y", fg: 2, bg: 5 });
});
it("keeps track of dirty lines", async () => {
const buffer = new CharBuffer({ w: 16, h: 3 });
expect(buffer.isDirty({ x: 0, y: 0 })).toBe(false);
buffer.set({ x: 0, y: 0 }, { ch: "x", bg: 0, fg: 0 });
expect(buffer.isDirty({ x: 0, y: 0 })).toBe(true);
expect(buffer.isDirty({ x: 1, y: 0 })).toBe(true);
expect(buffer.isDirty({ x: 2, y: 0 })).toBe(false);
expect(buffer.isDirty({ x: 0, y: 1 })).toBe(false);
let calls: BufferLocation[] = [];
await buffer.forEachDirty(async (at) => {
calls.push(at);
});
expect(calls).toEqual([{ x: 0, y: 0 }, { x: 1, y: 0 }]);
calls = [];
await buffer.forEachDirty(async (at) => {
calls.push(at);
});
expect(calls).toEqual([]);
});
});
describe(BufferDrawing, () => {

63
base.ts
View file

@ -25,19 +25,25 @@ export const SPACE: Char = { ch: " ", bg: 0, fg: 0 } as const;
/**
* Rectangular buffer of displayable characters
*
* All methods in this class do not properly check for
* out-of-bounds coordinates, use BufferDrawing for this
*/
export class CharBuffer {
private chars: Array<Char>;
private dirty: boolean;
private dirty_lines: Uint8Array;
private dirty_seg_length: number;
constructor(private size: BufferSize) {
this.chars = new Array(size.w * size.h).fill(SPACE);
this.dirty = false;
this.dirty_lines = new Uint8Array(size.h).fill(0x00);
this.dirty_seg_length = Math.ceil(size.w / 8);
}
/**
* Get the character buffered at a given at
*
* This does not properly check for out-of-bounds coordinates,
* use BufferDrawing for this
* Get the character buffered at a given location
*/
get(at: BufferLocation): Char {
const i = at.y * this.size.w + at.x;
@ -50,17 +56,60 @@ export class CharBuffer {
/**
* Change the character buffered at a given location
*
* This does not properly check for out-of-bounds coordinates,
* use BufferDrawing for this
*/
set(at: BufferLocation, char: Char): void {
const i = at.y * this.size.w + at.x;
if (i >= 0 && i < this.chars.length) {
// TODO only if changed?
this.chars[i] = char;
this.dirty = true;
this.dirty_lines[at.y] |= 1 << Math.floor(at.x / this.dirty_seg_length);
}
}
/**
* Call a method for each dirty character, then clear the dirty flags
*/
async forEachDirty(
op: (at: BufferLocation, char: Char) => Promise<void>,
): Promise<void> {
if (this.dirty) {
const { w, h } = this.getSize();
for (let y = 0; y < h; y++) {
const line = this.dirty_lines[y];
if (line) {
for (let sx = 0; sx < 8; sx++) {
if (line & 1 << sx) {
const limit = Math.min((sx + 1) * this.dirty_seg_length, w);
for (let x = sx * this.dirty_seg_length; x < limit; x++) {
await op({ x, y }, this.get({ x, y }));
}
}
}
}
}
this.setDirty(false);
}
}
/**
* Set the dirty state for all characters
*/
setDirty(dirty: boolean): void {
this.dirty = dirty;
this.dirty_lines.fill(dirty ? 0xFF : 0x00);
}
/**
* Check if a given location is potentially dirty
*
* This ignores the global dirty flag, only checks dirty line segments
*/
isDirty(at: BufferLocation): boolean {
const seg = Math.floor(at.x / this.dirty_seg_length);
return (this.dirty_lines[at.y] & (1 << seg)) != 0;
}
getSize(): BufferSize {
return this.size;
}

12
ui.ts
View file

@ -80,19 +80,15 @@ export class TextUI {
return;
}
// TODO only dirty chars
const { w, h } = this.screen.getSize();
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
await this.display.setChar({ x, y }, this.screen.get({ x, y }));
}
}
await this.screen.forEachDirty((at, char) =>
this.display.setChar(at, char)
);
}
/**
* Start the event loop, waiting for input
*/
async loop(refresh = 1000): Promise<void> {
async loop(refresh = 100): Promise<void> {
while (!this.quitting) {
for (const key of await this.display.getKeyStrokes()) {
if (!this.config.ignoreCtrlC && key == "ctrl+c") {