Message Management
HiNc applications use three independent message categories. Each category serves a distinct purpose and should not be mixed.
Categories
1. Diagnostic — IProgress<IMessage>
Operation-scoped progress and diagnostic messages. The caller provides an IProgress<IMessage> sink to the callee, which reports progress, warnings, and errors through it. Every IMessage carries a Severity, a Category, and a filterable id.
- Session-scoped: ShellProgress feeds the Session Message Panel; StepDiagnosticProgress retains step-anchored diagnostics and NcDiagnosticProgress retains NC-parsing diagnostics.
- XML IO chain: XFactory threads
IProgress<IMessage>through all deserialization calls so that parsing errors are reported to the caller rather than a global handler. - Script-level: ShellProgress exposes the session sink to user scripts.
- Per-call: inject a MessageCollector to buffer one call's messages and read them back afterwards (e.g., to return them inline in an HTTP response).
- Project IO: LoadProject and ReloadProject take a sink of their own, because project IO runs outside any session and none of the session sinks above exists while it runs. A load-time diagnostic — a referenced STL missing on disk, a child XML that will not deserialize — does not fail the load; the project comes up without that geometry and the call returns normally. Passing no sink leaves the application log as the only witness, and the caller sees an unqualified success.
Reporting to a sink and the log at once
An operation a caller may or may not be watching reports to both: always to the service ILogger, and additionally to an injected IProgress<IMessage> when there is one. EnableCollisionDetection, ResetRuntime and the two project-load entry points share that shape, and the parameter being optional is what lets a host with no caller to answer to — a script, a desktop shell — behave exactly as it did before the sink existed.
The counterpart obligation is that one operation reports one diagnostic once. Where a later stage re-reads what an earlier one has already reported on — the XML round trip that materialises execution equipment re-walks the sources the deserialization pass just read — that stage stays logger-only deliberately, so a caller collecting a project load does not receive every missing file twice.
Reporting helpers, and the args channel
Use the MessageUtil id-first helpers to report typed messages. Each is named {Category}{Severity} — SystemError, SystemWarning, ValidationWarning, ConfigurationWarning, … — takes the structured id first, and is null-safe on the sink.
Every helper has a {Category}{Severity}Fmt sibling that takes a FormattableString in place of the string. The sibling keeps the template and the values interpolated into it on the message itself, as GetFormat() and GetArgs(), so a consumer holding a translation of that template can re-render the message in another language instead of dropping the numbers. The notification stays the invariant English rendering, so a value carried in it never picks up a decimal separator from a locale. What a client does with the pair, and why the English template is load-bearing rather than descriptive, is under Internationalization.
progress.SystemErrorFmt("StlFile-Read--Failed", $"File Reading Failed: {path}");
Important
The sibling is opted into by name, and nothing reports its absence. An interpolated literal handed to the plain helper binds to string, is formatted on the spot, and the template is gone: the call compiles, reads identically at the site, and produces a message no client can translate. Report through Fmt wherever the text interpolates a value. Where it does not, stay on the plain helper — a client swaps a hole-less message by id alone, so giving it a template only adds a way for the equality gate to miss.
2. UI Error Notification — MessageBoardUtil
Toast-style popups for immediate user attention (e.g., “File saved”, “Load failed”). MessageBoardUtil triggers the ShowMessageBoard event consumed by the GUI layer.
Note
MessageBoardUtil is not yet mature for all scenarios. In practice, ILogger with level-filtered treatment is often applied instead.
3. App Log — ILogger
Standard .NET ILogger for application-level logging. Use ActionProgress<T>.FromLogger to bridge IProgress<object> APIs to an ILogger instance:
IProgress<object> progress = ActionProgress<object>.FromLogger(logger);
This routes each reported IMessage (or raw Exception) to the appropriate log level (LogError, LogWarning, LogInformation) based on its severity — with one precedence worth knowing: a message carrying an Exception as its detail goes to LogError with that exception attached whatever severity the message itself declares, so a warning that names an exception is logged as an error.
A null logger is accepted and becomes a no-op sink. A host built without dependency injection has none to hand over, and this bridge is reached from the error-reporting path itself, so a version that threw on a null logger would turn the first reported error into a crash inside the code meant to describe it.
Basic-Component / Utility Level
Low-level utilities (e.g., in Hi.Common, Hi.Geom) cannot assume which category the caller intends. These APIs accept Action<Exception> or IProgress<IMessage> as parameters so the caller decides how to handle messages:
await task.CatchExceptions(ex => progress?.Report(ex));
Design Rationale
Static/global message sinks mix the three categories, making it unclear whether a message is diagnostic, UI notification, or app log. The current pattern threads the handler explicitly through the call chain so each caller decides the appropriate category.
See Also
- About XML IO — the other cross-cutting service, and the one that threads a progress channel through deserialization
- ShellProgress — the session-scoped face of the diagnostic channel, as a script sees it