diff --git a/kitty/data-types.c b/kitty/data-types.c index 85f64925f..a64b87d8e 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -10,6 +10,7 @@ extern int init_LineBuf(PyObject *); extern int init_Cursor(PyObject *); extern int init_Line(PyObject *); extern int init_ColorProfile(PyObject *); +extern int init_SpriteMap(PyObject *); extern PyObject* create_256_color_table(); #include "gl.h" @@ -39,6 +40,7 @@ PyInit_fast_data_types(void) { if (!init_Line(m)) return NULL; if (!init_Cursor(m)) return NULL; if (!init_ColorProfile(m)) return NULL; + if (!init_SpriteMap(m)) return NULL; if (!add_module_gl_constants(m)) return NULL; PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT); PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); diff --git a/kitty/data-types.h b/kitty/data-types.h index 91757776b..ea266d001 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -157,6 +157,26 @@ typedef struct { } ColorProfile; +typedef struct SpritePosition SpritePosition; +struct SpritePosition { + SpritePosition *next; + unsigned int x, y, z; + char_type ch; + combining_type cc; + bool is_second; + bool filled; + bool rendered; +}; + +typedef struct { + PyObject_HEAD + + size_t max_array_len, max_texture_size, xnum, ynum, max_y; + unsigned int x, y, z; + SpritePosition cache[1024]; + +} SpriteMap; + Line* alloc_line(); Cursor* alloc_cursor(); diff --git a/kitty/sprites.c b/kitty/sprites.c new file mode 100644 index 000000000..c36d20b9c --- /dev/null +++ b/kitty/sprites.c @@ -0,0 +1,149 @@ +/* + * sprites.c + * Copyright (C) 2016 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "data-types.h" + +static PyObject* +new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { + SpriteMap *self; + unsigned long mlen, msz; + if (!PyArg_ParseTuple(args, "kk", &msz, &mlen)) return NULL; + + self = (SpriteMap *)type->tp_alloc(type, 0); + if (self != NULL) { + self->max_array_len = mlen; + self->max_texture_size = msz; + } + return (PyObject*) self; +} + +static void +dealloc(SpriteMap* self) { + SpritePosition *s, *t; + for (size_t i = 0; i < sizeof(self->cache)/sizeof(self->cache[0]); i++) { + s = &(self->cache[i]); + s = s->next; + while (s) { + t = s; + s = s->next; + PyMem_Free(t); + } + } + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject* +layout(SpriteMap *self, PyObject *args) { +#define layout_doc "layout(cell_width, cell_height) -> Invalidate the cache and prepare it for new cell size" + unsigned long cell_width, cell_height; + if (!PyArg_ParseTuple(args, "kk", &cell_width, &cell_height)) return NULL; + self->xnum = MAX(1, self->max_texture_size / cell_width); + self->max_y = MAX(1, self->max_texture_size / cell_height); + + for (size_t i = 0; i < sizeof(self->cache)/sizeof(self->cache[0]); i++) { + SpritePosition *s = &(self->cache[i]); + do { + s->filled = false; + s->is_second = false; + s->rendered = false; + s->ch = 0; s->cc = 0; + s->x = 0; s->y = 0; s->z = 0; + s = s->next; + } while (s != NULL); + } + Py_RETURN_NONE; +} + +static void +increment(SpriteMap *self, int *error) { + self->x++; + if (self->x >= self->xnum) { + self->x = 0; self->y++; + self->ynum = MIN(MAX(self->ynum, self->y + 1), self->max_y); + if (self->y >= self->max_y) { + self->y = 0; self->z++; + if (self->z >= self->max_array_len) *error = 2; + } + } +} + +static SpritePosition* +sprite_position_for(SpriteMap *self, char_type ch, combining_type cc, bool is_second, int *error) { + char_type attrs = ch >> ATTRS_SHIFT, pos_char; + uint8_t bold = (attrs >> BOLD_SHIFT) & 1, italic = (attrs >> ITALIC_SHIFT) & 1; + size_t idx = (ch & 0xff) | (bold << 8) | (italic << 9); + attrs = bold << BOLD_SHIFT | italic << ITALIC_SHIFT; + pos_char = (ch & CHAR_MASK) | (attrs << ATTRS_SHIFT); + SpritePosition *s = &(self->cache[idx]); + while(true) { + if (s->filled) { + if (s->ch == pos_char && s->cc == cc && s->is_second == is_second) return s; // Cache hit + } else { + break; + } + if (!s->next) { + s->next = PyMem_Calloc(1, sizeof(SpritePosition)); + if (s->next == NULL) { *error = 1; return NULL; } + } + s = s->next; + } + s->ch = pos_char; + s->cc = cc; + s->is_second = is_second; + s->filled = true; + s->x = self->x; s->y = self->y; s->z = self->z; + increment(self, error); + return s; +} + +static void set_sprite_error(int error) { + switch(error) { + case 1: + PyErr_NoMemory(); break; + case 2: + PyErr_SetString(PyExc_RuntimeError, "Out of texture space for sprites"); break; + default: + PyErr_SetString(PyExc_RuntimeError, "Unknown error occurred while allocating sprites"); break; + } +} + +static PyObject* +position_for(SpriteMap *self, PyObject *args) { +#define position_for_doc "position_for(ch, cc, is_second) -> x, y, z the sprite position for the specified text" + unsigned long ch = 0; + unsigned long long cc = 0; + int is_second = 0, error = 0; + if (!PyArg_ParseTuple(args, "|kKp", &ch, &cc, &is_second)) return NULL; + SpritePosition *pos = sprite_position_for(self, ch, cc, is_second, &error); + if (pos == NULL) {set_sprite_error(error); return NULL; } + return Py_BuildValue("III", pos->x, pos->y, pos->z); +} +// Boilerplate {{{ + + +static PyMethodDef methods[] = { + METHOD(layout, METH_VARARGS) + METHOD(position_for, METH_VARARGS) + {NULL} /* Sentinel */ +}; + + +static PyTypeObject SpriteMap_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "fast_data_types.SpriteMap", + .tp_basicsize = sizeof(SpriteMap), + .tp_dealloc = (destructor)dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "SpriteMap", + .tp_methods = methods, + .tp_new = new, +}; + +INIT_TYPE(SpriteMap) +// }}} + + diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index adf7bb8e3..79a4de046 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -8,7 +8,7 @@ from . import BaseTest, filled_line_buf, filled_cursor from kitty.config import build_ansi_color_table, defaults from kitty.utils import is_simple_string, wcwidth, sanitize_title -from kitty.fast_data_types import LineBuf, Cursor as C, REVERSE, ColorProfile +from kitty.fast_data_types import LineBuf, Cursor as C, REVERSE, ColorProfile, SpriteMap def create_lbuf(*lines): @@ -274,3 +274,16 @@ class TestDataTypes(BaseTest): col = getattr(defaults, 'color{}'.format(i)) self.assertEqual(c.ansi_color(30 + i), col[0] << 16 | col[1] << 8 | col[2]) self.ae(c.color_256(255), 0xeeeeee) + + def test_sprite_map(self): + s = SpriteMap(10, 2) + s.layout(5, 5) + self.ae(s.position_for(0), (0, 0, 0)) + self.ae(s.position_for(1), (1, 0, 0)) + self.ae(s.position_for(2), (0, 1, 0)) + self.ae(s.position_for(3), (1, 1, 0)) + self.ae(s.position_for(4), (0, 0, 1)) + self.ae(s.position_for(5), (1, 0, 1)) + self.ae(s.position_for(0, 1), (0, 1, 1)) + self.ae(s.position_for(0, 2), (1, 1, 1)) + self.ae(s.position_for(0, 2), (1, 1, 1)) diff --git a/setup.py b/setup.py index 2ea6d583f..f7c88f492 100644 --- a/setup.py +++ b/setup.py @@ -85,7 +85,10 @@ def main(): args = option_parser().parse_args() init_env(args.debug) if args.action == 'build': - compile_c_extension('kitty/fast_data_types', 'kitty/line.c', 'kitty/data-types.c', 'kitty/line-buf.c', 'kitty/cursor.c', 'kitty/colors.c') + compile_c_extension( + 'kitty/fast_data_types', 'kitty/line.c', 'kitty/data-types.c', + 'kitty/line-buf.c', 'kitty/cursor.c', 'kitty/colors.c', + 'kitty/sprites.c') elif args.action == 'test': os.execlp(sys.executable, sys.executable, os.path.join(base, 'test.py'))