mirror of https://github.com/yandex/odyssey.git
591 lines
15 KiB
C
591 lines
15 KiB
C
|
|
/*
|
|
* machinarium.
|
|
*
|
|
* cooperative multitasking engine.
|
|
*/
|
|
|
|
#include "zpq_stream.h"
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
/*
|
|
* Functions implementing streaming compression algorithm
|
|
*/
|
|
typedef struct {
|
|
/*
|
|
* Returns letter identifying compression algorithm.
|
|
*/
|
|
char (*name)(void);
|
|
|
|
/*
|
|
* Create compression stream with using rx/tx function for fetching/sending
|
|
* compressed data. tx_func: function for writing compressed data in
|
|
* underlying stream rx_func: function for receiving compressed data from
|
|
* underlying stream arg: context passed to the function rx_data: received
|
|
* data (compressed data already fetched from input stream) rx_data_size:
|
|
* size of data fetched from input stream
|
|
*/
|
|
mm_zpq_stream_t *(*create)(mm_zpq_tx_func tx_func,
|
|
mm_zpq_rx_func rx_func, void *arg,
|
|
char *rx_data, size_t rx_data_size);
|
|
|
|
/*
|
|
* Read up to "size" raw (decompressed) bytes.
|
|
* Returns number of decompressed bytes or error code.
|
|
* Error code is either ZPQ_DECOMPRESS_ERROR either error code returned by
|
|
* the rx function.
|
|
*/
|
|
ssize_t (*read)(mm_zpq_stream_t *zs, void *buf, size_t size);
|
|
|
|
/*
|
|
* Write up to "size" raw (decompressed) bytes.
|
|
* Returns number of written raw bytes or error code returned by tx
|
|
* function. In the last case amount of written raw bytes is stored in
|
|
* *processed.
|
|
*/
|
|
ssize_t (*write)(mm_zpq_stream_t *zs, void const *buf, size_t size,
|
|
size_t *processed);
|
|
|
|
/*
|
|
* Free stream created by create function.
|
|
*/
|
|
void (*free)(mm_zpq_stream_t *zs);
|
|
|
|
/*
|
|
* Get error message.
|
|
*/
|
|
char const *(*error)(mm_zpq_stream_t *zs);
|
|
|
|
/*
|
|
* Returns estimated amount of data left in internal tx decompression
|
|
* buffer.
|
|
*/
|
|
size_t (*buffered_tx)(mm_zpq_stream_t *zs);
|
|
|
|
/*
|
|
* Returns estimated amount of data left in internal rx compression
|
|
* buffer.
|
|
*/
|
|
size_t (*buffered_rx)(mm_zpq_stream_t *zs);
|
|
|
|
/*
|
|
* Returns 1 if there is deferred rx_func call operation.
|
|
* Otherwise returns 0.
|
|
*/
|
|
_Bool (*deferred_rx)(mm_zpq_stream_t *zs);
|
|
} zpq_algorithm_t;
|
|
|
|
struct mm_zpq_stream {
|
|
zpq_algorithm_t const *algorithm;
|
|
};
|
|
#ifdef MM_BUILD_COMPRESSION
|
|
#ifdef MM_HAVE_ZSTD
|
|
|
|
#include <stdlib.h>
|
|
#include <zstd.h>
|
|
|
|
#define MM_ZSTD_BUFFER_SIZE (8 * 1024)
|
|
#define MM_ZSTD_COMPRESSION_LEVEL 1
|
|
|
|
typedef struct zstd_stream {
|
|
mm_zpq_stream_t common;
|
|
ZSTD_CStream *tx_stream;
|
|
ZSTD_DStream *rx_stream;
|
|
ZSTD_outBuffer tx;
|
|
ZSTD_inBuffer rx;
|
|
size_t tx_not_flushed; /* Amount of data in internal zstd buffer */
|
|
size_t tx_buffered; /* Data consumed by zstd_write but not yet sent */
|
|
size_t rx_buffered; /* Data which is needed for ztd_read */
|
|
/* Flag that the last call of zstd_read did not call the rx_func */
|
|
_Bool deferred_rx_call;
|
|
mm_zpq_tx_func tx_func;
|
|
mm_zpq_rx_func rx_func;
|
|
void *arg;
|
|
char const *rx_error; /* Decompress error message */
|
|
size_t tx_total;
|
|
size_t tx_total_raw;
|
|
size_t rx_total;
|
|
size_t rx_total_raw;
|
|
char tx_buf[MM_ZSTD_BUFFER_SIZE];
|
|
char rx_buf[MM_ZSTD_BUFFER_SIZE];
|
|
} zstd_stream_t;
|
|
|
|
static mm_zpq_stream_t *zstd_create(mm_zpq_tx_func tx_func,
|
|
mm_zpq_rx_func rx_func, void *arg,
|
|
char *rx_data, size_t rx_data_size)
|
|
{
|
|
zstd_stream_t *zs = (zstd_stream_t *)malloc(sizeof(zstd_stream_t));
|
|
|
|
zs->tx_stream = ZSTD_createCStream();
|
|
ZSTD_initCStream(zs->tx_stream, MM_ZSTD_COMPRESSION_LEVEL);
|
|
zs->rx_stream = ZSTD_createDStream();
|
|
ZSTD_initDStream(zs->rx_stream);
|
|
zs->tx.dst = zs->tx_buf;
|
|
zs->tx.pos = 0;
|
|
zs->tx.size = MM_ZSTD_BUFFER_SIZE;
|
|
zs->rx.src = zs->rx_buf;
|
|
zs->rx.pos = 0;
|
|
zs->rx.size = 0;
|
|
zs->rx_func = rx_func;
|
|
zs->tx_func = tx_func;
|
|
zs->tx_buffered = 0;
|
|
zs->rx_buffered = 0;
|
|
zs->tx_not_flushed = 0;
|
|
zs->rx_error = NULL;
|
|
zs->arg = arg;
|
|
zs->tx_total = zs->tx_total_raw = 0;
|
|
zs->rx_total = zs->rx_total_raw = 0;
|
|
zs->rx.size = rx_data_size;
|
|
zs->deferred_rx_call = 0;
|
|
assert(rx_data_size < MM_ZSTD_BUFFER_SIZE);
|
|
memcpy(zs->rx_buf, rx_data, rx_data_size);
|
|
|
|
return (mm_zpq_stream_t *)zs;
|
|
}
|
|
|
|
static ssize_t zstd_read(mm_zpq_stream_t *zstream, void *buf, size_t size)
|
|
{
|
|
zstd_stream_t *zs = (zstd_stream_t *)zstream;
|
|
ssize_t rc;
|
|
ZSTD_outBuffer out;
|
|
out.dst = buf;
|
|
out.pos = 0;
|
|
out.size = size;
|
|
|
|
while (1) {
|
|
/* store the incomplete rx attempt flag */
|
|
zs->deferred_rx_call = 1;
|
|
if (zs->rx.pos != zs->rx.size || zs->rx_buffered == 0) {
|
|
rc = ZSTD_decompressStream(zs->rx_stream, &out,
|
|
&zs->rx);
|
|
if (ZSTD_isError(rc)) {
|
|
zs->rx_error = ZSTD_getErrorName(rc);
|
|
return MM_ZPQ_DECOMPRESS_ERROR;
|
|
}
|
|
/* Return result if we fill requested amount of bytes or read
|
|
* operation was performed */
|
|
if (out.pos != 0) {
|
|
zs->rx_total_raw += out.pos;
|
|
zs->rx_buffered = 0;
|
|
return out.pos;
|
|
}
|
|
zs->rx_buffered = rc;
|
|
if (zs->rx.pos == zs->rx.size) {
|
|
zs->rx.pos = zs->rx.size =
|
|
0; /* Reset rx buffer */
|
|
}
|
|
}
|
|
rc = zs->rx_func(zs->arg, (char *)zs->rx.src + zs->rx.size,
|
|
MM_ZSTD_BUFFER_SIZE - zs->rx.size);
|
|
/* if we've made a call to rx function, reset the deferred rx flag */
|
|
zs->deferred_rx_call = 0;
|
|
if (rc > 0) /* read fetches some data */
|
|
{
|
|
zs->rx.size += rc;
|
|
zs->rx_total += rc;
|
|
} else /* read failed */
|
|
{
|
|
zs->rx_total_raw += out.pos;
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
static ssize_t zstd_write(mm_zpq_stream_t *zstream, void const *buf,
|
|
size_t size, size_t *processed)
|
|
{
|
|
zstd_stream_t *zs = (zstd_stream_t *)zstream;
|
|
ssize_t rc;
|
|
ZSTD_inBuffer in_buf;
|
|
in_buf.src = buf;
|
|
in_buf.pos = 0;
|
|
in_buf.size = size;
|
|
|
|
do {
|
|
if (zs->tx.pos == 0) /* Compress buffer is empty */
|
|
{
|
|
zs->tx.dst =
|
|
zs->tx_buf; /* Reset pointer to the beginning of buffer */
|
|
|
|
if (in_buf.pos <
|
|
size) /* Has something to compress in input buffer */
|
|
ZSTD_compressStream(zs->tx_stream, &zs->tx,
|
|
&in_buf);
|
|
|
|
if (in_buf.pos ==
|
|
size) /* All data is compressed: flushed internal zstd buffer */
|
|
{
|
|
zs->tx_not_flushed = ZSTD_flushStream(
|
|
zs->tx_stream, &zs->tx);
|
|
}
|
|
}
|
|
rc = zs->tx_func(zs->arg, zs->tx.dst, zs->tx.pos);
|
|
if (rc > 0) {
|
|
zs->tx.pos -= rc;
|
|
zs->tx.dst = (char *)zs->tx.dst + rc;
|
|
zs->tx_total += rc;
|
|
} else {
|
|
*processed = in_buf.pos;
|
|
zs->tx_buffered = zs->tx.pos;
|
|
zs->tx_total_raw += in_buf.pos;
|
|
return rc;
|
|
}
|
|
/* repeat sending while there is some data in input or internal zstd
|
|
* buffer */
|
|
} while (in_buf.pos < size || zs->tx_not_flushed);
|
|
|
|
zs->tx_total_raw += in_buf.pos;
|
|
zs->tx_buffered = zs->tx.pos;
|
|
return in_buf.pos;
|
|
}
|
|
|
|
static void zstd_free(mm_zpq_stream_t *zstream)
|
|
{
|
|
zstd_stream_t *zs = (zstd_stream_t *)zstream;
|
|
if (zs != NULL) {
|
|
ZSTD_freeCStream(zs->tx_stream);
|
|
ZSTD_freeDStream(zs->rx_stream);
|
|
free(zs);
|
|
}
|
|
}
|
|
|
|
static char const *zstd_error(mm_zpq_stream_t *zstream)
|
|
{
|
|
zstd_stream_t *zs = (zstd_stream_t *)zstream;
|
|
return zs->rx_error;
|
|
}
|
|
|
|
static size_t zstd_buffered_tx(mm_zpq_stream_t *zstream)
|
|
{
|
|
zstd_stream_t *zs = (zstd_stream_t *)zstream;
|
|
return zs != NULL ? zs->tx_buffered + zs->tx_not_flushed : 0;
|
|
}
|
|
|
|
static size_t zstd_buffered_rx(mm_zpq_stream_t *zstream)
|
|
{
|
|
zstd_stream_t *zs = (zstd_stream_t *)zstream;
|
|
return zs != NULL ? zs->rx.size - zs->rx.pos : 0;
|
|
}
|
|
|
|
static _Bool zstd_deferred_rx(mm_zpq_stream_t *zstream)
|
|
{
|
|
zstd_stream_t *zs = (zstd_stream_t *)zstream;
|
|
return zs != NULL ? zs->deferred_rx_call : 0;
|
|
}
|
|
|
|
static char zstd_name(void)
|
|
{
|
|
return 'f';
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MM_HAVE_ZLIB
|
|
|
|
#include <stdlib.h>
|
|
#include <zlib.h>
|
|
|
|
#define MM_ZLIB_BUFFER_SIZE \
|
|
8192 /* We have to flush stream after each protocol command \
|
|
* and command is mostly limited by record length, \
|
|
* which in turn usually less than page size (except TOAST) \
|
|
*/
|
|
#define MM_ZLIB_COMPRESSION_LEVEL \
|
|
1 /* Experiments shows that default (fastest) compression level \
|
|
* provides the best size/speed ratio. It is significantly \
|
|
* (times) faster than more expensive levels and differences in \
|
|
* compression ratio is not so large \
|
|
*/
|
|
|
|
typedef struct zlib_stream {
|
|
mm_zpq_stream_t common;
|
|
|
|
z_stream tx;
|
|
z_stream rx;
|
|
|
|
mm_zpq_tx_func tx_func;
|
|
mm_zpq_rx_func rx_func;
|
|
void *arg;
|
|
unsigned tx_deflate_pending;
|
|
/* Flag that the last call of zlib_read did not call the rx_func */
|
|
_Bool deferred_rx_call;
|
|
size_t tx_buffered;
|
|
|
|
Bytef tx_buf[MM_ZLIB_BUFFER_SIZE];
|
|
Bytef rx_buf[MM_ZLIB_BUFFER_SIZE];
|
|
} zlib_stream_t;
|
|
|
|
static mm_zpq_stream_t *zlib_create(mm_zpq_tx_func tx_func,
|
|
mm_zpq_rx_func rx_func, void *arg,
|
|
char *rx_data, size_t rx_data_size)
|
|
{
|
|
int rc;
|
|
zlib_stream_t *zs = (zlib_stream_t *)malloc(sizeof(zlib_stream_t));
|
|
memset(&zs->tx, 0, sizeof(zs->tx));
|
|
zs->tx.next_out = zs->tx_buf;
|
|
zs->tx.avail_out = MM_ZLIB_BUFFER_SIZE;
|
|
zs->tx_buffered = 0;
|
|
rc = deflateInit(&zs->tx, MM_ZLIB_COMPRESSION_LEVEL);
|
|
if (rc != Z_OK) {
|
|
free(zs);
|
|
return NULL;
|
|
}
|
|
assert(zs->tx.next_out == zs->tx_buf &&
|
|
zs->tx.avail_out == MM_ZLIB_BUFFER_SIZE);
|
|
|
|
memset(&zs->rx, 0, sizeof(zs->tx));
|
|
zs->rx.next_in = zs->rx_buf;
|
|
zs->rx.avail_in = MM_ZLIB_BUFFER_SIZE;
|
|
zs->tx_deflate_pending = 0;
|
|
zs->deferred_rx_call = 0;
|
|
rc = inflateInit(&zs->rx);
|
|
if (rc != Z_OK) {
|
|
free(zs);
|
|
return NULL;
|
|
}
|
|
assert(zs->rx.next_in == zs->rx_buf &&
|
|
zs->rx.avail_in == MM_ZLIB_BUFFER_SIZE);
|
|
|
|
zs->rx.avail_in = rx_data_size;
|
|
assert(rx_data_size < MM_ZLIB_BUFFER_SIZE);
|
|
memcpy(zs->rx_buf, rx_data, rx_data_size);
|
|
|
|
zs->rx_func = rx_func;
|
|
zs->tx_func = tx_func;
|
|
zs->arg = arg;
|
|
|
|
return (mm_zpq_stream_t *)zs;
|
|
}
|
|
|
|
static ssize_t zlib_read(mm_zpq_stream_t *zstream, void *buf, size_t size)
|
|
{
|
|
zlib_stream_t *zs = (zlib_stream_t *)zstream;
|
|
int rc;
|
|
zs->rx.next_out = (Bytef *)buf;
|
|
zs->rx.avail_out = size;
|
|
|
|
while (1) {
|
|
/* store the incomplete rx attempt flag */
|
|
zs->deferred_rx_call = 1;
|
|
if (zs->rx.avail_in !=
|
|
0) /* If there is some data in receiver buffer,
|
|
then decompress it */
|
|
{
|
|
rc = inflate(&zs->rx, Z_SYNC_FLUSH);
|
|
if (rc != Z_OK && rc != Z_BUF_ERROR) {
|
|
return MM_ZPQ_DECOMPRESS_ERROR;
|
|
}
|
|
if (zs->rx.avail_out != size) {
|
|
return size - zs->rx.avail_out;
|
|
}
|
|
if (zs->rx.avail_in == 0) {
|
|
zs->rx.next_in = zs->rx_buf;
|
|
}
|
|
} else {
|
|
zs->rx.next_in = zs->rx_buf;
|
|
}
|
|
rc = zs->rx_func(zs->arg, zs->rx.next_in + zs->rx.avail_in,
|
|
zs->rx_buf + MM_ZLIB_BUFFER_SIZE -
|
|
zs->rx.next_in - zs->rx.avail_in);
|
|
/* if we've made a call to rx function, reset the deferred rx flag */
|
|
zs->deferred_rx_call = 0;
|
|
if (rc > 0) {
|
|
zs->rx.avail_in += rc;
|
|
} else {
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
static ssize_t zlib_write(mm_zpq_stream_t *zstream, void const *buf,
|
|
size_t size, size_t *processed)
|
|
{
|
|
zlib_stream_t *zs = (zlib_stream_t *)zstream;
|
|
int rc;
|
|
zs->tx.next_in = (Bytef *)buf;
|
|
zs->tx.avail_in = size;
|
|
do {
|
|
if (zs->tx.avail_out ==
|
|
MM_ZLIB_BUFFER_SIZE) /* Compress buffer is empty */
|
|
{
|
|
zs->tx.next_out =
|
|
zs->tx_buf; /* Reset pointer to the beginning of buffer */
|
|
|
|
if (zs->tx.avail_in != 0 ||
|
|
(zs->tx_deflate_pending >
|
|
0)) /* Has something in input or deflate buffer */
|
|
{
|
|
rc = deflate(&zs->tx, Z_SYNC_FLUSH);
|
|
assert(rc == Z_OK);
|
|
deflatePending(
|
|
&zs->tx, &zs->tx_deflate_pending,
|
|
Z_NULL); /* check if any data left in deflate buffer */
|
|
zs->tx.next_out =
|
|
zs->tx_buf; /* Reset pointer to the beginning of buffer */
|
|
}
|
|
}
|
|
rc = zs->tx_func(zs->arg, zs->tx.next_out,
|
|
MM_ZLIB_BUFFER_SIZE - zs->tx.avail_out);
|
|
if (rc > 0) {
|
|
zs->tx.next_out += rc;
|
|
zs->tx.avail_out += rc;
|
|
} else {
|
|
*processed = size - zs->tx.avail_in;
|
|
zs->tx_buffered =
|
|
MM_ZLIB_BUFFER_SIZE - zs->tx.avail_out;
|
|
return rc;
|
|
}
|
|
/* repeat sending while there is some data in input or deflate buffer */
|
|
} while (zs->tx.avail_in != 0 || zs->tx_deflate_pending > 0);
|
|
|
|
zs->tx_buffered = MM_ZLIB_BUFFER_SIZE - zs->tx.avail_out;
|
|
|
|
return size - zs->tx.avail_in;
|
|
}
|
|
|
|
static void zlib_free(mm_zpq_stream_t *zstream)
|
|
{
|
|
zlib_stream_t *zs = (zlib_stream_t *)zstream;
|
|
if (zs != NULL) {
|
|
inflateEnd(&zs->rx);
|
|
deflateEnd(&zs->tx);
|
|
free(zs);
|
|
}
|
|
}
|
|
|
|
static char const *zlib_error(mm_zpq_stream_t *zstream)
|
|
{
|
|
zlib_stream_t *zs = (zlib_stream_t *)zstream;
|
|
return zs->rx.msg;
|
|
}
|
|
|
|
static size_t zlib_buffered_tx(mm_zpq_stream_t *zstream)
|
|
{
|
|
zlib_stream_t *zs = (zlib_stream_t *)zstream;
|
|
return zs != NULL ? zs->tx_buffered + zs->tx_deflate_pending : 0;
|
|
}
|
|
|
|
static size_t zlib_buffered_rx(mm_zpq_stream_t *zstream)
|
|
{
|
|
zlib_stream_t *zs = (zlib_stream_t *)zstream;
|
|
return zs != NULL ? zs->rx.avail_in : 0;
|
|
}
|
|
|
|
static _Bool zlib_deferred_rx(mm_zpq_stream_t *zstream)
|
|
{
|
|
zlib_stream_t *zs = (zlib_stream_t *)zstream;
|
|
return zs != NULL ? zs->deferred_rx_call : 0;
|
|
}
|
|
|
|
static char zlib_name(void)
|
|
{
|
|
return 'z';
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
|
|
/*
|
|
* Array with all supported compression algorithms.
|
|
*/
|
|
static zpq_algorithm_t const zpq_algorithms[] = {
|
|
|
|
#ifdef MM_BUILD_COMPRESSION
|
|
#ifdef MM_HAVE_ZSTD
|
|
{ zstd_name, zstd_create, zstd_read, zstd_write, zstd_free, zstd_error,
|
|
zstd_buffered_tx, zstd_buffered_rx, zstd_deferred_rx },
|
|
#endif
|
|
#ifdef MM_HAVE_ZLIB
|
|
{ zlib_name, zlib_create, zlib_read, zlib_write, zlib_free, zlib_error,
|
|
zlib_buffered_tx, zlib_buffered_rx, zlib_deferred_rx },
|
|
#endif
|
|
#endif
|
|
{ NULL }
|
|
};
|
|
|
|
/*
|
|
* Index of used compression algorithm in zpq_algorithms array.
|
|
*/
|
|
mm_zpq_stream_t *zpq_create(int algorithm_impl, mm_zpq_tx_func tx_func,
|
|
mm_zpq_rx_func rx_func, void *arg, char *rx_data,
|
|
size_t rx_data_size)
|
|
{
|
|
mm_zpq_stream_t *stream = zpq_algorithms[algorithm_impl].create(
|
|
tx_func, rx_func, arg, rx_data, rx_data_size);
|
|
if (stream)
|
|
stream->algorithm = &zpq_algorithms[algorithm_impl];
|
|
return stream;
|
|
}
|
|
|
|
ssize_t mm_zpq_read(mm_zpq_stream_t *zs, void *buf, size_t size)
|
|
{
|
|
return zs->algorithm->read(zs, buf, size);
|
|
}
|
|
|
|
ssize_t mm_zpq_write(mm_zpq_stream_t *zs, void const *buf, size_t size,
|
|
size_t *processed)
|
|
{
|
|
return zs->algorithm->write(zs, buf, size, processed);
|
|
}
|
|
|
|
void mm_zpq_free(mm_zpq_stream_t *zs)
|
|
{
|
|
if (zs)
|
|
zs->algorithm->free(zs);
|
|
}
|
|
|
|
char const *mm_zpq_error(mm_zpq_stream_t *zs)
|
|
{
|
|
return zs->algorithm->error(zs);
|
|
}
|
|
|
|
size_t mm_zpq_buffered_rx(mm_zpq_stream_t *zs)
|
|
{
|
|
return zs ? zs->algorithm->buffered_rx(zs) : 0;
|
|
}
|
|
|
|
size_t mm_zpq_buffered_tx(mm_zpq_stream_t *zs)
|
|
{
|
|
return zs ? zs->algorithm->buffered_tx(zs) : 0;
|
|
}
|
|
|
|
_Bool mm_zpq_deferred_rx(mm_zpq_stream_t *zs)
|
|
{
|
|
return zs ? zs->algorithm->deferred_rx(zs) : 0;
|
|
}
|
|
|
|
/*
|
|
* Get list of the supported algorithms.
|
|
* Each algorithm is identified by one letter: 'f' - Facebook zstd, 'z' - zlib.
|
|
* Algorithm identifies are appended to the provided buffer and terminated by
|
|
* '\0'.
|
|
*/
|
|
void mm_zpq_get_supported_algorithms(char *algorithms)
|
|
{
|
|
int i;
|
|
for (i = 0; zpq_algorithms[i].name != NULL; i++) {
|
|
assert(i < MM_ZPQ_MAX_ALGORITHMS);
|
|
algorithms[i] = zpq_algorithms[i].name();
|
|
}
|
|
assert(i < MM_ZPQ_MAX_ALGORITHMS);
|
|
algorithms[i] = '\0';
|
|
}
|
|
|
|
/*
|
|
* Choose current algorithm implementation.
|
|
* Returns implementation number or -1 if algorithm with such name is not found
|
|
*/
|
|
int mm_zpq_get_algorithm_impl(char name)
|
|
{
|
|
int i;
|
|
if (name != MM_ZPQ_NO_COMPRESSION) {
|
|
for (i = 0; zpq_algorithms[i].name != NULL; i++) {
|
|
if (zpq_algorithms[i].name() == name) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|