#[cfg(feature = "burn")]
pub mod burn;
mod aligned;
#[doc(hidden)]
pub use aligned::CacheAlignedBuffer;
pub mod circuit;
pub mod local;
#[doc(hidden)]
pub mod queued;
#[cfg(not(target_arch = "wasm32"))]
pub mod circular;
pub mod slab;
#[cfg(feature = "wgpu")]
pub mod wgpu;
use std::any::Any;
use std::any::TypeId;
use std::fmt::Debug;
use std::future::Future;
use std::marker::PhantomData;
use std::mem::size_of;
use std::num::NonZeroUsize;
use std::sync::Arc;
use crate::runtime::BlockId;
use crate::runtime::BlockMessage;
use crate::runtime::Error;
use crate::runtime::PortIndex;
pub use crate::runtime::block_inbox::BlockInbox;
pub use crate::runtime::block_inbox::LocalBlockInbox;
use crate::runtime::config::config;
use crate::runtime::dev::BlockNotifier;
use crate::runtime::dev::ItemTag;
use crate::runtime::dev::LocalBlockNotifier;
use crate::runtime::dev::Tag;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BufferRequirements {
min_items: Option<usize>,
min_buffer_size_in_items: Option<usize>,
}
impl BufferRequirements {
pub const fn new() -> Self {
Self {
min_items: None,
min_buffer_size_in_items: None,
}
}
pub const fn with_min_items(min_items: usize) -> Self {
Self {
min_items: Some(min_items),
min_buffer_size_in_items: None,
}
}
pub const fn min_items(&self) -> Option<usize> {
self.min_items
}
pub const fn min_buffer_size_in_items(&self) -> Option<usize> {
self.min_buffer_size_in_items
}
pub fn set_min_items(&mut self, min_items: usize) {
self.min_items = Some(min_items);
}
pub fn raise_min_items(&mut self, min_items: usize) {
self.min_items = Some(self.min_items.unwrap_or(0).max(min_items));
}
pub fn set_min_buffer_size_in_items(&mut self, min_items: usize) {
self.min_buffer_size_in_items = Some(min_items);
}
pub fn raise_min_buffer_size_in_items(&mut self, min_items: usize) {
self.min_buffer_size_in_items =
Some(self.min_buffer_size_in_items.unwrap_or(0).max(min_items));
}
pub fn merge(&mut self, other: Self) {
if let Some(min_items) = other.min_items {
self.raise_min_items(min_items);
}
if let Some(min_items) = other.min_buffer_size_in_items {
self.raise_min_buffer_size_in_items(min_items);
}
}
}
pub trait BufferNotifier: Clone + Debug + 'static {
fn notify(&self);
}
impl BufferNotifier for BlockNotifier {
#[inline(always)]
fn notify(&self) {
BlockNotifier::notify(self);
}
}
impl BufferNotifier for LocalBlockNotifier {
#[inline(always)]
fn notify(&self) {
LocalBlockNotifier::notify(self);
}
}
pub trait BufferInbox: Clone + Debug + 'static {
type Notifier: BufferNotifier;
fn from_port_inboxes(inboxes: &PortInboxes) -> Self;
fn notifier(&self) -> Self::Notifier;
fn notify(&self);
fn stream_input_done(&self, input_id: PortIndex) -> impl Future<Output = Result<(), Error>>;
fn stream_output_done(&self, output_id: PortIndex) -> impl Future<Output = Result<(), Error>>;
}
impl BufferInbox for BlockInbox {
type Notifier = BlockNotifier;
fn from_port_inboxes(inboxes: &PortInboxes) -> Self {
inboxes.thread_safe_inbox()
}
fn notifier(&self) -> Self::Notifier {
BlockInbox::notifier(self)
}
#[inline(always)]
fn notify(&self) {
BlockInbox::notify(self);
}
async fn stream_input_done(&self, input_id: PortIndex) -> Result<(), Error> {
BlockInbox::stream_input_done(self, input_id).await
}
async fn stream_output_done(&self, output_id: PortIndex) -> Result<(), Error> {
BlockInbox::stream_output_done(self, output_id).await
}
}
impl BufferInbox for LocalBlockInbox {
type Notifier = LocalBlockNotifier;
fn from_port_inboxes(inboxes: &PortInboxes) -> Self {
inboxes.local_inbox()
}
fn notifier(&self) -> Self::Notifier {
LocalBlockInbox::notifier(self)
}
#[inline(always)]
fn notify(&self) {
LocalBlockInbox::notify(self);
}
async fn stream_input_done(&self, input_id: PortIndex) -> Result<(), Error> {
LocalBlockInbox::send(self, BlockMessage::StreamInputDone { input_id }).await
}
async fn stream_output_done(&self, output_id: PortIndex) -> Result<(), Error> {
LocalBlockInbox::send(self, BlockMessage::StreamOutputDone { output_id }).await
}
}
#[derive(Clone, Debug)]
pub struct PortInboxes {
thread_safe: BlockInbox,
local: Option<LocalBlockInbox>,
}
impl PortInboxes {
pub fn thread_safe(thread_safe: BlockInbox) -> Self {
Self {
thread_safe,
local: None,
}
}
pub fn local(thread_safe: BlockInbox, local: LocalBlockInbox) -> Self {
Self {
thread_safe,
local: Some(local),
}
}
pub fn thread_safe_inbox(&self) -> BlockInbox {
self.thread_safe.clone()
}
pub fn local_inbox(&self) -> LocalBlockInbox {
self.local
.clone()
.expect("local buffer used outside a local-domain block")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_inbox_notifier<I, N>()
where
I: BufferInbox<Notifier = N>,
N: BufferNotifier,
{
}
#[test]
fn built_in_inboxes_select_concrete_notifiers() {
assert_inbox_notifier::<BlockInbox, BlockNotifier>();
assert_inbox_notifier::<LocalBlockInbox, LocalBlockNotifier>();
}
#[test]
fn erased_connection_capability_follows_thread_safe_connect_impl() {
let writer = super::DefaultCpuWriter::<u8>::default();
assert!(DynBufferWriter::thread_safe_connect(&writer).is_some());
let writer = super::local::Writer::<u8>::default();
assert!(DynBufferWriter::thread_safe_connect(&writer).is_none());
}
#[test]
fn buffer_requirements_are_extracted_from_configured_ports() {
let mut writer = super::DefaultCpuWriter::<u8>::default();
BufferWriter::set_min_items(&mut writer, 4);
BufferWriter::set_min_buffer_size_in_items(&mut writer, 32);
let mut reader = super::DefaultCpuReader::<u8>::default();
BufferReader::set_min_items(&mut reader, 8);
BufferReader::set_min_buffer_size_in_items(&mut reader, 64);
let writer_requirements = BufferWriter::buffer_requirements(&writer);
let reader_requirements = BufferReader::buffer_requirements(&reader);
assert_eq!(writer_requirements.min_items(), Some(4));
assert_eq!(writer_requirements.min_buffer_size_in_items(), Some(32));
assert_eq!(BufferWriter::max_readers(&writer), usize::MAX);
assert_eq!(reader_requirements.min_items(), Some(8));
assert_eq!(reader_requirements.min_buffer_size_in_items(), Some(64));
}
}
#[derive(Debug, Clone)]
enum PortBinding<I: BufferInbox = BlockInbox> {
Unbound,
Bound {
block_id: BlockId,
port_index: PortIndex,
inbox: I,
},
}
#[derive(Debug, Clone)]
pub struct PortCore<I: BufferInbox = BlockInbox> {
binding: PortBinding<I>,
requirements: BufferRequirements,
}
impl<I: BufferInbox> PortCore<I> {
pub const fn new_unbound() -> Self {
Self::with_requirements(BufferRequirements::new())
}
pub const fn with_requirements(requirements: BufferRequirements) -> Self {
Self {
binding: PortBinding::Unbound,
requirements,
}
}
pub fn init(&mut self, block_id: BlockId, port_index: PortIndex, inbox: I) {
self.binding = PortBinding::Bound {
block_id,
port_index,
inbox,
};
}
pub fn is_bound(&self) -> bool {
matches!(self.binding, PortBinding::Bound { .. })
}
pub fn block_id(&self) -> BlockId {
match &self.binding {
PortBinding::Bound { block_id, .. } => *block_id,
PortBinding::Unbound => panic!("port is not bound to a flowgraph"),
}
}
pub fn port_id(&self) -> PortIndex {
match &self.binding {
PortBinding::Bound { port_index, .. } => *port_index,
PortBinding::Unbound => panic!("port is not bound to a flowgraph"),
}
}
pub fn port_id_if_bound(&self) -> Option<PortIndex> {
match &self.binding {
PortBinding::Bound { port_index, .. } => Some(*port_index),
PortBinding::Unbound => None,
}
}
pub fn inbox(&self) -> &I {
match &self.binding {
PortBinding::Bound { inbox, .. } => inbox,
PortBinding::Unbound => panic!("port is not bound to a flowgraph"),
}
}
pub fn notifier(&self) -> I::Notifier {
match &self.binding {
PortBinding::Bound { inbox, .. } => inbox.notifier(),
PortBinding::Unbound => panic!("port is not bound to a flowgraph"),
}
}
pub fn endpoint_if_bound(&self) -> Option<PortEndpoint<I>> {
match &self.binding {
PortBinding::Bound {
port_index, inbox, ..
} => Some(PortEndpoint::new(inbox.clone(), *port_index)),
PortBinding::Unbound => None,
}
}
pub fn min_items(&self) -> Option<usize> {
self.requirements.min_items()
}
pub fn set_min_items(&mut self, min_items: usize) {
self.requirements.set_min_items(min_items);
}
pub fn raise_min_items(&mut self, min_items: usize) {
self.requirements.raise_min_items(min_items);
}
pub fn min_buffer_size_in_items(&self) -> Option<usize> {
self.requirements.min_buffer_size_in_items()
}
pub fn requirements(&self) -> BufferRequirements {
self.requirements
}
pub fn raise_requirements(&mut self, requirements: BufferRequirements) {
self.requirements.merge(requirements);
}
pub fn set_min_buffer_size_in_items(&mut self, min_items: usize) {
self.requirements.set_min_buffer_size_in_items(min_items);
}
pub fn raise_min_buffer_size_in_items(&mut self, min_items: usize) {
self.requirements.raise_min_buffer_size_in_items(min_items);
}
pub fn not_connected_error(&self) -> Error {
match &self.binding {
PortBinding::Bound {
block_id,
port_index,
..
} => Error::ValidationError(format!("{block_id:?}:{port_index:?} not connected")),
PortBinding::Unbound => {
Error::ValidationError("stream port is not bound to a flowgraph".to_string())
}
}
}
}
#[derive(Debug, Clone)]
pub struct PortEndpoint<I: BufferInbox = BlockInbox> {
inbox: I,
port_id: PortIndex,
}
impl<I: BufferInbox> PortEndpoint<I> {
pub fn new(inbox: I, port_id: PortIndex) -> Self {
Self { inbox, port_id }
}
pub fn inbox(&self) -> &I {
&self.inbox
}
pub fn port_id(&self) -> PortIndex {
self.port_id
}
}
#[derive(Debug, Clone)]
pub(crate) struct CircuitReturn<I, Q> {
inbox: I,
queue: Q,
}
impl<I: BufferInbox, Q> CircuitReturn<I, Q> {
pub(crate) fn new(inbox: I, queue: Q) -> Self {
Self { inbox, queue }
}
pub(crate) fn notify(&self) {
self.inbox.notify();
}
pub(crate) fn queue(&self) -> &Q {
&self.queue
}
}
#[derive(Debug)]
pub enum ConnectionState<T> {
Disconnected,
Connected(T),
}
impl<T> ConnectionState<T> {
pub const fn disconnected() -> Self {
Self::Disconnected
}
pub fn is_connected(&self) -> bool {
matches!(self, Self::Connected(_))
}
pub fn as_ref(&self) -> Option<&T> {
match self {
Self::Disconnected => None,
Self::Connected(value) => Some(value),
}
}
pub fn as_mut(&mut self) -> Option<&mut T> {
match self {
Self::Disconnected => None,
Self::Connected(value) => Some(value),
}
}
pub fn connected(&self) -> &T {
self.as_ref()
.expect("buffer backend is disconnected after validation")
}
pub fn connected_mut(&mut self) -> &mut T {
self.as_mut()
.expect("buffer backend is disconnected after validation")
}
pub fn set_connected(&mut self, value: T) {
*self = Self::Connected(value);
}
pub fn take_connected(&mut self) -> Option<T> {
match std::mem::replace(self, Self::Disconnected) {
Self::Disconnected => None,
Self::Connected(value) => Some(value),
}
}
}
pub trait DynBufferReader: Any {
fn buffer_requirements(&self) -> BufferRequirements;
fn raise_buffer_requirements(&mut self, requirements: BufferRequirements);
fn init_from(&mut self, block_id: BlockId, port_index: PortIndex, inboxes: &PortInboxes);
fn validate(&self) -> Result<(), Error>;
fn finish(&mut self);
fn finished(&self) -> bool;
fn block_id(&self) -> BlockId;
fn port_id(&self) -> PortIndex;
}
impl<T: BufferReader> DynBufferReader for T {
fn buffer_requirements(&self) -> BufferRequirements {
BufferReader::buffer_requirements(self)
}
fn raise_buffer_requirements(&mut self, requirements: BufferRequirements) {
BufferReader::raise_buffer_requirements(self, requirements);
}
fn init_from(&mut self, block_id: BlockId, port_index: PortIndex, inboxes: &PortInboxes) {
BufferReader::init_from(self, block_id, port_index, inboxes);
}
fn validate(&self) -> Result<(), Error> {
BufferReader::validate(self)
}
fn finish(&mut self) {
BufferReader::finish(self);
}
fn finished(&self) -> bool {
BufferReader::finished(self)
}
fn block_id(&self) -> BlockId {
BufferReader::block_id(self)
}
fn port_id(&self) -> PortIndex {
BufferReader::port_id(self)
}
}
pub trait BufferReader: Any {
type Inbox: BufferInbox;
fn buffer_requirements(&self) -> BufferRequirements;
fn raise_buffer_requirements(&mut self, requirements: BufferRequirements);
fn set_min_items(&mut self, n: usize) {
self.raise_buffer_requirements(BufferRequirements::with_min_items(n));
}
fn set_min_buffer_size_in_items(&mut self, n: usize) {
let mut requirements = BufferRequirements::new();
requirements.set_min_buffer_size_in_items(n);
self.raise_buffer_requirements(requirements);
}
fn init(&mut self, block_id: BlockId, port_index: PortIndex, inbox: Self::Inbox);
fn init_from(&mut self, block_id: BlockId, port_index: PortIndex, inboxes: &PortInboxes) {
self.init(
block_id,
port_index,
Self::Inbox::from_port_inboxes(inboxes),
);
}
fn validate(&self) -> Result<(), Error>;
fn notify_finished(&mut self) -> impl Future<Output = ()>
where
Self: Sized;
fn finish(&mut self);
fn finished(&self) -> bool;
fn block_id(&self) -> BlockId;
fn port_id(&self) -> PortIndex;
}
pub trait ThreadSafeConnect: BufferWriter {
type ReaderToken: Send + 'static;
type WriterToken: Send + 'static;
fn take_reader_token(reader: &mut Self::Reader) -> Self::ReaderToken;
fn connect_reader(&mut self, token: Self::ReaderToken) -> Self::WriterToken;
fn finish_reader(reader: &mut Self::Reader, token: Self::WriterToken);
}
pub(crate) type DynThreadSafeToken = Box<dyn Any + Send>;
#[doc(hidden)]
pub trait DynThreadSafeConnect: Debug + Send + Sync {
fn take_reader(&self, reader: &mut dyn DynBufferReader) -> Result<DynThreadSafeToken, Error>;
fn connect_reader(
&self,
writer: &mut dyn DynBufferWriter,
token: DynThreadSafeToken,
) -> Result<DynThreadSafeToken, Error>;
fn finish_reader(
&self,
reader: &mut dyn DynBufferReader,
token: DynThreadSafeToken,
) -> Result<(), Error>;
}
struct ErasedThreadSafeConnect<W>(PhantomData<fn() -> W>);
impl<W> Debug for ErasedThreadSafeConnect<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DynThreadSafeConnect")
.finish_non_exhaustive()
}
}
impl<W> DynThreadSafeConnect for ErasedThreadSafeConnect<W>
where
W: ThreadSafeConnect + 'static,
{
fn take_reader(&self, reader: &mut dyn DynBufferReader) -> Result<DynThreadSafeToken, Error> {
let reader = (reader as &mut dyn Any)
.downcast_mut::<W::Reader>()
.ok_or_else(|| Error::ValidationError("stream reader has wrong type".to_string()))?;
Ok(Box::new(W::take_reader_token(reader)))
}
fn connect_reader(
&self,
writer: &mut dyn DynBufferWriter,
token: DynThreadSafeToken,
) -> Result<DynThreadSafeToken, Error> {
let writer = (writer as &mut dyn Any)
.downcast_mut::<W>()
.ok_or_else(|| Error::ValidationError("stream writer has wrong type".to_string()))?;
let token = token.downcast::<W::ReaderToken>().map_err(|_| {
Error::ValidationError("stream connection token has unexpected type".to_string())
})?;
Ok(Box::new(writer.connect_reader(*token)))
}
fn finish_reader(
&self,
reader: &mut dyn DynBufferReader,
token: DynThreadSafeToken,
) -> Result<(), Error> {
let reader = (reader as &mut dyn Any)
.downcast_mut::<W::Reader>()
.ok_or_else(|| Error::ValidationError("stream reader has wrong type".to_string()))?;
let token = token.downcast::<W::WriterToken>().map_err(|_| {
Error::ValidationError("stream connection token has unexpected type".to_string())
})?;
W::finish_reader(reader, *token);
Ok(())
}
}
pub trait DynBufferWriter: Any {
fn reader_type_id(&self) -> TypeId;
fn max_readers(&self) -> usize;
fn buffer_requirements(&self) -> BufferRequirements;
fn raise_buffer_requirements(&mut self, requirements: BufferRequirements);
fn init_from(&mut self, block_id: BlockId, port_index: PortIndex, inboxes: &PortInboxes);
fn validate(&self) -> Result<(), Error>;
fn connect_dyn(&mut self, dest: &mut dyn DynBufferReader) -> Result<(), Error>;
#[doc(hidden)]
fn thread_safe_connect(&self) -> Option<Arc<dyn DynThreadSafeConnect>>;
fn block_id(&self) -> BlockId;
fn port_id(&self) -> PortIndex;
}
impl<T> DynBufferWriter for T
where
T: BufferWriter + 'static,
{
fn reader_type_id(&self) -> TypeId {
TypeId::of::<T::Reader>()
}
fn max_readers(&self) -> usize {
BufferWriter::max_readers(self)
}
fn buffer_requirements(&self) -> BufferRequirements {
BufferWriter::buffer_requirements(self)
}
fn raise_buffer_requirements(&mut self, requirements: BufferRequirements) {
BufferWriter::raise_buffer_requirements(self, requirements);
}
fn init_from(&mut self, block_id: BlockId, port_index: PortIndex, inboxes: &PortInboxes) {
BufferWriter::init_from(self, block_id, port_index, inboxes);
}
fn validate(&self) -> Result<(), Error> {
BufferWriter::validate(self)
}
fn connect_dyn(&mut self, dest: &mut dyn DynBufferReader) -> Result<(), Error> {
BufferWriter::connect_dyn(self, dest)
}
default fn thread_safe_connect(&self) -> Option<Arc<dyn DynThreadSafeConnect>> {
None
}
fn block_id(&self) -> BlockId {
BufferWriter::block_id(self)
}
fn port_id(&self) -> PortIndex {
BufferWriter::port_id(self)
}
}
impl<T> DynBufferWriter for T
where
T: ThreadSafeConnect + 'static,
{
fn thread_safe_connect(&self) -> Option<Arc<dyn DynThreadSafeConnect>> {
Some(Arc::new(ErasedThreadSafeConnect::<T>(PhantomData)))
}
}
pub trait BufferWriter: Any {
type Inbox: BufferInbox;
type Reader: BufferReader<Inbox = Self::Inbox>;
fn max_readers(&self) -> usize {
1
}
fn buffer_requirements(&self) -> BufferRequirements;
fn raise_buffer_requirements(&mut self, requirements: BufferRequirements);
fn set_min_items(&mut self, n: usize) {
self.raise_buffer_requirements(BufferRequirements::with_min_items(n));
}
fn set_min_buffer_size_in_items(&mut self, n: usize) {
let mut requirements = BufferRequirements::new();
requirements.set_min_buffer_size_in_items(n);
self.raise_buffer_requirements(requirements);
}
fn init(&mut self, block_id: BlockId, port_index: PortIndex, inbox: Self::Inbox);
fn init_from(&mut self, block_id: BlockId, port_index: PortIndex, inboxes: &PortInboxes) {
self.init(
block_id,
port_index,
Self::Inbox::from_port_inboxes(inboxes),
);
}
fn validate(&self) -> Result<(), Error>;
fn connect(&mut self, dest: &mut Self::Reader);
fn connect_dyn(&mut self, dest: &mut dyn DynBufferReader) -> Result<(), Error> {
if let Some(concrete) = (dest as &mut dyn Any).downcast_mut::<Self::Reader>() {
self.connect(concrete);
Ok(())
} else {
Err(Error::ValidationError(
"dyn BufferReader has wrong type".to_string(),
))
}
}
fn notify_finished(&mut self) -> impl Future<Output = ()>;
fn block_id(&self) -> BlockId;
fn port_id(&self) -> PortIndex;
}
pub trait CpuSample: Copy + Default + std::fmt::Debug + Send + Sync + 'static {
const SIZE: NonZeroUsize =
NonZeroUsize::new(size_of::<Self>()).expect("sample types must not be zero-sized");
}
impl<T> CpuSample for T where T: Copy + Default + std::fmt::Debug + Send + Sync + 'static {}
pub trait CpuBufferReader: BufferReader + Default {
type Item: CpuSample;
fn slice_with_tags(&mut self) -> (&[Self::Item], &[ItemTag]);
fn slice(&mut self) -> &[Self::Item] {
self.slice_with_tags().0
}
fn consume(&mut self, n: usize);
fn max_contiguous_items(&self) -> usize;
}
pub trait CpuBufferWriter: BufferWriter + Default {
type Item: CpuSample;
fn slice_with_tags(&mut self) -> (&mut [Self::Item], Tags<'_>);
fn slice(&mut self) -> &mut [Self::Item] {
self.slice_with_tags().0
}
fn produce(&mut self, n: usize);
}
pub trait InplaceBuffer {
type Item: CpuSample;
fn set_valid(&mut self, valid: usize);
fn slice(&mut self) -> &mut [Self::Item];
fn slice_with_tags(&mut self) -> (&mut [Self::Item], &mut Vec<ItemTag>);
}
pub trait InplaceReader: BufferReader + Default {
type Item: CpuSample;
type Buffer: InplaceBuffer<Item = Self::Item>;
fn get_full_buffer(&mut self) -> Option<Self::Buffer>;
fn has_more_buffers(&mut self) -> bool;
}
pub trait InplaceWriter: BufferWriter + Default {
type Item: CpuSample;
type Buffer: InplaceBuffer<Item = Self::Item>;
fn put_full_buffer(&mut self, buffer: Self::Buffer) -> Result<(), Error>;
fn get_empty_buffer(&mut self) -> Option<Self::Buffer>;
fn has_more_buffers(&mut self) -> bool;
fn inject_buffers(&mut self, n_buffers: usize) {
let n_items = config().buffer_size / Self::Item::SIZE.get();
self.inject_buffers_with_items(n_buffers, n_items);
}
fn inject_buffers_with_items(&mut self, n_buffers: usize, n_items: usize);
}
#[cfg(not(target_arch = "wasm32"))]
pub type DefaultCpuReader<D> = circular::Reader<D>;
#[cfg(not(target_arch = "wasm32"))]
pub type DefaultCpuWriter<D> = circular::Writer<D>;
#[cfg(target_arch = "wasm32")]
pub type DefaultCpuReader<D> = slab::Reader<D>;
#[cfg(target_arch = "wasm32")]
pub type DefaultCpuWriter<D> = slab::Writer<D>;
pub type LocalCpuReader<D> = local::Reader<D>;
pub type LocalCpuWriter<D> = local::Writer<D>;
pub struct Tags<'a> {
tags: &'a mut Vec<ItemTag>,
offset: usize,
}
impl<'a> Tags<'a> {
pub fn new(tags: &'a mut Vec<ItemTag>, offset: usize) -> Self {
Self { tags, offset }
}
pub fn add_tag(&mut self, index: usize, tag: Tag) {
self.tags.push(ItemTag {
index: index + self.offset,
tag,
});
}
}