| Title: | Circular / Ring Buffers |
| Version: | 1.0.8 |
| Description: | Circular / ring buffers in R and C. There are a couple of different buffers here with different implementations that represent different trade-offs. |
| License: | MIT + file LICENSE |
| URL: | https://mrc-ide.github.io/ring/,https://github.com/mrc-ide/ring |
| BugReports: | https://github.com/mrc-ide/ring/issues |
| Imports: | R6 |
| Suggests: | knitr, rmarkdown, testthat |
| RoxygenNote: | 7.2.3 |
| VignetteBuilder: | knitr |
| Language: | en-GB |
| Encoding: | UTF-8 |
| NeedsCompilation: | yes |
| Packaged: | 2025-12-10 21:10:05 UTC; rich |
| Author: | Rich FitzJohn [aut, cre], Imperial College of Science, Technology and Medicine [cph] |
| Maintainer: | Rich FitzJohn <rich.fitzjohn@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2025-12-13 09:20:02 UTC |
Byte array based ring buffer
Description
Construct a ring buffer where the buffer holds a stream of bytes.Optionally, the buffer can be "strided" so that the bytesnaturally fall into chunks of exactly the same size. It isimplemented in C in the hope that it will be fast, with thelimitation that any data transfer to or from R will always involvecopies.
Usage
ring_buffer_bytes(size, stride = 1L, on_overflow = "overwrite")Arguments
size | Number of elements in the buffer, each of which willbe |
stride | Number of bytes per buffer element. Defaults to 1byte. If you want to store anything other than a bytestream inthe buffer, you will probably want more than one byte perelement; for example, on most R platforms an integer takes 4bytes and a double takes 8 (see |
on_overflow | Behaviour on buffer overflow. The default isto overwrite the oldest elements in the buffer( |
Details
In contrast withring_buffer_env, every element ofthis buffer has the same size; this makes it less flexible(because you have to decide ahead of time what you will bestoring), but at the same time this can make using the buffereasier to think about (because you decided ahead of time what youare storing).
If you want to use this to store fixed-size arrays of integers,numerics, etc, seering_buffer_bytes_typed whichwraps this with fast conversion functions.
If theon_overflow action is"grow" and the buffer overflows,then the size of the buffer will grow geometrically (this is alsothe case if you manually$grow() the buffer withexact = FALSE. When used this way, letn is the number ofadditionalelements that space is needed for;ring then looks at the totalneeded capacity (used plusn relative tosize()).If thebuffer needs to be made larger to fitn elements in then it isgrown by a factor of phi (the golden ratio, approximately 1.6).So if to fitn elements in the buffer needs to be increased insize bym then the smallest ofsize * phi,size * phi^2,size * phi^3, ... will be used as the new size.
In contrast, using thegrow() method withexact = TRUE willalways increase the size of the buffer so long asn ispositive.
Methods
Note that this methods reference section is repeated verbatim betweenthe three main ring buffer classes;ring_buffer_env("env"),ring_buffer_bytes ("bytes") andring_buffer_bytes_typed ("typed"). Almost all methods havethe same arguments and behaviour, but hopefully by listing everything together,the differences between implementations will be a bit more apparent.
resetReset the state of the buffer. This "zeros" the head and tail pointer (and may or may not actually reset the data) so that the buffer can be used as if fresh.
Usage:
reset(clear = FALSE)Arguments:
clear: Logical, indicating if the memory should also be cleared. Generally this is not necessary, but with environment buffers this can let the garbage collector clean up large elements. For the bytes buffer this zeros the memory.
Return value:Nothing; called for the side effect only.
duplicateClone the ring buffer, creating a copy. Copies both the underlying data and the position of the head and tail.
Usage:
duplicate()Return value:A new ring buffer object
growIncrease the size of the buffer by
nelements.Usage:
bytes, typed:
grow(n)env:
grow(n, exact = FALSE)
Arguments:
n: The number of additional elements that space should be reserved for (scalar non-negative integer).\item{\code{exact}: (For bytes buffer only) Logical scalar indicating if growth should increase the size by \emph{exactly} \code{n} elements (if \code{TRUE}) or so that \emph{at least} \code{n} additional elements will fit (growing the buffer geometrically if needed).}
Return value:Nothing; called for the side effect only.
sizeReturn the capacity (maximum size) of the ring buffer
Usage:
env:
size()bytes, typed:
size(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
bytes_dataReturn the total size of the data storage used in this object.
Usage:
env:(not supported)
bytes, typed:
bytes_data()
Return value:A scalar integer
strideLength of each element in the ring buffer, in bytes. Only implemented (and meaningful) for the bytes buffer; the environment buffer does not support this function as it makes no sense there.
Usage:
env:(not supported)
bytes, typed:
stride()
Return value:A scalar integer
usedReturn the amount of space used in the ring buffer.
Usage:
env:
used()bytes, typed:
used(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
freeReturn the amount of space free in the ring buffer.
Usage:
env:
free()bytes, typed:
free(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
is_emptyTest if the ring buffer is empty
Usage:
is_empty()Return value:A scalar logical
is_fullTest if the ring buffer is full
Usage:
is_full()Return value:A scalar logical
head_posReturn the number of entries from the "start" of the ring buffer the head is. This is mostly useful for debugging.
Usage:
env:
head_pos()bytes, typed:
head_pos(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the position should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
tail_posReturn the number of entries from the "start" of the ring buffer the tail is. This is mostly useful for debugging.
Usage:
env:
tail_pos()bytes, typed:
tail_pos(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the position should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
headReturn the contents of the head (the most recently written element in the ring buffer).
Usage:
head()Return value:It depends a little here. For
ring_buffer_envthis is a single R object. Forring_buffer_bytesit is a raw vector, the same length as the stride of the ring buffer. Forring_buffer_bytes_typed, a single R object that has been translated from raw.tailReturn the contents of the tail (the least recently written element in the ring buffer).
Usage:
tail()Return value:As for
headsetSet a number of ring entries to the same value. The exact behaviour here varies depending on the type of ring buffer. This function may overflow the ring buffer; in this case the tail will be moved.
Usage:
set(data, n)Arguments:
data: The data to set each ring element to. For an environment buffer, this may be any R object. For a bytes buffer it may be either a single byte (in which case each ring element will be set to that byte, repeatedstridetimes), or a raw vector of lengthstride.\item{\code{n}: The number of entries to set to \code{data}}
Return value:Invisibly returns the number of elements actually written (which may be less than
nif the buffer overflows). Primarily called for its side effect.pushPush elements onto the ring buffer head. This may overflow the ring buffer, destroying the oldest elements in the buffer (and moving the position of the tail).
Usage:
env:
push(data, iterate = TRUE)bytes, typed:
push(data)
Arguments:
data: Data to push onto the ring buffer. Forring_buffer_bytes, this must be a raw vector with a length that is a multiple of the buffer stride. Forring_buffer_bytes_typedit must be a vector of the appropriate type. Forring_buffer_envit may be an arbitrary R object (but seeiterate.\item{\code{iterate}: For \code{ring_buffer_env} only, changes the behaviour with vectors and lists. Because each element of a \code{ring_buffer_env} can b an arbitrary R object, for a list \code{x} it is ambiguous if \code{push(x)} should push one object onto the buffer, or \code{length(x)} objects (i.e. equivalent to \code{push(x[[1]])}, \code{push(x[[2]])}, etc. The \code{iterate} argument switches between interpretations; if \code{TRUE} (the default) the push will iterate over the object using \code{for (el in x)} (with appropriate S3 dispatch). If \code{iterate = FALSE}, then the entire object is pushed at once, so always updating only by a single element.}
Return value:For
ring_buffer_bytes, the data invisibly. Forring_buffer_bytesandring_buffer_bytes_typed, the position of the head pointer (relative to the beginning of the storage region).takeDestructively take elements from the ring buffer. This consumes from the tail (the least recently added elements). It is not possibly to underflow the buffer; if more elements are requested than can be supplied then an error will be thrown and the state of the buffer unmodified.
Usage:
take(n)Arguments:
n: The number of elements to take.
Return value:For
ring_buffer_envalistofnelements. Forring_buffer_bytes, a raw vector ofn * stridebytes. Forring_buffer_bytes_typed, an vector ofnelements of the storage mode of the ring.readNondestructively read elements from the ring buffer. This is identical to
takeexcept that the state of the buffer is not modified.Usage:
read(n)Arguments:
n: The number of elements to read.
Return value:For
ring_buffer_envalistofnelements. Forring_buffer_bytes, a raw vector ofn * stridebytes. Forring_buffer_bytes_typed, an vector ofnelements of the storage mode of the ring.copyCopy fromthis ring buffer into a different ring buffer. This is destructive with respect to both ring buffers; the tail pointer will be moved in this ring buffer as data are taken, and if the destination ring buffer overflows, the tail pointer will be moved too.
Usage:
copy(dest, n)Arguments:
dest: The destination ring buffer - will be modified by this call.\item{\code{n}: The number of elements to copy}
mirrorMirror the contents ofthis ring buffer into a different ring buffer. This differs from
copyin thatthis ring buffer is unaffected and in thatall of this ring buffer is copied over (including head/tail positions). This provides an alternative way of duplicating state toduplicateif you already have an appropriately sized ring buffer handy. No allocations will be done.Usage:
mirror(dest)Arguments:
dest: The destination ring buffer - will be modified by this call.
Return value:Nothing; called for the side effect only.
head_offsetNondestructively read the contents of the
headof the buffer, offset bynentries.Usage:
head_offset(n)Arguments:
n: Head offset. This moves away from the most recently added item. An offset of 0 reads the most recently added element, 1 reads the element added before that.
Return value:As for
headtail_offsetNondestructively read the contents of the
tailof the buffer, offset bynentries.Usage:
tail_offset(n)Arguments:
n: Tail offset. This moves away from the oldest item. An offset of 0 reads the oldest element, 1 reads the element added after that.
Return value:As for
tail(seehead)take_headAs for
take, but operating on the head rather than the tail. This is destructive with respect to the head.Usage:
take_head(n)Arguments:
n: Number of elements to take.
Return value:As for
takeread_headAs for
read, but operating on the head rather than the tail. This is not destructive with respect to the tail.Usage:
read_head(n)Arguments:
n: Number of elements to read.
Return value:As for
readhead_setSet data to the headwithout advancing. This is useful in cases where the head data will be set and advanced separately (with
head_advance). This is unlikely to be useful for all users. It is used extensively in dde (but called from C).Usage:
head_set(data)Arguments:
data: Data to set into the head. For the bytes buffer this must be exactlystridebytes long, and for the environment buffer it corresponds to a single "element".
Return value:Nothing; called for the side effect only.
head_dataRetrieve the current data stored in the head but not advanced. For many cases this may be junk - if the byte buffer has looped then it will be the bytes that will be overwritten on the next write. However, when using
head_setit will be the data that have been set into the buffer but not yet committed withhead_advance.Usage:
head_data()Return value:As for
headhead_advanceShift the head around one position. This commits any data written by
head_set.Usage:
head_advance()Return value:Nothing; called for the side effect only.
Examples
# Create a ring buffer of 100 bytesb <- ring_buffer_bytes(100)# Get the length, number of used and number of free bytes:b$size()b$used()b$free()# Nothing is used because we're empty:b$is_empty()# To work with a bytes buffer you need to use R's raw vectors;# here are 30 random bytes:bytes <- as.raw(as.integer(sample(256, 30, TRUE) - 1L))bytes# Push these onto the bytes buffer:b$push(bytes)b$used()# The head of the buffer points at the most recently added itemb$head()bytes[[length(bytes)]]# ...and the tail at the oldest (first added in this case)b$tail()bytes[[1]]# Elements are taken from the tail; these will be the oldest items:b$take(8)bytes[1:8]b$used()# To read from the buffer without removing elements, use read:b$read(8)bytes[9:16]# It is not possible to take or read more elements than are# present in the buffer; it will throw an error:## Not run: b$read(50) # error because there are only 22 bytes present## End(Not run)# More elements can be pushed on:b$push(as.raw(rep(0, 50)))b$used()b$read(b$used())# If many new elements are added, they will displace the old elements:b$push(as.raw(1:75))b$read(b$used())Translating bytes ring buffer
Description
This ring buffer is based onring_buffer_bytes butperforms conversion to/from bytes to something useful as data isstored/retrieved from the buffer. This is the interface throughwhichring_buffer_bytes_typed is implemented.
Usage
ring_buffer_bytes_translate(size, stride, to, from, on_overflow = "overwrite")Arguments
size | Number of elements in the buffer, each of which willbe |
stride | Number of bytes per buffer element. Defaults to 1byte. If you want to store anything other than a bytestream inthe buffer, you will probably want more than one byte perelement; for example, on most R platforms an integer takes 4bytes and a double takes 8 (see |
to | Function to convert an R object to a set of exactly |
from | Function to convert a set of bytes to an R object. Itmust take one argument (being a raw vector of a length that is amultiple of |
on_overflow | Behaviour on buffer overflow. The default isto overwrite the oldest elements in the buffer( |
Details
The idea here is that manually working with raw vectors can gettedious, and if you are planning on using a bytes-based bufferwhile working in R you may have a way of doing conversion fromand to R objects. This interface lets you specify the functionsonce and then will apply your conversion function in every casewhere they are needed.
Methods
Note that this methods reference section is repeated verbatim betweenthe three main ring buffer classes;ring_buffer_env("env"),ring_buffer_bytes ("bytes") andring_buffer_bytes_typed ("typed"). Almost all methods havethe same arguments and behaviour, but hopefully by listing everything together,the differences between implementations will be a bit more apparent.
resetReset the state of the buffer. This "zeros" the head and tail pointer (and may or may not actually reset the data) so that the buffer can be used as if fresh.
Usage:
reset(clear = FALSE)Arguments:
clear: Logical, indicating if the memory should also be cleared. Generally this is not necessary, but with environment buffers this can let the garbage collector clean up large elements. For the bytes buffer this zeros the memory.
Return value:Nothing; called for the side effect only.
duplicateClone the ring buffer, creating a copy. Copies both the underlying data and the position of the head and tail.
Usage:
duplicate()Return value:A new ring buffer object
growIncrease the size of the buffer by
nelements.Usage:
bytes, typed:
grow(n)env:
grow(n, exact = FALSE)
Arguments:
n: The number of additional elements that space should be reserved for (scalar non-negative integer).\item{\code{exact}: (For bytes buffer only) Logical scalar indicating if growth should increase the size by \emph{exactly} \code{n} elements (if \code{TRUE}) or so that \emph{at least} \code{n} additional elements will fit (growing the buffer geometrically if needed).}
Return value:Nothing; called for the side effect only.
sizeReturn the capacity (maximum size) of the ring buffer
Usage:
env:
size()bytes, typed:
size(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
bytes_dataReturn the total size of the data storage used in this object.
Usage:
env:(not supported)
bytes, typed:
bytes_data()
Return value:A scalar integer
strideLength of each element in the ring buffer, in bytes. Only implemented (and meaningful) for the bytes buffer; the environment buffer does not support this function as it makes no sense there.
Usage:
env:(not supported)
bytes, typed:
stride()
Return value:A scalar integer
usedReturn the amount of space used in the ring buffer.
Usage:
env:
used()bytes, typed:
used(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
freeReturn the amount of space free in the ring buffer.
Usage:
env:
free()bytes, typed:
free(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
is_emptyTest if the ring buffer is empty
Usage:
is_empty()Return value:A scalar logical
is_fullTest if the ring buffer is full
Usage:
is_full()Return value:A scalar logical
head_posReturn the number of entries from the "start" of the ring buffer the head is. This is mostly useful for debugging.
Usage:
env:
head_pos()bytes, typed:
head_pos(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the position should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
tail_posReturn the number of entries from the "start" of the ring buffer the tail is. This is mostly useful for debugging.
Usage:
env:
tail_pos()bytes, typed:
tail_pos(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the position should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
headReturn the contents of the head (the most recently written element in the ring buffer).
Usage:
head()Return value:It depends a little here. For
ring_buffer_envthis is a single R object. Forring_buffer_bytesit is a raw vector, the same length as the stride of the ring buffer. Forring_buffer_bytes_typed, a single R object that has been translated from raw.tailReturn the contents of the tail (the least recently written element in the ring buffer).
Usage:
tail()Return value:As for
headsetSet a number of ring entries to the same value. The exact behaviour here varies depending on the type of ring buffer. This function may overflow the ring buffer; in this case the tail will be moved.
Usage:
set(data, n)Arguments:
data: The data to set each ring element to. For an environment buffer, this may be any R object. For a bytes buffer it may be either a single byte (in which case each ring element will be set to that byte, repeatedstridetimes), or a raw vector of lengthstride.\item{\code{n}: The number of entries to set to \code{data}}
Return value:Invisibly returns the number of elements actually written (which may be less than
nif the buffer overflows). Primarily called for its side effect.pushPush elements onto the ring buffer head. This may overflow the ring buffer, destroying the oldest elements in the buffer (and moving the position of the tail).
Usage:
env:
push(data, iterate = TRUE)bytes, typed:
push(data)
Arguments:
data: Data to push onto the ring buffer. Forring_buffer_bytes, this must be a raw vector with a length that is a multiple of the buffer stride. Forring_buffer_bytes_typedit must be a vector of the appropriate type. Forring_buffer_envit may be an arbitrary R object (but seeiterate.\item{\code{iterate}: For \code{ring_buffer_env} only, changes the behaviour with vectors and lists. Because each element of a \code{ring_buffer_env} can b an arbitrary R object, for a list \code{x} it is ambiguous if \code{push(x)} should push one object onto the buffer, or \code{length(x)} objects (i.e. equivalent to \code{push(x[[1]])}, \code{push(x[[2]])}, etc. The \code{iterate} argument switches between interpretations; if \code{TRUE} (the default) the push will iterate over the object using \code{for (el in x)} (with appropriate S3 dispatch). If \code{iterate = FALSE}, then the entire object is pushed at once, so always updating only by a single element.}
Return value:For
ring_buffer_bytes, the data invisibly. Forring_buffer_bytesandring_buffer_bytes_typed, the position of the head pointer (relative to the beginning of the storage region).takeDestructively take elements from the ring buffer. This consumes from the tail (the least recently added elements). It is not possibly to underflow the buffer; if more elements are requested than can be supplied then an error will be thrown and the state of the buffer unmodified.
Usage:
take(n)Arguments:
n: The number of elements to take.
Return value:For
ring_buffer_envalistofnelements. Forring_buffer_bytes, a raw vector ofn * stridebytes. Forring_buffer_bytes_typed, an vector ofnelements of the storage mode of the ring.readNondestructively read elements from the ring buffer. This is identical to
takeexcept that the state of the buffer is not modified.Usage:
read(n)Arguments:
n: The number of elements to read.
Return value:For
ring_buffer_envalistofnelements. Forring_buffer_bytes, a raw vector ofn * stridebytes. Forring_buffer_bytes_typed, an vector ofnelements of the storage mode of the ring.copyCopy fromthis ring buffer into a different ring buffer. This is destructive with respect to both ring buffers; the tail pointer will be moved in this ring buffer as data are taken, and if the destination ring buffer overflows, the tail pointer will be moved too.
Usage:
copy(dest, n)Arguments:
dest: The destination ring buffer - will be modified by this call.\item{\code{n}: The number of elements to copy}
mirrorMirror the contents ofthis ring buffer into a different ring buffer. This differs from
copyin thatthis ring buffer is unaffected and in thatall of this ring buffer is copied over (including head/tail positions). This provides an alternative way of duplicating state toduplicateif you already have an appropriately sized ring buffer handy. No allocations will be done.Usage:
mirror(dest)Arguments:
dest: The destination ring buffer - will be modified by this call.
Return value:Nothing; called for the side effect only.
head_offsetNondestructively read the contents of the
headof the buffer, offset bynentries.Usage:
head_offset(n)Arguments:
n: Head offset. This moves away from the most recently added item. An offset of 0 reads the most recently added element, 1 reads the element added before that.
Return value:As for
headtail_offsetNondestructively read the contents of the
tailof the buffer, offset bynentries.Usage:
tail_offset(n)Arguments:
n: Tail offset. This moves away from the oldest item. An offset of 0 reads the oldest element, 1 reads the element added after that.
Return value:As for
tail(seehead)take_headAs for
take, but operating on the head rather than the tail. This is destructive with respect to the head.Usage:
take_head(n)Arguments:
n: Number of elements to take.
Return value:As for
takeread_headAs for
read, but operating on the head rather than the tail. This is not destructive with respect to the tail.Usage:
read_head(n)Arguments:
n: Number of elements to read.
Return value:As for
readhead_setSet data to the headwithout advancing. This is useful in cases where the head data will be set and advanced separately (with
head_advance). This is unlikely to be useful for all users. It is used extensively in dde (but called from C).Usage:
head_set(data)Arguments:
data: Data to set into the head. For the bytes buffer this must be exactlystridebytes long, and for the environment buffer it corresponds to a single "element".
Return value:Nothing; called for the side effect only.
head_dataRetrieve the current data stored in the head but not advanced. For many cases this may be junk - if the byte buffer has looped then it will be the bytes that will be overwritten on the next write. However, when using
head_setit will be the data that have been set into the buffer but not yet committed withhead_advance.Usage:
head_data()Return value:As for
headhead_advanceShift the head around one position. This commits any data written by
head_set.Usage:
head_advance()Return value:Nothing; called for the side effect only.
Author(s)
Rich FitzJohn
Examples
# The "typed" ring buffers do not allow for character vectors to# be stored, because strings are generally hard and have unknown# lengths. But if you wanted to store strings that are *always*# the same length, this is straightforward to do.# You can convert from string to bytes with charToRaw (or# as.raw(utf8ToInt(x))):bytes <- charToRaw("hello!")bytes# And back again with rawToChar (or intToUtf8(as.integer(x)))rawToChar(bytes)# So with these functions we can make a buffer for storing# fixed-length strings:b <- ring_buffer_bytes_translate(100, 8, charToRaw, rawToChar)# And with this we can store 8 character strings:b$push("abcdefgh")b$tail()# Other length strings cannot be added:try( b$push("hello!")) # error# Because the 'from' and 'to' arguments can be arbitrary R# functions we could tweak this to pad the character vector with# null bytes, and strip these off on return:char_to_raw <- function(x, max_len) { if (!(is.character(x) && length(x) == 1L)) { stop("Expected a single string") } n <- nchar(x) if (n > max_len) { stop("String is too long") } c(charToRaw(x), rep(raw(1), max_len - n))}char_from_raw <- function(x) { rawToChar(x[x != raw(1)])}# Because max_len is the same thing as stride, wrap this all up a# little:char_buffer <- function(size, max_len) { to <- function(x) char_to_raw(x, max_len) ring_buffer_bytes_translate(size, max_len, to, char_from_raw)}b <- char_buffer(100, 30) # 100 elements of up to 30 characters eachb$push("x")b$tail()b$push("hello world!")b$head()try( b$push("supercalafragalisticexpealadocious")) # error: string is too longTyped bytes ring buffer
Description
Create a ring buffer, backed by aring_buffer_bytes,where each element corresponds to a fixed-size vector of one ofR's atomic numeric types (logical, integer, double, and complex).
Usage
ring_buffer_bytes_typed(size, what, len = NULL, on_overflow = "overwrite")Arguments
size | The maximum number of elements the buffer can hold.Each element will be multiple bytes long. |
what | Either a vector on the style of |
len | If given, then the length of the storage. If it isgiven, then if |
on_overflow | Behaviour on buffer overflow. The default isto overwrite the oldest elements in the buffer( |
Details
Note that a logical ring buffer and an integer ring buffer takethe same number of bytes because a logical vector is stored as aninteger (4 bytes per element) to deal with missing values; see"writing R extensions".
Note that it is not possible to store character vectors in a ringbuffer of this type because each element of a character vector canbe any number of bytes.
Methods
Note that this methods reference section is repeated verbatim betweenthe three main ring buffer classes;ring_buffer_env("env"),ring_buffer_bytes ("bytes") andring_buffer_bytes_typed ("typed"). Almost all methods havethe same arguments and behaviour, but hopefully by listing everything together,the differences between implementations will be a bit more apparent.
resetReset the state of the buffer. This "zeros" the head and tail pointer (and may or may not actually reset the data) so that the buffer can be used as if fresh.
Usage:
reset(clear = FALSE)Arguments:
clear: Logical, indicating if the memory should also be cleared. Generally this is not necessary, but with environment buffers this can let the garbage collector clean up large elements. For the bytes buffer this zeros the memory.
Return value:Nothing; called for the side effect only.
duplicateClone the ring buffer, creating a copy. Copies both the underlying data and the position of the head and tail.
Usage:
duplicate()Return value:A new ring buffer object
growIncrease the size of the buffer by
nelements.Usage:
bytes, typed:
grow(n)env:
grow(n, exact = FALSE)
Arguments:
n: The number of additional elements that space should be reserved for (scalar non-negative integer).\item{\code{exact}: (For bytes buffer only) Logical scalar indicating if growth should increase the size by \emph{exactly} \code{n} elements (if \code{TRUE}) or so that \emph{at least} \code{n} additional elements will fit (growing the buffer geometrically if needed).}
Return value:Nothing; called for the side effect only.
sizeReturn the capacity (maximum size) of the ring buffer
Usage:
env:
size()bytes, typed:
size(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
bytes_dataReturn the total size of the data storage used in this object.
Usage:
env:(not supported)
bytes, typed:
bytes_data()
Return value:A scalar integer
strideLength of each element in the ring buffer, in bytes. Only implemented (and meaningful) for the bytes buffer; the environment buffer does not support this function as it makes no sense there.
Usage:
env:(not supported)
bytes, typed:
stride()
Return value:A scalar integer
usedReturn the amount of space used in the ring buffer.
Usage:
env:
used()bytes, typed:
used(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
freeReturn the amount of space free in the ring buffer.
Usage:
env:
free()bytes, typed:
free(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
is_emptyTest if the ring buffer is empty
Usage:
is_empty()Return value:A scalar logical
is_fullTest if the ring buffer is full
Usage:
is_full()Return value:A scalar logical
head_posReturn the number of entries from the "start" of the ring buffer the head is. This is mostly useful for debugging.
Usage:
env:
head_pos()bytes, typed:
head_pos(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the position should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
tail_posReturn the number of entries from the "start" of the ring buffer the tail is. This is mostly useful for debugging.
Usage:
env:
tail_pos()bytes, typed:
tail_pos(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the position should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
headReturn the contents of the head (the most recently written element in the ring buffer).
Usage:
head()Return value:It depends a little here. For
ring_buffer_envthis is a single R object. Forring_buffer_bytesit is a raw vector, the same length as the stride of the ring buffer. Forring_buffer_bytes_typed, a single R object that has been translated from raw.tailReturn the contents of the tail (the least recently written element in the ring buffer).
Usage:
tail()Return value:As for
headsetSet a number of ring entries to the same value. The exact behaviour here varies depending on the type of ring buffer. This function may overflow the ring buffer; in this case the tail will be moved.
Usage:
set(data, n)Arguments:
data: The data to set each ring element to. For an environment buffer, this may be any R object. For a bytes buffer it may be either a single byte (in which case each ring element will be set to that byte, repeatedstridetimes), or a raw vector of lengthstride.\item{\code{n}: The number of entries to set to \code{data}}
Return value:Invisibly returns the number of elements actually written (which may be less than
nif the buffer overflows). Primarily called for its side effect.pushPush elements onto the ring buffer head. This may overflow the ring buffer, destroying the oldest elements in the buffer (and moving the position of the tail).
Usage:
env:
push(data, iterate = TRUE)bytes, typed:
push(data)
Arguments:
data: Data to push onto the ring buffer. Forring_buffer_bytes, this must be a raw vector with a length that is a multiple of the buffer stride. Forring_buffer_bytes_typedit must be a vector of the appropriate type. Forring_buffer_envit may be an arbitrary R object (but seeiterate.\item{\code{iterate}: For \code{ring_buffer_env} only, changes the behaviour with vectors and lists. Because each element of a \code{ring_buffer_env} can b an arbitrary R object, for a list \code{x} it is ambiguous if \code{push(x)} should push one object onto the buffer, or \code{length(x)} objects (i.e. equivalent to \code{push(x[[1]])}, \code{push(x[[2]])}, etc. The \code{iterate} argument switches between interpretations; if \code{TRUE} (the default) the push will iterate over the object using \code{for (el in x)} (with appropriate S3 dispatch). If \code{iterate = FALSE}, then the entire object is pushed at once, so always updating only by a single element.}
Return value:For
ring_buffer_bytes, the data invisibly. Forring_buffer_bytesandring_buffer_bytes_typed, the position of the head pointer (relative to the beginning of the storage region).takeDestructively take elements from the ring buffer. This consumes from the tail (the least recently added elements). It is not possibly to underflow the buffer; if more elements are requested than can be supplied then an error will be thrown and the state of the buffer unmodified.
Usage:
take(n)Arguments:
n: The number of elements to take.
Return value:For
ring_buffer_envalistofnelements. Forring_buffer_bytes, a raw vector ofn * stridebytes. Forring_buffer_bytes_typed, an vector ofnelements of the storage mode of the ring.readNondestructively read elements from the ring buffer. This is identical to
takeexcept that the state of the buffer is not modified.Usage:
read(n)Arguments:
n: The number of elements to read.
Return value:For
ring_buffer_envalistofnelements. Forring_buffer_bytes, a raw vector ofn * stridebytes. Forring_buffer_bytes_typed, an vector ofnelements of the storage mode of the ring.copyCopy fromthis ring buffer into a different ring buffer. This is destructive with respect to both ring buffers; the tail pointer will be moved in this ring buffer as data are taken, and if the destination ring buffer overflows, the tail pointer will be moved too.
Usage:
copy(dest, n)Arguments:
dest: The destination ring buffer - will be modified by this call.\item{\code{n}: The number of elements to copy}
mirrorMirror the contents ofthis ring buffer into a different ring buffer. This differs from
copyin thatthis ring buffer is unaffected and in thatall of this ring buffer is copied over (including head/tail positions). This provides an alternative way of duplicating state toduplicateif you already have an appropriately sized ring buffer handy. No allocations will be done.Usage:
mirror(dest)Arguments:
dest: The destination ring buffer - will be modified by this call.
Return value:Nothing; called for the side effect only.
head_offsetNondestructively read the contents of the
headof the buffer, offset bynentries.Usage:
head_offset(n)Arguments:
n: Head offset. This moves away from the most recently added item. An offset of 0 reads the most recently added element, 1 reads the element added before that.
Return value:As for
headtail_offsetNondestructively read the contents of the
tailof the buffer, offset bynentries.Usage:
tail_offset(n)Arguments:
n: Tail offset. This moves away from the oldest item. An offset of 0 reads the oldest element, 1 reads the element added after that.
Return value:As for
tail(seehead)take_headAs for
take, but operating on the head rather than the tail. This is destructive with respect to the head.Usage:
take_head(n)Arguments:
n: Number of elements to take.
Return value:As for
takeread_headAs for
read, but operating on the head rather than the tail. This is not destructive with respect to the tail.Usage:
read_head(n)Arguments:
n: Number of elements to read.
Return value:As for
readhead_setSet data to the headwithout advancing. This is useful in cases where the head data will be set and advanced separately (with
head_advance). This is unlikely to be useful for all users. It is used extensively in dde (but called from C).Usage:
head_set(data)Arguments:
data: Data to set into the head. For the bytes buffer this must be exactlystridebytes long, and for the environment buffer it corresponds to a single "element".
Return value:Nothing; called for the side effect only.
head_dataRetrieve the current data stored in the head but not advanced. For many cases this may be junk - if the byte buffer has looped then it will be the bytes that will be overwritten on the next write. However, when using
head_setit will be the data that have been set into the buffer but not yet committed withhead_advance.Usage:
head_data()Return value:As for
headhead_advanceShift the head around one position. This commits any data written by
head_set.Usage:
head_advance()Return value:Nothing; called for the side effect only.
Author(s)
Rich FitzJohn
Examples
# Create a ring buffer of 30 integers:b <- ring_buffer_bytes_typed(30, integer(1))# Alternatively you can create the same buffer this way:b <- ring_buffer_bytes_typed(30, "integer", 1)# The buffer is empty to start withb$is_empty()# Note that the buffer has a stride of 4 (see ?ring_buffer_bytes)b$stride()# Push some numbers into the buffer:b$push(as.integer(1:10))# Report the number of elements used:b$used()# Get the first added element:b$tail()# The buffer behaves basically the same way now as# "ring_buffer_env" but will typecheck all inputs:## Not run: b$push(pi) # error because not an integer b$push(1) # error because not an integer (you must convert to int)## End(Not run)# Recycling: the typed buffer operates by converting the input# vector to a set of bytes and then pushing them onto the buffer;# this works so long as the vector of bytes has the correct# length.b <- ring_buffer_bytes_typed(30, integer(3))# These both fail because 2 and 4 do not end up as multiples of 3:## Not run: b$push(c(1L, 2L)) b$push(c(1L, 2L, 3L, 4L))## End(Not run)# But this is fine:b$push(seq_len(6))b$tail()b$tail_offset(1)Environment-based ring buffer
Description
An environment based ring buffer. In contrast withring_buffer_bytes, this ring buffer is trulycircular, implemented as a doubly linked list that loops back onitself. Each element of the ring buffer can hold an arbitrary Robject, and no checking is done to make sure that objects aresimilar types; in this way they are most similar to a circularversion of an Rlist.
Usage
ring_buffer_env(size, on_overflow = "overwrite")Arguments
size | The (maximum) number of entries the buffer cancontain. |
on_overflow | Behaviour on buffer overflow. The default isto overwrite the oldest elements in the buffer( |
Details
When pushing objects onto the buffer, you must be careful abouttheiterate argument. By default if the object has alength() greater than 1 then$push() will iterateover the object (equivalent to$push(data[[1]], iterate=FALSE),$push(data[[2]], iterate=FALSE), and soon).
For more information and usage examples, see the vignette(vignette("ring")).
On underflow (and overflow ifon_overflow = "error")ring will raise custom exceptions that can be caughtspecially bytryCatch. These will have classring_underflow (andring_overflow for overflow). Thisis not supported in the bytes buffer yet. See the examples forusage.
Methods
Note that this methods reference section is repeated verbatim betweenthe three main ring buffer classes;ring_buffer_env("env"),ring_buffer_bytes ("bytes") andring_buffer_bytes_typed ("typed"). Almost all methods havethe same arguments and behaviour, but hopefully by listing everything together,the differences between implementations will be a bit more apparent.
resetReset the state of the buffer. This "zeros" the head and tail pointer (and may or may not actually reset the data) so that the buffer can be used as if fresh.
Usage:
reset(clear = FALSE)Arguments:
clear: Logical, indicating if the memory should also be cleared. Generally this is not necessary, but with environment buffers this can let the garbage collector clean up large elements. For the bytes buffer this zeros the memory.
Return value:Nothing; called for the side effect only.
duplicateClone the ring buffer, creating a copy. Copies both the underlying data and the position of the head and tail.
Usage:
duplicate()Return value:A new ring buffer object
growIncrease the size of the buffer by
nelements.Usage:
bytes, typed:
grow(n)env:
grow(n, exact = FALSE)
Arguments:
n: The number of additional elements that space should be reserved for (scalar non-negative integer).\item{\code{exact}: (For bytes buffer only) Logical scalar indicating if growth should increase the size by \emph{exactly} \code{n} elements (if \code{TRUE}) or so that \emph{at least} \code{n} additional elements will fit (growing the buffer geometrically if needed).}
Return value:Nothing; called for the side effect only.
sizeReturn the capacity (maximum size) of the ring buffer
Usage:
env:
size()bytes, typed:
size(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
bytes_dataReturn the total size of the data storage used in this object.
Usage:
env:(not supported)
bytes, typed:
bytes_data()
Return value:A scalar integer
strideLength of each element in the ring buffer, in bytes. Only implemented (and meaningful) for the bytes buffer; the environment buffer does not support this function as it makes no sense there.
Usage:
env:(not supported)
bytes, typed:
stride()
Return value:A scalar integer
usedReturn the amount of space used in the ring buffer.
Usage:
env:
used()bytes, typed:
used(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
freeReturn the amount of space free in the ring buffer.
Usage:
env:
free()bytes, typed:
free(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the size should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
is_emptyTest if the ring buffer is empty
Usage:
is_empty()Return value:A scalar logical
is_fullTest if the ring buffer is full
Usage:
is_full()Return value:A scalar logical
head_posReturn the number of entries from the "start" of the ring buffer the head is. This is mostly useful for debugging.
Usage:
env:
head_pos()bytes, typed:
head_pos(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the position should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
tail_posReturn the number of entries from the "start" of the ring buffer the tail is. This is mostly useful for debugging.
Usage:
env:
tail_pos()bytes, typed:
tail_pos(bytes = FALSE)
Arguments:
bytes: (forring_buffer_bytesonly) Logical, indicating if the position should be returned in bytes (rather than logical entries, which is the default).
Return value:A scalar integer
headReturn the contents of the head (the most recently written element in the ring buffer).
Usage:
head()Return value:It depends a little here. For
ring_buffer_envthis is a single R object. Forring_buffer_bytesit is a raw vector, the same length as the stride of the ring buffer. Forring_buffer_bytes_typed, a single R object that has been translated from raw.tailReturn the contents of the tail (the least recently written element in the ring buffer).
Usage:
tail()Return value:As for
headsetSet a number of ring entries to the same value. The exact behaviour here varies depending on the type of ring buffer. This function may overflow the ring buffer; in this case the tail will be moved.
Usage:
set(data, n)Arguments:
data: The data to set each ring element to. For an environment buffer, this may be any R object. For a bytes buffer it may be either a single byte (in which case each ring element will be set to that byte, repeatedstridetimes), or a raw vector of lengthstride.\item{\code{n}: The number of entries to set to \code{data}}
Return value:Invisibly returns the number of elements actually written (which may be less than
nif the buffer overflows). Primarily called for its side effect.pushPush elements onto the ring buffer head. This may overflow the ring buffer, destroying the oldest elements in the buffer (and moving the position of the tail).
Usage:
env:
push(data, iterate = TRUE)bytes, typed:
push(data)
Arguments:
data: Data to push onto the ring buffer. Forring_buffer_bytes, this must be a raw vector with a length that is a multiple of the buffer stride. Forring_buffer_bytes_typedit must be a vector of the appropriate type. Forring_buffer_envit may be an arbitrary R object (but seeiterate.\item{\code{iterate}: For \code{ring_buffer_env} only, changes the behaviour with vectors and lists. Because each element of a \code{ring_buffer_env} can b an arbitrary R object, for a list \code{x} it is ambiguous if \code{push(x)} should push one object onto the buffer, or \code{length(x)} objects (i.e. equivalent to \code{push(x[[1]])}, \code{push(x[[2]])}, etc. The \code{iterate} argument switches between interpretations; if \code{TRUE} (the default) the push will iterate over the object using \code{for (el in x)} (with appropriate S3 dispatch). If \code{iterate = FALSE}, then the entire object is pushed at once, so always updating only by a single element.}
Return value:For
ring_buffer_bytes, the data invisibly. Forring_buffer_bytesandring_buffer_bytes_typed, the position of the head pointer (relative to the beginning of the storage region).takeDestructively take elements from the ring buffer. This consumes from the tail (the least recently added elements). It is not possibly to underflow the buffer; if more elements are requested than can be supplied then an error will be thrown and the state of the buffer unmodified.
Usage:
take(n)Arguments:
n: The number of elements to take.
Return value:For
ring_buffer_envalistofnelements. Forring_buffer_bytes, a raw vector ofn * stridebytes. Forring_buffer_bytes_typed, an vector ofnelements of the storage mode of the ring.readNondestructively read elements from the ring buffer. This is identical to
takeexcept that the state of the buffer is not modified.Usage:
read(n)Arguments:
n: The number of elements to read.
Return value:For
ring_buffer_envalistofnelements. Forring_buffer_bytes, a raw vector ofn * stridebytes. Forring_buffer_bytes_typed, an vector ofnelements of the storage mode of the ring.copyCopy fromthis ring buffer into a different ring buffer. This is destructive with respect to both ring buffers; the tail pointer will be moved in this ring buffer as data are taken, and if the destination ring buffer overflows, the tail pointer will be moved too.
Usage:
copy(dest, n)Arguments:
dest: The destination ring buffer - will be modified by this call.\item{\code{n}: The number of elements to copy}
mirrorMirror the contents ofthis ring buffer into a different ring buffer. This differs from
copyin thatthis ring buffer is unaffected and in thatall of this ring buffer is copied over (including head/tail positions). This provides an alternative way of duplicating state toduplicateif you already have an appropriately sized ring buffer handy. No allocations will be done.Usage:
mirror(dest)Arguments:
dest: The destination ring buffer - will be modified by this call.
Return value:Nothing; called for the side effect only.
head_offsetNondestructively read the contents of the
headof the buffer, offset bynentries.Usage:
head_offset(n)Arguments:
n: Head offset. This moves away from the most recently added item. An offset of 0 reads the most recently added element, 1 reads the element added before that.
Return value:As for
headtail_offsetNondestructively read the contents of the
tailof the buffer, offset bynentries.Usage:
tail_offset(n)Arguments:
n: Tail offset. This moves away from the oldest item. An offset of 0 reads the oldest element, 1 reads the element added after that.
Return value:As for
tail(seehead)take_headAs for
take, but operating on the head rather than the tail. This is destructive with respect to the head.Usage:
take_head(n)Arguments:
n: Number of elements to take.
Return value:As for
takeread_headAs for
read, but operating on the head rather than the tail. This is not destructive with respect to the tail.Usage:
read_head(n)Arguments:
n: Number of elements to read.
Return value:As for
readhead_setSet data to the headwithout advancing. This is useful in cases where the head data will be set and advanced separately (with
head_advance). This is unlikely to be useful for all users. It is used extensively in dde (but called from C).Usage:
head_set(data)Arguments:
data: Data to set into the head. For the bytes buffer this must be exactlystridebytes long, and for the environment buffer it corresponds to a single "element".
Return value:Nothing; called for the side effect only.
head_dataRetrieve the current data stored in the head but not advanced. For many cases this may be junk - if the byte buffer has looped then it will be the bytes that will be overwritten on the next write. However, when using
head_setit will be the data that have been set into the buffer but not yet committed withhead_advance.Usage:
head_data()Return value:As for
headhead_advanceShift the head around one position. This commits any data written by
head_set.Usage:
head_advance()Return value:Nothing; called for the side effect only.
Author(s)
Rich FitzJohn
Examples
buf <- ring_buffer_env(10)buf$push(1:10)buf$take(3)buf$push(11:15)buf$take(2)# The "on_overflow" argument by default allows for the buffer to# overwrite on overflow.buf <- ring_buffer_env(10)buf$push(1:10)unlist(buf$read(buf$used())) # 1:10# Over-write the first 5buf$push(11:15)unlist(buf$read(buf$used())) # 6:15# Unlike ring_buffer_bytes, these ring buffers can hold any R# object. However, you must be careful about use of iterate!buf$push(lm(mpg ~ cyl, mtcars), iterate = FALSE)buf$take(1)# Alternatively, grow the buffer as overwriting happensbuf <- ring_buffer_env(10, "grow")buf$push(1:10)buf$push(11:15)unlist(buf$read(buf$used())) # 1:15# Or throw an error on overflowbuf <- ring_buffer_env(10, "error")buf$push(1:10)try(buf$push(11:15))# The errors that are thrown on underflow / overflow are typed so# can be caught by tryCatch:tryCatch(buf$read(100), ring_underflow = function(e) message("nope"))tryCatch(buf$push(100), ring_overflow = function(e) message("nope again"))