diff --git a/docs/conf.py b/docs/conf.py index e75c34d34..952d54441 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,7 +42,8 @@ extensions = [ 'sphinx_sitemap', 'lv_example', 'sphinx_rtd_dark_mode', - 'link_roles' + 'link_roles', + 'sphinxcontrib.mermaid' ] default_dark_mode = False diff --git a/docs/libs/fs.rst b/docs/libs/fs.rst index a13506f29..73937468c 100644 --- a/docs/libs/fs.rst +++ b/docs/libs/fs.rst @@ -34,7 +34,7 @@ The work directory can be set with ``LV_FS_..._PATH``. E.g. ``"/home/joe/projects/"`` The actual file/directory paths will be appended to it. -Cached reading is also supported if ``LV_FS_..._CACHE_SIZE`` is set to +:ref:`Cached reading ` is also supported if ``LV_FS_..._CACHE_SIZE`` is set to not ``0`` value. :cpp:func:`lv_fs_read` caches this size of data to lower the number of actual reads from the storage. diff --git a/docs/overview/fs.rst b/docs/overview/fs.rst index bb7f019e9..c470597d3 100644 --- a/docs/overview/fs.rst +++ b/docs/overview/fs.rst @@ -155,6 +155,95 @@ To use files in image widgets the following callbacks are required: - seek - tell +.. _overview_file_system_cache: + +Optional file buffering/caching +******************************* + +Files will buffer their reads if the corresponding ``LV_FS_*_CACHE_SIZE`` +config option is set to a value greater than zero. Each open file will +buffer up to that many bytes to reduce the number of FS driver calls. + +Generally speaking, file buffering can be optimized for different kinds +of access patterns. The one implemented here is optimal for reading large +files in chunks, which is what the image decoder does. +It has the potential to call the driver's ``read`` fewer +times than ``lv_fs_read`` is called. In the best case where the cache size is +\>= the size of the file, ``read`` will only be called once. This strategy is good +for linear reading of large files but less helpful for short random reads across a file bigger than the buffer +since data will be buffered that will be discarded after the next seek and read. +The cache should be sufficiently large or disabled in that case. Another case where the cache should be disabled +is if the file contents are expected to change by an external factor like with special OS files. + +The implementation is documented below. Note that the FS functions make calls +to other driver FS functions when the cache is enabled. i.e., ``lv_fs_read`` may call the driver's ``seek`` +so the driver needs to implement more callbacks when the cache is enabled. + +``lv_fs_read`` :sub:`(behavior when the cache is enabled)` +------------------------------------------------- + +.. mermaid:: + :zoom: + + %%{init: {'theme':'neutral'}}%% + flowchart LR + A["call lv_fs_read and + the cache is enabled"] --> B{{"is there cached data + at the file position?"}} + B -->|yes| C{{"does the cache have + all required bytes available?"}} + C -->|yes| D["copy all required bytes from + the cache to the destination + buffer"] + C -->|no| F["copy the available + required bytes + until the end of the cache + into the destination buffer"] + --> G["seek the real file to the end + of what the cache had available"] + --> H{{"is the number of remaining bytes + larger than the size of the whole cache?"}} + H -->|yes| I["read the remaining bytes + from the real file to the + destination buffer"] + H -->|no| J["eagerly read the real file + to fill the whole cache + or as many bytes as the + read call can"] + --> O["copy the required bytes + to the destination buffer"] + B -->|no| K["seek the real file to + the file position"] + --> L{{"is the number of required + bytes greater than the + size of the entire cache?"}} + L -->|yes| M["read the real file to + the destination buffer"] + L -->|no| N["eagerly read the real file + to fill the whole cache + or as many bytes as the + read call can"] + --> P["copy the required bytes + to the destination buffer"] + +``lv_fs_write`` :sub:`(behavior when the cache is enabled)` +-------------------------------------------------- + +The part of the cache that coincides with the written content +will be updated to reflect the written content. + +``lv_fs_seek`` :sub:`(behavior when the cache is enabled)` +------------------------------------------------- + +The driver's ``seek`` will not actually be called unless the ``whence`` +is ``LV_FS_SEEK_END``, in which case ``seek`` and ``tell`` will be called +to determine where the end of the file is. + +``lv_fs_tell`` :sub:`(behavior when the cache is enabled)` +------------------------------------------------- + +The driver's ``tell`` will not actually be called. + .. _overview_file_system_api: API diff --git a/docs/requirements.txt b/docs/requirements.txt index 0d5074b8d..58d5d08c0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -10,5 +10,6 @@ sphinxcontrib-htmlhelp sphinxcontrib-jsmath sphinxcontrib-qthelp sphinxcontrib-serializinghtml +sphinxcontrib-mermaid sphinx-rtd-dark-mode typing-extensions diff --git a/src/misc/lv_fs.c b/src/misc/lv_fs.c index 2a326fcc9..c7079f521 100644 --- a/src/misc/lv_fs.c +++ b/src/misc/lv_fs.c @@ -27,6 +27,9 @@ * STATIC PROTOTYPES **********************/ static const char * lv_fs_get_real_path(const char * path); +static lv_fs_res_t lv_fs_read_cached(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br); +static lv_fs_res_t lv_fs_write_cached(lv_fs_file_t * file_p, const void * buf, uint32_t btw, uint32_t * bw); +static lv_fs_res_t lv_fs_seek_cached(lv_fs_file_t * file_p, uint32_t pos, lv_fs_whence_t whence); /********************** * STATIC VARIABLES @@ -171,100 +174,17 @@ lv_fs_res_t lv_fs_close(lv_fs_file_t * file_p) return res; } -static lv_fs_res_t lv_fs_read_cached(lv_fs_file_t * file_p, char * buf, uint32_t btr, uint32_t * br) -{ - LV_PROFILER_BEGIN; - - lv_fs_res_t res = LV_FS_RES_OK; - uint32_t file_position = file_p->cache->file_position; - uint32_t start = file_p->cache->start; - uint32_t end = file_p->cache->end; - char * buffer = file_p->cache->buffer; - uint32_t buffer_size = file_p->drv->cache_size; - - if(start <= file_position && file_position <= end) { - /* Data can be read from cache buffer */ - uint32_t buffer_remaining_length = (uint32_t)end - file_position + 1; - uint32_t buffer_offset = (end - start) - buffer_remaining_length + 1; - - /* Do not allow reading beyond the actual memory block for memory-mapped files */ - if(file_p->drv->cache_size == LV_FS_CACHE_FROM_BUFFER) { - if(btr > buffer_remaining_length) - btr = buffer_remaining_length - 1; - } - - if(btr <= buffer_remaining_length) { - /*Data is in cache buffer, and buffer end not reached, no need to read from FS*/ - lv_memcpy(buf, buffer + buffer_offset, btr); - *br = btr; - } - else { - /*First part of data is in cache buffer, but we need to read rest of data from FS*/ - lv_memcpy(buf, buffer + buffer_offset, buffer_remaining_length); - - file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->end + 1, - LV_FS_SEEK_SET); - - uint32_t bytes_read_to_buffer = 0; - if(btr - buffer_remaining_length > buffer_size) { - /*If remaining data chuck is bigger than buffer size, then do not use cache, instead read it directly from FS*/ - res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)(buf + buffer_remaining_length), - btr - buffer_remaining_length, &bytes_read_to_buffer); - } - else { - /*If remaining data chunk is smaller than buffer size, then read into cache buffer*/ - res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buffer, buffer_size, &bytes_read_to_buffer); - file_p->cache->start = file_p->cache->end + 1; - file_p->cache->end = file_p->cache->start + bytes_read_to_buffer - 1; - - uint16_t data_chunk_remaining = LV_MIN(btr - buffer_remaining_length, bytes_read_to_buffer); - lv_memcpy(buf + buffer_remaining_length, buffer, data_chunk_remaining); - } - *br = LV_MIN(buffer_remaining_length + bytes_read_to_buffer, btr); - } - } - else { - file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, - LV_FS_SEEK_SET); - - /*Data is not in cache buffer*/ - if(btr > buffer_size) { - /*If bigger data is requested, then do not use cache, instead read it directly*/ - res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buf, btr, br); - } - else { - /*If small data is requested, then read from FS into cache buffer*/ - if(buffer == NULL) { - file_p->cache->buffer = lv_malloc(buffer_size); - LV_ASSERT_MALLOC(file_p->cache->buffer); - buffer = file_p->cache->buffer; - } - - uint32_t bytes_read_to_buffer = 0; - res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buffer, buffer_size, &bytes_read_to_buffer); - file_p->cache->start = file_position; - file_p->cache->end = file_p->cache->start + bytes_read_to_buffer - 1; - - *br = LV_MIN(btr, bytes_read_to_buffer); - lv_memcpy(buf, buffer, *br); - - } - } - - if(res == LV_FS_RES_OK) { - file_p->cache->file_position += *br; - } - - LV_PROFILER_END; - - return res; -} - lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br) { if(br != NULL) *br = 0; if(file_p->drv == NULL) return LV_FS_RES_INV_PARAM; - if(file_p->drv->read_cb == NULL) return LV_FS_RES_NOT_IMP; + + if(file_p->drv->cache_size) { + if(file_p->drv->read_cb == NULL || file_p->drv->seek_cb == NULL) return LV_FS_RES_NOT_IMP; + } + else { + if(file_p->drv->read_cb == NULL) return LV_FS_RES_NOT_IMP; + } LV_PROFILER_BEGIN; @@ -272,7 +192,7 @@ lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t lv_fs_res_t res; if(file_p->drv->cache_size) { - res = lv_fs_read_cached(file_p, (char *)buf, btr, &br_tmp); + res = lv_fs_read_cached(file_p, buf, btr, &br_tmp); } else { res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buf, btr, &br_tmp); @@ -293,30 +213,25 @@ lv_fs_res_t lv_fs_write(lv_fs_file_t * file_p, const void * buf, uint32_t btw, u return LV_FS_RES_INV_PARAM; } - if(file_p->drv->write_cb == NULL) { - return LV_FS_RES_NOT_IMP; + if(file_p->drv->cache_size) { + if(file_p->drv->write_cb == NULL || file_p->drv->seek_cb == NULL) return LV_FS_RES_NOT_IMP; + } + else { + if(file_p->drv->write_cb == NULL) return LV_FS_RES_NOT_IMP; } LV_PROFILER_BEGIN; - lv_fs_res_t res = LV_FS_RES_OK; - - /*Need to do FS seek before writing data to FS*/ - if(file_p->drv->cache_size) { - res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, LV_FS_SEEK_SET); - if(res != LV_FS_RES_OK) { - LV_PROFILER_END; - return res; - } - } - + lv_fs_res_t res; uint32_t bw_tmp = 0; - res = file_p->drv->write_cb(file_p->drv, file_p->file_d, buf, btw, &bw_tmp); + if(file_p->drv->cache_size) { + res = lv_fs_write_cached(file_p, buf, btw, &bw_tmp); + } + else { + res = file_p->drv->write_cb(file_p->drv, file_p->file_d, buf, btw, &bw_tmp); + } if(bw != NULL) *bw = bw_tmp; - if(file_p->drv->cache_size && res == LV_FS_RES_OK) - file_p->cache->file_position += bw_tmp; - LV_PROFILER_END; return res; @@ -328,49 +243,18 @@ lv_fs_res_t lv_fs_seek(lv_fs_file_t * file_p, uint32_t pos, lv_fs_whence_t whenc return LV_FS_RES_INV_PARAM; } - if(file_p->drv->seek_cb == NULL) { - return LV_FS_RES_NOT_IMP; + if(file_p->drv->cache_size) { + if(file_p->drv->seek_cb == NULL || file_p->drv->tell_cb == NULL) return LV_FS_RES_NOT_IMP; + } + else { + if(file_p->drv->seek_cb == NULL) return LV_FS_RES_NOT_IMP; } LV_PROFILER_BEGIN; - lv_fs_res_t res = LV_FS_RES_OK; + lv_fs_res_t res; if(file_p->drv->cache_size) { - switch(whence) { - case LV_FS_SEEK_SET: { - file_p->cache->file_position = pos; - - /*FS seek if new position is outside cache buffer*/ - if(file_p->cache->file_position < file_p->cache->start || file_p->cache->file_position > file_p->cache->end) { - res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, LV_FS_SEEK_SET); - } - - break; - } - case LV_FS_SEEK_CUR: { - file_p->cache->file_position += pos; - - /*FS seek if new position is outside cache buffer*/ - if(file_p->cache->file_position < file_p->cache->start || file_p->cache->file_position > file_p->cache->end) { - res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, LV_FS_SEEK_SET); - } - - break; - } - case LV_FS_SEEK_END: { - /*Because we don't know the file size, we do a little trick: do a FS seek, then get the new file position from FS*/ - res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos, whence); - if(res == LV_FS_RES_OK) { - uint32_t tmp_position; - res = file_p->drv->tell_cb(file_p->drv, file_p->file_d, &tmp_position); - - if(res == LV_FS_RES_OK) { - file_p->cache->file_position = tmp_position; - } - } - break; - } - } + res = lv_fs_seek_cached(file_p, pos, whence); } else { res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos, whence); @@ -388,7 +272,7 @@ lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos) return LV_FS_RES_INV_PARAM; } - if(file_p->drv->tell_cb == NULL) { + if(file_p->drv->cache_size == 0 && file_p->drv->tell_cb == NULL) { *pos = 0; return LV_FS_RES_NOT_IMP; } @@ -621,3 +505,162 @@ static const char * lv_fs_get_real_path(const char * path) return path; } + +static lv_fs_res_t lv_fs_read_cached(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br) +{ + lv_fs_res_t res = LV_FS_RES_OK; + uint32_t file_position = file_p->cache->file_position; + uint32_t start = file_p->cache->start; + uint32_t end = file_p->cache->end; + char * buffer = file_p->cache->buffer; + uint32_t buffer_size = file_p->drv->cache_size; + + if(start <= file_position && file_position <= end) { + /* Data can be read from cache buffer */ + uint32_t buffer_remaining_length = (uint32_t)end - file_position + 1; + uint32_t buffer_offset = (end - start) - buffer_remaining_length + 1; + + /* Do not allow reading beyond the actual memory block for memory-mapped files */ + if(file_p->drv->cache_size == LV_FS_CACHE_FROM_BUFFER) { + if(btr > buffer_remaining_length) + btr = buffer_remaining_length - 1; + } + + if(btr <= buffer_remaining_length) { + /*Data is in cache buffer, and buffer end not reached, no need to read from FS*/ + lv_memcpy(buf, buffer + buffer_offset, btr); + *br = btr; + } + else { + /*First part of data is in cache buffer, but we need to read rest of data from FS*/ + lv_memcpy(buf, buffer + buffer_offset, buffer_remaining_length); + + file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->end + 1, + LV_FS_SEEK_SET); + + uint32_t bytes_read_to_buffer = 0; + if(btr - buffer_remaining_length > buffer_size) { + /*If remaining data chuck is bigger than buffer size, then do not use cache, instead read it directly from FS*/ + res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (char *)buf + buffer_remaining_length, + btr - buffer_remaining_length, &bytes_read_to_buffer); + } + else { + /*If remaining data chunk is smaller than buffer size, then read into cache buffer*/ + res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buffer, buffer_size, &bytes_read_to_buffer); + file_p->cache->start = file_p->cache->end + 1; + file_p->cache->end = file_p->cache->start + bytes_read_to_buffer - 1; + + uint16_t data_chunk_remaining = LV_MIN(btr - buffer_remaining_length, bytes_read_to_buffer); + lv_memcpy((char *)buf + buffer_remaining_length, buffer, data_chunk_remaining); + } + *br = LV_MIN(buffer_remaining_length + bytes_read_to_buffer, btr); + } + } + else { + file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, + LV_FS_SEEK_SET); + + /*Data is not in cache buffer*/ + if(btr > buffer_size) { + /*If bigger data is requested, then do not use cache, instead read it directly*/ + res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buf, btr, br); + } + else { + /*If small data is requested, then read from FS into cache buffer*/ + if(buffer == NULL) { + file_p->cache->buffer = lv_malloc(buffer_size); + LV_ASSERT_MALLOC(file_p->cache->buffer); + buffer = file_p->cache->buffer; + } + + uint32_t bytes_read_to_buffer = 0; + res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buffer, buffer_size, &bytes_read_to_buffer); + file_p->cache->start = file_position; + file_p->cache->end = file_p->cache->start + bytes_read_to_buffer - 1; + + *br = LV_MIN(btr, bytes_read_to_buffer); + lv_memcpy(buf, buffer, *br); + + } + } + + if(res == LV_FS_RES_OK) { + file_p->cache->file_position += *br; + } + + return res; +} + +static lv_fs_res_t lv_fs_write_cached(lv_fs_file_t * file_p, const void * buf, uint32_t btw, uint32_t * bw) +{ + lv_fs_res_t res = LV_FS_RES_OK; + + /*Need to do FS seek before writing data to FS*/ + res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, LV_FS_SEEK_SET); + if(res != LV_FS_RES_OK) return res; + + res = file_p->drv->write_cb(file_p->drv, file_p->file_d, buf, btw, bw); + if(res != LV_FS_RES_OK) return res; + + if(file_p->cache->end >= file_p->cache->start) { + uint32_t start_position = file_p->cache->file_position; + uint32_t end_position = file_p->cache->file_position + *bw - 1; + char * cache_buffer = file_p->cache->buffer; + const char * write_buffer = buf; + + if(start_position <= file_p->cache->start && end_position >= file_p->cache->end) { + lv_memcpy(cache_buffer, + write_buffer + (file_p->cache->start - start_position), + file_p->cache->end + 1 - file_p->cache->start); + } + else if(start_position >= file_p->cache->start && end_position <= file_p->cache->end) { + lv_memcpy(cache_buffer + (start_position - file_p->cache->start), + write_buffer, + end_position + 1 - start_position); + } + else if(end_position >= file_p->cache->start && end_position <= file_p->cache->end) { + lv_memcpy(cache_buffer, + write_buffer + (file_p->cache->start - start_position), + end_position + 1 - file_p->cache->start); + } + else if(start_position >= file_p->cache->start && start_position <= file_p->cache->end) { + lv_memcpy(cache_buffer + (start_position - file_p->cache->start), + write_buffer, + file_p->cache->end + 1 - start_position); + } + } + + file_p->cache->file_position += *bw; + + return res; +} + +static lv_fs_res_t lv_fs_seek_cached(lv_fs_file_t * file_p, uint32_t pos, lv_fs_whence_t whence) +{ + lv_fs_res_t res = LV_FS_RES_OK; + switch(whence) { + case LV_FS_SEEK_SET: { + file_p->cache->file_position = pos; + break; + } + case LV_FS_SEEK_CUR: { + file_p->cache->file_position += pos; + break; + } + case LV_FS_SEEK_END: { + /*Because we don't know the file size, we do a little trick: do a FS seek, then get the new file position from FS*/ + res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos, whence); + if(res == LV_FS_RES_OK) { + uint32_t tmp_position; + res = file_p->drv->tell_cb(file_p->drv, file_p->file_d, &tmp_position); + + if(res == LV_FS_RES_OK) { + file_p->cache->file_position = tmp_position; + } + } + break; + } + } + + return res; +} diff --git a/tests/src/test_cases/test_fs.c b/tests/src/test_cases/test_fs.c index 237412fab..8e27fe502 100644 --- a/tests/src/test_cases/test_fs.c +++ b/tests/src/test_cases/test_fs.c @@ -178,4 +178,99 @@ static void read_random_drv(char drv_letter, uint32_t cache_size) drv->cache_size = original_cache_size; } +void test_write_read_random(void) +{ + lv_fs_drv_t * drv = lv_fs_get_drv('A'); + uint32_t original_cache_size = drv->cache_size; + drv->cache_size = 7; + + /* create the file and reopen for read+write. stdio "rb+" mode requires the file to exist */ + lv_fs_res_t res; + lv_fs_file_t f; + res = lv_fs_open(&f, "A:fs_write_read_random.bin", LV_FS_MODE_WR); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + res = lv_fs_close(&f); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + res = lv_fs_open(&f, "A:fs_write_read_random.bin", LV_FS_MODE_WR | LV_FS_MODE_RD); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + + uint8_t buf1000[1000]; + for(uint32_t i = 0; i < 1000; i++) buf1000[i] = i; + res = lv_fs_write(&f, buf1000, 1000, NULL); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + + /** + * {{{action, amount}, ... x20 actions per combo}, ... x100 combos} + * actions are read (0), write (1), seek (2) + * read and write amount ranges from 1 to 20. seek amount ranges from -20 to 20. + * seeks occur as frequently as reads+writes combined. + */ + // *INDENT-OFF* + static const int8_t actions[100][20][2] = { + {{2, -20}, {2, 17}, {0, 20}, {2, 19}, {1, 18}, {1, 4}, {1, 13}, {2, -7}, {1, 6}, {1, 19}, {2, 3}, {1, 12}, {2, 12}, {2, -2}, {2, -20}, {2, -17}, {1, 15}, {2, -17}, {0, 18}, {2, 14}}, {{2, 19}, {2, 17}, {2, -12}, {2, -5}, {2, -1}, {2, -17}, {1, 4}, {2, 19}, {2, 3}, {2, 1}, {2, 10}, {2, 18}, {0, 10}, {2, 8}, {2, 17}, {2, 20}, {0, 5}, {0, 14}, {0, 6}, {1, 8}}, {{0, 1}, {2, -20}, {2, -12}, {1, 9}, {0, 9}, {2, 19}, {1, 8}, {0, 13}, {0, 5}, {2, -7}, {2, -14}, {0, 8}, {2, 13}, {1, 5}, {0, 3}, {0, 16}, {2, 19}, {0, 4}, {1, 6}, {2, -4}}, {{0, 1}, {2, 12}, {1, 7}, {1, 17}, {1, 19}, {0, 8}, {1, 1}, {2, 9}, {2, -14}, {2, 14}, {2, -17}, {2, 11}, {1, 4}, {2, 19}, {2, -18}, {1, 12}, {2, -14}, {0, 9}, {1, 7}, {2, 17}}, {{2, -6}, {0, 13}, {1, 15}, {1, 8}, {2, -1}, {2, 15}, {2, 3}, {2, -16}, {2, -11}, {2, 14}, {2, -19}, {2, 6}, {1, 10}, {1, 6}, {1, 12}, {2, 12}, {0, 10}, {2, 10}, {2, -4}, {2, 5}}, {{2, -6}, {1, 2}, {1, 11}, {1, 19}, {2, 2}, {2, 20}, {2, -19}, {2, -7}, {0, 13}, {2, 4}, {1, 6}, {0, 9}, {0, 6}, {2, -1}, {2, -5}, {2, 14}, {2, 15}, {0, 4}, {0, 6}, {0, 10}}, {{1, 17}, {1, 17}, {0, 15}, {0, 3}, {1, 8}, {2, -9}, {1, 13}, {0, 20}, {0, 3}, {1, 17}, {2, -8}, {2, -1}, {1, 13}, {0, 13}, {0, 8}, {1, 1}, {2, -4}, {2, 13}, {2, 17}, {2, -14}}, {{1, 17}, {1, 13}, {1, 17}, {1, 5}, {1, 3}, {0, 20}, {2, 12}, {0, 13}, {2, 5}, {2, -10}, {2, 10}, {0, 6}, {2, 18}, {0, 17}, {2, 2}, {1, 5}, {2, 16}, {2, -10}, {0, 2}, {2, -9}}, {{2, 7}, {2, -2}, {1, 14}, {0, 15}, {1, 2}, {2, 6}, {2, 11}, {1, 2}, {0, 12}, {2, 9}, {0, 17}, {0, 15}, {2, -19}, {0, 11}, {2, 3}, {2, 15}, {2, 19}, {2, -16}, {1, 6}, {1, 15}}, {{2, 12}, {1, 2}, {2, -4}, {2, -20}, {0, 18}, {2, -16}, {1, 15}, {2, -9}, {2, -7}, {1, 1}, {0, 7}, {0, 18}, {1, 18}, {0, 2}, {2, 17}, {1, 2}, {1, 8}, {1, 18}, {2, -7}, {0, 2}}, + {{2, 19}, {2, 17}, {2, -7}, {0, 17}, {0, 18}, {1, 4}, {1, 1}, {0, 3}, {2, 4}, {2, -4}, {1, 20}, {0, 1}, {2, 20}, {0, 6}, {2, 10}, {0, 19}, {2, -1}, {0, 7}, {0, 16}, {1, 13}}, {{2, 10}, {2, 13}, {2, 15}, {2, 3}, {2, 12}, {0, 14}, {2, 6}, {2, 14}, {2, -13}, {2, 18}, {0, 19}, {2, -8}, {2, -9}, {0, 12}, {2, -20}, {2, -6}, {2, 15}, {2, 18}, {1, 18}, {1, 10}}, {{0, 15}, {2, -6}, {2, 17}, {1, 7}, {0, 9}, {0, 11}, {0, 12}, {0, 20}, {2, -1}, {2, -8}, {0, 13}, {2, 19}, {1, 3}, {0, 18}, {1, 13}, {2, 12}, {2, 13}, {1, 5}, {2, -14}, {2, 18}}, {{0, 6}, {1, 4}, {1, 6}, {0, 2}, {2, 8}, {1, 9}, {2, 20}, {0, 9}, {2, -1}, {2, 5}, {0, 18}, {2, 8}, {2, 12}, {2, -14}, {0, 18}, {1, 1}, {2, -15}, {2, 14}, {2, -11}, {2, -2}}, {{1, 4}, {2, 6}, {2, 18}, {1, 19}, {0, 18}, {0, 19}, {0, 14}, {0, 12}, {2, 3}, {0, 19}, {2, -20}, {0, 8}, {2, -15}, {2, -15}, {0, 8}, {1, 17}, {2, 2}, {1, 14}, {0, 11}, {0, 13}}, {{2, -10}, {2, -8}, {1, 18}, {1, 11}, {1, 5}, {0, 2}, {2, -19}, {2, -9}, {1, 5}, {2, -1}, {2, 2}, {1, 5}, {0, 9}, {0, 17}, {2, 2}, {0, 10}, {1, 12}, {1, 10}, {0, 8}, {2, -13}}, {{0, 13}, {1, 5}, {2, -1}, {2, -16}, {2, 2}, {0, 6}, {2, -11}, {0, 5}, {2, 5}, {2, -6}, {2, -6}, {1, 19}, {0, 8}, {2, -16}, {2, 14}, {0, 11}, {0, 14}, {0, 18}, {0, 14}, {2, 1}}, {{2, 3}, {2, -2}, {0, 10}, {1, 1}, {0, 10}, {2, -15}, {2, 19}, {2, 2}, {1, 15}, {1, 10}, {2, -18}, {0, 20}, {0, 10}, {2, 3}, {1, 13}, {0, 8}, {0, 16}, {0, 14}, {2, 13}, {2, -7}}, {{0, 4}, {1, 7}, {0, 2}, {0, 15}, {0, 20}, {1, 9}, {0, 14}, {2, 18}, {0, 1}, {0, 4}, {2, -17}, {1, 10}, {1, 15}, {0, 2}, {2, -6}, {2, 4}, {2, 4}, {2, 17}, {2, -13}, {1, 20}}, {{1, 8}, {0, 20}, {2, 4}, {1, 6}, {2, -19}, {1, 4}, {0, 5}, {1, 8}, {1, 17}, {2, -4}, {2, 14}, {1, 10}, {2, 7}, {1, 18}, {2, 17}, {1, 6}, {0, 20}, {2, 1}, {2, -12}, {2, 18}}, + {{2, -7}, {1, 12}, {2, 4}, {2, 14}, {0, 13}, {1, 17}, {2, -2}, {2, -9}, {2, -13}, {1, 4}, {2, 4}, {1, 5}, {0, 15}, {2, -11}, {2, 3}, {2, 1}, {2, -11}, {2, 11}, {2, -11}, {0, 4}}, {{1, 7}, {2, 20}, {0, 6}, {0, 9}, {1, 2}, {0, 11}, {2, 4}, {2, -19}, {0, 17}, {2, -7}, {0, 20}, {0, 10}, {1, 11}, {1, 20}, {1, 6}, {2, 14}, {0, 11}, {0, 19}, {2, 4}, {1, 8}}, {{0, 9}, {2, -3}, {1, 17}, {2, 14}, {2, 3}, {1, 19}, {2, 17}, {0, 18}, {2, 16}, {0, 9}, {2, -15}, {1, 14}, {2, 11}, {2, -7}, {0, 17}, {2, 12}, {2, 6}, {2, 9}, {0, 7}, {1, 3}}, {{0, 1}, {2, 13}, {1, 19}, {0, 19}, {2, 6}, {2, -14}, {2, 8}, {2, -18}, {2, 4}, {2, -7}, {0, 12}, {2, 15}, {1, 3}, {2, -19}, {2, 15}, {2, -2}, {1, 19}, {1, 18}, {1, 19}, {0, 16}}, {{2, 7}, {2, 14}, {2, 14}, {2, -19}, {0, 1}, {2, -5}, {0, 17}, {2, 2}, {2, 15}, {1, 13}, {0, 20}, {0, 12}, {2, 20}, {0, 9}, {2, -8}, {0, 14}, {1, 19}, {2, -15}, {2, 17}, {2, -12}}, {{0, 16}, {2, -7}, {2, -4}, {1, 11}, {1, 17}, {2, -14}, {0, 16}, {2, 6}, {1, 19}, {2, 17}, {2, 10}, {2, -12}, {2, -17}, {1, 4}, {2, -14}, {2, -7}, {2, -17}, {2, -1}, {2, -16}, {1, 8}}, {{0, 5}, {0, 15}, {2, 4}, {2, 20}, {2, 7}, {2, 3}, {2, 17}, {0, 8}, {0, 18}, {2, -18}, {1, 1}, {0, 15}, {2, -4}, {0, 13}, {1, 11}, {0, 4}, {2, 11}, {1, 11}, {1, 10}, {2, -9}}, {{2, -5}, {2, -16}, {1, 2}, {1, 17}, {2, 9}, {2, -2}, {0, 7}, {0, 14}, {2, -10}, {0, 6}, {1, 14}, {1, 15}, {2, 12}, {2, 11}, {2, -9}, {2, -10}, {2, -14}, {2, 9}, {0, 13}, {2, 5}}, {{2, 8}, {2, -16}, {2, 20}, {1, 8}, {1, 2}, {2, -5}, {2, -20}, {1, 2}, {1, 4}, {1, 9}, {0, 19}, {2, -18}, {0, 6}, {0, 13}, {1, 5}, {2, 2}, {0, 4}, {1, 19}, {2, 15}, {1, 12}}, {{2, -8}, {0, 16}, {1, 10}, {2, 5}, {2, -16}, {0, 3}, {2, 5}, {0, 6}, {1, 17}, {2, 1}, {2, 13}, {0, 3}, {0, 6}, {0, 6}, {0, 19}, {1, 13}, {2, 19}, {2, 5}, {1, 16}, {2, 5}}, + {{2, -19}, {2, -1}, {0, 19}, {2, -3}, {2, 13}, {2, -12}, {0, 2}, {2, -20}, {2, 15}, {2, -9}, {2, -2}, {2, 13}, {2, -6}, {0, 2}, {1, 6}, {2, -1}, {0, 12}, {0, 20}, {2, -14}, {1, 2}}, {{1, 9}, {2, -14}, {2, -2}, {2, -13}, {0, 3}, {1, 6}, {2, 20}, {2, 11}, {2, 17}, {2, 5}, {0, 5}, {1, 1}, {2, -9}, {1, 8}, {2, -2}, {0, 18}, {2, -8}, {1, 11}, {2, 6}, {1, 7}}, {{2, 17}, {2, 14}, {2, 16}, {1, 20}, {2, -1}, {2, -7}, {1, 3}, {1, 14}, {0, 1}, {0, 18}, {2, -14}, {0, 10}, {0, 18}, {2, 19}, {1, 13}, {2, -16}, {1, 17}, {1, 1}, {2, -13}, {0, 13}}, {{2, -2}, {0, 14}, {0, 12}, {2, -8}, {1, 16}, {0, 18}, {2, 5}, {2, 13}, {1, 14}, {2, -4}, {2, 16}, {2, -16}, {2, 16}, {1, 18}, {2, 15}, {2, 4}, {2, -5}, {1, 7}, {2, -20}, {2, -9}}, {{2, 18}, {2, 2}, {2, 5}, {0, 4}, {2, 17}, {2, -15}, {2, 8}, {2, -11}, {1, 8}, {2, -6}, {2, -13}, {0, 5}, {0, 18}, {0, 15}, {2, -8}, {2, -15}, {1, 1}, {2, 4}, {2, -9}, {2, 14}}, {{2, -1}, {2, 1}, {1, 12}, {0, 9}, {2, -11}, {1, 4}, {1, 11}, {1, 5}, {0, 12}, {2, -7}, {1, 1}, {0, 6}, {0, 15}, {2, -10}, {2, -18}, {2, -12}, {0, 3}, {2, 2}, {2, -17}, {2, 1}}, {{2, -5}, {2, 18}, {1, 2}, {2, 14}, {2, 20}, {0, 13}, {0, 11}, {0, 6}, {2, -7}, {2, 13}, {1, 13}, {2, -1}, {1, 12}, {2, -9}, {2, 3}, {1, 6}, {2, -16}, {0, 6}, {0, 9}, {2, -14}}, {{1, 6}, {2, 7}, {2, -13}, {0, 5}, {1, 4}, {0, 15}, {2, 18}, {2, -4}, {2, 16}, {1, 1}, {2, 17}, {0, 2}, {1, 12}, {0, 17}, {1, 18}, {1, 5}, {1, 8}, {1, 4}, {2, -9}, {1, 9}}, {{0, 20}, {1, 10}, {2, 20}, {2, 13}, {0, 18}, {0, 10}, {1, 3}, {0, 8}, {2, -16}, {2, -16}, {2, -2}, {0, 20}, {2, -19}, {2, -11}, {1, 17}, {1, 18}, {1, 5}, {0, 6}, {2, -9}, {1, 8}}, {{0, 18}, {0, 19}, {2, 16}, {2, -14}, {1, 7}, {1, 9}, {0, 16}, {0, 16}, {1, 17}, {0, 14}, {2, -16}, {1, 6}, {1, 11}, {2, 7}, {1, 19}, {1, 11}, {2, -14}, {0, 7}, {1, 12}, {0, 2}}, + {{2, 3}, {2, -15}, {0, 13}, {2, -3}, {0, 16}, {1, 18}, {0, 11}, {0, 16}, {1, 13}, {2, -18}, {1, 14}, {2, 11}, {0, 11}, {2, -14}, {2, -19}, {0, 2}, {2, -7}, {0, 5}, {0, 20}, {2, -7}}, {{2, 9}, {2, 5}, {2, 7}, {0, 15}, {2, 4}, {0, 5}, {2, -17}, {2, 16}, {2, 8}, {0, 11}, {0, 1}, {1, 8}, {2, -5}, {1, 1}, {2, 4}, {2, -4}, {2, -17}, {0, 5}, {1, 10}, {0, 5}}, {{0, 11}, {1, 16}, {1, 18}, {0, 5}, {2, -13}, {0, 5}, {1, 13}, {1, 10}, {1, 11}, {0, 5}, {1, 9}, {1, 4}, {1, 19}, {2, 1}, {1, 19}, {1, 18}, {1, 8}, {2, 2}, {2, -5}, {1, 4}}, {{0, 12}, {1, 16}, {2, -1}, {1, 10}, {1, 10}, {2, -19}, {2, -20}, {1, 17}, {1, 19}, {1, 4}, {2, 12}, {0, 1}, {2, -15}, {2, -1}, {2, -10}, {2, -19}, {0, 18}, {0, 6}, {2, 18}, {0, 18}}, {{1, 10}, {0, 13}, {2, 3}, {0, 20}, {0, 19}, {0, 5}, {0, 9}, {0, 5}, {0, 20}, {0, 9}, {2, 16}, {1, 11}, {0, 12}, {2, -17}, {0, 20}, {0, 14}, {1, 17}, {2, 15}, {1, 7}, {2, -1}}, {{2, -10}, {2, 1}, {0, 2}, {2, -11}, {2, -17}, {2, -10}, {1, 9}, {2, -6}, {2, -19}, {1, 14}, {0, 4}, {1, 6}, {2, -9}, {2, 3}, {1, 2}, {2, 19}, {1, 15}, {0, 7}, {0, 4}, {2, 2}}, {{1, 12}, {2, -8}, {2, 13}, {2, -19}, {0, 17}, {0, 20}, {1, 3}, {1, 15}, {1, 20}, {2, 17}, {0, 12}, {2, -2}, {1, 8}, {2, -12}, {0, 11}, {2, 17}, {1, 14}, {1, 13}, {2, 2}, {2, 3}}, {{0, 13}, {1, 13}, {2, -4}, {1, 17}, {1, 1}, {1, 5}, {0, 10}, {1, 12}, {0, 9}, {2, -16}, {0, 13}, {2, 19}, {2, 9}, {1, 18}, {2, -1}, {2, -14}, {0, 5}, {1, 2}, {0, 20}, {2, -19}}, {{1, 7}, {2, 19}, {2, 11}, {2, -16}, {1, 19}, {2, -14}, {0, 7}, {1, 10}, {2, 8}, {0, 12}, {1, 12}, {2, -11}, {2, 6}, {2, -16}, {2, 15}, {1, 20}, {2, -12}, {1, 7}, {2, -16}, {2, 7}}, {{0, 8}, {2, 19}, {1, 12}, {2, -12}, {2, 4}, {1, 10}, {1, 13}, {2, 2}, {2, -14}, {2, -10}, {2, 18}, {1, 16}, {0, 10}, {2, 19}, {2, -1}, {1, 17}, {2, -1}, {0, 14}, {1, 11}, {0, 18}}, + {{2, 2}, {2, -2}, {2, 12}, {0, 7}, {0, 14}, {2, -15}, {2, 13}, {2, -3}, {0, 1}, {2, -2}, {1, 2}, {2, 4}, {2, 4}, {2, 16}, {1, 4}, {2, 1}, {1, 9}, {1, 6}, {1, 12}, {1, 11}}, {{2, -10}, {1, 16}, {1, 12}, {0, 18}, {2, -13}, {0, 19}, {2, 15}, {0, 11}, {0, 2}, {0, 4}, {0, 7}, {0, 10}, {2, -10}, {2, 11}, {0, 13}, {0, 10}, {2, -14}, {2, -18}, {0, 7}, {2, 16}}, {{2, -19}, {2, -5}, {1, 9}, {0, 15}, {2, 4}, {2, -14}, {1, 13}, {2, 10}, {2, -7}, {2, 14}, {2, 8}, {2, 9}, {0, 14}, {2, -4}, {1, 2}, {1, 19}, {0, 18}, {2, 18}, {1, 6}, {2, -6}}, {{2, -15}, {2, 1}, {0, 8}, {1, 5}, {2, -11}, {2, -3}, {0, 19}, {0, 4}, {1, 19}, {1, 2}, {1, 2}, {0, 3}, {0, 16}, {2, -3}, {2, 18}, {1, 20}, {2, -16}, {2, -2}, {2, -17}, {1, 15}}, {{1, 19}, {2, -11}, {1, 13}, {0, 14}, {1, 18}, {2, 11}, {2, 13}, {2, -14}, {1, 5}, {2, -6}, {0, 4}, {2, 19}, {0, 12}, {1, 4}, {2, -6}, {2, 8}, {2, 9}, {2, -10}, {2, -3}, {2, -19}}, {{2, 7}, {2, -9}, {2, 5}, {2, 12}, {2, -20}, {2, -3}, {2, -12}, {2, 5}, {0, 6}, {2, 15}, {1, 8}, {1, 9}, {2, 18}, {2, -14}, {2, 10}, {1, 5}, {1, 2}, {0, 14}, {2, 14}, {0, 19}}, {{2, 12}, {0, 9}, {0, 14}, {1, 4}, {2, 4}, {2, -8}, {2, 4}, {0, 13}, {1, 10}, {2, 18}, {2, 13}, {2, 15}, {1, 18}, {2, 7}, {2, -18}, {2, 18}, {1, 9}, {2, -9}, {0, 16}, {2, -14}}, {{0, 16}, {2, 9}, {2, 7}, {1, 6}, {0, 14}, {2, -18}, {1, 14}, {1, 2}, {0, 10}, {1, 6}, {0, 10}, {2, 2}, {1, 1}, {0, 19}, {1, 6}, {2, -3}, {2, 3}, {2, 12}, {2, 5}, {1, 18}}, {{2, -17}, {0, 17}, {0, 5}, {2, 18}, {2, 8}, {0, 14}, {0, 10}, {1, 20}, {2, 7}, {1, 10}, {1, 14}, {1, 10}, {0, 11}, {1, 15}, {0, 16}, {1, 14}, {0, 3}, {2, 16}, {2, -5}, {2, -4}}, {{1, 15}, {2, -9}, {0, 11}, {0, 16}, {2, 1}, {0, 12}, {0, 7}, {0, 16}, {2, 14}, {0, 3}, {1, 11}, {2, 4}, {1, 6}, {2, -1}, {1, 20}, {1, 13}, {0, 2}, {2, -12}, {0, 9}, {2, 8}}, + {{0, 1}, {2, -4}, {0, 19}, {2, -15}, {2, -16}, {2, -10}, {2, 7}, {0, 10}, {2, -6}, {2, -8}, {2, -3}, {2, 18}, {1, 20}, {2, 10}, {1, 18}, {2, -3}, {0, 7}, {2, 3}, {2, 14}, {2, 15}}, {{1, 17}, {0, 7}, {1, 17}, {2, -8}, {2, -12}, {2, -9}, {2, 15}, {0, 10}, {1, 10}, {2, -6}, {2, -20}, {1, 17}, {2, -9}, {1, 15}, {1, 3}, {1, 8}, {0, 1}, {1, 13}, {2, -5}, {0, 14}}, {{2, 8}, {0, 11}, {0, 12}, {2, 5}, {2, -4}, {2, -5}, {2, -2}, {0, 20}, {2, -4}, {1, 1}, {1, 2}, {0, 4}, {2, 14}, {1, 17}, {2, -13}, {1, 13}, {2, 12}, {2, 20}, {2, 14}, {2, -9}}, {{2, -20}, {2, 13}, {1, 20}, {2, 20}, {2, 9}, {0, 16}, {2, 1}, {0, 13}, {2, 17}, {2, 8}, {2, 13}, {2, -3}, {2, -1}, {2, -19}, {2, 1}, {1, 16}, {2, -16}, {0, 6}, {2, 2}, {1, 8}}, {{1, 4}, {1, 9}, {1, 8}, {2, -12}, {0, 4}, {1, 8}, {0, 9}, {2, -2}, {2, -19}, {2, 2}, {2, -1}, {0, 14}, {2, 4}, {2, -17}, {0, 1}, {2, 4}, {2, -20}, {2, 3}, {0, 7}, {1, 4}}, {{0, 6}, {2, -5}, {1, 13}, {2, 15}, {2, 11}, {0, 7}, {1, 17}, {2, 9}, {1, 14}, {2, -7}, {2, 12}, {2, -18}, {0, 19}, {2, -1}, {2, 14}, {2, 5}, {2, -16}, {1, 2}, {2, -2}, {0, 17}}, {{1, 11}, {2, 4}, {2, 7}, {1, 16}, {2, 14}, {0, 14}, {2, 16}, {1, 11}, {1, 10}, {2, -3}, {2, -10}, {1, 7}, {2, 9}, {0, 19}, {2, 1}, {0, 7}, {1, 11}, {0, 9}, {2, -16}, {1, 8}}, {{0, 10}, {0, 4}, {2, -8}, {2, 20}, {2, 16}, {2, 5}, {0, 16}, {1, 6}, {2, 2}, {2, 14}, {2, 11}, {1, 15}, {2, -10}, {2, -5}, {0, 4}, {0, 4}, {2, -13}, {2, 11}, {0, 13}, {0, 11}}, {{0, 6}, {1, 4}, {2, 15}, {2, 17}, {1, 1}, {2, -20}, {1, 18}, {0, 19}, {2, -12}, {2, 1}, {2, -15}, {2, -11}, {1, 19}, {0, 13}, {1, 2}, {0, 17}, {2, 14}, {0, 14}, {2, 12}, {0, 19}}, {{2, -16}, {1, 20}, {2, -14}, {1, 7}, {2, -13}, {2, 19}, {2, 20}, {2, -14}, {1, 17}, {2, 20}, {1, 2}, {2, -19}, {2, -17}, {2, 18}, {2, 6}, {1, 17}, {2, 7}, {0, 8}, {1, 20}, {2, 9}}, + {{2, 20}, {2, 17}, {2, 15}, {1, 13}, {0, 7}, {2, -6}, {0, 8}, {2, -17}, {1, 15}, {2, -20}, {0, 17}, {2, -17}, {0, 12}, {1, 14}, {2, -8}, {2, -17}, {0, 12}, {2, -3}, {2, -13}, {0, 7}}, {{0, 3}, {2, 12}, {2, 4}, {0, 13}, {0, 15}, {1, 4}, {2, 7}, {2, -20}, {0, 4}, {0, 6}, {0, 12}, {2, 10}, {2, -13}, {0, 20}, {2, -11}, {0, 16}, {2, -13}, {2, -5}, {1, 14}, {0, 7}}, {{2, 16}, {1, 11}, {2, -1}, {0, 9}, {0, 19}, {2, 9}, {2, 6}, {1, 15}, {2, 2}, {2, 8}, {1, 3}, {2, 20}, {2, -18}, {2, -15}, {2, -20}, {0, 10}, {1, 9}, {2, 5}, {2, -17}, {2, 14}}, {{2, -15}, {2, -10}, {2, -18}, {1, 20}, {1, 4}, {0, 18}, {1, 10}, {2, 19}, {2, -9}, {2, 20}, {0, 1}, {0, 11}, {2, -18}, {2, 5}, {1, 15}, {0, 10}, {2, -14}, {2, 6}, {0, 17}, {0, 3}}, {{1, 18}, {1, 5}, {0, 11}, {2, 11}, {0, 13}, {1, 5}, {0, 19}, {2, 20}, {1, 10}, {2, 19}, {0, 13}, {0, 7}, {2, 1}, {2, 14}, {1, 19}, {0, 9}, {1, 17}, {2, 18}, {0, 11}, {1, 9}}, {{2, -17}, {2, 10}, {2, -19}, {2, -18}, {2, 6}, {0, 9}, {0, 20}, {1, 7}, {2, -14}, {2, -11}, {0, 14}, {1, 1}, {0, 13}, {0, 2}, {2, 3}, {0, 2}, {2, 11}, {2, 10}, {2, 12}, {2, -4}}, {{2, 13}, {1, 14}, {2, 7}, {1, 12}, {2, -9}, {2, -4}, {2, -8}, {2, 13}, {0, 3}, {1, 20}, {2, 2}, {2, 5}, {0, 19}, {0, 10}, {2, 14}, {2, -15}, {1, 5}, {2, 18}, {2, -8}, {2, 6}}, {{1, 5}, {1, 9}, {1, 19}, {2, 19}, {0, 13}, {2, 2}, {2, 12}, {2, 17}, {2, -16}, {0, 12}, {0, 9}, {1, 17}, {0, 13}, {2, 7}, {1, 7}, {2, 14}, {0, 12}, {0, 7}, {2, -16}, {1, 7}}, {{2, 15}, {0, 9}, {1, 3}, {0, 4}, {2, -3}, {0, 12}, {2, 9}, {0, 6}, {1, 16}, {0, 10}, {2, -6}, {1, 20}, {2, 11}, {2, -3}, {2, -6}, {0, 15}, {0, 14}, {0, 11}, {2, -19}, {2, -14}}, {{0, 12}, {2, 8}, {1, 20}, {2, 18}, {2, 3}, {2, 2}, {2, 4}, {2, 7}, {2, -19}, {2, -9}, {2, 2}, {0, 19}, {0, 11}, {2, -9}, {0, 9}, {2, -14}, {1, 13}, {0, 2}, {0, 1}, {2, -19}}, + {{2, -4}, {1, 6}, {0, 18}, {2, 4}, {1, 12}, {0, 17}, {2, 10}, {2, 13}, {2, 10}, {1, 10}, {2, -1}, {2, -11}, {2, -15}, {2, -5}, {2, -16}, {2, -12}, {0, 2}, {2, -10}, {2, 15}, {1, 2}}, {{2, -9}, {2, -14}, {1, 4}, {0, 5}, {2, -9}, {0, 7}, {0, 9}, {1, 16}, {1, 10}, {2, -14}, {0, 7}, {2, -18}, {0, 19}, {1, 3}, {1, 7}, {2, -19}, {2, 14}, {0, 9}, {1, 7}, {2, -5}}, {{2, 16}, {0, 13}, {2, 20}, {2, -9}, {0, 20}, {2, 20}, {0, 5}, {0, 6}, {1, 4}, {2, -11}, {0, 14}, {0, 9}, {2, 18}, {0, 14}, {0, 12}, {2, 16}, {2, 8}, {2, -13}, {1, 9}, {1, 3}}, {{2, 1}, {2, 13}, {2, -8}, {0, 9}, {2, -16}, {0, 12}, {0, 12}, {2, 19}, {2, -10}, {2, 1}, {2, -20}, {1, 10}, {0, 18}, {2, -15}, {2, -15}, {1, 15}, {0, 15}, {0, 1}, {2, -5}, {0, 19}}, {{2, 6}, {2, 15}, {2, -20}, {1, 20}, {2, 3}, {2, 7}, {1, 1}, {0, 5}, {0, 4}, {2, 8}, {1, 19}, {2, -3}, {2, 18}, {1, 10}, {0, 11}, {2, -19}, {0, 3}, {2, 5}, {1, 4}, {0, 6}}, {{0, 5}, {2, -17}, {1, 1}, {0, 1}, {2, -9}, {0, 8}, {2, -10}, {2, -4}, {0, 13}, {2, 19}, {0, 6}, {2, 9}, {2, 14}, {2, 11}, {0, 16}, {2, 13}, {1, 1}, {2, 9}, {2, 14}, {0, 18}}, {{0, 10}, {1, 12}, {1, 4}, {2, -12}, {2, 7}, {0, 5}, {1, 15}, {2, 7}, {2, 17}, {1, 18}, {2, 3}, {0, 12}, {1, 8}, {1, 13}, {2, -17}, {1, 20}, {0, 18}, {1, 12}, {1, 16}, {0, 16}}, {{2, -10}, {2, -1}, {2, 10}, {1, 4}, {2, -12}, {1, 19}, {2, 18}, {2, -15}, {2, -5}, {1, 11}, {2, -7}, {0, 20}, {2, -12}, {2, -12}, {1, 11}, {2, 8}, {2, 2}, {2, -2}, {2, -4}, {2, 19}}, {{2, 5}, {1, 15}, {1, 18}, {2, 20}, {1, 4}, {0, 7}, {2, -8}, {2, 1}, {2, -6}, {2, 8}, {1, 1}, {2, 12}, {1, 15}, {0, 16}, {1, 13}, {0, 1}, {0, 19}, {0, 20}, {2, -3}, {0, 12}}, {{1, 18}, {2, -4}, {1, 8}, {2, 19}, {1, 11}, {0, 12}, {0, 20}, {2, 4}, {1, 12}, {0, 17}, {2, 8}, {1, 5}, {2, -13}, {1, 3}, {1, 14}, {1, 4}, {0, 20}, {1, 18}, {0, 7}, {0, 17}}, + {{2, 14}, {2, 10}, {2, -12}, {2, -15}, {1, 3}, {2, -17}, {1, 12}, {2, 9}, {2, -9}, {0, 11}, {2, 12}, {0, 16}, {0, 13}, {2, -17}, {2, 13}, {2, 11}, {1, 13}, {2, 16}, {0, 14}, {2, -8}}, {{0, 16}, {2, -10}, {1, 12}, {1, 12}, {2, 19}, {1, 5}, {2, 1}, {2, 19}, {0, 17}, {2, -19}, {2, 20}, {0, 7}, {1, 11}, {2, -3}, {2, -19}, {2, -20}, {2, 15}, {2, -18}, {2, 11}, {2, -19}}, {{2, -11}, {2, 10}, {0, 10}, {1, 15}, {1, 1}, {2, 7}, {2, -9}, {2, -3}, {0, 2}, {0, 20}, {0, 1}, {2, 14}, {2, -16}, {1, 14}, {0, 19}, {0, 11}, {2, 4}, {2, -9}, {2, -4}, {2, -10}}, {{2, 2}, {2, 17}, {1, 7}, {0, 18}, {0, 12}, {2, -15}, {2, 18}, {1, 7}, {1, 3}, {2, 14}, {1, 19}, {1, 6}, {2, -19}, {2, 9}, {0, 11}, {1, 13}, {2, -13}, {0, 19}, {1, 15}, {0, 14}}, {{2, 14}, {1, 3}, {2, 4}, {0, 14}, {1, 8}, {2, 7}, {0, 16}, {1, 11}, {2, 11}, {1, 13}, {1, 6}, {2, -10}, {0, 17}, {1, 18}, {1, 1}, {2, -11}, {1, 9}, {0, 16}, {2, 5}, {2, -6}}, {{0, 11}, {2, -2}, {1, 7}, {2, 8}, {2, 20}, {0, 7}, {0, 14}, {2, -17}, {2, 14}, {2, -14}, {2, -16}, {2, -12}, {2, -19}, {2, 10}, {2, -8}, {1, 1}, {1, 14}, {0, 5}, {0, 14}, {2, 14}}, {{2, 10}, {2, 18}, {1, 15}, {1, 15}, {2, 10}, {2, 14}, {1, 15}, {2, -16}, {2, -4}, {2, -1}, {2, -20}, {2, 2}, {1, 6}, {0, 2}, {2, -17}, {0, 13}, {2, -8}, {2, 18}, {2, 9}, {2, -12}}, {{2, -7}, {1, 16}, {2, 15}, {1, 14}, {1, 15}, {0, 8}, {1, 19}, {2, -13}, {2, 17}, {2, 4}, {2, -17}, {2, -2}, {0, 8}, {1, 11}, {2, 14}, {0, 13}, {2, -12}, {2, -1}, {2, -5}, {0, 6}}, {{0, 2}, {2, -1}, {2, -15}, {1, 9}, {0, 17}, {0, 8}, {2, 19}, {0, 5}, {1, 16}, {2, 3}, {1, 17}, {2, -8}, {2, -1}, {2, -4}, {2, -16}, {0, 4}, {2, -8}, {2, 14}, {2, -20}, {2, -15}}, {{0, 5}, {0, 20}, {1, 13}, {0, 13}, {2, 18}, {1, 19}, {0, 7}, {0, 13}, {2, -7}, {2, 11}, {0, 16}, {1, 6}, {2, -19}, {2, 6}, {1, 18}, {2, 4}, {2, -7}, {2, -8}, {2, 20}, {0, 19}} + }; + // *INDENT-ON* + + uint8_t buf[20]; + uint8_t n = 0; + uint32_t bres = 0; + for(uint32_t i = 0; i < 100; i++) { + /* bring the pos back to the middle of the file */ + int32_t pos = 500; + res = lv_fs_seek(&f, pos, LV_FS_SEEK_SET); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + for(uint32_t j = 0; j < 20; j++) { + int8_t action = actions[i][j][0]; + int8_t amount = actions[i][j][1]; + switch(action) { + case 0: /* read */ + res = lv_fs_read(&f, buf, amount, &bres); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + TEST_ASSERT_EQUAL(amount, bres); + TEST_ASSERT(0 == memcmp(buf, buf1000 + pos, amount)); + pos += amount; + break; + case 1: /* write */ + for(int32_t k = 0; k < amount; k++) { + buf[k] = n; + buf1000[pos + k] = n; + n++; + } + res = lv_fs_write(&f, buf, amount, &bres); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + TEST_ASSERT_EQUAL(amount, bres); + pos += amount; + break; + case 2: /* seek */ + pos += amount; /* amount may be negative */ + res = lv_fs_seek(&f, pos, LV_FS_SEEK_SET); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + break; + } + } + } + + /* test SEEK_END */ + uint32_t tell_pos; + res = lv_fs_seek(&f, 0, LV_FS_SEEK_END); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + res = lv_fs_tell(&f, &tell_pos); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + TEST_ASSERT_EQUAL(tell_pos, 1000); + + res = lv_fs_close(&f); + TEST_ASSERT_EQUAL(LV_FS_RES_OK, res); + + drv->cache_size = original_cache_size; +} + #endif