SDK 1.0.0-pre.3: actionable diagnostics
Recommended host: EpicTuner 0.8.5 or later. C ABI 1.1 and all service layouts are unchanged. Existing compiled plugins receive the richer host diagnostics without recompiling. Recompile to use the new C++ source helpers.
Read the actual failure
Tools > Plugins now shows UTC timestamps and INFO/WARN/ERROR labels. Use Copy diagnostics to copy the plugin identity, version, error and retained log. The log is bounded; capture a failure promptly instead of flooding retries.
Host UI rejections include the symbolic result, operation, request ID, panel and instance key, and up to three affected node/property targets. Conflict messages include the submitted and current revision and whether the latest change came from an accepted plugin patch or a user event. Detail is bounded to 512 characters; long identifiers or many targets can be truncated. Patch values are not logged.
Example (illustrative IDs and revisions):
request=42 ui.patch ET_ERROR_CONFLICT(12) panel='org.example.loader.main/': submitted=6 current=7; last change: accepted plugin patch. Use completion.value.u64 after success or the latest event.revision; rebuild after user input, do not retry a stale patch.; targets=[model.options; model.value]
If the last change was accepted plugin patch, a previous successful update advanced the revision. A timer or completion callback must stop reusing the old event revision. If the last change was user event, recompute from that new event; retrying an old model can overwrite a user's newer choice. The last-change description reports the most recent mutation, not a complete revision history.
Log both immediate and asynchronous results
ET_OK returned by ui.patch means queued, not applied. An immediate failure
does not schedule a completion. Check both paths. Store Context by value (as
the starter does); the start() reference itself is temporary.
static void ET_CALL completed(void* owner, const et_completion* c) noexcept {
auto& self = *static_cast<MyPlugin*>(owner);
try {
self.context->check(*c, "Populate Model/Tune and preview");
if (c->code == ET_OK && c->value.kind == ET_VALUE_U64) {
// This callback belongs to one known panel. Never move backwards
// if a newer event for that same panel has already arrived.
self.revision = std::max(self.revision, uint32_t(c->value.data.u64));
}
} catch (...) { /* Never let exceptions cross the ABI. */ }
}
// On the dispatcher, with a persistent completion owner:
context->check(ui.patch(panel, changes, revision, 0, &completion), "Queue preview");
Context::check(const et_completion&, step) logs failed completions, including
request/operation ID, symbolic/numeric result, integer revision when present, and
the borrowed detail copied inside the callback. completionMessage(c, step)
returns the same string for your own logger. resultName(code) returns a stable
symbolic label, including ET_ERROR_UNKNOWN for unknown codes. These are header
helpers, not new ABI entry points. Declare the log capability for SDK logging.
For UI patch completions, ET_VALUE_U64 is the panel revision, including on a
conflict. Do not treat integer values from other services as UI revisions.
Use separate state and completion context for each panel; reset it on close or
reopen. A failed patch does not authorize replay against its reported revision.
Avoid repeated conflicts
- Prefer one atomic patch for related options, selected indexes and preview data.
- Synchronous patches inside one event are sequenced by host 0.8.4+ (including default origin 0). This does not extend to later timers or job completions.
- For deferred updates, allow one in-flight patch per panel, retain the latest event and completion revisions, and coalesce newer display state while waiting.
- On conflict, report it once and wait for/recompute from fresh state. Do not increment a guessed revision or repeatedly resubmit the same patch.
- A failed event-chain predecessor cancels its dependent patches. The cancellation names the predecessor request and preserves its error context.
- Immediate limit diagnostics report pending/rate budgets; reduce update volume. A timeout reports the request, operation and timeout before stopping the worker.
The starter and cascading-select examples demonstrate completion logging. See UI contracts and troubleshooting for lifecycle, dispatcher, ownership and validation rules.