EchoMap 2026-07-25 6d3977c
An experimental cross-platform digital signal processing application for sound-source localisation.
Loading...
Searching...
No Matches
EchoMap.cpp
Go to the documentation of this file.
1
7
8#include "EchoMap.hpp"
9
10#include <imgui_impl_glfw.h>
11#include <imgui_impl_wgpu.h>
12#include <imgui_internal.h>
13#include <implot.h>
14#include <implot3d.h>
15#include <sigc++/adaptors/bind.h>
16
17#include "RobotoMedium.hpp"
22#include "objects/Project.hpp"
23#include "objects/Sensor.hpp"
24#include "objects/Signal.hpp"
25#include "panels/ChannelMappingPanel.hpp"
26#include "panels/MenuPanel.hpp"
27#include "panels/ProjectPanel.hpp"
32#include "utility/Logger.hpp"
33
34#ifdef __EMSCRIPTEN__
36#endif
37
38namespace echomap
39{
40
42 window(create_window(
43 static_cast<int>(viewport_width),
44 static_cast<int>(viewport_height)
45 )),
46 worker{[] {
47#if !defined(__EMSCRIPTEN__) || defined(__DOXYGEN__)
48 glfwPostEmptyEvent();
49#endif
50 }},
51 dockspace_id(ImHashStr("MainDockSpace"))
52{
53 setup_subscriptions();
54
55 static constexpr auto timed_wait_any = wgpu::InstanceFeatureName::TimedWaitAny;
56 constexpr wgpu::InstanceDescriptor instance_desc{.requiredFeatureCount = 1, .requiredFeatures = &timed_wait_any};
57
58 instance = wgpu::CreateInstance(&instance_desc);
59
60 int actual_width = 0;
61 int actual_height = 0;
62 glfwGetFramebufferSize(window, &actual_width, &actual_height);
63 assert(actual_width >= 0);
64 assert(actual_height >= 0);
65 viewport_width = static_cast<std::uint32_t>(actual_width);
66 viewport_height = static_cast<std::uint32_t>(actual_height);
67
68 surface = SurfaceFactory::create_surface(instance, window);
69
70 if (const auto adapter_wait = instance.WaitAny(request_adapter(), operation_timeout);
71 adapter_wait != wgpu::WaitStatus::Success || !adapter)
72 throw ConfigurationError("WebGPU adapter request did not complete");
73
74 if (const auto device_wait = instance.WaitAny(request_device(), operation_timeout);
75 device_wait != wgpu::WaitStatus::Success || !device)
76 throw ConfigurationError("WebGPU device request did not complete");
77
78 surface.GetCapabilities(adapter, &surface_capabilities);
79 configure_surface(surface, device, surface_capabilities, viewport_width, viewport_height);
80
81 setup_imgui();
82
83 panels.push_back(std::make_unique<MenuPanel>());
84 panels.push_back(std::make_unique<ProjectPanel>());
85 panels.push_back(std::make_unique<SignalWaveformPanel>(&worker, despatcher));
86 panels.push_back(std::make_unique<SensorGeometryPanel>(this));
87 panels.push_back(std::make_unique<ChannelMappingPanel>(this));
88 panels.push_back(std::make_unique<SignalDFTPanel>(&worker, despatcher, this));
89}
90
92{
93 if (ImGui::GetCurrentContext() != nullptr) {
94 ImGui_ImplWGPU_Shutdown();
95 ImGui_ImplGlfw_Shutdown();
96 ImPlot3D::DestroyContext();
97 ImPlot::DestroyContext();
98 ImGui::DestroyContext();
99 }
100
101 if (surface) {
102 surface.Unconfigure();
103 surface = nullptr;
104 }
105
106 device = nullptr;
107 adapter = nullptr;
108 instance = nullptr;
109
110 if (window != nullptr) {
111 glfwDestroyWindow(window);
112 window = nullptr;
113 }
114
115 glfwTerminate();
116}
117
119 // ReSharper disable once CppDFAConstantParameter
120 const int width,
121 // ReSharper disable once CppDFAConstantParameter
122 const int height
123)
124{
125 if (glfwInit() == 0)
126 throw ConfigurationError("glfwInit failed");
127
128 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
129
130 auto* const window = glfwCreateWindow(width, height, "EchoMap", nullptr, nullptr);
131
132 if (window == nullptr) {
133 glfwTerminate();
134 throw ConfigurationError("glfwCreateWindow failed");
135 }
136
137 return window;
138}
139
141 const wgpu::Surface& surface,
142 const wgpu::Device& device,
143 const wgpu::SurfaceCapabilities& capabilities,
144 const std::uint32_t viewport_width,
145 const std::uint32_t viewport_height
146) noexcept
147{
148 const wgpu::SurfaceConfiguration config{
149 .device = device,
150 // ReSharper disable once CppDFAConstantConditions
151 // ReSharper disable once CppDFAUnreachableCode
152 .format = capabilities.formats != nullptr ? *capabilities.formats : wgpu::TextureFormat::RGBA8Snorm,
153 .usage = wgpu::TextureUsage::RenderAttachment,
154 .width = viewport_width,
155 .height = viewport_height,
156 // ReSharper disable once CppDFAConstantConditions
157 // ReSharper disable once CppDFAUnreachableCode
158 .alphaMode =
159 capabilities.alphaModes != nullptr ? *capabilities.alphaModes : wgpu::CompositeAlphaMode::Opaque,
160 .presentMode = wgpu::PresentMode::Fifo,
161 };
162
163 surface.Configure(&config);
164}
165
167{
168 // NOLINTBEGIN(*-redundant-casting) - False positive; casts are required for libsigcpp to resolve overloads.
169 connections.emplace_back(
170 despatcher.load_project_finished_channel.nominate_consumer(
171 sigc::mem_fun(*this, static_cast<void (EchoMap::*)(LoadProjectResult&&)>(&EchoMap::handle_result))
172 ));
173
174 connections.emplace_back(
175 despatcher.load_signal_file_channel.nominate_consumer(
176 sigc::mem_fun(*this, static_cast<void (EchoMap::*)(LoadSignalFileResult&&)>(&EchoMap::handle_result))
177 ));
178 // NOLINTEND(*-redundant-casting)
179
180 connections.emplace_back(despatcher.error_channel.observe([this](const ErrorResult& error) {
181 raise_error(error.what());
182 LOG_F_ERROR("Error modal raised due to error: {}", error.what());
183 }));
184}
185
186wgpu::Future EchoMap::request_adapter() noexcept
187{
188 const wgpu::RequestAdapterOptions options{.compatibleSurface = surface};
189
190 return instance.RequestAdapter(
191 &options,
192 wgpu::CallbackMode::WaitAnyOnly,
193 [this](const wgpu::RequestAdapterStatus status, wgpu::Adapter new_adapter, const wgpu::StringView message) {
194 if (status != wgpu::RequestAdapterStatus::Success)
196 Logger::Level::Error,
198 "WebGPU adapter request failed {}: {}.",
199 std::to_underlying(status),
200 std::string_view(message)
201 );
202 else
203 adapter = std::move(new_adapter);
204 }
205 );
206}
207
208wgpu::Future EchoMap::request_device() noexcept
209{
210 wgpu::DeviceDescriptor desc{};
211 desc.SetDeviceLostCallback(
212 wgpu::CallbackMode::AllowSpontaneous,
213 [](const wgpu::Device&, const wgpu::DeviceLostReason reason, const wgpu::StringView message) {
214 if (reason == wgpu::DeviceLostReason::Destroyed)
215 return;
216
218 Logger::Level::Warning,
220 "Device lost because of reason {}: {}.",
221 std::to_underlying(reason),
222 std::string_view(message)
223 );
224 }
225 );
226
227 desc.SetUncapturedErrorCallback(
228 [](const wgpu::Device&, const wgpu::ErrorType error_type, const wgpu::StringView message) {
230 Logger::Level::Error,
232 "WebGPU error {}: {}",
233 std::to_underlying(error_type),
234 std::string_view(message)
235 );
236 }
237 );
238
239 return adapter.RequestDevice(
240 &desc,
241 wgpu::CallbackMode::WaitAnyOnly,
242 [this](const wgpu::RequestDeviceStatus status, wgpu::Device new_device, const wgpu::StringView message) {
243 if (status != wgpu::RequestDeviceStatus::Success)
245 Logger::Level::Error,
247 "WebGPU device request failed: {}",
248 std::string_view(message)
249 );
250 else
251 device = std::move(new_device);
252 }
253 );
254}
255
256void EchoMap::render() noexcept
257{
258 // Process any events that have arrived since the last cycle.
261
262 // Handle system/graphics changes.
264 return;
265
266 wgpu::SurfaceTexture surface_texture{};
267 surface.GetCurrentTexture(&surface_texture);
268
269 switch (surface_texture.status) {
270 case wgpu::SurfaceGetCurrentTextureStatus::Error:
271 case wgpu::SurfaceGetCurrentTextureStatus::Lost:
272 case wgpu::SurfaceGetCurrentTextureStatus::Outdated:
273 surface.Unconfigure();
274 configure_surface(surface, device, surface_capabilities, viewport_width, viewport_height);
275 return;
276
277 case wgpu::SurfaceGetCurrentTextureStatus::Timeout:
278 return;
279
280 case wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal:
281 case wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal:
282 break;
283 }
284
285 const wgpu::TextureView surface_view = surface_texture.texture.CreateView();
286
287 ImGui_ImplWGPU_NewFrame();
288 ImGui_ImplGlfw_NewFrame();
289 ImGui::NewFrame();
290
291 setup_dockspace();
292
293 // Draw the panels and express any applicable error state.
294 for (const auto& panel : panels)
295 panel->draw();
296
297 if (active_modal != nullptr)
298 active_modal->draw();
299
300 if (error_modal.has_value())
301 error_modal->draw();
302
303 ImGui::Render();
304
305 // Set up a command encoder for the render and allow Dear ImGui panels to provide work.
306 const wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
307
308 // TODO no panels use GPU yet.
309
310 // Provide the framebuffer to the WebGPU driver.
311 wgpu::RenderPassColorAttachment const attachment{
312 .view = surface_view,
313 .loadOp = wgpu::LoadOp::Clear,
314 .storeOp = wgpu::StoreOp::Store,
315 .clearValue = {.r = 0.1, .g = 0.1, .b = 0.1, .a = 1.0},
316 };
317
318 const wgpu::RenderPassDescriptor pass_descriptor{.colorAttachmentCount = 1, .colorAttachments = &attachment};
319 const wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&pass_descriptor);
320
321 ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass.Get());
322 pass.End();
323
324 const wgpu::CommandBuffer commands = encoder.Finish();
325
326 // Submit batched work to the GPU.
327 device.GetQueue().Submit(1, &commands);
328
329#if !defined(__EMSCRIPTEN__) || defined(__DOXYGEN__)
330 // ReSharper disable once CppExpressionWithoutSideEffects
331 surface.Present();
332#endif
333}
334
335// ReSharper disable once CppMemberFunctionMayBeConst - Not semantically constant
337{
338 if (!device)
339 throw ConfigurationError("Cannot initialise ImGui: WebGPU device is null");
340
341 // Bring up the Dear ImGui context and initialise the GLFW backend.
342 IMGUI_CHECKVERSION();
343 ImGui::CreateContext();
344 ImPlot::CreateContext();
345 ImPlot3D::CreateContext();
346
347 auto& io = ImGui::GetIO();
348 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // NOLINT(*-signed-bitwise) - Required by ImGui API.
349 io.Fonts->AddFontFromMemoryCompressedTTF(
350 // ReSharper disable once CppRedundantCastExpression
351 static_cast<const void*>(data::RobotoMedium_compressed_data),
352 std::size(data::RobotoMedium_compressed_data),
353 14
354 );
355 ImGui::StyleColorsLight();
356
357 if (!ImGui_ImplGlfw_InitForOther(window, true))
358 throw ConfigurationError("ImGui_ImplGlfw_InitForOther failed");
359
360 // Configure the WebGPU backend for Dear ImGui.
361 ImGui_ImplWGPU_InitInfo init_info{};
362 init_info.Device = device.Get();
363 // ReSharper disable once CppDFAConstantConditions
364 // ReSharper disable once CppDFAUnreachableCode
365 init_info.RenderTargetFormat = static_cast<WGPUTextureFormat>(std::to_underlying(
366 (surface_capabilities.formats != nullptr) ? *surface_capabilities.formats : wgpu::TextureFormat::RGBA8Snorm
367 ));
368
369 if (!ImGui_ImplWGPU_Init(&init_info))
370 throw ConfigurationError("ImGui_ImplWGPU_Init failed");
371
372#if defined(__EMSCRIPTEN__) || defined(__DOXYGEN__)
373 /*
374 * If we're targeting WebAssembly, the window dimensions reported by GLFW should match the size of the canvas
375 * identified by the CSS selector <code>#canvas</code>. Dear ImGui provides the helper
376 * ImGui_ImplGlfw_InstallEmscriptenCallbacks to interface with the DOM and trigger re-draws as needed.
377 */
378 ImGui_ImplGlfw_InstallEmscriptenCallbacks(window, "#canvas");
379#endif
380}
381
382void EchoMap::setup_dockspace()
383{
384 const auto* const viewport = ImGui::GetMainViewport();
385
386 if (!dockspace_configured) {
387 dockspace_configured = true;
388
389 if (ImGui::DockBuilderGetNode(dockspace_id) == nullptr) {
390 // Root dockspace node.
391 ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
392 ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size);
393
394 // Left (narrow project explorer pane) and main workspace.
395 ImGuiID dock_id_left = 0;
396 ImGuiID dock_id_main = 0;
397 ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, .15f, &dock_id_left, &dock_id_main);
398 ImGui::DockBuilderDockWindow(ProjectPanel::get_imgui_stable_name(), dock_id_left);
399
400 // Upper/lower
401 ImGuiID dock_id_main_upper = 0;
402 ImGuiID dock_id_main_lower = 0;
403 ImGui::DockBuilderSplitNode(dock_id_main, ImGuiDir_Up, .33f, &dock_id_main_upper, &dock_id_main_lower);
404
405 // Upper left/right
406 ImGuiID dock_id_main_upper_left = 0;
407 ImGuiID dock_id_main_upper_right = 0;
408 ImGui::DockBuilderSplitNode(
409 dock_id_main_upper,
410 ImGuiDir_Left,
411 .5f,
412 &dock_id_main_upper_left,
413 &dock_id_main_upper_right
414 );
415 ImGui::DockBuilderDockWindow(ChannelMappingPanel::get_imgui_stable_name(), dock_id_main_upper_left);
416 ImGui::DockBuilderDockWindow(SensorGeometryPanel::get_imgui_stable_name(), dock_id_main_upper_right);
417
418 // Lower left/right
419 ImGuiID dock_id_main_lower_left = 0;
420 ImGuiID dock_id_main_lower_right = 0;
421 ImGui::DockBuilderSplitNode(
422 dock_id_main_lower,
423 ImGuiDir_Left,
424 .5f,
425 &dock_id_main_lower_left,
426 &dock_id_main_lower_right
427 );
428 ImGui::DockBuilderDockWindow(SignalWaveformPanel::get_imgui_stable_name(), dock_id_main_lower_left);
429 ImGui::DockBuilderDockWindow(SignalDFTPanel::get_imgui_stable_name(), dock_id_main_lower_right);
430
431 ImGui::DockBuilderFinish(dockspace_id);
432 }
433 }
434
435 ImGui::DockSpaceOverViewport(dockspace_id, viewport, ImGuiDockNodeFlags_PassthruCentralNode);
436}
437
438// ReSharper disable once CppDFAUnreachableFunctionCall
440{
441 int fb_width = 0;
442 int fb_height = 0;
443 glfwGetFramebufferSize(window, &fb_width, &fb_height);
444
445 if (fb_width <= 0 || fb_height <= 0)
446 // The window is too small or collapsed.
447 return false;
448
449 const auto new_width = static_cast<std::uint32_t>(fb_width);
450 // ReSharper disable once CppTooWideScopeInitStatement
451 const auto new_height = static_cast<std::uint32_t>(fb_height);
452
453 if (new_width != viewport_width || new_height != viewport_height) {
454 // The window has been resized, so update the WebGPU surface.
455
456 viewport_width = new_width;
457 viewport_height = new_height;
458
459 surface.Unconfigure();
460 configure_surface(surface, device, surface_capabilities, viewport_width, viewport_height);
461
462 // The window size has changed and the surface re-configured accordingly.
463 return true;
464 }
465
466 // The window size is unchanged.
467 return true;
468}
469
471{
472 while (!notification_queue.empty()) {
473 auto& notification = notification_queue.back();
474 const auto type_name = NotificationNames::indexed_names[notification.index()];
475 auto* const hint = static_cast<void*>(&notification);
476
477 LOG_F_DEBUG("Consuming {} with hint {}.", type_name, hint);
478
479 try {
480 visit_notification(notification);
481 } catch (const IgnoredWarning& warning) {
482 LOG_F_WARN("{} with hint {} was dropped: {}", type_name, hint, warning.what());
483 } catch (const std::exception& exception) {
484 raise_error(exception.what());
485 LOG_F_ERROR("{} with hint {} was responsible for error: {}", type_name, hint, exception.what());
486 }
487
488 notification_queue.pop_back();
489 }
490}
491
493{
494 while (auto result = worker.try_get_result())
495 try {
496 despatcher.publish(std::move(*result));
497 } catch (const std::exception& exception) {
498 Logger::log(Logger::Level::Error, exception.what(), std::source_location::current());
499 }
500}
501
502void EchoMap::raise_error(
503 const std::string_view message
504)
505{
506 error_modal.emplace(message, [this] {
507 notify(ClearErrorNotification{});
508 });
509}
510
511void EchoMap::raise_error(
512 const std::string_view message,
513 const std::runtime_error& exception
514)
515{
516 error_modal.emplace(message, exception, [this] {
517 notify(ClearErrorNotification{});
518 });
519}
520
521void EchoMap::handle_notification(
522 const AddChannelMappingNotification& notification
523) const
524{
525 notification.verify_project(project.get());
526 project->add_association(notification.signal_id, notification.sensor_id);
527}
528
529void EchoMap::handle_notification(
530 const ModifySensorColourNotification& notification
531) const
532{
533 notification.verify_project(project.get());
534 project->get_mutable_sensor(notification.sensor_id).set_colour(notification.colour);
535}
536
537void EchoMap::handle_notification(
538 const ModifySensorPositionNotification& notification
539) const
540{
541 notification.verify_project(project.get());
542 project->get_mutable_sensor(notification.sensor_id).set_position(notification.position);
543}
544
545void EchoMap::handle_notification(
546 const ProjectSelectionCompleteNotification& notification
547)
548{
549 active_modal.reset();
550
551 if (notification.path.has_value())
552 worker.submit(std::make_unique<LoadProjectTask>(*notification.path, &worker));
553}
554
555void EchoMap::handle_notification(
556 const ClearErrorNotification& notification
557)
558{
559 std::ignore = notification;
560 error_modal.reset();
561}
562
563void EchoMap::handle_result(
564 LoadProjectResult&& result
565)
566{
567 if (active_modal != nullptr) {
568 LOG_WARN("Ignoring request to change active Project since there is an active modal.");
569 return;
570 }
571
572 change_active_project(std::move(std::move(result).take_project()));
573}
574
575void EchoMap::handle_result(
576 LoadSignalFileResult&& result
577)
578{
579 if (project == nullptr || result.get_project_id() != project->get_id())
581 "Dropping LoadSignalFileResult, which was intended for the unavailable Project with ID {}.",
582 result.get_project_id()
583 );
584 else
585 for (auto&& signals = std::move(result).take_signals(); auto signal : signals | std::views::as_rvalue)
586 project->add_signal(std::move(signal));
587}
588
589void EchoMap::change_active_project(
590 std::unique_ptr<Project> new_project
591) noexcept
592{
593 if (new_project == nullptr)
594 LOG_DEBUG("Clearing the active project.");
595 else
596 LOG_F_DEBUG("Changing active project to {}.", new_project->get_name());
597
598 for (const auto& panel : panels)
599 panel->change_active_project(new_project.get());
600
601 project = std::move(new_project);
602}
603
605 const Notification& notification
606)
607{
608 notification_queue.emplace_back(notification);
609
610 /*
611 * The address is just a "hint" (as opposed to an ID) because the queue might be re-allocated. It's a best-guess
612 * effort to quickly discriminate o notification without adding bloat to their structures.
613 */
615 "Scheduling {} with hint {} at position {}.",
616 NotificationNames::indexed_names[notification_queue.back().index()],
617 static_cast<void*>(&notification_queue.back()),
618 notification_queue.size() - 1
619 );
620}
621
623 const unsigned int count
624) noexcept
625{
626 forced_frames += count;
627}
628
629} // namespace echomap
AllNotifications specification.
EchoMap ConfigurationError exception specification.
EchoMap class specification.
IgnoredWarning specification.
Project-loading task specification.
EchoMap portable logger specification.
#define LOG_F_DEBUG(msg,...)
Conditionally logs a formatted debug-level message using echomap::Logger::log_f.
Definition Logger.hpp:94
#define LOG_DEBUG(msg,...)
Conditionally logs an unformatted debug-level message using echomap::Logger::log.
Definition Logger.hpp:141
#define LOG_F_WARN(msg,...)
Logs a formatted warning-level message using echomap::Logger::log_f.
Definition Logger.hpp:115
#define LOG_F_ERROR(msg,...)
Logs a formatted error-level message using echomap::Logger::log_f.
Definition Logger.hpp:125
#define LOG_WARN(msg)
Logs an unformatted warning-level message using echomap::Logger::log.
Definition Logger.hpp:162
PartialProject specification.
Roboto Medium font as a C-style array.
EchoMap sensor geometry panel specification.
SignalDFTPanel specification.
EchoMap signal waveform preview panel specification.
Audio signal class specification.
EchoMap WebGPU-GLFW Surface factory specification.
A ConfigurationError indicates an error encountered during the initial configuration of the EchoMap g...
void increment_forced_frames(unsigned int count=4) noexcept
Indicate to the renderer that the following frames should always be rendered, regardless of whether t...
Definition EchoMap.cpp:622
void notify(const Notification &notification)
Submit a new Notification to the application queue.
Definition EchoMap.cpp:604
std::vector< sigc::scoped_connection > connections
RAII lifetime manager for signal connections.
Definition EchoMap.hpp:240
EchoMap()
Initialise a EchoMap application instance.
Definition EchoMap.cpp:41
void setup_subscriptions()
Configure the core signals for the application instance.
Definition EchoMap.cpp:166
void render() noexcept
Perform a render cycle on the configured Surface and Device.
Definition EchoMap.cpp:256
wgpu::Future request_device() noexcept
Produce a WebGPU Future for requesting an accelerator device.
Definition EchoMap.cpp:208
std::unique_ptr< IPanel > active_modal
The current active non-ErrorModal modal panel.
Definition EchoMap.hpp:246
virtual void visit_notification(Notification &notification)=0
Uses std::visit on the given notification to invoke the corresponding handler.
static void configure_surface(const wgpu::Surface &surface, const wgpu::Device &device, const wgpu::SurfaceCapabilities &capabilities, std::uint32_t viewport_width, std::uint32_t viewport_height) noexcept
Configure a WebGPU Surface from a static context given metadata and Adapter capabilities.
Definition EchoMap.cpp:140
void process_notifications()
Handle any unconsumed Notification objects from the queue.
Definition EchoMap.cpp:470
bool handle_window_resize() noexcept
Check if the window has been resized compared with the stored dimensions, updating member variables a...
Definition EchoMap.cpp:439
static GLFWwindow * create_window(int width, int height)
Create a new GLFW window of the specified dimensions from a static context.
Definition EchoMap.cpp:118
std::vector< std::unique_ptr< IProjectPanel > > panels
Individual display components.
Definition EchoMap.hpp:242
void setup_imgui()
Create a context for Dear ImGui and ImPlot, and configure the plain GLFW and WebGPU backends.
Definition EchoMap.cpp:336
void process_worker_results()
Handle any unconsumed events from the Worker.
Definition EchoMap.cpp:492
wgpu::Future request_adapter() noexcept
Produce a WebGPU Future for requesting an Adapter.
Definition EchoMap.cpp:186
Worker worker
Multi-threaded worker for scheduling heavy computation tasks.
Definition EchoMap.hpp:238
std::optional< ErrorModal > error_modal
Persistent panel to indicate errors over all other panels.
Definition EchoMap.hpp:243
virtual ~EchoMap() noexcept
Clean up all persistent state registered by the application instance.
Definition EchoMap.cpp:91
WorkerResultDespatcher despatcher
Despatcher to manage Worker result channels.
Definition EchoMap.hpp:239
Indicates that an ITask did not successfully complete.
Exception class to indicate a non-fatal warning indicating an exceptional case of dropping some notif...
Denotes a loaded Project completed by a LoadProjectTask job.
static void log(Level level, std::string_view message, std::source_location location=std::source_location::current())
Log an unformatted message to the backend.
Definition Logger.cpp:22
static void log_f(const Level level, const std::source_location location, std::format_string< Args... > fmt, Args &&... args)
Log a formatted message to the backend with a specific source location.
Definition Logger.hpp:61
T current(T... args)
std::variant< AddChannelMappingNotification, ModifySensorColourNotification, ModifySensorPositionNotification, ProjectSelectionCompleteNotification, ClearErrorNotification, RaiseFileChooserNotification, RegisterVFSMappingNotification, CompleteProjectLoadNotification, CancelProjectLoadNotification > Notification
A Notification is a trivial message sent exclusively to the EchoMap controller.
T make_unique(T... args)
The main EchoMap outermost namespace for all non-exported symbols.
T signal(T... args)
T size(T... args)
A notification indicating that any error state visible to the user should be reset.
static const std::array< std::string_view, std::variant_size_v< Notification > > indexed_names
Aggregated names by the index of the alternative.
T what(T... args)