Diagnostics: let an object report its condition to the host - #193
Open
jcelerier wants to merge 2 commits into
Open
Diagnostics: let an object report its condition to the host#193jcelerier wants to merge 2 commits into
jcelerier wants to merge 2 commits into
Conversation
Objects had no way to say "I am currently misconfigured". avnd::logger is a
stream of lines for the developer -- OFX draws the same line between its
"Log" and "Message" types -- and a state rendered as a stream becomes one
identical console line per cook.
TouchDesigner exposes exactly this as node state (getErrorString /
getWarningString / getInfoPopupString), stubbed empty in every binding until
now, and it feeds the Errors Dialog, the Error DAT, op.errors() and the OP
Execute DAT callbacks. Houdini, Nuke, Blender geometry nodes, OFX persistent
messages and Node-RED all model the same thing.
The object side is one member:
struct MyObject
{
halp::diagnostics diagnostics;
void operator()()
{
if(!connected)
diagnostics.warning("no sender on port {}", port);
}
};
Diagnostics are scoped to one run and cleared by the binding beforehand, as
TouchDesigner's own addError/addWarning are ("only valid while the operator
is cooking") and as Blender and Houdini work. That removes all the
bookkeeping: no identifiers to manage, no clear() to call, and no
thread-safety burden, since an object that learns something on a worker
thread already has to publish that state for its outputs.
An identifier can be attached for hosts that suppress or localise by id, for
structured codes, and for tests:
diagnostics.error<"port_in_use">("port {} is already in use", port);
REQUIRE(obj.diagnostics.has<"port_in_use">());
It is a template argument because a leading string would be ambiguous with
the format string, and compile-time because a stable id is by definition not
computed at runtime -- only a pointer to a literal is stored.
Fixed capacity throughout: raising a diagnostic never allocates and is usable
from an audio callback. Text is truncated at the capacity; when the entry
array is full the lowest severity is evicted, so the most serious condition
is never the one lost, and the drop count is reported.
Verified: entries, ids, has<>, clear, eviction under overflow and truncation
of an oversized message all behave; the POP binding compiles both for an
object that opts in and one that does not.
Still to do: the other TD processor families reuse diagnostics_state the same
way, and the stream backends (max, pd, clap, godot) should emit on change.
Design notes and the survey behind this are in AVENDISH_DIAGNOSTICS_DESIGN.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The TouchDesigner binding appended the "... and N more" notice to the error channel whichever severity had overflowed, so an object chatty enough to drop an informational message would turn its node red. The dropped severity is now tracked and the notice routed accordingly. Also bound MaxEntries with a static_assert -- count and the loop indices are uint8_t, so a larger instantiation would have silently misbehaved -- and widen the dropped counter, which wrapped to zero after 256 drops in one run and suppressed the notice entirely. Verified: infos overflowing keep severity info, an error losing its slot raises it to error, and clear() resets both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objects currently have no way to say "I am misconfigured right now".
avnd::loggeris a stream of lines aimed at the developer — OFX draws the sameline between its
LogandMessagetypes — and a state rendered as a streambecomes one identical console line per cook. TouchDesigner exposes exactly this
as node state (
getErrorString/getWarningString/getInfoPopupString),stubbed empty in every binding until now, and it feeds the Errors Dialog, the
Error DAT,
op.errors()and OP Execute DAT callbacks. Houdini, Nuke, Blendergeometry nodes, OFX persistent messages and Node-RED all model the same thing.
Object-side API
Diagnostics are scoped to one run and cleared by the binding beforehand,
exactly as TouchDesigner's own
addError/addWarningare ("only valid if addedwhile the operator is cooking") and as Blender and Houdini work. That is what
keeps the API this small — it removes:
clear()— not stating a condition is clearing ithas to publish that state for its outputs, and the cook reads it
Optional stable ids
For hosts that suppress or localise by id, for structured codes (GStreamer
domains), and for tests:
A template argument rather than a leading string, because
error("id", "fmt {}", x)is ambiguous witherror("fmt {}", "x"). Compile-timebecause a stable id is by definition not computed at runtime, so only a pointer
to a literal is stored. Users who do not want ids never type
<...>.Properties
from an audio callback.
severity is evicted, so the most serious condition is never the one lost,
and the drop count is reported.
info/warning/error/fatal.Contents
avnd/concepts/diagnostics.hpphas_diagnosticshalp/diagnostics.hppbasic_diagnostics<MaxEntries, Capacity>avnd/binding/touchdesigner/diagnostics.hppdiagnostics_state, reusable by every TD family.../pop/particle_processor.hppexamples/Helpers/Diagnostics.hppTested
Entries and severities, ids attached only where given,
has<>,clear(),eviction under overflow (12
info+ 1error→ the error survives), andtruncation of a 1000-char message into a 128-byte buffer. The POP binding
compiles both for an object that opts in and one that does not.
Not done here
diagnostics_statethe sameway — three lines each.
object_post/_warn/_error(x, …),Pd
logpost/pd_error(x, …), CLAPclap_host_log, Godotpush_error/push_warning.Surveying the field for this also turned up four independent one-line defects in
the existing loggers, which I have deliberately left out of this PR:
logger::log_func/error_funcarenullptrand never assigned anywhere in the tree, so every methodearly-returns.
error()routes to::bug(), which upstream Pdreserves for internal Pd faults; the object-facing call is
pd_error(x, …).NULLas the object, losing the console'sclick-to-highlight — the main reason
object_errortakes one.clap_host_logexists with afull severity enum, but the backend uses
halp::no_logger.Happy to fix those in a separate PR.
🤖 Generated with Claude Code