Skip to main content

gled_protocol/
action.rs

1//! Actions: every change to the show, from any source. User interfaces, OSC
2//! and MIDI enqueue them; the engine applies them.
3//!
4//! `UiAction::enqueue` puts an action into the global queue the engine reads,
5//! unless the calling thread routes its actions elsewhere: a user interface
6//! sends its actions numbered to the engine, so it knows when they are applied
7//! (see `route_actions_on_this_thread`).
8
9use gled_model::{
10    asset::{
11        animation::{Animation, argument::ArgumentKindId},
12        curve::Curve,
13        group::Groups,
14        midi_controller::MidiController,
15        output_device::routing::OutputRoutings,
16        palette::Palette,
17        project::{
18            GridHighlight, Project,
19            io::{ArtnetConfig, ArtnetControlConfig, ChannelOverwrites, OscConfig},
20            scene_instance_path::SceneInstanceUnion,
21        },
22        scene::{Scene, color::SceneInstanceColor, grid::GridLocation, instance::SceneInstance},
23        svg::Svg,
24    },
25    asset_id::AssetId,
26    input::event::InputEvent,
27    sound::AudioDeviceId,
28};
29use kanal::{Receiver, Sender, unbounded};
30use once_cell::sync::OnceCell;
31use serde::{Deserialize, Serialize};
32use std::{
33    cell::RefCell,
34    collections::{BTreeSet, HashMap},
35    sync::Arc,
36};
37
38static ACTION_SENDER: OnceCell<Sender<UiAction>> = OnceCell::new();
39
40/// The project's own trigger inputs.
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct TriggerInputs {
43    pub tap: BTreeSet<InputEvent>,
44    pub blackout: BTreeSet<InputEvent>,
45    pub blackout_hold: BTreeSet<InputEvent>,
46    pub half: BTreeSet<InputEvent>,
47    pub double: BTreeSet<InputEvent>,
48}
49
50impl TriggerInputs {
51    pub fn of(project: &Project) -> Self {
52        Self {
53            tap: project.tap_input_events.clone(),
54            blackout: project.blackout_input_events.clone(),
55            blackout_hold: project.blackout_hold_input_events.clone(),
56            half: project.half_input_events.clone(),
57            double: project.double_input_events.clone(),
58        }
59    }
60
61    pub fn apply_to(self, project: &mut Project) {
62        project.tap_input_events = self.tap;
63        project.blackout_input_events = self.blackout;
64        project.blackout_hold_input_events = self.blackout_hold;
65        project.half_input_events = self.half;
66        project.double_input_events = self.double;
67    }
68}
69
70/// Where a thread's actions go.
71type ActionRoute = Box<dyn Fn(UiAction)>;
72
73thread_local! {
74    /// Where actions enqueued on this thread go instead of the global queue.
75    /// The user interface's thread sends its actions to the engine in order
76    /// with its other input, see `route_actions_on_this_thread`.
77    static ACTION_ROUTE: RefCell<Option<ActionRoute>> = const { RefCell::new(None) };
78}
79
80/// Sends every action enqueued on the calling thread to `route` from now on.
81pub fn route_actions_on_this_thread(route: impl Fn(UiAction) + 'static) {
82    ACTION_ROUTE.with(|cell| *cell.borrow_mut() = Some(Box::new(route)));
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub enum UiAction {
87    // Whole settings, sent for direct edits in the interface (see
88    // `ui::project_edits`).
89    SetProjectPalette(Box<Option<Palette>>),
90    SetProjectGroups(Groups),
91    SetGridSize(usize, usize),
92    SetGridHighlight(GridHighlight),
93    SetProjectChannelOverwrites(ChannelOverwrites),
94    SetProjectOutputRoutings(Arc<OutputRoutings>),
95    SetProjectArtnetConfig(Box<ArtnetConfig>),
96    SetProjectTriggerInputs(Box<TriggerInputs>),
97    SetMidiActiveMappings(HashMap<String, Option<AssetId<MidiController>>>),
98    SetProjectArtnetControl(ArtnetControlConfig),
99    SetProjectOsc(OscConfig),
100    /// Puts a scene instance at a grid location, or empties it.
101    ReplaceSceneInstance(GridLocation, Option<Box<SceneInstance>>),
102    CloseProject,
103    /// The output routings of the running output (not yet saved in the project).
104    SetOutputRoutings(Arc<OutputRoutings>),
105
106    AddScene(GridLocation, AssetId<Scene>),
107    SetProject(AssetId<Project>),
108    SelectScene(SceneInstanceUnion),
109    SelectSceneByLocation(GridLocation),
110    DeleteSceneInstance {
111        location: GridLocation,
112    },
113    DeleteSceneInstancePath(SceneInstanceUnion),
114    CloneSceneInstance(GridLocation),
115    CloneSceneInstancePath(SceneInstanceUnion),
116    ReloadShaderCode(Option<AssetId<Animation>>),
117    SetSvg(Option<Svg>),
118    Tap,
119    SpeedAdd(f32),
120    SpeedMultiply(f32),
121    SetBlackout(bool),
122    /// Set opacity for a scene instance
123    ///
124    /// This also activates the scene instance
125    SetSceneOpacity(SceneInstanceUnion, f32),
126    ToggleSceneActive(SceneInstanceUnion),
127    SetSceneActive(SceneInstanceUnion, bool),
128    SetSceneFlash(SceneInstanceUnion, bool),
129    SetMainDimmer(f32),
130    MidiOutputActive(bool),
131    SwapScenes(GridLocation, GridLocation),
132    SetSceneName(SceneInstanceUnion, String),
133    SetSceneColor(SceneInstanceUnion, SceneInstanceColor),
134    SetSceneInputDimmer(SceneInstanceUnion, f32),
135    SetSceneIgnoreMainDimmer(SceneInstanceUnion, bool),
136    SetSceneBeatOffset(SceneInstanceUnion, f32),
137    SetSceneSetOffsetOnFlash(SceneInstanceUnion, bool),
138    /// Set only the multiplier of the opacity curve (preserves the curve asset)
139    SetSceneOpacityMultiplier(SceneInstanceUnion, f32),
140    /// Set only the curve asset for the opacity (preserves the multiplier)
141    SetSceneOpacityCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
142    /// Set only the multiplier of the beat_offset curve (preserves the curve asset)
143    SetSceneBeatOffsetMultiplier(SceneInstanceUnion, f32),
144    /// Set only the curve asset for beat_offset (preserves the multiplier)
145    SetSceneBeatOffsetCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
146    SetSceneActivationInput(SceneInstanceUnion, Option<InputEvent>),
147    SetSceneFlashInput(SceneInstanceUnion, Option<InputEvent>),
148    SetSceneDimmerInput(SceneInstanceUnion, Option<InputEvent>),
149    SetScenePaletteOverwriteFromAsset(SceneInstanceUnion, Option<Option<AssetId<Palette>>>),
150    SetScenePaletteOverwritePrimary(SceneInstanceUnion, [f32; 3]),
151    SetScenePaletteOverwriteSecondary(SceneInstanceUnion, [f32; 3]),
152    SetScenePaletteOverwriteGradient(SceneInstanceUnion, usize, [f32; 3]),
153    ClearSceneGroupsOverwrite(SceneInstanceUnion),
154    SetSceneGroupsOverwriteEntry(SceneInstanceUnion, usize, String),
155    RemoveSceneGroupsOverwriteEntry(SceneInstanceUnion, usize),
156    SetProjectPaletteFromAsset(Option<AssetId<Palette>>),
157    SetProjectPalettePrimary([f32; 3]),
158    SetProjectPaletteSecondary([f32; 3]),
159    SetProjectPaletteGradient(usize, [f32; 3]),
160    SetProjectArtnetControlActive(bool),
161    SetProjectArtnetControlUniverse(u16),
162    SetProjectOscActive(bool),
163    SetProjectOscPort(u16),
164    SetProjectGroup(usize, String),
165    RemoveProjectGroup(usize),
166    ClearProjectGroups,
167    AddProjectTapInputArtnet(u16),
168    RemoveProjectTapInputArtnet(u16),
169    ClearProjectTapInput,
170    AddProjectBlackoutInputArtnet(u16),
171    RemoveProjectBlackoutInputArtnet(u16),
172    ClearProjectBlackoutInput,
173    AddProjectBlackoutHoldInputArtnet(u16),
174    RemoveProjectBlackoutHoldInputArtnet(u16),
175    ClearProjectBlackoutHoldInput,
176    AddProjectHalfInputArtnet(u16),
177    RemoveProjectHalfInputArtnet(u16),
178    ClearProjectHalfInput,
179    AddProjectDoubleInputArtnet(u16),
180    RemoveProjectDoubleInputArtnet(u16),
181    ClearProjectDoubleInput,
182    SetSceneEffectOpacity(SceneInstanceUnion, usize, f32),
183    SetSceneEffectOpacityMultiplier(SceneInstanceUnion, usize, f32),
184    SetSceneEffectOpacityCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
185    SetSceneEffectColorShift(SceneInstanceUnion, usize, f32),
186    SetSceneEffectColorShiftMultiplier(SceneInstanceUnion, usize, f32),
187    SetSceneEffectColorShiftCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
188    SetSceneEffectBeatProgression(SceneInstanceUnion, usize, f32),
189    SetSceneEffectBeatProgressionMultiplier(SceneInstanceUnion, usize, f32),
190    SetSceneEffectBeatProgressionCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
191    SetSceneEffectBeatOffset(SceneInstanceUnion, usize, f32),
192    SetSceneEffectBeatOffsetMultiplier(SceneInstanceUnion, usize, f32),
193    SetSceneEffectBeatOffsetCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
194    SetSceneEffectSpeedExponent(SceneInstanceUnion, usize, i32),
195    SetSceneEffectGroupIndex(SceneInstanceUnion, usize, usize),
196    SetSceneEffectAnimation(SceneInstanceUnion, usize, Option<AssetId<Animation>>),
197    SetSceneEffectAnimationConfigU32(SceneInstanceUnion, usize, usize, u32),
198    SetSceneEffectAnimationConfigF32(SceneInstanceUnion, usize, usize, f32),
199    AddSceneEffect(SceneInstanceUnion),
200    RemoveSceneEffect(SceneInstanceUnion, usize),
201    CloneSceneEffect(SceneInstanceUnion, usize),
202    SetAnimationShaderCode(AssetId<Animation>, String),
203    AddAnimationArgument(AssetId<Animation>),
204    RemoveAnimationArgument(AssetId<Animation>, usize),
205    SetAnimationArgumentName(AssetId<Animation>, usize, String),
206    SetAnimationArgumentKind(AssetId<Animation>, usize, ArgumentKindId),
207    Error(String),
208    SetAudioDevice(Option<AudioDeviceId>),
209}
210
211impl UiAction {
212    /// Creates the global queue. Call once; a second call returns `None`.
213    pub fn init_queue() -> Option<Receiver<UiAction>> {
214        let (sender, receiver) = unbounded();
215        ACTION_SENDER.set(sender).ok()?;
216        Some(receiver)
217    }
218
219    pub fn enqueue(self) {
220        let action = ACTION_ROUTE.with(|cell| match cell.borrow().as_ref() {
221            Some(route) => {
222                route(self);
223                None
224            }
225            None => Some(self),
226        });
227        let Some(action) = action else {
228            return;
229        };
230        match ACTION_SENDER.get() {
231            Some(sender) => {
232                if sender.send(action).is_err() {
233                    tracing::warn!("The action queue has no receiver any more");
234                }
235            }
236            None => tracing::warn!("Dropped {action:?}: the action queue is not set up"),
237        }
238    }
239}