载入中...
搜索中...
未找到
InputMapTarget.cpp
浏览该文件的文档.
2#include <algorithm>
3#include <cmath>
4#include <set>
5#include <utility>
7using namespace eve::editing;
8namespace {
9const EditorValue* field(const EditorValue& v, const char* k) {
10 auto* o = v.getIf<EditorValue::Object>();
11 if (!o) return nullptr;
12 auto i = o->find(k);
13 return i == o->end() ? nullptr : &i->second;
14}
16 return EditorValue::Object{{"id", v.id.value()}, {"name", v.name}, {"kind", v.kind}};
17}
19 return EditorValue::Object{{"id", v.id.value()}, {"action", v.action.value()}, {"device", v.device},
20 {"control", v.control}, {"scale", v.scale}, {"deadzone", v.deadzone},
21 {"invert", v.invert}};
22}
23PropertyDescriptor prop(std::string p, PropertyType t, EditorValue d, const char* c, double lo = -1e9,
24 double hi = 1e9) {
27 x.displayNameKey = "editor.input." + p;
28 x.category = c;
29 x.type = t;
30 x.defaultValue = std::move(d);
31 x.flags = PropertyFlag::Runtime;
32 x.numeric.minimum = lo;
33 x.numeric.maximum = hi;
34 return x;
35}
36bool errors(const std::vector<EditorDiagnostic>& d) {
37 return std::any_of(d.begin(), d.end(),
38 [](const auto& x) { return x.severity() == DiagnosticSeverity::Error; });
39}
40} // namespace
41InputMapTarget::InputMapTarget(std::string id) : id_(std::move(id)) {}
43 return {TargetId(id_), "input-map", revision_, false, {CapabilityId("eve.editor.target.input-map-properties")}};
44}
46 return c == CapabilityId("eve.editor.target.input-map-properties") ? static_cast<IPropertyProvider*>(this)
47 : nullptr;
48}
49bool InputMapTarget::matches(const SelectionSnapshot& s) const {
50 if (s.items.empty()) return false;
51 const auto type = s.items[0].type;
52 for (const auto& i : s.items) {
53 if (i.target != TargetId(id_) || i.type != type) return false;
54 bool found = type == "input.action" ? std::any_of(actions_.begin(), actions_.end(),
55 [&](const auto& v) { return v.id.value() == i.item.value(); })
56 : std::any_of(bindings_.begin(), bindings_.end(), [&](const auto& v) {
57 return v.id.value() == i.item.value();
58 });
59 if (!found) return false;
60 }
61 return type == "input.action" || type == "input.binding";
62}
63eve::Result<eve::Revision> InputMapTarget::currentRevision(const SelectionSnapshot& s) const {
64 if (!matches(s))
66 eve::DiagnosticCode::InvalidArgument, "Input selection mismatch", "editor.input.selection"));
68}
69PropertySchema InputMapTarget::schema(const SelectionSnapshot& s) const {
71 if (!s.items.empty() && s.items[0].type == "input.binding") {
72 x.typeId = "input.binding";
73 x.properties = {prop("binding.action", PropertyType::ObjectRef, "", "binding"),
74 prop("binding.device", PropertyType::Enum, "keyboard", "binding"),
75 prop("binding.control", PropertyType::String, "", "binding"),
76 prop("binding.scale", PropertyType::Float, 1.0, "response", -100, 100),
77 prop("binding.deadzone", PropertyType::Float, 0.0, "response", 0, .99),
78 prop("binding.invert", PropertyType::Bool, false, "response")};
79 x.properties[1].enumItems = {"keyboard", "mouse", "gamepad", "joystick", "touch"};
80 } else {
81 x.typeId = "input.action";
82 x.properties = {prop("action.name", PropertyType::String, "", "action"),
83 prop("action.kind", PropertyType::Enum, "button", "action")};
84 x.properties[1].enumItems = {"button", "axis1d"};
85 }
86 return x;
87}
88PropertyReadResult InputMapTarget::read(const SelectionSnapshot& s, const PropertyPath& p) const {
89 if (!matches(s) || !schema(s).find(p)) return {};
90 std::optional<EditorValue> common;
91 for (const auto& i : s.items) {
93 if (i.type == "input.action") {
94 const auto& a = *std::find_if(actions_.begin(), actions_.end(),
95 [&](const auto& x) { return x.id.value() == i.item.value(); });
96 v = p == PropertyPath("action.name") ? EditorValue(a.name) : EditorValue(a.kind);
97 } else {
98 const auto& b = *std::find_if(bindings_.begin(), bindings_.end(),
99 [&](const auto& x) { return x.id.value() == i.item.value(); });
100 if (p == PropertyPath("binding.action"))
101 v = b.action.value();
102 else if (p == PropertyPath("binding.device"))
103 v = b.device;
104 else if (p == PropertyPath("binding.control"))
105 v = b.control;
106 else if (p == PropertyPath("binding.scale"))
107 v = b.scale;
108 else if (p == PropertyPath("binding.deadzone"))
109 v = b.deadzone;
110 else
111 v = b.invert;
112 }
113 if (!common)
114 common = v;
115 else if (*common != v)
116 return {PropertyReadState::Mixed, {}, {}};
117 }
118 return {PropertyReadState::Value, *common, {}};
119}
120EditorValue InputMapTarget::contentValue() const {
122 for (const auto& v : actions_) a.push_back(action(v));
123 for (const auto& v : bindings_) b.push_back(binding(v));
124 return EditorValue::Object{{"actions", std::move(a)}, {"bindings", std::move(b)}};
125}
126Result<DomainOperation> InputMapTarget::replacement(EditorValue v, std::string p) const {
128 op.type = "input-map.replace.v1";
129 op.inverseType = op.type;
130 op.target = TargetId(id_);
131 op.payload = std::move(v);
132 op.inverse = contentValue();
133 op.hasInverse = true;
134 if (!p.empty()) op.affectedProperties.push_back(p);
135 op.mergeKey = "input-map:" + id_ + ":" + (p.empty() ? "structure" : p);
136 return eve::editing::applied<DomainOperation>(std::move(op));
137}
138Result<DomainOperation> InputMapTarget::makeSet(const SelectionSnapshot& s, const PropertyPath& p,
139 const EditorValue& v, PropertySetMode mode) const {
140 if (mode == PropertySetMode::Reset) return makeReset(s, p);
141 auto d = schema(s).find(p);
142 if (!matches(s) || !d || mode != PropertySetMode::Absolute || !validatePropertyValue(*d, v).ok())
143 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.input.set"),
144 "Input property edit is invalid");
145 auto c = *this;
146 for (const auto& i : s.items)
147 if (i.type == "input.action") {
148 auto& a = *std::find_if(c.actions_.begin(), c.actions_.end(),
149 [&](const auto& x) { return x.id.value() == i.item.value(); });
150 if (p == PropertyPath("action.name"))
151 a.name = *v.getIf<std::string>();
152 else
153 a.kind = *v.getIf<std::string>();
154 } else {
155 auto& b = *std::find_if(c.bindings_.begin(), c.bindings_.end(),
156 [&](const auto& x) { return x.id.value() == i.item.value(); });
157 if (p == PropertyPath("binding.action"))
158 b.action = ObjectId(*v.getIf<std::string>());
159 else if (p == PropertyPath("binding.device"))
160 b.device = *v.getIf<std::string>();
161 else if (p == PropertyPath("binding.control"))
162 b.control = *v.getIf<std::string>();
163 else if (p == PropertyPath("binding.scale"))
164 b.scale = *v.getIf<double>();
165 else if (p == PropertyPath("binding.deadzone"))
166 b.deadzone = *v.getIf<double>();
167 else
168 b.invert = *v.getIf<bool>();
169 }
170 if (errors(c.validate()))
171 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.input.invalid"),
172 "Input edit produces invalid bindings");
173 return replacement(c.contentValue(), p.value());
174}
175Result<DomainOperation> InputMapTarget::makeReset(const SelectionSnapshot& s, const PropertyPath& p) const {
176 auto d = schema(s).find(p);
177 if (!d)
178 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.input.property"),
179 "Unknown input property");
180 return makeSet(s, p, d->defaultValue, PropertySetMode::Absolute);
181}
182Result<DomainOperation> InputMapTarget::makeCreateAction(InputActionValue v) const {
183 auto c = *this;
184 c.actions_.push_back(std::move(v));
185 if (errors(c.validate()))
186 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.input.action"),
187 "Input action is invalid");
188 return replacement(c.contentValue());
189}
190Result<DomainOperation> InputMapTarget::makeDeleteAction(const ObjectId& id) const {
191 if (std::any_of(bindings_.begin(), bindings_.end(), [&](const auto& b) { return b.action == id; }))
192 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.input.action-referenced"),
193 "Delete bindings before deleting their action");
194 auto c = *this;
195 const auto before = c.actions_.size();
196 std::erase_if(c.actions_, [&](const auto& a) { return a.id == id; });
197 if (before == c.actions_.size())
198 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.input.action"),
199 "Input action does not exist");
200 return replacement(c.contentValue());
201}
202Result<DomainOperation> InputMapTarget::makeCreateBinding(InputBindingValue v) const {
203 auto c = *this;
204 c.bindings_.push_back(std::move(v));
205 if (errors(c.validate()))
206 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.input.binding"),
207 "Input binding is invalid");
208 return replacement(c.contentValue());
209}
210Result<DomainOperation> InputMapTarget::makeDeleteBinding(const ObjectId& id) const {
211 auto c = *this;
212 const auto before = c.bindings_.size();
213 std::erase_if(c.bindings_, [&](const auto& b) { return b.id == id; });
214 if (before == c.bindings_.size())
215 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.input.binding"),
216 "Input binding does not exist");
217 return replacement(c.contentValue());
218}
219std::vector<EditorDiagnostic> InputMapTarget::validate() const {
220 std::vector<EditorDiagnostic> d;
221 std::set<std::string> ids, names, controls;
222 for (const auto& a : actions_) {
223 if (a.id.empty() || a.name.empty() || !ids.insert(a.id.value()).second || !names.insert(a.name).second ||
224 (a.kind != "button" && a.kind != "axis1d"))
227 DiagnosticSeverity::Error, "Action IDs/names must be unique and kinds supported"));
228 }
229 for (const auto& b : bindings_) {
230 const bool target =
231 std::any_of(actions_.begin(), actions_.end(), [&](const auto& a) { return a.id == b.action; });
232 if (b.id.empty() || !ids.insert(b.id.value()).second || !target || b.control.empty() ||
233 !std::isfinite(b.scale) || b.scale == 0 || std::abs(b.scale) > 100 || !std::isfinite(b.deadzone) ||
234 b.deadzone < 0 || b.deadzone >= 1)
236 eve::DiagnosticCode::PreconditionViolation, RuleId("editor.input.binding"),
237 DiagnosticSeverity::Error, "Binding identity, reference or response is invalid"));
238 const std::string key = b.device + ":" + b.control;
239 if (!controls.insert(key).second)
241 eve::DiagnosticCode::Conflict, RuleId("editor.input.conflict"),
242 DiagnosticSeverity::Warning, "Control is bound more than once: " + key));
243 }
244 if (actions_.size() > 1024 || bindings_.size() > 8192)
247 DiagnosticSeverity::Error, "Input map exceeds action or binding budget"));
248 return d;
249}
250Result<void> InputMapTarget::applyDomainOperation(const DomainOperation& op) {
251 if (op.target != TargetId(id_) || op.type != "input-map.replace.v1" ||
252 !op.payload.isWithinLimits(5, 50000, 4 * 1024 * 1024))
253 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.input.operation"),
254 "Input map operation is invalid");
255 auto *av = field(op.payload, "actions"), *bv = field(op.payload, "bindings");
256 auto* aa = av ? av->getIf<EditorValue::Array>() : nullptr;
257 auto* ba = bv ? bv->getIf<EditorValue::Array>() : nullptr;
258 if (!aa || !ba || aa->size() > 1024 || ba->size() > 8192)
259 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.input.payload"),
260 "Input map payload exceeds limits");
261 InputMapTarget c(id_);
262 for (const auto& v : *aa) {
263 auto *i = field(v, "id"), *n = field(v, "name"), *k = field(v, "kind");
264 auto* is = i ? i->getIf<std::string>() : nullptr;
265 auto* ns = n ? n->getIf<std::string>() : nullptr;
266 auto* ks = k ? k->getIf<std::string>() : nullptr;
267 if (!is || !ns || !ks)
268 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.input.action"),
269 "Input action payload is invalid");
270 c.actions_.push_back({ObjectId(*is), *ns, *ks});
271 }
272 for (const auto& v : *ba) {
273 auto *i = field(v, "id"), *a = field(v, "action"), *dev = field(v, "device"), *ctl = field(v, "control"),
274 *scale = field(v, "scale"), *dead = field(v, "deadzone"), *inv = field(v, "invert");
275 auto* is = i ? i->getIf<std::string>() : nullptr;
276 auto* as = a ? a->getIf<std::string>() : nullptr;
277 auto* ds = dev ? dev->getIf<std::string>() : nullptr;
278 auto* cs = ctl ? ctl->getIf<std::string>() : nullptr;
279 auto* sn = scale ? scale->getIf<double>() : nullptr;
280 auto* dn = dead ? dead->getIf<double>() : nullptr;
281 auto* ib = inv ? inv->getIf<bool>() : nullptr;
282 if (!is || !as || !ds || !cs || !sn || !dn || !ib)
283 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.input.binding"),
284 "Input binding payload is invalid");
285 c.bindings_.push_back({ObjectId(*is), ObjectId(*as), *ds, *cs, *sn, *dn, *ib});
286 }
287 if (errors(c.validate()))
288 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.input.invalid"),
289 "Input map validation failed");
290 actions_ = std::move(c.actions_);
291 bindings_ = std::move(c.bindings_);
292 ++revision_;
293 dirty_.include(0, 0);
294 return eve::editing::applied<void>();
295}
296std::unique_ptr<IDomainOperationTarget> InputMapTarget::cloneDomainState() const {
297 return std::make_unique<InputMapTarget>(*this);
298}
299Result<void> InputMapTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget> c) {
300 auto* t = dynamic_cast<InputMapTarget*>(c.get());
301 if (!t || t->id_ != id_)
302 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.input.candidate"),
303 "Input candidate mismatch");
304 *this = *t;
305 return eve::editing::applied<void>();
306}
307EditorValue InputMapTarget::snapshotValue() const {
308 return EditorValue::Object{{"schemaVersion", int64_t{1}}, {"content", contentValue()}};
309}
310Result<void> InputMapTarget::loadSnapshot(const EditorValue& s) {
311 auto *v = field(s, "schemaVersion"), *c = field(s, "content");
312 auto* n = v ? v->getIf<int64_t>() : nullptr;
313 if (!n || *n != 1 || !c)
314 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.input.snapshot"),
315 "Unsupported input snapshot");
317 op.target = TargetId(id_);
318 op.type = "input-map.replace.v1";
319 op.payload = *c;
320 auto result = applyDomainOperation(op);
321 if (result.ok()) dirty_.clear();
322 return result;
323}
324Result<std::map<std::string, double>> InputMapEvaluator::evaluate(
325 const InputMapTarget& t, const std::vector<InputControlSample>& samples) const {
326 if (errors(t.validate()))
327 return eve::editing::failed<std::map<std::string, double>>(
328 EditorStatus::Rejected, RuleId("editor.input.invalid"), "Cannot evaluate invalid input map");
329 std::map<std::string, double> out;
330 for (const auto& a : t.actions()) out[a.name] = 0;
331 for (const auto& b : t.bindings()) {
332 auto s = std::find_if(samples.begin(), samples.end(),
333 [&](const auto& x) { return x.device == b.device && x.control == b.control; });
334 if (s == samples.end()) continue;
335 double value = std::clamp(s->value, -1.0, 1.0);
336 if (std::abs(value) <= b.deadzone)
337 value = 0;
338 else
339 value = std::copysign((std::abs(value) - b.deadzone) / (1 - b.deadzone), value);
340 if (b.invert) value = -value;
341 value *= b.scale;
342 auto a = std::find_if(t.actions().begin(), t.actions().end(), [&](const auto& x) { return x.id == b.action; });
343 if (a->kind == "button")
344 out[a->name] = std::max(out[a->name], std::abs(value) >= .5 ? 1.0 : 0.0);
345 else
346 out[a->name] = std::clamp(out[a->name] + value, -1.0, 1.0);
347 }
348 return eve::editing::applied<std::map<std::string, double>>(std::move(out));
349}
350void InputBindingCapture::begin(std::string device) {
351 active_ = true;
352 device_ = std::move(device);
353}
354void InputBindingCapture::cancel() {
355 active_ = false;
356 device_.clear();
357}
358std::optional<InputControlSample> InputBindingCapture::feed(InputControlSample s) {
359 if (!active_ || (!device_.empty() && s.device != device_) || s.control.empty() || !std::isfinite(s.value) ||
360 std::abs(s.value) < .5)
361 return {};
362 cancel();
363 return s;
364}
365} // namespace eve::input_editing
LogicalId target
double value
float x
Definition AnimClip.cpp:738
const std::string & s
glm::vec4 p[6]
vkb::Device & device
std::uint32_t key
glm::uvec4 ids
glm::vec3 n
Definition Grass.cpp:63
std::uint32_t aa
float v
std::int32_t c
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
eve::action::ActionVfxBinding binding
std::string action
Definition PlayHost.cpp:117
std::string id
Definition PlayHost.cpp:108
float begin
float d
float t
bool found
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
UI-independent property access for an immutable selection snapshot.
Dotted property path (layout.size) compared and persisted as text.
Definition EditingIds.h:78
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
EVENGINE_API_BACKENDS public API.
void * queryCapability(const CapabilityId &) override
Query an optional stable capability.
TargetDescriptor describe() const override
Describe the target for tools and automation.
InputMapTarget(std::string id)
Input map target.
StrongId< TargetIdTag > TargetId
Definition EditingIds.h:57
PropertyType
PropertyType public API.
PropertySetMode
PropertySetMode public API.
StrongId< CapabilityIdTag > CapabilityId
Definition EditingIds.h:61
Result< void > validatePropertyValue(const PropertyDescriptor &descriptor, const Value &value)
Validate property value.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
DomainOperation public API.
std::vector< std::string > affectedProperties
PropertyDescriptor public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
SelectionSnapshot public API.
Stable discovery metadata exposed by every editable target.
InputActionValue public API.
InputBindingValue public API.
InputControlSample public API.