feat(registry): commands, watches, samples, the timeline and conditions
The app side of Crafter.Debug, minus the session:
- Command(name, description, handler): the handler takes nothing or
one argument struct and returns void, a value, Result<T> or
Async<T>. Arguments are decoded before it runs, so a mistyped call
is answered with every mistake and never reaches it; a throwing
handler becomes an Exception error instead of unwinding into the
frame loop. This replaces 3DForts' three-file pattern per verb.
- Watch(path, variable | getter [, setter]) with bounds, help and
read-only, and Sample(path, value) from any thread with a
frame-stamped history. Paths resolve into values ("bodies[1].pos.y")
and set the same way, writing a member writes the whole value back.
Unknown paths and commands get a suggestion.
- Group: registrations under a prefix that end with their owner. Not
movable, so neither is its owner: its watches refer to the owner's
members, and a move would leave them pointing at the old object.
- Log, LogWarn, LogError, Check and Emit on one frame-stamped
timeline, with source locations captured without macros. A check
failing every frame is counted every time but stored sparsely, and
the session's listener hears every failure.
- Condition: what `wait` and `pause --when` evaluate each frame. A
condition that cannot be decided reports why instead of being false,
which would make a wait hang.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
89a12bcd61
commit
3500b73ecf
12 changed files with 1829 additions and 7 deletions
51
interfaces/Crafter.Debug-Condition.cppm
Normal file
51
interfaces/Crafter.Debug-Condition.cppm
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// The conditions `crafter-debug wait` and `pause --when` take: comparisons of
|
||||
// watch paths with literals and with each other, combined with && || ! and
|
||||
// parentheses.
|
||||
//
|
||||
// fort1.core.hp <= 0
|
||||
// scene == "Game" && !debug.paused
|
||||
// bodies[3].pos.y < -10 || (lives == 0 && score > highScore)
|
||||
//
|
||||
// A lone operand tests truthiness: true, a nonzero number, a non-empty
|
||||
// string, array or object. Numbers compare by value, strings in byte order;
|
||||
// == and != work on any two values, and ordering a string against a number
|
||||
// is an error rather than false, so a typo in a condition is reported
|
||||
// instead of waiting forever. Parsed once when it arrives, evaluated on the
|
||||
// Pump thread every frame.
|
||||
export module Crafter.Debug:Condition;
|
||||
import std;
|
||||
import Crafter.Debug.Core;
|
||||
|
||||
export namespace Crafter::Debug {
|
||||
class ConditionError : public std::runtime_error {
|
||||
public:
|
||||
std::size_t offset;
|
||||
ConditionError(std::size_t offset, std::string_view what);
|
||||
};
|
||||
|
||||
class Condition {
|
||||
public:
|
||||
// Throws ConditionError, with the offset of the problem.
|
||||
static Condition Parse(std::string_view text);
|
||||
|
||||
// Looks a path up: a value, or why there is none.
|
||||
using Resolver = std::function<std::expected<Value, std::string>(std::string_view path)>;
|
||||
// True or false, or why it cannot be decided (an unknown path, or
|
||||
// operands that cannot be ordered).
|
||||
std::expected<bool, std::string> Evaluate(const Resolver& resolve) const;
|
||||
|
||||
const std::string& Text() const { return text; }
|
||||
// The paths the condition reads, in order of appearance.
|
||||
std::span<const std::string> Paths() const { return paths; }
|
||||
|
||||
struct Node;
|
||||
|
||||
private:
|
||||
std::string text;
|
||||
std::shared_ptr<const Node> root;
|
||||
std::vector<std::string> paths;
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue