Быстрый алгоритм размещения блоков, нужен совет?

Мне нужно эмулировать стратегию размещения окна Fluxbox оконный менеджер.

В качестве приблизительного руководства визуализируйте окна с произвольным размером, заполняющие экран по одному за раз, где грубый размер каждого результата составляет в среднем 80 окон на экране без какого-либо окна, перекрывающего другое.

Если в вашей системе установлены Fluxbox и Xterm, вы можете попробовать xwinmidiarptoy BASH script, чтобы увидеть грубый прототип того, что я хочу. См. Примечание xwinmidiarptoy.txt. Я написал об этом, объясняя, что он делает и как его следует использовать.

Важно отметить, что окна будут закрыты, а пространство, закрывающее ранее занятые окна, снова станет доступным для размещения новых окон.

Алгоритм должен быть Online Algorithm обрабатывать данные "по частям в серийном режиме, т.е. в том порядке, в котором вход подается в алгоритм, без ввода всего ввода с самого начала".

Стратегия размещения окна Fluxbox имеет три бинарных параметра, которые я хочу подражать:

  • Windows строит горизонтальные строки или вертикальные столбцы (потенциально)

  • Windows размещаются слева направо или справа налево

  • Окна размещаются сверху вниз или снизу вверх

Различия между целевым алгоритмом и алгоритмом размещения окна

Единицы координат не являются пикселями. Сетка, в которой будут размещаться блоки, будет 128 x 128 единиц. Кроме того, область размещения может быть дополнительно уменьшена граничной областью, помещенной внутри сетки.

Почему алгоритм является проблемой?

Он должен работать до крайних сроков потока в реальном времени в звуковом приложении.

В настоящий момент меня интересует только быстрый алгоритм, не беспокойтесь о последствиях потоков реального времени и всех препятствий в программировании, которые это приносит.

И хотя алгоритм никогда не будет размещать окно, которое перекрывает другое, пользователь сможет размещать и перемещать определенные типы блоков, будут существовать перекрывающиеся окна. Структура данных, используемая для хранения окон и/или свободного пространства, должна иметь возможность справиться с этим перекрытием.

До сих пор у меня есть два варианта, из которых я создал проиллюстрированные прототипы для:

1) Порт алгоритма размещения Fluxbox в мой код.

Проблема заключается в том, что клиент (моя программа) выходит из звукового сервера (JACK), когда я пытаюсь помещая наихудший сценарий из 256 блоков с использованием алгоритма. Этот алгоритм выполняет более 14000 полных (линейных) сканирований списка блоков, уже размещенных при размещении 256-го окна.

Для демонстрации этого я создал программу под названием text_boxer-0.0.2.tar.bz2, которая принимает текстовый файл в качестве входного и упорядочивает его в ячейках ASCII. Выполните make, чтобы создать его. Немного недружелюбный, используйте --help (или любую другую недопустимую опцию) для списка параметров командной строки. Вы должны указать текстовый файл, используя опцию.

2) Мой альтернативный подход.

Только частично реализованный, этот подход использует структуру данных для каждой области прямоугольного пространства free unused (список окон может быть полностью разделенным и не нужен для тестирования этого алгоритма). Структура данных действует как node в двусвязном списке (с сортировкой вставки), а также содержит координаты верхнего левого угла, ширины и высоты.

Кроме того, каждая структура данных блока также содержит четыре ссылки, которые соединяются с каждым непосредственно смежным (касающимся) блоком на каждой из четырех сторон.

ВАЖНОЕ ПРАВИЛО: Каждый блок может касаться только одного блока на сторону. Это правило относится к алгоритму хранения свободного неиспользуемого пространства и не имеет никакого отношения к тому, сколько фактических окон может касаться друг друга.

Проблема с этим подходом заключается в том, что очень сложный, Я применил простые случаи, когда 1) пространство удалено из одного угла блока, 2) разделение соседних блоков так, чтобы соблюдалось ВАЖНОЕ ПРАВИЛО.

Менее простой случай, когда место, которое нужно удалить, может быть найдено только в столбце или строке ящиков, только частично реализовано - если один из блоков, которые нужно удалить, является точной подгонкой для ширины (то есть столбца) или высота (т.е. строка), тогда возникают проблемы. И даже не упоминайте, что это только проверяет столбцы на одну коробку шириной и строит одну коробку высотой.

Я реализовал этот алгоритм в C - языке, который я использую для этого проекта (я не использовал С++ в течение нескольких лет, и мне неудобно его использовать, сосредоточив все свое внимание на разработке C, это хобби), Реализация - это 700 + строк кода (включая множество пустых строк, линий фигур, комментариев и т.д.). Реализация работает только для стратегии размещения горизонтальных строк + left-right + top-bottom.

Итак, мне нужно добавить какой-то способ сделать эти строки кода +700 для других 7 вариантов стратегии размещения, или мне придется дублировать эти +700 строк кода для остальных семи опций, Ни один из них не привлекателен, во-первых, потому что существующий код достаточно сложный, второй - из-за раздувания.

Алгоритм даже не на том этапе, когда я могу использовать его в сценарии с наихудшим сценарием в реальном времени из-за отсутствия функциональности, поэтому я до сих пор не знаю, действительно ли он работает лучше или хуже первого подхода.

Текущее состояние реализации этого алгоритма C freespace.c. Я использую gcc -O0 -ggdb freespace.c для сборки и запускаю его в формате xterm, чтобы по крайней мере 124 х 60 символов.

Что еще есть?

Я снял и сбрасывал со счетов:

  • Bin Packing: их акцент на оптимальную подгонку не соответствуют требованиям этого алгоритм.

  • Рекурсивное бисекционное размещение: звуки многообещающие, но они предназначены для проектирования схем. Их упор - оптимальная длина провода.

Оба этих, особенно последних, все элементы, которые должны быть размещены/пакеты, известны до начала алгоритма.

Что вы думаете об этом? Как бы вы к нему подошли? На какие еще алгоритмы я должен смотреть? Или даже какие концепции я должен исследовать, видя, как я не изучал информатику/разработку программного обеспечения?

Пожалуйста, задавайте вопросы в комментариях, если необходима дополнительная информация.

Дальнейшие идеи, разработанные с момента запроса этого вопроса

  • Некоторая комбинация моего "альтернативного алгоритма" с пространственным хешмапом для определения того, будет ли размещено большое окно, будет охватывать несколько блоков свободного пространства.

Ответ 1

#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>


#define FSWIDTH 128
#define FSHEIGHT 128


#ifdef USE_64BIT_ARRAY
    #define FSBUFBITS 64
    #define FSBUFWIDTH 2
    typedef uint64_t fsbuf_type;
    #define TRAILING_ZEROS( v ) __builtin_ctzl(( v ))
    #define LEADING_ONES( v )   __builtin_clzl(~( v ))
#else
#ifdef USE_32BIT_ARRAY
    #define FSBUFBITS 32
    #define FSBUFWIDTH 4
    typedef uint32_t fsbuf_type;
    #define TRAILING_ZEROS( v ) __builtin_ctz(( v ))
    #define LEADING_ONES( v )   __builtin_clz(~( v ))
#else
#ifdef USE_16BIT_ARRAY
    #define FSBUFBITS 16
    #define FSBUFWIDTH 8
    typedef uint16_t fsbuf_type;
    #define TRAILING_ZEROS( v ) __builtin_ctz( 0xffff0000 | ( v ))
    #define LEADING_ONES( v )   __builtin_clz(~( v ) << 16)
#else
#ifdef USE_8BIT_ARRAY
    #define FSBUFBITS 8
    #define FSBUFWIDTH 16
    typedef uint8_t fsbuf_type;
    #define TRAILING_ZEROS( v ) __builtin_ctz( 0xffffff00 | ( v ))
    #define LEADING_ONES( v )   __builtin_clz(~( v ) << 24)
#else
    #define FSBUFBITS 1
    #define FSBUFWIDTH 128
    typedef unsigned char fsbuf_type;
    #define TRAILING_ZEROS( v ) (( v ) ? 0 : 1)
    #define LEADING_ONES( v )   (( v ) ? 1 : 0)
#endif
#endif
#endif
#endif


static const fsbuf_type fsbuf_max =   ~(fsbuf_type)0;
static const fsbuf_type fsbuf_high =  (fsbuf_type)1 << (FSBUFBITS - 1);


typedef struct freespacegrid
{
    fsbuf_type buf[FSHEIGHT][FSBUFWIDTH];

    _Bool left_to_right;
    _Bool top_to_bottom;

} freespace;


void freespace_dump(freespace* fs)
{
    int x, y;

    for (y = 0; y < FSHEIGHT; ++y)
    {
        for (x = 0; x < FSBUFWIDTH; ++x)
        {
            fsbuf_type i = FSBUFBITS;
            fsbuf_type b = fs->buf[y][x];

            for(; i != 0; --i, b <<= 1)
                putchar(b & fsbuf_high ? '#' : '/');
/*
            if (x + 1 < FSBUFWIDTH)
                putchar('|');
*/
        }
        putchar('\n');
    }
}


freespace* freespace_new(void)
{
    freespace* fs = malloc(sizeof(*fs));

    if (!fs)
        return 0;

    int y;

    for (y = 0; y < FSHEIGHT; ++y)
    {
        memset(&fs->buf[y][0], 0, sizeof(fsbuf_type) * FSBUFWIDTH);
    }

    fs->left_to_right = true;
    fs->top_to_bottom = true;

    return fs;
}


void freespace_delete(freespace* fs)
{
    if (!fs)
        return;

    free(fs);
}

/* would be private function: */
void fs_set_buffer( fsbuf_type buf[FSHEIGHT][FSBUFWIDTH],
                    unsigned x,
                    unsigned y1,
                    unsigned xoffset,
                    unsigned width,
                    unsigned height)
{
    fsbuf_type v;
    unsigned y;

    for (; width > 0 && x < FSBUFWIDTH; ++x)
    {
        if (width < xoffset)
            v = (((fsbuf_type)1 << width) - 1) << (xoffset - width);
        else if (xoffset < FSBUFBITS)
            v = ((fsbuf_type)1 << xoffset) - 1;
        else
            v = fsbuf_max;

        for (y = y1; y < y1 + height; ++y)
        {
#ifdef FREESPACE_DEBUG
            if (buf[y][x] & v)
                printf("**** over-writing area ****\n");
#endif
            buf[y][x] |= v;
        }

        if (width < xoffset)
            return;

        width -= xoffset;
        xoffset = FSBUFBITS;
    }
}


_Bool freespace_remove(   freespace* fs,
                          unsigned width, unsigned height,
                          int* resultx,   int* resulty)
{
    unsigned x, x1, y;
    unsigned w, h;
    unsigned xoffset, x1offset;
    unsigned tz; /* trailing zeros */

    fsbuf_type* xptr;
    fsbuf_type mask =   0;
    fsbuf_type v;

    _Bool scanning = false;
    _Bool offset = false;

    *resultx = -1;
    *resulty = -1;

    for (y = 0; y < (unsigned) FSHEIGHT - height; ++y)
    {
        scanning = false;
        xptr = &fs->buf[y][0];

        for (x = 0; x < FSBUFWIDTH; ++x, ++xptr)
        {
            if(*xptr == fsbuf_max)
            {
                scanning = false;
                continue;
            }

            if (!scanning)
            {
                scanning = true;
                x1 = x;
                x1offset = xoffset = FSBUFBITS;
                w = width;
            }
retry:
            if (w < xoffset)
                mask = (((fsbuf_type)1 << w) - 1) << (xoffset - w);
            else if (xoffset < FSBUFBITS)
                mask = ((fsbuf_type)1 << xoffset) - 1;
            else
                mask = fsbuf_max;

            offset = false;

            for (h = 0; h < height; ++h)
            {
                v = fs->buf[y + h][x] & mask;

                if (v)
                {
                    tz = TRAILING_ZEROS(v);
                    offset = true;
                    break;
                }
            }

            if (offset)
            {
                if (tz)
                {
                    x1 = x;
                    w = width;
                    x1offset = xoffset = tz;
                    goto retry;
                }
                scanning = false;
            }
            else
            {
                if (w <= xoffset) /***** RESULT! *****/
                {
                    fs_set_buffer(fs->buf, x1, y, x1offset, width, height);
                    *resultx = x1 * FSBUFBITS + (FSBUFBITS - x1offset);
                    *resulty = y;
                    return true;
                }
                w -= xoffset;
                xoffset = FSBUFBITS;
            }
        }
    }
    return false;
}


int main(int argc, char** argv)
{
    int x[1999];
    int y[1999];
    int w[1999];
    int h[1999];

    int i;

    freespace* fs = freespace_new();

    for (i = 0; i < 1999; ++i, ++u)
    {
        w[i] = rand() % 18 + 4;
        h[i] = rand() % 18 + 4;

        freespace_remove(fs, w[i], h[i], &x[i], &y[i]);
/*
        freespace_dump(fs);
        printf("w:%d h:%d x:%d y:%d\n", w[i], h[i], x[i], y[i]);
        if (x[i] == -1)
            printf("not removed space %d\n", i);
        getchar();
*/
    }

    freespace_dump(fs);
    freespace_delete(fs);

    return 0;
}

В приведенном выше коде требуется один из USE_64BIT_ARRAY, USE_32BIT_ARRAY, USE_16BIT_ARRAY, USE_8BIT_ARRAY, который будет определен в противном случае, он вернется к использованию только старшего разряда unsigned char для хранения состояния ячеек сетки.

Функция fs_set_buffer не будет объявлена ​​в заголовке и станет статичной в реализации, когда этот код будет разделен между файлами .h и .c. Будет предоставлена ​​более удобная для пользователя функция, скрывающая детали реализации, для удаления использованного пространства из сетки.

В целом, эта реализация выполняется быстрее без оптимизации, чем мой предыдущий ответ с максимальной оптимизацией (с использованием GCC на 64-битном Gentoo, варианты оптимизации -O0 и -O3 соответственно).

Что касается USE_NNBIT_ARRAY и разных размеров бит, я использовал два разных метода синхронизации кода, которые вызывают 1999 звонки на freespace_remove.

Сроки main() с использованием команды Unix time (и отключения любого выхода в коде), казалось, оправдали мои ожидания - более высокие размеры бит быстрее.

С другой стороны, временные индивидуальные вызовы freespace_remove (с использованием gettimeofday) и сравнение максимального времени, затраченного на вызовы 1999 года, как представляется, указывают на более низкие размеры бит.

Это было протестировано только на 64-битной системе (Intel Dual Core II).

Ответ 2

Я бы рассмотрел некоторую пространственную структуру хэширования. Представьте, что ваше свободное пространство грубо обрезано, назовите их блоками. Когда окна приходят и уходят, они занимают определенные множества смежных прямоугольных блоков. Для каждого блока отслеживайте самый большой неиспользуемый прямоугольник, инцидентный каждому углу, поэтому вам нужно хранить 2 * 4 действительных числа на блок. Для пустого блока прямоугольники в каждом углу имеют размер, равный блоку. Таким образом, блок может быть только "использован" в его углах, и поэтому максимум 4 окна могут сидеть в любом блоке.

Теперь каждый раз, когда вы добавляете окно, вы должны искать прямоугольный набор блоков, для которых будет соответствовать окно, а когда вы это сделаете, обновите свободные размеры угла. Вы должны размер блоков, чтобы горстка (~ 4x4) из них вписывались в типичное окно. Для каждого окна отслеживайте, какие блоки его касаются (вам нужно только отслеживать экстенты), а также какие окна касаются данного блока (не более 4 в этом алгоритме). Существует очевидный компромисс между гранулярностью блоков и количеством работы на вставку/удаление окна.

При удалении окна, перебирайте все блоки, к которым он прикасается, и для каждого блока пересчитывайте свободные угловые размеры (вы знаете, какие окна касаются его). Это быстро, так как внутренний цикл имеет длину не более 4.

Я представляю структуру данных вроде

struct block{
    int free_x[4]; // 0 = top left, 1 = top right,
    int free_y[4]; // 2 = bottom left, 3 = bottom right
    int n_windows; // number of windows that occupy this block
    int window_id[4]; // IDs of windows that occupy this block
};
block blocks[NX][NY];

struct window{
    int id;
    int used_block_x[2]; // 0 = first index of used block,
    int used_block_y[2]; // 1 = last index of used block
};

Edit

Вот изображение:

alt text

Здесь показаны два примера блоков. Цветные точки указывают на углы блока, а стрелки, исходящие из них, указывают на экстенты самого большого прямоугольника с этого угла.

Вы упомянули в редакции, что сетка, на которой будут размещаться ваши окна, уже довольно грубая (127x127), поэтому размеры блоков, вероятно, будут примерно такими же, как 4 ячейки сетки на стороне, что, вероятно, не принесет вам многого, Этот метод подходит, если ваши угловые координаты окна могут принимать множество значений (я думал, что они будут пикселями), но не так много в вашем случае. Вы все еще можете попробовать, так как это просто. Вероятно, вы захотите также сохранить список полностью пустых блоков, чтобы, если в окне будет больше ширины блока, тогда вы сначала посмотрите в этом списке, прежде чем искать подходящее свободное пространство в блочной сетке.

Ответ 3

После некоторых ложных запусков я в конце концов приехал сюда. Здесь было отказано в использовании структур данных для хранения прямоугольных областей свободного пространства. Вместо этого существует 2d-массив с 128 x 128 элементами для достижения того же результата, но с гораздо меньшей сложностью.

Следующая функция сканирует массив для области width * height по размеру. Первая найденная им позиция записывает верхние левые координаты, где resultx и resulty указывают на.

_Bool freespace_remove( freespace* fs,
                        int width,     int height,
                        int* resultx,  int* resulty)
{
    int x = 0;
    int y = 0;
    const int rx = FSWIDTH - width;
    const int by = FSHEIGHT - height;

    *resultx = -1;
    *resulty = -1;

    char* buf[height];

    for (y = 0; y < by; ++y)
    {
        x = 0;
        char* scanx = fs->buf[y];

        while (x < rx)
        {
            while(x < rx && *(scanx + x))
                ++x;

            int w, h;

            for (h = 0; h < height; ++h)
                buf[h] = fs->buf[y + h] + x;

            _Bool usable = true;
            w = 0;

            while (usable && w < width)
            {
                h = 0;
                while (usable && h < height)
                    if (*(buf[h++] + w))
                        usable = false;
                ++w;
            }

            if (usable)
            {
                for (w = 0; w < width; ++w)
                    for (h = 0; h < height; ++h)
                        *(buf[h] + w) = 1;

                *resultx = x;
                *resulty = y;
                return true;
            }

            x += w;
        }
    }

    return false;
}

2d-массив инициализируется нулем. Любые области в массиве, где используется пространство, равны 1. Эта структура и функция будут работать независимо от фактического списка окон, занимающих области, отмеченные 1 символом.

Преимущества этого метода - его простота. Он использует только одну структуру данных - массив. Функция короткая и не должна быть слишком сложной для адаптации к остальным вариантам размещения (здесь она обрабатывает только Row Smart + Left to Right + Top to Bottom).

Мои начальные тесты также выглядят многообещающими на скорости. Хотя я не думаю, что это было бы удобно для оконного менеджера, размещающего окна, например, с рабочим столом 1600 x 1200 с точностью до пикселя, для моих целей я считаю, что это будет намного лучше, чем любой из предыдущих методов, которые у меня есть попробовал.

Компилируемый тестовый код: http://jwm-art.net/art/text/freespace_grid.c
(в Linux я использую gcc -ggdb -O0 freespace_grid.c для компиляции)