Loading...
Searching...
No Matches
Switch.hpp
1#pragma once
2#include <cctype>
3#include <string_view>
4#include <ossia/dataflow/exec_state_facade.hpp>
5#include <ossia/dataflow/token_request.hpp>
6#include <ossia/dataflow/value_port.hpp>
7
8#include <halp/controls.hpp>
9#include <halp/meta.hpp>
10#include <halp/string_list.hpp>
11#include <ossia/detail/json.hpp>
12#include <rapidjson/document.h>
13
14#include <cmath>
15
16#include <vector>
17
18namespace ao
19{
20struct Switch
21{
22 halp_meta(name, "Switch")
23 halp_meta(c_name, "avnd_switch")
24 halp_meta(category, "Control/Mappings")
25 halp_meta(author, "ossia score")
26 halp_meta(
27 description,
28 "Route each event to the first matching case: a number, true, false, null "
29 "(matches impulse), or a string, quoted or not. Strings and booleans match "
30 "without coercion; numbers compare exactly after promotion to double. "
31 "Invalid literals never match. Row identities keep cables through reordering.")
32 halp_meta(uuid, "51083c8f-aea0-4617-b026-34fd2793c818")
33
34 struct
35 {
36 struct
37 {
38 halp_meta(name, "Input")
39 ossia::value_port* value{};
40 static constexpr bool is_event() { return true; }
41 } input;
42 struct : halp::string_list<"Cases">
43 {
44 halp_meta(
45 description,
46 "Numbers, true, false, null, or strings (quotes optional). "
47 "The first matching row receives the event; null matches impulse.")
48 void update(Switch& self) { self.compile(); }
49 static std::function<void(Switch&, const halp::string_list_value&)>
50 on_controller_interaction()
51 {
52 return [](Switch& self, const halp::string_list_value& rows) {
53 self.outputs.cases.set_rows(rows);
54 };
55 }
56 } cases;
57 } inputs;
58
59 struct case_port
60 {
61 halp_meta(name, "Case {}")
62 ossia::value_port* value{};
63 static constexpr bool is_event() { return true; }
64 };
65 struct
66 {
67 struct
68 {
69 halp_meta(name, "Unmatched")
70 ossia::value_port* value{};
71 static constexpr bool is_event() { return true; }
72 } unmatched;
73 halp::keyed_dynamic_port<case_port> cases;
74 } outputs;
75
76 struct Literal
77 {
78 enum Kind
79 {
80 invalid,
81 null,
82 boolean,
83 number,
84 string
85 } kind{invalid};
86 double numeric{};
87 bool flag{};
88 std::string text;
89
90 bool matches(const ossia::value& value) const
91 {
92 switch(kind)
93 {
94 case null:
95 return value.target<ossia::impulse>();
96 case boolean:
97 if(auto v = value.target<bool>())
98 return *v == flag;
99 return false;
100 case number:
101 if(auto v = value.target<int>())
102 return double(*v) == numeric;
103 if(auto v = value.target<float>())
104 return double(*v) == numeric;
105 return false;
106 case string:
107 if(auto v = value.target<std::string>())
108 return *v == text;
109 return false;
110 default:
111 return false;
112 }
113 }
114 };
115 std::vector<Literal> literals;
116
117 using tick = ossia::token_request;
118 ossia::exec_state_facade ossia_state;
119
120 void compile()
121 {
122 literals.clear();
123 literals.reserve(inputs.cases.value.size());
124 for(const auto& [id, text] : inputs.cases.value)
125 {
126 auto& literal = literals.emplace_back();
127 rapidjson::Document json;
128 if(text.find('\0') != std::string::npos)
129 continue;
130 json.Parse<rapidjson::kParseValidateEncodingFlag>(text.data(), text.size());
131 if(json.HasParseError())
132 {
133 // Not JSON: a bare word is the string it spells, so that "foo"
134 // needs no quotes. Blank rows stay unmatched.
135 std::string_view word{text};
136 while(!word.empty() && std::isspace(static_cast<unsigned char>(word.front())))
137 word.remove_prefix(1);
138 while(!word.empty() && std::isspace(static_cast<unsigned char>(word.back())))
139 word.remove_suffix(1);
140 if(!word.empty())
141 {
142 literal.kind = Literal::string;
143 literal.text.assign(word);
144 }
145 continue;
146 }
147 if(json.IsNull())
148 literal.kind = Literal::null;
149 else if(json.IsBool())
150 {
151 literal.kind = Literal::boolean;
152 literal.flag = json.GetBool();
153 }
154 else if(json.IsNumber())
155 {
156 literal.kind = Literal::number;
157 literal.numeric = json.GetDouble();
158 }
159 else if(json.IsString())
160 {
161 literal.kind = Literal::string;
162 literal.text.assign(json.GetString(), json.GetStringLength());
163 }
164 }
165 }
166
167 void operator()(const tick& t)
168 {
169 if(!inputs.input.value)
170 return;
171 const auto [start, frames] = ossia_state.timings(t);
172 for(const auto& event : inputs.input.value->get_data())
173 {
174 if(event.timestamp < start || event.timestamp >= start + frames)
175 continue;
176 auto output = outputs.unmatched.value;
177 for(std::size_t i = 0; i < literals.size() && i < outputs.cases.ports.size(); ++i)
178 if(literals[i].matches(event.value))
179 {
180 output = outputs.cases.ports[i].value;
181 break;
182 }
183 if(output)
184 output->write_value(event.value, event.timestamp);
185 }
186 }
187};
188}
Definition Switch.hpp:77
Definition Switch.hpp:60
Definition Switch.hpp:21
Definition ObjectMatches.hpp:6