use crate::watch::State;
use super::{
Datagrams, DatagramsReader, DatagramsWriter, ObjectsWriter, ServeError, Stream, StreamReader,
StreamWriter, Subgroups, SubgroupsReader, SubgroupsWriter,
};
use crate::coding::{Location, TrackNamespace};
use paste::paste;
use std::{ops::Deref, sync::Arc};
#[derive(Debug, Clone, PartialEq)]
pub struct Track {
pub namespace: TrackNamespace,
pub name: String,
}
impl Track {
pub fn new(namespace: TrackNamespace, name: String) -> Self {
Self { namespace, name }
}
pub fn produce(self) -> (TrackWriter, TrackReader) {
let (writer_track_state, reader_track_state) = State::default().split();
let info = Arc::new(self);
let writer = TrackWriter::new(writer_track_state, info.clone());
let reader = TrackReader::new(reader_track_state, info);
(writer, reader)
}
}
struct TrackState {
reader_mode: Option<TrackReaderMode>,
closed: Result<(), ServeError>,
}
impl Default for TrackState {
fn default() -> Self {
Self {
reader_mode: None,
closed: Ok(()),
}
}
}
pub struct TrackWriter {
state: State<TrackState>,
pub info: Arc<Track>,
}
impl TrackWriter {
fn new(state: State<TrackState>, info: Arc<Track>) -> Self {
Self { state, info }
}
pub fn stream(self, priority: u8) -> Result<StreamWriter, ServeError> {
let (writer, reader) = Stream {
track: self.info.clone(),
priority,
}
.produce();
let mut state = self.state.lock_mut().ok_or_else(|| {
tracing::debug!(
namespace = %self.info.namespace.to_utf8_path(),
track = %self.info.name,
"track state dropped (Cancel) in stream()"
);
ServeError::Cancel
})?;
state.reader_mode = Some(reader.into());
Ok(writer)
}
pub fn subgroups(self) -> Result<SubgroupsWriter, ServeError> {
let (writer, reader) = Subgroups {
track: self.info.clone(),
}
.produce();
let mut state = self.state.lock_mut().ok_or_else(|| {
tracing::debug!(
namespace = %self.info.namespace.to_utf8_path(),
track = %self.info.name,
"track state dropped (Cancel) in subgroups()"
);
ServeError::Cancel
})?;
state.reader_mode = Some(reader.into());
Ok(writer)
}
pub fn datagrams(self) -> Result<DatagramsWriter, ServeError> {
let (writer, reader) = Datagrams {
track: self.info.clone(),
}
.produce();
let mut state = self.state.lock_mut().ok_or_else(|| {
tracing::debug!(
namespace = %self.info.namespace.to_utf8_path(),
track = %self.info.name,
"track state dropped (Cancel) in datagrams()"
);
ServeError::Cancel
})?;
state.reader_mode = Some(reader.into());
Ok(writer)
}
pub fn close(self, err: ServeError) -> Result<(), ServeError> {
tracing::debug!(
namespace = %self.info.namespace.to_utf8_path(),
track = %self.info.name,
error = %err,
"track closing"
);
let state = self.state.lock();
state.closed.clone()?;
let mut state = state.into_mut().ok_or_else(|| {
tracing::debug!(
namespace = %self.info.namespace.to_utf8_path(),
track = %self.info.name,
"track state already dropped during close"
);
ServeError::Cancel
})?;
state.closed = Err(err);
Ok(())
}
}
impl Deref for TrackWriter {
type Target = Track;
fn deref(&self) -> &Self::Target {
&self.info
}
}
#[derive(Clone)]
pub struct TrackReader {
state: State<TrackState>,
pub info: Arc<Track>,
}
impl TrackReader {
fn new(state: State<TrackState>, info: Arc<Track>) -> Self {
Self { state, info }
}
pub async fn mode(&self) -> Result<TrackReaderMode, ServeError> {
loop {
{
let state = self.state.lock();
if let Some(mode) = &state.reader_mode {
return Ok(mode.clone());
}
state.closed.clone()?;
match state.modified() {
Some(notify) => notify,
None => return Err(ServeError::Done),
}
}
.await;
}
}
pub fn largest_location(&self) -> Option<Location> {
None
}
pub async fn closed(&self) -> Result<(), ServeError> {
loop {
{
let state = self.state.lock();
state.closed.clone()?;
match state.modified() {
Some(notify) => notify,
None => return Ok(()),
}
}
.await;
}
}
pub fn is_closed(&self) -> bool {
let state = self.state.lock();
if state.closed.is_err() {
return true;
}
if let Some(mode) = state.reader_mode.clone() {
drop(state);
return mode.is_closed();
}
state.modified().is_none()
}
}
impl Deref for TrackReader {
type Target = Track;
fn deref(&self) -> &Self::Target {
&self.info
}
}
macro_rules! track_readers {
{$($name:ident,)*} => {
paste! {
#[derive(Clone)]
pub enum TrackReaderMode {
$($name([<$name Reader>])),*
}
$(impl From<[<$name Reader>]> for TrackReaderMode {
fn from(reader: [<$name Reader >]) -> Self {
Self::$name(reader)
}
})*
impl TrackReaderMode {
pub fn latest(&self) -> Option<(u64, u64)> {
match self {
$(Self::$name(reader) => reader.latest(),)*
}
}
pub fn is_closed(&self) -> bool {
match self {
$(Self::$name(reader) => reader.is_closed(),)*
}
}
}
}
}
}
track_readers!(Stream, Subgroups, Datagrams,);
macro_rules! track_writers {
{$($name:ident,)*} => {
paste! {
pub enum TrackWriterMode {
$($name([<$name Writer>])),*
}
$(impl From<[<$name Writer>]> for TrackWriterMode {
fn from(writer: [<$name Writer>]) -> Self {
Self::$name(writer)
}
})*
impl TrackWriterMode {
pub fn close(self, err: ServeError) -> Result<(), ServeError>{
match self {
$(Self::$name(writer) => writer.close(err),)*
}
}
}
}
}
}
track_writers!(Track, Stream, Subgroups, Objects, Datagrams,);
#[cfg(test)]
mod tests {
use super::*;
use crate::coding::TrackNamespace;
use crate::serve::Subgroup;
#[test]
fn test_is_closed_false_before_mode_set() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (_writer, reader) = track.produce();
assert!(!reader.is_closed());
}
#[test]
fn test_is_closed_true_when_writer_dropped_without_mode() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
drop(writer);
assert!(reader.is_closed());
}
#[test]
fn test_is_closed_true_when_explicitly_closed() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
writer.close(ServeError::Cancel).unwrap();
assert!(reader.is_closed());
}
#[test]
fn test_is_closed_false_after_subgroups_transition_while_writer_alive() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
let _subgroups_writer = writer
.subgroups()
.expect("subgroups transition should succeed");
assert!(
!reader.is_closed(),
"track should NOT be closed while SubgroupsWriter is alive"
);
}
#[test]
fn test_is_closed_true_after_subgroups_writer_dropped() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
let subgroups_writer = writer
.subgroups()
.expect("subgroups transition should succeed");
drop(subgroups_writer);
assert!(
reader.is_closed(),
"track should be closed after SubgroupsWriter is dropped"
);
}
#[test]
fn test_is_closed_true_after_subgroups_writer_explicitly_closed() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
let subgroups_writer = writer
.subgroups()
.expect("subgroups transition should succeed");
subgroups_writer.close(ServeError::Cancel).unwrap();
assert!(
reader.is_closed(),
"track should be closed after SubgroupsWriter is explicitly closed"
);
}
#[test]
fn test_is_closed_false_after_stream_transition_while_writer_alive() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
let _stream_writer = writer.stream(0).expect("stream transition should succeed");
assert!(
!reader.is_closed(),
"track should NOT be closed while StreamWriter is alive"
);
}
#[test]
fn test_is_closed_true_after_stream_writer_dropped() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
let stream_writer = writer.stream(0).expect("stream transition should succeed");
drop(stream_writer);
assert!(
reader.is_closed(),
"track should be closed after StreamWriter is dropped"
);
}
#[test]
fn test_is_closed_false_after_datagrams_transition_while_writer_alive() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
let _datagrams_writer = writer
.datagrams()
.expect("datagrams transition should succeed");
assert!(
!reader.is_closed(),
"track should NOT be closed while DatagramsWriter is alive"
);
}
#[test]
fn test_is_closed_true_after_datagrams_writer_dropped() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
let datagrams_writer = writer
.datagrams()
.expect("datagrams transition should succeed");
drop(datagrams_writer);
assert!(
reader.is_closed(),
"track should be closed after DatagramsWriter is dropped"
);
}
#[test]
fn test_is_closed_false_while_subgroups_actively_writing() {
let track = Track::new(TrackNamespace::from_utf8_path("ns"), "t".to_string());
let (writer, reader) = track.produce();
let mut subgroups_writer = writer
.subgroups()
.expect("subgroups transition should succeed");
let _subgroup_writer = subgroups_writer
.create(Subgroup {
group_id: 0,
subgroup_id: 0,
priority: 0,
})
.expect("create subgroup should succeed");
assert!(
!reader.is_closed(),
"track should NOT be closed while actively writing subgroups"
);
}
}