Browse Source

Introduced warnings and made it possible to override message handling.

David Piuva 2 years ago
parent
commit
fdbd1cdefb

+ 27 - 2
Source/DFPSR/api/stringAPI.cpp

@@ -946,8 +946,33 @@ String& dsr::string_toStreamIndented(String& target, const uint8_t& value, const
 	return target;
 }
 
-void dsr::throwErrorMessage(const String& message) {
-	throw std::runtime_error(message.toStdString());
+static const std::function<void(const ReadableString &message, MessageType type)> defaultMessageAction = [](const ReadableString &message, MessageType type) {
+	if (type == MessageType::Error) {
+		throw std::runtime_error(message.toStdString());
+	} else {
+		message.toStream(std::cout);
+	}
+};
+
+static std::function<void(const ReadableString &message, MessageType type)> globalMessageAction = defaultMessageAction;
+
+void dsr::string_sendMessage(const ReadableString &message, MessageType type) {
+	globalMessageAction(message, type);
+	if (type == MessageType::Error) {
+		throw std::runtime_error("The message handler provided using string_assignMessageHandler did not throw an exception or terminate the program for the given error!\n");
+	}
+}
+
+void dsr::string_sendMessage_default(const ReadableString &message, MessageType type) {
+	defaultMessageAction(message, type);
+}
+
+void dsr::string_assignMessageHandler(std::function<void(const ReadableString &message, MessageType type)> newHandler) {
+	globalMessageAction = newHandler;
+}
+
+void dsr::string_unassignMessageHandler() {
+	globalMessageAction = defaultMessageAction;
 }
 
 void dsr::string_split_callback(std::function<void(ReadableString)> action, const ReadableString& source, DsrChar separator, bool removeWhiteSpace) {

+ 48 - 15
Source/DFPSR/api/stringAPI.h

@@ -371,17 +371,57 @@ inline String operator+ (const String& a, const ReadableString& b) { return stri
 inline String operator+ (const ReadableString& a, const String& b) { return string_combine(a, b); }
 
 
-// Methods used so often that they don't need to use the string_ prefix
+// ---------------- Message handling ----------------
 
 
-// Print information
+enum class MessageType {
+	Error, // Terminate as quickly as possible after saving and informing the user.
+	Warning, // Inform the user but let the caller continue.
+	StandardPrinting, // Print text to the terminal.
+	DebugPrinting // Print debug information to the terminal, if debug mode is active.
+};
+
+// Send a message
+void string_sendMessage(const ReadableString &message, MessageType type);
+// Send a message directly to the default message handler, ignoring string_assignMessageHandler.
+void string_sendMessage_default(const ReadableString &message, MessageType type);
+
+// Get a message
+// Pre-condition:
+//   The action function must throw an exception or terminate the program when given an error, otherwise string_sendMessage will throw an exception about failing to do so.
+//   Do not call string_sendMessage directly or indirectly from within action, use string_sendMessage_default instead to avoid infinite recursion.
+// Terminating the program as soon as possible is ideal, but one might want to save a backup or show what went wrong in a graphical interface before terminating.
+// Do not throw and catch errors as if they were warnings, because throwing and catching creates a partial transaction, potentially violating type invariants.
+//   Better to use warnings and let the sender of the warning figure out how to abort the action safely.
+void string_assignMessageHandler(std::function<void(const ReadableString &message, MessageType type)> action);
+
+// Undo string_assignMessageHandler, so that any messages will be handled the default way again.
+void string_unassignMessageHandler();
+
+// Throw an error, which must terminate the application or throw an error
+template<typename... ARGS>
+void throwError(ARGS... args) {
+	String result = string_combine(args...);
+	string_sendMessage(result, MessageType::Error);
+}
+
+// Send a warning, which might throw an exception, terminate the application or anything else that the application requests using string_handleMessages
+template<typename... ARGS>
+void sendWarning(ARGS... args) {
+	String result = string_combine(args...);
+	string_sendMessage(result, MessageType::Warning);
+}
+
+// Print information to the terminal or something else listening for messages using string_handleMessages
 template<typename... ARGS>
 void printText(ARGS... args) {
 	String result = string_combine(args...);
-	result.toStream(std::cout);
+	string_sendMessage(result, MessageType::StandardPrinting);
 }
 
-// Use for text printing that are useful when debugging but should not be given out in a release
+// Debug messages are automatically disabled in release mode, so that you don't have to worry about accidentally releasing a program with poor performance from constantly printing to the terminal
+//   Useful for selectively printing the most important information accumulated over time
+//   Less useful for profiling, because the debug mode is slower than the release mode
 #ifdef NDEBUG
 	// Supress debugText in release mode
 	template<typename... ARGS>
@@ -389,19 +429,12 @@ void printText(ARGS... args) {
 #else
 	// Print debugText in debug mode
 	template<typename... ARGS>
-	void debugText(ARGS... args) { printText(args...); }
+	void debugText(ARGS... args) {
+		String result = string_combine(args...);
+		string_sendMessage(result, MessageType::DebugPrinting);
+	}
 #endif
 
-// Raise an exception
-//   Only catch errors to display useful error messages, emergency backups or crash logs before terminating
-//   Further execution after a partial transaction will break object invariants
-void throwErrorMessage(const String& message);
-template<typename... ARGS>
-void throwError(ARGS... args) {
-	String result = string_combine(args...);
-	throwErrorMessage(result);
-}
-
 }
 
 #endif

+ 6 - 6
Source/DFPSR/gui/VisualComponent.cpp

@@ -248,11 +248,11 @@ void VisualComponent::drawOverlay(ImageRgbaU8& targetImage, const IVector2D &abs
 // Manual use with the correct type
 void VisualComponent::addChildComponent(std::shared_ptr<VisualComponent> child) {
 	if (!this->isContainer()) {
-		throwError(U"Cannot attach a child to a non-container parent component!\n");
+		sendWarning(U"Cannot attach a child to a non-container parent component!\n");
 	} else if (child.get() == this) {
-		throwError(U"Cannot attach a component to itself!\n");
+		sendWarning(U"Cannot attach a component to itself!\n");
 	} else if (child->hasChild(this)) {
-		throwError(U"Cannot attach to its own parent as a child component!\n");
+		sendWarning(U"Cannot attach to its own parent as a child component!\n");
 	} else {
 		// Remove from any previous parent
 		child->detachFromParent();
@@ -643,7 +643,7 @@ VisualTheme VisualComponent::getTheme() const {
 void VisualComponent::changedTheme(VisualTheme newTheme) {}
 
 String VisualComponent::call(const ReadableString &methodName, const ReadableString &arguments) {
-	throwError("Unimplemented custom call received");
+	sendWarning("Unimplemented custom call received");
 	return U"";
 }
 
@@ -673,8 +673,8 @@ MediaResult dsr::component_generateImage(VisualTheme theme, MediaMethod &method,
 			// Assigned by theme_assignMediaMachineArguments.
 		} else {
 			// TODO: Ask the theme for the argument using a specified style class for variations between different types of buttons, checkboxes, panels, et cetera.
-			//       Throw an exception if the theme did not provide an input argument to its own media function.
-			throwError(U"Unhandled setting \"", argumentName, U"\" requested by the media method \"", machine_getMethodName(machine, methodIndex), U"\" in the visual theme!\n");
+			//       Send a warning if the theme did not provide an input argument to its own media function.
+			sendWarning(U"Unhandled setting \"", argumentName, U"\" requested by the media method \"", machine_getMethodName(machine, methodIndex), U"\" in the visual theme!\n");
 		}
 	});
 }

+ 42 - 0
Source/SDK/guiExample/main.cpp

@@ -12,8 +12,35 @@ Component buttonAdd;
 Component myListBox;
 Component textElement;
 
+// Custom message handling
+List<String> messages;
+
+void showMessages() {
+	if (messages.length() > 0) {
+		// Summarizing all messages from the last action, which can also be used to display them in the same pop-up message.
+		String content;
+		string_append(content, U"Messages:\n");
+		for (int m = 0; m < messages.length(); m++) {
+			string_append(content, U"  * ", messages[m]);
+		}
+		string_append(content, U"\n");
+		string_sendMessage_default(content, MessageType::StandardPrinting);
+		messages.clear();
+	}
+}
+
 DSR_MAIN_CALLER(dsrMain)
 void dsrMain(List<String> args) {
+	// Assign custom message handling to get control over errors, warnings and any other text being printed to the terminal.
+	string_assignMessageHandler([](const ReadableString &message, MessageType type) {
+		// Deferring messages can be useful for showing them at a later time.
+		messages.push(message);
+		// A custom message handler still have to throw exceptions or terminate the program when errors are thrown.
+		if (type == MessageType::Error) {
+			string_sendMessage_default(message, MessageType::Error);
+		}
+	});
+
 	// Set current path to the application folder, so that it's safe to use relative paths for loading GUI resources.
 	// Loading and saving files will automatically convert / and \ to the local format using file_optimizePath, so that you can use them directly in relative paths.
 	file_setCurrentPath(file_getApplicationFolder());
@@ -27,6 +54,7 @@ void dsrMain(List<String> args) {
 
 	// Bind methods to events
 	window_setCloseEvent(window, []() {
+		sendWarning(U"Ahhh, you killed me! But closing a window directly is okay, because the program can run logic for saving things before terminating.");
 		running = false;
 	});
 
@@ -55,6 +83,13 @@ void dsrMain(List<String> args) {
 			}
 		}
 	});
+
+	// Connect actions to components without saving their handles
+	component_setPressedEvent(window_findComponentByName(window, U"menuExit"), []() {
+		sendWarning(U"You forgot to save your project and now I'm throwing it away because you forgot to save!");
+		running = false;
+	});
+
 	// Called when the selected index has changed, when indices have changed their meaning
 	//   Triggered by mouse, keyboard, list changes and initialization
 	component_setSelectEvent(myListBox, [](int64_t index) {
@@ -77,9 +112,16 @@ void dsrMain(List<String> args) {
 		}
 		// Busy loop instead of waiting
 		//window_executeEvents(window);
+		// Custom message handling
+		showMessages();
 		// Draw interface
 		window_drawComponents(window);
 		// Show the final image
 		window_showCanvas(window);
 	}
+
+	// Empty the messages and switch back to the default message handler so that errors from deallocating global resources can be displayed
+	showMessages();
+	string_unassignMessageHandler();
+	printText(U"Printing text using the default message handler again.\n");
 }

+ 14 - 7
Source/soundManagers/AlsaSound.cpp

@@ -38,35 +38,42 @@ bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(Saf
 	int errorCode;
 	if ((errorCode = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
 		terminateSound();
-		throwError("Cannot open sound device. (", snd_strerror(errorCode), ")\n");
+		sendWarning("Cannot open sound device. (", snd_strerror(errorCode), ")\n");
+		return false;
 	}
 	snd_pcm_hw_params_t *hardwareParameters;
 	snd_pcm_hw_params_alloca(&hardwareParameters);
 	snd_pcm_hw_params_any(pcm, hardwareParameters);
 	if ((errorCode = snd_pcm_hw_params_set_access(pcm, hardwareParameters, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
 		terminateSound();
-		throwError("Failed to select interleaved sound. (", snd_strerror(errorCode), ")\n");
+		sendWarning("Failed to select interleaved sound. (", snd_strerror(errorCode), ")\n");
+		return false;
 	}
 	if ((errorCode = snd_pcm_hw_params_set_format(pcm, hardwareParameters, SND_PCM_FORMAT_S16_LE)) < 0) {
 		terminateSound();
-		throwError("Failed to select sound format. (", snd_strerror(errorCode), ")\n");
+		sendWarning("Failed to select sound format. (", snd_strerror(errorCode), ")\n");
+		return false;
 	}
 	if ((errorCode = snd_pcm_hw_params_set_channels(pcm, hardwareParameters, channels)) < 0) {
 		terminateSound();
-		throwError("Failed to select channel count. (", snd_strerror(errorCode), ")\n");
+		sendWarning("Failed to select channel count. (", snd_strerror(errorCode), ")\n");
+		return false;
 	}
 	if ((errorCode = snd_pcm_hw_params_set_buffer_size(pcm, hardwareParameters, 2048)) < 0) {
 		terminateSound();
-		throwError("Failed to select buffer size. (", snd_strerror(errorCode), ")\n");
+		sendWarning("Failed to select buffer size. (", snd_strerror(errorCode), ")\n");
+		return false;
 	}
 	uint rate = sampleRate;
 	if ((errorCode = snd_pcm_hw_params_set_rate_near(pcm, hardwareParameters, &rate, 0)) < 0) {
 		terminateSound();
-		throwError("Failed to select approximate sample rate. (", snd_strerror(errorCode), ")\n");
+		sendWarning("Failed to select approximate sample rate. (", snd_strerror(errorCode), ")\n");
+		return false;
 	}
 	if ((errorCode = snd_pcm_hw_params(pcm, hardwareParameters)) < 0) {
 		terminateSound();
-		throwError("Failed to select hardware parameters. (", snd_strerror(errorCode), ")\n");
+		sendWarning("Failed to select hardware parameters. (", snd_strerror(errorCode), ")\n");
+		return false;
 	}
 	// Allocate a buffer for sending data to speakers
 	snd_pcm_uframes_t samplesPerChannel;

+ 11 - 5
Source/soundManagers/WinMMSound.cpp

@@ -55,7 +55,8 @@ bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(Saf
 	bufferEndEvent = CreateEvent(0, FALSE, FALSE, 0);
 	if (bufferEndEvent == 0) {
 		terminateSound();
-		throwError(U"Failed to create buffer end event!");
+		sendWarning(U"Failed to create buffer end event!");
+		return false;
 	}
 	int totalSamples = samplesPerChannel * channels;
 	allocateBuffers(totalSamples);
@@ -70,11 +71,13 @@ bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(Saf
 	format.cbSize = 0;
 	if(waveOutOpen(&waveOutput, WAVE_MAPPER, &format, (DWORD_PTR)bufferEndEvent, (DWORD_PTR)NULL, CALLBACK_EVENT) != MMSYSERR_NOERROR) {
 		terminateSound();
-		throwError(U"Failed to open wave output!");
+		sendWarning(U"Failed to open wave output!");
+		return false;
 	}
 	if(waveOutSetVolume(waveOutput, 0xFFFFFFFF) != MMSYSERR_NOERROR) {
 		terminateSound();
-		throwError(U"Failed to set volume!");
+		sendWarning(U"Failed to set volume!");
+		return false;
 	}
 	for (int b = 0; b < 2; b++) {
 		ZeroMemory(&header[b], sizeof(WAVEHDR));
@@ -82,7 +85,8 @@ bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(Saf
 		header[b].lpData = (LPSTR)(outputData[b].getUnsafe());
 		if (waveOutPrepareHeader(waveOutput, &header[b], sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
 			terminateSound();
-			throwError(U"Failed to prepare buffer for streaming!");
+			sendWarning(U"Failed to prepare buffer for streaming!");
+			return false;
 		}
 	}
 	running = true;
@@ -118,7 +122,9 @@ bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(Saf
 					target[t+7] = (int16_t)upper.w;
 				}
 				if (waveOutWrite(waveOutput, &header[b], sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
-					terminateSound(); throwError(U"Failed to write wave output!");
+					terminateSound();
+					sendWarning(U"Failed to write wave output!");
+					return false;
 				}
 				if (!running) { break; }
 			}

+ 1 - 1
Source/windowManagers/NoWindow.cpp

@@ -3,7 +3,7 @@
 #include "../DFPSR/api/stringAPI.h"
 
 std::shared_ptr<dsr::BackendWindow> createBackendWindow(const dsr::String& title, int width, int height) {
-	dsr::throwError("Tried to create a DsrWindow without a window manager selected!\n");
+	dsr::sendWarning("Tried to create a DsrWindow without a window manager selected!\n");
 	return std::shared_ptr<dsr::BackendWindow>();
 }
 

+ 1 - 1
Source/windowManagers/X11Window.cpp

@@ -705,7 +705,7 @@ std::shared_ptr<dsr::BackendWindow> createBackendWindow(const dsr::String& title
 		auto backend = std::make_shared<X11Window>(title, width, height);
 		return std::dynamic_pointer_cast<dsr::BackendWindow>(backend);
 	} else {
-		dsr::printText("No display detected. Aborting X11 window creation.\n");
+		dsr::sendWarning("No display detected. Aborting X11 window creation.\n");
 		return std::shared_ptr<dsr::BackendWindow>();
 	}
 }