crossflow/buffer/
buffer_gate.rs

1/*
2 * Copyright (C) 2025 Open Source Robotics Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16*/
17
18use bevy_ecs::{
19    change_detection::Mut,
20    prelude::{Commands, Entity, Query},
21    query::QueryEntityError,
22    system::SystemParam,
23};
24
25use crate::{AnyBufferKey, Gate, GateState, NotifyBufferUpdate};
26
27/// This system parameter lets you get read-only access to the gate of a buffer
28/// that exists within a workflow. Use a [`BufferKey`][1] or [`AnyBufferKey`]
29/// to unlock the access.
30///
31/// See [`BufferGateAccessMut`] for mutable access.
32///
33/// [1]: crate::BufferKey
34#[derive(SystemParam)]
35pub struct BufferGateAccess<'w, 's> {
36    query: Query<'w, 's, &'static GateState>,
37}
38
39impl<'w, 's> BufferGateAccess<'w, 's> {
40    pub fn get<'a>(
41        &'a self,
42        key: impl Into<AnyBufferKey>,
43    ) -> Result<BufferGateView<'a>, QueryEntityError> {
44        let key: AnyBufferKey = key.into();
45        let session = key.session();
46        self.query
47            .get(key.id())
48            .map(|gate| BufferGateView { gate, session })
49    }
50}
51
52/// Access to view the gate of a buffer that exists inside a workflow.
53pub struct BufferGateView<'a> {
54    pub(crate) gate: &'a GateState,
55    pub(crate) session: Entity,
56}
57
58impl<'a> BufferGateView<'a> {
59    /// Check whether the gate of this buffer is open or closed
60    pub fn gate(&self) -> Gate {
61        // Gate buffers are open by default, so pretend it's open if a session
62        // has never touched this buffer gate.
63        self.gate
64            .map
65            .get(&self.session)
66            .copied()
67            .unwrap_or(Gate::Open)
68    }
69}
70
71/// This system parameter lets you get mutable access to the gate of a buffer
72/// that exists within a workflow. Use a [`BufferKey`][1] or [`AnyBufferKey`]
73/// to unlock the access.
74///
75/// See [`BufferGateAccess`] for read-only access.
76///
77/// [1]: crate::BufferKey
78#[derive(SystemParam)]
79pub struct BufferGateAccessMut<'w, 's> {
80    query: Query<'w, 's, &'static mut GateState>,
81    commands: Commands<'w, 's>,
82}
83
84impl<'w, 's> BufferGateAccessMut<'w, 's> {
85    pub fn get<'a>(
86        &'a self,
87        key: impl Into<AnyBufferKey>,
88    ) -> Result<BufferGateView<'a>, QueryEntityError> {
89        let key: AnyBufferKey = key.into();
90        let session = key.session();
91        self.query
92            .get(key.id())
93            .map(|gate| BufferGateView { gate, session })
94    }
95
96    pub fn get_mut<'a>(
97        &'a mut self,
98        key: impl Into<AnyBufferKey>,
99    ) -> Result<BufferGateMut<'w, 's, 'a>, QueryEntityError> {
100        let key: AnyBufferKey = key.into();
101        let buffer = key.id();
102        let session = key.session();
103        let accessor = key.tag.accessor;
104        self.query
105            .get_mut(buffer)
106            .map(|gate| BufferGateMut::new(gate, buffer, session, accessor, &mut self.commands))
107    }
108}
109
110/// Access to mutate the gate of a buffer that exists inside a workflow.
111pub struct BufferGateMut<'w, 's, 'a> {
112    gate: Mut<'a, GateState>,
113    buffer: Entity,
114    session: Entity,
115    accessor: Option<Entity>,
116    commands: &'a mut Commands<'w, 's>,
117    modified: bool,
118}
119
120impl<'w, 's, 'a> BufferGateMut<'w, 's, 'a> {
121    /// See documentation of [`crate::BufferMut::allow_closed_loops`].
122    pub fn allow_closed_loops(mut self) -> Self {
123        self.accessor = None;
124        self
125    }
126
127    /// Check whether the gate of this buffer is open or closed.
128    pub fn get(&self) -> Gate {
129        self.gate
130            .map
131            .get(&self.session)
132            .copied()
133            .unwrap_or(Gate::Open)
134    }
135
136    /// Tell the buffer [`Gate`] to open.
137    pub fn open_gate(&mut self) {
138        if let Some(gate) = self.gate.map.get_mut(&self.session) {
139            if *gate != Gate::Open {
140                *gate = Gate::Open;
141                self.modified = true;
142            }
143        }
144    }
145
146    /// Tell the buffer [`Gate`] to close.
147    pub fn close_gate(&mut self) {
148        if let Some(gate) = self.gate.map.get_mut(&self.session) {
149            *gate = Gate::Closed;
150            // There is no need to to indicate that a modification happened
151            // because listeners do not get notified about gates closing.
152        }
153    }
154
155    /// Perform an action on the gate of the buffer.
156    pub fn gate_action(&mut self, action: Gate) {
157        match action {
158            Gate::Open => self.open_gate(),
159            Gate::Closed => self.close_gate(),
160        }
161    }
162
163    fn new(
164        gate: Mut<'a, GateState>,
165        buffer: Entity,
166        session: Entity,
167        accessor: Entity,
168        commands: &'a mut Commands<'w, 's>,
169    ) -> Self {
170        Self {
171            gate,
172            buffer,
173            session,
174            accessor: Some(accessor),
175            commands,
176            modified: false,
177        }
178    }
179}
180
181impl<'w, 's, 'a> Drop for BufferGateMut<'w, 's, 'a> {
182    fn drop(&mut self) {
183        if self.modified {
184            self.commands.queue(NotifyBufferUpdate::new(
185                self.buffer,
186                self.session,
187                self.accessor,
188            ));
189        }
190    }
191}